diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-11-02 22:44:12 +0100 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-11-02 22:44:12 +0100 |
commit | c8d5977bb546dae9ed59d81556639c49badd8121 (patch) | |
tree | 4c86750db26c1c3502b60f2cd78ca9611cfa01d6 /src | |
download | knot-c8d5977bb546dae9ed59d81556639c49badd8121.tar.gz |
Imported Upstream version 0.8.0~pre1upstream/0.8.0_pre1
Diffstat (limited to 'src')
256 files changed, 86269 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..3f65566 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,336 @@ +ACLOCAL_AMFLAGS = -I ../m4 +libexec_PROGRAMS = knot-zcompile unittests unittests-zcompile unittests-libknot-realdata unittests-libknot +sbin_PROGRAMS = knotc knotd +MANPAGES = knotc.8 knotd.8 +man8_MANS = knotc.8 knotd.8 +EXTRA_DIST = $(man8_MANS) + +# $(YACC) will generate header file +AM_CFLAGS = -Wall -Ilibknot -DLIBEXECDIR='"$(libexecdir)"' -DSYSCONFDIR='"$(sysconfdir)"' -DSBINDIR='"$(sbindir)"' +AM_YFLAGS = -d +libknotd_la_YFLAGS = -pcf_ -d +libknotd_la_LFLAGS = # TODO: reentrant parser, prefix + +BUILT_SOURCES = \ + tests/libknot/parsed_data.rc \ + tests/libknot/realdata/parsed_data.rc \ + tests/libknot/raw_data_queries.rc \ + tests/libknot/raw_data.rc \ + tests/libknot/realdata/raw_data.rc \ + tests/libknot/parsed_data_queries.rc \ + tests/sample_conf.rc \ + zparser.h \ + zparser.c \ + zlexer.c \ + libknotd_la-cf-lex.c \ + libknotd_la-cf-parse.c \ + libknotd_la-cf-parse.h + +CLEANFILES = \ + tests/libknot/parsed_data.rc \ + tests/libknot/realdata/parsed_data.rc \ + tests/libknot/raw_data_queries.rc \ + tests/libknot/raw_data.rc \ + tests/libknot/realdata/raw_data.rc \ + tests/libknot/parsed_data_queries.rc \ + tests/sample_conf.rc \ + zparser.h \ + zparser.c \ + zlexer.c \ + libknotd_la-cf-lex.c \ + libknotd_la-cf-parse.c \ + libknotd_la-cf-parse.h + +knotc_SOURCES = \ + knot/ctl/knotc_main.c + +knot_zcompile_SOURCES = \ + zcompile/zcompile_main.c \ + zcompile/zcompile-error.c \ + zcompile/parser-util.h \ + zcompile/parser-descriptor.h \ + zcompile/zparser.y \ + zcompile/zlexer.l \ + zcompile/zcompile.c \ + zcompile/parser-util.c \ + zcompile/parser-descriptor.c + +unittests_SOURCES = \ + tests/common/acl_tests.c \ + tests/common/acl_tests.h \ + tests/common/da_tests.c \ + tests/common/da_tests.h \ + tests/common/events_tests.c \ + tests/common/events_tests.h \ + tests/common/skiplist_tests.c \ + tests/common/skiplist_tests.h \ + tests/common/slab_tests.c \ + tests/common/slab_tests.h \ + tests/common/fdset_tests.c \ + tests/common/fdset_tests.h \ + tests/knot/conf_tests.c \ + tests/knot/conf_tests.h \ + tests/knot/dthreads_tests.c \ + tests/knot/dthreads_tests.h \ + tests/knot/journal_tests.c \ + tests/knot/journal_tests.h \ + tests/knot/server_tests.c \ + tests/knot/server_tests.h \ + tests/unittests_main.c + +unittests_libknot_realdata_SOURCES = \ + tests/libknot/realdata/libknot/dname_tests_realdata.c \ + tests/libknot/realdata/libknot/response_tests_realdata.c \ + tests/libknot/realdata/libknot/edns_tests_realdata.c \ + tests/libknot/realdata/libknot/node_tests_realdata.c \ + tests/libknot/realdata/libknot/rdata_tests_realdata.c \ + tests/libknot/realdata/libknot/rrset_tests_realdata.c \ + tests/libknot/realdata/libknot/zone_tests_realdata.c \ + tests/libknot/realdata/libknot/zonedb_tests_realdata.c \ + tests/libknot/realdata/libknot/packet_tests_realdata.h \ + tests/libknot/realdata/libknot/packet_tests_realdata.c \ + tests/libknot/realdata/libknot_tests_loader_realdata.h \ + tests/libknot/realdata/libknot_tests_loader_realdata.c \ + tests/libknot/realdata/unittests_libknot_realdata.c + +unittests_libknot_SOURCES = \ + tests/libknot/libknot/cuckoo_tests.c \ + tests/libknot/libknot/cuckoo_tests.h \ + tests/libknot/libknot/response_tests.h \ + tests/libknot/libknot/response_tests.c \ + tests/libknot/libknot/dname_tests.c \ + tests/libknot/libknot/dname_tests.h \ + tests/libknot/libknot/dname_table_tests.h \ + tests/libknot/libknot/dname_table_tests.c \ + tests/libknot/libknot/nsec3_tests.h \ + tests/libknot/libknot/nsec3_tests.c \ + tests/libknot/libknot/packet_tests.h \ + tests/libknot/libknot/packet_tests.c \ + tests/libknot/libknot/query_tests.h \ + tests/libknot/libknot/query_tests.c \ + tests/libknot/libknot/edns_tests.c \ + tests/libknot/libknot/edns_tests.h \ + tests/libknot/libknot/node_tests.c \ + tests/libknot/libknot/node_tests.h \ + tests/libknot/libknot/rdata_tests.c \ + tests/libknot/libknot/rdata_tests.h \ + tests/libknot/libknot/rrset_tests.c \ + tests/libknot/libknot/rrset_tests.h \ + tests/libknot/libknot/zone_tests.c \ + tests/libknot/libknot/zone_tests.h \ + tests/libknot/libknot/zone_tree_tests.h\ + tests/libknot/libknot/zone_tree_tests.c \ + tests/libknot/libknot/zonedb_tests.c \ + tests/libknot/libknot/zonedb_tests.h \ + tests/libknot/unittests_libknot.c + +unittests_zcompile_SOURCES = \ + zcompile/parser-util.h \ + zcompile/parser-descriptor.h \ + zcompile/zcompile-error.c \ + zcompile/zparser.y \ + zcompile/zlexer.l \ + zcompile/zcompile.c \ + zcompile/parser-util.c \ + zcompile/parser-descriptor.c \ + zcompile/tests/unittests_zp_main.c + +nodist_unittests_SOURCES = \ + tests/libknot/parsed_data.rc \ + tests/libknot/raw_data_queries.rc \ + tests/libknot/raw_data.rc \ + tests/libknot/parsed_data_queries.rc \ + tests/sample_conf.rc + +knotd_SOURCES = \ + knot/main.c + +noinst_LTLIBRARIES = libknot.la libknotd.la libknots.la + +libknot_la_SOURCES = \ + libknot/util/libknot_error.c \ + libknot/util/utils.c \ + libknot/util/debug.c \ + libknot/util/debug.h \ + libknot/util/utils.h \ + libknot/util/descriptor.c \ + libknot/util/tolower.h \ + libknot/util/tolower.c \ + libknot/util/descriptor.h \ + libknot/util/wire.h \ + libknot/packet/query.c \ + libknot/packet/response.c \ + libknot/packet/packet.c \ + libknot/packet/packet.h \ + libknot/packet/query.h \ + libknot/packet/response.h \ + libknot/zone/zone.c \ + libknot/zone/zone-contents.c \ + libknot/zone/zone-tree.c \ + libknot/zone/zone-tree.h \ + libknot/zone/node.h \ + libknot/zone/zone.h \ + libknot/zone/zone-contents.h \ + libknot/zone/zonedb.c \ + libknot/zone/zonedb.h \ + libknot/zone/node.c \ + libknot/zone/dname-table.h \ + libknot/zone/dname-table.c \ + libknot/hash/hash-functions.c \ + libknot/hash/cuckoo-hash-table.c \ + libknot/hash/universal-system.c \ + libknot/hash/universal-system.h \ + libknot/hash/cuckoo-hash-table.h \ + libknot/hash/hash-functions.h \ + libknot/nameserver/name-server.h \ + libknot/nameserver/name-server.c \ + libknot/updates/changesets.h \ + libknot/updates/changesets.c \ + libknot/updates/xfr-in.h \ + libknot/updates/xfr-in.c \ + libknot/updates/ddns.h \ + libknot/updates/ddns.c \ + libknot/edns.c \ + libknot/rrset.c \ + libknot/dname.c \ + libknot/rdata.c \ + libknot/nsec3.c \ + libknot/consts.h \ + libknot/edns.h \ + libknot/rdata.h \ + libknot/libknot.h \ + libknot/dname.h \ + libknot/rrset.h \ + libknot/nsec3.h \ + libknot/tsig.h \ + libknot/tsig.c \ + libknot/tsig-op.h \ + libknot/tsig-op.c + +libknots_la_SOURCES = \ + common/slab/slab.c \ + common/slab/malloc.c \ + common/slab/slab.h \ + common/slab/malloc.h \ + common/libtap/tap.c \ + common/libtap/tap.h \ + common/libtap/tap_unit.h \ + common/lists.c \ + common/base32.c \ + common/lists.h \ + common/base32.h \ + common/print.c \ + common/print.h \ + common/dynamic-array.c \ + common/skip-list.c \ + common/base32hex.c \ + common/skip-list.h \ + common/general-tree.h \ + common/general-tree.c \ + common/dynamic-array.h \ + common/tree.h \ + common/base32hex.h \ + common/evqueue.h \ + common/evqueue.c \ + common/evsched.h \ + common/evsched.c \ + common/acl.h \ + common/acl.c \ + common/sockaddr.h \ + common/sockaddr.c \ + common/crc.h \ + common/crc.c \ + common/ref.h \ + common/ref.c \ + common/errors.h \ + common/errors.c \ + common/WELL1024a.h \ + common/WELL1024a.c \ + common/fdset.h \ + common/fdset.c \ + common/fdset_poll.h \ + common/fdset_poll.c \ + common/fdset_kqueue.h \ + common/fdset_kqueue.c \ + common/fdset_epoll.h \ + common/fdset_epoll.c + +libknotd_la_SOURCES = \ + knot/stat/gatherer.c \ + knot/stat/stat.c \ + knot/stat/gatherer.h \ + knot/stat/stat.h \ + knot/common.h \ + knot/other/log.c \ + knot/other/log.h \ + knot/other/debug.h \ + knot/other/error.h \ + knot/other/error.c \ + knot/conf/cf-parse.y \ + knot/conf/cf-lex.l \ + knot/conf/conf.c \ + knot/conf/logconf.c \ + knot/conf/logconf.h \ + knot/conf/conf.h \ + knot/ctl/process.c \ + knot/ctl/process.h \ + knot/server/dthreads.c \ + knot/server/journal.c \ + knot/server/socket.c \ + knot/server/server.c \ + knot/server/udp-handler.c \ + knot/server/tcp-handler.c \ + knot/server/xfr-handler.c \ + knot/server/zones.c \ + knot/server/socket.h \ + knot/server/udp-handler.h \ + knot/server/tcp-handler.h \ + knot/server/xfr-handler.h \ + knot/server/dthreads.h \ + knot/server/journal.h \ + knot/server/zones.h \ + knot/server/notify.h \ + knot/server/notify.c \ + knot/server/zones.h \ + knot/zone/zone-load.c \ + knot/zone/zone-load.h \ + knot/zone/zone-dump.c \ + knot/zone/zone-dump-text.c \ + knot/zone/zone-dump-text.h \ + knot/zone/zone-dump.h \ + knot/server/server.h + +libknotd_la_LIBADD = ../libknot/libknot.la libknots.la @LIBOBJS@ +libknots_la_LIBADD = @LIBOBJS@ +knotd_LDADD = libknotd.la ../libknot/libknot.la libknots.la @LIBOBJS@ +knotc_LDADD = libknotd.la ../libknot/libknot.la libknots.la @LIBOBJS@ +knot_zcompile_LDADD = libknots.la ../libknot/libknot.la libknotd.la @LIBOBJS@ +unittests_LDADD = libknotd.la libknots.la @LIBOBJS@ +unittests_zcompile_LDADD = ../libknot/libknot.la libknots.la libknotd.la @LIBOBJS@ +unittests_libknot_LDADD = ../libknot/libknot.la libknots.la @LIBOBJS@ +unittests_libknot_realdata_LDADD = ../libknot/libknot.la libknots.la @LIBOBJS@ + +# automake complains on % rules: +# `%'-style pattern rules are a GNU make extension + +tests/libknot/parsed_data.rc: tests/libknot/files/parsed_data + ../resource.sh tests/libknot/files/parsed_data >$@ + +tests/libknot/realdata/parsed_data.rc: tests/libknot/realdata/files/parsed_data + ../resource.sh tests/libknot/realdata/files/parsed_data >$@ + +tests/libknot/parsed_data_queries.rc: tests/libknot/files/parsed_data_queries + ../resource.sh tests/libknot/files/parsed_data_queries >$@ + +tests/libknot/raw_data_queries.rc: tests/libknot/files/raw_data_queries + ../resource.sh tests/libknot/files/raw_data_queries >$@ + +tests/libknot/raw_data.rc: tests/libknot/files/raw_data + ../resource.sh tests/libknot/files/raw_data >$@ + +tests/libknot/realdata/raw_data.rc: tests/libknot/realdata/files/raw_data + ../resource.sh tests/libknot/realdata/files/raw_data >$@ + +tests/sample_conf.rc: tests/files/sample_conf + ../resource.sh tests/files/sample_conf >$@ + diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..7811da5 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,2396 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +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@ +libexec_PROGRAMS = knot-zcompile$(EXEEXT) unittests$(EXEEXT) \ + unittests-zcompile$(EXEEXT) \ + unittests-libknot-realdata$(EXEEXT) unittests-libknot$(EXEEXT) +sbin_PROGRAMS = knotc$(EXEEXT) knotd$(EXEEXT) +subdir = src +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in \ + $(srcdir)/config.h.in libknotd_la-cf-lex.c \ + libknotd_la-cf-parse.c libknotd_la-cf-parse.h zlexer.c \ + zparser.c zparser.h +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_compiler_flags.m4 \ + $(top_srcdir)/m4/ax_ext.m4 \ + $(top_srcdir)/m4/ax_gcc_x86_cpuid.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)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libknot_la_LIBADD = +am_libknot_la_OBJECTS = libknot_error.lo utils.lo debug.lo \ + descriptor.lo tolower.lo query.lo response.lo packet.lo \ + zone.lo zone-contents.lo zone-tree.lo zonedb.lo node.lo \ + dname-table.lo hash-functions.lo cuckoo-hash-table.lo \ + universal-system.lo name-server.lo changesets.lo xfr-in.lo \ + ddns.lo edns.lo rrset.lo dname.lo rdata.lo nsec3.lo tsig.lo \ + tsig-op.lo +libknot_la_OBJECTS = $(am_libknot_la_OBJECTS) +libknotd_la_DEPENDENCIES = ../libknot/libknot.la libknots.la @LIBOBJS@ +am_libknotd_la_OBJECTS = gatherer.lo stat.lo log.lo error.lo \ + libknotd_la-cf-parse.lo libknotd_la-cf-lex.lo conf.lo \ + logconf.lo process.lo dthreads.lo journal.lo socket.lo \ + server.lo udp-handler.lo tcp-handler.lo xfr-handler.lo \ + zones.lo notify.lo zone-load.lo zone-dump.lo zone-dump-text.lo +libknotd_la_OBJECTS = $(am_libknotd_la_OBJECTS) +libknots_la_DEPENDENCIES = @LIBOBJS@ +am_libknots_la_OBJECTS = slab.lo malloc.lo tap.lo lists.lo base32.lo \ + print.lo dynamic-array.lo skip-list.lo base32hex.lo \ + general-tree.lo evqueue.lo evsched.lo acl.lo sockaddr.lo \ + crc.lo ref.lo errors.lo WELL1024a.lo fdset.lo fdset_poll.lo \ + fdset_kqueue.lo fdset_epoll.lo +libknots_la_OBJECTS = $(am_libknots_la_OBJECTS) +am__installdirs = "$(DESTDIR)$(libexecdir)" "$(DESTDIR)$(sbindir)" \ + "$(DESTDIR)$(man8dir)" +PROGRAMS = $(libexec_PROGRAMS) $(sbin_PROGRAMS) +am_knot_zcompile_OBJECTS = zcompile_main.$(OBJEXT) \ + zcompile-error.$(OBJEXT) zparser.$(OBJEXT) zlexer.$(OBJEXT) \ + zcompile.$(OBJEXT) parser-util.$(OBJEXT) \ + parser-descriptor.$(OBJEXT) +knot_zcompile_OBJECTS = $(am_knot_zcompile_OBJECTS) +knot_zcompile_DEPENDENCIES = libknots.la ../libknot/libknot.la \ + libknotd.la @LIBOBJS@ +am_knotc_OBJECTS = knotc_main.$(OBJEXT) +knotc_OBJECTS = $(am_knotc_OBJECTS) +knotc_DEPENDENCIES = libknotd.la ../libknot/libknot.la libknots.la \ + @LIBOBJS@ $(am__empty) +am_knotd_OBJECTS = main.$(OBJEXT) +knotd_OBJECTS = $(am_knotd_OBJECTS) +knotd_DEPENDENCIES = libknotd.la ../libknot/libknot.la libknots.la \ + @LIBOBJS@ $(am__empty) +am_unittests_OBJECTS = acl_tests.$(OBJEXT) da_tests.$(OBJEXT) \ + events_tests.$(OBJEXT) skiplist_tests.$(OBJEXT) \ + slab_tests.$(OBJEXT) fdset_tests.$(OBJEXT) \ + conf_tests.$(OBJEXT) dthreads_tests.$(OBJEXT) \ + journal_tests.$(OBJEXT) server_tests.$(OBJEXT) \ + unittests_main.$(OBJEXT) +nodist_unittests_OBJECTS = +unittests_OBJECTS = $(am_unittests_OBJECTS) \ + $(nodist_unittests_OBJECTS) +unittests_DEPENDENCIES = libknotd.la libknots.la @LIBOBJS@ +am_unittests_libknot_OBJECTS = cuckoo_tests.$(OBJEXT) \ + response_tests.$(OBJEXT) dname_tests.$(OBJEXT) \ + dname_table_tests.$(OBJEXT) nsec3_tests.$(OBJEXT) \ + packet_tests.$(OBJEXT) query_tests.$(OBJEXT) \ + edns_tests.$(OBJEXT) node_tests.$(OBJEXT) \ + rdata_tests.$(OBJEXT) rrset_tests.$(OBJEXT) \ + zone_tests.$(OBJEXT) zone_tree_tests.$(OBJEXT) \ + zonedb_tests.$(OBJEXT) unittests_libknot.$(OBJEXT) +unittests_libknot_OBJECTS = $(am_unittests_libknot_OBJECTS) +unittests_libknot_DEPENDENCIES = ../libknot/libknot.la libknots.la \ + @LIBOBJS@ $(am__empty) +am_unittests_libknot_realdata_OBJECTS = \ + dname_tests_realdata.$(OBJEXT) \ + response_tests_realdata.$(OBJEXT) \ + edns_tests_realdata.$(OBJEXT) node_tests_realdata.$(OBJEXT) \ + rdata_tests_realdata.$(OBJEXT) rrset_tests_realdata.$(OBJEXT) \ + zone_tests_realdata.$(OBJEXT) zonedb_tests_realdata.$(OBJEXT) \ + packet_tests_realdata.$(OBJEXT) \ + libknot_tests_loader_realdata.$(OBJEXT) \ + unittests_libknot_realdata.$(OBJEXT) +unittests_libknot_realdata_OBJECTS = \ + $(am_unittests_libknot_realdata_OBJECTS) +unittests_libknot_realdata_DEPENDENCIES = ../libknot/libknot.la \ + libknots.la @LIBOBJS@ +am_unittests_zcompile_OBJECTS = zcompile-error.$(OBJEXT) \ + zparser.$(OBJEXT) zlexer.$(OBJEXT) zcompile.$(OBJEXT) \ + parser-util.$(OBJEXT) parser-descriptor.$(OBJEXT) \ + unittests_zp_main.$(OBJEXT) +unittests_zcompile_OBJECTS = $(am_unittests_zcompile_OBJECTS) +unittests_zcompile_DEPENDENCIES = ../libknot/libknot.la libknots.la \ + libknotd.la @LIBOBJS@ +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +@MAINTAINER_MODE_FALSE@am__skiplex = test -f $@ || +LEXCOMPILE = $(LEX) $(LFLAGS) $(AM_LFLAGS) +LTLEXCOMPILE = $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(LEX) $(LFLAGS) $(AM_LFLAGS) +YLWRAP = $(top_srcdir)/ylwrap +@MAINTAINER_MODE_FALSE@am__skipyacc = test -f $@ || +YACCCOMPILE = $(YACC) $(YFLAGS) $(AM_YFLAGS) +LTYACCCOMPILE = $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(YACC) $(YFLAGS) $(AM_YFLAGS) +SOURCES = $(libknot_la_SOURCES) $(libknotd_la_SOURCES) \ + $(libknots_la_SOURCES) $(knot_zcompile_SOURCES) \ + $(knotc_SOURCES) $(knotd_SOURCES) $(unittests_SOURCES) \ + $(nodist_unittests_SOURCES) $(unittests_libknot_SOURCES) \ + $(unittests_libknot_realdata_SOURCES) \ + $(unittests_zcompile_SOURCES) +DIST_SOURCES = $(libknot_la_SOURCES) $(libknotd_la_SOURCES) \ + $(libknots_la_SOURCES) $(knot_zcompile_SOURCES) \ + $(knotc_SOURCES) $(knotd_SOURCES) $(unittests_SOURCES) \ + $(unittests_libknot_SOURCES) \ + $(unittests_libknot_realdata_SOURCES) \ + $(unittests_zcompile_SOURCES) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +man8dir = $(mandir)/man8 +NROFF = nroff +MANS = $(man8_MANS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +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@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SIMD_FLAGS = @SIMD_FLAGS@ +STRIP = @STRIP@ +VERSION = @VERSION@ +YACC = @YACC@ +YFLAGS = @YFLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +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@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +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@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +lt_ECHO = @lt_ECHO@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +ACLOCAL_AMFLAGS = -I ../m4 +MANPAGES = knotc.8 knotd.8 +man8_MANS = knotc.8 knotd.8 +EXTRA_DIST = $(man8_MANS) + +# $(YACC) will generate header file +AM_CFLAGS = -Wall -Ilibknot -DLIBEXECDIR='"$(libexecdir)"' -DSYSCONFDIR='"$(sysconfdir)"' -DSBINDIR='"$(sbindir)"' +AM_YFLAGS = -d +libknotd_la_YFLAGS = -pcf_ -d +libknotd_la_LFLAGS = # TODO: reentrant parser, prefix +BUILT_SOURCES = \ + tests/libknot/parsed_data.rc \ + tests/libknot/realdata/parsed_data.rc \ + tests/libknot/raw_data_queries.rc \ + tests/libknot/raw_data.rc \ + tests/libknot/realdata/raw_data.rc \ + tests/libknot/parsed_data_queries.rc \ + tests/sample_conf.rc \ + zparser.h \ + zparser.c \ + zlexer.c \ + libknotd_la-cf-lex.c \ + libknotd_la-cf-parse.c \ + libknotd_la-cf-parse.h + +CLEANFILES = \ + tests/libknot/parsed_data.rc \ + tests/libknot/realdata/parsed_data.rc \ + tests/libknot/raw_data_queries.rc \ + tests/libknot/raw_data.rc \ + tests/libknot/realdata/raw_data.rc \ + tests/libknot/parsed_data_queries.rc \ + tests/sample_conf.rc \ + zparser.h \ + zparser.c \ + zlexer.c \ + libknotd_la-cf-lex.c \ + libknotd_la-cf-parse.c \ + libknotd_la-cf-parse.h + +knotc_SOURCES = \ + knot/ctl/knotc_main.c + +knot_zcompile_SOURCES = \ + zcompile/zcompile_main.c \ + zcompile/zcompile-error.c \ + zcompile/parser-util.h \ + zcompile/parser-descriptor.h \ + zcompile/zparser.y \ + zcompile/zlexer.l \ + zcompile/zcompile.c \ + zcompile/parser-util.c \ + zcompile/parser-descriptor.c + +unittests_SOURCES = \ + tests/common/acl_tests.c \ + tests/common/acl_tests.h \ + tests/common/da_tests.c \ + tests/common/da_tests.h \ + tests/common/events_tests.c \ + tests/common/events_tests.h \ + tests/common/skiplist_tests.c \ + tests/common/skiplist_tests.h \ + tests/common/slab_tests.c \ + tests/common/slab_tests.h \ + tests/common/fdset_tests.c \ + tests/common/fdset_tests.h \ + tests/knot/conf_tests.c \ + tests/knot/conf_tests.h \ + tests/knot/dthreads_tests.c \ + tests/knot/dthreads_tests.h \ + tests/knot/journal_tests.c \ + tests/knot/journal_tests.h \ + tests/knot/server_tests.c \ + tests/knot/server_tests.h \ + tests/unittests_main.c + +unittests_libknot_realdata_SOURCES = \ + tests/libknot/realdata/libknot/dname_tests_realdata.c \ + tests/libknot/realdata/libknot/response_tests_realdata.c \ + tests/libknot/realdata/libknot/edns_tests_realdata.c \ + tests/libknot/realdata/libknot/node_tests_realdata.c \ + tests/libknot/realdata/libknot/rdata_tests_realdata.c \ + tests/libknot/realdata/libknot/rrset_tests_realdata.c \ + tests/libknot/realdata/libknot/zone_tests_realdata.c \ + tests/libknot/realdata/libknot/zonedb_tests_realdata.c \ + tests/libknot/realdata/libknot/packet_tests_realdata.h \ + tests/libknot/realdata/libknot/packet_tests_realdata.c \ + tests/libknot/realdata/libknot_tests_loader_realdata.h \ + tests/libknot/realdata/libknot_tests_loader_realdata.c \ + tests/libknot/realdata/unittests_libknot_realdata.c + +unittests_libknot_SOURCES = \ + tests/libknot/libknot/cuckoo_tests.c \ + tests/libknot/libknot/cuckoo_tests.h \ + tests/libknot/libknot/response_tests.h \ + tests/libknot/libknot/response_tests.c \ + tests/libknot/libknot/dname_tests.c \ + tests/libknot/libknot/dname_tests.h \ + tests/libknot/libknot/dname_table_tests.h \ + tests/libknot/libknot/dname_table_tests.c \ + tests/libknot/libknot/nsec3_tests.h \ + tests/libknot/libknot/nsec3_tests.c \ + tests/libknot/libknot/packet_tests.h \ + tests/libknot/libknot/packet_tests.c \ + tests/libknot/libknot/query_tests.h \ + tests/libknot/libknot/query_tests.c \ + tests/libknot/libknot/edns_tests.c \ + tests/libknot/libknot/edns_tests.h \ + tests/libknot/libknot/node_tests.c \ + tests/libknot/libknot/node_tests.h \ + tests/libknot/libknot/rdata_tests.c \ + tests/libknot/libknot/rdata_tests.h \ + tests/libknot/libknot/rrset_tests.c \ + tests/libknot/libknot/rrset_tests.h \ + tests/libknot/libknot/zone_tests.c \ + tests/libknot/libknot/zone_tests.h \ + tests/libknot/libknot/zone_tree_tests.h\ + tests/libknot/libknot/zone_tree_tests.c \ + tests/libknot/libknot/zonedb_tests.c \ + tests/libknot/libknot/zonedb_tests.h \ + tests/libknot/unittests_libknot.c + +unittests_zcompile_SOURCES = \ + zcompile/parser-util.h \ + zcompile/parser-descriptor.h \ + zcompile/zcompile-error.c \ + zcompile/zparser.y \ + zcompile/zlexer.l \ + zcompile/zcompile.c \ + zcompile/parser-util.c \ + zcompile/parser-descriptor.c \ + zcompile/tests/unittests_zp_main.c + +nodist_unittests_SOURCES = \ + tests/libknot/parsed_data.rc \ + tests/libknot/raw_data_queries.rc \ + tests/libknot/raw_data.rc \ + tests/libknot/parsed_data_queries.rc \ + tests/sample_conf.rc + +knotd_SOURCES = \ + knot/main.c + +noinst_LTLIBRARIES = libknot.la libknotd.la libknots.la +libknot_la_SOURCES = \ + libknot/util/libknot_error.c \ + libknot/util/utils.c \ + libknot/util/debug.c \ + libknot/util/debug.h \ + libknot/util/utils.h \ + libknot/util/descriptor.c \ + libknot/util/tolower.h \ + libknot/util/tolower.c \ + libknot/util/descriptor.h \ + libknot/util/wire.h \ + libknot/packet/query.c \ + libknot/packet/response.c \ + libknot/packet/packet.c \ + libknot/packet/packet.h \ + libknot/packet/query.h \ + libknot/packet/response.h \ + libknot/zone/zone.c \ + libknot/zone/zone-contents.c \ + libknot/zone/zone-tree.c \ + libknot/zone/zone-tree.h \ + libknot/zone/node.h \ + libknot/zone/zone.h \ + libknot/zone/zone-contents.h \ + libknot/zone/zonedb.c \ + libknot/zone/zonedb.h \ + libknot/zone/node.c \ + libknot/zone/dname-table.h \ + libknot/zone/dname-table.c \ + libknot/hash/hash-functions.c \ + libknot/hash/cuckoo-hash-table.c \ + libknot/hash/universal-system.c \ + libknot/hash/universal-system.h \ + libknot/hash/cuckoo-hash-table.h \ + libknot/hash/hash-functions.h \ + libknot/nameserver/name-server.h \ + libknot/nameserver/name-server.c \ + libknot/updates/changesets.h \ + libknot/updates/changesets.c \ + libknot/updates/xfr-in.h \ + libknot/updates/xfr-in.c \ + libknot/updates/ddns.h \ + libknot/updates/ddns.c \ + libknot/edns.c \ + libknot/rrset.c \ + libknot/dname.c \ + libknot/rdata.c \ + libknot/nsec3.c \ + libknot/consts.h \ + libknot/edns.h \ + libknot/rdata.h \ + libknot/libknot.h \ + libknot/dname.h \ + libknot/rrset.h \ + libknot/nsec3.h \ + libknot/tsig.h \ + libknot/tsig.c \ + libknot/tsig-op.h \ + libknot/tsig-op.c + +libknots_la_SOURCES = \ + common/slab/slab.c \ + common/slab/malloc.c \ + common/slab/slab.h \ + common/slab/malloc.h \ + common/libtap/tap.c \ + common/libtap/tap.h \ + common/libtap/tap_unit.h \ + common/lists.c \ + common/base32.c \ + common/lists.h \ + common/base32.h \ + common/print.c \ + common/print.h \ + common/dynamic-array.c \ + common/skip-list.c \ + common/base32hex.c \ + common/skip-list.h \ + common/general-tree.h \ + common/general-tree.c \ + common/dynamic-array.h \ + common/tree.h \ + common/base32hex.h \ + common/evqueue.h \ + common/evqueue.c \ + common/evsched.h \ + common/evsched.c \ + common/acl.h \ + common/acl.c \ + common/sockaddr.h \ + common/sockaddr.c \ + common/crc.h \ + common/crc.c \ + common/ref.h \ + common/ref.c \ + common/errors.h \ + common/errors.c \ + common/WELL1024a.h \ + common/WELL1024a.c \ + common/fdset.h \ + common/fdset.c \ + common/fdset_poll.h \ + common/fdset_poll.c \ + common/fdset_kqueue.h \ + common/fdset_kqueue.c \ + common/fdset_epoll.h \ + common/fdset_epoll.c + +libknotd_la_SOURCES = \ + knot/stat/gatherer.c \ + knot/stat/stat.c \ + knot/stat/gatherer.h \ + knot/stat/stat.h \ + knot/common.h \ + knot/other/log.c \ + knot/other/log.h \ + knot/other/debug.h \ + knot/other/error.h \ + knot/other/error.c \ + knot/conf/cf-parse.y \ + knot/conf/cf-lex.l \ + knot/conf/conf.c \ + knot/conf/logconf.c \ + knot/conf/logconf.h \ + knot/conf/conf.h \ + knot/ctl/process.c \ + knot/ctl/process.h \ + knot/server/dthreads.c \ + knot/server/journal.c \ + knot/server/socket.c \ + knot/server/server.c \ + knot/server/udp-handler.c \ + knot/server/tcp-handler.c \ + knot/server/xfr-handler.c \ + knot/server/zones.c \ + knot/server/socket.h \ + knot/server/udp-handler.h \ + knot/server/tcp-handler.h \ + knot/server/xfr-handler.h \ + knot/server/dthreads.h \ + knot/server/journal.h \ + knot/server/zones.h \ + knot/server/notify.h \ + knot/server/notify.c \ + knot/server/zones.h \ + knot/zone/zone-load.c \ + knot/zone/zone-load.h \ + knot/zone/zone-dump.c \ + knot/zone/zone-dump-text.c \ + knot/zone/zone-dump-text.h \ + knot/zone/zone-dump.h \ + knot/server/server.h + +libknotd_la_LIBADD = ../libknot/libknot.la libknots.la @LIBOBJS@ +libknots_la_LIBADD = @LIBOBJS@ +knotd_LDADD = libknotd.la ../libknot/libknot.la libknots.la @LIBOBJS@ +knotc_LDADD = libknotd.la ../libknot/libknot.la libknots.la @LIBOBJS@ +knot_zcompile_LDADD = libknots.la ../libknot/libknot.la libknotd.la @LIBOBJS@ +unittests_LDADD = libknotd.la libknots.la @LIBOBJS@ +unittests_zcompile_LDADD = ../libknot/libknot.la libknots.la libknotd.la @LIBOBJS@ +unittests_libknot_LDADD = ../libknot/libknot.la libknots.la @LIBOBJS@ +unittests_libknot_realdata_LDADD = ../libknot/libknot.la libknots.la @LIBOBJS@ +all: $(BUILT_SOURCES) config.h + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .l .lo .o .obj .y +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(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) --gnu src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/Makefile +.PRECIOUS: 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__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +config.h: stamp-h1 + @if test ! -f $@; then \ + rm -f stamp-h1; \ + $(MAKE) $(AM_MAKEFLAGS) stamp-h1; \ + else :; fi + +stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status + @rm -f stamp-h1 + cd $(top_builddir) && $(SHELL) ./config.status src/config.h +$(srcdir)/config.h.in: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + ($(am__cd) $(top_srcdir) && $(AUTOHEADER)) + rm -f stamp-h1 + touch $@ + +distclean-hdr: + -rm -f config.h stamp-h1 + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +libknot.la: $(libknot_la_OBJECTS) $(libknot_la_DEPENDENCIES) + $(LINK) $(libknot_la_OBJECTS) $(libknot_la_LIBADD) $(LIBS) +libknotd_la-cf-parse.h: libknotd_la-cf-parse.c + @if test ! -f $@; then \ + rm -f libknotd_la-cf-parse.c; \ + $(MAKE) $(AM_MAKEFLAGS) libknotd_la-cf-parse.c; \ + else :; fi +libknotd.la: $(libknotd_la_OBJECTS) $(libknotd_la_DEPENDENCIES) + $(LINK) $(libknotd_la_OBJECTS) $(libknotd_la_LIBADD) $(LIBS) +libknots.la: $(libknots_la_OBJECTS) $(libknots_la_DEPENDENCIES) + $(LINK) $(libknots_la_OBJECTS) $(libknots_la_LIBADD) $(LIBS) +install-libexecPROGRAMS: $(libexec_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(libexecdir)" || $(MKDIR_P) "$(DESTDIR)$(libexecdir)" + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p || test -f $$p1; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(libexecdir)" && rm -f $$files + +clean-libexecPROGRAMS: + @list='$(libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-sbinPROGRAMS: $(sbin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(sbindir)" || $(MKDIR_P) "$(DESTDIR)$(sbindir)" + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p || test -f $$p1; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sbinPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(sbindir)" && rm -f $$files + +clean-sbinPROGRAMS: + @list='$(sbin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +zparser.h: zparser.c + @if test ! -f $@; then \ + rm -f zparser.c; \ + $(MAKE) $(AM_MAKEFLAGS) zparser.c; \ + else :; fi +knot-zcompile$(EXEEXT): $(knot_zcompile_OBJECTS) $(knot_zcompile_DEPENDENCIES) + @rm -f knot-zcompile$(EXEEXT) + $(LINK) $(knot_zcompile_OBJECTS) $(knot_zcompile_LDADD) $(LIBS) +knotc$(EXEEXT): $(knotc_OBJECTS) $(knotc_DEPENDENCIES) + @rm -f knotc$(EXEEXT) + $(LINK) $(knotc_OBJECTS) $(knotc_LDADD) $(LIBS) +knotd$(EXEEXT): $(knotd_OBJECTS) $(knotd_DEPENDENCIES) + @rm -f knotd$(EXEEXT) + $(LINK) $(knotd_OBJECTS) $(knotd_LDADD) $(LIBS) +unittests$(EXEEXT): $(unittests_OBJECTS) $(unittests_DEPENDENCIES) + @rm -f unittests$(EXEEXT) + $(LINK) $(unittests_OBJECTS) $(unittests_LDADD) $(LIBS) +unittests-libknot$(EXEEXT): $(unittests_libknot_OBJECTS) $(unittests_libknot_DEPENDENCIES) + @rm -f unittests-libknot$(EXEEXT) + $(LINK) $(unittests_libknot_OBJECTS) $(unittests_libknot_LDADD) $(LIBS) +unittests-libknot-realdata$(EXEEXT): $(unittests_libknot_realdata_OBJECTS) $(unittests_libknot_realdata_DEPENDENCIES) + @rm -f unittests-libknot-realdata$(EXEEXT) + $(LINK) $(unittests_libknot_realdata_OBJECTS) $(unittests_libknot_realdata_LDADD) $(LIBS) +unittests-zcompile$(EXEEXT): $(unittests_zcompile_OBJECTS) $(unittests_zcompile_DEPENDENCIES) + @rm -f unittests-zcompile$(EXEEXT) + $(LINK) $(unittests_zcompile_OBJECTS) $(unittests_zcompile_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WELL1024a.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base32.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base32hex.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/changesets.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conf.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conf_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cuckoo-hash-table.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cuckoo_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/da_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ddns.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/debug.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/descriptor.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dname-table.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dname.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dname_table_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dname_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dname_tests_realdata.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dthreads.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dthreads_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dynamic-array.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/edns.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/edns_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/edns_tests_realdata.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/error.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/errors.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/events_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/evqueue.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/evsched.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdset.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdset_epoll.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdset_kqueue.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdset_poll.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdset_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gatherer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/general-tree.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hash-functions.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/journal.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/journal_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/knotc_main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libknot_error.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libknot_tests_loader_realdata.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libknotd_la-cf-lex.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libknotd_la-cf-parse.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lists.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logconf.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/malloc.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/name-server.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/node.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/node_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/node_tests_realdata.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nsec3.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nsec3_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/packet.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/packet_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/packet_tests_realdata.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parser-descriptor.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parser-util.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/process.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/query.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/query_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rdata.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rdata_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rdata_tests_realdata.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ref.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/response.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/response_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/response_tests_realdata.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rrset.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rrset_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rrset_tests_realdata.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/skip-list.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/skiplist_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/slab.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/slab_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sockaddr.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stat.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tap.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tcp-handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tolower.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsig-op.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsig.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/udp-handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unittests_libknot.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unittests_libknot_realdata.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unittests_main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unittests_zp_main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/universal-system.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xfr-handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xfr-in.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zcompile-error.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zcompile.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zcompile_main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zlexer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zone-contents.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zone-dump-text.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zone-dump.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zone-load.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zone-tree.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zone.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zone_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zone_tests_realdata.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zone_tree_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zonedb.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zonedb_tests.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zonedb_tests_realdata.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zones.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zparser.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +libknot_error.lo: libknot/util/libknot_error.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libknot_error.lo -MD -MP -MF $(DEPDIR)/libknot_error.Tpo -c -o libknot_error.lo `test -f 'libknot/util/libknot_error.c' || echo '$(srcdir)/'`libknot/util/libknot_error.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libknot_error.Tpo $(DEPDIR)/libknot_error.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/util/libknot_error.c' object='libknot_error.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libknot_error.lo `test -f 'libknot/util/libknot_error.c' || echo '$(srcdir)/'`libknot/util/libknot_error.c + +utils.lo: libknot/util/utils.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT utils.lo -MD -MP -MF $(DEPDIR)/utils.Tpo -c -o utils.lo `test -f 'libknot/util/utils.c' || echo '$(srcdir)/'`libknot/util/utils.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/utils.Tpo $(DEPDIR)/utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/util/utils.c' object='utils.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o utils.lo `test -f 'libknot/util/utils.c' || echo '$(srcdir)/'`libknot/util/utils.c + +debug.lo: libknot/util/debug.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT debug.lo -MD -MP -MF $(DEPDIR)/debug.Tpo -c -o debug.lo `test -f 'libknot/util/debug.c' || echo '$(srcdir)/'`libknot/util/debug.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/debug.Tpo $(DEPDIR)/debug.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/util/debug.c' object='debug.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o debug.lo `test -f 'libknot/util/debug.c' || echo '$(srcdir)/'`libknot/util/debug.c + +descriptor.lo: libknot/util/descriptor.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT descriptor.lo -MD -MP -MF $(DEPDIR)/descriptor.Tpo -c -o descriptor.lo `test -f 'libknot/util/descriptor.c' || echo '$(srcdir)/'`libknot/util/descriptor.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/descriptor.Tpo $(DEPDIR)/descriptor.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/util/descriptor.c' object='descriptor.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o descriptor.lo `test -f 'libknot/util/descriptor.c' || echo '$(srcdir)/'`libknot/util/descriptor.c + +tolower.lo: libknot/util/tolower.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT tolower.lo -MD -MP -MF $(DEPDIR)/tolower.Tpo -c -o tolower.lo `test -f 'libknot/util/tolower.c' || echo '$(srcdir)/'`libknot/util/tolower.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/tolower.Tpo $(DEPDIR)/tolower.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/util/tolower.c' object='tolower.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o tolower.lo `test -f 'libknot/util/tolower.c' || echo '$(srcdir)/'`libknot/util/tolower.c + +query.lo: libknot/packet/query.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT query.lo -MD -MP -MF $(DEPDIR)/query.Tpo -c -o query.lo `test -f 'libknot/packet/query.c' || echo '$(srcdir)/'`libknot/packet/query.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/query.Tpo $(DEPDIR)/query.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/packet/query.c' object='query.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o query.lo `test -f 'libknot/packet/query.c' || echo '$(srcdir)/'`libknot/packet/query.c + +response.lo: libknot/packet/response.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT response.lo -MD -MP -MF $(DEPDIR)/response.Tpo -c -o response.lo `test -f 'libknot/packet/response.c' || echo '$(srcdir)/'`libknot/packet/response.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/response.Tpo $(DEPDIR)/response.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/packet/response.c' object='response.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o response.lo `test -f 'libknot/packet/response.c' || echo '$(srcdir)/'`libknot/packet/response.c + +packet.lo: libknot/packet/packet.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT packet.lo -MD -MP -MF $(DEPDIR)/packet.Tpo -c -o packet.lo `test -f 'libknot/packet/packet.c' || echo '$(srcdir)/'`libknot/packet/packet.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/packet.Tpo $(DEPDIR)/packet.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/packet/packet.c' object='packet.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o packet.lo `test -f 'libknot/packet/packet.c' || echo '$(srcdir)/'`libknot/packet/packet.c + +zone.lo: libknot/zone/zone.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone.lo -MD -MP -MF $(DEPDIR)/zone.Tpo -c -o zone.lo `test -f 'libknot/zone/zone.c' || echo '$(srcdir)/'`libknot/zone/zone.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone.Tpo $(DEPDIR)/zone.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/zone/zone.c' object='zone.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone.lo `test -f 'libknot/zone/zone.c' || echo '$(srcdir)/'`libknot/zone/zone.c + +zone-contents.lo: libknot/zone/zone-contents.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone-contents.lo -MD -MP -MF $(DEPDIR)/zone-contents.Tpo -c -o zone-contents.lo `test -f 'libknot/zone/zone-contents.c' || echo '$(srcdir)/'`libknot/zone/zone-contents.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone-contents.Tpo $(DEPDIR)/zone-contents.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/zone/zone-contents.c' object='zone-contents.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone-contents.lo `test -f 'libknot/zone/zone-contents.c' || echo '$(srcdir)/'`libknot/zone/zone-contents.c + +zone-tree.lo: libknot/zone/zone-tree.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone-tree.lo -MD -MP -MF $(DEPDIR)/zone-tree.Tpo -c -o zone-tree.lo `test -f 'libknot/zone/zone-tree.c' || echo '$(srcdir)/'`libknot/zone/zone-tree.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone-tree.Tpo $(DEPDIR)/zone-tree.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/zone/zone-tree.c' object='zone-tree.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone-tree.lo `test -f 'libknot/zone/zone-tree.c' || echo '$(srcdir)/'`libknot/zone/zone-tree.c + +zonedb.lo: libknot/zone/zonedb.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zonedb.lo -MD -MP -MF $(DEPDIR)/zonedb.Tpo -c -o zonedb.lo `test -f 'libknot/zone/zonedb.c' || echo '$(srcdir)/'`libknot/zone/zonedb.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zonedb.Tpo $(DEPDIR)/zonedb.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/zone/zonedb.c' object='zonedb.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zonedb.lo `test -f 'libknot/zone/zonedb.c' || echo '$(srcdir)/'`libknot/zone/zonedb.c + +node.lo: libknot/zone/node.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT node.lo -MD -MP -MF $(DEPDIR)/node.Tpo -c -o node.lo `test -f 'libknot/zone/node.c' || echo '$(srcdir)/'`libknot/zone/node.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/node.Tpo $(DEPDIR)/node.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/zone/node.c' object='node.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o node.lo `test -f 'libknot/zone/node.c' || echo '$(srcdir)/'`libknot/zone/node.c + +dname-table.lo: libknot/zone/dname-table.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dname-table.lo -MD -MP -MF $(DEPDIR)/dname-table.Tpo -c -o dname-table.lo `test -f 'libknot/zone/dname-table.c' || echo '$(srcdir)/'`libknot/zone/dname-table.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dname-table.Tpo $(DEPDIR)/dname-table.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/zone/dname-table.c' object='dname-table.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dname-table.lo `test -f 'libknot/zone/dname-table.c' || echo '$(srcdir)/'`libknot/zone/dname-table.c + +hash-functions.lo: libknot/hash/hash-functions.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT hash-functions.lo -MD -MP -MF $(DEPDIR)/hash-functions.Tpo -c -o hash-functions.lo `test -f 'libknot/hash/hash-functions.c' || echo '$(srcdir)/'`libknot/hash/hash-functions.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/hash-functions.Tpo $(DEPDIR)/hash-functions.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/hash/hash-functions.c' object='hash-functions.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o hash-functions.lo `test -f 'libknot/hash/hash-functions.c' || echo '$(srcdir)/'`libknot/hash/hash-functions.c + +cuckoo-hash-table.lo: libknot/hash/cuckoo-hash-table.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT cuckoo-hash-table.lo -MD -MP -MF $(DEPDIR)/cuckoo-hash-table.Tpo -c -o cuckoo-hash-table.lo `test -f 'libknot/hash/cuckoo-hash-table.c' || echo '$(srcdir)/'`libknot/hash/cuckoo-hash-table.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/cuckoo-hash-table.Tpo $(DEPDIR)/cuckoo-hash-table.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/hash/cuckoo-hash-table.c' object='cuckoo-hash-table.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o cuckoo-hash-table.lo `test -f 'libknot/hash/cuckoo-hash-table.c' || echo '$(srcdir)/'`libknot/hash/cuckoo-hash-table.c + +universal-system.lo: libknot/hash/universal-system.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT universal-system.lo -MD -MP -MF $(DEPDIR)/universal-system.Tpo -c -o universal-system.lo `test -f 'libknot/hash/universal-system.c' || echo '$(srcdir)/'`libknot/hash/universal-system.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/universal-system.Tpo $(DEPDIR)/universal-system.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/hash/universal-system.c' object='universal-system.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o universal-system.lo `test -f 'libknot/hash/universal-system.c' || echo '$(srcdir)/'`libknot/hash/universal-system.c + +name-server.lo: libknot/nameserver/name-server.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT name-server.lo -MD -MP -MF $(DEPDIR)/name-server.Tpo -c -o name-server.lo `test -f 'libknot/nameserver/name-server.c' || echo '$(srcdir)/'`libknot/nameserver/name-server.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/name-server.Tpo $(DEPDIR)/name-server.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/nameserver/name-server.c' object='name-server.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o name-server.lo `test -f 'libknot/nameserver/name-server.c' || echo '$(srcdir)/'`libknot/nameserver/name-server.c + +changesets.lo: libknot/updates/changesets.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT changesets.lo -MD -MP -MF $(DEPDIR)/changesets.Tpo -c -o changesets.lo `test -f 'libknot/updates/changesets.c' || echo '$(srcdir)/'`libknot/updates/changesets.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/changesets.Tpo $(DEPDIR)/changesets.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/updates/changesets.c' object='changesets.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o changesets.lo `test -f 'libknot/updates/changesets.c' || echo '$(srcdir)/'`libknot/updates/changesets.c + +xfr-in.lo: libknot/updates/xfr-in.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT xfr-in.lo -MD -MP -MF $(DEPDIR)/xfr-in.Tpo -c -o xfr-in.lo `test -f 'libknot/updates/xfr-in.c' || echo '$(srcdir)/'`libknot/updates/xfr-in.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/xfr-in.Tpo $(DEPDIR)/xfr-in.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/updates/xfr-in.c' object='xfr-in.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xfr-in.lo `test -f 'libknot/updates/xfr-in.c' || echo '$(srcdir)/'`libknot/updates/xfr-in.c + +ddns.lo: libknot/updates/ddns.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ddns.lo -MD -MP -MF $(DEPDIR)/ddns.Tpo -c -o ddns.lo `test -f 'libknot/updates/ddns.c' || echo '$(srcdir)/'`libknot/updates/ddns.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/ddns.Tpo $(DEPDIR)/ddns.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/updates/ddns.c' object='ddns.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ddns.lo `test -f 'libknot/updates/ddns.c' || echo '$(srcdir)/'`libknot/updates/ddns.c + +edns.lo: libknot/edns.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT edns.lo -MD -MP -MF $(DEPDIR)/edns.Tpo -c -o edns.lo `test -f 'libknot/edns.c' || echo '$(srcdir)/'`libknot/edns.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/edns.Tpo $(DEPDIR)/edns.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/edns.c' object='edns.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o edns.lo `test -f 'libknot/edns.c' || echo '$(srcdir)/'`libknot/edns.c + +rrset.lo: libknot/rrset.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rrset.lo -MD -MP -MF $(DEPDIR)/rrset.Tpo -c -o rrset.lo `test -f 'libknot/rrset.c' || echo '$(srcdir)/'`libknot/rrset.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rrset.Tpo $(DEPDIR)/rrset.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/rrset.c' object='rrset.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rrset.lo `test -f 'libknot/rrset.c' || echo '$(srcdir)/'`libknot/rrset.c + +dname.lo: libknot/dname.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dname.lo -MD -MP -MF $(DEPDIR)/dname.Tpo -c -o dname.lo `test -f 'libknot/dname.c' || echo '$(srcdir)/'`libknot/dname.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dname.Tpo $(DEPDIR)/dname.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/dname.c' object='dname.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dname.lo `test -f 'libknot/dname.c' || echo '$(srcdir)/'`libknot/dname.c + +rdata.lo: libknot/rdata.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rdata.lo -MD -MP -MF $(DEPDIR)/rdata.Tpo -c -o rdata.lo `test -f 'libknot/rdata.c' || echo '$(srcdir)/'`libknot/rdata.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rdata.Tpo $(DEPDIR)/rdata.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/rdata.c' object='rdata.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rdata.lo `test -f 'libknot/rdata.c' || echo '$(srcdir)/'`libknot/rdata.c + +nsec3.lo: libknot/nsec3.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nsec3.lo -MD -MP -MF $(DEPDIR)/nsec3.Tpo -c -o nsec3.lo `test -f 'libknot/nsec3.c' || echo '$(srcdir)/'`libknot/nsec3.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/nsec3.Tpo $(DEPDIR)/nsec3.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/nsec3.c' object='nsec3.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nsec3.lo `test -f 'libknot/nsec3.c' || echo '$(srcdir)/'`libknot/nsec3.c + +tsig.lo: libknot/tsig.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT tsig.lo -MD -MP -MF $(DEPDIR)/tsig.Tpo -c -o tsig.lo `test -f 'libknot/tsig.c' || echo '$(srcdir)/'`libknot/tsig.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/tsig.Tpo $(DEPDIR)/tsig.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/tsig.c' object='tsig.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o tsig.lo `test -f 'libknot/tsig.c' || echo '$(srcdir)/'`libknot/tsig.c + +tsig-op.lo: libknot/tsig-op.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT tsig-op.lo -MD -MP -MF $(DEPDIR)/tsig-op.Tpo -c -o tsig-op.lo `test -f 'libknot/tsig-op.c' || echo '$(srcdir)/'`libknot/tsig-op.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/tsig-op.Tpo $(DEPDIR)/tsig-op.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='libknot/tsig-op.c' object='tsig-op.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o tsig-op.lo `test -f 'libknot/tsig-op.c' || echo '$(srcdir)/'`libknot/tsig-op.c + +gatherer.lo: knot/stat/gatherer.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gatherer.lo -MD -MP -MF $(DEPDIR)/gatherer.Tpo -c -o gatherer.lo `test -f 'knot/stat/gatherer.c' || echo '$(srcdir)/'`knot/stat/gatherer.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/gatherer.Tpo $(DEPDIR)/gatherer.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/stat/gatherer.c' object='gatherer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gatherer.lo `test -f 'knot/stat/gatherer.c' || echo '$(srcdir)/'`knot/stat/gatherer.c + +stat.lo: knot/stat/stat.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT stat.lo -MD -MP -MF $(DEPDIR)/stat.Tpo -c -o stat.lo `test -f 'knot/stat/stat.c' || echo '$(srcdir)/'`knot/stat/stat.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/stat.Tpo $(DEPDIR)/stat.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/stat/stat.c' object='stat.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o stat.lo `test -f 'knot/stat/stat.c' || echo '$(srcdir)/'`knot/stat/stat.c + +log.lo: knot/other/log.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT log.lo -MD -MP -MF $(DEPDIR)/log.Tpo -c -o log.lo `test -f 'knot/other/log.c' || echo '$(srcdir)/'`knot/other/log.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/log.Tpo $(DEPDIR)/log.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/other/log.c' object='log.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o log.lo `test -f 'knot/other/log.c' || echo '$(srcdir)/'`knot/other/log.c + +error.lo: knot/other/error.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT error.lo -MD -MP -MF $(DEPDIR)/error.Tpo -c -o error.lo `test -f 'knot/other/error.c' || echo '$(srcdir)/'`knot/other/error.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/error.Tpo $(DEPDIR)/error.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/other/error.c' object='error.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o error.lo `test -f 'knot/other/error.c' || echo '$(srcdir)/'`knot/other/error.c + +conf.lo: knot/conf/conf.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT conf.lo -MD -MP -MF $(DEPDIR)/conf.Tpo -c -o conf.lo `test -f 'knot/conf/conf.c' || echo '$(srcdir)/'`knot/conf/conf.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/conf.Tpo $(DEPDIR)/conf.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/conf/conf.c' object='conf.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o conf.lo `test -f 'knot/conf/conf.c' || echo '$(srcdir)/'`knot/conf/conf.c + +logconf.lo: knot/conf/logconf.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT logconf.lo -MD -MP -MF $(DEPDIR)/logconf.Tpo -c -o logconf.lo `test -f 'knot/conf/logconf.c' || echo '$(srcdir)/'`knot/conf/logconf.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/logconf.Tpo $(DEPDIR)/logconf.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/conf/logconf.c' object='logconf.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o logconf.lo `test -f 'knot/conf/logconf.c' || echo '$(srcdir)/'`knot/conf/logconf.c + +process.lo: knot/ctl/process.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT process.lo -MD -MP -MF $(DEPDIR)/process.Tpo -c -o process.lo `test -f 'knot/ctl/process.c' || echo '$(srcdir)/'`knot/ctl/process.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/process.Tpo $(DEPDIR)/process.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/ctl/process.c' object='process.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o process.lo `test -f 'knot/ctl/process.c' || echo '$(srcdir)/'`knot/ctl/process.c + +dthreads.lo: knot/server/dthreads.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dthreads.lo -MD -MP -MF $(DEPDIR)/dthreads.Tpo -c -o dthreads.lo `test -f 'knot/server/dthreads.c' || echo '$(srcdir)/'`knot/server/dthreads.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dthreads.Tpo $(DEPDIR)/dthreads.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/server/dthreads.c' object='dthreads.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dthreads.lo `test -f 'knot/server/dthreads.c' || echo '$(srcdir)/'`knot/server/dthreads.c + +journal.lo: knot/server/journal.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT journal.lo -MD -MP -MF $(DEPDIR)/journal.Tpo -c -o journal.lo `test -f 'knot/server/journal.c' || echo '$(srcdir)/'`knot/server/journal.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/journal.Tpo $(DEPDIR)/journal.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/server/journal.c' object='journal.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o journal.lo `test -f 'knot/server/journal.c' || echo '$(srcdir)/'`knot/server/journal.c + +socket.lo: knot/server/socket.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT socket.lo -MD -MP -MF $(DEPDIR)/socket.Tpo -c -o socket.lo `test -f 'knot/server/socket.c' || echo '$(srcdir)/'`knot/server/socket.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/socket.Tpo $(DEPDIR)/socket.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/server/socket.c' object='socket.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o socket.lo `test -f 'knot/server/socket.c' || echo '$(srcdir)/'`knot/server/socket.c + +server.lo: knot/server/server.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT server.lo -MD -MP -MF $(DEPDIR)/server.Tpo -c -o server.lo `test -f 'knot/server/server.c' || echo '$(srcdir)/'`knot/server/server.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/server.Tpo $(DEPDIR)/server.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/server/server.c' object='server.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o server.lo `test -f 'knot/server/server.c' || echo '$(srcdir)/'`knot/server/server.c + +udp-handler.lo: knot/server/udp-handler.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT udp-handler.lo -MD -MP -MF $(DEPDIR)/udp-handler.Tpo -c -o udp-handler.lo `test -f 'knot/server/udp-handler.c' || echo '$(srcdir)/'`knot/server/udp-handler.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/udp-handler.Tpo $(DEPDIR)/udp-handler.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/server/udp-handler.c' object='udp-handler.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o udp-handler.lo `test -f 'knot/server/udp-handler.c' || echo '$(srcdir)/'`knot/server/udp-handler.c + +tcp-handler.lo: knot/server/tcp-handler.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT tcp-handler.lo -MD -MP -MF $(DEPDIR)/tcp-handler.Tpo -c -o tcp-handler.lo `test -f 'knot/server/tcp-handler.c' || echo '$(srcdir)/'`knot/server/tcp-handler.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/tcp-handler.Tpo $(DEPDIR)/tcp-handler.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/server/tcp-handler.c' object='tcp-handler.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o tcp-handler.lo `test -f 'knot/server/tcp-handler.c' || echo '$(srcdir)/'`knot/server/tcp-handler.c + +xfr-handler.lo: knot/server/xfr-handler.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT xfr-handler.lo -MD -MP -MF $(DEPDIR)/xfr-handler.Tpo -c -o xfr-handler.lo `test -f 'knot/server/xfr-handler.c' || echo '$(srcdir)/'`knot/server/xfr-handler.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/xfr-handler.Tpo $(DEPDIR)/xfr-handler.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/server/xfr-handler.c' object='xfr-handler.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xfr-handler.lo `test -f 'knot/server/xfr-handler.c' || echo '$(srcdir)/'`knot/server/xfr-handler.c + +zones.lo: knot/server/zones.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zones.lo -MD -MP -MF $(DEPDIR)/zones.Tpo -c -o zones.lo `test -f 'knot/server/zones.c' || echo '$(srcdir)/'`knot/server/zones.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zones.Tpo $(DEPDIR)/zones.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/server/zones.c' object='zones.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zones.lo `test -f 'knot/server/zones.c' || echo '$(srcdir)/'`knot/server/zones.c + +notify.lo: knot/server/notify.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT notify.lo -MD -MP -MF $(DEPDIR)/notify.Tpo -c -o notify.lo `test -f 'knot/server/notify.c' || echo '$(srcdir)/'`knot/server/notify.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/notify.Tpo $(DEPDIR)/notify.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/server/notify.c' object='notify.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o notify.lo `test -f 'knot/server/notify.c' || echo '$(srcdir)/'`knot/server/notify.c + +zone-load.lo: knot/zone/zone-load.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone-load.lo -MD -MP -MF $(DEPDIR)/zone-load.Tpo -c -o zone-load.lo `test -f 'knot/zone/zone-load.c' || echo '$(srcdir)/'`knot/zone/zone-load.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone-load.Tpo $(DEPDIR)/zone-load.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/zone/zone-load.c' object='zone-load.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone-load.lo `test -f 'knot/zone/zone-load.c' || echo '$(srcdir)/'`knot/zone/zone-load.c + +zone-dump.lo: knot/zone/zone-dump.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone-dump.lo -MD -MP -MF $(DEPDIR)/zone-dump.Tpo -c -o zone-dump.lo `test -f 'knot/zone/zone-dump.c' || echo '$(srcdir)/'`knot/zone/zone-dump.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone-dump.Tpo $(DEPDIR)/zone-dump.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/zone/zone-dump.c' object='zone-dump.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone-dump.lo `test -f 'knot/zone/zone-dump.c' || echo '$(srcdir)/'`knot/zone/zone-dump.c + +zone-dump-text.lo: knot/zone/zone-dump-text.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone-dump-text.lo -MD -MP -MF $(DEPDIR)/zone-dump-text.Tpo -c -o zone-dump-text.lo `test -f 'knot/zone/zone-dump-text.c' || echo '$(srcdir)/'`knot/zone/zone-dump-text.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone-dump-text.Tpo $(DEPDIR)/zone-dump-text.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/zone/zone-dump-text.c' object='zone-dump-text.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone-dump-text.lo `test -f 'knot/zone/zone-dump-text.c' || echo '$(srcdir)/'`knot/zone/zone-dump-text.c + +slab.lo: common/slab/slab.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT slab.lo -MD -MP -MF $(DEPDIR)/slab.Tpo -c -o slab.lo `test -f 'common/slab/slab.c' || echo '$(srcdir)/'`common/slab/slab.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/slab.Tpo $(DEPDIR)/slab.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/slab/slab.c' object='slab.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o slab.lo `test -f 'common/slab/slab.c' || echo '$(srcdir)/'`common/slab/slab.c + +malloc.lo: common/slab/malloc.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT malloc.lo -MD -MP -MF $(DEPDIR)/malloc.Tpo -c -o malloc.lo `test -f 'common/slab/malloc.c' || echo '$(srcdir)/'`common/slab/malloc.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/malloc.Tpo $(DEPDIR)/malloc.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/slab/malloc.c' object='malloc.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o malloc.lo `test -f 'common/slab/malloc.c' || echo '$(srcdir)/'`common/slab/malloc.c + +tap.lo: common/libtap/tap.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT tap.lo -MD -MP -MF $(DEPDIR)/tap.Tpo -c -o tap.lo `test -f 'common/libtap/tap.c' || echo '$(srcdir)/'`common/libtap/tap.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/tap.Tpo $(DEPDIR)/tap.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/libtap/tap.c' object='tap.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o tap.lo `test -f 'common/libtap/tap.c' || echo '$(srcdir)/'`common/libtap/tap.c + +lists.lo: common/lists.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lists.lo -MD -MP -MF $(DEPDIR)/lists.Tpo -c -o lists.lo `test -f 'common/lists.c' || echo '$(srcdir)/'`common/lists.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/lists.Tpo $(DEPDIR)/lists.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/lists.c' object='lists.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lists.lo `test -f 'common/lists.c' || echo '$(srcdir)/'`common/lists.c + +base32.lo: common/base32.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT base32.lo -MD -MP -MF $(DEPDIR)/base32.Tpo -c -o base32.lo `test -f 'common/base32.c' || echo '$(srcdir)/'`common/base32.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/base32.Tpo $(DEPDIR)/base32.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/base32.c' object='base32.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o base32.lo `test -f 'common/base32.c' || echo '$(srcdir)/'`common/base32.c + +print.lo: common/print.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT print.lo -MD -MP -MF $(DEPDIR)/print.Tpo -c -o print.lo `test -f 'common/print.c' || echo '$(srcdir)/'`common/print.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/print.Tpo $(DEPDIR)/print.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/print.c' object='print.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o print.lo `test -f 'common/print.c' || echo '$(srcdir)/'`common/print.c + +dynamic-array.lo: common/dynamic-array.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dynamic-array.lo -MD -MP -MF $(DEPDIR)/dynamic-array.Tpo -c -o dynamic-array.lo `test -f 'common/dynamic-array.c' || echo '$(srcdir)/'`common/dynamic-array.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dynamic-array.Tpo $(DEPDIR)/dynamic-array.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/dynamic-array.c' object='dynamic-array.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dynamic-array.lo `test -f 'common/dynamic-array.c' || echo '$(srcdir)/'`common/dynamic-array.c + +skip-list.lo: common/skip-list.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT skip-list.lo -MD -MP -MF $(DEPDIR)/skip-list.Tpo -c -o skip-list.lo `test -f 'common/skip-list.c' || echo '$(srcdir)/'`common/skip-list.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/skip-list.Tpo $(DEPDIR)/skip-list.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/skip-list.c' object='skip-list.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o skip-list.lo `test -f 'common/skip-list.c' || echo '$(srcdir)/'`common/skip-list.c + +base32hex.lo: common/base32hex.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT base32hex.lo -MD -MP -MF $(DEPDIR)/base32hex.Tpo -c -o base32hex.lo `test -f 'common/base32hex.c' || echo '$(srcdir)/'`common/base32hex.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/base32hex.Tpo $(DEPDIR)/base32hex.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/base32hex.c' object='base32hex.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o base32hex.lo `test -f 'common/base32hex.c' || echo '$(srcdir)/'`common/base32hex.c + +general-tree.lo: common/general-tree.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT general-tree.lo -MD -MP -MF $(DEPDIR)/general-tree.Tpo -c -o general-tree.lo `test -f 'common/general-tree.c' || echo '$(srcdir)/'`common/general-tree.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/general-tree.Tpo $(DEPDIR)/general-tree.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/general-tree.c' object='general-tree.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o general-tree.lo `test -f 'common/general-tree.c' || echo '$(srcdir)/'`common/general-tree.c + +evqueue.lo: common/evqueue.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT evqueue.lo -MD -MP -MF $(DEPDIR)/evqueue.Tpo -c -o evqueue.lo `test -f 'common/evqueue.c' || echo '$(srcdir)/'`common/evqueue.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/evqueue.Tpo $(DEPDIR)/evqueue.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/evqueue.c' object='evqueue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o evqueue.lo `test -f 'common/evqueue.c' || echo '$(srcdir)/'`common/evqueue.c + +evsched.lo: common/evsched.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT evsched.lo -MD -MP -MF $(DEPDIR)/evsched.Tpo -c -o evsched.lo `test -f 'common/evsched.c' || echo '$(srcdir)/'`common/evsched.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/evsched.Tpo $(DEPDIR)/evsched.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/evsched.c' object='evsched.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o evsched.lo `test -f 'common/evsched.c' || echo '$(srcdir)/'`common/evsched.c + +acl.lo: common/acl.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT acl.lo -MD -MP -MF $(DEPDIR)/acl.Tpo -c -o acl.lo `test -f 'common/acl.c' || echo '$(srcdir)/'`common/acl.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/acl.Tpo $(DEPDIR)/acl.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/acl.c' object='acl.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o acl.lo `test -f 'common/acl.c' || echo '$(srcdir)/'`common/acl.c + +sockaddr.lo: common/sockaddr.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT sockaddr.lo -MD -MP -MF $(DEPDIR)/sockaddr.Tpo -c -o sockaddr.lo `test -f 'common/sockaddr.c' || echo '$(srcdir)/'`common/sockaddr.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/sockaddr.Tpo $(DEPDIR)/sockaddr.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/sockaddr.c' object='sockaddr.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o sockaddr.lo `test -f 'common/sockaddr.c' || echo '$(srcdir)/'`common/sockaddr.c + +crc.lo: common/crc.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT crc.lo -MD -MP -MF $(DEPDIR)/crc.Tpo -c -o crc.lo `test -f 'common/crc.c' || echo '$(srcdir)/'`common/crc.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/crc.Tpo $(DEPDIR)/crc.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/crc.c' object='crc.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o crc.lo `test -f 'common/crc.c' || echo '$(srcdir)/'`common/crc.c + +ref.lo: common/ref.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ref.lo -MD -MP -MF $(DEPDIR)/ref.Tpo -c -o ref.lo `test -f 'common/ref.c' || echo '$(srcdir)/'`common/ref.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/ref.Tpo $(DEPDIR)/ref.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/ref.c' object='ref.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ref.lo `test -f 'common/ref.c' || echo '$(srcdir)/'`common/ref.c + +errors.lo: common/errors.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT errors.lo -MD -MP -MF $(DEPDIR)/errors.Tpo -c -o errors.lo `test -f 'common/errors.c' || echo '$(srcdir)/'`common/errors.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/errors.Tpo $(DEPDIR)/errors.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/errors.c' object='errors.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o errors.lo `test -f 'common/errors.c' || echo '$(srcdir)/'`common/errors.c + +WELL1024a.lo: common/WELL1024a.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT WELL1024a.lo -MD -MP -MF $(DEPDIR)/WELL1024a.Tpo -c -o WELL1024a.lo `test -f 'common/WELL1024a.c' || echo '$(srcdir)/'`common/WELL1024a.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/WELL1024a.Tpo $(DEPDIR)/WELL1024a.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/WELL1024a.c' object='WELL1024a.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o WELL1024a.lo `test -f 'common/WELL1024a.c' || echo '$(srcdir)/'`common/WELL1024a.c + +fdset.lo: common/fdset.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fdset.lo -MD -MP -MF $(DEPDIR)/fdset.Tpo -c -o fdset.lo `test -f 'common/fdset.c' || echo '$(srcdir)/'`common/fdset.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/fdset.Tpo $(DEPDIR)/fdset.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/fdset.c' object='fdset.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fdset.lo `test -f 'common/fdset.c' || echo '$(srcdir)/'`common/fdset.c + +fdset_poll.lo: common/fdset_poll.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fdset_poll.lo -MD -MP -MF $(DEPDIR)/fdset_poll.Tpo -c -o fdset_poll.lo `test -f 'common/fdset_poll.c' || echo '$(srcdir)/'`common/fdset_poll.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/fdset_poll.Tpo $(DEPDIR)/fdset_poll.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/fdset_poll.c' object='fdset_poll.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fdset_poll.lo `test -f 'common/fdset_poll.c' || echo '$(srcdir)/'`common/fdset_poll.c + +fdset_kqueue.lo: common/fdset_kqueue.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fdset_kqueue.lo -MD -MP -MF $(DEPDIR)/fdset_kqueue.Tpo -c -o fdset_kqueue.lo `test -f 'common/fdset_kqueue.c' || echo '$(srcdir)/'`common/fdset_kqueue.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/fdset_kqueue.Tpo $(DEPDIR)/fdset_kqueue.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/fdset_kqueue.c' object='fdset_kqueue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fdset_kqueue.lo `test -f 'common/fdset_kqueue.c' || echo '$(srcdir)/'`common/fdset_kqueue.c + +fdset_epoll.lo: common/fdset_epoll.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fdset_epoll.lo -MD -MP -MF $(DEPDIR)/fdset_epoll.Tpo -c -o fdset_epoll.lo `test -f 'common/fdset_epoll.c' || echo '$(srcdir)/'`common/fdset_epoll.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/fdset_epoll.Tpo $(DEPDIR)/fdset_epoll.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/fdset_epoll.c' object='fdset_epoll.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fdset_epoll.lo `test -f 'common/fdset_epoll.c' || echo '$(srcdir)/'`common/fdset_epoll.c + +zcompile_main.o: zcompile/zcompile_main.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zcompile_main.o -MD -MP -MF $(DEPDIR)/zcompile_main.Tpo -c -o zcompile_main.o `test -f 'zcompile/zcompile_main.c' || echo '$(srcdir)/'`zcompile/zcompile_main.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zcompile_main.Tpo $(DEPDIR)/zcompile_main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zcompile/zcompile_main.c' object='zcompile_main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zcompile_main.o `test -f 'zcompile/zcompile_main.c' || echo '$(srcdir)/'`zcompile/zcompile_main.c + +zcompile_main.obj: zcompile/zcompile_main.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zcompile_main.obj -MD -MP -MF $(DEPDIR)/zcompile_main.Tpo -c -o zcompile_main.obj `if test -f 'zcompile/zcompile_main.c'; then $(CYGPATH_W) 'zcompile/zcompile_main.c'; else $(CYGPATH_W) '$(srcdir)/zcompile/zcompile_main.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zcompile_main.Tpo $(DEPDIR)/zcompile_main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zcompile/zcompile_main.c' object='zcompile_main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zcompile_main.obj `if test -f 'zcompile/zcompile_main.c'; then $(CYGPATH_W) 'zcompile/zcompile_main.c'; else $(CYGPATH_W) '$(srcdir)/zcompile/zcompile_main.c'; fi` + +zcompile-error.o: zcompile/zcompile-error.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zcompile-error.o -MD -MP -MF $(DEPDIR)/zcompile-error.Tpo -c -o zcompile-error.o `test -f 'zcompile/zcompile-error.c' || echo '$(srcdir)/'`zcompile/zcompile-error.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zcompile-error.Tpo $(DEPDIR)/zcompile-error.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zcompile/zcompile-error.c' object='zcompile-error.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zcompile-error.o `test -f 'zcompile/zcompile-error.c' || echo '$(srcdir)/'`zcompile/zcompile-error.c + +zcompile-error.obj: zcompile/zcompile-error.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zcompile-error.obj -MD -MP -MF $(DEPDIR)/zcompile-error.Tpo -c -o zcompile-error.obj `if test -f 'zcompile/zcompile-error.c'; then $(CYGPATH_W) 'zcompile/zcompile-error.c'; else $(CYGPATH_W) '$(srcdir)/zcompile/zcompile-error.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zcompile-error.Tpo $(DEPDIR)/zcompile-error.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zcompile/zcompile-error.c' object='zcompile-error.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zcompile-error.obj `if test -f 'zcompile/zcompile-error.c'; then $(CYGPATH_W) 'zcompile/zcompile-error.c'; else $(CYGPATH_W) '$(srcdir)/zcompile/zcompile-error.c'; fi` + +zcompile.o: zcompile/zcompile.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zcompile.o -MD -MP -MF $(DEPDIR)/zcompile.Tpo -c -o zcompile.o `test -f 'zcompile/zcompile.c' || echo '$(srcdir)/'`zcompile/zcompile.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zcompile.Tpo $(DEPDIR)/zcompile.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zcompile/zcompile.c' object='zcompile.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zcompile.o `test -f 'zcompile/zcompile.c' || echo '$(srcdir)/'`zcompile/zcompile.c + +zcompile.obj: zcompile/zcompile.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zcompile.obj -MD -MP -MF $(DEPDIR)/zcompile.Tpo -c -o zcompile.obj `if test -f 'zcompile/zcompile.c'; then $(CYGPATH_W) 'zcompile/zcompile.c'; else $(CYGPATH_W) '$(srcdir)/zcompile/zcompile.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zcompile.Tpo $(DEPDIR)/zcompile.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zcompile/zcompile.c' object='zcompile.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zcompile.obj `if test -f 'zcompile/zcompile.c'; then $(CYGPATH_W) 'zcompile/zcompile.c'; else $(CYGPATH_W) '$(srcdir)/zcompile/zcompile.c'; fi` + +parser-util.o: zcompile/parser-util.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT parser-util.o -MD -MP -MF $(DEPDIR)/parser-util.Tpo -c -o parser-util.o `test -f 'zcompile/parser-util.c' || echo '$(srcdir)/'`zcompile/parser-util.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/parser-util.Tpo $(DEPDIR)/parser-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zcompile/parser-util.c' object='parser-util.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o parser-util.o `test -f 'zcompile/parser-util.c' || echo '$(srcdir)/'`zcompile/parser-util.c + +parser-util.obj: zcompile/parser-util.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT parser-util.obj -MD -MP -MF $(DEPDIR)/parser-util.Tpo -c -o parser-util.obj `if test -f 'zcompile/parser-util.c'; then $(CYGPATH_W) 'zcompile/parser-util.c'; else $(CYGPATH_W) '$(srcdir)/zcompile/parser-util.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/parser-util.Tpo $(DEPDIR)/parser-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zcompile/parser-util.c' object='parser-util.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o parser-util.obj `if test -f 'zcompile/parser-util.c'; then $(CYGPATH_W) 'zcompile/parser-util.c'; else $(CYGPATH_W) '$(srcdir)/zcompile/parser-util.c'; fi` + +parser-descriptor.o: zcompile/parser-descriptor.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT parser-descriptor.o -MD -MP -MF $(DEPDIR)/parser-descriptor.Tpo -c -o parser-descriptor.o `test -f 'zcompile/parser-descriptor.c' || echo '$(srcdir)/'`zcompile/parser-descriptor.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/parser-descriptor.Tpo $(DEPDIR)/parser-descriptor.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zcompile/parser-descriptor.c' object='parser-descriptor.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o parser-descriptor.o `test -f 'zcompile/parser-descriptor.c' || echo '$(srcdir)/'`zcompile/parser-descriptor.c + +parser-descriptor.obj: zcompile/parser-descriptor.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT parser-descriptor.obj -MD -MP -MF $(DEPDIR)/parser-descriptor.Tpo -c -o parser-descriptor.obj `if test -f 'zcompile/parser-descriptor.c'; then $(CYGPATH_W) 'zcompile/parser-descriptor.c'; else $(CYGPATH_W) '$(srcdir)/zcompile/parser-descriptor.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/parser-descriptor.Tpo $(DEPDIR)/parser-descriptor.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zcompile/parser-descriptor.c' object='parser-descriptor.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o parser-descriptor.obj `if test -f 'zcompile/parser-descriptor.c'; then $(CYGPATH_W) 'zcompile/parser-descriptor.c'; else $(CYGPATH_W) '$(srcdir)/zcompile/parser-descriptor.c'; fi` + +knotc_main.o: knot/ctl/knotc_main.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knotc_main.o -MD -MP -MF $(DEPDIR)/knotc_main.Tpo -c -o knotc_main.o `test -f 'knot/ctl/knotc_main.c' || echo '$(srcdir)/'`knot/ctl/knotc_main.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/knotc_main.Tpo $(DEPDIR)/knotc_main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/ctl/knotc_main.c' object='knotc_main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knotc_main.o `test -f 'knot/ctl/knotc_main.c' || echo '$(srcdir)/'`knot/ctl/knotc_main.c + +knotc_main.obj: knot/ctl/knotc_main.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT knotc_main.obj -MD -MP -MF $(DEPDIR)/knotc_main.Tpo -c -o knotc_main.obj `if test -f 'knot/ctl/knotc_main.c'; then $(CYGPATH_W) 'knot/ctl/knotc_main.c'; else $(CYGPATH_W) '$(srcdir)/knot/ctl/knotc_main.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/knotc_main.Tpo $(DEPDIR)/knotc_main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/ctl/knotc_main.c' object='knotc_main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o knotc_main.obj `if test -f 'knot/ctl/knotc_main.c'; then $(CYGPATH_W) 'knot/ctl/knotc_main.c'; else $(CYGPATH_W) '$(srcdir)/knot/ctl/knotc_main.c'; fi` + +main.o: knot/main.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT main.o -MD -MP -MF $(DEPDIR)/main.Tpo -c -o main.o `test -f 'knot/main.c' || echo '$(srcdir)/'`knot/main.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/main.Tpo $(DEPDIR)/main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/main.c' object='main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o main.o `test -f 'knot/main.c' || echo '$(srcdir)/'`knot/main.c + +main.obj: knot/main.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT main.obj -MD -MP -MF $(DEPDIR)/main.Tpo -c -o main.obj `if test -f 'knot/main.c'; then $(CYGPATH_W) 'knot/main.c'; else $(CYGPATH_W) '$(srcdir)/knot/main.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/main.Tpo $(DEPDIR)/main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/main.c' object='main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o main.obj `if test -f 'knot/main.c'; then $(CYGPATH_W) 'knot/main.c'; else $(CYGPATH_W) '$(srcdir)/knot/main.c'; fi` + +acl_tests.o: tests/common/acl_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT acl_tests.o -MD -MP -MF $(DEPDIR)/acl_tests.Tpo -c -o acl_tests.o `test -f 'tests/common/acl_tests.c' || echo '$(srcdir)/'`tests/common/acl_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/acl_tests.Tpo $(DEPDIR)/acl_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/acl_tests.c' object='acl_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o acl_tests.o `test -f 'tests/common/acl_tests.c' || echo '$(srcdir)/'`tests/common/acl_tests.c + +acl_tests.obj: tests/common/acl_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT acl_tests.obj -MD -MP -MF $(DEPDIR)/acl_tests.Tpo -c -o acl_tests.obj `if test -f 'tests/common/acl_tests.c'; then $(CYGPATH_W) 'tests/common/acl_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/acl_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/acl_tests.Tpo $(DEPDIR)/acl_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/acl_tests.c' object='acl_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o acl_tests.obj `if test -f 'tests/common/acl_tests.c'; then $(CYGPATH_W) 'tests/common/acl_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/acl_tests.c'; fi` + +da_tests.o: tests/common/da_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT da_tests.o -MD -MP -MF $(DEPDIR)/da_tests.Tpo -c -o da_tests.o `test -f 'tests/common/da_tests.c' || echo '$(srcdir)/'`tests/common/da_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/da_tests.Tpo $(DEPDIR)/da_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/da_tests.c' object='da_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o da_tests.o `test -f 'tests/common/da_tests.c' || echo '$(srcdir)/'`tests/common/da_tests.c + +da_tests.obj: tests/common/da_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT da_tests.obj -MD -MP -MF $(DEPDIR)/da_tests.Tpo -c -o da_tests.obj `if test -f 'tests/common/da_tests.c'; then $(CYGPATH_W) 'tests/common/da_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/da_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/da_tests.Tpo $(DEPDIR)/da_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/da_tests.c' object='da_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o da_tests.obj `if test -f 'tests/common/da_tests.c'; then $(CYGPATH_W) 'tests/common/da_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/da_tests.c'; fi` + +events_tests.o: tests/common/events_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT events_tests.o -MD -MP -MF $(DEPDIR)/events_tests.Tpo -c -o events_tests.o `test -f 'tests/common/events_tests.c' || echo '$(srcdir)/'`tests/common/events_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/events_tests.Tpo $(DEPDIR)/events_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/events_tests.c' object='events_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o events_tests.o `test -f 'tests/common/events_tests.c' || echo '$(srcdir)/'`tests/common/events_tests.c + +events_tests.obj: tests/common/events_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT events_tests.obj -MD -MP -MF $(DEPDIR)/events_tests.Tpo -c -o events_tests.obj `if test -f 'tests/common/events_tests.c'; then $(CYGPATH_W) 'tests/common/events_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/events_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/events_tests.Tpo $(DEPDIR)/events_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/events_tests.c' object='events_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o events_tests.obj `if test -f 'tests/common/events_tests.c'; then $(CYGPATH_W) 'tests/common/events_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/events_tests.c'; fi` + +skiplist_tests.o: tests/common/skiplist_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT skiplist_tests.o -MD -MP -MF $(DEPDIR)/skiplist_tests.Tpo -c -o skiplist_tests.o `test -f 'tests/common/skiplist_tests.c' || echo '$(srcdir)/'`tests/common/skiplist_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/skiplist_tests.Tpo $(DEPDIR)/skiplist_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/skiplist_tests.c' object='skiplist_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o skiplist_tests.o `test -f 'tests/common/skiplist_tests.c' || echo '$(srcdir)/'`tests/common/skiplist_tests.c + +skiplist_tests.obj: tests/common/skiplist_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT skiplist_tests.obj -MD -MP -MF $(DEPDIR)/skiplist_tests.Tpo -c -o skiplist_tests.obj `if test -f 'tests/common/skiplist_tests.c'; then $(CYGPATH_W) 'tests/common/skiplist_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/skiplist_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/skiplist_tests.Tpo $(DEPDIR)/skiplist_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/skiplist_tests.c' object='skiplist_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o skiplist_tests.obj `if test -f 'tests/common/skiplist_tests.c'; then $(CYGPATH_W) 'tests/common/skiplist_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/skiplist_tests.c'; fi` + +slab_tests.o: tests/common/slab_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT slab_tests.o -MD -MP -MF $(DEPDIR)/slab_tests.Tpo -c -o slab_tests.o `test -f 'tests/common/slab_tests.c' || echo '$(srcdir)/'`tests/common/slab_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/slab_tests.Tpo $(DEPDIR)/slab_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/slab_tests.c' object='slab_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o slab_tests.o `test -f 'tests/common/slab_tests.c' || echo '$(srcdir)/'`tests/common/slab_tests.c + +slab_tests.obj: tests/common/slab_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT slab_tests.obj -MD -MP -MF $(DEPDIR)/slab_tests.Tpo -c -o slab_tests.obj `if test -f 'tests/common/slab_tests.c'; then $(CYGPATH_W) 'tests/common/slab_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/slab_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/slab_tests.Tpo $(DEPDIR)/slab_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/slab_tests.c' object='slab_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o slab_tests.obj `if test -f 'tests/common/slab_tests.c'; then $(CYGPATH_W) 'tests/common/slab_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/slab_tests.c'; fi` + +fdset_tests.o: tests/common/fdset_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fdset_tests.o -MD -MP -MF $(DEPDIR)/fdset_tests.Tpo -c -o fdset_tests.o `test -f 'tests/common/fdset_tests.c' || echo '$(srcdir)/'`tests/common/fdset_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/fdset_tests.Tpo $(DEPDIR)/fdset_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/fdset_tests.c' object='fdset_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fdset_tests.o `test -f 'tests/common/fdset_tests.c' || echo '$(srcdir)/'`tests/common/fdset_tests.c + +fdset_tests.obj: tests/common/fdset_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fdset_tests.obj -MD -MP -MF $(DEPDIR)/fdset_tests.Tpo -c -o fdset_tests.obj `if test -f 'tests/common/fdset_tests.c'; then $(CYGPATH_W) 'tests/common/fdset_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/fdset_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/fdset_tests.Tpo $(DEPDIR)/fdset_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/fdset_tests.c' object='fdset_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fdset_tests.obj `if test -f 'tests/common/fdset_tests.c'; then $(CYGPATH_W) 'tests/common/fdset_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/fdset_tests.c'; fi` + +conf_tests.o: tests/knot/conf_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT conf_tests.o -MD -MP -MF $(DEPDIR)/conf_tests.Tpo -c -o conf_tests.o `test -f 'tests/knot/conf_tests.c' || echo '$(srcdir)/'`tests/knot/conf_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/conf_tests.Tpo $(DEPDIR)/conf_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/knot/conf_tests.c' object='conf_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o conf_tests.o `test -f 'tests/knot/conf_tests.c' || echo '$(srcdir)/'`tests/knot/conf_tests.c + +conf_tests.obj: tests/knot/conf_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT conf_tests.obj -MD -MP -MF $(DEPDIR)/conf_tests.Tpo -c -o conf_tests.obj `if test -f 'tests/knot/conf_tests.c'; then $(CYGPATH_W) 'tests/knot/conf_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/knot/conf_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/conf_tests.Tpo $(DEPDIR)/conf_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/knot/conf_tests.c' object='conf_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o conf_tests.obj `if test -f 'tests/knot/conf_tests.c'; then $(CYGPATH_W) 'tests/knot/conf_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/knot/conf_tests.c'; fi` + +dthreads_tests.o: tests/knot/dthreads_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dthreads_tests.o -MD -MP -MF $(DEPDIR)/dthreads_tests.Tpo -c -o dthreads_tests.o `test -f 'tests/knot/dthreads_tests.c' || echo '$(srcdir)/'`tests/knot/dthreads_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dthreads_tests.Tpo $(DEPDIR)/dthreads_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/knot/dthreads_tests.c' object='dthreads_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dthreads_tests.o `test -f 'tests/knot/dthreads_tests.c' || echo '$(srcdir)/'`tests/knot/dthreads_tests.c + +dthreads_tests.obj: tests/knot/dthreads_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dthreads_tests.obj -MD -MP -MF $(DEPDIR)/dthreads_tests.Tpo -c -o dthreads_tests.obj `if test -f 'tests/knot/dthreads_tests.c'; then $(CYGPATH_W) 'tests/knot/dthreads_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/knot/dthreads_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dthreads_tests.Tpo $(DEPDIR)/dthreads_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/knot/dthreads_tests.c' object='dthreads_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dthreads_tests.obj `if test -f 'tests/knot/dthreads_tests.c'; then $(CYGPATH_W) 'tests/knot/dthreads_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/knot/dthreads_tests.c'; fi` + +journal_tests.o: tests/knot/journal_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT journal_tests.o -MD -MP -MF $(DEPDIR)/journal_tests.Tpo -c -o journal_tests.o `test -f 'tests/knot/journal_tests.c' || echo '$(srcdir)/'`tests/knot/journal_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/journal_tests.Tpo $(DEPDIR)/journal_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/knot/journal_tests.c' object='journal_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o journal_tests.o `test -f 'tests/knot/journal_tests.c' || echo '$(srcdir)/'`tests/knot/journal_tests.c + +journal_tests.obj: tests/knot/journal_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT journal_tests.obj -MD -MP -MF $(DEPDIR)/journal_tests.Tpo -c -o journal_tests.obj `if test -f 'tests/knot/journal_tests.c'; then $(CYGPATH_W) 'tests/knot/journal_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/knot/journal_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/journal_tests.Tpo $(DEPDIR)/journal_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/knot/journal_tests.c' object='journal_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o journal_tests.obj `if test -f 'tests/knot/journal_tests.c'; then $(CYGPATH_W) 'tests/knot/journal_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/knot/journal_tests.c'; fi` + +server_tests.o: tests/knot/server_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT server_tests.o -MD -MP -MF $(DEPDIR)/server_tests.Tpo -c -o server_tests.o `test -f 'tests/knot/server_tests.c' || echo '$(srcdir)/'`tests/knot/server_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/server_tests.Tpo $(DEPDIR)/server_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/knot/server_tests.c' object='server_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o server_tests.o `test -f 'tests/knot/server_tests.c' || echo '$(srcdir)/'`tests/knot/server_tests.c + +server_tests.obj: tests/knot/server_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT server_tests.obj -MD -MP -MF $(DEPDIR)/server_tests.Tpo -c -o server_tests.obj `if test -f 'tests/knot/server_tests.c'; then $(CYGPATH_W) 'tests/knot/server_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/knot/server_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/server_tests.Tpo $(DEPDIR)/server_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/knot/server_tests.c' object='server_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o server_tests.obj `if test -f 'tests/knot/server_tests.c'; then $(CYGPATH_W) 'tests/knot/server_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/knot/server_tests.c'; fi` + +unittests_main.o: tests/unittests_main.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unittests_main.o -MD -MP -MF $(DEPDIR)/unittests_main.Tpo -c -o unittests_main.o `test -f 'tests/unittests_main.c' || echo '$(srcdir)/'`tests/unittests_main.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/unittests_main.Tpo $(DEPDIR)/unittests_main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/unittests_main.c' object='unittests_main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unittests_main.o `test -f 'tests/unittests_main.c' || echo '$(srcdir)/'`tests/unittests_main.c + +unittests_main.obj: tests/unittests_main.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unittests_main.obj -MD -MP -MF $(DEPDIR)/unittests_main.Tpo -c -o unittests_main.obj `if test -f 'tests/unittests_main.c'; then $(CYGPATH_W) 'tests/unittests_main.c'; else $(CYGPATH_W) '$(srcdir)/tests/unittests_main.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/unittests_main.Tpo $(DEPDIR)/unittests_main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/unittests_main.c' object='unittests_main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unittests_main.obj `if test -f 'tests/unittests_main.c'; then $(CYGPATH_W) 'tests/unittests_main.c'; else $(CYGPATH_W) '$(srcdir)/tests/unittests_main.c'; fi` + +cuckoo_tests.o: tests/libknot/libknot/cuckoo_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT cuckoo_tests.o -MD -MP -MF $(DEPDIR)/cuckoo_tests.Tpo -c -o cuckoo_tests.o `test -f 'tests/libknot/libknot/cuckoo_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/cuckoo_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/cuckoo_tests.Tpo $(DEPDIR)/cuckoo_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/cuckoo_tests.c' object='cuckoo_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o cuckoo_tests.o `test -f 'tests/libknot/libknot/cuckoo_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/cuckoo_tests.c + +cuckoo_tests.obj: tests/libknot/libknot/cuckoo_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT cuckoo_tests.obj -MD -MP -MF $(DEPDIR)/cuckoo_tests.Tpo -c -o cuckoo_tests.obj `if test -f 'tests/libknot/libknot/cuckoo_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/cuckoo_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/cuckoo_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/cuckoo_tests.Tpo $(DEPDIR)/cuckoo_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/cuckoo_tests.c' object='cuckoo_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o cuckoo_tests.obj `if test -f 'tests/libknot/libknot/cuckoo_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/cuckoo_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/cuckoo_tests.c'; fi` + +response_tests.o: tests/libknot/libknot/response_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT response_tests.o -MD -MP -MF $(DEPDIR)/response_tests.Tpo -c -o response_tests.o `test -f 'tests/libknot/libknot/response_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/response_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/response_tests.Tpo $(DEPDIR)/response_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/response_tests.c' object='response_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o response_tests.o `test -f 'tests/libknot/libknot/response_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/response_tests.c + +response_tests.obj: tests/libknot/libknot/response_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT response_tests.obj -MD -MP -MF $(DEPDIR)/response_tests.Tpo -c -o response_tests.obj `if test -f 'tests/libknot/libknot/response_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/response_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/response_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/response_tests.Tpo $(DEPDIR)/response_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/response_tests.c' object='response_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o response_tests.obj `if test -f 'tests/libknot/libknot/response_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/response_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/response_tests.c'; fi` + +dname_tests.o: tests/libknot/libknot/dname_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dname_tests.o -MD -MP -MF $(DEPDIR)/dname_tests.Tpo -c -o dname_tests.o `test -f 'tests/libknot/libknot/dname_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/dname_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dname_tests.Tpo $(DEPDIR)/dname_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/dname_tests.c' object='dname_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dname_tests.o `test -f 'tests/libknot/libknot/dname_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/dname_tests.c + +dname_tests.obj: tests/libknot/libknot/dname_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dname_tests.obj -MD -MP -MF $(DEPDIR)/dname_tests.Tpo -c -o dname_tests.obj `if test -f 'tests/libknot/libknot/dname_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/dname_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/dname_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dname_tests.Tpo $(DEPDIR)/dname_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/dname_tests.c' object='dname_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dname_tests.obj `if test -f 'tests/libknot/libknot/dname_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/dname_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/dname_tests.c'; fi` + +dname_table_tests.o: tests/libknot/libknot/dname_table_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dname_table_tests.o -MD -MP -MF $(DEPDIR)/dname_table_tests.Tpo -c -o dname_table_tests.o `test -f 'tests/libknot/libknot/dname_table_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/dname_table_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dname_table_tests.Tpo $(DEPDIR)/dname_table_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/dname_table_tests.c' object='dname_table_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dname_table_tests.o `test -f 'tests/libknot/libknot/dname_table_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/dname_table_tests.c + +dname_table_tests.obj: tests/libknot/libknot/dname_table_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dname_table_tests.obj -MD -MP -MF $(DEPDIR)/dname_table_tests.Tpo -c -o dname_table_tests.obj `if test -f 'tests/libknot/libknot/dname_table_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/dname_table_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/dname_table_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dname_table_tests.Tpo $(DEPDIR)/dname_table_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/dname_table_tests.c' object='dname_table_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dname_table_tests.obj `if test -f 'tests/libknot/libknot/dname_table_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/dname_table_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/dname_table_tests.c'; fi` + +nsec3_tests.o: tests/libknot/libknot/nsec3_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nsec3_tests.o -MD -MP -MF $(DEPDIR)/nsec3_tests.Tpo -c -o nsec3_tests.o `test -f 'tests/libknot/libknot/nsec3_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/nsec3_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/nsec3_tests.Tpo $(DEPDIR)/nsec3_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/nsec3_tests.c' object='nsec3_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nsec3_tests.o `test -f 'tests/libknot/libknot/nsec3_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/nsec3_tests.c + +nsec3_tests.obj: tests/libknot/libknot/nsec3_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nsec3_tests.obj -MD -MP -MF $(DEPDIR)/nsec3_tests.Tpo -c -o nsec3_tests.obj `if test -f 'tests/libknot/libknot/nsec3_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/nsec3_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/nsec3_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/nsec3_tests.Tpo $(DEPDIR)/nsec3_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/nsec3_tests.c' object='nsec3_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nsec3_tests.obj `if test -f 'tests/libknot/libknot/nsec3_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/nsec3_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/nsec3_tests.c'; fi` + +packet_tests.o: tests/libknot/libknot/packet_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT packet_tests.o -MD -MP -MF $(DEPDIR)/packet_tests.Tpo -c -o packet_tests.o `test -f 'tests/libknot/libknot/packet_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/packet_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/packet_tests.Tpo $(DEPDIR)/packet_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/packet_tests.c' object='packet_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o packet_tests.o `test -f 'tests/libknot/libknot/packet_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/packet_tests.c + +packet_tests.obj: tests/libknot/libknot/packet_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT packet_tests.obj -MD -MP -MF $(DEPDIR)/packet_tests.Tpo -c -o packet_tests.obj `if test -f 'tests/libknot/libknot/packet_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/packet_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/packet_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/packet_tests.Tpo $(DEPDIR)/packet_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/packet_tests.c' object='packet_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o packet_tests.obj `if test -f 'tests/libknot/libknot/packet_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/packet_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/packet_tests.c'; fi` + +query_tests.o: tests/libknot/libknot/query_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT query_tests.o -MD -MP -MF $(DEPDIR)/query_tests.Tpo -c -o query_tests.o `test -f 'tests/libknot/libknot/query_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/query_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/query_tests.Tpo $(DEPDIR)/query_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/query_tests.c' object='query_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o query_tests.o `test -f 'tests/libknot/libknot/query_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/query_tests.c + +query_tests.obj: tests/libknot/libknot/query_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT query_tests.obj -MD -MP -MF $(DEPDIR)/query_tests.Tpo -c -o query_tests.obj `if test -f 'tests/libknot/libknot/query_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/query_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/query_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/query_tests.Tpo $(DEPDIR)/query_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/query_tests.c' object='query_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o query_tests.obj `if test -f 'tests/libknot/libknot/query_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/query_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/query_tests.c'; fi` + +edns_tests.o: tests/libknot/libknot/edns_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT edns_tests.o -MD -MP -MF $(DEPDIR)/edns_tests.Tpo -c -o edns_tests.o `test -f 'tests/libknot/libknot/edns_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/edns_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/edns_tests.Tpo $(DEPDIR)/edns_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/edns_tests.c' object='edns_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o edns_tests.o `test -f 'tests/libknot/libknot/edns_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/edns_tests.c + +edns_tests.obj: tests/libknot/libknot/edns_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT edns_tests.obj -MD -MP -MF $(DEPDIR)/edns_tests.Tpo -c -o edns_tests.obj `if test -f 'tests/libknot/libknot/edns_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/edns_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/edns_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/edns_tests.Tpo $(DEPDIR)/edns_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/edns_tests.c' object='edns_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o edns_tests.obj `if test -f 'tests/libknot/libknot/edns_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/edns_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/edns_tests.c'; fi` + +node_tests.o: tests/libknot/libknot/node_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT node_tests.o -MD -MP -MF $(DEPDIR)/node_tests.Tpo -c -o node_tests.o `test -f 'tests/libknot/libknot/node_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/node_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/node_tests.Tpo $(DEPDIR)/node_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/node_tests.c' object='node_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o node_tests.o `test -f 'tests/libknot/libknot/node_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/node_tests.c + +node_tests.obj: tests/libknot/libknot/node_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT node_tests.obj -MD -MP -MF $(DEPDIR)/node_tests.Tpo -c -o node_tests.obj `if test -f 'tests/libknot/libknot/node_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/node_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/node_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/node_tests.Tpo $(DEPDIR)/node_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/node_tests.c' object='node_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o node_tests.obj `if test -f 'tests/libknot/libknot/node_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/node_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/node_tests.c'; fi` + +rdata_tests.o: tests/libknot/libknot/rdata_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rdata_tests.o -MD -MP -MF $(DEPDIR)/rdata_tests.Tpo -c -o rdata_tests.o `test -f 'tests/libknot/libknot/rdata_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/rdata_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rdata_tests.Tpo $(DEPDIR)/rdata_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/rdata_tests.c' object='rdata_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rdata_tests.o `test -f 'tests/libknot/libknot/rdata_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/rdata_tests.c + +rdata_tests.obj: tests/libknot/libknot/rdata_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rdata_tests.obj -MD -MP -MF $(DEPDIR)/rdata_tests.Tpo -c -o rdata_tests.obj `if test -f 'tests/libknot/libknot/rdata_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/rdata_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/rdata_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rdata_tests.Tpo $(DEPDIR)/rdata_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/rdata_tests.c' object='rdata_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rdata_tests.obj `if test -f 'tests/libknot/libknot/rdata_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/rdata_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/rdata_tests.c'; fi` + +rrset_tests.o: tests/libknot/libknot/rrset_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rrset_tests.o -MD -MP -MF $(DEPDIR)/rrset_tests.Tpo -c -o rrset_tests.o `test -f 'tests/libknot/libknot/rrset_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/rrset_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rrset_tests.Tpo $(DEPDIR)/rrset_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/rrset_tests.c' object='rrset_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rrset_tests.o `test -f 'tests/libknot/libknot/rrset_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/rrset_tests.c + +rrset_tests.obj: tests/libknot/libknot/rrset_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rrset_tests.obj -MD -MP -MF $(DEPDIR)/rrset_tests.Tpo -c -o rrset_tests.obj `if test -f 'tests/libknot/libknot/rrset_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/rrset_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/rrset_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rrset_tests.Tpo $(DEPDIR)/rrset_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/rrset_tests.c' object='rrset_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rrset_tests.obj `if test -f 'tests/libknot/libknot/rrset_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/rrset_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/rrset_tests.c'; fi` + +zone_tests.o: tests/libknot/libknot/zone_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone_tests.o -MD -MP -MF $(DEPDIR)/zone_tests.Tpo -c -o zone_tests.o `test -f 'tests/libknot/libknot/zone_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/zone_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone_tests.Tpo $(DEPDIR)/zone_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/zone_tests.c' object='zone_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone_tests.o `test -f 'tests/libknot/libknot/zone_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/zone_tests.c + +zone_tests.obj: tests/libknot/libknot/zone_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone_tests.obj -MD -MP -MF $(DEPDIR)/zone_tests.Tpo -c -o zone_tests.obj `if test -f 'tests/libknot/libknot/zone_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/zone_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/zone_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone_tests.Tpo $(DEPDIR)/zone_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/zone_tests.c' object='zone_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone_tests.obj `if test -f 'tests/libknot/libknot/zone_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/zone_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/zone_tests.c'; fi` + +zone_tree_tests.o: tests/libknot/libknot/zone_tree_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone_tree_tests.o -MD -MP -MF $(DEPDIR)/zone_tree_tests.Tpo -c -o zone_tree_tests.o `test -f 'tests/libknot/libknot/zone_tree_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/zone_tree_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone_tree_tests.Tpo $(DEPDIR)/zone_tree_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/zone_tree_tests.c' object='zone_tree_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone_tree_tests.o `test -f 'tests/libknot/libknot/zone_tree_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/zone_tree_tests.c + +zone_tree_tests.obj: tests/libknot/libknot/zone_tree_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone_tree_tests.obj -MD -MP -MF $(DEPDIR)/zone_tree_tests.Tpo -c -o zone_tree_tests.obj `if test -f 'tests/libknot/libknot/zone_tree_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/zone_tree_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/zone_tree_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone_tree_tests.Tpo $(DEPDIR)/zone_tree_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/zone_tree_tests.c' object='zone_tree_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone_tree_tests.obj `if test -f 'tests/libknot/libknot/zone_tree_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/zone_tree_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/zone_tree_tests.c'; fi` + +zonedb_tests.o: tests/libknot/libknot/zonedb_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zonedb_tests.o -MD -MP -MF $(DEPDIR)/zonedb_tests.Tpo -c -o zonedb_tests.o `test -f 'tests/libknot/libknot/zonedb_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/zonedb_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zonedb_tests.Tpo $(DEPDIR)/zonedb_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/zonedb_tests.c' object='zonedb_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zonedb_tests.o `test -f 'tests/libknot/libknot/zonedb_tests.c' || echo '$(srcdir)/'`tests/libknot/libknot/zonedb_tests.c + +zonedb_tests.obj: tests/libknot/libknot/zonedb_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zonedb_tests.obj -MD -MP -MF $(DEPDIR)/zonedb_tests.Tpo -c -o zonedb_tests.obj `if test -f 'tests/libknot/libknot/zonedb_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/zonedb_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/zonedb_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zonedb_tests.Tpo $(DEPDIR)/zonedb_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/libknot/zonedb_tests.c' object='zonedb_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zonedb_tests.obj `if test -f 'tests/libknot/libknot/zonedb_tests.c'; then $(CYGPATH_W) 'tests/libknot/libknot/zonedb_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/libknot/zonedb_tests.c'; fi` + +unittests_libknot.o: tests/libknot/unittests_libknot.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unittests_libknot.o -MD -MP -MF $(DEPDIR)/unittests_libknot.Tpo -c -o unittests_libknot.o `test -f 'tests/libknot/unittests_libknot.c' || echo '$(srcdir)/'`tests/libknot/unittests_libknot.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/unittests_libknot.Tpo $(DEPDIR)/unittests_libknot.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/unittests_libknot.c' object='unittests_libknot.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unittests_libknot.o `test -f 'tests/libknot/unittests_libknot.c' || echo '$(srcdir)/'`tests/libknot/unittests_libknot.c + +unittests_libknot.obj: tests/libknot/unittests_libknot.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unittests_libknot.obj -MD -MP -MF $(DEPDIR)/unittests_libknot.Tpo -c -o unittests_libknot.obj `if test -f 'tests/libknot/unittests_libknot.c'; then $(CYGPATH_W) 'tests/libknot/unittests_libknot.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/unittests_libknot.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/unittests_libknot.Tpo $(DEPDIR)/unittests_libknot.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/unittests_libknot.c' object='unittests_libknot.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unittests_libknot.obj `if test -f 'tests/libknot/unittests_libknot.c'; then $(CYGPATH_W) 'tests/libknot/unittests_libknot.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/unittests_libknot.c'; fi` + +dname_tests_realdata.o: tests/libknot/realdata/libknot/dname_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dname_tests_realdata.o -MD -MP -MF $(DEPDIR)/dname_tests_realdata.Tpo -c -o dname_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/dname_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/dname_tests_realdata.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dname_tests_realdata.Tpo $(DEPDIR)/dname_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/dname_tests_realdata.c' object='dname_tests_realdata.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dname_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/dname_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/dname_tests_realdata.c + +dname_tests_realdata.obj: tests/libknot/realdata/libknot/dname_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dname_tests_realdata.obj -MD -MP -MF $(DEPDIR)/dname_tests_realdata.Tpo -c -o dname_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/dname_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/dname_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/dname_tests_realdata.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dname_tests_realdata.Tpo $(DEPDIR)/dname_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/dname_tests_realdata.c' object='dname_tests_realdata.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dname_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/dname_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/dname_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/dname_tests_realdata.c'; fi` + +response_tests_realdata.o: tests/libknot/realdata/libknot/response_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT response_tests_realdata.o -MD -MP -MF $(DEPDIR)/response_tests_realdata.Tpo -c -o response_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/response_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/response_tests_realdata.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/response_tests_realdata.Tpo $(DEPDIR)/response_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/response_tests_realdata.c' object='response_tests_realdata.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o response_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/response_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/response_tests_realdata.c + +response_tests_realdata.obj: tests/libknot/realdata/libknot/response_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT response_tests_realdata.obj -MD -MP -MF $(DEPDIR)/response_tests_realdata.Tpo -c -o response_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/response_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/response_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/response_tests_realdata.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/response_tests_realdata.Tpo $(DEPDIR)/response_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/response_tests_realdata.c' object='response_tests_realdata.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o response_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/response_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/response_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/response_tests_realdata.c'; fi` + +edns_tests_realdata.o: tests/libknot/realdata/libknot/edns_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT edns_tests_realdata.o -MD -MP -MF $(DEPDIR)/edns_tests_realdata.Tpo -c -o edns_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/edns_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/edns_tests_realdata.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/edns_tests_realdata.Tpo $(DEPDIR)/edns_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/edns_tests_realdata.c' object='edns_tests_realdata.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o edns_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/edns_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/edns_tests_realdata.c + +edns_tests_realdata.obj: tests/libknot/realdata/libknot/edns_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT edns_tests_realdata.obj -MD -MP -MF $(DEPDIR)/edns_tests_realdata.Tpo -c -o edns_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/edns_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/edns_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/edns_tests_realdata.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/edns_tests_realdata.Tpo $(DEPDIR)/edns_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/edns_tests_realdata.c' object='edns_tests_realdata.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o edns_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/edns_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/edns_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/edns_tests_realdata.c'; fi` + +node_tests_realdata.o: tests/libknot/realdata/libknot/node_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT node_tests_realdata.o -MD -MP -MF $(DEPDIR)/node_tests_realdata.Tpo -c -o node_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/node_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/node_tests_realdata.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/node_tests_realdata.Tpo $(DEPDIR)/node_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/node_tests_realdata.c' object='node_tests_realdata.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o node_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/node_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/node_tests_realdata.c + +node_tests_realdata.obj: tests/libknot/realdata/libknot/node_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT node_tests_realdata.obj -MD -MP -MF $(DEPDIR)/node_tests_realdata.Tpo -c -o node_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/node_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/node_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/node_tests_realdata.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/node_tests_realdata.Tpo $(DEPDIR)/node_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/node_tests_realdata.c' object='node_tests_realdata.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o node_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/node_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/node_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/node_tests_realdata.c'; fi` + +rdata_tests_realdata.o: tests/libknot/realdata/libknot/rdata_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rdata_tests_realdata.o -MD -MP -MF $(DEPDIR)/rdata_tests_realdata.Tpo -c -o rdata_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/rdata_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/rdata_tests_realdata.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rdata_tests_realdata.Tpo $(DEPDIR)/rdata_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/rdata_tests_realdata.c' object='rdata_tests_realdata.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rdata_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/rdata_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/rdata_tests_realdata.c + +rdata_tests_realdata.obj: tests/libknot/realdata/libknot/rdata_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rdata_tests_realdata.obj -MD -MP -MF $(DEPDIR)/rdata_tests_realdata.Tpo -c -o rdata_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/rdata_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/rdata_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/rdata_tests_realdata.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rdata_tests_realdata.Tpo $(DEPDIR)/rdata_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/rdata_tests_realdata.c' object='rdata_tests_realdata.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rdata_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/rdata_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/rdata_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/rdata_tests_realdata.c'; fi` + +rrset_tests_realdata.o: tests/libknot/realdata/libknot/rrset_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rrset_tests_realdata.o -MD -MP -MF $(DEPDIR)/rrset_tests_realdata.Tpo -c -o rrset_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/rrset_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/rrset_tests_realdata.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rrset_tests_realdata.Tpo $(DEPDIR)/rrset_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/rrset_tests_realdata.c' object='rrset_tests_realdata.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rrset_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/rrset_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/rrset_tests_realdata.c + +rrset_tests_realdata.obj: tests/libknot/realdata/libknot/rrset_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rrset_tests_realdata.obj -MD -MP -MF $(DEPDIR)/rrset_tests_realdata.Tpo -c -o rrset_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/rrset_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/rrset_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/rrset_tests_realdata.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rrset_tests_realdata.Tpo $(DEPDIR)/rrset_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/rrset_tests_realdata.c' object='rrset_tests_realdata.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rrset_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/rrset_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/rrset_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/rrset_tests_realdata.c'; fi` + +zone_tests_realdata.o: tests/libknot/realdata/libknot/zone_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone_tests_realdata.o -MD -MP -MF $(DEPDIR)/zone_tests_realdata.Tpo -c -o zone_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/zone_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/zone_tests_realdata.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone_tests_realdata.Tpo $(DEPDIR)/zone_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/zone_tests_realdata.c' object='zone_tests_realdata.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/zone_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/zone_tests_realdata.c + +zone_tests_realdata.obj: tests/libknot/realdata/libknot/zone_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone_tests_realdata.obj -MD -MP -MF $(DEPDIR)/zone_tests_realdata.Tpo -c -o zone_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/zone_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/zone_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/zone_tests_realdata.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone_tests_realdata.Tpo $(DEPDIR)/zone_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/zone_tests_realdata.c' object='zone_tests_realdata.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/zone_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/zone_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/zone_tests_realdata.c'; fi` + +zonedb_tests_realdata.o: tests/libknot/realdata/libknot/zonedb_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zonedb_tests_realdata.o -MD -MP -MF $(DEPDIR)/zonedb_tests_realdata.Tpo -c -o zonedb_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/zonedb_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/zonedb_tests_realdata.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zonedb_tests_realdata.Tpo $(DEPDIR)/zonedb_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/zonedb_tests_realdata.c' object='zonedb_tests_realdata.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zonedb_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/zonedb_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/zonedb_tests_realdata.c + +zonedb_tests_realdata.obj: tests/libknot/realdata/libknot/zonedb_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zonedb_tests_realdata.obj -MD -MP -MF $(DEPDIR)/zonedb_tests_realdata.Tpo -c -o zonedb_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/zonedb_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/zonedb_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/zonedb_tests_realdata.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zonedb_tests_realdata.Tpo $(DEPDIR)/zonedb_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/zonedb_tests_realdata.c' object='zonedb_tests_realdata.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zonedb_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/zonedb_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/zonedb_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/zonedb_tests_realdata.c'; fi` + +packet_tests_realdata.o: tests/libknot/realdata/libknot/packet_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT packet_tests_realdata.o -MD -MP -MF $(DEPDIR)/packet_tests_realdata.Tpo -c -o packet_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/packet_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/packet_tests_realdata.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/packet_tests_realdata.Tpo $(DEPDIR)/packet_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/packet_tests_realdata.c' object='packet_tests_realdata.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o packet_tests_realdata.o `test -f 'tests/libknot/realdata/libknot/packet_tests_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot/packet_tests_realdata.c + +packet_tests_realdata.obj: tests/libknot/realdata/libknot/packet_tests_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT packet_tests_realdata.obj -MD -MP -MF $(DEPDIR)/packet_tests_realdata.Tpo -c -o packet_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/packet_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/packet_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/packet_tests_realdata.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/packet_tests_realdata.Tpo $(DEPDIR)/packet_tests_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot/packet_tests_realdata.c' object='packet_tests_realdata.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o packet_tests_realdata.obj `if test -f 'tests/libknot/realdata/libknot/packet_tests_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot/packet_tests_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot/packet_tests_realdata.c'; fi` + +libknot_tests_loader_realdata.o: tests/libknot/realdata/libknot_tests_loader_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libknot_tests_loader_realdata.o -MD -MP -MF $(DEPDIR)/libknot_tests_loader_realdata.Tpo -c -o libknot_tests_loader_realdata.o `test -f 'tests/libknot/realdata/libknot_tests_loader_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot_tests_loader_realdata.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libknot_tests_loader_realdata.Tpo $(DEPDIR)/libknot_tests_loader_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot_tests_loader_realdata.c' object='libknot_tests_loader_realdata.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libknot_tests_loader_realdata.o `test -f 'tests/libknot/realdata/libknot_tests_loader_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/libknot_tests_loader_realdata.c + +libknot_tests_loader_realdata.obj: tests/libknot/realdata/libknot_tests_loader_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libknot_tests_loader_realdata.obj -MD -MP -MF $(DEPDIR)/libknot_tests_loader_realdata.Tpo -c -o libknot_tests_loader_realdata.obj `if test -f 'tests/libknot/realdata/libknot_tests_loader_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot_tests_loader_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot_tests_loader_realdata.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libknot_tests_loader_realdata.Tpo $(DEPDIR)/libknot_tests_loader_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/libknot_tests_loader_realdata.c' object='libknot_tests_loader_realdata.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libknot_tests_loader_realdata.obj `if test -f 'tests/libknot/realdata/libknot_tests_loader_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/libknot_tests_loader_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/libknot_tests_loader_realdata.c'; fi` + +unittests_libknot_realdata.o: tests/libknot/realdata/unittests_libknot_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unittests_libknot_realdata.o -MD -MP -MF $(DEPDIR)/unittests_libknot_realdata.Tpo -c -o unittests_libknot_realdata.o `test -f 'tests/libknot/realdata/unittests_libknot_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/unittests_libknot_realdata.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/unittests_libknot_realdata.Tpo $(DEPDIR)/unittests_libknot_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/unittests_libknot_realdata.c' object='unittests_libknot_realdata.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unittests_libknot_realdata.o `test -f 'tests/libknot/realdata/unittests_libknot_realdata.c' || echo '$(srcdir)/'`tests/libknot/realdata/unittests_libknot_realdata.c + +unittests_libknot_realdata.obj: tests/libknot/realdata/unittests_libknot_realdata.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unittests_libknot_realdata.obj -MD -MP -MF $(DEPDIR)/unittests_libknot_realdata.Tpo -c -o unittests_libknot_realdata.obj `if test -f 'tests/libknot/realdata/unittests_libknot_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/unittests_libknot_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/unittests_libknot_realdata.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/unittests_libknot_realdata.Tpo $(DEPDIR)/unittests_libknot_realdata.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/libknot/realdata/unittests_libknot_realdata.c' object='unittests_libknot_realdata.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unittests_libknot_realdata.obj `if test -f 'tests/libknot/realdata/unittests_libknot_realdata.c'; then $(CYGPATH_W) 'tests/libknot/realdata/unittests_libknot_realdata.c'; else $(CYGPATH_W) '$(srcdir)/tests/libknot/realdata/unittests_libknot_realdata.c'; fi` + +unittests_zp_main.o: zcompile/tests/unittests_zp_main.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unittests_zp_main.o -MD -MP -MF $(DEPDIR)/unittests_zp_main.Tpo -c -o unittests_zp_main.o `test -f 'zcompile/tests/unittests_zp_main.c' || echo '$(srcdir)/'`zcompile/tests/unittests_zp_main.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/unittests_zp_main.Tpo $(DEPDIR)/unittests_zp_main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zcompile/tests/unittests_zp_main.c' object='unittests_zp_main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unittests_zp_main.o `test -f 'zcompile/tests/unittests_zp_main.c' || echo '$(srcdir)/'`zcompile/tests/unittests_zp_main.c + +unittests_zp_main.obj: zcompile/tests/unittests_zp_main.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unittests_zp_main.obj -MD -MP -MF $(DEPDIR)/unittests_zp_main.Tpo -c -o unittests_zp_main.obj `if test -f 'zcompile/tests/unittests_zp_main.c'; then $(CYGPATH_W) 'zcompile/tests/unittests_zp_main.c'; else $(CYGPATH_W) '$(srcdir)/zcompile/tests/unittests_zp_main.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/unittests_zp_main.Tpo $(DEPDIR)/unittests_zp_main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='zcompile/tests/unittests_zp_main.c' object='unittests_zp_main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unittests_zp_main.obj `if test -f 'zcompile/tests/unittests_zp_main.c'; then $(CYGPATH_W) 'zcompile/tests/unittests_zp_main.c'; else $(CYGPATH_W) '$(srcdir)/zcompile/tests/unittests_zp_main.c'; fi` + +.l.c: + $(am__skiplex) $(SHELL) $(YLWRAP) $< $(LEX_OUTPUT_ROOT).c $@ -- $(LEXCOMPILE) + +libknotd_la-cf-lex.c: knot/conf/cf-lex.l + \ + $(am__skiplex) \ + $(SHELL) $(YLWRAP) `test -f 'knot/conf/cf-lex.l' || echo '$(srcdir)/'`knot/conf/cf-lex.l $(LEX_OUTPUT_ROOT).c libknotd_la-cf-lex.c -- $(LEX) $(LFLAGS) $(libknotd_la_LFLAGS) + +zlexer.c: zcompile/zlexer.l + \ + $(am__skiplex) \ + $(SHELL) $(YLWRAP) `test -f 'zcompile/zlexer.l' || echo '$(srcdir)/'`zcompile/zlexer.l $(LEX_OUTPUT_ROOT).c zlexer.c -- $(LEX) $(LFLAGS) $(AM_LFLAGS) + +.y.c: + $(am__skipyacc) $(SHELL) $(YLWRAP) $< y.tab.c $@ y.tab.h $*.h y.output $*.output -- $(YACCCOMPILE) + +libknotd_la-cf-parse.c: knot/conf/cf-parse.y + \ + $(am__skipyacc) \ + $(SHELL) $(YLWRAP) `test -f 'knot/conf/cf-parse.y' || echo '$(srcdir)/'`knot/conf/cf-parse.y y.tab.c libknotd_la-cf-parse.c y.tab.h libknotd_la-cf-parse.h y.output libknotd_la-cf-parse.output -- $(YACC) $(YFLAGS) $(libknotd_la_YFLAGS) + +zparser.c: zcompile/zparser.y + \ + $(am__skipyacc) \ + $(SHELL) $(YLWRAP) `test -f 'zcompile/zparser.y' || echo '$(srcdir)/'`zcompile/zparser.y y.tab.c zparser.c y.tab.h zparser.h y.output zparser.output -- $(YACC) $(YFLAGS) $(AM_YFLAGS) + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-man8: $(man8_MANS) + @$(NORMAL_INSTALL) + test -z "$(man8dir)" || $(MKDIR_P) "$(DESTDIR)$(man8dir)" + @list='$(man8_MANS)'; test -n "$(man8dir)" || exit 0; \ + { for i in $$list; do echo "$$i"; done; \ + } | while read p; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; echo "$$p"; \ + done | \ + sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ + sed 'N;N;s,\n, ,g' | { \ + list=; while read file base inst; do \ + if test "$$base" = "$$inst"; then list="$$list $$file"; else \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man8dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$inst" || exit $$?; \ + fi; \ + done; \ + for i in $$list; do echo "$$i"; done | $(am__base_list) | \ + while read files; do \ + test -z "$$files" || { \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man8dir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(man8dir)" || exit $$?; }; \ + done; } + +uninstall-man8: + @$(NORMAL_UNINSTALL) + @list='$(man8_MANS)'; test -n "$(man8dir)" || exit 0; \ + files=`{ for i in $$list; do echo "$$i"; done; \ + } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ + test -z "$$files" || { \ + echo " ( cd '$(DESTDIR)$(man8dir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(man8dir)" && rm -f $$files; } + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @list='$(MANS)'; if test -n "$$list"; then \ + list=`for p in $$list; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + if test -f "$$d$$p"; then echo "$$d$$p"; else :; fi; done`; \ + if test -n "$$list" && \ + grep 'ab help2man is required to generate this page' $$list >/dev/null; then \ + echo "error: found man pages containing the \`missing help2man' replacement text:" >&2; \ + grep -l 'ab help2man is required to generate this page' $$list | sed 's/^/ /' >&2; \ + echo " to fix them, install help2man, remove and regenerate the man pages;" >&2; \ + echo " typically \`make maintainer-clean' will remove them" >&2; \ + exit 1; \ + else :; fi; \ + else :; fi + @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: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(LTLIBRARIES) $(PROGRAMS) $(MANS) config.h +installdirs: + for dir in "$(DESTDIR)$(libexecdir)" "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man8dir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) 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: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +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." + -rm -f libknotd_la-cf-lex.c + -rm -f libknotd_la-cf-parse.c + -rm -f libknotd_la-cf-parse.h + -rm -f zlexer.c + -rm -f zparser.c + -rm -f zparser.h + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-generic clean-libexecPROGRAMS clean-libtool \ + clean-noinstLTLIBRARIES clean-sbinPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-hdr distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-man + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-libexecPROGRAMS install-sbinPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: install-man8 + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-libexecPROGRAMS uninstall-man \ + uninstall-sbinPROGRAMS + +uninstall-man: uninstall-man8 + +.MAKE: all check install install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-libexecPROGRAMS clean-libtool clean-noinstLTLIBRARIES \ + clean-sbinPROGRAMS ctags distclean distclean-compile \ + distclean-generic distclean-hdr distclean-libtool \ + distclean-tags 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-libexecPROGRAMS install-man install-man8 install-pdf \ + install-pdf-am install-ps install-ps-am install-sbinPROGRAMS \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags uninstall uninstall-am \ + uninstall-libexecPROGRAMS uninstall-man uninstall-man8 \ + uninstall-sbinPROGRAMS + + +# automake complains on % rules: +# `%'-style pattern rules are a GNU make extension + +tests/libknot/parsed_data.rc: tests/libknot/files/parsed_data + ../resource.sh tests/libknot/files/parsed_data >$@ + +tests/libknot/realdata/parsed_data.rc: tests/libknot/realdata/files/parsed_data + ../resource.sh tests/libknot/realdata/files/parsed_data >$@ + +tests/libknot/parsed_data_queries.rc: tests/libknot/files/parsed_data_queries + ../resource.sh tests/libknot/files/parsed_data_queries >$@ + +tests/libknot/raw_data_queries.rc: tests/libknot/files/raw_data_queries + ../resource.sh tests/libknot/files/raw_data_queries >$@ + +tests/libknot/raw_data.rc: tests/libknot/files/raw_data + ../resource.sh tests/libknot/files/raw_data >$@ + +tests/libknot/realdata/raw_data.rc: tests/libknot/realdata/files/raw_data + ../resource.sh tests/libknot/realdata/files/raw_data >$@ + +tests/sample_conf.rc: tests/files/sample_conf + ../resource.sh tests/files/sample_conf >$@ + +# 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/common/WELL1024a.c b/src/common/WELL1024a.c new file mode 100644 index 0000000..dddf75e --- /dev/null +++ b/src/common/WELL1024a.c @@ -0,0 +1,116 @@ +/* ***************************************************************************** */ +/* Copyright: Francois Panneton and Pierre L'Ecuyer, University of Montreal */ +/* Makoto Matsumoto, Hiroshima University */ +/* Notice: This code can be used freely for personal, academic, */ +/* or non-commercial purposes. For commercial purposes, */ +/* please contact P. L'Ecuyer at: lecuyer@iro.UMontreal.ca */ +/* ***************************************************************************** */ + +#include <pthread.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <stdio.h> + +#include "WELL1024a.h" + +#define W 32 +#define M1 3 +#define M2 24 +#define M3 10 + +#define MAT0POS(t,v) (v^(v>>t)) +#define MAT0NEG(t,v) (v^(v<<(-(t)))) +#define Identity(v) (v) + +#define V0(s) (s)->state[(s)->i ] +#define VM1(s) (s)->state[((s)->i+M1) & 0x0000001fU] +#define VM2(s) (s)->state[((s)->i+M2) & 0x0000001fU] +#define VM3(s) (s)->state[((s)->i+M3) & 0x0000001fU] +#define VRm1(s) (s)->state[((s)->i+31) & 0x0000001fU] +#define newV0(s) (s)->state[((s)->i+31) & 0x0000001fU] +#define newV1(s) (s)->state[(s)->i ] + +#define FACT 2.32830643653869628906e-10 + +rngstate_t* InitWELLRNG1024a (unsigned *init) { + + rngstate_t *s = malloc(sizeof(rngstate_t)); + if (s == 0) { + return 0; + } + + s->i = 0; + for (int j = 0; j < WELL1024_WIDTH; j++) + s->state[j] = init[j]; + return s; +} + +double WELLRNG1024a (rngstate_t* s) { + unsigned z0 = VRm1(s); + unsigned z1 = Identity(V0(s)) ^ MAT0POS (8, VM1(s)); + unsigned z2 = MAT0NEG (-19, VM2(s)) ^ MAT0NEG(-14,VM3(s)); + newV1(s) = z1 ^ z2; + newV0(s) = MAT0NEG (-11,z0) ^ MAT0NEG(-7,z1) ^ MAT0NEG(-13,z2) ; + s->i = (s->i + 31) & 0x0000001fU; + return ((double) s->state[s->i] * FACT); +} + +/*! \brief TLS unique key for each thread seed. */ +static pthread_key_t tls_prng_key; +static pthread_once_t tls_prng_once = PTHREAD_ONCE_INIT; + +static void tls_prng_deinit(void *ptr) +{ + free(ptr); +} + +static void tls_prng_deinit_main() +{ + tls_prng_deinit(pthread_getspecific(tls_prng_key)); +} + +static void tls_prng_init() +{ + (void) pthread_key_create(&tls_prng_key, tls_prng_deinit); + atexit(tls_prng_deinit_main); // Main thread cleanup +} + +double tls_rand() +{ + /* Setup PRNG state for current thread. */ + (void)pthread_once(&tls_prng_once, tls_prng_init); + + /* Create PRNG state if not exists. */ + rngstate_t* s = pthread_getspecific(tls_prng_key); + if (!s) { + /* Initialize seed from system PRNG generator. */ + unsigned init[WELL1024_WIDTH]; + FILE *fp = fopen("/dev/urandom", "r"); + for (unsigned i = 0; i < WELL1024_WIDTH; ++i) { + int rc = fread(&init[i], sizeof(unsigned), 1, fp); + rc = rc; + } + fclose(fp); + + /* Initialize PRNG state. */ + s = InitWELLRNG1024a(init); + (void)pthread_setspecific(tls_prng_key, s); + } + + return WELLRNG1024a(s); +} + +void tls_seed_set(unsigned init[WELL1024_WIDTH]) +{ + /* Initialize new PRNG state if not exists. */ + rngstate_t* s = pthread_getspecific(tls_prng_key); + if (!s) { + s = InitWELLRNG1024a(init); + (void)pthread_setspecific(tls_prng_key, s); + } else { + /* Reset PRNG state if exists. */ + memcpy(s->state, init, sizeof(unsigned) * WELL1024_WIDTH); + s->i = 0; + } +} diff --git a/src/common/WELL1024a.h b/src/common/WELL1024a.h new file mode 100644 index 0000000..04bf1a1 --- /dev/null +++ b/src/common/WELL1024a.h @@ -0,0 +1,32 @@ +/* ***************************************************************************** */ +/* Copyright: Francois Panneton and Pierre L'Ecuyer, University of Montreal */ +/* Makoto Matsumoto, Hiroshima University */ +/* Notice: This code can be used freely for personal, academic, */ +/* or non-commercial purposes. For commercial purposes, */ +/* please contact P. L'Ecuyer at: lecuyer@iro.UMontreal.ca */ +/* ***************************************************************************** */ + +#define WELL1024_WIDTH 32 /* 128 bytes */ + +typedef struct { + unsigned i; + unsigned state[WELL1024_WIDTH]; +} rngstate_t; + +rngstate_t* InitWELLRNG1024a (unsigned *init); +double WELLRNG1024a (rngstate_t* s); + +/*! + * \brief Get pseudorandom number from PRNG initialized in thread-local storage. + * + * No need for initialization, TLS will take care of it. + * + * \retval Pseudorandom number. + */ +double tls_rand(); + +/*! + * \brief Set PRNG seed in thread-local storage to requested value. + * + */ +void tls_seed_set(unsigned init[WELL1024_WIDTH]); diff --git a/src/common/acl.c b/src/common/acl.c new file mode 100644 index 0000000..e73c4dd --- /dev/null +++ b/src/common/acl.c @@ -0,0 +1,185 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <stdlib.h> + +#include "common/acl.h" + +static int acl_compare(void *k1, void *k2) +{ + sockaddr_t* a1 = (sockaddr_t *)k1; + sockaddr_t* a2 = (sockaddr_t *)k2; + + /* Check different length, IPv4 goes first. */ + int ldiff = a1->len - a2->len; + if (ldiff != 0) { + return ldiff < 0 ? -1 : 1; + } + + /* Compare integers if IPv4. */ + if (a1->len == sizeof(struct sockaddr_in)) { + + /* Allow if k1 == INADDR_ANY. */ + if (a1->addr4.sin_addr.s_addr == 0) { + return 0; + } + + /* Compare address. */ + ldiff = a1->addr4.sin_addr.s_addr - a2->addr4.sin_addr.s_addr; + if (ldiff != 0) { + return ldiff < 0 ? -1 : 1; + } + + /* Port = 0 means any port match. */ + if (a1->addr4.sin_port == 0) { + return 0; + } + + /* Compare ports on address match. */ + ldiff = ntohs(a1->addr4.sin_port) - ntohs(a2->addr4.sin_port); + if (ldiff != 0) { + return ldiff < 0 ? -1 : 1; + } + return 0; + } + + /* IPv6 matching. */ +#ifndef DISABLE_IPV6 + if (a1->len == sizeof(struct sockaddr_in6)) { + + /* Compare address. */ + /*! \todo Maybe use memcmp()? */ + ldiff = 0; + const unsigned int *a6 = (const unsigned int *)&a1->addr6.sin6_addr; + const unsigned int *b6 = (const unsigned int *)&a2->addr6.sin6_addr; + for (int i = 0; i < (sizeof(struct in6_addr)/ sizeof(int)) ; ++i) { + ldiff = a6[i] - b6[i]; + if (ldiff < 0) { + return -1; + } + if (ldiff > 0) { + return 1; + } + } + + /* Port = 0 means any port match. */ + if (a1->addr6.sin6_port == 0) { + return 0; + } + + /* Compare ports on address match. */ + ldiff = ntohs(a1->addr6.sin6_port) - ntohs(a2->addr6.sin6_port); + if (ldiff != 0) { + return ldiff < 0 ? -1 : 1; + } + return 0; + } +#endif + + return 0; +} + +acl_t *acl_new(acl_rule_t default_rule, const char *name) +{ + /* Trailing '\0' for NULL name. */ + size_t name_len = 1; + if (name) { + name_len += strlen(name); + } else { + name = ""; + } + + /* Allocate memory for ACL. */ + acl_t* acl = malloc(sizeof(acl_t) + name_len); + if (!acl) { + return 0; + } + + /* Initialize skip list. */ + acl->rules = skip_create_list(acl_compare); + if (!acl->rules) { + free(acl); + return 0; + } + + /* Initialize. */ + memcpy(&acl->name, name, name_len); + acl->default_rule = default_rule; + return acl; +} + +void acl_delete(acl_t **acl) +{ + if ((acl == NULL) || (*acl == NULL)) { + return; + } + + /* Truncate rules. */ + if (acl_truncate(*acl) != ACL_ACCEPT) { + return; + } + + /* Free ACL. */ + free(*acl); + *acl = 0; +} + +int acl_create(acl_t *acl, const sockaddr_t* addr, acl_rule_t rule) +{ + if (!acl || !addr || rule < 0) { + return ACL_ERROR; + } + + /* Insert into skip list. */ + sockaddr_t *key = malloc(sizeof(sockaddr_t)); + memcpy(key, addr, sizeof(sockaddr_t)); + + skip_insert(acl->rules, key, (void*)((ssize_t)rule + 1), 0); + + return ACL_ACCEPT; +} + +int acl_match(acl_t *acl, sockaddr_t* addr) +{ + if (!acl || !addr) { + return ACL_ERROR; + } + + /* Return default rule if not found. + * Conversion to the same length integer is made, + * but we can be sure, that the value range is within <-1,1>. + */ + ssize_t val = ((ssize_t)skip_find(acl->rules, addr)) - 1; + if (val < 0) { + return acl->default_rule; + } + + /* Return stored rule if found. */ + return (int)val; +} + +int acl_truncate(acl_t *acl) +{ + if (acl == NULL) { + return ACL_ERROR; + } + + /* Destroy all rules. */ + skip_destroy_list(&acl->rules, free, 0); + + return ACL_ACCEPT; +} diff --git a/src/common/acl.h b/src/common/acl.h new file mode 100644 index 0000000..c79db7f --- /dev/null +++ b/src/common/acl.h @@ -0,0 +1,138 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file acl.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Access control lists. + * + * An access control list is a named structure + * for efficient IP address and port matching. + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_ACL_H_ +#define _KNOTD_ACL_H_ + +#include "common/skip-list.h" +#include "common/sockaddr.h" + +/*! \brief ACL rules types. */ +typedef enum acl_rule_t { + ACL_ERROR = -1, + ACL_DENY = 0, + ACL_ACCEPT = 1 +} acl_rule_t; + +/*! \brief ACL structure. */ +typedef struct acl_t { + acl_rule_t default_rule; + skip_list_t *rules; + const char name[]; +} acl_t; + +/*! + * \brief Create a new ACL. + * + * \param default_rule Default rule for address matching. + * \param name ACL symbolic name (or NULL). + * + * \retval New ACL instance when successful. + * \retval NULL on errors. + */ +acl_t *acl_new(acl_rule_t default_rule, const char *name); + +/*! + * \brief Delete ACL structure. + * + * \param acl Pointer to ACL instance. + */ +void acl_delete(acl_t **acl); + +/*! + * \brief Create new ACL rule. + * + * \todo Support address subnets. + * + * \param acl Pointer to ACL instance. + * \param addr IP address (will be duplicated). + * \param rule Rule. + * + * \retval ACL_ACCEPT if successful. + * \retval ACP_ERROR on error. + */ +int acl_create(acl_t *acl, const sockaddr_t* addr, acl_rule_t rule); + +/*! + * \brief Match address against ACL. + * + * \param acl Pointer to ACL instance. + * \param addr IP address. + * + * \retval ACL_ACCEPT if the address is accepted. + * \retval ACL_DENY if the address is not accepted. + * \retval ACP_ERROR on error. + */ +int acl_match(acl_t *acl, sockaddr_t* addr); + +/*! + * \brief Truncate ACL. + * + * All but the default rule will be dropped. + * + * \param acl Pointer to ACL instance. + * + * \retval ACL_ACCEPT if successful. + * \retval ACP_ERROR on error. + */ +int acl_truncate(acl_t *acl); + +/*! + * \brief Return ACL name. + * + * \param acl Pointer to ACL instance. + * + * \retval ACL name. + */ +static inline const char* acl_name(acl_t *acl) { + if (!acl) { + return 0; + } + + return acl->name; +} + +/*! + * \brief Return ACL rules. + * + * \param acl Pointer to ACL instance. + * + * \retval ACL rules skip-list. + */ +static inline skip_list_t* acl_rules(acl_t *acl) { + if (!acl) { + return 0; + } + + return acl->rules; +} + +#endif /* _KNOTD_ACL_H_ */ + +/*! @} */ diff --git a/src/common/base32.c b/src/common/base32.c new file mode 100644 index 0000000..43b86c1 --- /dev/null +++ b/src/common/base32.c @@ -0,0 +1,539 @@ +/* base32.c -- Encode binary data using printable characters. + Copyright (C) 1999, 2000, 2001, 2004, 2005, 2006, 2010 Free Software + Foundation, Inc. + + 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 2, 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, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Adapted from base64.{h,c} by Ondřej Surý. base64.{h,c} was written + * by Simon Josefsson. Partially adapted from GNU MailUtils + * (mailbox/filter_trans.c, as of 2004-11-28). Improved by review + * from Paul Eggert, Bruno Haible, and Stepan Kasal. + * + * See also RFC 4648 <http://www.ietf.org/rfc/rfc4648.txt>. + * + * Be careful with error checking. Here is how you would typically + * use these functions: + * + * bool ok = base32_decode_alloc (in, inlen, &out, &outlen); + * if (!ok) + * FAIL: input was not valid base32 + * if (out == NULL) + * FAIL: memory allocation error + * OK: data in OUT/OUTLEN + * + * size_t outlen = base32_encode_alloc (in, inlen, &out); + * if (out == NULL && outlen == 0 && inlen != 0) + * FAIL: input too long + * if (out == NULL) + * FAIL: memory allocation error + * OK: data in OUT/OUTLEN. + * + */ + +/* Get prototype. */ +#include "base32.h" + +/* Get malloc. */ +#include <stdlib.h> + +/* Get UCHAR_MAX. */ +#include <limits.h> + +/* C89 compliant way to cast 'char' to 'unsigned char'. */ +static inline unsigned char to_uchar(char ch) +{ + return ch; +} + +/* Base32 encode IN array of size INLEN into OUT array of size OUTLEN. + If OUTLEN is less than BASE32_LENGTH(INLEN), write as many bytes as + possible. If OUTLEN is larger than BASE32_LENGTH(INLEN), also zero + terminate the output buffer. */ +void base32_encode(const char *in, size_t inlen, char *out, size_t outlen) +{ + static const char b32str[32] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + + while (inlen && outlen) { + *out++ = b32str[(to_uchar(in[0]) >> 3) & 0x1f]; + if (!--outlen) { + break; + } + *out++ = b32str[((to_uchar(in[0]) << 2) + + (--inlen ? to_uchar(in[1]) >> 6 : 0)) + & 0x1f]; + if (!--outlen) { + break; + } + *out++ =(inlen + ? b32str[(to_uchar(in[1]) >> 1) & 0x1f] + : '='); + if (!--outlen) { + break; + } + *out++ = (inlen + ? b32str[((to_uchar(in[1]) << 4) + + (--inlen ? to_uchar(in[2]) >> 4 : 0)) + & 0x1f] + : '='); + if (!--outlen) { + break; + } + *out++ = (inlen + ? b32str[((to_uchar(in[2]) << 1) + + (--inlen ? to_uchar(in[3]) >> 7 : 0)) + & 0x1f] + : '='); + if (!--outlen) { + break; + } + *out++ = (inlen + ? b32str[(to_uchar(in[3]) >> 2) & 0x1f] + : '='); + if (!--outlen) + { + break; + } + *out++ = (inlen + ? b32str[((to_uchar(in[3]) << 3) + + (--inlen ? to_uchar(in[4]) >> 5 : 0)) + & 0x1f] + : '='); + if (!--outlen) { + break; + } + *out++ = inlen ? b32str[to_uchar(in[4]) & 0x1f] : '='; + if (!--outlen) { + break; + } + if (inlen) { + inlen--; + } + if (inlen) { + in += 5; + } + } + + if (outlen) { + *out = '\0'; + } +} + +/* Allocate a buffer and store zero terminated base32 encoded data + from array IN of size INLEN, returning BASE32_LENGTH(INLEN), i.e., + the length of the encoded data, excluding the terminating zero. On + return, the OUT variable will hold a pointer to newly allocated + memory that must be deallocated by the caller. If output string + length would overflow, 0 is returned and OUT is set to NULL. If + memory allocation failed, OUT is set to NULL, and the return value + indicates length of the requested memory block, i.e., + BASE32_LENGTH(inlen) + 1. */ +size_t base32_encode_alloc(const char *in, size_t inlen, char **out) +{ + size_t outlen = 1 + BASE32_LENGTH (inlen); + + /* Check for overflow in outlen computation. + * + * If there is no overflow, outlen >= inlen. + * + * If the operation (inlen + 2) overflows then it yields at most +1, so + * outlen is 0. + * + * If the multiplication overflows, we lose at least half of the + * correct value, so the result is < ((inlen + 2) / 3) * 2, which is + * less than (inlen + 2) * 0.66667, which is less than inlen as soon as + * (inlen > 4). + */ + if (inlen > outlen) + { + *out = NULL; + return 0; + } + + *out = malloc(outlen); + if (!*out) { + return outlen; + } + + base32_encode(in, inlen, *out, outlen); + + return outlen - 1; +} + +/* With this approach this file works independent of the charset used + (think EBCDIC). However, it does assume that the characters in the + Base32 alphabet (A-Z2-7) are encoded in 0..255. POSIX + 1003.1-2001 require that char and unsigned char are 8-bit + quantities, though, taking care of that problem. But this may be a + potential problem on non-POSIX C99 platforms. + + IBM C V6 for AIX mishandles "#define B32(x) ...'x'...", so use "_" + as the formal parameter rather than "x". */ +#define B32(_) \ + ((_) == 'A' ? 0 \ + : (_) == 'B' ? 1 \ + : (_) == 'C' ? 2 \ + : (_) == 'D' ? 3 \ + : (_) == 'E' ? 4 \ + : (_) == 'F' ? 5 \ + : (_) == 'G' ? 6 \ + : (_) == 'H' ? 7 \ + : (_) == 'I' ? 8 \ + : (_) == 'J' ? 9 \ + : (_) == 'K' ? 10 \ + : (_) == 'L' ? 11 \ + : (_) == 'M' ? 12 \ + : (_) == 'N' ? 13 \ + : (_) == 'O' ? 14 \ + : (_) == 'P' ? 15 \ + : (_) == 'Q' ? 16 \ + : (_) == 'R' ? 17 \ + : (_) == 'S' ? 18 \ + : (_) == 'T' ? 19 \ + : (_) == 'U' ? 20 \ + : (_) == 'V' ? 21 \ + : (_) == 'W' ? 22 \ + : (_) == 'X' ? 23 \ + : (_) == 'Y' ? 24 \ + : (_) == 'Z' ? 25 \ + : (_) == '2' ? 26 \ + : (_) == '3' ? 27 \ + : (_) == '4' ? 28 \ + : (_) == '5' ? 29 \ + : (_) == '6' ? 30 \ + : (_) == '7' ? 31 \ + : -1) + +static const signed char b32[0x100] = { + B32 (0), B32 (1), B32 (2), B32 (3), + B32 (4), B32 (5), B32 (6), B32 (7), + B32 (8), B32 (9), B32 (10), B32 (11), + B32 (12), B32 (13), B32 (14), B32 (15), + B32 (16), B32 (17), B32 (18), B32 (19), + B32 (20), B32 (21), B32 (22), B32 (23), + B32 (24), B32 (25), B32 (26), B32 (27), + B32 (28), B32 (29), B32 (30), B32 (31), + B32 (32), B32 (33), B32 (34), B32 (35), + B32 (36), B32 (37), B32 (38), B32 (39), + B32 (40), B32 (41), B32 (42), B32 (43), + B32 (44), B32 (45), B32 (46), B32 (47), + B32 (48), B32 (49), B32 (50), B32 (51), + B32 (52), B32 (53), B32 (54), B32 (55), + B32 (56), B32 (57), B32 (58), B32 (59), + B32 (60), B32 (61), B32 (62), B32 (63), + B32 (64), B32 (65), B32 (66), B32 (67), + B32 (68), B32 (69), B32 (70), B32 (71), + B32 (72), B32 (73), B32 (74), B32 (75), + B32 (76), B32 (77), B32 (78), B32 (79), + B32 (80), B32 (81), B32 (82), B32 (83), + B32 (84), B32 (85), B32 (86), B32 (87), + B32 (88), B32 (89), B32 (90), B32 (91), + B32 (92), B32 (93), B32 (94), B32 (95), + B32 (96), B32 (97), B32 (98), B32 (99), + B32 (100), B32 (101), B32 (102), B32 (103), + B32 (104), B32 (105), B32 (106), B32 (107), + B32 (108), B32 (109), B32 (110), B32 (111), + B32 (112), B32 (113), B32 (114), B32 (115), + B32 (116), B32 (117), B32 (118), B32 (119), + B32 (120), B32 (121), B32 (122), B32 (123), + B32 (124), B32 (125), B32 (126), B32 (127), + B32 (128), B32 (129), B32 (130), B32 (131), + B32 (132), B32 (133), B32 (134), B32 (135), + B32 (136), B32 (137), B32 (138), B32 (139), + B32 (140), B32 (141), B32 (142), B32 (143), + B32 (144), B32 (145), B32 (146), B32 (147), + B32 (148), B32 (149), B32 (150), B32 (151), + B32 (152), B32 (153), B32 (154), B32 (155), + B32 (156), B32 (157), B32 (158), B32 (159), + B32 (160), B32 (161), B32 (162), B32 (163), + B32 (164), B32 (165), B32 (166), B32 (167), + B32 (168), B32 (169), B32 (170), B32 (171), + B32 (172), B32 (173), B32 (174), B32 (175), + B32 (176), B32 (177), B32 (178), B32 (179), + B32 (180), B32 (181), B32 (182), B32 (183), + B32 (184), B32 (185), B32 (186), B32 (187), + B32 (188), B32 (189), B32 (190), B32 (191), + B32 (192), B32 (193), B32 (194), B32 (195), + B32 (196), B32 (197), B32 (198), B32 (199), + B32 (200), B32 (201), B32 (202), B32 (203), + B32 (204), B32 (205), B32 (206), B32 (207), + B32 (208), B32 (209), B32 (210), B32 (211), + B32 (212), B32 (213), B32 (214), B32 (215), + B32 (216), B32 (217), B32 (218), B32 (219), + B32 (220), B32 (221), B32 (222), B32 (223), + B32 (224), B32 (225), B32 (226), B32 (227), + B32 (228), B32 (229), B32 (230), B32 (231), + B32 (232), B32 (233), B32 (234), B32 (235), + B32 (236), B32 (237), B32 (238), B32 (239), + B32 (240), B32 (241), B32 (242), B32 (243), + B32 (244), B32 (245), B32 (246), B32 (247), + B32 (248), B32 (249), B32 (250), B32 (251), + B32 (252), B32 (253), B32 (254), B32 (255) +}; + +#if UCHAR_MAX == 255 +#define uchar_in_range(c) true +#else +#define uchar_in_range(c) ((c) <= 255) +#endif + +/* Return true if CH is a character from the Base32 alphabet, and + false otherwise. Note that '=' is padding and not considered to be + part of the alphabet. */ +bool isbase32(char ch) +{ + return uchar_in_range(to_uchar(ch)) && 0 <= b32[to_uchar(ch)]; +} + +/* Decode base32 encoded input array IN of length INLEN to output + array OUT that can hold *OUTLEN bytes. Return true if decoding was + successful, i.e. if the input was valid base32 data, false + otherwise. If *OUTLEN is too small, as many bytes as possible will + be written to OUT. On return, *OUTLEN holds the length of decoded + bytes in OUT. Note that as soon as any non-alphabet characters are + encountered, decoding is stopped and false is returned. This means + that, when applicable, you must remove any line terminators that is + part of the data stream before calling this function. */ +bool base32_decode(const char *in, size_t inlen, char *out, size_t *outlen) +{ + size_t outleft = *outlen; + + while (inlen >= 2) { + if (!isbase32(in[0]) || !isbase32(in[1])) { + break; + } + + if (outleft) { + *out++ = ((b32[to_uchar(in[0])] << 3) + | (b32[to_uchar(in[1])] >> 2)); + outleft--; + } + + if (inlen == 2) { + break; + } + + if (in[2] == '=') { + if (inlen != 8) { + break; + } + + if ((in[3] != '=') || + (in[4] != '=') || + (in[5] != '=') || + (in[6] != '=') || + (in[7] != '=')) { + break; + } + } else { + if (!isbase32(in[2]) || !isbase32(in[3])) { + break; + } + + if (outleft) { + *out++ = ((b32[to_uchar(in[1])] << 6) + | ((b32[to_uchar(in[2])] << 1) & 0x3E) + | (b32[to_uchar(in[3])] >> 4)); + outleft--; + } + + if (inlen == 4) { + break; + } + + if (in[4] == '=') { + if (inlen != 8) { + break; + } + + if ((in[5] != '=') || + (in[6] != '=') || + (in[7] != '=')) { + break; + } + } else { + if (!isbase32 (in[3]) || !isbase32(in[4])) { + break; + } + + if (outleft) { + *out++ = ((b32[to_uchar(in[3])] << 4) + | (b32[to_uchar(in[4])] >> 1)); + outleft--; + } + + if (inlen == 5) { + break; + } + + if (in[5] == '=') { + if (inlen != 8) { + break; + } + + if ((in[6] != '=') + || (in[7] != '=')) { + break; + } + } else { + if (!isbase32 (in[5]) + || !isbase32 (in[6])) { + break; + } + + if (outleft) { + *out++ = ((b32[to_uchar(in[4])] + << 7) + | (b32[to_uchar(in[5])] << 2) + | (b32[to_uchar(in[6])] + >> 3)); + outleft--; + } + + if (inlen == 7) { + break; + } + + if (in[7] == '=') { + if (inlen != 8) { + break; + } + } else { + if (!isbase32 (in[7])) { + break; + } + + if (outleft) { + *out++ = + ((b32[to_uchar(in[6])] + << 5) | (b32[ + to_uchar(in[7])])); + outleft--; + } + } + } + } + } + + in += 8; + inlen -= 8; + } + + *outlen -= outleft; + + if (inlen != 0) { + return false; + } + + return true; +} + +/* Allocate an output buffer in *OUT, and decode the base32 encoded + data stored in IN of size INLEN to the *OUT buffer. On return, the + size of the decoded data is stored in *OUTLEN. OUTLEN may be NULL, + if the caller is not interested in the decoded length. *OUT may be + NULL to indicate an out of memory error, in which case *OUTLEN + contains the size of the memory block needed. The function returns + true on successful decoding and memory allocation errors. (Use the + *OUT and *OUTLEN parameters to differentiate between successful + decoding and memory error.) The function returns false if the + input was invalid, in which case *OUT is NULL and *OUTLEN is + undefined. */ +bool base32_decode_alloc(const char *in, size_t inlen, char **out, + size_t *outlen) +{ + /* This may allocate a few bytes too much, depending on input, + but it's not worth the extra CPU time to compute the exact amount. + The exact amount is 5 * inlen / 8, minus 1 if the input ends + with "=" and minus another 1 if the input ends with "==", etc. + Dividing before multiplying avoids the possibility of overflow. */ + size_t needlen = 5 * (inlen / 8) + 4; + + *out = malloc(needlen); + if (!*out) { + return true; + } + + if (!base32_decode(in, inlen, *out, &needlen)) { + free (*out); + *out = NULL; + return false; + } + + if (outlen) { + *outlen = needlen; + } + + return true; +} + +#ifdef MAIN + +#include <stddef.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> +#include "base32.h" + +int main(int argc, char **argv) { + int i = 1; + size_t inlen, outlen, argvlen; + char *out; + char *in; + bool ok; + + while (argc > 1) { + argv++; argc--; + argvlen = strlen(*argv); + + outlen = base32_encode_alloc(*argv, argvlen, &out); + + if (out == NULL && outlen == 0 && inlen != 0) { + fprintf(stderr, "ERROR(encode): input too long: %zd\n", + outlen); + return 1; + } + + if (out == NULL) { + fprintf(stderr, "ERROR(encode): memory allocation error" + "\n"); + return 1; + } + + ok = base32_decode_alloc(out, outlen, &in, &inlen); + + if (!ok) { + fprintf(stderr, "ERROR(decode): input was not valid " + "base32: `%s'\n", out); + return 1; + } + + if (in == NULL) { + fprintf(stderr, "ERROR(decode): memory allocation " + "error\n"); + } + + if ((inlen != argvlen) || + strcmp(*argv, in) != 0) { + fprintf(stderr, "ERROR(encode/decode): input `%s' and " + "output `%s'\n", *argv, in); + return 1; + } + printf("INPUT: `%s'\nENCODE: `%s'\nDECODE: `%s'\n", *argv, out, + in); + } +} + +#endif diff --git a/src/common/base32.h b/src/common/base32.h new file mode 100644 index 0000000..45df9fa --- /dev/null +++ b/src/common/base32.h @@ -0,0 +1,121 @@ +/* base32.h -- Encode binary data using printable characters. + Copyright (C) 2004, 2005, 2006, 2010 Free Software Foundation, Inc. + Written by Ondřej Surý & Simon Josefsson. + + 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 2, 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, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _BASE32_H_ +#define _BASE32_H_ + +/* Get size_t. */ +#include <stddef.h> + +/* Get bool. */ +#include <stdbool.h> + +/*! + * \brief Counts the size of the Base32-encoded output for given input length. + * + * \note This uses that the expression (n+(k-1))/k means the smallest + * integer >= n/k, i.e., the ceiling of n/k. + */ +#define BASE32_LENGTH(inlen) ((((inlen) + 4) / 5) * 8) + +/*! + * \brief Checks if the given character belongs to the Base32 alphabet. + * + * \param ch Character to check. + * + * \retval true if \a ch belongs to the Base32 alphabet. + * \retval false otherwise. + */ +extern bool isbase32(char ch); + +/*! + * \brief Encodes the given character array using Base32 encoding. + * + * If \a outlen is less than BASE32_LENGTH(\a inlen), the function writes as + * many bytes as possible to the output buffer. If \a outlen is more than + * BASE32_LENGTH(\a inlen), the output will be zero-terminated. + * + * \param in Input array of characters. + * \param inlen Length of the input array. + * \param out Output buffer. + * \param outlen Size of the output buffer. + */ +extern void base32_encode(const char *in, size_t inlen, char *out, + size_t outlen); + +/*! + * \brief Encodes the given character array using Base32 encoding and allocates + * space for the output. + * + * \param in Input array of characters. + * \param inlen Length of the input array. + * \param out Output buffer. + * + * \return Size of the allocated output buffer (0 if failed). + */ +extern size_t base32_encode_alloc(const char *in, size_t inlen, char **out); + +/*! + * \brief Decodes the given character array in Base32 encoding. + * + * If \a *outlen is too small, as many bytes as possible will be written to + * \a out. On return, \a *outlen holds the length of decoded bytes in \a out. + * + * \note As soon as any non-alphabet characters are encountered, decoding is + * stopped and false is returned. This means that, when applicable, you + * must remove any line terminators that is part of the data stream before + * calling this function. + * + * \param in Input array of characters. + * \param inlen Length of the input array. + * \param out Output buffer. + * \param outlen Size of the output buffer. + * + * \retval true if decoding was successful, i.e. if the input was valid base32 + * data. + * \retval false otherwise. + */ +extern bool base32_decode(const char *in, size_t inlen, char *out, + size_t *outlen); + +/*! + * \brief Allocate an output buffer and decode the base32 encoded data to it. + * + * On return, the size of the decoded data is stored in \a *outlen. \a outlen + * may be NULL, if the caller is not interested in the decoded length. \a *out + * may be NULL to indicate an out of memory error, in which case \a *outlen + * contains the size of the memory block needed. + * + * \param in Input array of characters. + * \param inlen Length of the input array. + * \param out Output buffer. \a *out may be NULL to indicate an out of memory + * error in which case \a *outlen contains the size of the memory + * block needed + * \param outlen Size of the output buffer. May be NULL, if the caller is not + * interested in the decoded length + * + * \retval true on successful decoding and memory allocation errors. (Use the + * \a *out and \a *outlen parameters to differentiate between + * successful decoding and memory error.) + * \retval false if the input was invalid, in which case \a *out is NULL and + * \a *outlen is undefined. + */ +extern bool base32_decode_alloc(const char *in, size_t inlen, char **out, + size_t *outlen); + +#endif /* _BASE32_H_ */ diff --git a/src/common/base32hex.c b/src/common/base32hex.c new file mode 100644 index 0000000..cd2d2ce --- /dev/null +++ b/src/common/base32hex.c @@ -0,0 +1,562 @@ +/* base32hex.c -- Encode binary data using printable characters. + Copyright (C) 1999, 2000, 2001, 2004, 2005, 2006, 2010 Free Software + Foundation, Inc. + + 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 2, 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, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Adapted from base32.{h,c}. base32.{h,c} was adapted from + * base64.{h,c} by Ondřej Surý. base64.{h,c} was written by Simon + * Josefsson. Partially adapted from GNU MailUtils + * (mailbox/filter_trans.c, as of 2004-11-28). Improved by review + * from Paul Eggert, Bruno Haible, and Stepan Kasal. + * + * See also RFC 4648 <http://www.ietf.org/rfc/rfc4648.txt>. + * + * Be careful with error checking. Here is how you would typically + * use these functions: + * + * bool ok = base32hex_decode_alloc (in, inlen, &out, &outlen); + * if (!ok) + * FAIL: input was not valid base32hex + * if (out == NULL) + * FAIL: memory allocation error + * OK: data in OUT/OUTLEN + * + * size_t outlen = base32hex_encode_alloc (in, inlen, &out); + * if (out == NULL && outlen == 0 && inlen != 0) + * FAIL: input too long + * if (out == NULL) + * FAIL: memory allocation error + * OK: data in OUT/OUTLEN. + * + */ + +/* Get prototype. */ +#include "base32hex.h" + +/* Get malloc. */ +#include <stdlib.h> + +/* Get UCHAR_MAX. */ +#include <limits.h> + +/* C89 compliant way to cast 'char' to 'unsigned char'. */ +static inline unsigned char to_uchar(char ch) +{ + return ch; +} + +/* Base32hex encode IN array of size INLEN into OUT array of size OUTLEN. + If OUTLEN is less than BASE32HEX_LENGTH(INLEN), write as many bytes as + possible. If OUTLEN is larger than BASE32HEX_LENGTH(INLEN), also zero + terminate the output buffer. */ +void base32hex_encode(const char *in, size_t inlen, char *out, size_t outlen) +{ + static const char b32str[32] = + "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + + while (inlen && outlen) { + *out++ = b32str[(to_uchar(in[0]) >> 3) & 0x1f]; + if (!--outlen) { + break; + } + *out++ = b32str[((to_uchar(in[0]) << 2) + + (--inlen ? to_uchar(in[1]) >> 6 : 0)) + & 0x1f]; + if (!--outlen) { + break; + } + *out++ =(inlen + ? b32str[(to_uchar(in[1]) >> 1) & 0x1f] + : '='); + if (!--outlen) { + break; + } + *out++ = (inlen + ? b32str[((to_uchar(in[1]) << 4) + + (--inlen ? to_uchar(in[2]) >> 4 : 0)) + & 0x1f] + : '='); + if (!--outlen) { + break; + } + *out++ = (inlen + ? b32str[((to_uchar(in[2]) << 1) + + (--inlen ? to_uchar(in[3]) >> 7 : 0)) + & 0x1f] + : '='); + if (!--outlen) { + break; + } + *out++ = (inlen + ? b32str[(to_uchar(in[3]) >> 2) & 0x1f] + : '='); + if (!--outlen) + { + break; + } + *out++ = (inlen + ? b32str[((to_uchar(in[3]) << 3) + + (--inlen ? to_uchar(in[4]) >> 5 : 0)) + & 0x1f] + : '='); + if (!--outlen) { + break; + } + *out++ = inlen ? b32str[to_uchar(in[4]) & 0x1f] : '='; + if (!--outlen) { + break; + } + if (inlen) { + inlen--; + } + if (inlen) { + in += 5; + } + } + + if (outlen) { + *out = '\0'; + } +} + +/* Allocate a buffer and store zero terminated base32hex encoded data + from array IN of size INLEN, returning BASE32HEX_LENGTH(INLEN), i.e., + the length of the encoded data, excluding the terminating zero. On + return, the OUT variable will hold a pointer to newly allocated + memory that must be deallocated by the caller. If output string + length would overflow, 0 is returned and OUT is set to NULL. If + memory allocation failed, OUT is set to NULL, and the return value + indicates length of the requested memory block, i.e., + BASE32HEX_LENGTH(inlen) + 1. */ +size_t base32hex_encode_alloc(const char *in, size_t inlen, char **out) +{ + size_t outlen = 1 + BASE32HEX_LENGTH (inlen); + + /* Check for overflow in outlen computation. + * + * If there is no overflow, outlen >= inlen. + * + * If the operation (inlen + 2) overflows then it yields at most +1, so + * outlen is 0. + * + * If the multiplication overflows, we lose at least half of the + * correct value, so the result is < ((inlen + 2) / 3) * 2, which is + * less than (inlen + 2) * 0.66667, which is less than inlen as soon as + * (inlen > 4). + */ + if (inlen > outlen) + { + *out = NULL; + return 0; + } + + *out = malloc(outlen); + if (!*out) { + return outlen; + } + + base32hex_encode(in, inlen, *out, outlen); + + return outlen - 1; +} + +/* With this approach this file works independent of the charset used + (think EBCDIC). However, it does assume that the characters in the + Base32hex alphabet (A-Z2-7) are encoded in 0..255. POSIX + 1003.1-2001 require that char and unsigned char are 8-bit + quantities, though, taking care of that problem. But this may be a + potential problem on non-POSIX C99 platforms. + + IBM C V6 for AIX mishandles "#define B32(x) ...'x'...", so use "_" + as the formal parameter rather than "x". */ +#define B32(_) \ + ((_) == '0' ? 0 \ + : (_) == '1' ? 1 \ + : (_) == '2' ? 2 \ + : (_) == '3' ? 3 \ + : (_) == '4' ? 4 \ + : (_) == '5' ? 5 \ + : (_) == '6' ? 6 \ + : (_) == '7' ? 7 \ + : (_) == '8' ? 8 \ + : (_) == '9' ? 9 \ + : (_) == 'A' ? 10 \ + : (_) == 'B' ? 11 \ + : (_) == 'C' ? 12 \ + : (_) == 'D' ? 13 \ + : (_) == 'E' ? 14 \ + : (_) == 'F' ? 15 \ + : (_) == 'G' ? 16 \ + : (_) == 'H' ? 17 \ + : (_) == 'I' ? 18 \ + : (_) == 'J' ? 19 \ + : (_) == 'K' ? 20 \ + : (_) == 'L' ? 21 \ + : (_) == 'M' ? 22 \ + : (_) == 'N' ? 23 \ + : (_) == 'O' ? 24 \ + : (_) == 'P' ? 25 \ + : (_) == 'Q' ? 26 \ + : (_) == 'R' ? 27 \ + : (_) == 'S' ? 28 \ + : (_) == 'T' ? 29 \ + : (_) == 'U' ? 30 \ + : (_) == 'V' ? 31 \ + : (_) == 'a' ? 10 \ + : (_) == 'b' ? 11 \ + : (_) == 'c' ? 12 \ + : (_) == 'd' ? 13 \ + : (_) == 'e' ? 14 \ + : (_) == 'f' ? 15 \ + : (_) == 'g' ? 16 \ + : (_) == 'h' ? 17 \ + : (_) == 'i' ? 18 \ + : (_) == 'j' ? 19 \ + : (_) == 'k' ? 20 \ + : (_) == 'l' ? 21 \ + : (_) == 'm' ? 22 \ + : (_) == 'n' ? 23 \ + : (_) == 'o' ? 24 \ + : (_) == 'p' ? 25 \ + : (_) == 'q' ? 26 \ + : (_) == 'r' ? 27 \ + : (_) == 's' ? 28 \ + : (_) == 't' ? 29 \ + : (_) == 'u' ? 30 \ + : (_) == 'v' ? 31 \ + : -1) + +static const signed char b32[0x100] = { + B32 (0), B32 (1), B32 (2), B32 (3), + B32 (4), B32 (5), B32 (6), B32 (7), + B32 (8), B32 (9), B32 (10), B32 (11), + B32 (12), B32 (13), B32 (14), B32 (15), + B32 (16), B32 (17), B32 (18), B32 (19), + B32 (20), B32 (21), B32 (22), B32 (23), + B32 (24), B32 (25), B32 (26), B32 (27), + B32 (28), B32 (29), B32 (30), B32 (31), + B32 (32), B32 (33), B32 (34), B32 (35), + B32 (36), B32 (37), B32 (38), B32 (39), + B32 (40), B32 (41), B32 (42), B32 (43), + B32 (44), B32 (45), B32 (46), B32 (47), + B32 (48), B32 (49), B32 (50), B32 (51), + B32 (52), B32 (53), B32 (54), B32 (55), + B32 (56), B32 (57), B32 (58), B32 (59), + B32 (60), B32 (61), B32 (62), B32 (63), + B32 (64), B32 (65), B32 (66), B32 (67), + B32 (68), B32 (69), B32 (70), B32 (71), + B32 (72), B32 (73), B32 (74), B32 (75), + B32 (76), B32 (77), B32 (78), B32 (79), + B32 (80), B32 (81), B32 (82), B32 (83), + B32 (84), B32 (85), B32 (86), B32 (87), + B32 (88), B32 (89), B32 (90), B32 (91), + B32 (92), B32 (93), B32 (94), B32 (95), + B32 (96), B32 (97), B32 (98), B32 (99), + B32 (100), B32 (101), B32 (102), B32 (103), + B32 (104), B32 (105), B32 (106), B32 (107), + B32 (108), B32 (109), B32 (110), B32 (111), + B32 (112), B32 (113), B32 (114), B32 (115), + B32 (116), B32 (117), B32 (118), B32 (119), + B32 (120), B32 (121), B32 (122), B32 (123), + B32 (124), B32 (125), B32 (126), B32 (127), + B32 (128), B32 (129), B32 (130), B32 (131), + B32 (132), B32 (133), B32 (134), B32 (135), + B32 (136), B32 (137), B32 (138), B32 (139), + B32 (140), B32 (141), B32 (142), B32 (143), + B32 (144), B32 (145), B32 (146), B32 (147), + B32 (148), B32 (149), B32 (150), B32 (151), + B32 (152), B32 (153), B32 (154), B32 (155), + B32 (156), B32 (157), B32 (158), B32 (159), + B32 (160), B32 (161), B32 (162), B32 (163), + B32 (164), B32 (165), B32 (166), B32 (167), + B32 (168), B32 (169), B32 (170), B32 (171), + B32 (172), B32 (173), B32 (174), B32 (175), + B32 (176), B32 (177), B32 (178), B32 (179), + B32 (180), B32 (181), B32 (182), B32 (183), + B32 (184), B32 (185), B32 (186), B32 (187), + B32 (188), B32 (189), B32 (190), B32 (191), + B32 (192), B32 (193), B32 (194), B32 (195), + B32 (196), B32 (197), B32 (198), B32 (199), + B32 (200), B32 (201), B32 (202), B32 (203), + B32 (204), B32 (205), B32 (206), B32 (207), + B32 (208), B32 (209), B32 (210), B32 (211), + B32 (212), B32 (213), B32 (214), B32 (215), + B32 (216), B32 (217), B32 (218), B32 (219), + B32 (220), B32 (221), B32 (222), B32 (223), + B32 (224), B32 (225), B32 (226), B32 (227), + B32 (228), B32 (229), B32 (230), B32 (231), + B32 (232), B32 (233), B32 (234), B32 (235), + B32 (236), B32 (237), B32 (238), B32 (239), + B32 (240), B32 (241), B32 (242), B32 (243), + B32 (244), B32 (245), B32 (246), B32 (247), + B32 (248), B32 (249), B32 (250), B32 (251), + B32 (252), B32 (253), B32 (254), B32 (255) +}; + +#if UCHAR_MAX == 255 +#define uchar_in_range(c) true +#else +#define uchar_in_range(c) ((c) <= 255) +#endif + +/* Return true if CH is a character from the Base32hex alphabet, and + false otherwise. Note that '=' is padding and not considered to be + part of the alphabet. */ +bool isbase32hex(char ch) +{ + return uchar_in_range(to_uchar(ch)) && 0 <= b32[to_uchar(ch)]; +} + +/* Decode base32hex encoded input array IN of length INLEN to output + array OUT that can hold *OUTLEN bytes. Return true if decoding was + successful, i.e. if the input was valid base32hex data, false + otherwise. If *OUTLEN is too small, as many bytes as possible will + be written to OUT. On return, *OUTLEN holds the length of decoded + bytes in OUT. Note that as soon as any non-alphabet characters are + encountered, decoding is stopped and false is returned. This means + that, when applicable, you must remove any line terminators that is + part of the data stream before calling this function. */ +bool base32hex_decode(const char *in, size_t inlen, char *out, size_t *outlen) +{ + size_t outleft = *outlen; + + while (inlen >= 2) { + if (!isbase32hex(in[0]) || !isbase32hex(in[1])) { + break; + } + + if (outleft) { + *out++ = ((b32[to_uchar(in[0])] << 3) + | (b32[to_uchar(in[1])] >> 2)); + outleft--; + } + + if (inlen == 2) { + break; + } + + if (in[2] == '=') { + if (inlen != 8) { + break; + } + + if ((in[3] != '=') || + (in[4] != '=') || + (in[5] != '=') || + (in[6] != '=') || + (in[7] != '=')) { + break; + } + } else { + if (!isbase32hex(in[2]) || !isbase32hex(in[3])) { + break; + } + + if (outleft) { + *out++ = ((b32[to_uchar(in[1])] << 6) + | ((b32[to_uchar(in[2])] << 1) & 0x3E) + | (b32[to_uchar(in[3])] >> 4)); + outleft--; + } + + if (inlen == 4) { + break; + } + + if (in[4] == '=') { + if (inlen != 8) { + break; + } + + if ((in[5] != '=') || + (in[6] != '=') || + (in[7] != '=')) { + break; + } + } else { + if (!isbase32hex (in[3]) || !isbase32hex(in[4])) { + break; + } + + if (outleft) { + *out++ = ((b32[to_uchar(in[3])] << 4) + | (b32[to_uchar(in[4])] >> 1)); + outleft--; + } + + if (inlen == 5) { + break; + } + + if (in[5] == '=') { + if (inlen != 8) { + break; + } + + if ((in[6] != '=') + || (in[7] != '=')) { + break; + } + } else { + if (!isbase32hex (in[5]) + || !isbase32hex (in[6])) { + break; + } + + if (outleft) { + *out++ = ((b32[to_uchar(in[4])] + << 7) + | (b32[to_uchar(in[5])] << 2) + | (b32[to_uchar(in[6])] + >> 3)); + outleft--; + } + + if (inlen == 7) { + break; + } + + if (in[7] == '=') { + if (inlen != 8) { + break; + } + } else { + if (!isbase32hex (in[7])) { + break; + } + + if (outleft) { + *out++ = + ((b32[to_uchar(in[6])] + << 5) | (b32[ + to_uchar(in[7])])); + outleft--; + } + } + } + } + } + + in += 8; + inlen -= 8; + } + + *outlen -= outleft; + + if (inlen != 0) { + return false; + } + + return true; +} + +/* Allocate an output buffer in *OUT, and decode the base32hex encoded + data stored in IN of size INLEN to the *OUT buffer. On return, the + size of the decoded data is stored in *OUTLEN. OUTLEN may be NULL, + if the caller is not interested in the decoded length. *OUT may be + NULL to indicate an out of memory error, in which case *OUTLEN + contains the size of the memory block needed. The function returns + true on successful decoding and memory allocation errors. (Use the + *OUT and *OUTLEN parameters to differentiate between successful + decoding and memory error.) The function returns false if the + input was invalid, in which case *OUT is NULL and *OUTLEN is + undefined. */ +bool base32hex_decode_alloc(const char *in, size_t inlen, char **out, + size_t *outlen) +{ + /* This may allocate a few bytes too much, depending on input, + but it's not worth the extra CPU time to compute the exact amount. + The exact amount is 5 * inlen / 8, minus 1 if the input ends + with "=" and minus another 1 if the input ends with "==", etc. + Dividing before multiplying avoids the possibility of overflow. */ + size_t needlen = 5 * (inlen / 8) + 4; + + *out = malloc(needlen); + if (!*out) { + return true; + } + + if (!base32hex_decode(in, inlen, *out, &needlen)) { + free (*out); + *out = NULL; + return false; + } + + if (outlen) { + *outlen = needlen; + } + + return true; +} + +#ifdef MAIN + +#include <stddef.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> +#include "base32hex.h" + +int main(int argc, char **argv) { + int i = 1; + size_t inlen, outlen, argvlen; + char *out; + char *in; + bool ok; + + while (argc > 1) { + argv++; argc--; + argvlen = strlen(*argv); + + outlen = base32hex_encode_alloc(*argv, argvlen, &out); + + if (out == NULL && outlen == 0 && inlen != 0) { + fprintf(stderr, "ERROR(encode): input too long: %zd\n", + outlen); + return 1; + } + + if (out == NULL) { + fprintf(stderr, "ERROR(encode): memory allocation error" + "\n"); + return 1; + } + + ok = base32hex_decode_alloc(out, outlen, &in, &inlen); + + if (!ok) { + fprintf(stderr, "ERROR(decode): input was not valid " + "base32hex: `%s'\n", out); + return 1; + } + + if (in == NULL) { + fprintf(stderr, "ERROR(decode): memory allocation " + "error\n"); + } + + if ((inlen != argvlen) || + strcmp(*argv, in) != 0) { + fprintf(stderr, "ERROR(encode/decode): input `%s' and " + "output `%s'\n", *argv, in); + return 1; + } + printf("INPUT: `%s'\nENCODE: `%s'\nDECODE: `%s'\n", *argv, out, + in); + } +} + +#endif diff --git a/src/common/base32hex.h b/src/common/base32hex.h new file mode 100644 index 0000000..9ac4fa8 --- /dev/null +++ b/src/common/base32hex.h @@ -0,0 +1,124 @@ +/* base32hex.h -- Encode binary data using printable characters. + Copyright (C) 2004, 2005, 2006, 2010 Free Software Foundation, Inc. + Written by Ondřej Surý & Simon Josefsson. + + 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 2, 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, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _BASE32HEX_H_ +#define _BASE32HEX_H_ + +/* Get size_t. */ +#include <stddef.h> + +/* Get bool. */ +#include <stdbool.h> + +/*! + * \brief Counts the size of the Base32Hex-encoded output for given input + * length. + * + * \note This uses that the expression (n+(k-1))/k means the smallest + * integer >= n/k, i.e., the ceiling of n/k. + */ +#define BASE32HEX_LENGTH(inlen) ((((inlen) + 4) / 5) * 8) + +/*! + * \brief Checks if the given character belongs to the Base32Hex alphabet. + * + * \param ch Character to check. + * + * \retval true if \a ch belongs to the Base32Hex alphabet. + * \retval false otherwise. + */ +extern bool isbase32hex(char ch); + +/*! + * \brief Encodes the given character array using Base32 encoding with extended + * hex alphabet. + * + * If \a outlen is less than BASE32HEX_LENGTH(\a inlen), the function writes as + * many bytes as possible to the output buffer. If \a outlen is more than + * BASE32HEX_LENGTH(\a inlen), the output will be zero-terminated. + * + * \param in Input array of characters. + * \param inlen Length of the input array. + * \param out Output buffer. + * \param outlen Size of the output buffer. + */ +extern void base32hex_encode(const char *in, size_t inlen, char *out, + size_t outlen); + +/*! + * \brief Encodes the given character array using Base32 encoding with extended + * hex alphabet and allocates space for the output. + * + * \param in Input array of characters. + * \param inlen Length of the input array. + * \param out Output buffer. + * + * \return Size of the allocated output buffer (0 if failed). + */ +extern size_t base32hex_encode_alloc(const char *in, size_t inlen, char **out); + +/*! + * \brief Decodes the given character array in Base32 encoding with extended + * hex alphabet. + * + * If \a *outlen is too small, as many bytes as possible will be written to + * \a out. On return, \a *outlen holds the length of decoded bytes in \a out. + * + * \note As soon as any non-alphabet characters are encountered, decoding is + * stopped and false is returned. This means that, when applicable, you + * must remove any line terminators that is part of the data stream before + * calling this function. + * + * \param in Input array of characters. + * \param inlen Length of the input array. + * \param out Output buffer. + * \param outlen Size of the output buffer. + * + * \retval true if decoding was successful, i.e. if the input was valid + * base32hex data. + * \retval false otherwise. + */ +extern bool base32hex_decode(const char *in, size_t inlen, char *out, + size_t *outlen); + +/*! + * \brief Allocate an output buffer and decode the base32hex encoded data to it. + * + * On return, the size of the decoded data is stored in \a *outlen. \a outlen + * may be NULL, if the caller is not interested in the decoded length. \a *out + * may be NULL to indicate an out of memory error, in which case \a *outlen + * contains the size of the memory block needed. + * + * \param in Input array of characters. + * \param inlen Length of the input array. + * \param out Output buffer. \a *out may be NULL to indicate an out of memory + * error in which case \a *outlen contains the size of the memory + * block needed + * \param outlen Size of the output buffer. May be NULL, if the caller is not + * interested in the decoded length + * + * \retval true on successful decoding and memory allocation errors. (Use the + * \a *out and \a *outlen parameters to differentiate between + * successful decoding and memory error.) + * \retval false if the input was invalid, in which case \a *out is NULL and + * \a *outlen is undefined. + */ +extern bool base32hex_decode_alloc(const char *in, size_t inlen, char **out, + size_t *outlen); + +#endif /* _BASE32HEX_H_ */ diff --git a/src/common/crc.c b/src/common/crc.c new file mode 100644 index 0000000..33bf903 --- /dev/null +++ b/src/common/crc.c @@ -0,0 +1,155 @@ +/* + Copyright (c) 2006-2011, Thomas Pircher <tehpeh@gmx.net> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * \file crc.c + * Functions and types for CRC checks. + * + * Generated on Fri May 6 11:25:47 2011, + * by pycrc v0.7.7, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 32 + * Poly = 0x04c11db7 + * XorIn = 0xffffffff + * ReflectIn = True + * XorOut = 0xffffffff + * ReflectOut = True + * Algorithm = table-driven + *****************************************************************************/ +#include "crc.h" +#include <stdint.h> +#include <stdlib.h> + +/** + * Static table used for the table_driven implementation. + *****************************************************************************/ +static const crc_t crc_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +/** + * Reflect all bits of a \a data word of \a data_len bytes. + * + * \param data The data word to be reflected. + * \param data_len The width of \a data expressed in number of bits. + * \return The reflected data. + *****************************************************************************/ +crc_t crc_reflect(crc_t data, size_t data_len) +{ + unsigned int i; + crc_t ret; + + ret = data & 0x01; + for (i = 1; i < data_len; i++) { + data >>= 1; + ret = (ret << 1) | (data & 0x01); + } + return ret; +} + + +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +crc_t crc_update(crc_t crc, const unsigned char *data, size_t data_len) +{ + unsigned int tbl_idx; + + while (data_len--) { + tbl_idx = (crc ^ *data) & 0xff; + crc = (crc_table[tbl_idx] ^ (crc >> 8)) & 0xffffffff; + + data++; + } + return crc & 0xffffffff; +} + + + diff --git a/src/common/crc.h b/src/common/crc.h new file mode 100644 index 0000000..41971a9 --- /dev/null +++ b/src/common/crc.h @@ -0,0 +1,110 @@ +/* + Copyright (c) 2006-2011, Thomas Pircher <tehpeh@gmx.net> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ +/** + * \file crc.h + * Functions and types for CRC checks. + * + * Generated on Fri May 6 11:25:43 2011, + * by pycrc v0.7.7, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 32 + * Poly = 0x04c11db7 + * XorIn = 0xffffffff + * ReflectIn = True + * XorOut = 0xffffffff + * ReflectOut = True + * Algorithm = table-driven + *****************************************************************************/ +#ifndef __CRC_H__ +#define __CRC_H__ + +#include <stdint.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * The definition of the used algorithm. + *****************************************************************************/ +#define CRC_ALGO_TABLE_DRIVEN 1 + + +/** + * The type of the CRC values. + * + * This type must be big enough to contain at least 32 bits. + *****************************************************************************/ +typedef uint32_t crc_t; + + +/** + * Reflect all bits of a \a data word of \a data_len bytes. + * + * \param data The data word to be reflected. + * \param data_len The width of \a data expressed in number of bits. + * \return The reflected data. + *****************************************************************************/ +crc_t crc_reflect(crc_t data, size_t data_len); + + +/** + * Calculate the initial crc value. + * + * \return The initial crc value. + *****************************************************************************/ +static inline crc_t crc_init(void) +{ + return 0xffffffff; +} + + +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +crc_t crc_update(crc_t crc, const unsigned char *data, size_t data_len); + + +/** + * Calculate the final crc value. + * + * \param crc The current crc value. + * \return The final crc value. + *****************************************************************************/ +static inline crc_t crc_finalize(crc_t crc) +{ + return crc ^ 0xffffffff; +} + + +#ifdef __cplusplus +} /* closing brace for extern "C" */ +#endif + +#endif /* __CRC_H__ */ diff --git a/src/common/dynamic-array.c b/src/common/dynamic-array.c new file mode 100644 index 0000000..1e2efac --- /dev/null +++ b/src/common/dynamic-array.c @@ -0,0 +1,224 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <pthread.h> +#include <assert.h> +#include <stdio.h> + +#include <urcu.h> + +//#include "common.h" +#include "common/dynamic-array.h" + +#ifndef ERR_ALLOC_FAILED +#define ERR_ALLOC_FAILED fprintf(stderr, "Allocation failed at %s:%d\n", \ + __FILE__, __LINE__) +#endif + +//#define DA_DEBUG + +#ifndef dbg_da +#ifdef DA_DEBUG +#define dbg_da(msg...) fprintf(stderr, msg) +#else +#define dbg_da(msg...) +#endif +#endif + +/*----------------------------------------------------------------------------*/ +/* Private functions */ +/*----------------------------------------------------------------------------*/ + +enum da_resize_type { + DA_LARGER, DA_SMALLER +}; + +typedef enum da_resize_type da_resize_type_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \retval 1 On success. + * \retval -1 On failure. + */ +static int da_resize(da_array_t *array, da_resize_type_t type) +{ + dbg_da("da_resize(): array pointer: %p, items pointer: %p\n", array, + array->items); + + unsigned new_size = ((type == DA_LARGER) + ? (array->allocated *= 2) + : (array->allocated /= 2)); + + void *new_items = malloc(new_size * array->item_size); + if (new_items == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + + dbg_da("Place for new items: %p\n", new_items); + + // copy the contents from the old array to the new + memcpy(new_items, array->items, array->count * array->item_size); + + // do RCU update + void *old_items = rcu_xchg_pointer(&array->items, new_items); + array->allocated = new_size; + + dbg_da("Old items pointer: %p\n", old_items); + + // wait for readers to finish + synchronize_rcu(); + // deallocate the old array + dbg_da("RCU synchronized, deallocating old items array at address %p." + "\n", old_items); + free(old_items); + + return 1; +} + +/*----------------------------------------------------------------------------*/ +/* Public functions */ +/*----------------------------------------------------------------------------*/ + +da_array_t *da_create(unsigned count, size_t item_size) +{ + da_array_t *a = (da_array_t *)malloc(sizeof(da_array_t)); + if (a == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + da_initialize(a, count, item_size); + return a; +} + +/*----------------------------------------------------------------------------*/ + +int da_initialize(da_array_t *array, unsigned count, size_t item_size) +{ + assert(array != NULL); + pthread_mutex_init(&array->mtx, NULL); + pthread_mutex_lock(&array->mtx); + + array->items = malloc(count * item_size); + if (array->items == NULL) { + array->allocated = 0; + array->count = 0; + ERR_ALLOC_FAILED; + return -1; + } + + array->allocated = count; + array->count = 0; + array->item_size = item_size; + memset(array->items, 0, count * item_size); + + pthread_mutex_unlock(&array->mtx); + return 0; +} + +/*----------------------------------------------------------------------------*/ + +int da_reserve(da_array_t *array, unsigned count) +{ + pthread_mutex_lock(&array->mtx); + unsigned res = 0; + + assert(array->allocated >= array->count); + if ((array->allocated - array->count) >= count) { + dbg_da("Enough place in the array, no resize needed.\n"); + res = 0; + } else { + dbg_da("Resizing array.\n"); + res = da_resize(array, DA_LARGER); + } + pthread_mutex_unlock(&array->mtx); + + return res; +} + +/*----------------------------------------------------------------------------*/ + +int da_occupy(da_array_t *array, unsigned count) +{ + pthread_mutex_lock(&array->mtx); + unsigned res = 0; + assert(array->allocated >= array->count); + + if ((array->allocated - array->count) < count) { + dbg_da("Not enough place to occupy.\n"); + res = -1; + } else { + array->count += count; + } + + pthread_mutex_unlock(&array->mtx); + return res; +} + +/*----------------------------------------------------------------------------*/ + +unsigned da_try_reserve(const da_array_t *array, unsigned count) +{ + assert(array->allocated >= array->count); + if ((array->allocated - array->count) >= count) { + return 0; + } + + return 1; +} + +/*----------------------------------------------------------------------------*/ + +void da_release(da_array_t *array, unsigned count) +{ + pthread_mutex_lock(&array->mtx); + + assert(array->allocated >= array->count); + assert(array->count >= count); + dbg_da("Decreasing count of items in array.\n"); + array->count -= count; + + pthread_mutex_unlock(&array->mtx); +} + +/*----------------------------------------------------------------------------*/ + +void da_destroy(da_array_t *array) +{ + pthread_mutex_lock(&array->mtx); + void *old_items = rcu_dereference(array->items); + rcu_set_pointer(&array->items, NULL); + pthread_mutex_unlock(&array->mtx); + + synchronize_rcu(); + free(old_items); + pthread_mutex_destroy(&array->mtx); +} + +/*----------------------------------------------------------------------------*/ + +void *da_get_items(const da_array_t *array) +{ + return array->items; +} + +/*----------------------------------------------------------------------------*/ + +unsigned da_get_count(const da_array_t *array) +{ + return array->count; +} diff --git a/src/common/dynamic-array.h b/src/common/dynamic-array.h new file mode 100644 index 0000000..77a5d13 --- /dev/null +++ b/src/common/dynamic-array.h @@ -0,0 +1,156 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file dynamic-array.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Safe dynamic array implementation. + * + * \todo Somehow check if the array is initialized and do not use otherwise. + * Maybe some magic, or so. + * \todo This structure is too slow because of the mutex. + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_COMMON_DYNAMIC_ARRAY_H_ +#define _KNOTD_COMMON_DYNAMIC_ARRAY_H_ + +#include <string.h> +#include <pthread.h> + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Dynamic array structure. + * + * Before using the dynamic array, it must be initialized using da_initialize(). + * When getting individual items always use da_get_items() to obtain pointer to + * the actual array. + * + * Items in the array cannot be dereferenced (it uses void * for storing the + * the items). It is needed to type-cast the item array (obtained by calling + * da_get_items()) to a proper type before dereferencing. + * + * When adding items, first reserve enough space for them by callling + * da_reserve() and subsequently tell the array about the inserted items by + * calling da_occupy(). When removing, the array must be told about the fact + * by calling da_release(). + * + * For getting the actual number of items in array use da_get_count(). + * + * When the array is no more needed, the da_destroy() function must be called + * before deallocating the structure. + */ +struct da_array { + /*! \brief The actual array. The items can't be dereferenced directly.*/ + void *items; + + /*! + * \brief Size of the stored items in bytes (used in counting of space + * needed. + */ + size_t item_size; + + /*! + * \brief Size of allocated space in number of items that can be stored. + */ + unsigned allocated; + + /*! \brief Number of items actually stored in the array. */ + unsigned count; + + /*! \brief Mutex. */ + pthread_mutex_t mtx; +}; + +typedef struct da_array da_array_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates and initializes the dynamic array. + * + * Initialization comprises of allocating place for \a count items of size + * \a item_size and setting the items to zeros. + * + * \retval 0 if successful. + * \retval -1 if not successful. + */ +da_array_t *da_create(unsigned count, size_t item_size); + +/*! + * \brief Initializes the dynamic array. + * + * Initialization comprises of allocating place for \a count items of size + * \a item_size and setting the items to zeros. + * + * \retval 0 if successful. + * \retval -1 if not successful. + */ +int da_initialize(da_array_t *array, unsigned count, size_t item_size); + +/*! + * \brief Reserves space for \a count more items. + * + * \retval 0 if successful and resizing was not necessary. + * \retval 1 if successful and the array was enlarged. + * \retval -1 if not successful - resizing was needed but could not be done. + */ +int da_reserve(da_array_t *array, unsigned count); + +/*! + * \brief Increases the number of items in array by \a count. + * + * \retval 0 If successful. + * \retval -1 If not successful (not enough allocated space, i.e. must run + * da_reserve()). + */ +int da_occupy(da_array_t *array, unsigned count); + +/*! + * \brief Tries to reserve space for \a count more items. + * + * \retval 0 if successful and resizing is not necessary. + * \retval 1 if successful but the array will need to be resized. + */ +unsigned da_try_reserve(const da_array_t *array, unsigned count); + +/*! + * \brief Releases space taken by \a count items. + */ +void da_release(da_array_t *array, unsigned count); + +/*! + * \brief Poperly deallocates the array. + */ +void da_destroy(da_array_t *array); + +/*! + * \brief Returns the array of items as a void *. + */ +void *da_get_items(const da_array_t *array); + +/*! + * \brief Returns count of items in the array. + */ +unsigned da_get_count(const da_array_t *array); + +/*----------------------------------------------------------------------------*/ + +#endif /* _KNOTD_COMMON_DYNAMIC_ARRAY_H_ */ + +/*! @} */ diff --git a/src/common/errors.c b/src/common/errors.c new file mode 100644 index 0000000..f1e650d --- /dev/null +++ b/src/common/errors.c @@ -0,0 +1,74 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +#include "common/errors.h" + +/*! + * \brief Looks up the given id in the lookup table. + * + * \param table Lookup table. + * \param id ID to look up. + * + * \return Item in the lookup table with the given id or NULL if no such is + * present. + */ +static const error_table_t *error_lookup_by_id(const error_table_t *table, + int id) +{ + while (table->name != 0) { + if (table->id == id) { + return table; + } + table++; + } + + return 0; +} + +const char *error_to_str(const error_table_t *table, int code) +{ + const error_table_t *msg = error_lookup_by_id(table, code); + if (msg != 0) { + return msg->name; + } else { + return "Unknown error."; + } +} + +int _map_errno(int fallback_value, int arg0, ...) +{ + /* Iterate all variable-length arguments. */ + va_list ap; + va_start(ap, arg0); + + /* KNOTD_ERROR serves as a sentinel. */ + for (int c = arg0; c != 0; c = va_arg(ap, int)) { + + /* Error code matches with mapped. */ + if (c == errno) { + /* Return negative value of the code. */ + return -abs(c); + } + } + va_end(ap); + + /* Fallback error code. */ + return fallback_value; +} diff --git a/src/common/errors.h b/src/common/errors.h new file mode 100644 index 0000000..a2773ac --- /dev/null +++ b/src/common/errors.h @@ -0,0 +1,80 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file errors.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Error codes and function for getting error message. + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_COMMON_ERROR_H_ +#define _KNOTD_COMMON_ERROR_H_ + +#include <errno.h> + +/*! \brief Error lookup table. */ +typedef struct error_table_t { + int id; + const char *name; +} error_table_t; + +/*! + * \brief Returns error message for the given error code. + * + * \param table Table of error messages to use. + * \param code Error code. + * + * \return String containing the error message. + */ +const char *error_to_str(const error_table_t *table, int code); + +/*! + * \brief Safe errno mapper that automatically appends sentinel value. + * + * \see _map_errno() + * + * \param fallback_value Fallback error value (used if the code could not be + * mapped. + * \param err POSIX errno. + * \param ... List of handled codes. + * + * \return Mapped error code. + */ +#define map_errno(fallback_value, err...) _map_errno(fallback_value, err, 0) + +/*! + * \brief Returns a mapped POSIX errcode. + * + * \warning Last error must be 0, it serves as a sentinel value. + * Use map_errno() instead. + * + * \param fallback_value Fallback error value (used if the code could not be + * mapped. + * \param arg0 First mandatory argument. + * \param ... List of handled codes. + * + * \return Mapped error code. + */ +int _map_errno(int fallback_value, int arg0, ...); + +#endif /* _KNOTD_COMMON_ERROR_H_ */ + +/*! @} */ diff --git a/src/common/evqueue.c b/src/common/evqueue.c new file mode 100644 index 0000000..ca3027f --- /dev/null +++ b/src/common/evqueue.c @@ -0,0 +1,130 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "common/evqueue.h" + +/*! \brief Singleton application-wide event queue. */ +evqueue_t *s_evqueue = 0; + +evqueue_t *evqueue_new() +{ + evqueue_t* q = malloc(sizeof(evqueue_t)); + + /* Initialize fds. */ + if (pipe(q->fds) < 0) { + free(q); + q = 0; + } + + return q; +} + +void evqueue_free(evqueue_t **q) +{ + /* Check. */ + if (!q) { + return; + } + + /* Invalidate pointer to queue. */ + evqueue_t *eq = *q; + *q = 0; + + /* Deinitialize. */ + close(eq->fds[EVQUEUE_READFD]); + close(eq->fds[EVQUEUE_WRITEFD]); + free(eq); +} + +int evqueue_poll(evqueue_t *q, const struct timespec *ts, + const sigset_t *sigmask) +{ + /* Check. */ + if (!q) { + return -1; + } + + /* Prepare fd set. */ + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(q->fds[EVQUEUE_READFD], &rfds); + + /* Wait for events. */ + int ret = pselect(q->fds[EVQUEUE_READFD] + 1, &rfds, 0, 0, ts, sigmask); + if (ret < 0) { + return -1; + } + + return ret; +} + +int evqueue_read(evqueue_t *q, void *dst, size_t len) +{ + if (!q || !dst || len == 0) { + return -1; + } + + return read(q->fds[EVQUEUE_READFD], dst, len); +} + +int evqueue_write(evqueue_t *q, const void *src, size_t len) +{ + if (!q || !src || len == 0) { + return -1; + } + + return write(q->fds[EVQUEUE_WRITEFD], src, len); +} + +int evqueue_get(evqueue_t *q, event_t *ev) +{ + /* Check. */ + if (!q || !ev) { + return -1; + } + + /* Read data. */ + int ret = evqueue_read(q, ev, sizeof(event_t)); + if (ret != sizeof(event_t)) { + return -1; + } + + /* Set parent. */ + ev->parent = q; + + return 0; +} + +int evqueue_add(evqueue_t *q, const event_t *ev) +{ + /* Check. */ + if (!q || !ev) { + return -1; + } + + /* Write data. */ + int ret = evqueue_write(q, ev, sizeof(event_t)); + if (ret != sizeof(event_t)) { + return -1; + } + + return 0; +} + diff --git a/src/common/evqueue.h b/src/common/evqueue.h new file mode 100644 index 0000000..ffb3860 --- /dev/null +++ b/src/common/evqueue.h @@ -0,0 +1,200 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file evqueue.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Event queue. + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_COMMON_EVQUEUE_H_ +#define _KNOTD_COMMON_EVQUEUE_H_ + +#include <pthread.h> +#include <signal.h> // sigset_t +#include <time.h> +#include <sys/time.h> + +//#include "knot/common.h" +#include "common/lists.h" + +struct event_t; + +/*! + * \brief Event callback. + * + * Pointer to whole event structure is passed to the callback. + * Callback should return 0 on success and negative integer on error. + * + * Example callback: + * \code + * int print_callback(event_t *t) { + * return printf("Callback: %s\n", t->data); + * } + * \endcode + */ +typedef int (*event_cb_t)(struct event_t *); + +/*! + * \brief Event structure. + */ +typedef struct event_t { + node n; /*!< Node for event queue. */ + int type; /*!< Event type. */ + struct timeval tv; /*!< Event scheduled time. */ + void *data; /*!< Usable data ptr. */ + event_cb_t cb; /*!< Event callback. */ + void *parent; /*!< Pointer to parent (evqueue, scheduler...) */ +} event_t; + +/*! + * \brief Event queue constants. + */ +enum { + EVQUEUE_READFD = 0, + EVQUEUE_WRITEFD = 1 +}; + +/*! + * \brief Event queue structure. + */ +typedef struct { + int fds[2]; /*!< Read and Write fds. */ +} evqueue_t; + +/*! + * \brief Create new event queue. + * + * Event queue is thread-safe and POSIX signal-safe. + * It uses piped fds for queueing and pselect(2) to + * wait for events. + * + * \retval New instance on success. + * \retval NULL on error. + */ +evqueue_t *evqueue_new(); + +/*! + * \brief Deinitialize and free event queue. + * + * \param q Pointer to queue instance. + * \note *q is set to 0. + */ +void evqueue_free(evqueue_t **q); + +/*! + * \brief Poll for new events. + * + * Unblocked signals during polling are specified + * in a sigmask. + * + * \param q Event queue. + * \param ts Timeout (or NULL for infinite). + * \param sigmask Bitmask of signals to receive (or NULL). + * + * \retval Number of polled events on success. + * \retval -1 On error or signal interrupt. + */ +int evqueue_poll(evqueue_t *q, const struct timespec *ts, const sigset_t *sigmask); + +/*! + * \brief Return evqueue pollable fd. + * + * \param q Event queue. + * + * \retval File descriptor available for polling. + * \retval -1 On error. + */ +static inline int evqueue_pollfd(evqueue_t *q) { + return q->fds[EVQUEUE_READFD]; +} + +/*! + * \brief Read data from event queue. + * + * This function is useful for sending custom + * events or other data types through the event queue. + * + * \param q Event queue. + * \param dst Destination buffer. + * \param len Number of bytes to read. + * + * \retval Number of read bytes on success. + * \retval -1 on error, \see read(2). + */ +int evqueue_read(evqueue_t *q, void *dst, size_t len); + +/*! + * \brief Write data to event queue. + * + * This function is useful for sending custom + * events or other data types through the event queue. + * + * \param q Event queue. + * \param src Source buffer. + * \param len Number of bytes to write. + * + * \retval Number of written bytes on success. + * \retval -1 on error, \see write(2). + */ +int evqueue_write(evqueue_t *q, const void *src, size_t len); + +/*! + * \brief Read event from event queue. + * + * \param q Event queue. + * \param ev Event structure for writing. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int evqueue_get(evqueue_t *q, event_t *ev); + +/*! + * \brief Add event to queue. + * + * \param q Event queue. + * \param ev Event structure to read. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int evqueue_add(evqueue_t *q, const event_t *ev); + +/* Singleton event queue pointer. */ +extern evqueue_t *s_evqueue; + +/*! + * \brief Event queue singleton. + */ +static inline evqueue_t *evqueue() { + return s_evqueue; +} + +/*! + * \brief Set event queue singleton. + */ +static inline void evqueue_set(evqueue_t *q) { + s_evqueue = q; +} + +#endif /* _KNOTD_COMMON_EVQUEUE_H_ */ + +/*! @} */ diff --git a/src/common/evsched.c b/src/common/evsched.c new file mode 100644 index 0000000..4e56028 --- /dev/null +++ b/src/common/evsched.c @@ -0,0 +1,309 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <sys/time.h> +#include <stdlib.h> +#include <stdio.h> + +#include "common/evsched.h" + +/*! + * \brief Set event timer to T (now) + dt miliseconds. + */ +static void evsched_settimer(event_t *e, uint32_t dt) +{ + if (!e) { + return; + } + + /* Get absolute time T. */ + gettimeofday(&e->tv, 0); + + /* Add number of seconds. */ + e->tv.tv_sec += dt / 1000; + + /* Add the number of microseconds. */ + e->tv.tv_usec += (dt % 1000) * 1000; + + /* Check for overflow. */ + while (e->tv.tv_usec > 999999) { + e->tv.tv_sec += 1; + e->tv.tv_usec -= 1 * 1000 * 1000; + } +} + +/*! \brief Singleton application-wide event scheduler. */ +evsched_t *s_evsched = 0; + +evsched_t *evsched_new() +{ + evsched_t *s = malloc(sizeof(evsched_t)); + if (!s) { + return 0; + } + + /* Initialize event calendar. */ + pthread_mutex_init(&s->rl, 0); + pthread_mutex_init(&s->mx, 0); + pthread_cond_init(&s->notify, 0); + pthread_mutex_init(&s->cache.lock, 0); + slab_cache_init(&s->cache.alloc, sizeof(event_t)); + init_list(&s->calendar); + return s; +} + +void evsched_delete(evsched_t **s) +{ + if (!s) { + return; + } + if (!*s) { + return; + } + + /* Deinitialize event calendar. */ + pthread_mutex_destroy(&(*s)->rl); + pthread_mutex_destroy(&(*s)->mx); + pthread_cond_destroy(&(*s)->notify); + node *n = 0, *nxt = 0; + WALK_LIST_DELSAFE(n, nxt, (*s)->calendar) { + evsched_event_free((*s), (event_t*)n); + } + + /* Free allocator. */ + slab_cache_destroy(&(*s)->cache.alloc); + pthread_mutex_destroy(&(*s)->cache.lock); + + /* Free scheduler. */ + free(*s); + *s = 0; +} + +event_t *evsched_event_new(evsched_t *s, int type) +{ + if (!s) { + return 0; + } + + /* Allocate. */ + pthread_mutex_lock(&s->cache.lock); + event_t *e = slab_cache_alloc(&s->cache.alloc); + pthread_mutex_unlock(&s->cache.lock); + + /* Initialize. */ + memset(e, 0, sizeof(event_t)); + e->type = type; + return e; +} + +void evsched_event_free(evsched_t *s, event_t *ev) +{ + if (!s || !ev) { + return; + } + + pthread_mutex_lock(&s->cache.lock); + slab_free(ev); + pthread_mutex_unlock(&s->cache.lock); +} + +event_t* evsched_next(evsched_t *s) +{ + /* Check. */ + if (!s) { + return 0; + } + + /* Lock calendar. */ + pthread_mutex_lock(&s->mx); + + while(1) { + + /* Check event queue. */ + if (!EMPTY_LIST(s->calendar)) { + + /* Get current time. */ + struct timeval dt; + gettimeofday(&dt, 0); + + /* Get next event. */ + event_t *next_ev = HEAD(s->calendar); + + /* Immediately return. */ + if (timercmp(&dt, &next_ev->tv, >=)) { + rem_node(&next_ev->n); + pthread_mutex_unlock(&s->mx); + pthread_mutex_lock(&s->rl); + s->current = next_ev; + return next_ev; + } + + /* Wait for next event or interrupt. Unlock calendar. */ + struct timespec ts; + ts.tv_sec = next_ev->tv.tv_sec; + ts.tv_nsec = next_ev->tv.tv_usec * 1000L; + pthread_cond_timedwait(&s->notify, &s->mx, &ts); + } else { + /* Block until an event is scheduled. Unlock calendar.*/ + pthread_cond_wait(&s->notify, &s->mx); + } + } + + /* Unlock calendar, this shouldn't happen. */ + pthread_mutex_unlock(&s->mx); + return 0; + +} + +int evsched_event_finished(evsched_t *s) +{ + if (!s) { + return -1; + } + + /* Mark as finished. */ + if (s->current) { + s->current = 0; + pthread_mutex_unlock(&s->rl); + return 0; + } + + /* Finished event is not current. */ + return -1; +} + +int evsched_schedule(evsched_t *s, event_t *ev, uint32_t dt) +{ + if (!s || !ev || dt < 0) { + return -1; + } + + /* Update event timer. */ + evsched_settimer(ev, dt); + + /* Lock calendar. */ + pthread_mutex_lock(&s->mx); + + /* Schedule event. */ + node *n = 0, *prev = 0; + if (!EMPTY_LIST(s->calendar)) { + WALK_LIST(n, s->calendar) { + event_t* cur = (event_t *)n; + if (timercmp(&cur->tv, &ev->tv, <)) { + prev = n; + } else { + break; + } + } + } + + /* Append to list. */ + ev->parent = s; + if (prev) { + insert_node(&ev->n, prev); + } else { + add_head(&s->calendar, &ev->n); + } + + + /* Unlock calendar. */ + pthread_cond_signal(&s->notify); + pthread_mutex_unlock(&s->mx); + + return 0; +} + +event_t* evsched_schedule_cb(evsched_t *s, event_cb_t cb, void *data, uint32_t dt) +{ + if (!s) { + return 0; + } + + /* Create event. */ + event_t *e = evsched_event_new(s, EVSCHED_CB); + if (!e) { + return 0; + } + e->cb = cb; + e->data = data; + + /* Schedule. */ + if (evsched_schedule(s, e, dt) != 0) { + evsched_event_free(s, e); + e = 0; + } + + return e; +} + +event_t* evsched_schedule_term(evsched_t *s, uint32_t dt) +{ + if (!s) { + return 0; + } + + /* Create event. */ + event_t *e = evsched_event_new(s, EVSCHED_TERM); + if (!e) { + return 0; + } + + /* Schedule. */ + if (evsched_schedule(s, e, dt) != 0) { + evsched_event_free(s, e); + e = 0; + } + + return e; +} + +int evsched_cancel(evsched_t *s, event_t *ev) +{ + if (!s || !ev) { + return -1; + } + + /* Lock calendar. */ + pthread_mutex_lock(&s->mx); + + /* Make sure not running. */ + pthread_mutex_lock(&s->rl); + + /* Find in list. */ + event_t *n = 0; + int found = 0; + WALK_LIST(n, s->calendar) { + if (n == ev) { + found = 1; + break; + } + } + + /* Remove from list. */ + if (found) { + rem_node(&ev->n); + } + + /* Enable running events. */ + pthread_mutex_unlock(&s->rl); + + /* Unlock calendar. */ + pthread_cond_signal(&s->notify); + pthread_mutex_unlock(&s->mx); + + return 0; +} + diff --git a/src/common/evsched.h b/src/common/evsched.h new file mode 100644 index 0000000..2a682e1 --- /dev/null +++ b/src/common/evsched.h @@ -0,0 +1,240 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file evsched.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Event scheduler. + * + * Scheduler works with the same event_t type as event queue. + * It is also thread-safe so the scheduler can run in a separate thread + * while events can be enqueued from different threads. + * + * Guideline is, that the scheduler run loop should exit with + * a special event type EVSCHED_TERM. + * + * Example usage: + * \code + * evsched_t *s = evsched_new(); + * + * // Schedule myfunc() after 1000ms + * evsched_schedule_cb(s, myfunc, data, 1000) + * + * // Schedule termination event after 1500ms + * evsched_schedule_term(s, 1500); + * + * // Event scheduler main loop + * while (1) { + * // Wait for next scheduled event + * event_t *ev = evsched_next(); + * + * // Break on termination event + * if (ev->type == EVSCHED_TERM) { + * evsched_event_free(s, ev); + * break; + * } + * + * // Execute and discard event + * if (ev->cb) { + * ev->cb(ev); + * } + * evsched_event_free(s, ev); // Free executed event + * } + * + * // Delete event scheduler + * evsched_delete(s); + * \endcode + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_COMMON_EVSCHED_H_ +#define _KNOTD_COMMON_EVSCHED_H_ + +#include <pthread.h> +#include "common/slab/slab.h" +#include "common/lists.h" +#include "common/evqueue.h" + +/*! + * \brief Scheduler event types. + */ +typedef enum evsched_ev_t { + EVSCHED_NOOP = 0, /*!< No-op action, skip. */ + EVSCHED_CB, /*!< Callback action. */ + EVSCHED_TERM /*!< Terminal action, stop event scheduler. */ +} evsched_ev_t; + +/*! + * \brief Event scheduler structure. + * + * Keeps list of scheduled events. Events are executed in their scheduled + * time and kept in an ordered list (queue). + * Scheduler is terminated with a special EVSCHED_TERM event type. + */ +typedef struct { + pthread_mutex_t rl; /*!< Event running lock. */ + event_t *current; /*!< Current running event. */ + pthread_mutex_t mx; /*!< Event queue locking. */ + pthread_cond_t notify; /*!< Event queue notification. */ + list calendar; /*!< Event calendar. */ + struct { + slab_cache_t alloc; /*!< Events SLAB cache. */ + pthread_mutex_t lock; /*!< Events cache spin lock. */ + } cache; +} evsched_t; + +/*! + * \brief Create new event scheduler instance. + * + * \retval New instance on success. + * \retval NULL on error. + */ +evsched_t *evsched_new(); + +/*! + * \brief Deinitialize and free event scheduler instance. + * + * \param s Pointer to event scheduler instance. + * \note *sched is set to 0. + */ +void evsched_delete(evsched_t **s); + +/*! + * \brief Create an empty event. + * + * \param s Pointer to event scheduler instance. + * \param type Event type. + * \retval New instance on success. + * \retval NULL on error. + */ +event_t *evsched_event_new(evsched_t *s, int type); + +/*! + * \brief Dispose event instance. + * + * \param s Pointer to event scheduler instance. + * \param ev Event instance. + */ +void evsched_event_free(evsched_t *s, event_t *ev); + +/*! + * \brief Fetch next-event. + * + * Scheduler may block until a next event is available. + * Send scheduler an EVSCHED_NOOP or EVSCHED_TERM event to unblock it. + * + * \warning Returned event must be marked as finished, or deadlock occurs. + * + * \param s Event scheduler. + * + * \retval Scheduled event. + * \retval NULL on error. + */ +event_t* evsched_next(evsched_t *s); + +/*! + * \brief Mark running event as finished. + * + * Need to call this after each event returned by evsched_next() is finished. + * + * \param s Event scheduler. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int evsched_event_finished(evsched_t *s); + +/*! + * \brief Schedule an event. + * + * \param s Event scheduler. + * \param ev Prepared event. + * \param dt Time difference in milliseconds from now (dt is relative). + * + * \retval 0 on success. + * \retval <0 on error. + */ +int evsched_schedule(evsched_t *s, event_t *ev, uint32_t dt); + +/*! + * \brief Schedule callback event. + * + * Execute callback after dt miliseconds has passed. + * + * \param s Event scheduler. + * \param cb Callback handler. + * \param data Data for callback. + * \param dt Time difference in milliseconds from now (dt is relative). + * + * \retval Event instance on success. + * \retval NULL on error. + */ +event_t* evsched_schedule_cb(evsched_t *s, event_cb_t cb, void *data, uint32_t dt); + +/*! + * \brief Schedule termination event. + * + * Special action for scheduler termination. + * + * \param s Event scheduler. + * \param dt Time difference in milliseconds from now (dt is relative). + * + * \retval Event instance on success. + * \retval NULL on error. + */ +event_t* evsched_schedule_term(evsched_t *s, uint32_t dt); + +/*! + * \brief Cancel a scheduled event. + * + * \warning May block until current running event is finished (as it cannot + * interrupt running event). + * + * \warning Never cancel event in it's callback. As it never finishes, + * it deadlocks. + * + * \param s Event scheduler. + * \param ev Scheduled event. + * + * \retval 0 on success. + * \retval <0 on error. + */ +int evsched_cancel(evsched_t *s, event_t *ev); + +/* Singleton event scheduler pointer. */ +extern evsched_t *s_evsched; + +/*! + * \brief Event scheduler singleton. + */ +static inline evsched_t *evsched() { + return s_evsched; +} + +/*! + * \brief Set event scheduler singleton. + */ +static inline void evsched_set(evsched_t *s) { + s_evsched = s; +} + + +#endif /* _KNOTD_COMMON_EVSCHED_H_ */ + +/*! @} */ diff --git a/src/common/fdset.c b/src/common/fdset.c new file mode 100644 index 0000000..8c070ad --- /dev/null +++ b/src/common/fdset.c @@ -0,0 +1,80 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* Required for RTLD_DEFAULT. */ +#endif + +#include <dlfcn.h> +#include <string.h> +#include <stdio.h> +#include "common/fdset.h" +#include <config.h> + +struct fdset_backend_t _fdset_backend = { +}; + +/*! \brief Set backend implementation. */ +static void fdset_set_backend(struct fdset_backend_t *backend) { + memcpy(&_fdset_backend, backend, sizeof(struct fdset_backend_t)); +} + +/* Linux epoll API. */ +#ifdef HAVE_EPOLL_WAIT + #include "common/fdset_epoll.h" +#endif /* HAVE_EPOLL_WAIT */ + +/* BSD kqueue API */ +#ifdef HAVE_KQUEUE + #include "common/fdset_kqueue.h" +#endif /* HAVE_KQUEUE */ + +/* POSIX poll API */ +#ifdef HAVE_POLL + #include "common/fdset_poll.h" +#endif /* HAVE_POLL */ + +/*! \brief Bootstrap polling subsystem (it is called automatically). */ +void __attribute__ ((constructor)) fdset_init() +{ + /* Preference: epoll */ +#ifdef HAVE_EPOLL_WAIT + if (dlsym(RTLD_DEFAULT, "epoll_wait") != 0) { + fdset_set_backend(&FDSET_EPOLL); + return; + } +#endif + + /* Preference: kqueue */ +#ifdef HAVE_KQUEUE + if (dlsym(RTLD_DEFAULT, "kqueue") != 0) { + fdset_set_backend(&FDSET_KQUEUE); + return; + } +#endif + + /* Fallback: poll */ +#ifdef HAVE_POLL + if (dlsym(RTLD_DEFAULT, "poll") != 0) { + fdset_set_backend(&FDSET_POLL); + return; + } +#endif + + /* This shouldn't happen. */ + fprintf(stderr, "fdset: fatal error - no valid fdset backend found\n"); + return; +} diff --git a/src/common/fdset.h b/src/common/fdset.h new file mode 100644 index 0000000..10196bf --- /dev/null +++ b/src/common/fdset.h @@ -0,0 +1,196 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file fdset.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Wrapper for native I/O multiplexing. + * + * Selects best implementation according to config. + * - select() + * - poll() \todo + * - epoll() + * - kqueue() + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_FDSET_H_ +#define _KNOTD_FDSET_H_ + +#include <stddef.h> + +/*! \brief Opaque pointer to implementation-specific fdset data. */ +typedef struct fdset_t fdset_t; + +/*! \brief Unified event types. */ +enum fdset_event_t { + OS_EV_READ = 1 << 0, /*!< Readable event. */ + OS_EV_WRITE = 1 << 1, /*!< Writeable event. */ + OS_EV_ERROR = 1 << 2 /*!< Error event. */ +}; + +/*! \brief File descriptor set iterator. */ +typedef struct fdset_it_t { + int fd; /*!< Current file descriptor. */ + int events; /*!< Returned events. */ + size_t pos; /* Internal usage. */ +} fdset_it_t; + +/*! + * \brief File descriptor set implementation backend. + * \notice Functions documentation following. + * \internal + */ +struct fdset_backend_t +{ + fdset_t* (*fdset_new)(); + int (*fdset_destroy)(fdset_t*); + int (*fdset_add)(fdset_t*, int, int); + int (*fdset_remove)(fdset_t*, int); + int (*fdset_wait)(fdset_t*); + int (*fdset_begin)(fdset_t*, fdset_it_t*); + int (*fdset_end)(fdset_t*, fdset_it_t*); + int (*fdset_next)(fdset_t*, fdset_it_t*); + const char* (*fdset_method)(); +}; + +/*! + * \brief Selected backend. + * \internal + */ +extern struct fdset_backend_t _fdset_backend; + +/*! + * \brief Create new fdset. + * + * FDSET implementation depends on system. + * + * \retval Pointer to initialized FDSET structure if successful. + * \retval NULL on error. + */ +static inline fdset_t *fdset_new() { + return _fdset_backend.fdset_new(); +} + +/*! + * \brief Destroy FDSET. + * + * \retval 0 if successful. + * \retval -1 on error. + */ +static inline int fdset_destroy(fdset_t * fdset) { + return _fdset_backend.fdset_destroy(fdset); +} + +/*! + * \brief Add file descriptor to watched set. + * + * \param fdset Target set. + * \param fd Added file descriptor. + * \param events Mask of watched events. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +static inline int fdset_add(fdset_t *fdset, int fd, int events) { + return _fdset_backend.fdset_add(fdset, fd, events); +} + + +/*! + * \brief Remove file descriptor from watched set. + * + * \param fdset Target set. + * \param fd File descriptor to be removed. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +static inline int fdset_remove(fdset_t *fdset, int fd) { + return _fdset_backend.fdset_remove(fdset, fd); +} + +/*! + * \brief Poll set for new events. + * + * \param fdset Target set. + * + * \retval Number of events if successful. + * \retval -1 on errors. + * + * \todo Timeout. + */ +static inline int fdset_wait(fdset_t *fdset) { + return _fdset_backend.fdset_wait(fdset); +} + +/*! + * \brief Set event iterator to the beginning of last polled events. + * + * \param fdset Target set. + * \param it Event iterator. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +static inline int fdset_begin(fdset_t *fdset, fdset_it_t *it) { + return _fdset_backend.fdset_begin(fdset, it); +} + +/*! + * \brief Set event iterator to the end of last polled events. + * + * \param fdset Target set. + * \param it Event iterator. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +static inline int fdset_end(fdset_t *fdset, fdset_it_t *it) { + return _fdset_backend.fdset_end(fdset, it); +} + +/*! + * \brief Set event iterator to the next event. + * + * Event iterator fd will be set to -1 if next event doesn't exist. + * + * \param fdset Target set. + * \param it Event iterator. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +static inline int fdset_next(fdset_t *fdset, fdset_it_t *it) { + return _fdset_backend.fdset_next(fdset, it); +} + +/*! + * \brief Returned name of underlying poll method. + * + * \retval Name if successful. + * \retval NULL if no method was loaded (shouldn't happen). + */ +static inline const char* fdset_method() { + return _fdset_backend.fdset_method(); +} + +#endif /* _KNOTD_FDSET_H_ */ + +/*! @} */ diff --git a/src/common/fdset_epoll.c b/src/common/fdset_epoll.c new file mode 100644 index 0000000..cb2e3e1 --- /dev/null +++ b/src/common/fdset_epoll.c @@ -0,0 +1,216 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#ifdef HAVE_EPOLL_WAIT + +#include <sys/epoll.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "fdset_epoll.h" + +#define OS_FDS_CHUNKSIZE 8 /*!< Number of pollfd structs in a chunk. */ +#define OS_FDS_KEEPCHUNKS 32 /*!< Will attempt to free memory when reached. */ + +struct fdset_t { + int epfd; + struct epoll_event *events; + size_t nfds; + size_t reserved; + size_t polled; +}; + +fdset_t *fdset_epoll_new() +{ + fdset_t *set = malloc(sizeof(fdset_t)); + if (!set) { + return 0; + } + + /* Blank memory. */ + memset(set, 0, sizeof(fdset_t)); + + /* Create epoll fd. */ + set->epfd = epoll_create(OS_FDS_CHUNKSIZE); + + return set; +} + +int fdset_epoll_destroy(fdset_t * fdset) +{ + if(!fdset) { + return -1; + } + + /* Teardown epoll. */ + close(fdset->epfd); + + /* OK if NULL. */ + free(fdset->events); + free(fdset); + return 0; +} + +int fdset_epoll_add(fdset_t *fdset, int fd, int events) +{ + if (!fdset || fd < 0 || events <= 0) { + return -1; + } + + /* Realloc needed. */ + if (fdset->nfds == fdset->reserved) { + const size_t chunk = OS_FDS_CHUNKSIZE; + const size_t nsize = (fdset->reserved + chunk) * + sizeof(struct epoll_event); + struct epoll_event *events_n = malloc(nsize); + if (!events_n) { + return -1; + } + + /* Clear and copy old fdset data. */ + memset(events_n, 0, nsize); + memcpy(events_n, fdset->events, + fdset->nfds * sizeof(struct epoll_event)); + free(fdset->events); + fdset->events = events_n; + fdset->reserved += chunk; + } + + /* Add to epoll set. */ + struct epoll_event ev; + memset(&ev, 0, sizeof(struct epoll_event)); + ev.events = EPOLLIN; /*! \todo MAP events. */ + ev.data.fd = fd; + if (epoll_ctl(fdset->epfd, EPOLL_CTL_ADD, fd, &ev) < 0) { + return -1; + } + + ++fdset->nfds; + return 0; +} + +int fdset_epoll_remove(fdset_t *fdset, int fd) +{ + if (!fdset || fd < 0) { + return -1; + } + + /* Attempt to remove from set. */ + struct epoll_event ev; + memset(&ev, 0, sizeof(struct epoll_event)); + if (epoll_ctl(fdset->epfd, EPOLL_CTL_DEL, fd, &ev) < 0) { + return -1; + } + + /* Overwrite current item. */ + --fdset->nfds; + + /*! \todo Return memory if overallocated (nfds is far lower than reserved). */ + return 0; +} + +int fdset_epoll_wait(fdset_t *fdset) +{ + if (!fdset || fdset->nfds < 1 || !fdset->events) { + return -1; + } + + /* Poll new events. */ + fdset->polled = 0; + int nfds = epoll_wait(fdset->epfd, fdset->events, fdset->nfds, -1); + + /* Check. */ + if (nfds < 0) { + return -1; + } + + /* Events array is ordered from 0 to nfds. */ + fdset->polled = nfds; + return nfds; +} + +int fdset_epoll_begin(fdset_t *fdset, fdset_it_t *it) +{ + if (!fdset || !it) { + return -1; + } + + /* Find first. */ + it->pos = 0; + return fdset_next(fdset, it); +} + +int fdset_epoll_end(fdset_t *fdset, fdset_it_t *it) +{ + if (!fdset || !it || fdset->nfds < 1) { + return -1; + } + + /* Check for polled events. */ + if (fdset->polled < 1) { + it->fd = -1; + it->pos = 0; + return -1; + } + + /* No end found, ends on the beginning. */ + size_t nid = fdset->polled - 1; + it->fd = fdset->events[nid].data.fd; + it->pos = nid; + it->events = 0; /*! \todo Map events. */ + return -1; +} + +int fdset_epoll_next(fdset_t *fdset, fdset_it_t *it) +{ + if (!fdset || !it || fdset->nfds < 1) { + return -1; + } + + /* Check boundaries. */ + if (it->pos >= fdset->polled) { + return -1; + } + + /* Select next. */ + size_t nid = it->pos++; + it->fd = fdset->events[nid].data.fd; + it->events = 0; /*! \todo Map events. */ + return 0; +} + +const char* fdset_epoll_method() +{ + return "epoll"; +} + +/* Package APIs. */ +struct fdset_backend_t FDSET_EPOLL = { + .fdset_new = fdset_epoll_new, + .fdset_destroy = fdset_epoll_destroy, + .fdset_add = fdset_epoll_add, + .fdset_remove = fdset_epoll_remove, + .fdset_wait = fdset_epoll_wait, + .fdset_begin = fdset_epoll_begin, + .fdset_end = fdset_epoll_end, + .fdset_next = fdset_epoll_next, + .fdset_method = fdset_epoll_method +}; + +#endif diff --git a/src/common/fdset_epoll.h b/src/common/fdset_epoll.h new file mode 100644 index 0000000..551394d --- /dev/null +++ b/src/common/fdset_epoll.h @@ -0,0 +1,133 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file fdset_epoll.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Wrapper for epoll I/O multiplexing. + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_FDSET_EPOLL_H_ +#define _KNOTD_FDSET_EPOLL_H_ + +#include "fdset.h" + +/*! + * \brief Create new fdset. + * + * Linux epoll() backend. + * + * \retval Pointer to initialized FDSET structure if successful. + * \retval NULL on error. + */ +fdset_t *fdset_epoll_new(); + +/*! + * \brief Destroy FDSET. + * + * \retval 0 if successful. + * \retval -1 on error. + */ +int fdset_epoll_destroy(fdset_t * fdset); + +/*! + * \brief Add file descriptor to watched set. + * + * \param fdset Target set. + * \param fd Added file descriptor. + * \param events Mask of watched events. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_epoll_add(fdset_t *fdset, int fd, int events); + +/*! + * \brief Remove file descriptor from watched set. + * + * \param fdset Target set. + * \param fd File descriptor to be removed. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_epoll_remove(fdset_t *fdset, int fd); + +/*! + * \brief Poll set for new events. + * + * \param fdset Target set. + * + * \retval Number of events if successful. + * \retval -1 on errors. + * + * \todo Timeout. + */ +int fdset_epoll_wait(fdset_t *fdset); + +/*! + * \brief Set event iterator to the beginning of last polled events. + * + * \param fdset Target set. + * \param it Event iterator. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_epoll_begin(fdset_t *fdset, fdset_it_t *it); + +/*! + * \brief Set event iterator to the end of last polled events. + * + * \param fdset Target set. + * \param it Event iterator. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_epoll_end(fdset_t *fdset, fdset_it_t *it); + +/*! + * \brief Set event iterator to the next event. + * + * Event iterator fd will be set to -1 if next event doesn't exist. + * + * \param fdset Target set. + * \param it Event iterator. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_epoll_next(fdset_t *fdset, fdset_it_t *it); + +/*! + * \brief Returned name of epoll method. + * + * \retval Name if successful. + * \retval NULL if no method was loaded (shouldn't happen). + */ +const char* fdset_epoll_method(); + +/*! \brief Exported API. */ +extern struct fdset_backend_t FDSET_EPOLL; + +#endif /* _KNOTD_FDSET_EPOLL_H_ */ + +/*! @} */ diff --git a/src/common/fdset_kqueue.c b/src/common/fdset_kqueue.c new file mode 100644 index 0000000..c7199ae --- /dev/null +++ b/src/common/fdset_kqueue.c @@ -0,0 +1,251 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#ifdef HAVE_KQUEUE + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/event.h> +#include <sys/time.h> + +#include "fdset_kqueue.h" + +#define OS_FDS_CHUNKSIZE 8 /*!< Number of pollfd structs in a chunk. */ +#define OS_FDS_KEEPCHUNKS 32 /*!< Will attempt to free memory when reached. */ + +struct fdset_t { + int kq; + struct kevent *events; + struct kevent *revents; + size_t nfds; + size_t reserved; + size_t polled; +}; + +fdset_t *fdset_kqueue_new() +{ + fdset_t *set = malloc(sizeof(fdset_t)); + if (!set) { + return 0; + } + + /* Blank memory. */ + memset(set, 0, sizeof(fdset_t)); + + /* Create kqueue fd. */ + set->kq = kqueue(); + if (set->kq < 0) { + free(set); + set = 0; + } + + return set; +} + +int fdset_kqueue_destroy(fdset_t * fdset) +{ + if(!fdset) { + return -1; + } + + /* Teardown kqueue. */ + close(fdset->kq); + + /* OK if NULL. */ + free(fdset->revents); + free(fdset->events); + free(fdset); + return 0; +} + +int fdset_kqueue_realloc(void **old, size_t oldsize, size_t nsize) +{ + void *nmem = malloc(nsize); + if (!nmem) { + return -1; + } + + /* Clear and copy old fdset data. */ + memset(nmem, 0, nsize); + if (oldsize > 0) { + memcpy(nmem, *old, oldsize); + free(*old); + } + + *old = nmem; + return 0; +} + +int fdset_kqueue_add(fdset_t *fdset, int fd, int events) +{ + if (!fdset || fd < 0 || events <= 0) { + return -1; + } + + /* Realloc needed. */ + if (fdset->nfds == fdset->reserved) { + const size_t chunk = OS_FDS_CHUNKSIZE; + const size_t nsize = (fdset->reserved + chunk) * + sizeof(struct kevent); + const size_t oldsize = fdset->nfds * sizeof(struct kevent); + + if (fdset_kqueue_realloc(&fdset->events, oldsize, nsize) < 0) { + return -1; + } + + if (fdset_kqueue_realloc(&fdset->revents, oldsize, nsize) < 0) { + return -1; + } + + } + + /* Add to kqueue set. */ + int evfilt = EVFILT_READ; /*! \todo Map events. */ + EV_SET(&fdset->events[fdset->nfds], fd, evfilt, + EV_ADD|EV_ENABLE, 0, 0, 0); + + ++fdset->nfds; + return 0; +} + +int fdset_kqueue_remove(fdset_t *fdset, int fd) +{ + if (!fdset || fd < 0) { + return -1; + } + + /* Find in set. */ + int pos = -1; + for (int i = 0; i < fdset->nfds; ++i) { + if (fdset->events[i].ident == fd) { + pos = i; + break; + } + } + + if (pos < 0) { + return -1; + } + + /* Remove filters. */ + EV_SET(&fdset->events[pos], fd, EVFILT_READ, + EV_DISABLE|EV_DELETE, 0, 0, 0); + + /* Attempt to remove from set. */ + size_t remaining = ((fdset->nfds - pos) - 1) * sizeof(struct kevent); + memmove(fdset->events + pos, fdset->events + (pos + 1), remaining); + + /* Overwrite current item. */ + --fdset->nfds; + + /*! \todo Return memory if overallocated (nfds is far lower than reserved). */ + return 0; +} + +int fdset_kqueue_wait(fdset_t *fdset) +{ + if (!fdset || fdset->nfds < 1 || !fdset->events) { + return -1; + } + + /* Poll new events. */ + fdset->polled = 0; + int nfds = kevent(fdset->kq, fdset->events, fdset->nfds, + fdset->revents, fdset->nfds, 0); + + /* Check. */ + if (nfds < 0) { + return -1; + } + + /* Events array is ordered from 0 to nfds. */ + fdset->polled = nfds; + return nfds; +} + +int fdset_kqueue_begin(fdset_t *fdset, fdset_it_t *it) +{ + if (!fdset || !it) { + return -1; + } + + /* Find first. */ + it->pos = 0; + return fdset_next(fdset, it); +} + +int fdset_kqueue_end(fdset_t *fdset, fdset_it_t *it) +{ + if (!fdset || !it || fdset->nfds < 1) { + return -1; + } + + /* Check for polled events. */ + if (fdset->polled < 1) { + it->fd = -1; + it->pos = 0; + return -1; + } + + /* No end found, ends on the beginning. */ + size_t nid = fdset->polled - 1; + it->fd = fdset->revents[nid].ident; + it->pos = nid; + it->events = 0; /*! \todo Map events. */ + return -1; +} + +int fdset_kqueue_next(fdset_t *fdset, fdset_it_t *it) +{ + if (!fdset || !it || fdset->nfds < 1) { + return -1; + } + + /* Check boundaries. */ + if (it->pos >= fdset->polled) { + return -1; + } + + /* Select next. */ + size_t nid = it->pos++; + it->fd = fdset->revents[nid].ident; + it->events = 0; /*! \todo Map events. */ + return 0; +} + +const char* fdset_kqueue_method() +{ + return "kqueue"; +} + +/* Package APIs. */ +struct fdset_backend_t FDSET_KQUEUE = { + .fdset_new = fdset_kqueue_new, + .fdset_destroy = fdset_kqueue_destroy, + .fdset_add = fdset_kqueue_add, + .fdset_remove = fdset_kqueue_remove, + .fdset_wait = fdset_kqueue_wait, + .fdset_begin = fdset_kqueue_begin, + .fdset_end = fdset_kqueue_end, + .fdset_next = fdset_kqueue_next, + .fdset_method = fdset_kqueue_method +}; + +#endif diff --git a/src/common/fdset_kqueue.h b/src/common/fdset_kqueue.h new file mode 100644 index 0000000..f64482f --- /dev/null +++ b/src/common/fdset_kqueue.h @@ -0,0 +1,133 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file fdset_kqueue.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Wrapper for kqueue I/O multiplexing. + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_FDSET_KQUEUE_H_ +#define _KNOTD_FDSET_KQUEUE_H_ + +#include "fdset.h" + +/*! + * \brief Create new fdset. + * + * BSD kqueue() backend. + * + * \retval Pointer to initialized FDSET structure if successful. + * \retval NULL on error. + */ +fdset_t *fdset_kqueue_new(); + +/*! + * \brief Destroy FDSET. + * + * \retval 0 if successful. + * \retval -1 on error. + */ +int fdset_kqueue_destroy(fdset_t * fdset); + +/*! + * \brief Add file descriptor to watched set. + * + * \param fdset Target set. + * \param fd Added file descriptor. + * \param events Mask of watched events. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_kqueue_add(fdset_t *fdset, int fd, int events); + +/*! + * \brief Remove file descriptor from watched set. + * + * \param fdset Target set. + * \param fd File descriptor to be removed. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_kqueue_remove(fdset_t *fdset, int fd); + +/*! + * \brief Poll set for new events. + * + * \param fdset Target set. + * + * \retval Number of events if successful. + * \retval -1 on errors. + * + * \todo Timeout. + */ +int fdset_kqueue_wait(fdset_t *fdset); + +/*! + * \brief Set event iterator to the beginning of last polled events. + * + * \param fdset Target set. + * \param it Event iterator. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_kqueue_begin(fdset_t *fdset, fdset_it_t *it); + +/*! + * \brief Set event iterator to the end of last polled events. + * + * \param fdset Target set. + * \param it Event iterator. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_kqueue_end(fdset_t *fdset, fdset_it_t *it); + +/*! + * \brief Set event iterator to the next event. + * + * Event iterator fd will be set to -1 if next event doesn't exist. + * + * \param fdset Target set. + * \param it Event iterator. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_kqueue_next(fdset_t *fdset, fdset_it_t *it); + +/*! + * \brief Returned name of kqueue method. + * + * \retval Name if successful. + * \retval NULL if no method was loaded (shouldn't happen). + */ +const char* fdset_kqueue_method(); + +/*! \brief Exported API. */ +extern struct fdset_backend_t FDSET_KQUEUE; + +#endif /* _KNOTD_FDSET_KQUEUE_H_ */ + +/*! @} */ diff --git a/src/common/fdset_poll.c b/src/common/fdset_poll.c new file mode 100644 index 0000000..8682eaf --- /dev/null +++ b/src/common/fdset_poll.c @@ -0,0 +1,230 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#ifdef HAVE_POLL + +#include <stdlib.h> +#include <string.h> +#include <sys/poll.h> +#include <stddef.h> + +#include "common/fdset_poll.h" + +#define OS_FDS_CHUNKSIZE 8 /*!< Number of pollfd structs in a chunk. */ +#define OS_FDS_KEEPCHUNKS 32 /*!< Will attempt to free memory when reached. */ + +struct fdset_t { + struct pollfd *fds; + nfds_t nfds; + size_t reserved; + size_t polled; + size_t begin; +}; + +fdset_t *fdset_poll_new() +{ + fdset_t *set = malloc(sizeof(fdset_t)); + if (!set) { + return 0; + } + + /* Blank memory. */ + memset(set, 0, sizeof(fdset_t)); + return set; +} + +int fdset_poll_destroy(fdset_t * fdset) +{ + if(!fdset) { + return -1; + } + + /*! \todo No teardown required I guess. */ + + /* OK if NULL. */ + free(fdset->fds); + free(fdset); + return 0; +} + +int fdset_poll_add(fdset_t *fdset, int fd, int events) +{ + if (!fdset || fd < 0 || events <= 0) { + return -1; + } + + /* Realloc needed. */ + if (fdset->nfds == fdset->reserved) { + const size_t chunk = OS_FDS_CHUNKSIZE; + const size_t nsize = sizeof(struct pollfd) * (fdset->reserved + chunk); + struct pollfd *fds_n = malloc(nsize); + if (!fds_n) { + return -1; + } + + /* Clear and copy old fdset data. */ + memset(fds_n, 0, nsize); + memcpy(fds_n, fdset->fds, fdset->nfds * sizeof(struct pollfd)); + free(fdset->fds); + fdset->fds = fds_n; + fdset->reserved += chunk; + } + + /* Append. */ + int nid = fdset->nfds++; + fdset->fds[nid].fd = fd; + fdset->fds[nid].events = POLLIN; /*! \todo Map events to POLL events. */ + return 0; +} + +int fdset_poll_remove(fdset_t *fdset, int fd) +{ + if (!fdset || fd < 0) { + return -1; + } + + /* Find file descriptor. */ + unsigned found = 0; + size_t pos = 0; + for (size_t i = 0; i < fdset->nfds; ++i) { + if (fdset->fds[i].fd == fd) { + found = 1; + pos = i; + break; + } + } + + /* Check. */ + if (!found) { + return -1; + } + + /* Overwrite current item. */ + size_t remaining = ((fdset->nfds - pos) - 1) * sizeof(struct pollfd); + memmove(fdset->fds + pos, fdset->fds + (pos + 1), remaining); + --fdset->nfds; + + /*! \todo Return memory if overallocated (nfds is far lower than reserved). */ + /*! \todo Maybe >64 free chunks is excess? */ + return 0; +} + +int fdset_poll_wait(fdset_t *fdset) +{ + if (!fdset || fdset->nfds < 1 || !fdset->fds) { + return -1; + } + + /* Initialize pointers. */ + fdset->polled = 0; + fdset->begin = 0; + + /* Poll for events. */ + int ret = poll(fdset->fds, fdset->nfds, -1); + if (ret < 0) { + return -1; + } + + /* Set pointers for iterating. */ + fdset->polled = ret; + fdset->begin = 0; + return ret; +} + +int fdset_poll_begin(fdset_t *fdset, fdset_it_t *it) +{ + if (!fdset || !it) { + return -1; + } + + /* Find first. */ + it->pos = 0; + return fdset_next(fdset, it); +} + +int fdset_poll_end(fdset_t *fdset, fdset_it_t *it) +{ + if (!fdset || !it || fdset->nfds < 1) { + return -1; + } + + /* Check for polled events. */ + if (fdset->polled < 1) { + it->fd = -1; + it->pos = 0; + return -1; + } + + /* Trace last matching item from the end. */ + struct pollfd* pfd = fdset->fds + fdset->nfds - 1; + while (pfd != fdset->fds) { + if (pfd->events & pfd->revents) { + it->fd = pfd->fd; + it->pos = pfd - fdset->fds; + return 0; + } + } + + /* No end found, ends on the beginning. */ + it->fd = -1; + it->pos = 0; + return -1; +} + +int fdset_poll_next(fdset_t *fdset, fdset_it_t *it) +{ + if (!fdset || !it || fdset->nfds < 1) { + return -1; + } + + /* Find next with matching flags. */ + for (; it->pos < fdset->nfds; ++it->pos) { + struct pollfd* pfd = fdset->fds + it->pos; + if (pfd->events & pfd->revents) { + it->fd = pfd->fd; + it->events = pfd->revents; /*! \todo MAP events. */ + ++it->pos; /* Next will start after current. */ + return 0; + } + } + + /* No matching event found. */ + it->fd = -1; + it->pos = 0; + return -1; +} + +const char* fdset_poll_method() +{ + return "poll"; +} + +/* Package APIs. */ +struct fdset_backend_t FDSET_POLL = { + .fdset_new = fdset_poll_new, + .fdset_destroy = fdset_poll_destroy, + .fdset_add = fdset_poll_add, + .fdset_remove = fdset_poll_remove, + .fdset_wait = fdset_poll_wait, + .fdset_begin = fdset_poll_begin, + .fdset_end = fdset_poll_end, + .fdset_next = fdset_poll_next, + .fdset_method = fdset_poll_method +}; + +#endif diff --git a/src/common/fdset_poll.h b/src/common/fdset_poll.h new file mode 100644 index 0000000..d72b5bb --- /dev/null +++ b/src/common/fdset_poll.h @@ -0,0 +1,133 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file fdset_poll.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Wrapper for poll I/O multiplexing. + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_FDSET_POLL_H_ +#define _KNOTD_FDSET_POLL_H_ + +#include "fdset.h" + +/*! + * \brief Create new fdset. + * + * POSIX poll() backend. + * + * \retval Pointer to initialized FDSET structure if successful. + * \retval NULL on error. + */ +fdset_t *fdset_poll_new(); + +/*! + * \brief Destroy FDSET. + * + * \retval 0 if successful. + * \retval -1 on error. + */ +int fdset_poll_destroy(fdset_t * fdset); + +/*! + * \brief Add file descriptor to watched set. + * + * \param fdset Target set. + * \param fd Added file descriptor. + * \param events Mask of watched events. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_poll_add(fdset_t *fdset, int fd, int events); + +/*! + * \brief Remove file descriptor from watched set. + * + * \param fdset Target set. + * \param fd File descriptor to be removed. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_poll_remove(fdset_t *fdset, int fd); + +/*! + * \brief Poll set for new events. + * + * \param fdset Target set. + * + * \retval Number of events if successful. + * \retval -1 on errors. + * + * \todo Timeout. + */ +int fdset_poll_wait(fdset_t *fdset); + +/*! + * \brief Set event iterator to the beginning of last polled events. + * + * \param fdset Target set. + * \param it Event iterator. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_poll_begin(fdset_t *fdset, fdset_it_t *it); + +/*! + * \brief Set event iterator to the end of last polled events. + * + * \param fdset Target set. + * \param it Event iterator. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_poll_end(fdset_t *fdset, fdset_it_t *it); + +/*! + * \brief Set event iterator to the next event. + * + * Event iterator fd will be set to -1 if next event doesn't exist. + * + * \param fdset Target set. + * \param it Event iterator. + * + * \retval 0 if successful. + * \retval -1 on errors. + */ +int fdset_poll_next(fdset_t *fdset, fdset_it_t *it); + +/*! + * \brief Returned name of poll method. + * + * \retval Name if successful. + * \retval NULL if no method was loaded (shouldn't happen). + */ +const char* fdset_poll_method(); + +/*! \brief Exported API. */ +extern struct fdset_backend_t FDSET_POLL; + +#endif /* _KNOTD_FDSET_POLL_H_ */ + +/*! @} */ diff --git a/src/common/general-tree.c b/src/common/general-tree.c new file mode 100644 index 0000000..202b31a --- /dev/null +++ b/src/common/general-tree.c @@ -0,0 +1,215 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include "common/general-tree.h" +#include "common/errors.h" + +MOD_TREE_DEFINE(general_tree_node, avl); + +static void gen_rem_func(struct general_tree_node *n1) +{ + free(n1); +} + +general_tree_t *gen_tree_new(int (*comp_func)(void *, void *)) +{ + general_tree_t *ret = malloc(sizeof(general_tree_t)); + if (ret == NULL) { + return NULL; + } + ret->tree = malloc(sizeof(general_avl_tree_t)); + if (ret->tree == NULL) { + free(ret); + return NULL; + } + MOD_TREE_INIT(ret->tree, comp_func); + return ret; +} + +int gen_tree_add(general_tree_t *tree, + void *node, int (*mrg_func)(void **n1, void **n2)) +{ + struct general_tree_node *tree_node = + malloc(sizeof(struct general_tree_node)); + if (tree_node == NULL) { + return -1; + } + memset(tree_node, 0, sizeof(struct general_tree_node)); + tree_node->data = node; + int merged = 0; + MOD_TREE_INSERT(tree->tree, general_tree_node, avl, + tree_node, mrg_func, &merged); + if (merged) { + free(tree_node); + } + return merged; +} + +void gen_tree_remove(general_tree_t *tree, + void *what) +{ + struct general_tree_node tree_node; + tree_node.data = what; + MOD_TREE_REMOVE(tree->tree, general_tree_node, avl, &tree_node, + gen_rem_func); +} + +void *gen_tree_find(general_tree_t *tree, + void *what) +{ + struct general_tree_node tree_node; + tree_node.data = what; + struct general_tree_node *found_node = + MOD_TREE_FIND(tree->tree, general_tree_node, avl, &tree_node); + if (found_node) { + return found_node->data; + } else { + return NULL; + } +} + +int gen_tree_find_less_or_equal(general_tree_t *tree, + void *what, + void **found) +{ + if (tree == NULL || tree->tree == NULL) { + return -1; + } + + /* Check if tree is empty. */ + if (tree->tree->th_root == NULL) { + *found = NULL; + return 0; + } + + struct general_tree_node *f = NULL, *prev = NULL; + struct general_tree_node tree_node; + tree_node.data = what; + int exact_match = + MOD_TREE_FIND_LESS_EQUAL(tree->tree, general_tree_node, avl, + &tree_node, &f, &prev); + if (exact_match < 0) { + *found = NULL; + exact_match = 0; + } else if (exact_match == 0) { + assert(prev != NULL); + *found = prev->data; + } else { + assert(f != NULL); + *found = f->data; + } +// *found = (exact_match > 0) ? f->data : prev->data; + return exact_match; +} + +void gen_tree_apply_inorder(general_tree_t *tree, + void (*app_func) + (void *node, void *data), void *data) +{ + MOD_TREE_FORWARD_APPLY(tree->tree, general_tree_node, avl, + app_func, data); +} + +void gen_tree_destroy(general_tree_t **tree, + void (*dest_func)(void *node, void *data), void *data) +{ +// gen_tree_apply_inorder(*tree, print_node, NULL); + MOD_TREE_DESTROY((*tree)->tree, general_tree_node, avl, dest_func, + gen_rem_func, data); + free((*tree)->tree); + free(*tree); + *tree = NULL; +} + +void gen_tree_clear(general_tree_t *tree) +{ + MOD_TREE_DESTROY(tree->tree, general_tree_node, avl, NULL, + gen_rem_func, NULL); +} + +//static void add_node_to_tree(void *n, void *data) +//{ +// general_tree_t *tree = (general_tree_t *)data; +// gen_tree_add(tree, n, NULL); +//} + +static int gen_tree_copy_node(const struct general_tree_node *from, + struct general_tree_node **to) +{ + if (from == NULL) { + return 0; + } + + *to = malloc(sizeof(struct general_tree_node)); + if (*to == NULL) { + return -1; + } + memset(*to, 0, sizeof(struct general_tree_node)); + + (*to)->data = from->data; + (*to)->avl.avl_height = from->avl.avl_height; + + int ret = gen_tree_copy_node(from->avl.avl_left, + &(*to)->avl.avl_left); + if (ret != 0) { + return ret; + } + + ret = gen_tree_copy_node(from->avl.avl_right, + &(*to)->avl.avl_right); + if (ret != 0) { + /*! \todo Partially cleaunp tree! */ + (*to)->avl.avl_left = NULL; + return ret; + } + + return 0; +} + +general_tree_t *gen_tree_shallow_copy(general_tree_t *tree) +{ + general_tree_t *new_tree = malloc(sizeof(general_tree_t)); + if (new_tree == NULL) { + return NULL; + } + new_tree->tree = malloc(sizeof(general_avl_tree_t)); + if (new_tree->tree == NULL) { + free(new_tree); + return NULL; + } + + MOD_TREE_INIT(new_tree->tree, tree->tree->th_cmp); + assert(new_tree->tree->th_cmp == tree->tree->th_cmp); + +// gen_tree_apply_inorder(tree, add_node_to_tree, new_tree); + + if (gen_tree_copy_node(tree->tree->th_root, + &new_tree->tree->th_root) != 0) { + return NULL; + } + + /* CLEANUP */ +// gen_tree_apply_inorder(tree, print_node, NULL); +// printf("--------------------------\n"); +// gen_tree_apply_inorder(new_tree, print_node, NULL); + + return new_tree; +} + diff --git a/src/common/general-tree.h b/src/common/general-tree.h new file mode 100644 index 0000000..552638a --- /dev/null +++ b/src/common/general-tree.h @@ -0,0 +1,73 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_COMMON_GENERAL_TREE_H_ +#define _KNOTD_COMMON_GENERAL_TREE_H_ + +#include "common/modified_tree.h" + +typedef MOD_TREE_HEAD(tree, general_tree_node) general_avl_tree_t; + +/* Define tree with void * nodes */ +struct general_tree_node { + MOD_TREE_ENTRY(general_tree_node) avl; +// int (*cmp_func)(void *n1, +// void *n2); +// int (*mrg_func)(void **n1, +// void **n2); +// void (*app_func)(void *n, +// void *data); + void *data; +}; + +struct general_tree { +// int (*cmp_func)(void *n1, +// void *n2); +// int (*mrg_func)(void **n1, +// void **n2); + general_avl_tree_t *tree; +}; + +typedef struct general_tree general_tree_t; + +general_tree_t *gen_tree_new(int (*cmp_func)(void *p1, void *p2)); + +int gen_tree_add(general_tree_t *tree, + void *node, + int (*mrg_func)(void **n1, void **n2)); + +void *gen_tree_find(general_tree_t *tree, + void *what); + +void gen_tree_remove(general_tree_t *tree, + void *what); + +void gen_tree_apply_inorder(general_tree_t *tree, + void (*app_func)(void *node, void *data), + void *data); + +void gen_tree_destroy(general_tree_t **tree, + void (*dest_func)(void *node, void *data), void *data); + +void gen_tree_clear(general_tree_t *tree); + +int gen_tree_find_less_or_equal(general_tree_t *tree, + void *what, + void **found); + +general_tree_t *gen_tree_shallow_copy(general_tree_t *tree); + +#endif // _KNOTD_COMMON_GENERAL_TREE_H_ diff --git a/src/common/latency.c b/src/common/latency.c new file mode 100644 index 0000000..a563f58 --- /dev/null +++ b/src/common/latency.c @@ -0,0 +1,197 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifdef PROF_LATENCY + +#include <sys/resource.h> +#include <sys/socket.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <pthread.h> + + +/*! \brief Profiler statistics. */ +typedef struct pstat_t { + double M; /*!< \brief Mean value. */ + unsigned long min, max; /*!< \brief Minimum, maximum. */ + unsigned long total; /*!< \brief Total profiled time. */ + unsigned long count; /*!< \brief Number of profiled calls. */ +} pstat_t; + +/*! \brief Function call profile. */ +typedef struct profile_t { + const char* call; + pstat_t stat; +} profile_t; + +/*! \brief Observed function call codes. */ +enum pcall_code_t { + PF_RECVFROM = 0, + PF_SENDTO, + PF_PTHREAD_MUTEX_LOCK, + PF_PTHREAD_MUTEX_UNLOCK, + PF_CALL_SIZE +} pcall_code_t; + +/*! \brief Table of observed function calls. */ +static profile_t table[] = { + { "recvfrom", {0} }, + { "sendto", {0} }, + { "pthread_mutex_lock", {0} }, + { "pthread_mutex_unlock", {0} }, + { "NULL", {0} } +}; + + +/*! \brief Add value to statistics. */ +static inline void add_stat(pstat_t *stat, unsigned long val) { + + if (val < stat->min) { + stat->min = val; + } + if (val > stat->max) { + stat->max = val; + } + + stat->total += val; + + double Mprev = stat->M, M = stat->M; + M += (val - M)/((double)stat->count + 1); + stat->M = M; + //S += (val - M)*(x[i] - Mprev); + + ++stat->count; +} + +/*! \brief Call profiler table initialization (automatically called on load). */ +void __attribute__ ((constructor)) profiler_init() +{ + for (int i = 0; i < PF_CALL_SIZE; ++i) { + pstat_t* stat = &table[i].stat; + stat->M = 0; + stat->max = 0; + stat->min = (unsigned long)~0; + stat->total = 0; + stat->count = 0; + } +} + +/*! \brief Call profiler table evaluation (automatically called on exit). */ +void __attribute__ ((destructor)) profiler_deinit() +{ + + /* Get resource usage. */ + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage) < 0) { + memset(&usage, 0, sizeof(struct rusage)); + } + + fprintf(stderr, "\nStatistics:"); + fprintf(stderr, "\n==================\n"); + + fprintf(stderr, "User time: %.03lf ms\nSystem time: %.03lf ms\n", + usage.ru_utime.tv_sec * (double) 1000.0 + + usage.ru_utime.tv_usec / (double)1000.0, + usage.ru_stime.tv_sec * (double) 1000.0 + + usage.ru_stime.tv_usec / (double)1000.0); + fprintf(stderr, "Voluntary context switches: %lu\nInvoluntary context switches: %lu\n", + usage.ru_nvcsw, + usage.ru_nivcsw); + fprintf(stderr, "==================\n"); + fprintf(stderr, "\n"); + + /* Callers statistics. */ + for (int i = 0; i < PF_CALL_SIZE; ++i) { + pstat_t* stat = &table[i].stat; + fprintf(stderr, "%s: M=%lf min=%lu,max=%lu (total=%lu, %lu times) (usec)\n", + table[i].call, stat->M, stat->min, stat->max, stat->total, + stat->count); + } + +} + +ssize_t pf_recvfrom(int socket, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromlen, + const char* caller, const char* file, int line) +{ + unsigned long elapsed = 0; + int ret = 0; + perf_begin(); + ret = recvfrom(socket, buf, len, flags, from, fromlen); + perf_end(elapsed); + + /* Discard wakeup delays, count statistics otherwise. */ + if (elapsed < 200000) { + add_stat(&table[PF_RECVFROM].stat, elapsed); + } + return ret; +} + +ssize_t pf_sendto(int socket, const void *buf, size_t len, int flags, + const struct sockaddr *to, socklen_t tolen, + const char* caller, const char* file, int line) +{ + unsigned long elapsed = 0; + int ret = 0; + perf_begin(); + ret = sendto(socket, buf, len, flags, to, tolen); + perf_end(elapsed); + + /* Discard wakeup delays, count statistics otherwise. */ + if (elapsed < 200000) { + add_stat(&table[PF_SENDTO].stat, elapsed); + } + return ret; +} + +/* Pthreads */ +int pf_pthread_mutex_lock(pthread_mutex_t *mutex, + const char* caller, const char* file, int line) +{ + unsigned long elapsed = 0; + int ret = 0; + perf_begin(); + ret = pthread_mutex_lock(mutex); + perf_end(elapsed); + + /* Discard wakeup delays, count statistics otherwise. */ + if (elapsed < 200000) { + add_stat(&table[PF_PTHREAD_MUTEX_LOCK].stat, elapsed); + } + + return ret; +} + +int pf_pthread_mutex_unlock(pthread_mutex_t *mutex, + const char* caller, const char* file, int line) +{ + unsigned long elapsed = 0; + int ret = 0; + perf_begin(); + ret = pthread_mutex_unlock(mutex); + perf_end(elapsed); + + /* Discard wakeup delays, count statistics otherwise. */ + if (elapsed < 200000) { + add_stat(&table[PF_PTHREAD_MUTEX_UNLOCK].stat, elapsed); + } + + return ret; +} + +#endif // PROF_LATENCY diff --git a/src/common/latency.h b/src/common/latency.h new file mode 100644 index 0000000..d965c56 --- /dev/null +++ b/src/common/latency.h @@ -0,0 +1,115 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file latency.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Utilities for latency profiling. + * + * Selected calls latency profiler is enabled with PROF_LATENCY define. + * You can roughly profile own code with perf_begin() and perf_end() macros. + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_COMMON_LATENCY_H_ +#define _KNOTD_COMMON_LATENCY_H_ + +/* Optional. */ +#ifdef PROF_LATENCY + +/* Do not include from latency.c */ +#include <sys/time.h> +#include <sys/socket.h> +#include <pthread.h> + +/* Profiler tools */ + +/*! \brief Time profile begin macro. */ +#define perf_begin() \ +do { \ + struct timeval __begin; \ + gettimeofday(&__begin, 0) + +/*! \brief Time profile end macro + * \param d Will contain the number of microseconds passed from perf_begin(). + */ +#define perf_end(d) \ + struct timeval __end; \ + gettimeofday(&__end, 0); \ + unsigned long __us = (__end.tv_sec - __begin.tv_sec) * 1000L * 1000L; \ + __us += (__end.tv_usec - __begin.tv_usec); \ + (d) = __us; \ +} while(0) + +/* Prototypes. */ + +/*! \brief Profiled recvfrom(). */ +ssize_t pf_recvfrom(int socket, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromlen, + const char* caller, const char* file, int line); + +/*! \brief Profiled sendto(). */ +ssize_t pf_sendto(int socket, const void *buf, size_t len, int flags, + const struct sockaddr *to, socklen_t tolen, + const char* caller, const char* file, int line); + +/*! \brief Profiled pthread_mutex_lock(). */ +int pf_pthread_mutex_lock(pthread_mutex_t *mutex, + const char* caller, const char* file, int line); + +/*! \brief Profiled pthread_mutex_unlock(). */ +int pf_pthread_mutex_unlock(pthread_mutex_t *mutex, + const char* caller, const char* file, int line); + +/* + * Sockets. + */ + +/*! \brief Rerouted recvfrom(). */ +#define recvfrom(s, buf, len, flags, from, fromlen) \ + pf_recvfrom((s), (buf), (len), (flags), (from), (fromlen), \ + __FUNCTION__, __FILE__, __LINE__) + +/*! \brief Rerouted sendto(). */ +#define sendto(s, buf, len, flags, to, tolen) \ + pf_sendto((s), (buf), (len), (flags), (to), (tolen), \ + __FUNCTION__, __FILE__, __LINE__) + +/* + * Pthreads. + */ + +/*! \brief Rerouted pthread_mutex_lock(). */ +#define pthread_mutex_lock(m) \ + pf_pthread_mutex_lock(m, __FUNCTION__, __FILE__, __LINE__) + +/*! \brief Rerouted pthread_mutex_unlock(). */ +#define pthread_mutex_unlock(m) \ + pf_pthread_mutex_unlock(m, __FUNCTION__, __FILE__, __LINE__) + +#else // PROF_LATENCY + +/* Profiler tools */ +#define perf_begin() +#define perf_end(d) + +#endif // PROF_LATENCY +#endif // _KNOTD_COMMON_LATENCY_H_ + +/*! @} */ diff --git a/src/common/libtap/README b/src/common/libtap/README new file mode 100644 index 0000000..d57b81d --- /dev/null +++ b/src/common/libtap/README @@ -0,0 +1,231 @@ +NAME +==== + +libtap - Write tests in C + +SYNOPSIS +======== + + #include <tap.h> + + int foo () {return 3;} + char *bar () {return "fnord";} + + int main () { + plan(5); + ok(foo() == 3); + is(bar(), "eek"); + ok(foo() <= 8732, "foo <= %d", 8732); + like(bar(), "f(yes|no)r*[a-f]$", "is like"); + cmp_ok(foo(), ">=", 10, "foo is greater than ten"); + return exit_status(); + } + +results in: + + 1..5 + ok 1 + not ok 2 + # Failed test at synopsis.c line 9. + # got: 'fnord' + # expected: 'eek' + ok 3 - foo <= 8732 + ok 4 - is like + not ok 5 - foo is greater than ten + # Failed test 'foo is greater than ten' + # at synopsis.c line 12. + # 3 + # >= + # 10 + # Looks like you failed 2 tests of 5 run. + +DESCRIPTION +=========== + +tap is an easy to read and easy to write way of creating tests for your +software. This library creates functions that can be used to generate it for +your C programs. It is mostly based on the Test::More Perl module. + +FUNCTIONS +========= + +- plan(tests) +- plan(NO_PLAN) + + Use this to start a series of tests. When you know how many tests there + will be, you can put a number as a number of tests you expect to run. If + you do not know how many tests there will be, you can use plan(NO_PLAN) + or not call this function. When you pass it a number of tests to run, a + message similar to the following will appear in the output: + + 1..5 + +- ok(test) +- ok(test, fmt, ...) + + Specify a test. the test can be any statement returning a true or false + value. You may optionally pass a format string describing the test. + + ok(r = reader_new("Of Mice and Men"), "create a new reader"); + ok(reader_go_to_page(r, 55), "can turn the page"); + ok(r->page == 55, "page turned to the right one"); + + Should print out: + + ok 1 - create a new reader + ok 2 - can turn the page + ok 3 - page turned to the right one + + On failure, a diagnostic message will be printed out. + + not ok 3 - page turned to the right one + # Failed test 'page turned to the right one' + # at reader.c line 13. + +- is(got, expected) +- is(got, expected, fmt, ...) +- isnt(got, expected) +- isnt(got, expected, fmt, ...) + + Tests that the string you got is what you expected. with isnt, it is the + reverse. + + is("this", "that", "this is that"); + + prints: + + not ok 1 - this is that + # Failed test 'this is that' + # at is.c line 6. + # got: 'this' + # expected: 'that' + +- cmp_ok(a, op, b) +- cmp_ok(a, op, b, fmt, ...) + + Compares two ints with any binary operator that doesn't require an lvalue. + This is nice to use since it provides a better error message than an + equivalent ok. + + cmp_ok(420, ">", 666); + + prints: + + not ok 1 + # Failed test at cmpok.c line 5. + # 420 + # > + # 666 + +- like(got, expected) +- like(got, expected, fmt, ...) +- unlike(got, expected) +- unlike(got, expected, fmt, ...) + + Tests that the string you got matches the expected extended POSIX regex. + unlike is the reverse. These macros are the equivalent of a skip on + Windows. + + like("stranger", "^s.(r).*\\1$", "matches the regex"); + + prints: + + ok 1 - matches the regex + +- pass() +- pass(fmt, ...) +- fail() +- fail(fmt, ...) + + Speciy that a test succeeded or failed. Use these when the statement is + longer than you can fit into the argument given to an ok() test. + +- dies_ok(code) +- dies_ok(code, fmt, ...) +- lives_ok(code) +- lives_ok(code, fmt, ...) + + Tests whether the given code causes your program to exit. The code gets + passed to a macro that will test it in a forked process. If the code + succeeds it will be executed in the parent process. You can test things + like passing a function a null pointer and make sure it doesnt + dereference it and crash. + + dies_ok({abort();}, "abort does close your program"); + dies_ok({int x = 0/0;}, "divide by zero crash"); + lives ok({pow(3.0, 5.0)}, "nothing wrong with taking 3**5"); + + On Windows, these macros are the equivalent of a skip. + +- exit_status() + + Summarizes the tests that occurred. If there was no plan, it will print + out the number of tests as. + + 1..5 + + It will also print a diagnostic message about how many + failures there were. + + # Looks like you failed 2 tests of 3 run. + + If all planned tests were successful, it will return 0. If any test fails, + it will return the number of failed tests (including ones that were + missing). If they all passed, but there were missing tests, it will return + 255. + +- note(fmt, ...) +- diag(fmt, ...) + + print out a message to the tap output. note prints to stdout and diag + prints to stderr. Each line is preceeded by a "# " so that you know its a + diagnostic message. + + note("This is\na note\nto describe\nsomething."); + + prints: + + # This is + # a note + # to describe + # something + + ok() and these functions return ints so you can use them like: + + ok(1) && note("yo!"); + ok(0) || diag("I have no idea what just happened"); + +- skip(test, n) +- skip(test, n, fmt, ...) +- endskip + + Skip a series of n tests if test is true. You may give a reason why you are + skipping them or not. The (possibly) skipped tests must occur between the + skip and endskip macros. + + skip(TRUE, 2); + ok(1); + ok(0); + endskip; + + prints: + + ok 1 # skip + ok 2 # skip + +- todo() +- todo(fmt, ...) +- endtodo + + Specifies a series of tests that you expect to fail because they are not + yet implemented. + + todo() + ok(0); + endtodo; + + prints: + + not ok 1 # TODO + # Failed (TODO) test at todo.c line 7 + diff --git a/src/common/libtap/tap.c b/src/common/libtap/tap.c new file mode 100644 index 0000000..61e0528 --- /dev/null +++ b/src/common/libtap/tap.c @@ -0,0 +1,313 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +//#include "common.h" +#include "tap.h" + +static int expected_tests = NO_PLAN; +static int failed_tests; +static int current_test; +static char *todo_mesg; + +void +plan (int tests) { + expected_tests = tests; + if (tests != NO_PLAN) + printf("1..%d\n", tests); +} + +static char * +vstrdupf (const char *fmt, va_list args) { + char *str; + int size; + va_list args2; + va_copy(args2, args); + if (!fmt) + fmt = ""; + size = vsnprintf(NULL, 0, fmt, args2) + 2; + str = malloc(size); + vsprintf(str, fmt, args); + va_end(args2); + return str; +} + +int +vok_at_loc (const char *file, int line, int test, const char *fmt, + va_list args) +{ + char *name = vstrdupf(fmt, args); + printf("%sok %d", test ? "" : "not ", ++current_test); + if (*name) + printf(" - %s", name); + if (todo_mesg) { + printf(" # TODO"); + if (*todo_mesg) + printf(" %s", todo_mesg); + } + printf("\n"); + if (!test) { + if (*name) + diag(" Failed%s test '%s'\n at %s line %d.", + todo_mesg ? " (TODO)" : "", name, file, line); + else + diag(" Failed%s test at %s line %d.", + todo_mesg ? " (TODO)" : "", file, line); + if (!todo_mesg) + failed_tests++; + } + free(name); + return test; +} + +int +ok_at_loc (const char *file, int line, int test, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + vok_at_loc(file, line, test, fmt, args); + va_end(args); + return test; +} + +static int +mystrcmp (const char *a, const char *b) { + return a == b ? 0 : !a ? -1 : !b ? 1 : strcmp(a, b); +} + +#define eq(a, b) (!mystrcmp(a, b)) +#define ne(a, b) (mystrcmp(a, b)) + +int +is_at_loc (const char *file, int line, const char *got, const char *expected, + const char *fmt, ...) +{ + int test = eq(got, expected); + va_list args; + va_start(args, fmt); + vok_at_loc(file, line, test, fmt, args); + va_end(args); + if (!test) { + diag(" got: '%s'", got); + diag(" expected: '%s'", expected); + } + return test; +} + +int +isnt_at_loc (const char *file, int line, const char *got, const char *expected, + const char *fmt, ...) +{ + int test = ne(got, expected); + va_list args; + va_start(args, fmt); + vok_at_loc(file, line, test, fmt, args); + va_end(args); + if (!test) { + diag(" got: '%s'", got); + diag(" expected: anything else"); + } + return test; +} + +int +cmp_ok_at_loc (const char *file, int line, int a, const char *op, int b, + const char *fmt, ...) +{ + int test = eq(op, "||") ? a || b + : eq(op, "&&") ? a && b + : eq(op, "|") ? a | b + : eq(op, "^") ? a ^ b + : eq(op, "&") ? a & b + : eq(op, "==") ? a == b + : eq(op, "!=") ? a != b + : eq(op, "<") ? a < b + : eq(op, ">") ? a > b + : eq(op, "<=") ? a <= b + : eq(op, ">=") ? a >= b + : eq(op, "<<") ? a << b + : eq(op, ">>") ? a >> b + : eq(op, "+") ? a + b + : eq(op, "-") ? a - b + : eq(op, "*") ? a * b + : eq(op, "/") ? a / b + : eq(op, "%") ? a % b + : diag("unrecognized operator '%s'", op); + va_list args; + va_start(args, fmt); + vok_at_loc(file, line, test, fmt, args); + va_end(args); + if (!test) { + diag(" %d", a); + diag(" %s", op); + diag(" %d", b); + } + return test; +} + +static void +vdiag_to_fh (FILE *fh, const char *fmt, va_list args) { + char *mesg, *line; + int i; + if (!fmt) + return; + mesg = vstrdupf(fmt, args); + line = mesg; + for (i = 0; *line; i++) { + char c = mesg[i]; + if (!c || c == '\n') { + mesg[i] = '\0'; + fprintf(fh, "# %s\n", line); + if (!c) break; + mesg[i] = c; + line = &mesg[i+1]; + } + } + free(mesg); + return; +} + +int +diag (const char *fmt, ...) { + va_list args; + va_start(args, fmt); + vdiag_to_fh(stderr, fmt, args); + va_end(args); + return 0; +} + +int +note (const char *fmt, ...) { + va_list args; + va_start(args, fmt); + vdiag_to_fh(stdout, fmt, args); + va_end(args); + return 0; +} + +int +exit_status () { + int retval = 0; + if (expected_tests == NO_PLAN) { + printf("1..%d\n", current_test); + } + else if (current_test != expected_tests) { + diag("Looks like you planned %d test%s but ran %d.", + expected_tests, expected_tests > 1 ? "s" : "", current_test); + retval = 255; + } + if (failed_tests) { + diag("Looks like you failed %d test%s of %d run.", + failed_tests, failed_tests > 1 ? "s" : "", current_test); + if (expected_tests == NO_PLAN) + retval = failed_tests; + else + retval = expected_tests - current_test + failed_tests; + } + return retval; +} + +void +skippy (int n, const char *fmt, ...) { + char *why; + va_list args; + va_start(args, fmt); + why = vstrdupf(fmt, args); + va_end(args); + while (n --> 0) { + printf("ok %d ", ++current_test); + note("skip %s\n", why); + } + free(why); +} + +void +ctodo (int ignore, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + todo_mesg = vstrdupf(fmt, args); + va_end(args); +} + +void +cendtodo () { + free(todo_mesg); + todo_mesg = NULL; +} + +#ifndef _WIN32 +#include <sys/mman.h> +#include <regex.h> + +#ifndef MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif + +/* Create a shared memory int to keep track of whether a piece of code executed +dies. to be used in the dies_ok and lives_ok macros */ +int +tap_test_died (int status) { + static int *test_died = NULL; + int prev; + if (!test_died) { + test_died = mmap(0, sizeof (int), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + *test_died = 0; + } + prev = *test_died; + *test_died = status; + return prev; +} + +int +like_at_loc (int for_match, const char *file, int line, const char *got, + const char *expected, const char *fmt, ...) +{ + int test; + regex_t re; + int err = regcomp(&re, expected, REG_EXTENDED); + if (err) { + char errbuf[256]; + regerror(err, &re, errbuf, sizeof errbuf); + fprintf(stderr, "Unable to compile regex '%s': %s at %s line %d\n", + expected, errbuf, file, line); + exit(255); + } + err = regexec(&re, got, 0, NULL, 0); + regfree(&re); + test = for_match ? !err : err; + va_list args; + va_start(args, fmt); + vok_at_loc(file, line, test, fmt, args); + va_end(args); + if (!test) { + if (for_match) { + diag(" '%s'", got); + diag(" doesn't match: '%s'", expected); + } + else { + diag(" '%s'", got); + diag(" matches: '%s'", expected); + } + } + return test; +} +#endif + diff --git a/src/common/libtap/tap.h b/src/common/libtap/tap.h new file mode 100644 index 0000000..2e89b90 --- /dev/null +++ b/src/common/libtap/tap.h @@ -0,0 +1,101 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef __TAP_H__ +#define __TAP_H__ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +#define NO_PLAN -1 +#define ok(...) ok_at_loc(__FILE__, __LINE__, __VA_ARGS__, NULL) +#define pass(...) ok(1, ## __VA_ARGS__) +#define fail(...) ok(0, ## __VA_ARGS__) +#define is(...) is_at_loc(__FILE__, __LINE__, __VA_ARGS__, NULL) +#define isnt(...) isnt_at_loc(__FILE__, __LINE__, __VA_ARGS__, NULL) +#define cmp_ok(...) cmp_ok_at_loc(__FILE__, __LINE__, __VA_ARGS__, NULL) + +int vok_at_loc (const char *file, int line, int test, const char *fmt, + va_list args); +void plan (int tests); +int ok_at_loc (const char *file, int line, int test, const char *fmt, + ...); +int diag (const char *fmt, ...); +int note (const char *fmt, ...); +int exit_status (void); +void skippy (int n, const char *fmt, ...); +void ctodo (int ignore, const char *fmt, ...); +void cendtodo (void); +int is_at_loc (const char *file, int line, const char *got, + const char *expected, const char *fmt, ...); +int isnt_at_loc (const char *file, int line, const char *got, + const char *expected, const char *fmt, ...); +int cmp_ok_at_loc (const char *file, int line, int a, const char *op, + int b, const char *fmt, ...); + +#ifdef _WIN32 +#define like(...) skippy(1, "like is not implemented on MSWin32") +#define unlike(...) like() +#else +#define like(...) like_at_loc(1, __FILE__, __LINE__, __VA_ARGS__, NULL) +#define unlike(...) like_at_loc(0, __FILE__, __LINE__, __VA_ARGS__, NULL) +int like_at_loc (int for_match, const char *file, int line, + const char *got, const char *expected, + const char *fmt, ...); +#endif + +#define skip(test, ...) do {if (test) {skippy(__VA_ARGS__, NULL); break;} +#define endskip } while (0) + +#define todo(...) ctodo(0, ## __VA_ARGS__, NULL) +#define endtodo cendtodo() + +#define dies_ok(code, ...) dies_ok_common(code, 1, ## __VA_ARGS__) +#define lives_ok(code, ...) dies_ok_common(code, 0, ## __VA_ARGS__) + +#ifdef _WIN32 +#define dies_ok_common(...) \ + skippy(1, "Death detection is not supported on MSWin32") +#else +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +int tap_test_died (int status); +#define dies_ok_common(code, for_death, ...) \ + do { \ + tap_test_died(1); \ + int cpid = fork(); \ + switch (cpid) { \ + case -1: \ + perror("fork error"); \ + exit(EXIT_FAILURE); \ + case 0: /* child */ \ + close(1); close(2); \ + code \ + tap_test_died(0); \ + exit(EXIT_SUCCESS); \ + } \ + if (waitpid(cpid, NULL, 0) < 0) { \ + perror("waitpid error"); \ + exit(EXIT_FAILURE); \ + } \ + int it_died = tap_test_died(0); \ + if (!it_died) {code} \ + ok(for_death ? it_died : !it_died, ## __VA_ARGS__); \ + } while (0) +#endif +#endif diff --git a/src/common/libtap/tap_unit.h b/src/common/libtap/tap_unit.h new file mode 100644 index 0000000..c248fde --- /dev/null +++ b/src/common/libtap/tap_unit.h @@ -0,0 +1,94 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file tap_unit.h + * \author Marek Vavrusa <marek.vavusa@nic.cz> + * + * \brief libtap test unit. + * + * Contains description of a single test unit API. + * + * Export unit_api in each module header file, + * and set function pointer to according test routines. + * + * <b>Example code for myunit.h</b> + * \code + * #ifndef MYUNIT_TEST_H + * #define MYUNIT_TEST_H + * + * // Export unittest symbol + * unit_api mymodule; + * + * #endif // MYUNIT_TEST_H + * \endcode + * + * <b>Example code for myunit.c</b> + * \code + * #include "myunit.h" + * + * // Function to return unit test count + * int myunit_count(int argc, char *argv[]) { + * return 1; // Number of tests in this unit + * } + * + * // Function to perform tests + * int myunit_run(int argc, char *argv[]) { + * // 1. test + * ok(1 == 1, "test OK"); + * return 0; + * } + * + * // Declare module API + * unit_api mymodule = { + * "My module", + * &myunit_count, + * &myunit_run + * }; + * \endcode + * + * To incorporate test, add it to unit tests main(). + * + * See https://github.com/zorgnax/libtap for libtap API reference. + * + * \addtogroup tests + * @{ + */ + +#ifndef _TAP_UNIT_H_ +#define _TAP_UNIT_H_ + +#include "common/libtap/tap.h" + +/*! \brief Pointer to function for unit_api. */ +typedef int(unitapi_f)(int, char*[]); + + +/*! + * \brief Basic Unit APIs. + * + * Each unit should have one global variable with + * an initialized instance of unit_api. + */ +typedef struct { + const char *name; /*!< Test unit name. */ + unitapi_f *count; /*!< Function to calculate number of tests. */ + unitapi_f *run; /*!< Function to run unit tests. */ +} unit_api; + +#endif // _TAP_UNIT_H_ + +/*! @} */ + diff --git a/src/common/lists.c b/src/common/lists.c new file mode 100644 index 0000000..9a93733 --- /dev/null +++ b/src/common/lists.c @@ -0,0 +1,160 @@ +/* + * BIRD Library -- Linked Lists + * + * (c) 1998 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/** + * DOC: Linked lists + * + * The BIRD library provides a set of functions for operating on linked + * lists. The lists are internally represented as standard doubly linked + * lists with synthetic head and tail which makes all the basic operations + * run in constant time and contain no extra end-of-list checks. Each list + * is described by a &list structure, nodes can have any format as long + * as they start with a &node structure. If you want your nodes to belong + * to multiple lists at once, you can embed multiple &node structures in them + * and use the SKIP_BACK() macro to calculate a pointer to the start of the + * structure from a &node pointer, but beware of obscurity. + * + * There also exist safe linked lists (&slist, &snode and all functions + * being prefixed with |s_|) which support asynchronous walking very + * similar to that used in the &fib structure. + */ + +#define _BIRD_LISTS_C_ + +#include <stdlib.h> +#include <string.h> +#include "common/lists.h" + +/** + * add_tail - append a node to a list + * @l: linked list + * @n: list node + * + * add_tail() takes a node @n and appends it at the end of the list @l. + */ +LIST_INLINE void +add_tail(list *l, node *n) +{ + node *z = l->tail; + + n->next = (node *) &l->null; + n->prev = z; + z->next = n; + l->tail = n; +} + +/** + * add_head - prepend a node to a list + * @l: linked list + * @n: list node + * + * add_head() takes a node @n and prepends it at the start of the list @l. + */ +LIST_INLINE void +add_head(list *l, node *n) +{ + node *z = l->head; + + n->next = z; + n->prev = (node *) &l->head; + z->prev = n; + l->head = n; +} + +/** + * insert_node - insert a node to a list + * @n: a new list node + * @after: a node of a list + * + * Inserts a node @n to a linked list after an already inserted + * node @after. + */ +LIST_INLINE void +insert_node(node *n, node *after) +{ + node *z = after->next; + + n->next = z; + n->prev = after; + after->next = n; + z->prev = n; +} + +/** + * rem_node - remove a node from a list + * @n: node to be removed + * + * Removes a node @n from the list it's linked in. + */ +LIST_INLINE void +rem_node(node *n) +{ + node *z = n->prev; + node *x = n->next; + + z->next = x; + x->prev = z; + n->prev = 0; + n->next = 0; +} + +/** + * init_list - create an empty list + * @l: list + * + * init_list() takes a &list structure and initializes its + * fields, so that it represents an empty list. + */ +LIST_INLINE void +init_list(list *l) +{ + l->head = (node *) &l->null; + l->null = NULL; + l->tail = (node *) &l->head; +} + +/** + * add_tail_list - concatenate two lists + * @to: destination list + * @l: source list + * + * This function appends all elements of the list @l to + * the list @to in constant time. + */ +LIST_INLINE void +add_tail_list(list *to, list *l) +{ + node *p = to->tail; + node *q = l->head; + + p->next = q; + q->prev = p; + q = l->tail; + q->next = (node *) &to->null; + to->tail = q; +} + +/** + * list_dup - duplicate list + * @to: destination list + * @l: source list + * + * This function duplicates all elements of the list @l to + * the list @to in linear time. + * + * This function only works with a homogenous item size. + */ +void list_dup(list *dst, list *src, size_t itemsz) +{ + node *n = 0; + WALK_LIST(n, *src) { + node *i = malloc(itemsz); + memcpy(i, n, itemsz); + add_tail(dst, i); + } +} diff --git a/src/common/lists.h b/src/common/lists.h new file mode 100644 index 0000000..972ea49 --- /dev/null +++ b/src/common/lists.h @@ -0,0 +1,103 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/* + * BIRD Library -- Linked Lists + * + * (c) 1998 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_LISTS_H_ +#define _BIRD_LISTS_H_ + +/* + * I admit the list structure is very tricky and also somewhat awkward, + * but it's both efficient and easy to manipulate once one understands the + * basic trick: The list head always contains two synthetic nodes which are + * always present in the list: the head and the tail. But as the `next' + * entry of the tail and the `prev' entry of the head are both NULL, the + * nodes can overlap each other: + * + * head head_node.next + * null head_node.prev tail_node.next + * tail tail_node.prev + */ + +#include <string.h> // size_t + +typedef struct node { + struct node *next, *prev; +} node; + +typedef struct list { /* In fact two overlayed nodes */ + struct node *head, *null, *tail; +} list; + +#define NODE (node *) +#define HEAD(list) ((void *)((list).head)) +#define TAIL(list) ((void *)((list).tail)) +#define WALK_LIST(n,list) for(n=HEAD(list);(NODE (n))->next; \ + n=(void *)((NODE (n))->next)) +#define WALK_LIST_DELSAFE(n,nxt,list) \ + for(n=HEAD(list); (nxt=(void *)((NODE (n))->next)); n=(void *) nxt) +/* WALK_LIST_FIRST supposes that called code removes each processed node */ +#define WALK_LIST_FIRST(n,list) \ + while(n=HEAD(list), (NODE (n))->next) +#define WALK_LIST_BACKWARDS(n,list) for(n=TAIL(list);(NODE (n))->prev; \ + n=(void *)((NODE (n))->prev)) +#define WALK_LIST_BACKWARDS_DELSAFE(n,prv,list) \ + for(n=TAIL(list); prv=(void *)((NODE (n))->prev); n=(void *) prv) + +#define EMPTY_LIST(list) (!(list).head->next) + +/*! \brief Free every node in the list. */ +#define WALK_LIST_FREE(list) \ + do { \ + node *n=0,*nxt=0; \ + WALK_LIST_DELSAFE(n,nxt,list) { \ + free(n); \ + } \ + } while(0) + +void add_tail(list *, node *); +void add_head(list *, node *); +void rem_node(node *); +void add_tail_list(list *, list *); +void init_list(list *); +void insert_node(node *, node *); +void list_dup(list *dst, list *src, size_t itemsz); + +/*! + * \brief List item for string lists. + */ +typedef struct strnode_t { + node n; + char *str; +} strnode_t; + +/*! \todo This is broken atm. +#ifndef _BIRD_LISTS_C_ +#define LIST_INLINE extern inline +#include "knot/lib/lists.c" +#undef LIST_INLINE +#else +#define LIST_INLINE +#endif +*/ +#define LIST_INLINE + +#endif diff --git a/src/common/modified_tree.h b/src/common/modified_tree.h new file mode 100644 index 0000000..4c3e325 --- /dev/null +++ b/src/common/modified_tree.h @@ -0,0 +1,292 @@ +/* tree.h -- AVL trees (in the spirit of BSD's 'queue.h') -*- C -*- */ + +/* Copyright (c) 2005 Ian Piumarta + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the 'Software'), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * provided that the above copyright notice(s) and this permission notice appear + * in all copies of the Software and that both the above copyright notice(s) and + * this permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. + */ + +/* This file defines an AVL balanced binary tree [Georgii M. Adelson-Velskii and + * Evgenii M. Landis, 'An algorithm for the organization of information', + * Doklady Akademii Nauk SSSR, 146:263-266, 1962 (Russian). Also in Myron + * J. Ricci (trans.), Soviet Math, 3:1259-1263, 1962 (English)]. + * + * An AVL tree is headed by pointers to the root node and to a function defining + * the ordering relation between nodes. Each node contains an arbitrary payload + * plus three fields per tree entry: the depth of the subtree for which it forms + * the root and two pointers to child nodes (singly-linked for minimum space, at + * the expense of direct access to the parent node given a pointer to one of the + * children). The tree is rebalanced after every insertion or removal. The + * tree may be traversed in two directions: forward (in-order left-to-right) and + * reverse (in-order, right-to-left). + * + * Because of the recursive nature of many of the operations on trees it is + * necessary to define a number of helper functions for each type of tree node. + * The macro TREE_DEFINE(node_tag, entry_name) defines these functions with + * unique names according to the node_tag. This macro should be invoked, + * thereby defining the necessary functions, once per node tag in the program. + * + * For details on the use of these macros, see the tree(3) manual page. + */ + +#ifndef _modified_tree_h +#define _modified_tree_h + + +#define MOD_TREE_DELTA_MAX 1 + +#define MOD_TREE_ENTRY(type) \ + struct { \ + struct type *avl_left; \ + struct type *avl_right; \ + int avl_height; \ + } + +#define MOD_TREE_HEAD(name, type) \ + struct name { \ + struct type *th_root; \ + int (*th_cmp)(void *lhs, void *rhs); \ + } + +#define MOD_TREE_INITIALIZER(cmp) { 0, cmp} + +#define MOD_TREE_DELTA(self, field) \ + (( (((self)->field.avl_left) ? (self)->field.avl_left->field.avl_height : 0)) \ + - (((self)->field.avl_right) ? (self)->field.avl_right->field.avl_height : 0)) + +/* Recursion prevents the following from being defined as macros. */ + +#define MOD_TREE_DEFINE(node, field) \ + \ + struct node *MOD_TREE_BALANCE_##node##_##field(struct node *); \ + \ + struct node *MOD_TREE_ROTL_##node##_##field(struct node *self) \ + { \ + struct node *r= self->field.avl_right; \ + self->field.avl_right= r->field.avl_left; \ + r->field.avl_left= MOD_TREE_BALANCE_##node##_##field(self); \ + return MOD_TREE_BALANCE_##node##_##field(r); \ + } \ + \ + struct node *MOD_TREE_ROTR_##node##_##field(struct node *self) \ + { \ + struct node *l= self->field.avl_left; \ + self->field.avl_left= l->field.avl_right; \ + l->field.avl_right= MOD_TREE_BALANCE_##node##_##field(self); \ + return MOD_TREE_BALANCE_##node##_##field(l); \ + } \ + \ + struct node *MOD_TREE_BALANCE_##node##_##field(struct node *self) \ + { \ + int delta= MOD_TREE_DELTA(self, field); \ + \ + if (delta < -MOD_TREE_DELTA_MAX) \ + { \ + if (MOD_TREE_DELTA(self->field.avl_right, field) > 0) \ + self->field.avl_right= MOD_TREE_ROTR_##node##_##field(self->field.avl_right); \ + return MOD_TREE_ROTL_##node##_##field(self); \ + } \ + else if (delta > MOD_TREE_DELTA_MAX) \ + { \ + if (MOD_TREE_DELTA(self->field.avl_left, field) < 0) \ + self->field.avl_left= MOD_TREE_ROTL_##node##_##field(self->field.avl_left); \ + return MOD_TREE_ROTR_##node##_##field(self); \ + } \ + self->field.avl_height= 0; \ + if (self->field.avl_left && (self->field.avl_left->field.avl_height > self->field.avl_height)) \ + self->field.avl_height= self->field.avl_left->field.avl_height; \ + if (self->field.avl_right && (self->field.avl_right->field.avl_height > self->field.avl_height)) \ + self->field.avl_height= self->field.avl_right->field.avl_height; \ + self->field.avl_height += 1; \ + return self; \ + } \ + \ + struct node *MOD_TREE_INSERT_##node##_##field \ + (struct node *self, struct node *elm, int (*compare)(void *lhs, void *rhs), int (*merge)(void **lhs, void **rhs), int *merged)\ + { \ + if (!self) { \ + *merged = 0; \ + return elm; } \ + int cmp = compare(elm->data, self->data); \ + if (cmp < 0) \ + self->field.avl_left= MOD_TREE_INSERT_##node##_##field(self->field.avl_left, elm, compare, merge, merged); \ + else if (cmp > 0) \ + self->field.avl_right= MOD_TREE_INSERT_##node##_##field(self->field.avl_right, elm, compare, merge, merged); \ + else if (merge) { \ + merge(&(elm->data), &(self->data)); \ + *merged = 1; } \ + else \ + self->field.avl_right= MOD_TREE_INSERT_##node##_##field(self->field.avl_right, elm, compare, merge, merged); \ + return MOD_TREE_BALANCE_##node##_##field(self); \ + } \ + \ + struct node *MOD_TREE_FIND_##node##_##field \ + (struct node *self, struct node *elm, int (*compare)(void *lhs, void *rhs)) \ + { \ + if (!compare) \ + return 0; \ + if (!self) \ + return 0; \ + if (compare(elm->data, self->data) == 0) \ + return self; \ + if (compare(elm->data, self->data) < 0) \ + return MOD_TREE_FIND_##node##_##field(self->field.avl_left, elm, compare); \ + else \ + return MOD_TREE_FIND_##node##_##field(self->field.avl_right, elm, compare); \ + } \ + \ + int MOD_TREE_FIND_LESS_EQUAL_##node##_##field \ + (struct node *self, struct node *elm, int (*compare)(void *lhs, void *rhs), struct node **found, struct node **prev) \ + { \ + if (!self) \ + return 0; \ + if (compare(elm->data, self->data) == 0) { \ + *found = self; \ + return 1; \ + } \ + if (compare(elm->data, self->data) < 0) { \ + int ret = MOD_TREE_FIND_LESS_EQUAL_##node##_##field(self->field.avl_left, elm, compare, found, prev); \ + if (ret == 0 && *prev == NULL) { \ + *prev = self; \ + ret = -1; \ + } \ + return ret; \ + } else { \ + *found = self; \ + *prev = self; \ + return MOD_TREE_FIND_LESS_EQUAL_##node##_##field(self->field.avl_right, elm, compare, found, prev); \ + } \ + } \ + \ + struct node *MOD_TREE_MOVE_RIGHT_##node##_##field(struct node *self, struct node *rhs) \ + { \ + if (!self) \ + return rhs; \ + self->field.avl_right= MOD_TREE_MOVE_RIGHT_##node##_##field(self->field.avl_right, rhs); \ + return MOD_TREE_BALANCE_##node##_##field(self); \ + } \ + \ + struct node *MOD_TREE_REMOVE_##node##_##field \ + (struct node *self, struct node *elm, int (*compare)(void *lhs, void *rhs), void (*del)(struct node *lhs)) \ + { \ + if (!self) return 0; \ + \ + if (compare(elm->data, self->data) == 0) \ + { \ + struct node *tmp= MOD_TREE_MOVE_RIGHT_##node##_##field(self->field.avl_left, self->field.avl_right); \ + self->field.avl_left= 0; \ + self->field.avl_right= 0; \ + del(self); \ + return tmp; \ + } \ + if (compare(elm->data, self->data) < 0) \ + self->field.avl_left= MOD_TREE_REMOVE_##node##_##field(self->field.avl_left, elm, compare, del); \ + else \ + self->field.avl_right= MOD_TREE_REMOVE_##node##_##field(self->field.avl_right, elm, compare, del); \ + return MOD_TREE_BALANCE_##node##_##field(self); \ + } \ + \ + void MOD_TREE_FORWARD_APPLY_ALL_##node##_##field \ + (struct node *self, void (*function)(void *node, void *data), void *data) \ + { \ + if (self) \ + { \ + MOD_TREE_FORWARD_APPLY_ALL_##node##_##field(self->field.avl_left, function, data); \ + function(self->data, data); \ + MOD_TREE_FORWARD_APPLY_ALL_##node##_##field(self->field.avl_right, function, data); \ + } \ + } \ + \ + void MOD_TREE_REVERSE_APPLY_ALL_##node##_##field \ + (struct node *self, void (*function)(struct node *node, void *data), void *data) \ + { \ + if (self) \ + { \ + MOD_TREE_REVERSE_APPLY_ALL_##node##_##field(self->field.avl_right, function, data); \ + function(self, data); \ + MOD_TREE_REVERSE_APPLY_ALL_##node##_##field(self->field.avl_left, function, data); \ + } \ + } \ + \ + void MOD_TREE_POST_ORDER_APPLY_ALL_##node##_##field \ + (struct node *self, void (*function)(struct node *node, void *data), void *data) \ + { \ + if (self) \ + { \ + MOD_TREE_POST_ORDER_APPLY_ALL_##node##_##field(self->field.avl_left, function, data); \ + MOD_TREE_POST_ORDER_APPLY_ALL_##node##_##field(self->field.avl_right, function, data); \ + function(self, data); \ + } \ + } \ + \ +void MOD_TREE_DESTROY_ALL_##node##_##field \ + (struct node *self, void (*function)(void *node, void *data), void(*dest)(struct node *node), void *data) \ +{ \ + if (self) \ + { \ + MOD_TREE_DESTROY_ALL_##node##_##field(self->field.avl_left, function, dest, data); \ + MOD_TREE_DESTROY_ALL_##node##_##field(self->field.avl_right, function, dest, data); \ + if (function != NULL) \ + function(self->data, data); \ + dest(self); \ + } \ +} \ + \ + void MOD_TREE_REVERSE_APPLY_POST_ALL_##node##_##field \ + (struct node *self, void (*function)(struct node *node, void *data), void *data) \ + { \ + if (self) \ + { \ + MOD_TREE_REVERSE_APPLY_POST_ALL_##node##_##field(self->field.avl_right, function, data); \ + MOD_TREE_REVERSE_APPLY_POST_ALL_##node##_##field(self->field.avl_left, function, data); \ + function(self, data); \ + } \ +} + +#define MOD_TREE_INSERT(head, node, field, elm, merge, merged) \ + ((head)->th_root= MOD_TREE_INSERT_##node##_##field((head)->th_root, (elm), (head)->th_cmp, merge, merged)) + +#define MOD_TREE_FIND(head, node, field, elm) \ + (MOD_TREE_FIND_##node##_##field((head)->th_root, (elm), (head)->th_cmp)) + +#define MOD_TREE_FIND_LESS_EQUAL(head, node, field, elm, found, prev) \ + (MOD_TREE_FIND_LESS_EQUAL_##node##_##field((head)->th_root, (elm), (head)->th_cmp, found, prev)) + +#define MOD_TREE_REMOVE(head, node, field, elm, rem) \ + ((head)->th_root= MOD_TREE_REMOVE_##node##_##field((head)->th_root, (elm), (head)->th_cmp, (rem))) + +#define MOD_TREE_DEPTH(head, field) \ + ((head)->th_root->field.avl_height) + +#define MOD_TREE_FORWARD_APPLY(head, node, field, function, data) \ + MOD_TREE_FORWARD_APPLY_ALL_##node##_##field((head)->th_root, function, data) + +#define MOD_TREE_REVERSE_APPLY(head, node, field, function, data) \ + MOD_TREE_REVERSE_APPLY_ALL_##node##_##field((head)->th_root, function, data) + +#define MOD_TREE_POST_ORDER_APPLY(head, node, field, function, data) \ + MOD_TREE_POST_ORDER_APPLY_ALL_##node##_##field((head)->th_root, function, data) + +#define MOD_TREE_DESTROY(head, node, field, function, dest, data) \ + MOD_TREE_DESTROY_ALL_##node##_##field((head)->th_root, function, dest, data) + +#define MOD_TREE_REVERSE_APPLY_POST(head, node, field, function, data) \ + MOD_TREE_REVERSE_APPLY_POST_ALL_##node##_##field((head)->th_root, function, data) + +#define MOD_TREE_INIT(head, cmp) do { \ + (head)->th_root= 0; \ + (head)->th_cmp= (cmp); \ + } while (0) + + +#endif /* __MOD_TREE_h */ diff --git a/src/common/print.c b/src/common/print.c new file mode 100644 index 0000000..9764568 --- /dev/null +++ b/src/common/print.c @@ -0,0 +1,57 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> + +#include "print.h" + +void hex_printf(const char *data, int length, printf_t print_handler) +{ + int ptr = 0; + for (; ptr < length; ptr++) { + print_handler("0x%02x ", (unsigned char)*(data + ptr)); + } + print_handler("\n"); +} + +void hex_print(const char *data, int length) +{ + hex_printf(data, length, &printf); +} + +void bit_printf(const char *data, int length, printf_t print_handler) +{ + unsigned char mask = 0x01; + int ptr = 0; + int bit = 0; + for (; ptr < length; ptr++) { + for (bit = 7; bit >= 0; bit--) { + if ((mask << bit) & (unsigned char)*(data + ptr)) { + print_handler("1"); + } else { + print_handler("0"); + } + } + print_handler(" "); + } + print_handler("\n"); +} + +void bit_print(const char *data, int length) +{ + bit_printf(data, length, &printf); +} diff --git a/src/common/print.h b/src/common/print.h new file mode 100644 index 0000000..482f55e --- /dev/null +++ b/src/common/print.h @@ -0,0 +1,72 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file print.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Custom printing functions. + * + * Downloaded hex_print, bit_print from http://www.digitalpeer.com/id/print + * Updated with generic printf handler. + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_COMMON_PRINT_H_ +#define _KNOTD_COMMON_PRINT_H_ + +typedef int (*printf_t)(const char *fmt, ...); + +/*! + * \brief Prints the given data as hexadecimal characters. + * + * \param data Data to print. + * \param length Size of the \a data array. + */ +void hex_print(const char *data, int length); + +/*! + * \brief Prints the given data as hexadecimal characters using the given + * handler. + * + * \param data Data to print. + * \param length Size of the \a data array. + * \param print_handler Handler for printing. + */ +void hex_printf(const char *data, int length, printf_t print_handler); + +/*! + * \brief Prints the given data as a bitmap. + * + * \param data Data to print. + * \param length Size of the \a data array. + */ +void bit_print(const char *data, int length); + +/*! + * \brief Prints the given data as a bitmap using the given handler. + * + * \param data Data to print. + * \param length Size of the \a data array. + * \param print_handler Handler for printing. + */ +void bit_printf(const char *data, int length, printf_t print_handler); + +#endif /* _KNOTD_COMMON_PRINT_H_ */ + +/*! @} */ diff --git a/src/common/ref.c b/src/common/ref.c new file mode 100644 index 0000000..3b9c033 --- /dev/null +++ b/src/common/ref.c @@ -0,0 +1,44 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> + +#include "ref.h" + +void ref_init(ref_t *p, ref_destructor_t dtor) +{ + if (p) { + p->count = 0; + p->dtor = dtor; + } +} + +void ref_retain(ref_t *p) +{ + if (p) { + __sync_add_and_fetch(&p->count, 1); + } +} + +void ref_release(ref_t *p) +{ + if (p) { + int rc = __sync_sub_and_fetch(&p->count, 1); + if (rc == 0 && p->dtor) { + p->dtor(p); + } + } +} diff --git a/src/common/ref.h b/src/common/ref.h new file mode 100644 index 0000000..13a7037 --- /dev/null +++ b/src/common/ref.h @@ -0,0 +1,90 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file ref.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Atomic reference counting structures. + * + * Reference counting allows implicit sharing of objects + * between threads with custom destructor functions. + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_REF_H_ +#define _KNOTD_REF_H_ + +#include <stddef.h> + +struct ref_t; + +/*! \brief Prototype for object destructor callback. */ +typedef void (*ref_destructor_t)(struct ref_t * p); + +/*! + * \brief Structure for reference counting. + * + * Size equals to two sizes of pointer size. + * Structure may be embedded to the structures which + * we want to use for reference counting. + * + * \code + * struct mystruct { + * ref_t ref; + * int mydata; + * char *mystr; + * } + * \endcode + */ +typedef struct ref_t { + size_t count; /*! \brief Reference counter. */ + ref_destructor_t dtor; /*! \brief Object destructor function. */ +} ref_t; + +/*! + * \brief Initialize reference counter. + * + * Set reference counter to 0 and initialize destructor callback. + * + * \param p Reference-counted object. + * \param dtor Destructor function. + */ +void ref_init(ref_t *p, ref_destructor_t dtor); + +/*! + * \brief Mark object as used by the caller. + * + * Reference counter will be incremented. + * + * \param p Reference-counted object. + */ +void ref_retain(ref_t *p); + +/*! + * \brief Marks object as unused by the caller. + * + * Reference counter will be decremented. + * + * \param p Reference-counted object. + */ +void ref_release(ref_t *p); + +#endif /* _KNOTD_REF_H_ */ + +/*! @} */ diff --git a/src/common/skip-list.c b/src/common/skip-list.c new file mode 100644 index 0000000..79e9429 --- /dev/null +++ b/src/common/skip-list.c @@ -0,0 +1,437 @@ +/* Copyright (c) 2010 the authors listed at the following URL, and/or +the authors of referenced articles or incorporated external code: +http://en.literateprograms.org/Skip_list_(C)?action=history&offset=20080313195128 + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Retrieved from: http://en.literateprograms.org/Skip_list_(C)?oldid=12811 +*/ + +/* + * Modifications by Lubos Slovak, 2010-2011 + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <string.h> +#include <assert.h> + +//#include "common.h" +#include "common/skip-list.h" + +#define ERR_ALLOC_FAILED fprintf(stderr, "Allocation failed at %s:%d\n", \ + __FILE__, __LINE__) + +/*----------------------------------------------------------------------------*/ + +static const float P = 0.5; + +/*! + * \brief Maximum level of a node, i.e. maximum number of skip list levels. + */ +static const int MAX_LEVEL = 6; + +/*----------------------------------------------------------------------------*/ +/* Private functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Generates random real number between 0 and 1. + */ +static float frand() +{ + unsigned seed = (unsigned)time(0); + return (float) rand_r(&seed) / RAND_MAX; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Returns random level between 0 and MAX_LEVEL. + */ +static int skip_random_level() +{ + static int first = 1; + int lvl = 0; + + if (first) { + first = 0; + } + + while (frand() < P && lvl < MAX_LEVEL) { + lvl++; + } + + return lvl; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a new skip list node with the given key and value. + * + * \param level Level of the skip list node. + * \param key Key of the new node. + * \param value Value to be stored in the node. + * + * \return Pointer to the newly created node or NULL if not successful. + */ +static skip_node_t *skip_make_node(int level, void *key, void *value) +{ + skip_node_t *sn = (skip_node_t *)malloc(sizeof(skip_node_t)); + if (sn == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + sn->forward = (skip_node_t **)calloc(level + 1, sizeof(skip_node_t *)); + if (sn->forward == NULL) { + ERR_ALLOC_FAILED; + free(sn); + return NULL; + } + sn->key = key; + sn->value = value; + return sn; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Properly deallocates the given skip list node, and optionally destroys + * its key and value. + * + * \param node Skip list node to be deleted. + * \param destroy_key Function for properly destroying the key. If set tu NULL, + * the object at which the key points will not be destroyed. + * \param destroy_value Function for properly destroying the key. If set tu + * NULL, the object at which the value points will not be + * destroyed. + */ +static void skip_delete_node(skip_node_t **node, void (*destroy_key)(void *), + void (*destroy_value)(void *)) +{ + if (destroy_key != NULL) { + destroy_key((*node)->key); + } + if (destroy_value != NULL) { + destroy_value((*node)->value); + } + + free((*node)->forward); + free(*node); + + node = NULL; +} + +/*----------------------------------------------------------------------------*/ +/* Public functions */ +/*----------------------------------------------------------------------------*/ + +skip_list_t *skip_create_list(int (*compare_keys)(void *, void *)) +{ + assert(compare_keys != NULL); + + skip_list_t *ss = (skip_list_t *)malloc(sizeof(skip_list_t)); + if (ss == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + ss->head = skip_make_node(MAX_LEVEL, NULL, NULL); + if (ss->head == NULL) { + ERR_ALLOC_FAILED; + free(ss); + return NULL; + } + + ss->level = 0; + ss->compare_keys = compare_keys; + + return ss; +} + +/*----------------------------------------------------------------------------*/ + +void skip_destroy_list(skip_list_t **list, void (*destroy_key)(void *), + void (*destroy_value)(void *)) +{ + assert((*list) != NULL); + assert((*list)->head != NULL); + + skip_node_t *x; + + while ((*list)->head->forward[0] != NULL) { + x = (*list)->head->forward[0]; + for (int i = 0; i <= (*list)->level; i++) { + if ((*list)->head->forward[i] != x) { + break; + } + (*list)->head->forward[i] = x->forward[i]; + } + + // delete the item + skip_delete_node(&x, destroy_key, destroy_value); + + while ((*list)->level > 0 + && (*list)->head->forward[(*list)->level] == NULL) { + (*list)->level--; + } + } + + // free the head + skip_delete_node(&(*list)->head, NULL, NULL); + + free(*list); + *list = NULL; +} + +/*----------------------------------------------------------------------------*/ + +void *skip_find(const skip_list_t *list, void *key) +{ + assert(list != NULL); + assert(list->head != NULL); + assert(list->compare_keys != NULL); + + int i; + skip_node_t *x = list->head; + for (i = list->level; i >= 0; i--) { + while (x->forward[i] != NULL + && list->compare_keys(x->forward[i]->key, key) == -1) { + x = x->forward[i]; + } + } + x = x->forward[0]; + + if (x != NULL && list->compare_keys(x->key, key) == 0) { + return x->value; + } + return NULL; +} + +/*----------------------------------------------------------------------------*/ + +void *skip_find_less_or_equal(const skip_list_t *list, void *key) +{ + assert(list != NULL); + assert(list->head != NULL); + assert(list->compare_keys != NULL); + + int i; + skip_node_t *x = list->head; + for (i = list->level; i >= 0; i--) { + while (x->forward[i] != NULL + && list->compare_keys(x->forward[i]->key, key) <= 0) { + x = x->forward[i]; + } + } + + return x->value; +} + +/*----------------------------------------------------------------------------*/ + +int skip_insert(skip_list_t *list, void *key, void *value, + int (*merge_values)(void **, void **)) +{ + assert(list != NULL); + assert(list->head != NULL); + assert(list->compare_keys != NULL); + + int i; + skip_node_t *x = list->head; + skip_node_t *update[MAX_LEVEL + 1]; + memset(update, 0, MAX_LEVEL + 1); + + for (i = list->level; i >= 0; i--) { + while (x->forward[i] != NULL + && list->compare_keys(x->forward[i]->key, key) == -1) { + x = x->forward[i]; + } + update[i] = x; + } + x = x->forward[0]; + + if (x == NULL || list->compare_keys(x->key, key) != 0) { + int lvl = skip_random_level(); + + if (lvl > list->level) { + for (i = list->level + 1; i <= lvl; i++) { + update[i] = list->head; + } + list->level = lvl; + } + + x = skip_make_node(lvl, key, value); + if (x == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + + for (i = 0; i <= lvl; i++) { + x->forward[i] = update[i]->forward[i]; + update[i]->forward[i] = x; + } + + return 0; + } else { // already in the list + if (merge_values != NULL) { // if merge function provided, merge + return (merge_values(&x->value, &value) == 0) ? 2 : -2; + } else { + return 1; + } + } +} + +/*----------------------------------------------------------------------------*/ + +int skip_remove(skip_list_t *list, void *key, void (*destroy_key)(void *), + void (*destroy_value)(void *)) +{ + assert(list != NULL); + assert(list->head != NULL); + assert(list->compare_keys != NULL); + + int i; + skip_node_t *x = list->head; + skip_node_t *update[MAX_LEVEL + 1]; + memset(update, 0, MAX_LEVEL + 1); + + for (i = list->level; i >= 0; i--) { + while (x->forward[i] != NULL + && list->compare_keys(x->forward[i]->key, key) == -1) { + x = x->forward[i]; + } + update[i] = x; + } + x = x->forward[0]; + + if (x != NULL && list->compare_keys(x->key, key) == 0) { + for (i = 0; i <= list->level; i++) { + if (update[i]->forward[i] != x) { + break; + } + update[i]->forward[i] = x->forward[i]; + } + + // delete the item + skip_delete_node(&x, destroy_key, destroy_value); + + while (list->level > 0 + && list->head->forward[list->level] == NULL) { + list->level--; + } + return 0; + } else { + return -1; + } +} + +/*----------------------------------------------------------------------------*/ + +int skip_is_empty(const skip_list_t *list) +{ + if (!list) { + return 1; + } + + return (list->head->forward[0] == NULL); +} + +/*----------------------------------------------------------------------------*/ + +const skip_node_t *skip_first(const skip_list_t *list) +{ + if (!list) { + return NULL; + } + + return list->head->forward[0]; +} + +/*----------------------------------------------------------------------------*/ + +const skip_node_t *skip_next(const skip_node_t *node) +{ + return node->forward[0]; +} + +/*----------------------------------------------------------------------------*/ + +void skip_print_list(const skip_list_t *list, + void (*print_item)(void *, void *)) +{ + assert(list != NULL); + assert(list->head != NULL); + assert(list->compare_keys != NULL); + assert(print_item != NULL); + + skip_node_t *x = list->head->forward[0]; + while (x != NULL) { + print_item(x->key, x->value); + x = x->forward[0]; + } +} + +/*----------------------------------------------------------------------------*/ + +skip_list_t *skip_copy_list(const skip_list_t *list) +{ + skip_list_t *ss = (skip_list_t *)malloc(sizeof(skip_list_t)); + if (ss == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + ss->head = skip_make_node(list->level, NULL, NULL); + if (ss->head == NULL) { + ERR_ALLOC_FAILED; + free(ss); + return NULL; + } + + ss->level = list->level; + ss->compare_keys = list->compare_keys; + + skip_node_t *x = list->head->forward[0]; + skip_node_t *prev = list->head; + skip_node_t *new_prev = ss->head; + while (x != NULL) { + //print_item(x->key, x->value); + + // create new node + skip_node_t *n = skip_make_node(list->level, x->key, x->value); + if (n == NULL) { + skip_destroy_list(&ss, NULL, NULL); + return NULL; + } + // set forward pointers from the previous node + for (int i = 0; i <= list->level; ++i) { + if (prev->forward[i] == x) { + new_prev->forward[i] = n; + } + } + + prev = x; + x = x->forward[0]; + new_prev = n; + } + + return ss; +} diff --git a/src/common/skip-list.h b/src/common/skip-list.h new file mode 100644 index 0000000..784f366 --- /dev/null +++ b/src/common/skip-list.h @@ -0,0 +1,215 @@ +/*! + * \file skip-list.h + * + * \author Copyright (c) 2010 the authors listed at the following URL, and/or + * the authors of referenced articles or incorporated external code: + * http://en.literateprograms.org/Skip_list_(C)?action=history&offset=20080313195128 + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Generic skip-list implementation. + * + * Original retrieved from http://en.literateprograms.org/Skip_list_(C)?oldid=12811 + * Modifications by Lubos Slovak, 2010 + * + * \addtogroup common_lib + * @{ + */ + +/* Copyright (c) 2010 the authors listed at the following URL, and/or +the authors of referenced articles or incorporated external code: +http://en.literateprograms.org/Skip_list_(C)?action=history&offset=20080313195128 + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Retrieved from: http://en.literateprograms.org/Skip_list_(C)?oldid=12811 +*/ + +#ifndef _KNOTD_COMMON_SKIP_LIST_H_ +#define _KNOTD_COMMON_SKIP_LIST_H_ + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Skip list node. + */ +struct skip_node { + void *key; /*!< Key of the node. Used for ordering. */ + + void *value; /*!< Value stored in the node. */ + + /*! \brief Pointers to next item on various levels. */ + struct skip_node **forward; +}; + +typedef struct skip_node skip_node_t; + +/*! + * \brief Skip list. + * + * \todo Implement quasi-randomization. + */ +struct skip_list { + /*! \brief Head of the list (with no actual key and value stored). */ + skip_node_t *head; + + /*! \brief Actual maximum level of the list. */ + int level; + + /*! \brief Function for comparing two skip list item's keys. */ + int (*compare_keys)(void *, void *); +}; + +typedef struct skip_list skip_list_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a new generic skip list. + * + * \param compare_keys Function for comparing the keys. + * \todo What should compare_keys exactly return? + * + * \return Pointer to the newly created skip list if successful. NULL otherwise. + */ +skip_list_t *skip_create_list(int (*compare_keys)(void *, void *)); + +/*! + * \brief Properly destroys the list, possibly also with the keys and values. + * + * \param list Skip list to be destroyed. + * \param destroy_key Function for properly destroying the key. If set tu NULL, + * the object at which the key points will not be destroyed. + * \param destroy_value Function for properly destroying the key. If set tu + * NULL, the object at which the value points will not be + * destroyed. + */ +void skip_destroy_list(skip_list_t **list, void (*destroy_key)(void *), + void (*destroy_value)(void *)); + +/*! + * \brief Inserts a new key-value pair into the skip list. + * + * The \a merge_values function should merge the second value to the first + * value. It must not delete the first value. The second value also should not + * be deleted (as this is concern of the caller). + * + * \param list The skip list to insert to. + * \param key Key of the item to be inserted. Used for ordering. + * \param value Value of the item to be inserted. + * \param merge_values Function for merging the saved values. Optional. If set + * to NULL, the skip list will not merge values when + * attempting to insert item with key already present in the + * list. + * \todo What are the function parameters and what should it + * return as integer? + * + * \retval 0 If successful and the key was not yet present in the list. + * \retval 1 If the key was already present and the new value was ignored + * (because no merging function was provided). + * \retval 2 If successful, the key was already present and the values were + * merged. + * \retval -1 If an error occured and the key was not present in the list. + * \retval -2 If the key is already present in the list and merging was + * unsuccessful. + */ +int skip_insert(skip_list_t *list, void *key, void *value, + int (*merge_values)(void **, void **)); + +/*! + * \brief Removes an item with the given key from the list and optionally + * deletes the item's key and value. + * + * \param list Skip list to delete from. + * \param key Key of the item to be deleted. + * \param destroy_key Function for properly destroying the key. If set tu NULL, + * the object at which the key points will not be destroyed. + * \param destroy_value Function for properly destroying the key. If set tu + * NULL, the object at which the value points will not be + * destroyed. + * + * \retval 0 If successful. + * \retval -1 If the item was not present in the list. + */ +int skip_remove(skip_list_t *list, void *key, void (*destroy_key)(void *), + void (*destroy_value)(void *)); + +/*! + * \brief Tries to find item with the given key in the list. + * + * \param list Skip list to search in. + * \param key Key of the item to be found. + * + * \return Value stored in the item with key \a key, or NULL if the key was not + * found. + */ +void *skip_find(const skip_list_t *list, void *key); + +/*! + * \brief Returns item with largest key smaller or equal than \a key. + * + * \param list Skip list to search in. + * \param key Key of the item to be found. + * + * \return Value stored in the item with largest key smaller or equal than \a + * key, or NULL if the key was not found. + */ +void *skip_find_less_or_equal(const skip_list_t *list, void *key); + +/*! + * \brief Checks if the skip list is empty. + * + * \param list Skip list to check. + * + * \retval 1 if empty. + * \retval 0 if non-empty. + */ +int skip_is_empty(const skip_list_t *list); + +/*! + * \brief Returns the first item in the skip list. + */ +const skip_node_t *skip_first(const skip_list_t *list); + +/*! + * \brief Returns the next item in the skip list. + */ +const skip_node_t *skip_next(const skip_node_t *node); + +/*! + * \brief Prints the whole list using the given print function. + * + * \param list Skip list to be printed. + * \param print_item Function for printing the key-value pair. + */ +void skip_print_list(const skip_list_t *list, + void (*print_item)(void *, void *)); + +/*! + * \brief Copies the skip list. + * + * \param list Skip list to be copied. + * + * \return Copy of \a list. + * + * \todo Test!!! + */ +skip_list_t *skip_copy_list(const skip_list_t *list); + +#endif /* _KNOTD_COMMON_SKIP_LIST_H_ */ + +/*! @} */ diff --git a/src/common/slab/alloc-common.h b/src/common/slab/alloc-common.h new file mode 100644 index 0000000..32878ab --- /dev/null +++ b/src/common/slab/alloc-common.h @@ -0,0 +1,61 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file alloc-common.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Common macros for alloc. + * + * \addtogroup alloc + * @{ + */ + +#ifndef _KNOTD_COMMON_ALLOC_COMMON_H_ +#define _KNOTD_COMMON_ALLOC_COMMON_H_ + +#include <stdio.h> + +//#define MEM_DEBUG +//#define MEM_NOSLAB +//#define MEM_POISON +#define MEM_SLAB_CAP 5 // Cap slab_cache empty slab count (undefined = inf) +#define MEM_COLORING // Slab cache coloring +//#define MEM_SLAB_DEPOT // Use slab depot for slab caching (not thread-safe) + +/* Eliminate compiler warning with unused parameters. */ +#ifndef UNUSED +#define UNUSED(param) (void)(param) +#endif + +/* Optimisation macros. */ +#ifndef likely +#define likely(x) __builtin_expect((x),1) +#endif +#ifndef unlikely +#define unlikely(x) __builtin_expect((x),0) +#endif + +#ifdef MEM_DEBUG +#define dbg_mem(msg...) fprintf(stderr, msg) +#else +#define dbg_mem(msg...) +#endif + + +#endif /* _KNOTD_COMMON_ALLOC_COMMON_H_ */ + +/*! @} */ diff --git a/src/common/slab/malloc.c b/src/common/slab/malloc.c new file mode 100644 index 0000000..ec5a68d --- /dev/null +++ b/src/common/slab/malloc.c @@ -0,0 +1,60 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +/* + * Skip unit if not debugging memory. + */ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/resource.h> + +#include "common/slab/alloc-common.h" + +#ifdef MEM_DEBUG +/* + * ((destructor)) attribute executes this function after main(). + * \see http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html + */ +void __attribute__ ((destructor)) usage_dump() +#else +void usage_dump() +#endif +{ + /* Get resource usage. */ + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage) < 0) { + memset(&usage, 0, sizeof(struct rusage)); + } + + fprintf(stderr, "\nMemory statistics:"); + fprintf(stderr, "\n==================\n"); + + fprintf(stderr, "User time: %.03lf ms\nSystem time: %.03lf ms\n", + usage.ru_utime.tv_sec * (double) 1000.0 + + usage.ru_utime.tv_usec / (double)1000.0, + usage.ru_stime.tv_sec * (double) 1000.0 + + usage.ru_stime.tv_usec / (double)1000.0); + fprintf(stderr, "Major page faults: %lu (required I/O)\nMinor page faults: %lu\n", + usage.ru_majflt, usage.ru_minflt); + fprintf(stderr, "Number of swaps: %lu\n", + usage.ru_nswap); + fprintf(stderr, "Voluntary context switches: %lu\nInvoluntary context switches: %lu\n", + usage.ru_nvcsw, + usage.ru_nivcsw); + fprintf(stderr, "==================\n"); +} diff --git a/src/common/slab/malloc.h b/src/common/slab/malloc.h new file mode 100644 index 0000000..8ca9f58 --- /dev/null +++ b/src/common/slab/malloc.h @@ -0,0 +1,43 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file malloc.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Memory allocation related functions. + * + * \addtogroup alloc + * @{ + */ + +#ifndef _KNOTD_COMMON_MALLOC_H_ +#define _KNOTD_COMMON_MALLOC_H_ + +#include <stdlib.h> + +/*! \brief Print usage statistics. + * + * \note This function has destructor attribute set if MEM_DEBUG is enabled. + * + * \warning Not all printed statistics are available on every OS, + * consult manual page for getrusage(2). + */ +void usage_dump(); + +#endif // _KNOTD_COMMON_MALLOC_H_ + +/*! @} */ diff --git a/src/common/slab/slab.c b/src/common/slab/slab.c new file mode 100644 index 0000000..ccdf7ca --- /dev/null +++ b/src/common/slab/slab.c @@ -0,0 +1,732 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <assert.h> +#include <stdlib.h> +#include <sys/mman.h> + +#include "common/slab/alloc-common.h" +#include "common/slab/slab.h" + +/* + * Magic constants. + */ +#define SLAB_MAGIC 0x51 /*!< "Sl" magic byte (slab type). */ +#define LOBJ_MAGIC 0x0B /*!< "Ob" magic byte (object type). */ +#define POISON_DWORD 0xdeadbeef /*!< Memory boundary guard magic. */ +#define SLAB_MINCOLOR 64 /*!< Minimum space reserved for cache coloring. */ +#define SLAB_HEADER sizeof(slab_t) /*!< Slab header size. */ +#define ALIGN_PTRSZ __attribute__ ((__aligned__(sizeof(void*)))) + +/*! \brief Fast cache id lookup table. + * + * Provides O(1) lookup. + * Filled with interesting values from default + * or on-demand. + */ +unsigned ALIGN_PTRSZ SLAB_CACHE_LUT[SLAB_SIZE] = { + [24] = SLAB_GP_COUNT + 1, + [800] = SLAB_GP_COUNT + 2 +}; + +/*! \brief Find the next highest power of 2. */ +static inline unsigned get_next_pow2(unsigned v) +{ + // Next highest power of 2 + --v; + v |= v >> 1; v |= v >> 2; + v |= v >> 4; v |= v >> 8; + v |= v >> 16; + ++v; + + return v; +} + +/*! \brief Return binary logarithm of a number, which is a power of 2. */ +static inline unsigned fastlog2(unsigned v) +{ + // Works if we know the size is a power of 2 + register unsigned int r = (v & 0xAAAAAAAA) != 0; + r |= ((v & 0xFFFF0000) != 0) << 4; + r |= ((v & 0xFF00FF00) != 0) << 3; + r |= ((v & 0xF0F0F0F0) != 0) << 2; + r |= ((v & 0xCCCCCCCC) != 0) << 1; + return r; +} + +/*! + * \brief Fast hashing function. + * + * Finds the next highest power of 2 and returns binary logarithm. + * Values are stored in LUT cache for future access. + */ +static unsigned slab_cache_id(unsigned size) +{ + // Assert cache id of the smallest bufsize is 0 + if(size <= SLAB_MIN_BUFLEN) { + return 0; + } + + // Check LUT + unsigned id = 0; + if ((id = SLAB_CACHE_LUT[size])) { + return id; + } else { + + // Compute binary logarithm + // Next highest power of 2 + id = fastlog2(get_next_pow2(size)); + + // Shift cacheid of SLAB_MIN_BUFLEN to 0 + id -= SLAB_EXP_OFFSET; + + // Store + SLAB_CACHE_LUT[size] = id; + } + + return id; +} + +/* + * Slab run-time constants. + */ + +size_t SLAB_MASK = 0; /*!< \brief Slab address mask (for computing offsets). */ +static unsigned SLAB_LOGSIZE = 0; /*!< \brief Binary logarithm of slab size. */ + +/*! + * Depot is a caching sub-allocator of slabs. + * It mitigates performance impact of sequentially allocating and freeing + * from a slab with just a few slab items by caching N slabs before returning + * them to the system. + * + * \todo With wider use, locking or RCU will be necessary. + */ +#ifdef MEM_SLAB_DEPOT +static slab_depot_t _depot_g; /*! \brief Global slab depot. */ +#endif // MEM_SLAB_DEPOT + +/*! + * \brief Allocate a slab of given bufsize from depot. + * + * \retval Reserved memory for slab on success. + * \retval NULL on errors. + */ +static void* slab_depot_alloc(size_t bufsize) +{ + void *page = 0; +#ifdef MEM_SLAB_DEPOT + if (_depot_g.available) { + for (int i = _depot_g.available - 1; i > -1 ; --i) { + if(_depot_g.cache[i]->bufsize == bufsize) { + page = _depot_g.cache[i]; + _depot_g.cache[i] = _depot_g.cache[--_depot_g.available]; + return page; + } + } + page = _depot_g.cache[--_depot_g.available]; + } else { + if(posix_memalign(&page, SLAB_SIZE, SLAB_SIZE) == 0) { + ((slab_t*)page)->bufsize = 0; + } else { + page = 0; + } + + } +#else // MEM_SLAB_DEPOT + if(posix_memalign(&page, SLAB_SIZE, SLAB_SIZE) == 0) { + ((slab_t*)page)->bufsize = 0; + } else { + page = 0; + } +#endif // MEM_SLAB_DEPOT + + return page; +} + +/*! + * \brief Return a slab to the depot. + * + * \note If the depot is full, slab gets immediately freed. + */ +static inline void slab_depot_free(void* slab) +{ +#ifdef MEM_SLAB_DEPOT + if (_depot_g.available < SLAB_DEPOT_SIZE) { + _depot_g.cache[_depot_g.available++] = slab; + } else { + free(slab); + } +#else // MEM_SLAB_DEPOT + free(slab); +#endif // MEM_SLAB_DEPOT +} + +/*! \brief Initialize slab depot. */ +static void slab_depot_init() +{ +#ifdef MEM_SLAB_DEPOT + _depot_g.available = 0; +#endif // MEM_SLAB_DEPOT +} + +/*! \brief Destroy slab depot. */ +static void slab_depot_destroy() +{ +#ifdef MEM_SLAB_DEPOT + while(_depot_g.available) { + free(_depot_g.cache[--_depot_g.available]); + } +#endif // MEM_SLAB_DEPOT +} + +/* + * Initializers. + */ + +/*! \brief Initializes slab subsystem (it is called automatically). */ +void __attribute__ ((constructor)) slab_init() +{ + // Fetch page size + SLAB_LOGSIZE = fastlog2(SLAB_SIZE); + + // Compute slab page mask + SLAB_MASK = 0; + for (int i = 0; i < SLAB_LOGSIZE; ++i) { + SLAB_MASK |= 1 << i; + } + SLAB_MASK = ~SLAB_MASK; + + // Initialize depot + slab_depot_init(); +} + +/*! \brief Deinitializes slab subsystem (it is called automatically). */ +void __attribute__ ((destructor)) slab_deinit() +{ + // Deinitialize global allocator + if (SLAB_LOGSIZE) { + slab_depot_destroy(); + SLAB_LOGSIZE = SLAB_MASK = 0; + } +} + +/* + * Cache helper functions. + */ + +/* \notice Not used right now. +static void slab_dump(slab_t* slab) { + + printf("%s: buffers (bufsize=%zuB, %u/%u free): \n", + __func__, slab->cache->bufsize, slab->bufs_free, + slab->bufs_count); + + void** buf = slab->head; + int i = 0, n = 0; + while(buf != 0) { + size_t diff = (size_t)((char*)buf - (char*)slab->base); + printf("-> %lu", diff / slab->cache->bufsize); + buf = (void**)(*buf); + if (++i == 10) { + printf("\n"); + i = 0; + } + ++n; + } + + printf("\n"); +} +*/ + +/*! + * \brief Free all slabs from a slab cache. + * \return Number of freed slabs. + */ +static inline int slab_cache_free_slabs(slab_t* slab) +{ + int count = 0; + while (slab) { + slab_t* next = slab->next; + slab_destroy(&slab); + ++count; + slab = next; + + } + return count; +} + +/* + * Slab helper functions. + */ + +/*! \brief Return number of slabs in a linked list. */ +static inline unsigned slab_list_walk(slab_t* slab) +{ + unsigned count = 0; + while(slab) { + slab = slab->next; + ++count; + } + return count; +} + +/*! \brief Remove slab from a linked list. */ +static void slab_list_remove(slab_t* slab) +{ + // Disconnect from list + if (slab->prev) { + slab->prev->next = slab->next; + } + if(slab->next) { + slab->next->prev = slab->prev; + } + + // Disconnect from cache + slab_cache_t* cache = slab->cache; + { + if (cache->slabs_free == slab) { + cache->slabs_free = slab->next; + } else if (cache->slabs_full == slab) { + cache->slabs_full = slab->next; + } + } +} + +/*! \brief Insert slab into a linked list. */ +static void slab_list_insert(slab_t** list, slab_t* item) +{ + // If list exists, push to the top + item->prev = 0; + item->next = *list; + if(*list) { + (*list)->prev = item; + } + *list = item; +} + +/*! \brief Move slab from one linked list to another. */ +static inline void slab_list_move(slab_t** target, slab_t* slab) +{ + slab_list_remove(slab); + slab_list_insert(target, slab); +} + +/* + * API functions. + */ + +slab_t* slab_create(slab_cache_t* cache) +{ + const size_t size = SLAB_SIZE; + + slab_t* slab = slab_depot_alloc(cache->bufsize); + + if (unlikely(slab < 0)) { + dbg_mem("%s: failed to allocate aligned memory block\n", + __func__); + return 0; + } + + /* Initialize slab. */ + slab->magic = SLAB_MAGIC; + slab->cache = cache; + slab_list_insert(&cache->slabs_free, slab); +#ifdef MEM_SLAB_CAP + ++cache->empty; +#endif + + /* Already initialized? */ + if (slab->bufsize == cache->bufsize) { + return slab; + } else { + slab->bufsize = cache->bufsize; + } + + /* Ensure the item size can hold at least a size of ptr. */ + size_t item_size = slab->bufsize; + if (unlikely(item_size < SLAB_MIN_BUFLEN)) { + item_size = SLAB_MIN_BUFLEN; + } + + /* Ensure at least some space for coloring */ + size_t data_size = size - sizeof(slab_t); +#ifdef MEM_COLORING + size_t free_space = data_size % item_size; + if (unlikely(free_space < SLAB_MINCOLOR)) { + free_space = SLAB_MINCOLOR; + } + + + /// unsigned short color = __sync_fetch_and_add(&cache->color, 1); + unsigned short color = (cache->color += sizeof(void*)); + color = color % free_space; +#else + const unsigned short color = 0; +#endif + + /* Calculate useable data size */ + data_size -= color; + slab->bufs_count = data_size / item_size; + slab->bufs_free = slab->bufs_count; + + // Save first item as next free + slab->base = (char*)slab + sizeof(slab_t) + color; + slab->head = (void**)slab->base; + + // Create freelist, skip last member, which is set to NULL + char* item = (char*)slab->head; + for(unsigned i = 0; i < slab->bufs_count - 1; ++i) { + *((void**)item) = item + item_size; + item += item_size; + } + + // Set last buf to NULL (tail) + *((void**)item) = (void*)0; + + // Ensure the last item has a NULL next + dbg_mem("%s: created slab (%p, %p) (%zu B)\n", + __func__, slab, slab + size, size); + return slab; +} + +void slab_destroy(slab_t** slab) +{ + /* Disconnect from the list */ + slab_list_remove(*slab); + + /* Free slab */ + slab_depot_free(*slab); + + /* Invalidate pointer. */ + dbg_mem("%s: deleted slab %p\n", __func__, *slab); + *slab = 0; +} + +void* slab_alloc(slab_t* slab) +{ + // Fetch first free item + void **item = 0; + { + if((item = slab->head)) { + slab->head = (void**)*item; + --slab->bufs_free; + } else { + // No more free items + return 0; + } + } + +#ifdef MEM_DEBUG + // Increment statistics + __sync_add_and_fetch(&slab->cache->stat_allocs, 1); +#endif + + // Move to full? + if (unlikely(slab->bufs_free == 0)) { + slab_list_move(&slab->cache->slabs_full, slab); + } else { +#ifdef MEM_SLAB_CAP + // Mark not empty? + if (unlikely(slab->bufs_free == slab->bufs_count - 1)) { + --slab->cache->empty; + } +#endif + } + + return item; +} + +void slab_free(void* ptr) +{ + // Null pointer check + if (unlikely(!ptr)) { + return; + } + + // Get slab start address + slab_t* slab = slab_from_ptr(ptr); + assert(slab); + + // Check if it exists in directory + if (slab->magic == SLAB_MAGIC) { + + // Return buf to slab + *((void**)ptr) = (void*)slab->head; + slab->head = (void**)ptr; + ++slab->bufs_free; + +#ifdef MEM_DEBUG + // Increment statistics + __sync_add_and_fetch(&slab->cache->stat_frees, 1); +#endif + + // Return to partial + if(unlikely(slab->bufs_free == 1)) { + slab_list_move(&slab->cache->slabs_free, slab); + } else { +#ifdef MEM_SLAB_CAP + // Recycle if empty + if(unlikely(slab_isempty(slab))) { + if(slab->cache->empty == MEM_SLAB_CAP) { + slab_destroy(&slab); + } else { + ++slab->cache->empty; + } + } +#endif + } + + } else { + + // Pointer is not a slab + // Presuming it's a large block + slab_obj_t* bs = (slab_obj_t*)ptr - 1; + +#ifdef MEM_POISON + // Remove memory barrier + mprotect(ptr + bs->size, sizeof(int), PROT_READ|PROT_WRITE); +#endif + + // Unmap + dbg_mem("%s: unmapping large block of %zu bytes at %p\n", + __func__, bs->size, ptr); + free(bs); + } +} + +int slab_cache_init(slab_cache_t* cache, size_t bufsize) +{ + if (unlikely(!bufsize)) { + return -1; + } + + cache->empty = 0; + cache->bufsize = bufsize; + cache->slabs_free = cache->slabs_full = 0; + cache->color = 0; + + /* Initialize stats */ + cache->stat_allocs = cache->stat_frees = 0; + + dbg_mem("%s: created cache of size %zu\n", + __func__, bufsize); + + return 0; +} + +void slab_cache_destroy(slab_cache_t* cache) { + + // Free slabs + unsigned free_s = slab_cache_free_slabs(cache->slabs_free); + unsigned full_s = slab_cache_free_slabs(cache->slabs_full); +#ifndef MEM_DEBUG + UNUSED(free_s); + UNUSED(full_s); +#else + dbg_mem("%s: %u empty/partial, %u full caches\n", + __func__, free_s, full_s); +#endif + + // Invalidate cache + cache->bufsize = 0; + cache->slabs_free = cache->slabs_full = 0; +} + +void* slab_cache_alloc(slab_cache_t* cache) +{ + slab_t* slab = cache->slabs_free; + if(!cache->slabs_free) { + slab = slab_create(cache); + if (slab == NULL) { + return NULL; + } + } + + + return slab_alloc(slab); +} + +int slab_cache_reap(slab_cache_t* cache) +{ + // For now, just free empty slabs + slab_t* slab = cache->slabs_free; + int count = 0; + while (slab) { + slab_t* next = slab->next; + if (slab_isempty(slab)) { + slab_destroy(&slab); + ++count; + } + slab = next; + + } + + cache->empty = 0; + return count; +} + +int slab_alloc_init(slab_alloc_t* alloc) +{ + // Invalidate + memset(alloc, 0, sizeof(slab_alloc_t)); + + // Initialize descriptors cache + slab_cache_init(&alloc->descriptors, sizeof(slab_cache_t)); + + return 0; +} + +void slab_alloc_destroy(slab_alloc_t* alloc) +{ + // Destroy all caches + for (unsigned i = 0; i < SLAB_CACHE_COUNT; ++i) { + if (alloc->caches[i] != 0) { + slab_cache_destroy(alloc->caches[i]); + } + } + + // Destroy cache for descriptors + slab_cache_destroy(&alloc->descriptors); +} + +void* slab_alloc_alloc(slab_alloc_t* alloc, size_t size) +{ + // Invalid size check + if (unlikely(!size)) { + return 0; + } + +#ifdef MEM_POISON + // Reserve memory for poison + size += sizeof(int); +#endif + // Directly map large block + if (unlikely(size > SLAB_SIZE/2)) { + + // Map block + size += sizeof(slab_obj_t); + slab_obj_t* p = 0; + p = malloc(size); + + dbg_mem("%s: mapping large block of %zu bytes at %p\n", + __func__, size, p + 1); + + /* Initialize. */ + p->magic = LOBJ_MAGIC; + p->size = size - sizeof(slab_obj_t); + +#ifdef MEM_POISON + // Reduce real size + p->size -= sizeof(int); + + // Memory barrier + int* pb = (int*)((char*)p + size - sizeof(int)); + *pb = POISON_DWORD; + mprotect(pb, sizeof(int), PROT_NONE); +#endif + + return p + 1; + } + + // Get cache id from size + unsigned cache_id = slab_cache_id(size); + + // Check if associated cache exists + if (unlikely(alloc->caches[cache_id] == 0)) { + + // Assert minimum cache size + if (unlikely(size < SLAB_MIN_BUFLEN)) { + size = SLAB_MIN_BUFLEN; + } + + // Calculate cache bufsize + size_t bufsize = size; + if (cache_id < SLAB_GP_COUNT) { + bufsize = get_next_pow2(size); + } + + // Create cache + dbg_mem("%s: creating cache of %zuB (req. %zuB) (id=%u)\n", + __func__, bufsize, size, cache_id); + + slab_cache_t* cache = slab_cache_alloc(&alloc->descriptors); + slab_cache_init(cache, bufsize); + alloc->caches[cache_id] = cache; + } + + // Allocate from cache + void* mem = slab_cache_alloc(alloc->caches[cache_id]); + +#ifdef MEM_POISON + // Memory barrier + /*! \todo Broken, need to store the barrier byte size. */ + //int* pb = (int*)((char*)mem + size - sizeof(int)); + //mprotect(pb, sizeof(int), PROT_NONE); +#endif + return mem; +} + +void *slab_alloc_realloc(slab_alloc_t* alloc, void *ptr, size_t size) +{ + // realloc(0) equals to free(ptr) + if (!size) { + slab_free(ptr); + return 0; + } + + // Allocate new buf + void *nptr = slab_alloc_alloc(alloc, size); + assert(nptr); + + // Copy memory if present + if (ptr) { + slab_t* slab = slab_from_ptr(ptr); + memcpy(nptr, ptr, slab->cache->bufsize); + + // Free old buf + slab_free(ptr); + } + + return nptr; +} + +void slab_alloc_stats(slab_alloc_t* alloc) +{ +#ifdef MEM_DEBUG + printf("Cache usage:\n"); + for (int i = 0; i < SLAB_CACHE_COUNT; ++i) { + + if (!alloc->caches[i]) + continue; + + slab_cache_t* cache = alloc->caches[i]; + unsigned free_s = slab_list_walk(cache->slabs_free); + unsigned full_s = slab_list_walk(cache->slabs_full); + printf("%4zu: allocs=%lu frees=%lu " + "(%u empty+partial, %u full)\n", + cache->bufsize, cache->stat_allocs, + cache->stat_frees, free_s, full_s); + } +#else + printf("Cache usage: not available, enable MEM_DEBUG and recompile.\n"); +#endif +} + diff --git a/src/common/slab/slab.h b/src/common/slab/slab.h new file mode 100644 index 0000000..d64188e --- /dev/null +++ b/src/common/slab/slab.h @@ -0,0 +1,353 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file slab.h + * + * \author Marek Vavrusa <marek.vavusa@nic.cz> + * + * \brief SLAB allocator. + * + * SLAB cache works with either custom SLAB sizes and + * Next-Highest-Power-Of-2 sizes. + * + * Slab size is a multiple of PAGE_SIZE and uses + * system allocator for larger blocks. + * + * Allocated SLABs are PAGE_SIZE aligned for a fast O(1) + * address-from-item lookup. This results in nearly none memory + * overhead for a very small blocks (<64B), but it requires the + * underlying allocator to be effective in allocating page size aligned memory + * as well. The major disadvantage is that each Slab must be aligned to it's + * size as opposed to boundary tags. + * + * Slab implements simple coloring mechanism to improve + * cache line utilisation. + * + * \ref SLAB_SIZE is a fixed size of a slab. As a rule of thumb, the slab is + * effective when the maximum allocated block size is below 1/4 of a SLAB_SIZE. + * f.e. 16kB SLAB is most effective up to 4kB block size. + * + * \ref MEM_POISON flag enables checking read/writes after the allocated memory + * and segmentation fault. This poses a significant time and space overhead. + * Enable only when debugging. + * + * \ref MEM_SLAB_CAP defines a maximum limit of a number of empty slabs that a cache + * can keep at a time. This results in a slight performance regression, + * but actively recycles unuse memory. + * + * \ref MEM_DEPOT_COUNT defines how many recycled slabs will be cached for a later + * use instead of returning them immediately to the OS. This significantly + * reduces a number of syscalls in some cases. + * f.e. 16 means 16 * SLAB_SIZE cache, for 16kB slabs = 256kB cache + * + * \ref MEM_COLORING enables simple cache coloring. This is generally a useful + * feature since all slabs are page size aligned and + * (depending on architecture) this slightly improves performance + * and cacheline usage at the cost of a minimum of 64 bytes per slab of + * overhead. Undefine MEM_COLORING in common.h to disable coloring. + * + * Optimal usage for a specific behavior (similar allocation sizes): + * \code + * slab_cache_t cache; + * slab_cache_init(&cache, N); // Initialize, N means cache chunk size + * ... + * void* mem = slab_cache_alloc(&cache); // Allocate N bytes + * ... + * slab_free(mem); // Recycle memory + * ... + * slab_cache_destroy(&cache); // Deinitialize cache + * \endcode + * + * + * \todo Allocate slab headers elsewhere and use just first sizeof(void*) bytes + * in each slab as a pointer to slab header. This could improve the + * performance. + * + * \note Slab allocation is not thread safe for performance reasons. + * + * \addtogroup alloc + * @{ + */ + +#ifndef _KNOTD_COMMON_SLAB_H_ +#define _KNOTD_COMMON_SLAB_H_ + +#include <pthread.h> +#include <stdint.h> + +/* Constants. */ +#define SLAB_SIZE (4096*4) //!< Slab size (16K blocks) +#define SLAB_MIN_BUFLEN 8 //!< Minimal allocation block size is 8B. +#define SLAB_EXP_OFFSET 3 //!< Minimal allocation size is 8B = 2^3, exp is 3. +#define SLAB_GP_COUNT 10 //!< General-purpose caches count. +#define SLAB_US_COUNT 10 //!< User-specified caches count. +#define SLAB_DEPOT_SIZE 16 //!< N slabs cached = N*SLAB_SIZE kB cap +#define SLAB_CACHE_COUNT (SLAB_GP_COUNT + SLAB_US_COUNT) //!< Slab cache count. +struct slab_cache_t; + +/* Macros. */ + +/*! \brief Return slab base address from pointer. */ +#define slab_from_ptr(p) ((void*)((size_t)(p) & SLAB_MASK)) + +/*! \brief Return true if slab is empty. */ +#define slab_isempty(s) ((s)->bufs_free == (s)->bufs_count) + +/*! + * \brief Slab descriptor. + * + * Slab is a block of memory used for an allocation of + * smaller objects (bufs) later on. + * Each slab is currently aligned to page size to easily + * determine slab address from buf pointer. + * + * \warning Do not use slab_t directly as it cannot grow, see slab_cache_t. + */ +typedef struct slab_t { + char magic; /*!< Identifies memory block type. */ + unsigned short bufsize; /*!< Slab bufsize. */ + struct slab_cache_t *cache; /*!< Owner cache. */ + struct slab_t *prev, *next; /*!< Neighbours in slab lists. */ + unsigned bufs_count; /*!< Number of bufs in slab. */ + unsigned bufs_free; /*!< Number of available bufs. */ + void **head; /*!< Pointer to first available buf. */ + char* base; /*!< Base address for bufs. */ +} slab_t; + +/*! + * \brief Slab depot. + * + * To mitigate slab initialization costs, depot keeps a finite number of + * stacked slabs before returning them to the system. + */ +typedef struct slab_depot_t { + size_t available; /*!< Number of available pages. */ + slab_t* cache[SLAB_DEPOT_SIZE]; /*!< Stack of free slabs. */ +} slab_depot_t; + +/*! + * \brief Large object descriptor. + * + * Large object differs from slab with magic byte and + * contains object size. + * + * Magic needs to be first to overlap with slab_t magic byte. + */ +typedef struct slab_obj_t { + char magic; /*!< Identifies memory block type. */ + size_t size; /*!< Object size. */ +} slab_obj_t; + +/*! + * \brief Slab cache descriptor. + * + * Slab cache is a list of 0..n slabs with the same buf size. + * It is responsible for slab state keeping. + * + * Once a slab is created, it is moved to free list. + * When it is full, it is moved to full list. + * Once a buf from full slab is freed, the slab is moved to + * free list again (there may be some hysteresis for mitigating + * a sequential alloc/free). + * + * Allocation of new slabs is on-demand, empty slabs are reused if possible. + * + * \note Slab implementation is different from Bonwick (Usenix 2001) + * http://www.usenix.org/event/usenix01/bonwick.html + * as it doesn't feature empty and partial list. + * This is due to fact, that user space allocator rarely + * needs to count free slabs. There is no way the OS could + * notify the application, that the memory is scarce. + * A slight performance increased is measured in benchmark. + * + * \note Statistics are only available if MEM_DEBUG is enabled. + */ +typedef struct slab_cache_t { + unsigned short color; /*!< Current cache color. */ + unsigned short empty; /*!< Number of empty slabs. */ + size_t bufsize; /*!< Cache object (buf) size. */ + slab_t *slabs_free; /*!< List of free slabs. */ + slab_t *slabs_full; /*!< List of full slabs. */ + + /* Statistics. */ + unsigned long stat_allocs; /*!< Allocation count. */ + unsigned long stat_frees; /*!< Free count. */ +} slab_cache_t; + +/*! + * \brief Slab allocator descriptor. + * + * \note For a number of slab caches, consult SLAB_GP_COUNT + * and a number of specific records in SLAB_CACHE_LUT lookup table. + * + * \warning It is currently not advised to use this general purpose allocator, + * as it usually doesn't yield an expected performance for higher + * bookkeeping costs and it also depends on the allocation behavior + * as well. Look for slab_cache for a specialized use in most cases. + */ +typedef struct slab_alloc_t { + slab_cache_t descriptors; /*!< Slab cache for cache descriptors. */ + slab_cache_t* caches[SLAB_CACHE_COUNT]; /*!< Number of slab caches. */ +} slab_alloc_t; + +/*! + * \brief Create a slab of predefined size. + * + * At the moment, slabs are equal to page size and page size aligned. + * This enables quick and efficient buf to slab lookup by pointer arithmetic. + * + * Slab uses simple coloring scheme with and the memory block is always + * sizeof(void*) aligned. + * + * \param cache Parent cache. + * \retval Slab instance on success. + * \retval NULL on error. + */ +slab_t* slab_create(slab_cache_t* cache); + +/*! + * \brief Destroy slab instance. + * + * Slab is disconnected from any list and freed. + * Dereferenced slab parameter is set to NULL. + * + * \param slab Pointer to given slab. + */ +void slab_destroy(slab_t** slab); + +/*! + * \brief Allocate a buf from slab. + * + * Returns a pointer to allocated memory or NULL on error. + * + * \param slab Given slab instance. + * \retval Pointer to allocated memory. + * \retval NULL on error. + */ +void* slab_alloc(slab_t* slab); + +/*! + * \brief Recycle memory. + * + * Given memory is returned to owner slab. + * Memory content may be rewritten. + * + * \param ptr Returned memory. + */ +void slab_free(void* ptr); + +/*! + * \brief Create a slab cache. + * + * Create a slab cache with no allocated slabs. + * Slabs are allocated on-demand. + * + * \param cache Pointer to uninitialized cache. + * \param bufsize Single item size for later allocs. + * \retval 0 on success. + * \retval -1 on error; + */ +int slab_cache_init(slab_cache_t* cache, size_t bufsize); + +/*! + * \brief Destroy a slab cache. + * + * Destroy a slab cache and all associated slabs. + * + * \param cache Pointer to slab cache. + */ +void slab_cache_destroy(slab_cache_t* cache); + +/*! + * \brief Allocate from the cache. + * + * It tries to use partially free caches first, + * empty caches second and allocates a new cache + * as a last resort. + * + * \param cache Given slab cache. + * \retval Pointer to allocated memory. + * \retval NULL on error. + */ +void* slab_cache_alloc(slab_cache_t* cache); + +/*! + * \brief Free unused slabs from cache. + * + * \param cache Given slab cache. + * \return Number of freed slabs. + */ +int slab_cache_reap(slab_cache_t* cache); + +/*! + * \brief Create a general purpose slab allocator. + * + * \note Please consult struct slab_alloc_t for performance hints. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int slab_alloc_init(slab_alloc_t* alloc); + +/*! + * \brief Delete slab allocator. + * + * This destroys all associated caches and frees memory. + * + * \param alloc Given allocator instance. + */ +void slab_alloc_destroy(slab_alloc_t* alloc); + +/*! + * \brief Allocate a block of memory. + * + * Returns a block of allocated memory. + * + * \note At least SLAB_MIN_BUFSIZE bytes is allocated. + * + * \note Please consult struct slab_alloc_t for performance hints. + * + * \param alloc Allocator instance. + * \param size Requested block size. + * \retval Pointer to allocated memory. + * \retval NULL on error. + */ +void* slab_alloc_alloc(slab_alloc_t* alloc, size_t size); + +/*! + * \brief Reallocate data from one slab to another. + * + * \param alloc Allocator instance. + * \param ptr Pointer to allocated memory. + * \param size Requested memory block size. + * \retval Pointer to newly allocated memory. + * \retval NULL on error. + * + * \todo Realloc could be probably implement more effectively. + */ +void *slab_alloc_realloc(slab_alloc_t* alloc, void *ptr, size_t size); + +/*! + * + * \brief Dump allocator stats. + * + * \param alloc Allocator instance. + */ +void slab_alloc_stats(slab_alloc_t* alloc); + +#endif /* _KNOTD_COMMON_SLAB_H_ */ + +/*! @} */ diff --git a/src/common/sockaddr.c b/src/common/sockaddr.c new file mode 100644 index 0000000..cd3a4b9 --- /dev/null +++ b/src/common/sockaddr.c @@ -0,0 +1,174 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> + +#include "common/sockaddr.h" + +int sockaddr_init(sockaddr_t *addr, int af) +{ + /* Reset pointer. */ + memset(addr, 0, sizeof(sockaddr_t)); + addr->family = -1; + + /* Initialize address size. */ + switch(af) { + case AF_INET: + addr->len = sizeof(struct sockaddr_in); + break; +#ifndef DISABLE_IPV6 + case AF_INET6: + addr->len = sizeof(struct sockaddr_in6); + break; +#endif + default: + return -1; + } + + /* Update pointer. */ + addr->family = af; + return sockaddr_update(addr); +} + +int sockaddr_update(sockaddr_t *addr) +{ + /* Update internal pointer. */ + switch(addr->len) { + case sizeof(struct sockaddr_in): + addr->ptr = (struct sockaddr*)&addr->addr4; + break; +#ifndef DISABLE_IPV6 + case sizeof(struct sockaddr_in6): + addr->ptr = (struct sockaddr*)&addr->addr6; + break; +#endif + default: + return -1; + } + + return 0; +} + +int sockaddr_set(sockaddr_t *dst, int family, const char* addr, int port) +{ + if (!dst || !addr || port < 0) { + return -1; + } + + /* Initialize. */ + dst->family = -1; + dst->ptr = 0; + dst->len = 0; + sockaddr_init(dst, family); + + /* Initialize depending on address family. */ + void *paddr = 0; + switch(family) { + case AF_INET: + dst->addr4.sin_family = family; + dst->addr4.sin_port = htons(port); + paddr = &dst->addr4.sin_addr; + dst->addr4.sin_addr.s_addr = INADDR_ANY; + break; +#ifndef DISABLE_IPV6 + case AF_INET6: + dst->addr6.sin6_family = family; + dst->addr6.sin6_port = htons(port); + paddr = &dst->addr6.sin6_addr; + memcpy(&dst->addr6.sin6_addr, + &in6addr_any, sizeof(in6addr_any)); + break; +#endif + default: + return -1; + } + + /* Convert address. */ + return inet_pton(family, addr, paddr); +} + +int sockaddr_tostr(sockaddr_t *addr, char *dst, size_t size) +{ + if (!addr || !dst || size == 0) { + return -1; + } + + /* Minimum length. */ + size_t minlen = INET_ADDRSTRLEN; + + /* Check unsupported IPv6. */ +#ifdef DISABLE_IPV6 + if (addr->family == AF_INET6) { + return -1; + } +#else + minlen = INET6_ADDRSTRLEN; +#endif + + /* Check minimum length. */ + if (size < minlen) { + return -1; + } + + /* Convert. */ +#ifdef DISABLE_IPV6 + dst[0] = '\0'; +#else + /* Load IPv6 addr if default. */ + if (addr->family == AF_INET6) { + inet_ntop(addr->family, &addr->addr6.sin6_addr, + dst, size); + } +#endif + /* Load IPv4 if set. */ + if (addr->family == AF_INET) { + inet_ntop(addr->family, &addr->addr4.sin_addr, + dst, size); + } + + return 0; +} + +int sockaddr_portnum(sockaddr_t *addr) +{ + if (!addr) { + return -1; + } + + switch(addr->family) { + + /* IPv4 */ + case AF_INET: + return ntohs(addr->addr4.sin_port); + break; + + /* IPv6 */ +#ifndef DISABLE_IPV6 + case AF_INET6: + return ntohs(addr->addr6.sin6_port); + break; +#endif + + /* N/A */ + default: + return -1; + break; + } +} diff --git a/src/common/sockaddr.h b/src/common/sockaddr.h new file mode 100644 index 0000000..51ba779 --- /dev/null +++ b/src/common/sockaddr.h @@ -0,0 +1,120 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file sockaddr.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Socket address abstraction layer. + * + * \addtogroup common_lib + * @{ + */ + +#ifndef _KNOTD_SOCKADDR_H_ +#define _KNOTD_SOCKADDR_H_ + +/* BSD IPv6 */ +#ifndef __POSIX_VISIBLE +#define __POSIX_VISIBLE = 200112 +#endif + +#include <netinet/in.h> +#include <arpa/inet.h> + +/*! \brief Universal socket address. */ +typedef struct sockaddr_t { + int family; /*!< Address family. */ + struct sockaddr* ptr; /*!< Pointer to used sockaddr. */ + socklen_t len; /*!< Length of used sockaddr. */ + union { + struct sockaddr_in addr4; /*!< IPv4 sockaddr. */ +#ifndef DISABLE_IPV6 + struct sockaddr_in6 addr6; /*!< IPv6 sockaddr. */ +#endif + }; +} sockaddr_t; + +/*! \brief Maximum address length in string format. */ +#ifdef DISABLE_IPV6 +#define SOCKADDR_STRLEN INET_ADDRSTRLEN +#else +#define SOCKADDR_STRLEN INET6_ADDRSTRLEN +#endif + +/*! + * \brief Initialize address structure. + * + * Members ptr and len will be initialized to correct address family. + * + * \param addr Socket address structure. + * \param af Requested address family. + * + * \retval 0 on success. + * \retval -1 on unsupported address family (probably INET6). + */ +int sockaddr_init(sockaddr_t *addr, int af); + +/*! + * \brief Update internal pointers according to length. + * + * \param addr Socket address structure. + * + * \retval 0 on success. + * \retval -1 on invalid size. + */ +int sockaddr_update(sockaddr_t *addr); + +/*! + * \brief Set address and port. + * + * \param dst Target address structure. + * \param family Address family. + * \param addr IP address in string format. + * \param port Port. + * + * \retval 0 if addr is not valid address in string format. + * \retval positive value in case of success. + * \retval -1 on error. + * \see inet_pton(3) + */ +int sockaddr_set(sockaddr_t *dst, int family, const char* addr, int port); + +/*! + * \brief Return string representation of socket address. + * + * \param addr Socket address structure. + * \param dst Destination for string representation. + * \param size Maximum number of written bytes. + * + * \retval 0 on success. + * \retval -1 on invalid parameters. + */ +int sockaddr_tostr(sockaddr_t *addr, char *dst, size_t size); + +/*! + * \brief Return port number from address. + * + * \param addr Socket address structure. + * + * \retval Port number on success. + * \retval -1 on errors. + */ +int sockaddr_portnum(sockaddr_t *addr); + +#endif /* _KNOTD_SOCKADDR_H_ */ + +/*! @} */ diff --git a/src/common/tree.h b/src/common/tree.h new file mode 100644 index 0000000..efea65b --- /dev/null +++ b/src/common/tree.h @@ -0,0 +1,268 @@ +/* tree.h -- AVL trees (in the spirit of BSD's 'queue.h') -*- C -*- */ + +/* Copyright (c) 2005 Ian Piumarta + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the 'Software'), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * provided that the above copyright notice(s) and this permission notice appear + * in all copies of the Software and that both the above copyright notice(s) and + * this permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. + */ + +/* This file defines an AVL balanced binary tree [Georgii M. Adelson-Velskii and + * Evgenii M. Landis, 'An algorithm for the organization of information', + * Doklady Akademii Nauk SSSR, 146:263-266, 1962 (Russian). Also in Myron + * J. Ricci (trans.), Soviet Math, 3:1259-1263, 1962 (English)]. + * + * An AVL tree is headed by pointers to the root node and to a function defining + * the ordering relation between nodes. Each node contains an arbitrary payload + * plus three fields per tree entry: the depth of the subtree for which it forms + * the root and two pointers to child nodes (singly-linked for minimum space, at + * the expense of direct access to the parent node given a pointer to one of the + * children). The tree is rebalanced after every insertion or removal. The + * tree may be traversed in two directions: forward (in-order left-to-right) and + * reverse (in-order, right-to-left). + * + * Because of the recursive nature of many of the operations on trees it is + * necessary to define a number of helper functions for each type of tree node. + * The macro TREE_DEFINE(node_tag, entry_name) defines these functions with + * unique names according to the node_tag. This macro should be invoked, + * thereby defining the necessary functions, once per node tag in the program. + * + * For details on the use of these macros, see the tree(3) manual page. + */ + +/* Downloaded from http://piumarta.com/software/tree/ */ + +#ifndef __tree_h +#define __tree_h + + +#define TREE_DELTA_MAX 1 + +#define TREE_ENTRY(type) \ + struct { \ + struct type *avl_left; \ + struct type *avl_right; \ + int avl_height; \ + } + +#define TREE_HEAD(name, type) \ + struct name { \ + struct type *th_root; \ + int (*th_cmp)(struct type *lhs, struct type *rhs); \ + } + +#define TREE_INITIALIZER(cmp) { 0, cmp } + +#define TREE_DELTA(self, field) \ + (( (((self)->field.avl_left) ? (self)->field.avl_left->field.avl_height : 0)) \ + - (((self)->field.avl_right) ? (self)->field.avl_right->field.avl_height : 0)) + +/* Recursion prevents the following from being defined as macros. */ + +#define TREE_DEFINE(node, field) \ + \ + struct node *TREE_BALANCE_##node##_##field(struct node *); \ + \ + struct node *TREE_ROTL_##node##_##field(struct node *self) \ + { \ + struct node *r= self->field.avl_right; \ + self->field.avl_right= r->field.avl_left; \ + r->field.avl_left= TREE_BALANCE_##node##_##field(self); \ + return TREE_BALANCE_##node##_##field(r); \ + } \ + \ + struct node *TREE_ROTR_##node##_##field(struct node *self) \ + { \ + struct node *l= self->field.avl_left; \ + self->field.avl_left= l->field.avl_right; \ + l->field.avl_right= TREE_BALANCE_##node##_##field(self); \ + return TREE_BALANCE_##node##_##field(l); \ + } \ + \ + struct node *TREE_BALANCE_##node##_##field(struct node *self) \ + { \ + int delta= TREE_DELTA(self, field); \ + \ + if (delta < -TREE_DELTA_MAX) \ + { \ + if (TREE_DELTA(self->field.avl_right, field) > 0) \ + self->field.avl_right= TREE_ROTR_##node##_##field(self->field.avl_right); \ + return TREE_ROTL_##node##_##field(self); \ + } \ + else if (delta > TREE_DELTA_MAX) \ + { \ + if (TREE_DELTA(self->field.avl_left, field) < 0) \ + self->field.avl_left= TREE_ROTL_##node##_##field(self->field.avl_left); \ + return TREE_ROTR_##node##_##field(self); \ + } \ + self->field.avl_height= 0; \ + if (self->field.avl_left && (self->field.avl_left->field.avl_height > self->field.avl_height)) \ + self->field.avl_height= self->field.avl_left->field.avl_height; \ + if (self->field.avl_right && (self->field.avl_right->field.avl_height > self->field.avl_height)) \ + self->field.avl_height= self->field.avl_right->field.avl_height; \ + self->field.avl_height += 1; \ + return self; \ + } \ + \ + struct node *TREE_INSERT_##node##_##field \ + (struct node *self, struct node *elm, int (*compare)(struct node *lhs, struct node *rhs)) \ + { \ + if (!self) \ + return elm; \ + if (compare(elm, self) < 0) \ + self->field.avl_left= TREE_INSERT_##node##_##field(self->field.avl_left, elm, compare); \ + else \ + self->field.avl_right= TREE_INSERT_##node##_##field(self->field.avl_right, elm, compare); \ + return TREE_BALANCE_##node##_##field(self); \ + } \ + \ + struct node *TREE_FIND_##node##_##field \ + (struct node *self, struct node *elm, int (*compare)(struct node *lhs, struct node *rhs)) \ + { \ + if (!self) \ + return 0; \ + if (compare(elm, self) == 0) \ + return self; \ + if (compare(elm, self) < 0) \ + return TREE_FIND_##node##_##field(self->field.avl_left, elm, compare); \ + else \ + return TREE_FIND_##node##_##field(self->field.avl_right, elm, compare); \ + } \ + \ + int TREE_FIND_LESS_EQUAL_##node##_##field \ + (struct node *self, struct node *elm, int (*compare)(struct node *lhs, struct node *rhs), struct node **found, struct node **prev) \ + { \ + if (!self) \ + return 0; \ + if (compare(elm, self) == 0) { \ + *found = self; \ + return 1; \ + } \ + if (compare(elm, self) < 0) { \ + int ret = TREE_FIND_LESS_EQUAL_##node##_##field(self->field.avl_left, elm, compare, found, prev); \ + if (ret == 0 && *prev == NULL) { \ + *prev = self; \ + ret = -1; \ + } \ + return ret; \ + } else { \ + *found = self; \ + *prev = self; \ + return TREE_FIND_LESS_EQUAL_##node##_##field(self->field.avl_right, elm, compare, found, prev); \ + } \ + } \ + \ + struct node *TREE_MOVE_RIGHT_##node##_##field(struct node *self, struct node *rhs) \ + { \ + if (!self) \ + return rhs; \ + self->field.avl_right= TREE_MOVE_RIGHT_##node##_##field(self->field.avl_right, rhs); \ + return TREE_BALANCE_##node##_##field(self); \ + } \ + \ + struct node *TREE_REMOVE_##node##_##field \ + (struct node *self, struct node *elm, int (*compare)(struct node *lhs, struct node *rhs)) \ + { \ + if (!self) return 0; \ + \ + if (compare(elm, self) == 0) \ + { \ + struct node *tmp= TREE_MOVE_RIGHT_##node##_##field(self->field.avl_left, self->field.avl_right); \ + self->field.avl_left= 0; \ + self->field.avl_right= 0; \ + return tmp; \ + } \ + if (compare(elm, self) < 0) \ + self->field.avl_left= TREE_REMOVE_##node##_##field(self->field.avl_left, elm, compare); \ + else \ + self->field.avl_right= TREE_REMOVE_##node##_##field(self->field.avl_right, elm, compare); \ + return TREE_BALANCE_##node##_##field(self); \ + } \ + \ + void TREE_FORWARD_APPLY_ALL_##node##_##field \ + (struct node *self, void (*function)(struct node *node, void *data), void *data) \ + { \ + if (self) \ + { \ + TREE_FORWARD_APPLY_ALL_##node##_##field(self->field.avl_left, function, data); \ + function(self, data); \ + TREE_FORWARD_APPLY_ALL_##node##_##field(self->field.avl_right, function, data); \ + } \ + } \ + \ + void TREE_REVERSE_APPLY_ALL_##node##_##field \ + (struct node *self, void (*function)(struct node *node, void *data), void *data) \ + { \ + if (self) \ + { \ + TREE_REVERSE_APPLY_ALL_##node##_##field(self->field.avl_right, function, data); \ + function(self, data); \ + TREE_REVERSE_APPLY_ALL_##node##_##field(self->field.avl_left, function, data); \ + } \ + } \ + \ + void TREE_POST_ORDER_APPLY_ALL_##node##_##field \ + (struct node *self, void (*function)(struct node *node, void *data), void *data) \ + { \ + if (self) \ + { \ + TREE_POST_ORDER_APPLY_ALL_##node##_##field(self->field.avl_left, function, data); \ + TREE_POST_ORDER_APPLY_ALL_##node##_##field(self->field.avl_right, function, data); \ + function(self, data); \ + } \ + } \ + \ + void TREE_REVERSE_APPLY_POST_ALL_##node##_##field \ + (struct node *self, void (*function)(struct node *node, void *data), void *data) \ + { \ + if (self) \ + { \ + TREE_REVERSE_APPLY_POST_ALL_##node##_##field(self->field.avl_right, function, data); \ + TREE_REVERSE_APPLY_POST_ALL_##node##_##field(self->field.avl_left, function, data); \ + function(self, data); \ + } \ +} + +#define TREE_INSERT(head, node, field, elm) \ + ((head)->th_root= TREE_INSERT_##node##_##field((head)->th_root, (elm), (head)->th_cmp)) + +#define TREE_FIND(head, node, field, elm) \ + (TREE_FIND_##node##_##field((head)->th_root, (elm), (head)->th_cmp)) + +#define TREE_FIND_LESS_EQUAL(head, node, field, elm, found, prev) \ + (TREE_FIND_LESS_EQUAL_##node##_##field((head)->th_root, (elm), (head)->th_cmp, found, prev)) + +#define TREE_REMOVE(head, node, field, elm) \ + ((head)->th_root= TREE_REMOVE_##node##_##field((head)->th_root, (elm), (head)->th_cmp)) + +#define TREE_DEPTH(head, field) \ + ((head)->th_root->field.avl_height) + +#define TREE_FORWARD_APPLY(head, node, field, function, data) \ + TREE_FORWARD_APPLY_ALL_##node##_##field((head)->th_root, function, data) + +#define TREE_REVERSE_APPLY(head, node, field, function, data) \ + TREE_REVERSE_APPLY_ALL_##node##_##field((head)->th_root, function, data) + +#define TREE_POST_ORDER_APPLY(head, node, field, function, data) \ + TREE_POST_ORDER_APPLY_ALL_##node##_##field((head)->th_root, function, data) + +#define TREE_REVERSE_APPLY_POST(head, node, field, function, data) \ + TREE_REVERSE_APPLY_POST_ALL_##node##_##field((head)->th_root, function, data) + +#define TREE_INIT(head, cmp) do { \ + (head)->th_root= 0; \ + (head)->th_cmp= (cmp); \ + } while (0) + + +#endif /* __tree_h */ diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..521adb8 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,307 @@ +/* src/config.h.in. Generated from configure.ac by autoheader. */ + +/* Enable brief debugging messages. */ +#undef DEBUG_ENABLE_BRIEF + +/* Enable details debugging messages. */ +#undef DEBUG_ENABLE_DETAILS + +/* Enable verbose debugging messages. */ +#undef DEBUG_ENABLE_VERBOSE + +/* recvmmsg enabled */ +#undef ENABLE_RECVMMSG + +/* Define to 1 if you have the <arpa/inet.h> header file. */ +#undef HAVE_ARPA_INET_H + +/* Define to 1 if you have the <arpa/nameser.h> header file. */ +#undef HAVE_ARPA_NAMESER_H + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#undef HAVE_DLFCN_H + +/* Define to 1 if you have the `epoll_wait' function. */ +#undef HAVE_EPOLL_WAIT + +/* Define to 1 if you have the <ev.h> header file. */ +#undef HAVE_EV_H + +/* Define to 1 if you have the <fcntl.h> header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the `fork' function. */ +#undef HAVE_FORK + +/* Define to 1 if you have the `gethostbyname' function. */ +#undef HAVE_GETHOSTBYNAME + +/* Define to 1 if you have the `getpagesize' function. */ +#undef HAVE_GETPAGESIZE + +/* Define to 1 if you have the `gettimeofday' function. */ +#undef HAVE_GETTIMEOFDAY + +/* Define to 1 if you have the <inttypes.h> header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the `kqueue' function. */ +#undef HAVE_KQUEUE + +/* ldns present */ +#undef HAVE_LDNS + +/* Define to 1 if you have the <limits.h> header file. */ +#undef HAVE_LIMITS_H + +/* Define to 1 if you have the <malloc.h> header file. */ +#undef HAVE_MALLOC_H + +/* Define to 1 if you have the `memmove' function. */ +#undef HAVE_MEMMOVE + +/* Define to 1 if you have the <memory.h> header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `memset' function. */ +#undef HAVE_MEMSET + +/* Define to 1 if you have a working `mmap' system call. */ +#undef HAVE_MMAP + +/* Support mmx instructions */ +#undef HAVE_MMX + +/* Define to 1 if you have the `munmap' function. */ +#undef HAVE_MUNMAP + +/* Define to 1 if you have the <netdb.h> header file. */ +#undef HAVE_NETDB_H + +/* Define to 1 if you have the <netinet/in.h> header file. */ +#undef HAVE_NETINET_IN_H + +/* Define to 1 if you have the `poll' function. */ +#undef HAVE_POLL + +/* Define to 1 if you have the `regcomp' function. */ +#undef HAVE_REGCOMP + +/* Define to 1 if you have the <resolv.h> header file. */ +#undef HAVE_RESOLV_H + +/* Define to 1 if you have the `select' function. */ +#undef HAVE_SELECT + +/* Define to 1 if you have the `socket' function. */ +#undef HAVE_SOCKET + +/* Define to 1 if you have the `sqrt' function. */ +#undef HAVE_SQRT + +/* Support SSE (Streaming SIMD Extensions) instructions */ +#undef HAVE_SSE + +/* Support SSE2 (Streaming SIMD Extensions 2) instructions */ +#undef HAVE_SSE2 + +/* Support SSE3 (Streaming SIMD Extensions 3) instructions */ +#undef HAVE_SSE3 + +/* Support SSSE3 (Supplemental Streaming SIMD Extensions 3) instructions */ +#undef HAVE_SSSE3 + +/* Define to 1 if stdbool.h conforms to C99. */ +#undef HAVE_STDBOOL_H + +/* Define to 1 if you have the <stdint.h> header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the <stdlib.h> header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the `strcasecmp' function. */ +#undef HAVE_STRCASECMP + +/* Define to 1 if you have the `strchr' function. */ +#undef HAVE_STRCHR + +/* Define to 1 if you have the `strdup' function. */ +#undef HAVE_STRDUP + +/* Define to 1 if you have the `strerror' function. */ +#undef HAVE_STRERROR + +/* Define to 1 if you have the <strings.h> header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the <string.h> header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strncasecmp' function. */ +#undef HAVE_STRNCASECMP + +/* Define to 1 if you have the `strtol' function. */ +#undef HAVE_STRTOL + +/* Define to 1 if you have the `strtoul' function. */ +#undef HAVE_STRTOUL + +/* Define to 1 if you have the <syslog.h> header file. */ +#undef HAVE_SYSLOG_H + +/* Define to 1 if you have the <sys/param.h> header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define to 1 if you have the <sys/socket.h> header file. */ +#undef HAVE_SYS_SOCKET_H + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the <sys/time.h> header file. */ +#undef HAVE_SYS_TIME_H + +/* Define to 1 if you have the <sys/types.h> header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the <unistd.h> header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the <urcu.h> header file. */ +#undef HAVE_URCU_H + +/* Define to 1 if you have the `vfork' function. */ +#undef HAVE_VFORK + +/* Define to 1 if you have the <vfork.h> header file. */ +#undef HAVE_VFORK_H + +/* Define to 1 if `fork' works. */ +#undef HAVE_WORKING_FORK + +/* Define to 1 if `vfork' works. */ +#undef HAVE_WORKING_VFORK + +/* Define to 1 if the system has the type `_Bool'. */ +#undef HAVE__BOOL + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#undef LT_OBJDIR + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Enable extensions on AIX 3, Interix. */ +#ifndef _ALL_SOURCE +# undef _ALL_SOURCE +#endif +/* Enable GNU extensions on systems that have them. */ +#ifndef _GNU_SOURCE +# undef _GNU_SOURCE +#endif +/* Enable threading extensions on Solaris. */ +#ifndef _POSIX_PTHREAD_SEMANTICS +# undef _POSIX_PTHREAD_SEMANTICS +#endif +/* Enable extensions on HP NonStop. */ +#ifndef _TANDEM_SOURCE +# undef _TANDEM_SOURCE +#endif +/* Enable general extensions on Solaris. */ +#ifndef __EXTENSIONS__ +# undef __EXTENSIONS__ +#endif + + +/* Version number of package */ +#undef VERSION + +/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a + `char[]'. */ +#undef YYTEXT_POINTER + +/* Define to 1 if on MINIX. */ +#undef _MINIX + +/* Define to 2 if the system does not provide POSIX.1 features except with + this defined. */ +#undef _POSIX_1_SOURCE + +/* Define to 1 if you need to in order for `stat' and other things to work. */ +#undef _POSIX_SOURCE + +/* Define for Solaris 2.5.1 so the uint32_t typedef from <sys/synch.h>, + <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the + #define below would cause a syntax error. */ +#undef _UINT32_T + +/* Define for Solaris 2.5.1 so the uint64_t typedef from <sys/synch.h>, + <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the + #define below would cause a syntax error. */ +#undef _UINT64_T + +/* Define for Solaris 2.5.1 so the uint8_t typedef from <sys/synch.h>, + <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the + #define below would cause a syntax error. */ +#undef _UINT8_T + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to the type of a signed integer type of width exactly 64 bits if + such a type exists and the standard includes do not define it. */ +#undef int64_t + +/* Define to `int' if <sys/types.h> does not define. */ +#undef pid_t + +/* Define to `unsigned int' if <sys/types.h> does not define. */ +#undef size_t + +/* Define to `int' if <sys/types.h> does not define. */ +#undef ssize_t + +/* Define to the type of an unsigned integer type of width exactly 16 bits if + such a type exists and the standard includes do not define it. */ +#undef uint16_t + +/* Define to the type of an unsigned integer type of width exactly 32 bits if + such a type exists and the standard includes do not define it. */ +#undef uint32_t + +/* Define to the type of an unsigned integer type of width exactly 64 bits if + such a type exists and the standard includes do not define it. */ +#undef uint64_t + +/* Define to the type of an unsigned integer type of width exactly 8 bits if + such a type exists and the standard includes do not define it. */ +#undef uint8_t + +/* Define as `fork' if `vfork' does not work. */ +#undef vfork diff --git a/src/knot/common.h b/src/knot/common.h new file mode 100644 index 0000000..e9cfce1 --- /dev/null +++ b/src/knot/common.h @@ -0,0 +1,133 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file common.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Common macros, includes and utilities. + * + * \addtogroup utils + * @{ + */ + +#ifndef _KNOTD_COMMON_H_ +#define _KNOTD_COMMON_H_ + +#include <signal.h> +#include <stdint.h> +#include <config.h> + +/* + * Common types and constants. + */ + +#ifndef UINT_DEFINED +typedef unsigned int uint; /*!< \brief Unsigned. */ +#define UINT_DEFINED +#endif + +#define PROJECT_EXEC SBINDIR "/" "knotd" /*!< \brief Project executable. */ +#define ZONEPARSER_EXEC LIBEXECDIR "/" "knot-zcompile" /*!< \brief Zoneparser executable. */ +#define PID_FILE "knot.pid" /*!< \brief Server PID file name. */ + +/* + * Server. + */ + +#define CPU_ESTIMATE_MAGIC 0 /*!< \brief Extra threads to the number of cores.*/ +#define DEFAULT_THR_COUNT 2 /*!< \brief Default thread count. */ +#define DEFAULT_PORT 53531 /*!< \brief Default interface port. */ +#define TCP_BACKLOG_SIZE 5 /*!< \brief TCP listen backlog size. */ +#define XFR_THREADS_COUNT 3 /*!< \brief Number of threads for XFR handler. */ +#define RECVMMSG_BATCHLEN 32 /*!< \brief Define for recvmmsg() batch size. */ + +///*! \brief If defined, zone structures will use hash table for lookup. */ +//#define COMPRESSION_PEDANTIC + +///*! +// * \brief If defined, tests will use ldns library to parse sample data. +// * +// * If not defined, some tests will be disabled. +// */ +//#define TEST_WITH_LDNS + +///*! \brief If defined, the statistics module will be enabled. */ +//#define STAT_COMPILE + + +#ifdef HAVE_LDNS +#define TEST_WITH_LDNS +#endif + +/* + * Common includes. + */ + +#include "common/latency.h" +#include "common/print.h" +#include "knot/other/log.h" +#include "knot/other/debug.h" + +/*! \brief Eliminate compiler warning with unused parameters. */ +#define UNUSED(param) (void)(param) + +/*! \brief Type-safe minimum macro. */ +#define MIN(a, b) \ + ({ typeof (a) _a = (a); typeof (b) _b = (b); _a < _b ? _a : _b; }) + +/*! \brief Type-safe maximum macro. */ +#define MAX(a, b) \ + ({ typeof (a) _a = (a); typeof (b) _b = (b); _a > _b ? _a : _b; }) + +/* Optimisation macros. */ +#ifndef likely +/*! \brief Optimize for x to be true value. */ +#define likely(x) __builtin_expect((x),1) +#endif +#ifndef unlikely +/*! \brief Optimize for x to be false value. */ +#define unlikely(x) __builtin_expect((x),0) +#endif + +/*! \todo Refactor theese. We should have an allocator function handling this.*/ +#ifndef ERR_ALLOC_FAILED +#define ERR_ALLOC_FAILED fprintf(stderr, "Allocation failed at %s:%d (%s)\n", \ + __FILE__, __LINE__, PACKAGE_STRING) +#endif + +#ifndef CHECK_ALLOC_LOG +#define CHECK_ALLOC_LOG(var, ret) \ + do { \ + if ((var) == NULL) { \ + ERR_ALLOC_FAILED; \ + return (ret); \ + } \ + } while (0) +#endif + +#ifndef CHECK_ALLOC +#define CHECK_ALLOC(var, ret) \ + do { \ + if ((var) == NULL) { \ + return (ret); \ + } \ + } while (0) +#endif + +#endif /* _KNOTD_COMMON_H_ */ + +/*! @} */ diff --git a/src/knot/conf/cf-lex.l b/src/knot/conf/cf-lex.l new file mode 100644 index 0000000..97ac8f8 --- /dev/null +++ b/src/knot/conf/cf-lex.l @@ -0,0 +1,218 @@ +/*! + * \file cf-lex.l + * + * \author Ondrej Sury <ondrej.sury@nic.cz> + * + * \brief Server configuration structures and API. + * + * IP address conversions from BIRD, (c) 1998--2000 Martin Mares <mj@ucw.cz> + */ +%{ + +#include <errno.h> +#include <stdlib.h> +#include <stdarg.h> + +#include "common/sockaddr.h" +#include "knot/conf/conf.h" +#include "knot/other/log.h" +#include "libknotd_la-cf-parse.h" /* Automake generated header. */ + +/* Imported symbols. */ +#define lval (yylval->tok) +extern void cf_error(void *scanner, const char *msg); +extern int (*cf_read_hook)(char *buf, size_t nbytes); +void switch_input(const char *str, void *scanner) +{ + yy_scan_string(str, scanner); +} + +//#define YY_INPUT(buf,result,max) result = cf_read_hook(buf, max); +#define YY_NO_UNPUT + +%} + +%option reentrant +%option bison-bridge +%option noyywrap +%option noinput +%option nounput +%option noreject +%option yylineno +%option prefix = "cf_" +%option outfile = "lex.yy.c" + +ALPHA [a-zA-Z_] +DIGIT [0-9] +HEXA [0-9a-fA-F] +ALNUM [a-zA-Z_0-9] +BLANK [ \t\n] + +%% +\#.*\n /* Ignore comments */; +{BLANK}+ /* Ignore whitespace */; +: /* Optional : in assignments. */; +[\!\$\%\^\&\*\(\)\/\+\-\@\{\}\;\,] { return yytext[0]; } +system { lval.t = yytext; return SYSTEM; } +identity { lval.t = yytext; return IDENTITY; } +version { lval.t = yytext; return VERSION; } +storage { lval.t = yytext; return STORAGE; } +key { lval.t = yytext; return KEY; } +keys { lval.t = yytext; return KEYS; } +remotes { lval.t = yytext; return REMOTES; } + +zones { lval.t = yytext; return ZONES; } +file { lval.t = yytext; return FILENAME; } +semantic-checks { lval.t = yytext; return SEMANTIC_CHECKS; } +notify-retries { lval.t = yytext; return NOTIFY_RETRIES; } +notify-timeout { lval.t = yytext; return NOTIFY_TIMEOUT; } +zonefile-sync { lval.t = yytext; return DBSYNC_TIMEOUT; } +ixfr-fslimit { lval.t = yytext; return IXFR_FSLIMIT; } +xfr-in { lval.t = yytext; return XFR_IN; } +xfr-out { lval.t = yytext; return XFR_OUT; } +notify-in { lval.t = yytext; return NOTIFY_IN; } +notify-out { lval.t = yytext; return NOTIFY_OUT; } +workers { lval.t = yytext; return WORKERS; } + +interfaces { lval.t = yytext; return INTERFACES; } +address { lval.t = yytext; return ADDRESS; } +port { lval.t = yytext; return PORT; } + +log { lval.t = yytext; return LOG; } + +any { lval.t = yytext; lval.i = LOG_ANY; return LOG_SRC; } +server { lval.t = yytext; lval.i = LOG_SERVER; return LOG_SRC; } +answering { lval.t = yytext; lval.i = LOG_ANSWER; return LOG_SRC; } +zone { lval.t = yytext; lval.i = LOG_ZONE; return LOG_SRC; } +stdout { lval.t = yytext; lval.i = LOGT_STDOUT; return LOG_DEST; } +stderr { lval.t = yytext; lval.i = LOGT_STDERR; return LOG_DEST; } +syslog { lval.t = yytext; lval.i = LOGT_SYSLOG; return LOG_DEST; } +all { lval.t = yytext; lval.i = LOG_UPTO(LOG_DEBUG); return LOG_LEVEL; } +debug { lval.t = yytext; lval.i = LOG_MASK(LOG_DEBUG); return LOG_LEVEL; } +info { lval.t = yytext; lval.i = LOG_MASK(LOG_INFO); return LOG_LEVEL; } +notice { lval.t = yytext; lval.i = LOG_MASK(LOG_NOTICE); return LOG_LEVEL; } +warning { lval.t = yytext; lval.i = LOG_MASK(LOG_WARNING); return LOG_LEVEL; } +error { lval.t = yytext; lval.i = LOG_MASK(LOG_ERR); return LOG_LEVEL; } + +on|off { + lval.t = yytext; + lval.i = 0; + if (strcmp(yytext, "on") == 0) { + lval.i = 1; + } + return BOOL; +} + +{DIGIT}+[smhd] { + size_t mpos = strlen(yytext) - 1; + char multiplier = yytext[mpos]; + yytext[mpos] = '\0'; + lval.i = atoi(yytext); + if (lval.i < 1) { + cf_error(yyscanner, "interval must be a positive integer"); + return END; + } + + /* Handle multiplier. */ + switch(multiplier) { + case 'm': lval.i *= 60; break; /* minutes */ + case 'h': lval.i *= 60*60; break; /* hours */ + case 'd': lval.i *= 24*60*60; break; /* days */ + case 's': /* seconds */ + default: break; + } + + return INTERVAL; +} + +{DIGIT}+[kMG] { + size_t mpos = strlen(yytext) - 1; + char multiplier = yytext[mpos]; + yytext[mpos] = '\0'; + lval.i = atol(yytext); + if (lval.i < 1) { + cf_error(yyscanner, "size must be a positive integer"); + return END; + } + + /* Handle multiplier. */ + switch(multiplier) { + case 'k': lval.l = lval.i * 1024; break; /* kB */ + case 'M': lval.l = lval.i * 1024*1024; break; /* MB */ + case 'G': lval.l = lval.i * 1024*1024*1024; break; /* GB */ + default: break; + } + + return SIZE; +} + +{DIGIT}+ { + lval.i = atol(yytext); + return NUM; +} + +{DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+ { + unsigned char buf[sizeof(struct in_addr)]; + if (inet_pton(AF_INET, yytext, buf)) { + lval.t = strdup(yytext); + return IPA; + } + cf_error(yyscanner, "Invalid IP address."); +} + +\[({HEXA}*::|({HEXA}*:){3,})({HEXA}*|{DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+)\] { +#ifdef DISABLE_IPV6 + lval.t = strdup(yytext); + cf_error(yyscanner, "IPv6 address support not compiled."); + return TEXT; +#else + unsigned char buf[sizeof(struct in6_addr)]; + yytext[strlen(yytext)-1] = '\0'; + if (inet_pton(AF_INET6, yytext+1, buf)) { + lval.t = strdup(yytext+1); + return IPA6; + } + cf_error(yyscanner, "Invalid IPv6 address."); +#endif + } + +({HEXA}*::|({HEXA}*:){3,})({HEXA}*|{DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+) { +#ifdef DISABLE_IPV6 + lval.t = strdup(yytext); + cf_error(yyscanner, "IPv6 address support not compiled."); + return TEXT; +#else + unsigned char buf[sizeof(struct in6_addr)]; + if (inet_pton(AF_INET6, yytext, buf)) { + lval.t = strdup(yytext); + return IPA6; + } + cf_error(yyscanner, "Invalid IPv6 address."); +#endif +} + +gss-tsig { lval.alg = KNOT_TSIG_ALG_GSS_TSIG; return TSIG_ALGO_NAME; } +hmac-md5 { lval.alg = KNOT_TSIG_ALG_HMAC_MD5; return TSIG_ALGO_NAME; } +hmac-sha1 { lval.alg = KNOT_TSIG_ALG_HMAC_SHA1; return TSIG_ALGO_NAME; } +hmac-sha224 { lval.alg = KNOT_TSIG_ALG_HMAC_SHA224; return TSIG_ALGO_NAME; } +hmac-sha256 { lval.alg = KNOT_TSIG_ALG_HMAC_SHA256; return TSIG_ALGO_NAME; } +hmac-sha384 { lval.alg = KNOT_TSIG_ALG_HMAC_SHA384; return TSIG_ALGO_NAME; } +hmac-sha512 { lval.alg = KNOT_TSIG_ALG_HMAC_SHA512; return TSIG_ALGO_NAME; } + +["][^"\n]*["] { + yytext[yyleng-1] = 0; + lval.t = strdup(yytext + 1); + return TEXT; +} + +["][^"\n]*\n cf_error(yyscanner, "Unterminated string."); + +[a-zA-Z0-9\.\-\_]+ { + lval.t = strdup(yytext); + return TEXT /* Last resort, alphanumeric word. */; +} + +<<EOF>> return END; + +%% + diff --git a/src/knot/conf/cf-parse.y b/src/knot/conf/cf-parse.y new file mode 100644 index 0000000..d793865 --- /dev/null +++ b/src/knot/conf/cf-parse.y @@ -0,0 +1,646 @@ +/*! + * \file cf-parse.y + * + * \author Ondrej Sury <ondrej.sury@nic.cz> + * + * \brief Server configuration structures and API. + */ +%{ +/* Headers */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "libknot/dname.h" +#include "knot/conf/conf.h" +#include "libknotd_la-cf-parse.h" /* Automake generated header. */ + +extern int cf_lex (YYSTYPE *lvalp, void *scanner); +extern void cf_error(void *scanner, const char *msg); +extern conf_t *new_config; +static conf_iface_t *this_iface = 0; +static conf_iface_t *this_remote = 0; +static conf_zone_t *this_zone = 0; +static list *this_list = 0; +static conf_log_t *this_log = 0; +static conf_log_map_t *this_logmap = 0; +//#define YYERROR_VERBOSE 1 + +static void conf_start_iface(char* ifname) +{ + this_iface = malloc(sizeof(conf_iface_t)); + memset(this_iface, 0, sizeof(conf_iface_t)); + this_iface->name = ifname; + this_iface->address = 0; // No default address (mandatory) + this_iface->port = CONFIG_DEFAULT_PORT; + add_tail(&new_config->ifaces, &this_iface->n); + ++new_config->ifaces_count; +} + +static void conf_start_remote(char *remote) +{ + this_remote = malloc(sizeof(conf_iface_t)); + memset(this_remote, 0, sizeof(conf_iface_t)); + this_remote->name = remote; + this_remote->address = 0; // No default address (mandatory) + this_remote->port = 0; // Port wildcard + add_tail(&new_config->remotes, &this_remote->n); + ++new_config->remotes_count; +} + +static void conf_acl_item(void *scanner, char *item) +{ + /* Find existing node in remotes. */ + node* r = 0; conf_iface_t* found = 0; + WALK_LIST (r, new_config->remotes) { + if (strcmp(((conf_iface_t*)r)->name, item) == 0) { + found = (conf_iface_t*)r; + break; + } + } + + /* Append to list if found. */ + if (!found) { + char buf[512]; + snprintf(buf, sizeof(buf), "remote '%s' is not defined", item); + cf_error(scanner, buf); + } else { + /* check port if xfrin/notify-out */ + if (this_list == &this_zone->acl.xfr_in || + this_list == &this_zone->acl.notify_out) { + if (found->port == 0) { + cf_error(scanner, "remote specified for XFR/IN or NOTIFY/OUT " + " needs to have valid port!"); + free(item); + return; + } + } + conf_remote_t *remote = malloc(sizeof(conf_remote_t)); + if (!remote) { + cf_error(scanner, "out of memory"); + } else { + remote->remote = found; + add_tail(this_list, &remote->n); + } + } + + /* Free text token. */ + free(item); + } + +static int conf_key_exists(void *scanner, char *item) +{ + /* Find existing node in keys. */ + knot_dname_t *sample = knot_dname_new_from_str(item, strlen(item), 0); + char buf[512]; + conf_key_t* r = 0; + WALK_LIST (r, new_config->keys) { + if (knot_dname_compare(r->k.name, sample) == 0) { + snprintf(buf, sizeof(buf), "key '%s' is already defined", item); + cf_error(scanner, buf); + knot_dname_free(&sample); + return 1; + } + } + + knot_dname_free(&sample); + return 0; +} + +static int conf_key_add(void *scanner, knot_key_t **key, char *item) +{ + /* Reset */ + *key = 0; + + /* Find in keys */ + knot_dname_t *sample = knot_dname_new_from_str(item, strlen(item), 0); + + conf_key_t* r = 0; + WALK_LIST (r, new_config->keys) { + if (knot_dname_compare(r->k.name, sample) == 0) { + *key = &r->k; + knot_dname_free(&sample); + return 0; + } + } + + char buf[512]; + snprintf(buf, sizeof(buf), "key '%s' is not defined", item); + cf_error(scanner, buf); + knot_dname_free(&sample); + return 1; +} + +%} + +%pure-parser +%parse-param{void *scanner} +%lex-param{void *scanner} +%name-prefix = "cf_" + +%union { + struct { + char *t; + long i; + size_t l; + tsig_algorithm_t alg; + } tok; +} + +%token END INVALID_TOKEN +%token <tok> TEXT +%token <tok> NUM +%token <tok> INTERVAL +%token <tok> SIZE +%token <tok> BOOL + +%token <tok> SYSTEM IDENTITY VERSION STORAGE KEY KEYS +%token <tok> TSIG_ALGO_NAME +%token <tok> WORKERS + +%token <tok> REMOTES + +%token <tok> ZONES FILENAME +%token <tok> SEMANTIC_CHECKS +%token <tok> NOTIFY_RETRIES +%token <tok> NOTIFY_TIMEOUT +%token <tok> DBSYNC_TIMEOUT +%token <tok> IXFR_FSLIMIT +%token <tok> XFR_IN +%token <tok> XFR_OUT +%token <tok> NOTIFY_IN +%token <tok> NOTIFY_OUT + +%token <tok> INTERFACES ADDRESS PORT +%token <tok> IPA +%token <tok> IPA6 + +%token <tok> LOG +%token <tok> LOG_DEST +%token <tok> LOG_SRC +%token <tok> LOG_LEVEL + +%% + +config: conf_entries END { return 0; } ; + +conf_entries: + /* EMPTY */ + | conf_entries conf + ; + +interface_start: + | TEXT { conf_start_iface($1.t); } + | REMOTES { conf_start_iface(strdup($1.t)); } /* Allow strings reserved by token. */ + | LOG_SRC { conf_start_iface(strdup($1.t)); } + | LOG { conf_start_iface(strdup($1.t)); } + | LOG_LEVEL { conf_start_iface(strdup($1.t)); } + ; + +interface: + interface_start '{' + | interface PORT NUM ';' { + if (this_iface->port != CONFIG_DEFAULT_PORT) { + cf_error(scanner, "only one port definition is allowed in interface section\n"); + } else { + this_iface->port = $3.i; + } + } + | interface ADDRESS IPA ';' { + if (this_iface->address != 0) { + cf_error(scanner, "only one address is allowed in interface section\n"); + } else { + this_iface->address = $3.t; + this_iface->family = AF_INET; + } + } + | interface ADDRESS IPA '@' NUM ';' { + if (this_iface->address != 0) { + cf_error(scanner, "only one address is allowed in interface section\n"); + } else { + this_iface->address = $3.t; + this_iface->family = AF_INET; + if (this_iface->port != CONFIG_DEFAULT_PORT) { + cf_error(scanner, "only one port definition is allowed in interface section\n"); + } else { + this_iface->port = $5.i; + } + } + } + | interface ADDRESS IPA6 ';' { + if (this_iface->address != 0) { + cf_error(scanner, "only one address is allowed in interface section\n"); + } else { + this_iface->address = $3.t; + this_iface->family = AF_INET6; + } + } + | interface ADDRESS IPA6 '@' NUM ';' { + if (this_iface->address != 0) { + cf_error(scanner, "only one address is allowed in interface section\n"); + } else { + this_iface->address = $3.t; + this_iface->family = AF_INET6; + if (this_iface->port != CONFIG_DEFAULT_PORT) { + cf_error(scanner, "only one port definition is allowed in interface section\n"); + } else { + this_iface->port = $5.i; + } + } + } + ; + +interfaces: + INTERFACES '{' + | interfaces interface '}' { + if (this_iface->address == 0) { + char buf[512]; + snprintf(buf, sizeof(buf), "interface '%s' has no defined address", this_iface->name); + cf_error(scanner, buf); + } + } + ; + +system: + SYSTEM '{' + | system VERSION TEXT ';' { new_config->version = $3.t; } + | system IDENTITY TEXT ';' { new_config->identity = $3.t; } + | system STORAGE TEXT ';' { new_config->storage = $3.t; } + | system KEY TSIG_ALGO_NAME TEXT ';' { + fprintf(stderr, "warning: Config option 'system.key' is deprecated " + "and has no effect.\n"); + free($4.t); + } + | system WORKERS NUM ';' { + if ($3.i <= 0) { + cf_error(scanner, "worker count must be greater than 0\n"); + } else { + new_config->workers = $3.i; + } + } + ; + +keys: + KEYS '{' + | keys TEXT TSIG_ALGO_NAME TEXT ';' { + /* Normalize to FQDN */ + char *fqdn = $2.t; + if (fqdn[strlen(fqdn) - 1] != '.') { + char* tmp = malloc(strlen(fqdn) + 1 + 1); /* '.', '\0' */ + if (!tmp) { + cf_error(scanner, "out of memory when allocating string"); + free(fqdn); + fqdn = 0; + } else { + strcpy(tmp, fqdn); + strcat(tmp, "."); + free(fqdn); + fqdn = tmp; + } + } + + if (!conf_key_exists(scanner, fqdn)) { + knot_dname_t *dname = knot_dname_new_from_str(fqdn, strlen(fqdn), 0); + if (!dname) { + char buf[512]; + snprintf(buf, sizeof(buf), "key name '%s' not in valid domain " + "name format", fqdn); + cf_error(scanner, buf); + free(fqdn); + free($4.t); + } else { + conf_key_t *k = malloc(sizeof(conf_key_t)); + memset(k, 0, sizeof(conf_key_t)); + k->k.name = dname; + k->k.algorithm = $3.alg; + k->k.secret = $4.t; + add_tail(&new_config->keys, &k->n); + ++new_config->key_count; + free(fqdn); + } + } else { + free(fqdn); + free($4.t); + } +} + +remote_start: + | TEXT { conf_start_remote($1.t); } + | LOG_SRC { conf_start_remote(strdup($1.t)); } + | LOG { conf_start_remote(strdup($1.t)); } + | LOG_LEVEL { conf_start_remote(strdup($1.t)); } + ; + +remote: + remote_start '{' + | remote PORT NUM ';' { + if (this_remote->port != 0) { + cf_error(scanner, "only one port definition is allowed in remote section\n"); + } else { + this_remote->port = $3.i; + } + } + | remote ADDRESS IPA ';' { + if (this_remote->address != 0) { + cf_error(scanner, "only one address is allowed in remote section\n"); + } else { + this_remote->address = $3.t; + this_remote->family = AF_INET; + } + } + | remote ADDRESS IPA '@' NUM ';' { + if (this_remote->address != 0) { + cf_error(scanner, "only one address is allowed in remote section\n"); + } else { + this_remote->address = $3.t; + this_remote->family = AF_INET; + if (this_remote->port != 0) { + cf_error(scanner, "only one port definition is allowed in remote section\n"); + } else { + this_remote->port = $5.i; + } + } + } + | remote ADDRESS IPA6 ';' { + if (this_remote->address != 0) { + cf_error(scanner, "only one address is allowed in remote section\n"); + } else { + this_remote->address = $3.t; + this_remote->family = AF_INET6; + } + } + | remote ADDRESS IPA6 '@' NUM ';' { + if (this_remote->address != 0) { + cf_error(scanner, "only one address is allowed in remote section\n"); + } else { + this_remote->address = $3.t; + this_remote->family = AF_INET6; + if (this_remote->port != 0) { + cf_error(scanner, "only one port definition is allowed in remote section\n"); + } else { + this_remote->port = $5.i; + } + } + } + | remote KEY TEXT ';' { + if (this_remote->key != 0) { + cf_error(scanner, "only one TSIG key definition is allowed in remote section\n"); + } else { + conf_key_add(scanner, &this_remote->key, $3.t); + } + } + ; + +remotes: + REMOTES '{' + | remotes remote '}' { + if (this_remote->address == 0) { + char buf[512]; + snprintf(buf, sizeof(buf), "remote '%s' has no defined address", this_remote->name); + cf_error(scanner, buf); + } + } + ; + +zone_acl_start: + XFR_IN { + this_list = &this_zone->acl.xfr_in; + } + | XFR_OUT { + this_list = &this_zone->acl.xfr_out; + } + | NOTIFY_IN { + this_list = &this_zone->acl.notify_in; + } + | NOTIFY_OUT { + this_list = &this_zone->acl.notify_out; + } + ; + +zone_acl_item: + | TEXT { conf_acl_item(scanner, $1.t); } + | LOG_SRC { conf_acl_item(scanner, strdup($1.t)); } + | LOG { conf_acl_item(scanner, strdup($1.t)); } + | LOG_LEVEL { conf_acl_item(scanner, strdup($1.t)); } + ; + +zone_acl_list: + zone_acl_start + | zone_acl_list zone_acl_item ',' + | zone_acl_list zone_acl_item ';' + ; + +zone_acl: + zone_acl_start '{' + | zone_acl TEXT ';' { + /* Find existing node in remotes. */ + node* r = 0; conf_iface_t* found = 0; + WALK_LIST (r, new_config->remotes) { + if (strcmp(((conf_iface_t*)r)->name, $2.t) == 0) { + found = (conf_iface_t*)r; + break; + } + } + + /* Append to list if found. */ + if (!found) { + char buf[256]; + snprintf(buf, sizeof(buf), "remote '%s' is not defined", $2.t); + cf_error(scanner, buf); + } else { + conf_remote_t *remote = malloc(sizeof(conf_remote_t)); + if (!remote) { + cf_error(scanner, "out of memory"); + } else { + remote->remote = found; + add_tail(this_list, &remote->n); + } + } + + /* Free text token. */ + free($2.t); + } + ; + +zone_start: TEXT { + this_zone = malloc(sizeof(conf_zone_t)); + memset(this_zone, 0, sizeof(conf_zone_t)); + this_zone->enable_checks = -1; // Default policy applies + this_zone->notify_timeout = -1; // Default policy applies + this_zone->notify_retries = 0; // Default policy applies + this_zone->ixfr_fslimit = -1; // Default policy applies + this_zone->dbsync_timeout = -1; // Default policy applies + this_zone->name = $1.t; + + // Append mising dot to ensure FQDN + size_t nlen = strlen(this_zone->name); + if (this_zone->name[nlen - 1] != '.') { + this_zone->name = realloc(this_zone->name, nlen + 1 + 1); + strcat(this_zone->name, "."); + } + + /* Check domain name. */ + knot_dname_t *dn = knot_dname_new_from_str(this_zone->name, + nlen + 1, + 0); + if (dn == 0) { + free(this_zone->name); + free(this_zone); + cf_error(scanner, "invalid zone origin"); + } else { + /* Directly discard dname, won't be needed. */ + knot_dname_free(&dn); + add_tail(&new_config->zones, &this_zone->n); + ++new_config->zones_count; + + /* Initialize ACL lists. */ + init_list(&this_zone->acl.xfr_in); + init_list(&this_zone->acl.xfr_out); + init_list(&this_zone->acl.notify_in); + init_list(&this_zone->acl.notify_out); + } + } + ; + +zone: + zone_start '{' + | zone zone_acl '}' + | zone zone_acl_list + | zone FILENAME TEXT ';' { this_zone->file = $3.t; } + | zone SEMANTIC_CHECKS BOOL ';' { this_zone->enable_checks = $3.i; } + | zone DBSYNC_TIMEOUT NUM ';' { this_zone->dbsync_timeout = $3.i; } + | zone DBSYNC_TIMEOUT INTERVAL ';' { this_zone->dbsync_timeout = $3.i; } + | zone IXFR_FSLIMIT SIZE ';' { new_config->ixfr_fslimit = $3.l; } + | zone IXFR_FSLIMIT NUM ';' { this_zone->ixfr_fslimit = $3.i; } + | zone NOTIFY_RETRIES NUM ';' { + if ($3.i < 1) { + cf_error(scanner, "notify retries must be positive integer"); + } else { + this_zone->notify_retries = $3.i; + } + } + | zone NOTIFY_TIMEOUT NUM ';' { + if ($3.i < 1) { + cf_error(scanner, "notify timeout must be positive integer"); + } else { + this_zone->notify_timeout = $3.i; + } + } + ; + +zones: + ZONES '{' + | zones zone '}' + | zones SEMANTIC_CHECKS BOOL ';' { new_config->zone_checks = $3.i; } + | zones IXFR_FSLIMIT SIZE ';' { new_config->ixfr_fslimit = $3.l; } + | zones IXFR_FSLIMIT NUM ';' { new_config->ixfr_fslimit = $3.i; } + | zones NOTIFY_RETRIES NUM ';' { + if ($3.i < 1) { + cf_error(scanner, "notify retries must be positive integer"); + } else { + new_config->notify_retries = $3.i; + } + } + | zones NOTIFY_TIMEOUT NUM ';' { + if ($3.i < 1) { + cf_error(scanner, "notify timeout must be positive integer"); + } else { + new_config->notify_timeout = $3.i; + } + } + | zones DBSYNC_TIMEOUT NUM ';' { + if ($3.i < 1) { + cf_error(scanner, "zonefile sync timeout must be positive integer"); + } else { + new_config->dbsync_timeout = $3.i; + } + } + | zones DBSYNC_TIMEOUT INTERVAL ';' { new_config->dbsync_timeout = $3.i; } + ; + +log_prios_start: { + this_logmap = malloc(sizeof(conf_log_map_t)); + this_logmap->source = 0; + this_logmap->prios = 0; + add_tail(&this_log->map, &this_logmap->n); +} +; + +log_prios: + log_prios_start + | log_prios LOG_LEVEL ',' { this_logmap->prios |= $2.i; } + | log_prios LOG_LEVEL ';' { this_logmap->prios |= $2.i; } + ; + +log_src: + | log_src LOG_SRC log_prios { + this_logmap->source = $2.i; + this_logmap = 0; + } + ; + +log_dest: LOG_DEST { + /* Find already existing rule. */ + this_log = 0; + node *n = 0; + WALK_LIST(n, new_config->logs) { + conf_log_t* log = (conf_log_t*)n; + if (log->type == $1.i) { + this_log = log; + break; + } + } + + if (!this_log) { + this_log = malloc(sizeof(conf_log_t)); + this_log->type = $1.i; + this_log->file = 0; + init_list(&this_log->map); + add_tail(&new_config->logs, &this_log->n); + ++new_config->logs_count; + } +} +; + +log_file: FILENAME TEXT { + /* Find already existing rule. */ + this_log = 0; + node *n = 0; + WALK_LIST(n, new_config->logs) { + conf_log_t* log = (conf_log_t*)n; + if (log->type == LOGT_FILE) { + if (strcmp($2.t, log->file) == 0) { + this_log = log; + free($2.t); + break; + } + } + } + + /* Create new rule. */ + if (!this_log) { + this_log = malloc(sizeof(conf_log_t)); + this_log->type = LOGT_FILE; + this_log->file = strcpath($2.t); + init_list(&this_log->map); + add_tail(&new_config->logs, &this_log->n); + ++new_config->logs_count; + } +} +; + +log_end: { +} +; + +log_start: + | log_start log_dest '{' log_src '}' + | log_start log_file '{' log_src '}' + ; + +log: LOG '{' log_start log_end; + + +conf: ';' | system '}' | interfaces '}' | keys '}' | remotes '}' | zones '}' | log '}'; + +%% + diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c new file mode 100644 index 0000000..4e2d665 --- /dev/null +++ b/src/knot/conf/conf.c @@ -0,0 +1,715 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <pthread.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +#include <urcu.h> +#include "knot/conf/conf.h" +#include "knot/common.h" +#include "knot/other/error.h" + +/* + * Defaults. + */ + +/*! \brief Default config paths. */ +static const char *DEFAULT_CONFIG[] = { + SYSCONFDIR "/" "knot.conf", +}; + +#define DEFAULT_CONF_COUNT 1 /*!< \brief Number of default config paths. */ + +/* + * Utilities. + */ + +/*! + * \brief Recursively create directories. + * + * Similar to "mkdir -p". + * * \retval 0 on success. + * \retval <0 on error. + */ +static int rmkdir(char *path, int mode) +{ + char *p = path; + while((p = strchr(p + 1, '/'))) { + *p = '\0'; + mkdir(path, mode); + *p = '/'; + } + + // Final path + return mkdir(path, mode); +} + +/* Prototypes for cf-parse.y */ +extern int cf_parse(void *scanner); +extern int cf_get_lineno(void *scanner); +extern char *cf_get_text(void *scanner); +extern int cf_lex_init(void *scanner); +extern void cf_set_in(FILE *f, void *scanner); +extern void cf_lex_destroy(void *scanner); +extern void switch_input(const char *str, void *scanner); + +conf_t *new_config = 0; /*!< \brief Currently parsed config. */ +static volatile int _parser_res = 0; /*!< \brief Parser result. */ +static pthread_mutex_t _parser_lock = PTHREAD_MUTEX_INITIALIZER; + +/*! \brief Config error report. */ +void cf_error(void *scanner, const char *msg) +{ + int lineno = -1; + char *text = "???"; + if (scanner) { + lineno = cf_get_lineno(scanner); + text = (char *)cf_get_text(scanner); + } + + log_server_error("Config '%s' - %s on line %d (current token '%s').\n", + new_config->filename, msg, lineno, text); + + + _parser_res = KNOTD_EPARSEFAIL; +} + +/* + * Config helper functions. + */ + +/*! \brief Free TSIG key. */ +static void key_free(conf_key_t *k) +{ + /* Secure erase. */ + if (k->k.secret) { + memset(k->k.secret, 0, strlen(k->k.secret)); + } + free(k->k.secret); + knot_dname_free(&k->k.name); + free(k); +} + +/*! \brief Free config interfaces. */ +static void iface_free(conf_iface_t *iface) +{ + if (!iface) { + return; + } + + free(iface->name); + free(iface->address); + free(iface); +} + +/*! \brief Free config logs. */ +static void log_free(conf_log_t *log) +{ + if (!log) { + return; + } + + if (log->file) { + free(log->file); + } + + /* Free loglevel mapping. */ + node *n = 0, *nxt = 0; + WALK_LIST_DELSAFE(n, nxt, log->map) { + free((conf_log_map_t*)n); + } + + free(log); +} + +/*! \brief Free config zones. */ +static void zone_free(conf_zone_t *zone) +{ + if (!zone) { + return; + } + + /* Free ACL lists. */ + WALK_LIST_FREE(zone->acl.xfr_in); + WALK_LIST_FREE(zone->acl.xfr_out); + WALK_LIST_FREE(zone->acl.notify_in); + WALK_LIST_FREE(zone->acl.notify_out); + + free(zone->name); + free(zone->file); + free(zone->db); + free(zone->ixfr_db); + free(zone); +} + +/*! + * \brief Call config hooks that need updating. + * + * This function is called automatically after config update. + * + * \todo Selective hooks. + */ +static void conf_update_hooks(conf_t *conf) +{ + node *n = 0; + conf->_touched = CONF_ALL; + WALK_LIST (n, conf->hooks) { + conf_hook_t *hook = (conf_hook_t*)n; + if ((hook->sections & conf->_touched) && hook->update) { + hook->update(conf, hook->data); + } + } +} + +/*! + * \brief Process parsed configuration. + * + * This functions is called automatically after config parsing. + * It is needed to setup needed primitives, check and update paths. + * + * \retval 0 on success. + * \retval <0 on error. + */ +static int conf_process(conf_t *conf) +{ + // Check + if (!conf->storage) { + conf->storage = strdup("/var/lib/"PROJECT_EXEC); + } + + // Normalize paths + conf->storage = strcpath(conf->storage); + struct stat st; + if (stat(conf->storage, &st) != 0) { + rmkdir(conf->storage, S_IRWXU); + } + + // Create PID file + conf->pidfile = strcdup(conf->storage, "/" PID_FILE); + + // Postprocess zones + node *n = 0; + WALK_LIST (n, conf->zones) { + conf_zone_t *zone = (conf_zone_t*)n; + + // Default policy for dbsync timeout + if (zone->dbsync_timeout < 0) { + zone->dbsync_timeout = conf->dbsync_timeout; + } + + // Default policy for semantic checks + if (zone->enable_checks < 0) { + zone->enable_checks = conf->zone_checks; + } + + // Default policy for NOTIFY retries + if (zone->notify_retries <= 0) { + zone->notify_retries = conf->notify_retries; + } + + // Default policy for NOTIFY timeout + if (zone->notify_timeout <= 0) { + zone->notify_timeout = conf->notify_timeout; + } + + // Default policy for IXFR FSLIMIT + if (zone->ixfr_fslimit == 0) { + zone->ixfr_fslimit = conf->ixfr_fslimit; + } + + // Normalize zone filename + zone->file = strcpath(zone->file); + + // Create zone db filename + size_t stor_len = strlen(conf->storage); + size_t size = stor_len + strlen(zone->name) + 4; // db/,\0 + char *dest = malloc(size); + strcpy(dest, conf->storage); + if (conf->storage[stor_len - 1] != '/') { + strcat(dest, "/"); + } + + strcat(dest, zone->name); + strcat(dest, "db"); + zone->db = dest; + + // Create IXFR db filename + stor_len = strlen(conf->storage); + size = stor_len + strlen(zone->name) + 9; // diff.db/,\0 + dest = malloc(size); + strcpy(dest, conf->storage); + if (conf->storage[stor_len - 1] != '/') { + strcat(dest, "/"); + } + + strcat(dest, zone->name); + strcat(dest, "diff.db"); + zone->ixfr_db = dest; + } + + return 0; +} + +/* + * Singletion configuration API. + */ + +conf_t *s_config = 0; /*! \brief Singleton config instance. */ + +/*! \brief Singleton config constructor (automatically called on load). */ +void __attribute__ ((constructor)) conf_init() +{ + // Create new config + s_config = conf_new(0); + + /* Create default interface. */ + conf_iface_t * iface = malloc(sizeof(conf_iface_t)); + memset(iface, 0, sizeof(conf_iface_t)); + iface->name = strdup("any"); + iface->address = strdup("0.0.0.0"); + iface->port = CONFIG_DEFAULT_PORT; + add_tail(&s_config->ifaces, &iface->n); + ++s_config->ifaces_count; + + /* Create default storage. */ + s_config->storage = strdup("/var/lib/"PROJECT_EXEC); + + /* Create default logs. */ + + /* Syslog */ + conf_log_t *log = malloc(sizeof(conf_log_t)); + log->type = LOGT_SYSLOG; + log->file = 0; + init_list(&log->map); + + conf_log_map_t *map = malloc(sizeof(conf_log_map_t)); + map->source = LOG_ANY; + map->prios = LOG_MASK(LOG_WARNING)|LOG_MASK(LOG_ERR); + add_tail(&log->map, &map->n); + add_tail(&s_config->logs, &log->n); + ++s_config->logs_count; + + /* Stderr */ + log = malloc(sizeof(conf_log_t)); + log->type = LOGT_STDERR; + log->file = 0; + init_list(&log->map); + + map = malloc(sizeof(conf_log_map_t)); + map->source = LOG_ANY; + map->prios = LOG_MASK(LOG_WARNING)|LOG_MASK(LOG_ERR); + add_tail(&log->map, &map->n); + add_tail(&s_config->logs, &log->n); + ++s_config->logs_count; + + /* Process config. */ + conf_process(s_config); +} + +/*! \brief Singleton config destructor (automatically called on exit). */ +void __attribute__ ((destructor)) conf_deinit() +{ + if (s_config) { + conf_free(s_config); + s_config = 0; + } +} + +/*! + * \brief Parse config (from file). + * \return yyparser return value. + */ +static int conf_fparser(conf_t *conf) +{ + if (!conf->filename) { + return KNOTD_EINVAL; + } + + int ret = KNOTD_EOK; + pthread_mutex_lock(&_parser_lock); + // { + // Hook new configuration + new_config = conf; + FILE *f = fopen(conf->filename, "r"); + if (f == 0) { + pthread_mutex_unlock(&_parser_lock); + return KNOTD_ENOENT; + } + + // Parse config + _parser_res = KNOTD_EOK; + new_config->filename = conf->filename; + void *sc = NULL; + cf_lex_init(&sc); + cf_set_in(f, sc); + cf_parse(sc); + cf_lex_destroy(sc); + ret = _parser_res; + fclose(f); + // } + pthread_mutex_unlock(&_parser_lock); + return ret; +} + +/*! \brief Parse config (from string). + * \return yyparser return value. + */ +static int conf_strparser(conf_t *conf, const char *src) +{ + if (!src) { + return KNOTD_EINVAL; + } + + int ret = KNOTD_EOK; + pthread_mutex_lock(&_parser_lock); + // { + // Hook new configuration + new_config = conf; + if (src == 0) { + pthread_mutex_unlock(&_parser_lock); + return KNOTD_ENOENT; + } + + // Parse config + _parser_res = KNOTD_EOK; + char *oldfn = new_config->filename; + new_config->filename = "(stdin)"; + void *sc = NULL; + cf_lex_init(&sc); + switch_input(src, sc); + cf_parse(sc); + cf_lex_destroy(sc); + new_config->filename = oldfn; + ret = _parser_res; + // } + pthread_mutex_unlock(&_parser_lock); + return ret; +} + +/* + * API functions. + */ + +conf_t *conf_new(const char* path) +{ + conf_t *c = malloc(sizeof(conf_t)); + memset(c, 0, sizeof(conf_t)); + + // Add path + if (path) { + c->filename = strdup(path); + } + + // Initialize lists + init_list(&c->logs); + init_list(&c->ifaces); + init_list(&c->zones); + init_list(&c->hooks); + init_list(&c->remotes); + init_list(&c->keys); + + // Defaults + c->zone_checks = 0; + c->notify_retries = CONFIG_NOTIFY_RETRIES; + c->notify_timeout = CONFIG_NOTIFY_TIMEOUT; + c->dbsync_timeout = CONFIG_DBSYNC_TIMEOUT; + c->ixfr_fslimit = -1; + + return c; +} + +int conf_add_hook(conf_t * conf, int sections, + int (*on_update)(const conf_t*, void*), void *data) +{ + conf_hook_t *hook = malloc(sizeof(conf_hook_t)); + if (!hook) { + return KNOTD_ENOMEM; + } + + hook->sections = sections; + hook->update = on_update; + hook->data = data; + add_tail(&conf->hooks, &hook->n); + ++conf->hooks_count; + + return KNOTD_EOK; +} + +int conf_parse(conf_t *conf) +{ + /* Parse file. */ + int ret = conf_fparser(conf); + + /* Postprocess config. */ + conf_process(conf); + + /* Update hooks. */ + conf_update_hooks(conf); + + if (ret < 0) { + return KNOTD_EPARSEFAIL; + } + + return KNOTD_EOK; +} + +int conf_parse_str(conf_t *conf, const char* src) +{ + /* Parse config from string. */ + int ret = conf_strparser(conf, src); + + /* Postprocess config. */ + conf_process(conf); + + /* Update hooks */ + conf_update_hooks(conf); + + if (ret < 0) { + return KNOTD_EPARSEFAIL; + } + + return KNOTD_EOK; +} + +void conf_truncate(conf_t *conf, int unload_hooks) +{ + if (!conf) { + return; + } + + node *n = 0, *nxt = 0; + + // Unload hooks + if (unload_hooks) { + WALK_LIST_DELSAFE(n, nxt, conf->hooks) { + //! \todo call hook unload. + free((conf_hook_t*)n); + } + conf->hooks_count = 0; + init_list(&conf->hooks); + } + + // Free keys + WALK_LIST_DELSAFE(n, nxt, conf->keys) { + key_free((conf_key_t *)n); + } + + // Free interfaces + WALK_LIST_DELSAFE(n, nxt, conf->ifaces) { + iface_free((conf_iface_t*)n); + } + conf->ifaces_count = 0; + init_list(&conf->ifaces); + + // Free logs + WALK_LIST_DELSAFE(n, nxt, conf->logs) { + log_free((conf_log_t*)n); + } + conf->logs_count = 0; + init_list(&conf->logs); + + // Free remotes + WALK_LIST_DELSAFE(n, nxt, conf->remotes) { + iface_free((conf_iface_t*)n); + } + conf->remotes_count = 0; + init_list(&conf->remotes); + + // Free zones + WALK_LIST_DELSAFE(n, nxt, conf->zones) { + zone_free((conf_zone_t*)n); + } + conf->zones_count = 0; + init_list(&conf->zones); + + if (conf->filename) { + free(conf->filename); + conf->filename = 0; + } + if (conf->identity) { + free(conf->identity); + conf->identity = 0; + } + if (conf->version) { + free(conf->version); + conf->version = 0; + } + if (conf->storage) { + free(conf->storage); + conf->storage = 0; + } + if (conf->pidfile) { + free(conf->pidfile); + conf->pidfile = 0; + } +} + +void conf_free(conf_t *conf) +{ + if (!conf) { + return; + } + + // Truncate config + conf_truncate(conf, 1); + + // Free config + free(conf); +} + +char* conf_find_default() +{ + /* Try sequentially each default path. */ + char *path = 0; + for (int i = 0; i < DEFAULT_CONF_COUNT; ++i) { + path = strcpath(strdup(DEFAULT_CONFIG[i])); + + /* Break, if the path exists. */ + struct stat st; + if (stat(path, &st) == 0) { + break; + } + + log_server_notice("Config '%s' does not exist.\n", + path); + + /* Keep the last item. */ + if (i < DEFAULT_CONF_COUNT - 1) { + free(path); + path = 0; + } + } + + log_server_info("Using '%s' as default configuration.\n", + path); + return path; +} + +int conf_open(const char* path) +{ + /* Check path. */ + if (!path) { + return KNOTD_EINVAL; + } + + /* Check if exists. */ + FILE *fp = fopen(path, "r"); + if (fp == 0) { + return KNOTD_ENOENT; + } else { + fclose(fp); + } + + /* Create new config. */ + conf_t *nconf = conf_new(path); + + /* Parse config. */ + int ret = conf_fparser(nconf); + if (ret != KNOTD_EOK) { + conf_free(nconf); + return ret; + } + + /* Replace current config. */ + conf_t *oldconf = rcu_xchg_pointer(&s_config, nconf); + + /* Copy hooks. */ + if (oldconf) { + node *n = 0, *nxt = 0; + WALK_LIST_DELSAFE (n, nxt, oldconf->hooks) { + conf_hook_t *hook = (conf_hook_t*)n; + conf_add_hook(nconf, hook->sections, + hook->update, hook->data); + } + } + + /* Postprocess config. */ + conf_process(nconf); + + /* Synchronize. */ + synchronize_rcu(); + + /* Free old config. */ + if (oldconf) { + conf_free(oldconf); + } + + /* Update hooks. */ + conf_update_hooks(nconf); + + return KNOTD_EOK; +} + +char* strcdup(const char *s1, const char *s2) +{ + if (!s1 || !s2) { + return 0; + } + + size_t slen = strlen(s1); + size_t nlen = slen + strlen(s2) + 1; + char* dst = malloc(nlen); + if (!dst) { + return 0; + } + + memcpy(dst, s1, slen); + strcpy(dst + slen, s2); // With trailing '\0' + return dst; +} + +char* strcpath(char *path) +{ + // NULL path + if (!path) { + return 0; + } + + // Remote trailing slash + size_t plen = strlen(path); + if (path[plen - 1] == '/') { + path[--plen] = '\0'; + } + + // Expand '~' + char* tild_p = strchr(path,'~'); + if (tild_p != 0) { + // Get full path + char *tild_exp = getenv("HOME"); + size_t tild_len = strlen(tild_exp); + if (tild_exp[tild_len - 1] == '/') { + tild_exp[--tild_len] = '\0'; + } + + // Expand + char *npath = malloc(plen + tild_len + 1); + npath[0] = '\0'; + strncpy(npath, path, (size_t)(tild_p - path)); + strcat(npath, tild_exp); + strcat(npath, tild_p + 1); + free(path); + path = npath; + } + + return path; +} + diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h new file mode 100644 index 0000000..ef35e41 --- /dev/null +++ b/src/knot/conf/conf.h @@ -0,0 +1,361 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file conf.h + * + * \author Ondrej Sury <ondrej.sury@nic.cz> + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Server configuration structures and API. + * + * \addtogroup config + * @{ + */ + +#ifndef _KNOTD_CONF_H_ +#define _KNOTD_CONF_H_ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <urcu.h> + +#include "libknot/dname.h" +#include "libknot/util/descriptor.h" +#include "libknot/tsig.h" +#include "common/lists.h" +#include "knot/other/log.h" + +/* Constants. */ +#define CONFIG_DEFAULT_PORT 53 +#define CONFIG_NOTIFY_RETRIES 5 /*!< 5 retries (suggested in RFC1996) */ +#define CONFIG_NOTIFY_TIMEOUT 60 /*!< 60s (suggested in RFC1996) */ +#define CONFIG_DBSYNC_TIMEOUT (60*60) /*!< 1 hour. */ + +/*! + * \brief Configuration for the interface + * + * This structure holds the configuration of the various interfaces + * used in the configuration. Same interface could be used for + * listening and outgoing function. + */ +typedef struct conf_iface_t { + node n; + char *name; /*!< Internal name for the interface. */ + char *address; /*!< IP (IPv4/v6) address for this interface */ + int port; /*!< Port number for this interface */ + int family; /*!< Address family. */ + knot_key_t *key; /*!< TSIG key (only valid for remotes). */ +} conf_iface_t; + +/*! + * \brief Node containing poiner to remote. + * + * Used for zone ACL lists to prevent node duplication. + */ +typedef struct conf_remote_t { + node n; /*!< List node. */ + conf_iface_t *remote; /*!< Pointer to interface descriptor. */ +} conf_remote_t; + +/*! + * \brief Zone configuration. + * + * This structure holds the configuration for the zone. In it's most + * basic form, it just allows to read a zone from the specific + * location on the disk. It also allows to have multiple DNS servers + * as a source for the zone transfer and multiple DNS servers to allow + * zone transfers. Same logic applies for the NOTIFY. + * + * \todo Missing XFR type (AXFR/IXFR/IXFR-ONLY) for each server. + */ +typedef struct conf_zone_t { + node n; + char *name; /*!< Zone name. */ + enum knot_rr_class cls; /*!< Zone class (IN or CH). */ + char *file; /*!< Path to a zone file. */ + char *db; /*!< Path to a database file. */ + char *ixfr_db; /*!< Path to a IXFR database file. */ + size_t ixfr_fslimit; /*!< File size limit for IXFR journal. */ + int dbsync_timeout; /*!< Interval between syncing to zonefile.*/ + int enable_checks; /*!< Semantic checks for parser.*/ + int notify_retries; /*!< NOTIFY query retries. */ + int notify_timeout; /*!< Timeout for NOTIFY response (s). */ + struct { + list xfr_in; /*!< Remotes accepted for for xfr-in.*/ + list xfr_out; /*!< Remotes accepted for xfr-out.*/ + list notify_in; /*!< Remotes accepted for notify-in.*/ + list notify_out; /*!< Remotes accepted for notify-out.*/ + } acl; +} conf_zone_t; + +/*! + * \brief Mapping of loglevels to message sources. + */ +typedef struct conf_log_map_t { + node n; + int source; /*!< Log message source mask. */ + int prios; /*!< Log priorities mask. */ +} conf_log_map_t; + +/*! + * \brief Log facility descriptor. + */ +typedef struct conf_log_t { + node n; + logtype_t type; /*!< Type of the log (SYSLOG/STDERR/FILE). */ + char *file; /*!< Filename in case of LOG_FILE, else NULL. */ + list map; /*!< Log levels mapping. */ +} conf_log_t; + +/*! + * \brief Configuration sections. + */ +typedef enum conf_section_t { + CONF_LOG = 1 << 0, /*!< Log section. */ + CONF_IFACES = 1 << 1, /*!< Interfaces. */ + CONF_ZONES = 1 << 2, /*!< Zones. */ + CONF_OTHER = 1 << 3, /*!< Other sections. */ + CONF_ALL = ~0 /*!< All sections. */ +} conf_section_t; + +/*! + * \brief TSIG key list item. + */ +typedef struct conf_key_t { + node n; + knot_key_t k; +} conf_key_t; + +/*! + * \brief Main config structure. + * + * Configuration structure. + */ +typedef struct conf_t { + /* + * System + */ + char *filename; /*!< Name of the config file. */ + char *identity; /*!< Identity to return on CH TXT id.server. */ + char *version; /*!< Version for CH TXT version.{bind|server} */ + char *storage; /*!< Persistent storage path for databases and such. */ + char *pidfile; /*!< PID file path. */ + int workers; /*!< Number of workers per interface. */ + + /* + * Log + */ + list logs; /*!< List of logging facilites. */ + int logs_count; /*!< Count of logging facilities. */ + + /* + * Interfaces + */ + list ifaces; /*!< List of interfaces. */ + int ifaces_count; /*!< Count of interfaces. */ + + /* + * TSIG keys + */ + list keys; /*!< List of TSIG keys. */ + int key_count; /*!< Count of TSIG keys. */ + + /* + * Remotes + */ + list remotes; /*!< List of remotes. */ + int remotes_count;/*!< Count of remotes. */ + + /* + * Zones + */ + list zones; /*!< List of zones. */ + int zones_count; /*!< Count of zones. */ + int zone_checks; /*!< Semantic checks for parser.*/ + int notify_retries; /*!< NOTIFY query retries. */ + int notify_timeout; /*!< Timeout for NOTIFY response in seconds. */ + int dbsync_timeout; /*!< Default interval between syncing to zonefile.*/ + size_t ixfr_fslimit; /*!< File size limit for IXFR journal. */ + + /* + * Implementation specifics + */ + list hooks; /*!< List of config hooks. */ + int hooks_count; /*!< Count of config hooks. */ + int _touched; /*!< Bitmask of sections touched by last update. */ +} conf_t; + +/*! + * \brief Config hook prototype. + */ +typedef struct conf_hook_t { + node n; + int sections; /*!< Bitmask of watched sections. */ + int (*update)(const conf_t*, void*); /*!< Function executed on config load. */ + void *data; +} conf_hook_t; + +/* + * Specific configuration API. + */ + +/*! + * \brief Create new configuration structure. + * + * \param path Path to configuration file. + * \retval new structure if successful. + * \retval NULL on error. + */ +conf_t *conf_new(const char* path); + +/*! + * \brief Register on-update callback. + * + * \param conf Configuration context. + * \param sections Bitmask of watched sections or CONF_ALL. + * \param on_update Callback. + * \param data User specified data for hook. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_ENOMEM out of memory error. + */ +int conf_add_hook(conf_t * conf, int sections, + int (*on_update)(const conf_t*, void*), void *data); + +/*! + * \brief Parse configuration from associated file. + * + * \note Registered callbacks may be executed if applicable. + * + * \param conf Configuration context. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EPARSEFAIL on parser error. + */ +int conf_parse(conf_t *conf); + +/*! + * \brief Parse configuration from string. + * + * \note Registered callbacks may be executed if applicable. + * + * \param conf Configuration context. + * \param src Source string. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EPARSEFAIL on parser error. + */ +int conf_parse_str(conf_t *conf, const char* src); + +/*! + * \brief Truncate configuration context. + * + * \param conf Configuration context. + * \param unload_hooks If true, hooks will be unregistered and freed as well. + */ +void conf_truncate(conf_t *conf, int unload_hooks); + +/*! + * \brief Destroy configuration context. + * + * \param conf Configuration context. + */ +void conf_free(conf_t *conf); + +/* + * Singleton configuration API. + */ + +/*! + * \brief Find implicit configuration file. + * + * Ordering: + * 1. ~/.knot/knot.conf (if exists) + * 2. /etc/knot/knot.conf (fallback) + * + * \return Path to implicit configuration file. + */ +char* conf_find_default(); + +/*! + * \brief Open singleton configuration from file. + * + * \note Registered callbacks may be executed if applicable. + * + * \param path Path to configuration file. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on null path. + * \retval KNOTD_ENOENT if the path doesn't exist. + */ +int conf_open(const char* path); + +/* Imported singleton */ +extern conf_t *s_config; + +/*! + * \brief Singleton configuration context accessor. + * + * \return Configuration context. + */ +static inline conf_t* conf() { + return s_config; // Inline for performance reasons. +} + +/*! + * \brief Lock configuration for reading. + * + * \return Configuration context. + */ +static inline void conf_read_lock() { + rcu_read_lock(); +} + +/*! + * \brief Unlock configuration for reading. + */ +static inline void conf_read_unlock() { + rcu_read_unlock(); +} + +/* + * Utilities. + */ + +/*! + * \brief Create new string from a concatenation of s1 and s2. + * + * \param s1 First string. + * \param s2 Second string. + * + * \retval Newly allocated string on success. + * \retval NULL on error. + */ +char* strcdup(const char *s1, const char *s2); + +/*! + * \brief Normalize file path and expand '~' placeholders. + * + * \note Old pointer may be freed. + * + * \retval Pointer to normalized path. + */ +char* strcpath(char *path); + +#endif /* _KNOTD_CONF_H_ */ + +/*! @} */ diff --git a/src/knot/conf/logconf.c b/src/knot/conf/logconf.c new file mode 100644 index 0000000..a57afd9 --- /dev/null +++ b/src/knot/conf/logconf.c @@ -0,0 +1,102 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> + +#include "knot/other/debug.h" +#include "knot/conf/logconf.h" +#include "knot/conf/conf.h" +#include "knot/other/log.h" +#include "knot/other/error.h" +#include "common/lists.h" +#include "knot/common.h" + +int log_conf_hook(const struct conf_t *conf, void *data) +{ + // Data not used + int ret = 0; + UNUSED(data); + + // Check if log declaration exists, otherwise ignore + if (conf->logs_count < 1) { + return KNOTD_EINVAL; + } + + // Find maximum log facility id + node *n = 0; size_t files = 0; + WALK_LIST(n, conf->logs) { + conf_log_t* log = (conf_log_t*)n; + if (log->type == LOGT_FILE) { + ++files; + } + } + + // Initialize logsystem + log_truncate(); + if ((ret = log_setup(files)) < 0) { + return ret; + } + + // Setup logs + int loaded_sections = 0; + n = 0; + WALK_LIST(n, conf->logs) { + + // Calculate offset + conf_log_t* log = (conf_log_t*)n; + int facility = log->type; + if (facility == LOGT_FILE) { + facility = log_open_file(log->file); + if (facility < 0) { + log_server_error("Failed to open " + "logfile '%s'.\n", log->file); + continue; + } + } + + // Auto-assign fatal errors to syslog or stderr + if (facility <= LOGT_STDERR) { + int mask = LOG_MASK(LOG_FATAL); + log_levels_add(facility, LOG_ANY, mask); + loaded_sections |= 1 << facility; + } + + // Setup sources mapping + node *m = 0; + WALK_LIST(m, log->map) { + + // Assign mapped level + conf_log_map_t *map = (conf_log_map_t*)m; + log_levels_add(facility, map->source, map->prios); + } + } + + // Load defaults for syslog or stderr + int bmask = LOG_MASK(LOG_ERR)|LOG_MASK(LOG_FATAL); + if (!(loaded_sections & (1 << LOGT_SYSLOG))) { + log_levels_set(LOGT_SYSLOG, LOG_ANY, bmask); + } + if (!(loaded_sections & (1 << LOGT_STDERR))) { + log_levels_set(LOGT_STDERR, LOG_ANY, bmask); + } + + return KNOTD_EOK; +} + diff --git a/src/knot/conf/logconf.h b/src/knot/conf/logconf.h new file mode 100644 index 0000000..7b9e054 --- /dev/null +++ b/src/knot/conf/logconf.h @@ -0,0 +1,45 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file log.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Logging facility (configuration file interface). + * + * \addtogroup logging + * @{ + */ + +#ifndef _KNOTD_LOGCONF_H_ +#define _KNOTD_LOGCONF_H_ + +struct conf_t; + +/*! + * \brief Setup logging facilities from config. + * + * \see syslog.h + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_ENOMEM out of memory error. + */ +int log_conf_hook(const struct conf_t *conf, void *data); + +#endif /* _KNOTD_LOGCONF_H_ */ + +/*! @} */ diff --git a/src/knot/ctl/knotc_main.c b/src/knot/ctl/knotc_main.c new file mode 100644 index 0000000..16c3863 --- /dev/null +++ b/src/knot/ctl/knotc_main.c @@ -0,0 +1,658 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/wait.h> +#include <time.h> +#include <sys/select.h> +#include <sys/stat.h> +#include <getopt.h> + +#include "knot/common.h" +#include "knot/other/error.h" +#include "knot/ctl/process.h" +#include "knot/conf/conf.h" +#include "knot/conf/logconf.h" +#include "knot/zone/zone-load.h" + +/*! \brief Controller constants. */ +enum knotc_constants_t { + WAITPID_TIMEOUT = 10 /*!< \brief Timeout for waiting for process. */ +}; + +/*! \brief Print help. */ +void help(int argc, char **argv) +{ + printf("Usage: %sc [parameters] start|stop|restart|reload|running|" + "compile\n", PACKAGE_NAME); + printf("Parameters:\n" + " -c [file], --config=[file] Select configuration file.\n" + " -j [num], --jobs=[num] Number of parallel tasks to run (only for 'compile').\n" + " -f, --force Force operation - override some checks.\n" + " -v, --verbose Verbose mode - additional runtime information.\n" + " -V, --version Print %s server version.\n" + " -w, --wait Wait for the server to finish start/stop operations.\n" + " -i, --interactive Interactive mode (do not daemonize).\n" + " -h, --help Print help and usage.\n", + PACKAGE_NAME); + printf("Actions:\n" + " start Start %s server zone (no-op if running).\n" + " stop Stop %s server (no-op if not running).\n" + " restart Stops and then starts %s server.\n" + " reload Reload %s configuration and compiled zones.\n" + " running check if server is running.\n" + "\n" + " compile Compile zone file.\n", + PACKAGE_NAME, PACKAGE_NAME, PACKAGE_NAME, PACKAGE_NAME); +} + +/*! + * \brief Check if the zone needs recompilation. + * + * \param db Path to zone db file. + * \param source Path to zone source file. + * + * \retval KNOTD_EOK if up to date. + * \retval KNOTD_ERROR if needs recompilation. + */ +int check_zone(const char *db, const char* source) +{ + /* Check zonefile. */ + struct stat st; + if (stat(source, &st) != 0) { + fprintf(stderr, "Zone file '%s' doesn't exist.\n", source); + return KNOTD_ENOENT; + } + + /* Read zonedb header. */ + zloader_t *zl = 0; + knot_zload_open(&zl, db); + if (!zl) { + return KNOTD_ERROR; + } + + /* Check source files and mtime. */ + int ret = KNOTD_ERROR; + int src_changed = strcmp(source, zl->source) != 0; + if (!src_changed && !knot_zload_needs_update(zl)) { + ret = KNOTD_EOK; + } + + knot_zload_close(zl); + return ret; +} + +pid_t wait_cmd(pid_t proc, int *rc) +{ + /* Wait for finish. */ + sigset_t newset; + sigfillset(&newset); + sigprocmask(SIG_BLOCK, &newset, 0); + proc = waitpid(proc, rc, 0); + sigprocmask(SIG_UNBLOCK, &newset, 0); + return proc; +} + +pid_t start_cmd(const char *argv[], int argc) +{ + pid_t chproc = fork(); + if (chproc == 0) { + + /* Duplicate, it doesn't run from stack address anyway. */ + char **args = malloc((argc + 1) * sizeof(char*)); + memset(args, 0, (argc + 1) * sizeof(char*)); + int ci = 0; + for (int i = 0; i < argc; ++i) { + if (strlen(argv[i]) > 0) { + args[ci++] = strdup(argv[i]); + } + } + args[ci] = 0; + + /* Execute command. */ + fflush(stdout); + fflush(stderr); + execvp(args[0], args); + + /* Execute failed. */ + fprintf(stderr, "Failed to run executable '%s'\n", args[0]); + for (int i = 0; i < argc; ++i) { + free(args[i]); + } + free(args); + + exit(1); + return -1; + } + + return chproc; +} + +int exec_cmd(const char *argv[], int argc) +{ + int ret = 0; + pid_t proc = start_cmd(argv, argc); + wait_cmd(proc, &ret); + return ret; +} + +/*! \brief Zone compiler task. */ +typedef struct { + conf_zone_t *zone; + pid_t proc; +} knotc_zctask_t; + +/*! \brief Create set of watched tasks. */ +knotc_zctask_t *zctask_create(int count) +{ + if (count <= 0) { + return 0; + } + + knotc_zctask_t *t = malloc(count * sizeof(knotc_zctask_t)); + for (unsigned i = 0; i < count; ++i) { + t[i].proc = -1; + t[i].zone = 0; + } + + return t; +} + +/*! \brief Wait for single task to finish. */ +int zctask_wait(knotc_zctask_t *tasks, int count) +{ + /* Wait for children to finish. */ + int rc = 0; + pid_t pid = wait_cmd(-1, &rc); + + /* Find task. */ + conf_zone_t *z = 0; + for (unsigned i = 0; i < count; ++i) { + if (tasks[i].proc == pid) { + tasks[i].proc = -1; /* Invalidate. */ + z = tasks[i].zone; + break; + } + } + + if (z == 0) { + fprintf(stderr, "error: Failed to find zone for finished " + "zone compilation process.\n"); + return 1; + } + + /* Evaluate. */ + if (!WIFEXITED(rc)) { + fprintf(stderr, "error: Compilation of '%s' " + "failed, process was killed.\n", + z->name); + return 1; + } else { + if (rc < 0 || WEXITSTATUS(rc) != 0) { + fprintf(stderr, "error: Compilation of " + "'%s' failed, knot-zcompile " + "return code was '%d'\n", + z->name, WEXITSTATUS(rc)); + return 1; + } + } + + return 0; +} + +/*! \brief Register running zone compilation process. */ +int zctask_add(knotc_zctask_t *tasks, int count, pid_t pid, conf_zone_t *zone) +{ + /* Find free space. */ + for (unsigned i = 0; i < count; ++i) { + if (tasks[i].proc == -1) { + tasks[i].proc = pid; + tasks[i].zone = zone; + return 0; + } + } + + /* Free space not found. */ + return -1; +} + +/*! + * \brief Execute specified action. + * + * \param action Action to be executed (start, stop, restart...) + * \param argv Additional arguments vector. + * \param argc Addition arguments count. + * \param pid Specified PID for action. + * \param verbose True if running in verbose mode. + * \param force True if forced operation is required. + * \param wait Wait for the operation to finish. + * \param interactive Interactive mode. + * \param jobs Number of parallel tasks to run. + * \param pidfile Specified PID file for action. + * + * \retval 0 on success. + * \retval error return code for main on error. + * + * \todo Make enumerated flags instead of many parameters... + */ +int execute(const char *action, char **argv, int argc, pid_t pid, int verbose, + int force, int wait, int interactive, int jobs, const char *pidfile) +{ + int valid_cmd = 0; + int rc = 0; + if (strcmp(action, "start") == 0) { + + // Check PID + valid_cmd = 1; +// if (pid < 0 && pid == KNOT_ERANGE) { +// fprintf(stderr, "control: Another server instance " +// "is already starting.\n"); +// return 1; +// } + if (pid > 0 && pid_running(pid)) { + + fprintf(stderr, "control: Server PID found, " + "already running.\n"); + + if (!force) { + return 1; + } else { + fprintf(stderr, "control: forcing " + "server start, killing old pid=%ld.\n", + (long)pid); + kill(pid, SIGKILL); + pid_remove(pidfile); + } + } + + // Lock configuration + conf_read_lock(); + + // Prepare command + const char *cfg = conf()->filename; + const char *args[] = { + PROJECT_EXEC, + interactive ? "" : "-d", + cfg ? "-c" : "", + cfg ? cfg : "", + verbose ? "-v" : "", + argc > 0 ? argv[0] : "" + }; + + // Unlock configuration + conf_read_unlock(); + + // Execute command + if (interactive) { + printf("control: Running in interactive mode.\n"); + fflush(stderr); + fflush(stdout); + } + if ((rc = exec_cmd(args, 6)) < 0) { + pid_remove(pidfile); + rc = 1; + } + fflush(stderr); + fflush(stdout); + + // Wait for finish + if (wait && !interactive) { + if (verbose) { + fprintf(stdout, "control: waiting for server " + "to load.\n"); + } + /* Periodically read pidfile and wait for + * valid result. */ + pid = 0; + while(pid == 0 || !pid_running(pid)) { + pid = pid_read(pidfile); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 500 * 1000; + select(0, 0, 0, 0, &tv); + } + } + } + if (strcmp(action, "stop") == 0) { + + // Check PID + valid_cmd = 1; + rc = 0; + if (pid <= 0 || !pid_running(pid)) { + fprintf(stderr, "Server PID not found, " + "probably not running.\n"); + + if (!force) { + rc = 1; + } else { + fprintf(stderr, "control: forcing " + "server stop.\n"); + } + } + + // Stop + if (rc == 0) { + if (kill(pid, SIGTERM) < 0) { + pid_remove(pidfile); + rc = 1; + } + } + + // Wait for finish + if (rc == 0 && wait) { + if (verbose) { + fprintf(stdout, "control: waiting for server " + "to stop.\n"); + } + /* Periodically read pidfile and wait for + * valid result. */ + while(pid_running(pid)) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 500 * 1000; + select(0, 0, 0, 0, &tv); + } + } + } + if (strcmp(action, "restart") == 0) { + valid_cmd = 1; + execute("stop", argv, argc, pid, verbose, force, wait, + interactive, jobs, pidfile); + + int i = 0; + while((pid = pid_read(pidfile)) > 0) { + + if (!pid_running(pid)) { + pid_remove(pidfile); + break; + } + if (i == WAITPID_TIMEOUT) { + fprintf(stderr, "Timeout while " + "waiting for the server to finish.\n"); + //pid_remove(pidfile); + break; + } else { + sleep(1); + ++i; + } + } + + printf("Restarting server.\n"); + rc = execute("start", argv, argc, -1, verbose, force, wait, + interactive, jobs, pidfile); + } + if (strcmp(action, "reload") == 0) { + + // Check PID + valid_cmd = 1; + if (pid <= 0 || !pid_running(pid)) { + fprintf(stderr, "Server PID not found, " + "probably not running.\n"); + + if (force) { + fprintf(stderr, "control: forcing " + "server stop.\n"); + } else { + return 1; + } + } + + // Stop + if (kill(pid, SIGHUP) < 0) { + pid_remove(pidfile); + rc = 1; + } + } + if (strcmp(action, "running") == 0) { + + // Check PID + valid_cmd = 1; + if (pid <= 0) { + printf("Server PID not found, " + "probably not running.\n"); + rc = 1; + } else { + if (!pid_running(pid)) { + printf("Server PID not found, " + "probably not running.\n"); + fprintf(stderr, + "warning: PID file is stale.\n"); + } else { + printf("Server running as PID %ld.\n", + (long)pid); + } + rc = 0; + } + } + if (strcmp(action, "compile") == 0) { + + // Print job count + if (jobs > 1) { + printf("warning: Will attempt to compile %d zones " + "in parallel, this increases memory consumption " + "for large zones.\n", jobs); + } + + // Check zone + valid_cmd = 1; + + // Lock configuration + conf_read_lock(); + + // Generate databases for all zones + node *n = 0; + int running = 0; + knotc_zctask_t *tasks = zctask_create(jobs); + WALK_LIST(n, conf()->zones) { + + // Fetch zone + conf_zone_t *zone = (conf_zone_t*)n; + + // Check source files and mtime + int zone_status = check_zone(zone->db, zone->file); + if (zone_status == KNOTD_EOK) { + printf("Zone '%s' is up-to-date.\n", + zone->name); + + if (force) { + fprintf(stderr, "control: forcing " + "zone recompilation.\n"); + } else { + continue; + } + } + + // Check for not existing source + if (zone_status == KNOTD_ENOENT) { + continue; + } + + /* Evaluate space for new task. */ + if (running == jobs) { + zctask_wait(tasks, jobs); + --running; + } + + const char *args[] = { + ZONEPARSER_EXEC, + zone->enable_checks ? "-s" : "", + verbose ? "-v" : "", + "-o", + zone->db, + zone->name, + zone->file + }; + + // Execute command + if (verbose) { + printf("Compiling '%s' as '%s'...\n", + zone->name, zone->db); + } + fflush(stdout); + fflush(stderr); + pid_t zcpid = start_cmd(args, 7); + zctask_add(tasks, jobs, zcpid, zone); + ++running; + } + + /* Wait for all running tasks. */ + while (running > 0) { + zctask_wait(tasks, jobs); + --running; + } + free(tasks); + + // Unlock configuration + conf_read_unlock(); + } + if (!valid_cmd) { + fprintf(stderr, "Invalid command: '%s'\n", action); + return 1; + } + + // Log + if (verbose) { + printf("'%s' finished (return code %d)\n", action, rc); + } + return rc; +} + +int main(int argc, char **argv) +{ + // Parse command line arguments + int c = 0, li = 0; + int force = 0; + int verbose = 0; + int wait = 0; + int interactive = 0; + int jobs = 1; + const char* config_fn = 0; + + /* Long options. */ + struct option opts[] = { + {"wait", no_argument, 0, 'w'}, + {"force", no_argument, 0, 'f'}, + {"config", required_argument, 0, 'c'}, + {"verbose", no_argument, 0, 'v'}, + {"interactive", no_argument, 0, 'i'}, + {"jobs", required_argument, 0, 'c'}, + {"version", no_argument, 0, 'V'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + while ((c = getopt_long(argc, argv, "wfc:vij:Vh", opts, &li)) != -1) { + switch (c) + { + case 'w': + wait = 1; + break; + case 'f': + force = 1; + break; + case 'c': + config_fn = optarg; + break; + case 'v': + verbose = 1; + break; + case 'i': + interactive = 1; + break; + case 'j': + jobs = atoi(optarg); + if (jobs < 1) { + fprintf(stderr, "Invalid parameter '%s' to " + "'-j', expects number <1..n>\n", + optarg); + help(argc, argv); + return 1; + } + break; + case 'V': + printf("%s, version %s\n", "Knot DNS", PACKAGE_VERSION); + return 0; + case 'h': + case '?': + default: + help(argc, argv); + return 1; + } + } + + // Check if there's at least one remaining non-option + if (argc - optind < 1) { + help(argc, argv); + return 1; + } + + // Initialize log (no output) + log_init(); + log_levels_set(LOGT_SYSLOG, LOG_ANY, 0); + log_levels_set(LOGT_STDOUT, LOG_ANY, 0); + closelog(); + + // Find implicit configuration file + char *default_fn = 0; + if (!config_fn) { + default_fn = conf_find_default(); + config_fn = default_fn; + } + + // Open configuration + if (conf_open(config_fn) != 0) { + fprintf(stderr, "Failed to parse configuration '%s'.\n", + config_fn); + free(default_fn); + return 1; + } + + // Free default config filename if exists + free(default_fn); + + // Verbose mode + if (verbose) { + int mask = LOG_MASK(LOG_INFO)|LOG_MASK(LOG_DEBUG); + log_levels_add(LOGT_STDOUT, LOG_ANY, mask); + } + + // Fetch PID + char* pidfile = pid_filename(); + if (!pidfile) { + fprintf(stderr, "No configuration found, " + "please specify with '-c' parameter.\n"); + log_close(); + return 1; + } + + pid_t pid = pid_read(pidfile); + + // Actions + const char* action = argv[optind]; + + // Execute action + int rc = execute(action, argv + optind + 1, argc - optind - 1, + pid, verbose, force, wait, interactive, jobs, pidfile); + + // Finish + free(pidfile); + log_close(); + return rc; +} diff --git a/src/knot/ctl/process.c b/src/knot/ctl/process.c new file mode 100644 index 0000000..e46fa37 --- /dev/null +++ b/src/knot/ctl/process.c @@ -0,0 +1,126 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <signal.h> + +#include "knot/common.h" +#include "knot/ctl/process.h" +#include "knot/conf/conf.h" +#include "knot/other/error.h" + +char* pid_filename() +{ + conf_read_lock(); + + /* Read configuration. */ + char* ret = 0; + if (conf()) { + ret = strdup(conf()->pidfile); + } + + conf_read_unlock(); + + return ret; +} + +pid_t pid_read(const char* fn) +{ + char buf[64]; + + if (fn) { + FILE *fp = fopen(fn, "r"); + if (!fp) { + return KNOTD_ENOENT; + } + + int readb = 0; + int rc = fread(buf, 1, 1, fp); + while (rc > 0) { + if (++readb == sizeof(buf) - 1) { + break; + } + rc = fread(buf + readb, 1, 1, fp); + } + buf[readb] = '\0'; + fclose(fp); + + // Check read result + if (readb < 1) { + return KNOTD_ENOENT; + } + + // Convert pid + char* ep = 0; + unsigned long pid = strtoul(buf, &ep, 10); + if ((errno == ERANGE) || (*ep && !isspace(*ep))) { + return KNOTD_ERANGE; + } + + return (pid_t)pid; + } + + return KNOTD_EINVAL; +} + +int pid_write(const char* fn) +{ + if (!fn) { + return KNOTD_EINVAL; + } + + // Convert + char buf[64]; + int wbytes = 0; + wbytes = snprintf(buf, sizeof(buf), "%lu", (unsigned long) getpid()); + if (wbytes < 0) { + return KNOTD_EINVAL; + } + + // Write + FILE *fp = fopen(fn, "w"); + if (fp) { + int rc = fwrite(buf, wbytes, 1, fp); + fclose(fp); + if (rc < 0) { + return KNOTD_ERROR; + } + + return 0; + } + + return KNOTD_ENOENT; +} + +int pid_remove(const char* fn) +{ + if (unlink(fn) < 0) { + return KNOTD_EINVAL; + } + + return KNOTD_EOK; +} + +int pid_running(pid_t pid) +{ + return kill(pid, 0) == 0; +} + diff --git a/src/knot/ctl/process.h b/src/knot/ctl/process.h new file mode 100644 index 0000000..d8f2f4c --- /dev/null +++ b/src/knot/ctl/process.h @@ -0,0 +1,88 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file process.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Functions for POSIX process handling. + * + * \addtogroup ctl + * @{ + */ + +#ifndef _KNOTD_PROCESS_H_ +#define _KNOTD_PROCESS_H_ + +#include <unistd.h> + +/*! + * \brief Return a filename of the default compiled database file. + * + * \retval Filename of the database file. + * \retval NULL if not exists. + */ +char* pid_filename(); + +/*! + * \brief Read PID from given file. + * + * \param fn Filename containing PID. + * + * \retval PID on success (positive integer). + * \retval KNOTD_EINVAL on null path. + * \retval KNOTD_ENOENT if the filename content cannot be read. + * \retval KNOTD_ERANGE if the stored PID is out of range. + */ +pid_t pid_read(const char* fn); + +/*! + * \brief Write PID to given file. + * + * \param fn Filename containing PID. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on null path. + * \retval KNOTD_ENOENT filename cannot be opened for writing. + * \retval KNOTD_ERROR unspecified error. + */ +int pid_write(const char* fn); + +/*! + * \brief Remove file containing PID. + * + * \param fn Filename containing PID. + * + * \warning Filename content won't be checked. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL failed to remove filename. + */ +int pid_remove(const char* fn); + +/*! + * \brief Return true if the PID is running. + * + * \param pid Process ID. + * + * \retval 1 if running. + * \retval 0 if not running (or error). + */ +int pid_running(pid_t pid); + +#endif // _KNOTD_PROCESS_H_ + +/*! @} */ diff --git a/src/knot/main.c b/src/knot/main.c new file mode 100644 index 0000000..4091055 --- /dev/null +++ b/src/knot/main.c @@ -0,0 +1,336 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include "common.h" + +#include "knot/common.h" +#include "knot/other/error.h" +#include "knot/server/server.h" +#include "zcompile/zcompile.h" +#include "knot/ctl/process.h" +#include "knot/conf/conf.h" +#include "knot/conf/logconf.h" +#include "common/evqueue.h" +#include "knot/server/zones.h" + +/*----------------------------------------------------------------------------*/ + +/* Signal flags. */ +static volatile short sig_req_stop = 0; +static volatile short sig_req_reload = 0; +static volatile short sig_stopping = 0; + +// SIGINT signal handler +void interrupt_handle(int s) +{ + // Reload configuration + if (s == SIGHUP) { + sig_req_reload = 1; + return; + } + + // Stop server + if (s == SIGINT || s == SIGTERM) { + if (sig_stopping == 0) { + sig_req_stop = 1; + sig_stopping = 1; + } else { + log_server_notice("OK! Exiting immediately.\n"); + exit(1); + } + } +} + +void help(int argc, char **argv) +{ + printf("Usage: %sd [parameters]\n", + PACKAGE_NAME); + printf("Parameters:\n" + " -c, --config [file] Select configuration file.\n" + " -d, --daemonize Run server as a daemon.\n" + " -v, --verbose Verbose mode - additional runtime information.\n" + " -V, --version Print version of the server.\n" + " -h, --help Print help and usage.\n"); +} + +int main(int argc, char **argv) +{ + // Parse command line arguments + int c = 0, li = 0; + int verbose = 0; + int daemonize = 0; + char* config_fn = 0; + + /* Long options. */ + struct option opts[] = { + {"config", required_argument, 0, 'c'}, + {"daemonize", no_argument, 0, 'd'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + while ((c = getopt_long(argc, argv, "c:dvVh", opts, &li)) != -1) { + switch (c) + { + case 'c': + config_fn = strdup(optarg); + break; + case 'd': + daemonize = 1; + break; + case 'v': + verbose = 1; + break; + case 'V': + printf("%s, version %s\n", "Knot DNS", PACKAGE_VERSION); + return 0; + case 'h': + case '?': + default: + help(argc, argv); + return 1; + } + } + + // Now check if we want to daemonize + if (daemonize) { + if (daemon(1, 0) != 0) { + fprintf(stderr, "Daemonization failed, " + "shutting down...\n"); + return 1; + } + } + + // Register service and signal handler + struct sigaction emptyset; + emptyset.sa_handler = interrupt_handle; + sigemptyset(&emptyset.sa_mask); + emptyset.sa_flags = 0; + sigaction(SIGALRM, &emptyset, NULL); // Interrupt + + // Setup event queue + evqueue_set(evqueue_new()); + + // Initialize log + log_init(); + + // Verbose mode + if (verbose) { + int mask = LOG_MASK(LOG_INFO)|LOG_MASK(LOG_DEBUG); + log_levels_add(LOGT_STDOUT, LOG_ANY, mask); + } + + // Initialize pseudorandom number generator + srand(time(0)); + + // Create server + server_t *server = server_create(); + + // Initialize configuration + conf_read_lock(); + conf_add_hook(conf(), CONF_LOG, log_conf_hook, 0); + conf_add_hook(conf(), CONF_LOG, zones_ns_conf_hook, server->nameserver); + conf_add_hook(conf(), CONF_LOG, server_conf_hook, server); + conf_read_unlock(); + + // Find implicit configuration file + if (!config_fn) { + config_fn = conf_find_default(); + } + + // Find absolute path for config file + if (config_fn[0] != '/') + { + // Get absolute path to cwd + size_t cwbuflen = 64; + char *cwbuf = malloc((cwbuflen + 2) * sizeof(char)); + while (getcwd(cwbuf, cwbuflen) == 0) { + cwbuflen *= 2; + cwbuf = realloc(cwbuf, (cwbuflen + 2) * sizeof(char)); + } + cwbuflen = strlen(cwbuf); + + // Append ending slash + if (cwbuf[cwbuflen - 1] != '/') { + cwbuf = strcat(cwbuf, "/"); + } + + // Assemble path to config file + char *abs_cfg = strcdup(cwbuf, config_fn); + free(config_fn); + free(cwbuf); + config_fn = abs_cfg; + } + + // Open configuration + log_server_info("Parsing configuration '%s' ...\n", config_fn); + if (conf_open(config_fn) != KNOTD_EOK) { + + log_server_error("Failed to parse configuration file '%s'.\n", + config_fn); + server_destroy(&server); + free(config_fn); + return 1; + } else { + log_server_info("Configured %d interfaces and %d zones.\n", + conf()->ifaces_count, conf()->zones_count); + } + log_server_info("\n"); + + // Create server instance + char* pidfile = pid_filename(); + + // Run server + int res = 0; + log_server_info("Starting server...\n"); + if ((res = server_start(server)) == KNOTD_EOK) { + + // Save PID + int has_pid = 1; + int rc = pid_write(pidfile); + if (rc < 0) { + has_pid = 0; + log_server_warning("Failed to create " + "PID file '%s'.\n", pidfile); + } + + // Change directory if daemonized + if (daemonize) { + log_server_info("Server started as a daemon, " + "PID = %ld\n", (long)getpid()); + res = chdir("/"); + } else { + log_server_info("Server started in foreground, " + "PID = %ld\n", (long)getpid()); + } + if (has_pid) { + log_server_info("PID stored in %s\n", pidfile); + } else { + log_server_warning("Server running without PID file.\n"); + } + size_t zcount = server->nameserver->zone_db->zone_count; + if (!zcount) { + log_server_warning("Server started, but no zones served.\n"); + } + + // Setup signal handler + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = interrupt_handle; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGHUP, &sa, NULL); + sa.sa_flags = 0; + pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL); + + /* Run event loop. */ + for(;;) { + pthread_sigmask(SIG_UNBLOCK, &sa.sa_mask, NULL); + int ret = evqueue_poll(evqueue(), 0, 0); + pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL); + + /* Interrupts. */ + /*! \todo More robust way to exit evloop. + * Event loop should exit with a special + * event. + */ + if (sig_req_stop) { + sig_req_stop = 0; + server_stop(server); + break; + } + if (sig_req_reload) { + log_server_info("Reloading configuration...\n"); + sig_req_reload = 0; + int cf_ret = cf_ret = conf_open(config_fn); + switch (cf_ret) { + case KNOTD_EOK: + log_server_info("Configuration " + "reloaded.\n"); + break; + case KNOTD_ENOENT: + log_server_error("Configuration " + "file '%s' " + "not found.\n", + conf()->filename); + break; + default: + log_server_error("Configuration " + "reload failed.\n"); + break; + } + } + + /* Events. */ + if (ret > 0) { + event_t ev; + if (evqueue_get(evqueue(), &ev) == 0) { + dbg_server_verb("Event: " + "received new event.\n"); + if (ev.cb) { + ev.cb(&ev); + } + } + } + } + pthread_sigmask(SIG_UNBLOCK, &sa.sa_mask, NULL); + + if ((res = server_wait(server)) != KNOTD_EOK) { + log_server_error("An error occured while " + "waiting for server to finish.\n"); + } else { + log_server_info("Server finished.\n"); + } + + } else { + log_server_fatal("An error occured while " + "starting the server.\n"); + } + + // Stop server and close log + server_destroy(&server); + + // Remove PID file + if (pid_remove(pidfile) < 0) { + log_server_warning("Failed to remove PID file.\n"); + } + + log_server_info("Shut down.\n"); + log_close(); + free(pidfile); + + // Destroy event loop + evqueue_t *q = evqueue(); + evqueue_free(&q); + + // Free default config filename if exists + free(config_fn); + + if (!daemonize) { + fflush(stdout); + fflush(stderr); + } + + return res; +} diff --git a/src/knot/other/debug.h b/src/knot/other/debug.h new file mode 100644 index 0000000..aa80373 --- /dev/null +++ b/src/knot/other/debug.h @@ -0,0 +1,426 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file other/debug.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Debugging facility, uses log.h. + * + * \addtogroup debugging + * @{ + */ + +#ifndef _KNOTD_DEBUG_H_ +#define _KNOTD_DEBUG_H_ + +#include "config.h" /* autoconf generated */ + +#include "knot/other/log.h" +#include "common/print.h" + +/*! \todo Set these during configure as well. */ +//#define KNOTD_SERVER_DEBUG +//#define KNOTD_THREADS_DEBUG +//#define KNOTD_JOURNAL_DEBUG +//#define KNOTD_NET_DEBUG +//#define KNOTD_ZONES_DEBUG +//#define KNOTD_XFR_DEBUG +//#define KNOTD_NOTIFY_DEBUG +//#define KNOTD_ZDUMP_DEBUG +//#define KNOTD_ZLOAD_DEBUG + +/******************************************************************************/ + +#ifdef KNOTD_NOTIFY_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_notify(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_notify_hex(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_notify(msg...) +#define dbg_notify_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_notify_verb(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_notify_hex_verb(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_notify_verb(msg...) +#define dbg_notify_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_notify_detail(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_notify_hex_detail(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_notify_detail(msg...) +#define dbg_notify_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_notify(msg...) +#define dbg_notify_hex(data, len) +#define dbg_notify_verb(msg...) +#define dbg_notify_hex_verb(data, len) +#define dbg_notify_detail(msg...) +#define dbg_notify_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOTD_SERVER_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_server(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_server_hex(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_server(msg...) +#define dbg_server_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_server_verb(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_server_hex_verb(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_server_verb(msg...) +#define dbg_server_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_server_detail(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_server_hex_detail(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_server_detail(msg...) +#define dbg_server_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_server(msg...) +#define dbg_server_hex(data, len) +#define dbg_server_verb(msg...) +#define dbg_server_hex_verb(data, len) +#define dbg_server_detail(msg...) +#define dbg_server_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOTD_NET_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_net(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_net_hex(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_net(msg...) +#define dbg_net_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_net_verb(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_net_hex_verb(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_net_verb(msg...) +#define dbg_net_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_net_detail(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_net_hex_detail(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_net_detail(msg...) +#define dbg_net_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_net(msg...) +#define dbg_net_hex(data, len) +#define dbg_net_verb(msg...) +#define dbg_net_hex_verb(data, len) +#define dbg_net_detail(msg...) +#define dbg_net_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOTD_THREADS_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_dt(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_dt_hex(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_dt(msg...) +#define dbg_dt_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_dt_verb(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_dt_hex_verb(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_dt_verb(msg...) +#define dbg_dt_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_dt_detail(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_dt_hex_detail(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_dt_detail(msg...) +#define dbg_dt_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_dt(msg...) +#define dbg_dt_hex(data, len) +#define dbg_dt_verb(msg...) +#define dbg_dt_hex_verb(data, len) +#define dbg_dt_detail(msg...) +#define dbg_dt_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOTD_JOURNAL_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_journal(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_journal_hex(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_journal(msg...) +#define dbg_journal_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_journal_verb(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_journal_hex_verb(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_journal_verb(msg...) +#define dbg_journal_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_journal_detail(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_journal_hex_detail(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_journal_detail(msg...) +#define dbg_journal_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_journal(msg...) +#define dbg_journal_hex(data, len) +#define dbg_journal_verb(msg...) +#define dbg_journal_hex_verb(data, len) +#define dbg_journal_detail(msg...) +#define dbg_journal_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOTD_ZONES_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_zones(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_zones_hex(data, len) hex_log(LOG_SERVER, (data), (len)) +#define dbg_zones_exec(cmds) do { cmds } while (0) +#else +#define dbg_zones(msg...) +#define dbg_zones_hex(data, len) +#define dbg_zones_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_zones_verb(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_zones_hex_verb(data, len) hex_log(LOG_SERVER, (data), (len)) +#define dbg_zones_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_zones_verb(msg...) +#define dbg_zones_hex_verb(data, len) +#define dbg_zones_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_zones_detail(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_zones_hex_detail(data, len) hex_log(LOG_SERVER, (data), (len)) +#define dbg_zones_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_zones_detail(msg...) +#define dbg_zones_hex_detail(data, len) +#define dbg_zones_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_zones(msg...) +#define dbg_zones_hex(data, len) +#define dbg_zones_verb(msg...) +#define dbg_zones_hex_verb(data, len) +#define dbg_zones_detail(msg...) +#define dbg_zones_hex_detail(data, len) +#define dbg_zones_exec(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOTD_XFR_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_xfr(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_xfr_hex(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_xfr(msg...) +#define dbg_xfr_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_xfr_verb(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_xfr_hex_verb(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_xfr_verb(msg...) +#define dbg_xfr_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_xfr_detail(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_xfr_hex_detail(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_xfr_detail(msg...) +#define dbg_xfr_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_xfr(msg...) +#define dbg_xfr_hex(data, len) +#define dbg_xfr_verb(msg...) +#define dbg_xfr_hex_verb(data, len) +#define dbg_xfr_detail(msg...) +#define dbg_xfr_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOTD_ZDUMP_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_zdump(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_zdump_hex(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_zdump(msg...) +#define dbg_zdump_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_zdump_verb(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_zdump_hex_verb(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_zdump_verb(msg...) +#define dbg_zdump_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_zdump_detail(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_zdump_hex_detail(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_zdump_detail(msg...) +#define dbg_zdump_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_zdump(msg...) +#define dbg_zdump_hex(data, len) +#define dbg_zdump_verb(msg...) +#define dbg_zdump_hex_verb(data, len) +#define dbg_zdump_detail(msg...) +#define dbg_zdump_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOTD_ZLOAD_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_zload(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_zload_hex(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_zload(msg...) +#define dbg_zload_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_zload_verb(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_zload_hex_verb(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_zload_verb(msg...) +#define dbg_zload_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_zload_detail(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) +#define dbg_zload_hex_detail(data, len) hex_log(LOG_SERVER, (data), (len)) +#else +#define dbg_zload_detail(msg...) +#define dbg_zload_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_zload(msg...) +#define dbg_zload_hex(data, len) +#define dbg_zload_verb(msg...) +#define dbg_zload_hex_verb(data, len) +#define dbg_zload_detail(msg...) +#define dbg_zload_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#endif /* _KNOTD_DEBUG_H_ */ + +/*! @} */ diff --git a/src/knot/other/error.c b/src/knot/other/error.c new file mode 100644 index 0000000..a149966 --- /dev/null +++ b/src/knot/other/error.c @@ -0,0 +1,46 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include "knot/other/error.h" +#include "common/errors.h" + +const error_table_t knotd_error_msgs[] = { + + /* Mapped errors. */ + {KNOTD_EOK, "OK"}, + {KNOTD_ENOMEM, "Not enough memory."}, + {KNOTD_EINVAL, "Invalid parameter passed."}, + {KNOTD_ENOTSUP, "Parameter not supported."}, + {KNOTD_EBUSY, "Requested resource is busy."}, + {KNOTD_EAGAIN, "The system lacked the necessary resource, try again."}, + {KNOTD_EACCES, "Permission to perform requested operation is denied."}, + {KNOTD_ECONNREFUSED, "Connection is refused."}, + {KNOTD_EISCONN, "Already connected."}, + {KNOTD_EADDRINUSE, "Address already in use."}, + {KNOTD_ENOENT, "Resource not found."}, + {KNOTD_ERANGE, "Value is out of range."}, + + /* Custom errors. */ + {KNOTD_ERROR, "Generic error."}, + {KNOTD_EZONEINVAL, "Invalid zone file."}, + {KNOTD_ENOTRUNNING, "Resource is not running."}, + {KNOTD_EPARSEFAIL, "Parser failed."}, + {KNOTD_ENOIPV6, "IPv6 support disabled."}, + {KNOTD_EMALF, "Malformed data."}, + {KNOTD_ESPACE, "Not enough space provided."}, + {KNOTD_EEXPIRED, "Resource is expired."}, + {KNOTD_ERROR, 0} +}; diff --git a/src/knot/other/error.h b/src/knot/other/error.h new file mode 100644 index 0000000..65c51cf --- /dev/null +++ b/src/knot/other/error.h @@ -0,0 +1,99 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file other/error.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Error codes and function for getting error message. + * + * \addtogroup utils + * @{ + */ + +#ifndef _KNOTD__ERROR_H_ +#define _KNOTD__ERROR_H_ + +#include <errno.h> + +#include "common/errors.h" + +/*! + * \brief Error codes used in the server. + * + * Some viable errors are directly mapped + * to libc errno codes. + */ +enum knot_error_t { + + /* Directly mapped error codes. */ + KNOTD_EOK = 0, + KNOTD_ENOMEM = -ENOMEM, /*!< \brief Out of memory. */ + KNOTD_EINVAL = -EINVAL, /*!< \brief Invalid parameter passed. */ + KNOTD_ENOTSUP = -ENOTSUP, /*!< \brief Parameter not supported. */ + KNOTD_EBUSY = -EBUSY, /*!< \brief Requested resource is busy. */ + KNOTD_EAGAIN = -EAGAIN, /*!< \brief OS lacked necessary resources. */ + KNOTD_EACCES = -EACCES, /*!< \brief Permission is denied. */ + KNOTD_ECONNREFUSED = -ECONNREFUSED, /*!< \brief Connection is refused. */ + KNOTD_EISCONN = -EISCONN, /*!< \brief Already connected. */ + KNOTD_EADDRINUSE = -EADDRINUSE, /*!< \brief Address already in use. */ + KNOTD_ENOENT = -ENOENT, /*!< \brief Resource not found. */ + KNOTD_ERANGE = -ERANGE, /*!< \brief Value is out of range. */ + + /* Custom error codes. */ + KNOTD_ERROR = -16384, /*!< \brief Generic error. */ + KNOTD_EZONEINVAL, /*!< \brief Invalid zone file. */ + KNOTD_ENOTRUNNING, /*!< \brief Resource is not running. */ + KNOTD_EPARSEFAIL, /*!< \brief Parser fail. */ + KNOTD_ENOIPV6, /*!< \brief No IPv6 support. */ + KNOTD_EMALF, /*!< \brief Malformed data. */ + KNOTD_ESPACE, /*!< \brief Not enough space provided. */ + KNOTD_EEXPIRED, /*!< \brief Resource is expired. */ + + KNOTD_ERROR_COUNT = 21 +}; + +/*! \brief Table linking error messages to error codes. */ +extern const error_table_t knotd_error_msgs[KNOTD_ERROR_COUNT]; + +/*! + * \brief Returns error message for the given error code. + * + * \param code Error code. + * + * \return String containing the error message. + */ +static inline const char *knotd_strerror(int code) +{ + return error_to_str((const error_table_t*)knotd_error_msgs, code); +} + +/*! + * \brief errno mapper that automatically prepends fallback value. + * + * \see map_errno() + * + * \param err POSIX errno. + * \param ... List of handled codes. + * + * \return Mapped error code. + */ +#define knot_map_errno(err...) map_errno(KNOTD_ERROR, err); + +#endif /* _KNOTD__ERROR_H_ */ + +/*! @} */ diff --git a/src/knot/other/log.c b/src/knot/other/log.c new file mode 100644 index 0000000..9318d5f --- /dev/null +++ b/src/knot/other/log.c @@ -0,0 +1,305 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "knot/common.h" +#include "knot/other/error.h" +#include "knot/other/log.h" +#include "common/lists.h" +#include "knot/conf/conf.h" + +/*! Log source table. */ +static uint8_t *LOG_FCL = 0; +static volatile size_t LOG_FCL_SIZE = 0; +static FILE** LOG_FDS = 0; +static ssize_t LOG_FDS_OPEN = 0; + +#define facility_at(i) (LOG_FCL + ((i) << LOG_SRC_BITS)) +#define facility_next(f) (f) += (1 << LOG_SRC_BITS) +#define facility_levels(f, i) *((f) + (i)) + +int log_setup(int logfiles) +{ + /* Check facilities count. */ + if (logfiles < 0) { + return KNOTD_EINVAL; + } + + /* Ensure minimum facilities count. */ + int facilities = LOGT_FILE + logfiles; + + /* Reserve space for facilities. */ + size_t new_size = facilities << LOG_SRC_BITS; + LOG_FDS = 0; + LOG_FDS_OPEN = 0; + LOG_FCL = 0; + LOG_FCL_SIZE = 0; + LOG_FCL = malloc(new_size); + if (!LOG_FCL) { + return KNOTD_ENOMEM; + } + + /* Reserve space for logfiles. */ + if (logfiles > 0) { + LOG_FDS = malloc(sizeof(FILE*) * logfiles); + if (!LOG_FDS) { + free(LOG_FCL); + LOG_FCL = 0; + return KNOTD_ENOMEM; + } + memset(LOG_FDS, 0, sizeof(FILE*) * logfiles); + } + + memset(LOG_FCL, 0, new_size); + LOG_FCL_SIZE = new_size; // Assign only when all is set + return KNOTD_EOK; +} + + + +int log_init() +{ + /* Initialize globals. */ + LOG_FCL = 0; + LOG_FCL_SIZE = 0; + LOG_FDS = 0; + LOG_FDS_OPEN = 0; + + /* Setup initial state. */ + int ret = KNOTD_EOK; + int emask = LOG_MASK(LOG_WARNING)|LOG_MASK(LOG_ERR)|LOG_MASK(LOG_FATAL); + int imask = LOG_MASK(LOG_INFO)|LOG_MASK(LOG_NOTICE); + + /* Add debug messages. */ + emask |= LOG_MASK(LOG_DEBUG); + + ret = log_setup(0); + log_levels_set(LOGT_SYSLOG, LOG_ANY, emask); + log_levels_set(LOGT_STDERR, LOG_ANY, emask); + log_levels_set(LOGT_STDOUT, LOG_ANY, imask); + + /// \todo May change to LOG_DAEMON. + setlogmask(LOG_UPTO(LOG_DEBUG)); + openlog(PACKAGE_NAME, LOG_PID, LOG_DAEMON); + return ret; +} + +void log_close() +{ + log_truncate(); + closelog(); +} + +void log_truncate() +{ + LOG_FCL_SIZE = 0; + if (LOG_FCL) { + free(LOG_FCL); + LOG_FCL = 0; + } + if (LOG_FDS) { + + /* Close open logfiles. */ + for (int i = 0; i < LOG_FDS_OPEN; ++i) { + fclose(LOG_FDS[i]); + } + + free(LOG_FDS); + LOG_FDS = 0; + LOG_FDS_OPEN = 0; + } +} + +int log_isopen() +{ + return LOG_FCL_SIZE; +} + +int log_open_file(const char* filename) +{ + // Check facility + if (unlikely(!LOG_FCL_SIZE || LOGT_FILE + LOG_FDS_OPEN >= LOG_FCL_SIZE)) { + return KNOTD_ERROR; + } + + // Open file + LOG_FDS[LOG_FDS_OPEN] = fopen(filename, "w"); + if (!LOG_FDS[LOG_FDS_OPEN]) { + return KNOTD_EINVAL; + } + + // Disable buffering + setvbuf(LOG_FDS[LOG_FDS_OPEN], (char *)0, _IONBF, 0); + + return LOGT_FILE + LOG_FDS_OPEN++; +} + +uint8_t log_levels(int facility, logsrc_t src) +{ + // Check facility + if (unlikely(!LOG_FCL_SIZE || facility >= LOG_FCL_SIZE)) { + return 0; + } + + return *(LOG_FCL + (facility << LOG_SRC_BITS) + src); +} + +int log_levels_set(int facility, logsrc_t src, uint8_t levels) +{ + // Check facility + if (unlikely(!LOG_FCL_SIZE || facility >= LOG_FCL_SIZE)) { + return KNOTD_EINVAL; + } + + // Get facility pointer from offset + uint8_t *lp = LOG_FCL + (facility << LOG_SRC_BITS); + + // Assign level if not multimask + if (src != LOG_ANY) { + *(lp + src) = levels; + } else { + // Any == set to all sources + for (int i = 0; i <= LOG_ANY; ++i) { + *(lp + i) = levels; + } + } + + return KNOTD_EOK; +} + +int log_levels_add(int facility, logsrc_t src, uint8_t levels) +{ + uint8_t new_levels = log_levels(facility, src) | levels; + return log_levels_set(facility, src, new_levels); +} + +static int _log_msg(logsrc_t src, int level, const char *msg) +{ + if(!log_isopen()) { + return KNOTD_ERROR; + } + + int ret = 0; + FILE *stream = stdout; + uint8_t *f = facility_at(LOGT_SYSLOG); + + // Syslog + if (facility_levels(f, src) & LOG_MASK(level)) { + syslog(level, "%s", msg); + ret = 1; // To prevent considering the message as ignored. + } + + // Convert level to mask + level = LOG_MASK(level); + + // Log streams + for (int i = LOGT_STDERR; i < LOGT_FILE + LOG_FDS_OPEN; ++i) { + + // Check facility levels mask + f = facility_at(i); + if (facility_levels(f, src) & level) { + + // Select stream + switch(i) { + case LOGT_STDERR: stream = stderr; break; + case LOGT_STDOUT: stream = stdout; break; + default: stream = LOG_FDS[i - LOGT_FILE]; break; + } + + // Print + ret = fprintf(stream, "%s", msg); + if (stream == stdout) { + fflush(stream); + } + } + } + + if (ret < 0) { + return KNOTD_EINVAL; + } + + return ret; +} + +int log_msg(logsrc_t src, int level, const char *msg, ...) +{ + /* Buffer for log message. */ + char sbuf[4096]; + char *buf = sbuf; + int buflen = sizeof(sbuf) - 1; + + /* Prefix error level. */ + const char *prefix = ""; + switch (level) { + case LOG_DEBUG: break; + case LOG_INFO: break; + case LOG_NOTICE: prefix = "notice: "; break; + case LOG_WARNING: prefix = "warning: "; break; + case LOG_ERR: prefix = "error: "; break; + case LOG_FATAL: prefix = "fatal: "; break; + default: break; + } + + /* Prepend prefix. */ + int plen = strlen(prefix); + if (plen > 0) { + strcpy(buf, prefix); + buf += plen; + buflen -= plen; + } + + /* Compile log message. */ + int ret = 0; + va_list ap; + va_start(ap, msg); + ret = vsnprintf(buf, buflen, msg, ap); + va_end(ap); + + /* Send to logging facilities. */ + if (ret > 0) { + ret = _log_msg(src, level, sbuf); + } + + return ret; +} + +int log_vmsg(logsrc_t src, int level, const char *msg, va_list ap) +{ + int ret = 0; + char buf[2048]; + ret = vsnprintf(buf, sizeof(buf) - 1, msg, ap); + + if (ret > 0) { + ret = _log_msg(src, level, buf); + } + + return ret; +} + +void hex_log(int source, const char *data, int length) +{ + int ptr = 0; + for (; ptr < length; ptr++) { + log_msg(source, LOG_DEBUG, "0x%02x ", + (unsigned char)*(data + ptr)); + } + log_msg(source, LOG_DEBUG, "\n"); +} diff --git a/src/knot/other/log.h b/src/knot/other/log.h new file mode 100644 index 0000000..305020c --- /dev/null +++ b/src/knot/other/log.h @@ -0,0 +1,208 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file log.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Logging facility. + * + * \note Loglevel defined in syslog.h, may be redefined in other backend, but + * keep naming. LOG_FATAL, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG + * + * In standard mode, only LOG_FATAL, LOG_ERR and LOG_WARNING is logged. + * Verbose mode enables LOG_NOTICE and LOG_INFO for additional information. + * + * \addtogroup logging + * @{ + */ + +#ifndef _KNOTD_LOG_H_ +#define _KNOTD_LOG_H_ + +/* + */ +#include <syslog.h> +#include <stddef.h> +#include <stdint.h> +#include <stdarg.h> + +/*! \brief Log facility types. */ +typedef enum { + LOGT_SYSLOG = 0, /*!< Logging to syslog(3) facility. */ + LOGT_STDERR = 1, /*!< Print log messages to the stderr. */ + LOGT_STDOUT = 2, /*!< Print log messages to the stdout. */ + LOGT_FILE = 3 /*!< Generic logging to (unbuffered) file on the disk. */ +} logtype_t; + +/*! \brief Log sources width (bits). */ +#define LOG_SRC_BITS 3 + +/*! \brief Log sources (max. LOG_SRC_BITS bits). */ +typedef enum { + LOG_SERVER = 0, /*!< Server module. */ + LOG_ANSWER = 1, /*!< Query answering module. */ + LOG_ZONE = 2, /*!< Zone manipulation module. */ + LOG_ANY = 7 /*!< Any module. */ +} logsrc_t; + +/*! \brief Severity mapping. */ +#define LOG_FATAL LOG_CRIT /*!< Fatal errors cannot be masked. */ + +/* Logging facility setup. */ + +/*! + * \brief Create logging facilities respecting their + * canonical order. + * + * Facilities ordering: Syslog, Stderr, Stdout, File0... + * \see logtype_t + * + * \param logfiles Number of extra logfiles. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL invalid number of logfiles (negative). + */ +int log_setup(int logfiles); + +/*! + * \brief Setup logging subsystem. + * + * \see syslog.h + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_ENOMEM out of memory error. + */ +int log_init(); + +/*! + * \brief Close and deinitialize log. + */ +void log_close(); + +/*! + * \brief Truncate current log setup. + */ +void log_truncate(); + +/*! + * \brief Return positive number if open. + * + * \return 1 if open (boolean true) + * \return 0 if closed (boolean false) + */ +int log_isopen(); + +/*! + * \brief Open file as a logging facility. + * + * \param filename File path. + * + * \retval associated facility index on success. + * \retval KNOTD_EINVAL filename cannot be opened for writing. + * \retval KNOTD_ERROR unspecified error. + */ +int log_open_file(const char* filename); + +/*! + * \brief Return log levels for a given facility. + * + * \param facility Given log facility index. + * \param src Given log source in the context of current facility. + * + * \retval Associated log level flags on success. + * \retval 0 on error. + */ +uint8_t log_levels(int facility, logsrc_t src); + +/*! + * \brief Set log levels for given facility. + * + * \param facility Logging facility index (LOGT_SYSLOG...). + * \param src Logging source (LOG_SERVER...LOG_ANY). + * \param levels Bitmask of specified log levels. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL invalid parameters (facility out of range). + */ +int log_levels_set(int facility, logsrc_t src, uint8_t levels); + +/*! + * \brief Add log levels to a given facility. + * + * New levels are added on top of existing, the resulting + * levels set is "old_levels OR new_levels". + * + * \param facility Logging facility index (LOGT_SYSLOG...). + * \param src Logging source (LOG_SERVER...LOG_ANY). + * \param levels Bitmask of specified log levels. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL invalid parameters (facility out of range). + */ +int log_levels_add(int facility, logsrc_t src, uint8_t levels); + +/*! + * \brief Log message. + * + * Function follows printf() format. + * + * \param src Origin of the message. + * \param level Message error level. + * \param msg Content of the logged message. + * + * \retval Number of logged bytes on success. + * \retval 0 When the message is ignored. + * \retval KNOTD_EINVAL invalid parameters. + * \retval KNOTD_ERROR unspecified error. + */ +int log_msg(logsrc_t src, int level, const char *msg, ...) + __attribute__((format(printf, 3, 4))); + +/*! + * \brief Log message for stdarg. + * + * \see log_msg + */ +int log_vmsg(logsrc_t src, int level, const char *msg, va_list ap); + +void hex_log(int source, const char *data, int length); + +/* Convenient logging. */ +#define log_server_fatal(msg...) log_msg(LOG_SERVER, LOG_FATAL, msg) +#define log_server_error(msg...) log_msg(LOG_SERVER, LOG_ERR, msg) +#define log_server_warning(msg...) log_msg(LOG_SERVER, LOG_WARNING, msg) +#define log_server_notice(msg...) log_msg(LOG_SERVER, LOG_NOTICE, msg) +#define log_server_info(msg...) log_msg(LOG_SERVER, LOG_INFO, msg) +#define log_server_debug(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg) + +#define log_answer_fatal(msg...) log_msg(LOG_ANSWER, LOG_FATAL, msg) +#define log_answer_error(msg...) log_msg(LOG_ANSWER, LOG_ERR, msg) +#define log_answer_warning(msg...) log_msg(LOG_ANSWER, LOG_WARNING, msg) +#define log_answer_notice(msg...) log_msg(LOG_ANSWER, LOG_NOTICE, msg) +#define log_answer_info(msg...) log_msg(LOG_ANSWER, LOG_INFO, msg) +#define log_answer_debug(msg...) log_msg(LOG_ANSWER, LOG_DEBUG, msg) + +#define log_zone_fatal(msg...) log_msg(LOG_ZONE, LOG_FATAL, msg) +#define log_zone_error(msg...) log_msg(LOG_ZONE, LOG_ERR, msg) +#define log_zone_warning(msg...) log_msg(LOG_ZONE, LOG_WARNING, msg) +#define log_zone_notice(msg...) log_msg(LOG_ZONE, LOG_NOTICE, msg) +#define log_zone_info(msg...) log_msg(LOG_ZONE, LOG_INFO, msg) +#define log_zone_debug(msg...) log_msg(LOG_ZONE, LOG_DEBUG, msg) + +#endif /* _KNOTD_LOG_H_ */ + +/*! @} */ diff --git a/src/knot/server/dthreads.c b/src/knot/server/dthreads.c new file mode 100644 index 0000000..9707e57 --- /dev/null +++ b/src/knot/server/dthreads.c @@ -0,0 +1,1006 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> + +#include "knot/common.h" +#include "knot/server/dthreads.h" +#include "knot/other/log.h" +#include "knot/other/error.h" + +/*! \brief Lock thread state for R/W. */ +static inline void lock_thread_rw(dthread_t *thread) +{ + pthread_mutex_lock(&thread->_mx); +} +/*! \brief Unlock thread state for R/W. */ +static inline void unlock_thread_rw(dthread_t *thread) +{ + pthread_mutex_unlock(&thread->_mx); +} + +/*! \brief Signalize thread state change. */ +static inline void unit_signalize_change(dt_unit_t *unit) +{ + pthread_mutex_lock(&unit->_report_mx); + pthread_cond_signal(&unit->_report); + pthread_mutex_unlock(&unit->_report_mx); +} + +/*! + * \brief Update thread state with notification. + * \param thread Given thread. + * \param state New state for thread. + * \retval 0 on success. + * \retval <0 on error (EINVAL, ENOTSUP). + */ +static inline int dt_update_thread(dthread_t *thread, int state) +{ + // Check + if (thread == 0) { + return KNOTD_EINVAL; + } + + // Cancel with lone thread + dt_unit_t *unit = thread->unit; + if (unit == 0) { + return KNOTD_ENOTSUP; + } + + // Cancel current runnable if running + pthread_mutex_lock(&unit->_notify_mx); + lock_thread_rw(thread); + if (thread->state & (ThreadIdle | ThreadActive)) { + + // Update state + thread->state = state; + unlock_thread_rw(thread); + + // Notify thread + dt_signalize(thread, SIGALRM); + pthread_cond_broadcast(&unit->_notify); + pthread_mutex_unlock(&unit->_notify_mx); + } else { + /* Unable to update thread, it is already dead. */ + unlock_thread_rw(thread); + pthread_mutex_unlock(&unit->_notify_mx); + return KNOTD_EINVAL; + } + + return KNOTD_EOK; +} + +/*! + * \brief Thread entrypoint function. + * + * When a thread is created and started, it immediately enters this function. + * Depending on thread state, it either enters runnable or + * blocks until it is awakened. + * + * This function also handles "ThreadIdle" state to quickly suspend and resume + * threads and mitigate thread creation costs. Also, thread runnable may + * be changed to alter the thread behavior on runtime + */ +static void *thread_ep(void *data) +{ + // Check data + dthread_t *thread = (dthread_t *)data; + if (thread == 0) { + return 0; + } + + // Check if is a member of unit + dt_unit_t *unit = thread->unit; + if (unit == 0) { + return 0; + } + + // Ignore specific signals (except SIGALRM) + sigset_t ignset; + sigemptyset(&ignset); + sigaddset(&ignset, SIGINT); + sigaddset(&ignset, SIGTERM); + sigaddset(&ignset, SIGHUP); + pthread_sigmask(SIG_BLOCK, &ignset, 0); /*! \todo Review under BSD. */ + + dbg_dt("dthreads: [%p] entered ep\n", thread); + + // Run loop + for (;;) { + + // Check thread state + lock_thread_rw(thread); + if (thread->state == ThreadDead) { + dbg_dt("dthreads: [%p] marked as dead\n", thread); + unlock_thread_rw(thread); + break; + } + + // Update data + thread->data = thread->_adata; + runnable_t _run = thread->run; + + // Start runnable if thread is marked Active + if ((thread->state == ThreadActive) && (thread->run != 0)) { + unlock_thread_rw(thread); + dbg_dt("dthreads: [%p] entering runnable\n", thread); + _run(thread); + dbg_dt("dthreads: [%p] exited runnable\n", thread); + } else { + unlock_thread_rw(thread); + } + + // If the runnable was cancelled, start new iteration + lock_thread_rw(thread); + if (thread->state & ThreadCancelled) { + dbg_dt("dthreads: [%p] cancelled\n", thread); + thread->state &= ~ThreadCancelled; + unlock_thread_rw(thread); + continue; + } + unlock_thread_rw(thread); + + // Runnable finished without interruption, mark as Idle + pthread_mutex_lock(&unit->_notify_mx); + lock_thread_rw(thread); + if (thread->state & ThreadActive) { + thread->state &= ~ThreadActive; + thread->state |= ThreadIdle; + } + + // Go to sleep if idle + if (thread->state & ThreadIdle) { + unlock_thread_rw(thread); + + // Signalize state change + unit_signalize_change(unit); + + // Wait for notification from unit + dbg_dt("dthreads: [%p] going idle\n", thread); + /*! \todo Check return value. */ + pthread_cond_wait(&unit->_notify, &unit->_notify_mx); + pthread_mutex_unlock(&unit->_notify_mx); + dbg_dt("dthreads: [%p] resumed from idle\n", thread); + } else { + unlock_thread_rw(thread); + pthread_mutex_unlock(&unit->_notify_mx); + } + } + + // Report thread state change + dbg_dt("dthreads: [%p] thread finished\n", thread); + unit_signalize_change(unit); + dbg_dt("dthreads: [%p] thread exited ep\n", thread); + lock_thread_rw(thread); + thread->state |= ThreadJoinable; + unlock_thread_rw(thread); + + // Return + return 0; +} + +/*! + * \brief Create single thread. + * \retval New thread instance on success. + * \retval NULL on error. + */ +static dthread_t *dt_create_thread(dt_unit_t *unit) +{ + // Alloc thread + dthread_t *thread = malloc(sizeof(dthread_t)); + if (thread == 0) { + return 0; + } + + memset(thread, 0, sizeof(dthread_t)); + + // Blank thread state + thread->state = ThreadJoined; + pthread_mutex_init(&thread->_mx, 0); + + // Set membership in unit + thread->unit = unit; + + // Initialize attribute + pthread_attr_t *attr = &thread->_attr; + pthread_attr_init(attr); + pthread_attr_setinheritsched(attr, PTHREAD_INHERIT_SCHED); + pthread_attr_setschedpolicy(attr, SCHED_OTHER); + return thread; +} + +/*! \brief Delete single thread. */ +static void dt_delete_thread(dthread_t **thread) +{ + // Check + if (thread == 0) { + return; + } + if (*thread == 0) { + return; + } + + dthread_t* thr = *thread; + thr->unit = 0; + *thread = 0; + + // Delete attribute + pthread_attr_destroy(&(thr)->_attr); + + // Delete mutex + pthread_mutex_destroy(&(thr)->_mx); + + // Free memory + free(thr); +} + +/* + * Public APIs. + */ + +dt_unit_t *dt_create(int count) +{ + // Check count + if (count <= 0) { + return 0; + } + + dt_unit_t *unit = malloc(sizeof(dt_unit_t)); + if (unit == 0) { + return 0; + } + + // Initialize conditions + if (pthread_cond_init(&unit->_notify, 0) != 0) { + free(unit); + return 0; + } + if (pthread_cond_init(&unit->_report, 0) != 0) { + pthread_cond_destroy(&unit->_notify); + free(unit); + return 0; + } + + // Initialize mutexes + if (pthread_mutex_init(&unit->_notify_mx, 0) != 0) { + pthread_cond_destroy(&unit->_notify); + pthread_cond_destroy(&unit->_report); + free(unit); + return 0; + } + if (pthread_mutex_init(&unit->_report_mx, 0) != 0) { + pthread_cond_destroy(&unit->_notify); + pthread_cond_destroy(&unit->_report); + pthread_mutex_destroy(&unit->_notify_mx); + free(unit); + return 0; + } + if (pthread_mutex_init(&unit->_mx, 0) != 0) { + pthread_cond_destroy(&unit->_notify); + pthread_cond_destroy(&unit->_report); + pthread_mutex_destroy(&unit->_notify_mx); + pthread_mutex_destroy(&unit->_report_mx); + free(unit); + return 0; + } + + // Save unit size + unit->size = count; + + // Alloc threads + unit->threads = malloc(count * sizeof(dthread_t *)); + if (unit->threads == 0) { + pthread_cond_destroy(&unit->_notify); + pthread_cond_destroy(&unit->_report); + pthread_mutex_destroy(&unit->_notify_mx); + pthread_mutex_destroy(&unit->_report_mx); + pthread_mutex_destroy(&unit->_mx); + free(unit); + return 0; + } + + // Initialize threads + int init_success = 1; + for (int i = 0; i < count; ++i) { + unit->threads[i] = dt_create_thread(unit); + if (unit->threads[i] == 0) { + init_success = 0; + break; + } + } + + // Check thread initialization + if (!init_success) { + + // Delete created threads + for (int i = 0; i < count; ++i) { + dt_delete_thread(&unit->threads[i]); + } + + // Free rest of the unit + pthread_cond_destroy(&unit->_notify); + pthread_cond_destroy(&unit->_report); + pthread_mutex_destroy(&unit->_notify_mx); + pthread_mutex_destroy(&unit->_report_mx); + pthread_mutex_destroy(&unit->_mx); + free(unit->threads); + free(unit); + return 0; + } + + return unit; +} + +dt_unit_t *dt_create_coherent(int count, runnable_t runnable, void *data) +{ + // Check count + if (count <= 0) { + return 0; + } + + // Create unit + dt_unit_t *unit = dt_create(count); + if (unit == 0) { + return 0; + } + + // Set threads common purpose + pthread_mutex_lock(&unit->_notify_mx); + dt_unit_lock(unit); + + for (int i = 0; i < count; ++i) { + dthread_t *thread = unit->threads[i]; + lock_thread_rw(thread); + thread->run = runnable; + thread->_adata = data; + unlock_thread_rw(thread); + } + + dt_unit_unlock(unit); + pthread_mutex_unlock(&unit->_notify_mx); + + return unit; +} + +void dt_delete(dt_unit_t **unit) +{ + /* + * All threads must be stopped or idle at this point, + * or else the behavior is undefined. + * Sorry. + */ + + // Check + if (unit == 0) { + return; + } + if (*unit == 0) { + return; + } + + // Compact and reclaim idle threads + dt_unit_t *d_unit = *unit; + dt_compact(d_unit); + + // Delete threads + for (int i = 0; i < d_unit->size; ++i) { + dt_delete_thread(&d_unit->threads[i]); + } + + // Deinit mutexes + pthread_mutex_destroy(&d_unit->_notify_mx); + pthread_mutex_destroy(&d_unit->_report_mx); + + // Deinit conditions + pthread_cond_destroy(&d_unit->_notify); + pthread_cond_destroy(&d_unit->_report); + + // Free memory + free(d_unit->threads); + free(d_unit); + *unit = 0; +} + +int dt_resize(dt_unit_t *unit, int size) +{ + // Check input + if (unit == 0 || size <= 0) { + return KNOTD_EINVAL; + } + + // Evaluate delta + int delta = unit->size - size; + + // Same size + if (delta == 0) { + return 0; + } + + // Unit expansion + if (delta < 0) { + + // Lock unit + pthread_mutex_lock(&unit->_notify_mx); + dt_unit_lock(unit); + + // Realloc threads + dbg_dt("dthreads: growing from %d to %d threads\n", + unit->size, size); + + dthread_t **threads = realloc(unit->threads, + size * sizeof(dthread_t *)); + if (threads == NULL) { + dt_unit_unlock(unit); + pthread_mutex_unlock(&unit->_notify_mx); + return -1; + } + + // Reassign + unit->threads = threads; + + // Create new threads + for (int i = unit->size; i < size; ++i) { + threads[i] = dt_create_thread(unit); + } + + // Update unit + unit->size = size; + dt_unit_unlock(unit); + pthread_mutex_unlock(&unit->_notify_mx); + return 0; + } + + + // Unit shrinking + int remaining = size; + dbg_dt("dthreads: shrinking from %d to %d threads\n", + unit->size, size); + + // New threads vector + dthread_t **threads = malloc(size * sizeof(dthread_t *)); + if (threads == 0) { + return KNOTD_ENOMEM; + } + + // Lock unit + pthread_mutex_lock(&unit->_notify_mx); + dt_unit_lock(unit); + + // Iterate while there is space in new unit + memset(threads, 0, size * sizeof(dthread_t *)); + int threshold = ThreadActive; + for (;;) { + + // Find threads matching given criterias + int inspected = 0; + for (int i = 0; i < unit->size; ++i) { + + // Get thread + dthread_t *thread = unit->threads[i]; + if (thread == 0) { + continue; + } + + // Count thread as inspected + ++inspected; + + lock_thread_rw(thread); + + // Populate with matching threads + if ((remaining > 0) && + (!threshold || (thread->state & threshold))) { + + // Append to new vector + threads[size - remaining] = thread; + --remaining; + + // Invalidate in old vector + unit->threads[i] = 0; + dbg_dt_verb("dthreads: [%p] dt_resize: elected\n", + thread); + + } else if (remaining <= 0) { + + // Not enough space, delete thread + if (thread->state & ThreadDead) { + unlock_thread_rw(thread); + --inspected; + continue; + } + + // Signalize thread to stop + thread->state = ThreadDead | ThreadCancelled; + dt_signalize(thread, SIGALRM); + dbg_dt_verb("dthreads: [%p] dt_resize: " + "is discarded\n", thread); + } + + // Unlock thread and continue + unlock_thread_rw(thread); + } + + // Finished inspecting running threads + if (inspected == 0) { + break; + } + + // Lower threshold + switch (threshold) { + case ThreadActive: + threshold = ThreadIdle; + break; + case ThreadIdle: + threshold = ThreadDead; + break; + default: + threshold = ThreadJoined; + break; + } + } + + // Notify idle threads to wake up + pthread_cond_broadcast(&unit->_notify); + pthread_mutex_unlock(&unit->_notify_mx); + + // Join discarded threads + for (int i = 0; i < unit->size; ++i) { + + // Get thread + dthread_t *thread = unit->threads[i]; + if (thread == 0) { + continue; + } + + pthread_join(thread->_thr, 0); + thread->state = ThreadJoined; + + // Delete thread + dt_delete_thread(&thread); + unit->threads[i] = 0; + } + + // Reassign unit threads vector + unit->size = size; + free(unit->threads); + unit->threads = threads; + + // Unlock unit + dt_unit_unlock(unit); + + return 0; +} + +int dt_start(dt_unit_t *unit) +{ + // Check input + if (unit == 0) { + return KNOTD_EINVAL; + } + + // Lock unit + pthread_mutex_lock(&unit->_notify_mx); + dt_unit_lock(unit); + for (int i = 0; i < unit->size; ++i) { + + dthread_t *thread = unit->threads[i]; + int res = dt_start_id(thread); + if (res != 0) { + dbg_dt("dthreads: failed to create thread '%d'.", i); + dt_unit_unlock(unit); + pthread_mutex_unlock(&unit->_notify_mx); + return res; + } + + dbg_dt("dthreads: [%p] %s: thread started\n", + thread, __func__); + } + + // Unlock unit + dt_unit_unlock(unit); + pthread_cond_broadcast(&unit->_notify); + pthread_mutex_unlock(&unit->_notify_mx); + return 0; +} + +int dt_start_id(dthread_t *thread) +{ + // Check input + if (thread == 0) { + return KNOTD_EINVAL; + } + + lock_thread_rw(thread); + + // Update state + int prev_state = thread->state; + thread->state |= ThreadActive; + thread->state &= ~ThreadIdle; + thread->state &= ~ThreadDead; + thread->state &= ~ThreadJoined; + thread->state &= ~ThreadJoinable; + + // Do not re-create running threads + if (prev_state != ThreadJoined) { + dbg_dt("dthreads: [%p] %s: refused to recreate thread\n", + thread, __func__); + unlock_thread_rw(thread); + return 0; + } + + // Start thread + int res = pthread_create(&thread->_thr, /* pthread_t */ + &thread->_attr, /* pthread_attr_t */ + thread_ep, /* routine: thread_ep */ + thread); /* passed object: dthread_t */ + + // Unlock thread + unlock_thread_rw(thread); + return res; +} + +int dt_signalize(dthread_t *thread, int signum) +{ + // Check input + if (thread == 0) { + return KNOTD_EINVAL; + } + + int ret = pthread_kill(thread->_thr, signum); + + /* Not thread id found or invalid signum. */ + if (ret == EINVAL || ret == ESRCH) { + return KNOTD_EINVAL; + } + + /* Generic error. */ + if (ret < 0) { + return KNOTD_ERROR; + } + + return KNOTD_EOK; +} + +int dt_join(dt_unit_t *unit) +{ + // Check input + if (unit == 0) { + return KNOTD_EINVAL; + } + + for (;;) { + + // Lock unit + pthread_mutex_lock(&unit->_report_mx); + dt_unit_lock(unit); + + // Browse threads + int active_threads = 0; + for (int i = 0; i < unit->size; ++i) { + + // Count active or cancelled but pending threads + dthread_t *thread = unit->threads[i]; + lock_thread_rw(thread); + if (thread->state & (ThreadActive|ThreadCancelled)) { + ++active_threads; + } + + // Reclaim dead threads, but only fast + if (thread->state & ThreadJoinable) { + unlock_thread_rw(thread); + dbg_dt_verb("dthreads: [%p] %s: reclaiming\n", + thread, __func__); + pthread_join(thread->_thr, 0); + dbg_dt("dthreads: [%p] %s: reclaimed\n", + thread, __func__); + thread->state = ThreadJoined; + } else { + unlock_thread_rw(thread); + } + } + + // Unlock unit + dt_unit_unlock(unit); + + // Check result + if (active_threads == 0) { + pthread_mutex_unlock(&unit->_report_mx); + break; + } + + // Wait for a thread to finish + pthread_cond_wait(&unit->_report, &unit->_report_mx); + pthread_mutex_unlock(&unit->_report_mx); + } + + return KNOTD_EOK; +} + +int dt_stop_id(dthread_t *thread) +{ + // Check input + if (thread == 0) { + return KNOTD_EINVAL; + } + + // Signalize active thread to stop + lock_thread_rw(thread); + if (thread->state & (ThreadIdle | ThreadActive)) { + thread->state = ThreadDead | ThreadCancelled; + dt_signalize(thread, SIGALRM); + } + unlock_thread_rw(thread); + + // Broadcast notification + dt_unit_t *unit = thread->unit; + if (unit != 0) { + pthread_mutex_lock(&unit->_notify_mx); + pthread_cond_broadcast(&unit->_notify); + pthread_mutex_unlock(&unit->_notify_mx); + } + + return KNOTD_EOK; +} + +int dt_stop(dt_unit_t *unit) +{ + // Check unit + if (unit == 0) { + return KNOTD_EINVAL; + } + + // Lock unit + pthread_mutex_lock(&unit->_notify_mx); + dt_unit_lock(unit); + + // Signalize all threads to stop + for (int i = 0; i < unit->size; ++i) { + + // Lock thread + dthread_t *thread = unit->threads[i]; + lock_thread_rw(thread); + if (thread->state & (ThreadIdle | ThreadActive)) { + thread->state = ThreadDead | ThreadCancelled; + dbg_dt("dthreads: [%p] %s: stopping thread\n", + thread, __func__); + dt_signalize(thread, SIGALRM); + } + unlock_thread_rw(thread); + } + + // Unlock unit + dt_unit_unlock(unit); + + // Broadcast notification + pthread_cond_broadcast(&unit->_notify); + pthread_mutex_unlock(&unit->_notify_mx); + + return KNOTD_EOK; +} + +int dt_setprio(dthread_t *thread, int prio) +{ + // Check input + if (thread == 0) { + return KNOTD_EINVAL; + } + + // Clamp priority + int policy = SCHED_FIFO; + prio = MIN(MAX(sched_get_priority_min(policy), prio), + sched_get_priority_max(policy)); + + // Update scheduler policy + int ret = pthread_attr_setschedpolicy(&thread->_attr, policy); + + // Update priority + if (ret >= 0) { + struct sched_param sp; + sp.sched_priority = prio; + ret = pthread_attr_setschedparam(&thread->_attr, &sp); + } + + /* Map error codes. */ + if (ret < 0) { + dbg_dt("dthreads: [%p] %s(%d): failed", + thread, __func__, prio); + + /* Map "not supported". */ + if (ret == ENOTSUP) { + return KNOTD_ENOTSUP; + } + + return KNOTD_EINVAL; + } + + return KNOTD_EOK; +} + +int dt_repurpose(dthread_t *thread, runnable_t runnable, void *data) +{ + // Check + if (thread == 0) { + return KNOTD_EINVAL; + } + + // Stop here if thread isn't a member of a unit + dt_unit_t *unit = thread->unit; + if (unit == 0) { + lock_thread_rw(thread); + thread->state = ThreadActive | ThreadCancelled; + unlock_thread_rw(thread); + return KNOTD_ENOTSUP; + } + + // Lock thread state changes + pthread_mutex_lock(&unit->_notify_mx); + lock_thread_rw(thread); + + // Repurpose it's object and runnable + thread->run = runnable; + thread->_adata = data; + + // Cancel current runnable if running + if (thread->state & (ThreadIdle | ThreadActive)) { + + // Update state + thread->state = ThreadActive | ThreadCancelled; + unlock_thread_rw(thread); + + // Notify thread + pthread_cond_broadcast(&unit->_notify); + pthread_mutex_unlock(&unit->_notify_mx); + } else { + unlock_thread_rw(thread); + pthread_mutex_unlock(&unit->_notify_mx); + } + + return KNOTD_EOK; +} + +int dt_activate(dthread_t *thread) +{ + return dt_update_thread(thread, ThreadActive); +} + +int dt_cancel(dthread_t *thread) +{ + return dt_update_thread(thread, ThreadIdle | ThreadCancelled); +} + +int dt_compact(dt_unit_t *unit) +{ + // Check input + if (unit == 0) { + return KNOTD_EINVAL; + } + + // Lock unit + pthread_mutex_lock(&unit->_notify_mx); + dt_unit_lock(unit); + + // Reclaim all Idle threads + for (int i = 0; i < unit->size; ++i) { + + // Locked state update + dthread_t *thread = unit->threads[i]; + lock_thread_rw(thread); + if (thread->state & (ThreadIdle)) { + thread->state = ThreadDead | ThreadCancelled; + dt_signalize(thread, SIGALRM); + } + unlock_thread_rw(thread); + } + + // Notify all threads + pthread_cond_broadcast(&unit->_notify); + pthread_mutex_unlock(&unit->_notify_mx); + + // Join all threads + for (int i = 0; i < unit->size; ++i) { + + // Reclaim all dead threads + dthread_t *thread = unit->threads[i]; + lock_thread_rw(thread); + if (thread->state & (ThreadDead)) { + dbg_dt_verb("dthreads: [%p] %s: reclaiming thread\n", + thread, __func__); + unlock_thread_rw(thread); + pthread_join(thread->_thr, 0); + dbg_dt("dthreads: [%p] %s: thread reclaimed\n", + thread, __func__); + thread->state = ThreadJoined; + } else { + unlock_thread_rw(thread); + } + } + + dbg_dt_verb("dthreads: compact: joined all threads\n"); + + // Unlock unit + dt_unit_unlock(unit); + + return KNOTD_EOK; +} + +int dt_optimal_size() +{ +#ifdef _SC_NPROCESSORS_ONLN + int ret = (int) sysconf(_SC_NPROCESSORS_ONLN); + if (ret >= 1) { + return ret + CPU_ESTIMATE_MAGIC; + } +#endif + dbg_dt("dthreads: failed to fetch the number of online CPUs."); + return DEFAULT_THR_COUNT; +} + +/*! + * \todo Use memory barriers or asynchronous read-only access, locking + * poses a thread performance decrease by 1.31%. + */ + +int dt_is_cancelled(dthread_t *thread) +{ + // Check input + if (thread == 0) { + return 0; + } + + lock_thread_rw(thread); + int ret = thread->state & ThreadCancelled; + unlock_thread_rw(thread); + return ret; +} + +int dt_unit_lock(dt_unit_t *unit) +{ + // Check input + if (unit == 0) { + return KNOTD_EINVAL; + } + + int ret = pthread_mutex_lock(&unit->_mx); + + /* Map errors. */ + if (ret < 0) { + return knot_map_errno(EINVAL, EAGAIN); + } + + return KNOTD_EOK; +} + +int dt_unit_unlock(dt_unit_t *unit) +{ + // Check input + if (unit == 0) { + return KNOTD_EINVAL; + } + + int ret = pthread_mutex_unlock(&unit->_mx); + + /* Map errors. */ + if (ret < 0) { + return knot_map_errno(EINVAL, EAGAIN); + } + + return KNOTD_EOK; +} diff --git a/src/knot/server/dthreads.h b/src/knot/server/dthreads.h new file mode 100644 index 0000000..8a5e2b4 --- /dev/null +++ b/src/knot/server/dthreads.h @@ -0,0 +1,353 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file dthreads.h + * + * \author Marek Vavrusa <marek.vavusa@nic.cz> + * + * \brief Threading API. + * + * Dynamic threads provide: + * - coherent and incoherent threading capabilities + * - thread repurposing + * - thread prioritization + * - on-the-fly changing of threading unit size + * + * Coherent threading unit is when all threads execute + * the same runnable function. + * + * Incoherent function is when at least one thread executes + * a different runnable than the others. + * + * \addtogroup threading + * @{ + */ + +#ifndef _KNOTD_DTHREADS_H_ +#define _KNOTD_DTHREADS_H_ + +#include <pthread.h> + +/* Forward decls */ +struct dthread_t; +struct dt_unit_t; + +/*! + * \brief Thread state enumeration. + */ +typedef enum { + ThreadJoined = 1 << 0, /*!< Thread is finished and joined. */ + ThreadJoinable = 1 << 1, /*!< Thread is waiting to be reclaimed. */ + ThreadCancelled = 1 << 2, /*!< Thread is cancelled, finishing task. */ + ThreadDead = 1 << 3, /*!< Thread is finished, exiting. */ + ThreadIdle = 1 << 4, /*!< Thread is idle, waiting for purpose. */ + ThreadActive = 1 << 5 /*!< Thread is active, working on a task. */ + +} dt_state_t; + +/*! + * \brief Thread runnable prototype. + * + * Runnable is basically a pointer to function which is called on active + * thread runtime. + * + * \note When implementing a runnable, keep in mind to check thread state as + * it may change, and implement a cooperative cancellation point. + * + * Implement this by checking dt_is_cancelled() and return + * as soon as possible. + */ +typedef int (*runnable_t)(struct dthread_t *); + +/*! + * \brief Single thread descriptor public API. + */ +typedef struct dthread_t { + volatile unsigned state; /*!< Bitfield of dt_flag flags. */ + runnable_t run; /*!< Runnable function or 0. */ + void *data; /*!< Currently active data */ + struct dt_unit_t *unit; /*!< Reference to assigned unit. */ + void *_adata; /* Thread-specific data. */ + pthread_t _thr; /* Thread */ + pthread_attr_t _attr; /* Thread attributes */ + pthread_mutex_t _mx; /* Thread state change lock. */ +} dthread_t; + +/*! + * \brief Thread unit descriptor API. + * + * Thread unit consists of 1..N threads. + * Unit is coherent if all threads execute + * the same runnable. + */ +typedef struct dt_unit_t { + int size; /*!< Unit width (number of threads) */ + struct dthread_t **threads; /*!< Array of threads */ + pthread_cond_t _notify; /* Notify thread */ + pthread_mutex_t _notify_mx; /* Condition mutex */ + pthread_cond_t _report; /* Report thread state */ + pthread_mutex_t _report_mx; /* Condition mutex */ + pthread_mutex_t _mx; /* Unit lock */ +} dt_unit_t; + +/*! + * \brief Create a set of threads with no initial runnable. + * + * \note All threads are created with Dead state. + * This means, they're not physically created unit dt_start() is called. + * + * \param count Requested thread count. + * + * \retval New instance if successful + * \retval NULL on error + */ +dt_unit_t *dt_create(int count); + +/*! + * \brief Create a set of coherent threads. + * + * Coherent means, that the threads will share a common runnable and the data. + * + * \param count Requested thread count. + * \param runnable Runnable function for all threads. + * \param data Any data passed onto threads. + * + * \retval New instance if successful + * \retval NULL on error + */ +dt_unit_t *dt_create_coherent(int count, runnable_t runnable, void *data); + +/*! + * \brief Free unit. + * + * \warning Behavior is undefined if threads are still active, make sure + * to call dt_join() first. + * + * \param unit Unit to be deleted. + */ +void dt_delete(dt_unit_t **unit); + +/*! + * \brief Resize unit to given number. + * + * \note Newly created dthreads will have no runnable or data, their state + * will be ThreadJoined (that means no thread will be physically created + * until the next dt_start()). + * + * \warning Be careful when shrinking unit, joined and idle threads are + * reclaimed first, but it may kill your active threads + * as a last resort. + * Threads will stop at their nearest cancellation point, + * so this is potentially an expensive and blocking operation. + * + * \param unit Unit to be resized. + * \param size New unit size. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_ENOMEM out of memory error. + */ +int dt_resize(dt_unit_t *unit, int size); + +/*! + * \brief Start all threads in selected unit. + * + * \param unit Unit to be started. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters (unit is null). + */ +int dt_start(dt_unit_t *unit); + +/*! + * \brief Start given thread. + * + * \param thread Target thread instance. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + */ +int dt_start_id(dthread_t *thread); + +/*! + * \brief Send given signal to thread. + * + * \note This is useful to interrupt some blocking I/O as well, for example + * with SIGALRM, which is handled by default. + * \note Signal handler may be overriden in runnable. + * + * \param thread Target thread instance. + * \param signum Signal code. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_ERROR unspecified error. + */ +int dt_signalize(dthread_t *thread, int signum); + +/*! + * \brief Wait for all thread in unit to finish. + * + * \param unit Unit to be joined. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + */ +int dt_join(dt_unit_t *unit); + +/*! + * \brief Stop thread from running. + * + * Active thread is interrupted at the nearest runnable cancellation point. + * + * \param thread Target thread instance. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + */ +int dt_stop_id(dthread_t *thread); + +/*! + * \brief Stop all threads in unit. + * + * Thread is interrupted at the nearest runnable cancellation point. + * + * \param unit Unit to be stopped. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + */ +int dt_stop(dt_unit_t *unit); + +/*! + * \brief Modify thread priority. + * + * \param thread Target thread instance. + * \param prio Requested priority (positive integer, default is 0). + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + */ +int dt_setprio(dthread_t *thread, int prio); + +/*! + * \brief Set thread to execute another runnable. + * + * \param thread Target thread instance. + * \param runnable Runnable function for target thread. + * \param data Data passed to target thread. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_ENOTSUP operation not supported. + */ +int dt_repurpose(dthread_t *thread, runnable_t runnable, void *data); + +/*! + * \brief Wake up thread from idle state. + * + * Thread is awoken from idle state and reenters runnable. + * This function only affects idle threads. + * + * \note Unit needs to be started with dt_start() first, as the function + * doesn't affect dead threads. + * + * \param thread Target thread instance. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_ENOTSUP operation not supported. + */ +int dt_activate(dthread_t *thread); + +/*! + * \brief Put thread to idle state, cancells current runnable function. + * + * Thread is flagged with Cancel flag and returns from runnable at the nearest + * cancellation point, which requires complying runnable function. + * + * \note Thread isn't disposed, but put to idle state until it's requested + * again or collected by dt_compact(). + * + * \param thread Target thread instance. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + */ +int dt_cancel(dthread_t *thread); + +/*! + * \brief Collect and dispose idle threads. + * + * \param unit Target unit instance. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + */ +int dt_compact(dt_unit_t *unit); + +/*! + * \brief Return optimal number of threads for instance. + * + * It is estimated as NUM_CPUs + 1. + * Fallback is DEFAULT_THR_COUNT (\see common.h). + * + * \return Number of threads. + */ +int dt_optimal_size(); + +/*! + * \brief Return true if thread is cancelled. + * + * Synchronously check for ThreadCancelled flag. + * + * \param thread Target thread instance. + * + * \retval 1 if cancelled. + * \retval 0 if not cancelled. + */ +int dt_is_cancelled(dthread_t *thread); + +/*! + * \brief Lock unit to prevent parallel operations which could alter unit + * at the same time. + * + * \param unit Target unit instance. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_EAGAIN lack of resources to lock unit, try again. + * \retval KNOTD_ERROR unspecified error. + */ +int dt_unit_lock(dt_unit_t *unit); + +/*! + * \brief Unlock unit. + * + * \see dt_unit_lock() + * + * \param unit Target unit instance. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_EAGAIN lack of resources to unlock unit, try again. + * \retval KNOTD_ERROR unspecified error. + */ +int dt_unit_unlock(dt_unit_t *unit); + +#endif // _KNOTD_DTHREADS_H_ + +/*! @} */ diff --git a/src/knot/server/journal.c b/src/knot/server/journal.c new file mode 100644 index 0000000..651f0f3 --- /dev/null +++ b/src/knot/server/journal.c @@ -0,0 +1,636 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "knot/other/error.h" +#include "knot/other/debug.h" +#include "journal.h" + +/*! \brief Infinite file size limit. */ +#define FSLIMIT_INF (~((size_t)0)) + +/*! \brief Node classification macros. */ +#define jnode_flags(j, i) ((j)->nodes[(i)].flags) + +/*! \brief Next node. */ +#define jnode_next(j, i) (((i) + 1) % (j)->max_nodes) + +/*! \brief Previous node. */ +#define jnode_prev(j, i) (((i) == 0) ? (j)->max_nodes - 1 : (i) - 1) + +static inline int sfread(void *dst, size_t len, int fd) +{ + return read(fd, dst, len) == len; +} + +static inline int sfwrite(const void *src, size_t len, int fd) +{ + return write(fd, src, len) == len; +} + +/*! \brief Equality compare function. */ +static inline int journal_cmp_eq(uint64_t k1, uint64_t k2) +{ + if (k1 == k2) { + return 0; + } + + if (k1 < k2) { + return -1; + } + + return 1; +} + +/*! \brief Recover metadata from journal. */ +static int journal_recover(journal_t *j) +{ + /* Attempt to recover queue. */ + int qstate[2] = { -1, -1 }; + unsigned c = 0, p = j->max_nodes - 1; + while (1) { + + /* Fetch previous and current node. */ + journal_node_t *np = j->nodes + p; + journal_node_t *nc = j->nodes + c; + + /* Check flags + * p c (0 = free, 1 = non-free) + * 0 0 - in free segment + * 0 1 - c-node is qhead + * 1 0 - c-node is qtail + * 1 1 - in full segment + */ + unsigned c_set = (nc->flags > JOURNAL_FREE); + unsigned p_set = (np->flags > JOURNAL_FREE); + if (!p_set && c_set && qstate[0] < 0) { + qstate[0] = c; /* Recovered qhead. */ + dbg_journal_verb("journal: recovered qhead=%u\n", + qstate[0]); + } + if (p_set && !c_set && qstate[1] < 0) {\ + qstate[1] = c; /* Recovered qtail. */ + dbg_journal_verb("journal: recovered qtail=%u\n", + qstate[1]); + } + + /* Both qstates set. */ + if (qstate[0] > -1 && qstate[1] > -1) { + break; + } + + /* Set prev and next. */ + p = c; + c = (c + 1) % j->max_nodes; + + /* All nodes probed. */ + if (c == 0) { + dbg_journal("journal: failed to recover node queue\n"); + break; + } + } + + /* Evaluate */ + if (qstate[0] < 0 || qstate[1] < 0) { + return KNOTD_ERANGE; + } + + /* Write back. */ + lseek(j->fd, JOURNAL_HSIZE - 2 * sizeof(uint16_t), SEEK_SET); + if (!sfwrite(qstate, 2 * sizeof(uint16_t), j->fd)) { + dbg_journal("journal: failed to write back queue state\n"); + return KNOTD_ERROR; + } + + /* Reset queue state. */ + j->qhead = qstate[0]; + j->qtail = qstate[1]; + dbg_journal("journal: node queue=<%u,%u> recovered\n", + qstate[0], qstate[1]); + + + return KNOTD_EOK; +} + +int journal_create(const char *fn, uint16_t max_nodes) +{ + /* File lock. */ + struct flock fl; + memset(&fl, 0, sizeof(struct flock)); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_pid = getpid(); + + /* Create journal file. */ + int fd = open(fn, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU|S_IRWXG); + if (fd < 0) { + dbg_journal("journal: failed to create file '%s'\n", fn); + return KNOTD_EINVAL; + } + + /* Lock. */ + fcntl(fd, F_SETLKW, &fl); + fl.l_type = F_UNLCK; + + /* Create journal header. */ + dbg_journal("journal: creating header\n"); + if (!sfwrite(&max_nodes, sizeof(uint16_t), fd)) { + fcntl(fd, F_SETLK, &fl); + close(fd); + remove(fn); + return KNOTD_ERROR; + } + + /* Create node queue head + tail. + * qhead points to least recent node + * qtail points to next free node + * qhead == qtail means empty queue + */ + uint16_t zval = 0; + if (!sfwrite(&zval, sizeof(uint16_t), fd)) { + fcntl(fd, F_SETLK, &fl); + close(fd); + remove(fn); + return KNOTD_ERROR; + } + + if (!sfwrite(&zval, sizeof(uint16_t), fd)) { + fcntl(fd, F_SETLK, &fl); + close(fd); + remove(fn); + return KNOTD_ERROR; + } + + dbg_journal_verb("journal: creating free segment descriptor\n"); + + /* Create free segment descriptor. */ + journal_node_t jn; + memset(&jn, 0, sizeof(journal_node_t)); + jn.id = 0; + jn.flags = JOURNAL_VALID; + jn.pos = JOURNAL_HSIZE + (max_nodes + 1) * sizeof(journal_node_t); + jn.len = 0; + if (!sfwrite(&jn, sizeof(journal_node_t), fd)) { + fcntl(fd, F_SETLK, &fl); + close(fd); + remove(fn); + return KNOTD_ERROR; + } + + /* Create nodes. */ + dbg_journal("journal: creating node table, size=%u\n", max_nodes); + memset(&jn, 0, sizeof(journal_node_t)); + for(uint16_t i = 0; i < max_nodes; ++i) { + if (!sfwrite(&jn, sizeof(journal_node_t), fd)) { + fcntl(fd, F_SETLK, &fl); + close(fd); + remove(fn); + return KNOTD_ERROR; + } + } + + /* Unlock and close. */ + fcntl(fd, F_SETLK, &fl); + close(fd); + + /* Journal file created. */ + dbg_journal("journal: file '%s' initialized\n", fn); + return KNOTD_EOK; +} + +journal_t* journal_open(const char *fn, size_t fslimit, uint16_t bflags) +{ + /*! \todo Memory mapping may be faster than stdio? */ + + /* File lock. */ + struct flock fl; + memset(&fl, 0, sizeof(struct flock)); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_pid = getpid(); + + /* Check file. */ + struct stat st; + if (stat(fn, &st) < 0) { + return 0; + } + + /* Open journal file for r/w. */ + int fd = open(fn, O_RDWR); + if (fd < 0) { + dbg_journal("journal: failed to open file '%s'\n", fn); + return 0; + } + + /* Attempt to lock. */ + dbg_journal_verb("journal: locking journal %s\n", fn); + int ret = fcntl(fd, F_SETLK, &fl); + + /* Lock. */ + if (ret < 0) { + struct flock efl; + memcpy(&efl, &fl, sizeof(struct flock)); + fcntl(fd, F_GETLK, &efl); + log_server_warning("Journal file '%s' is locked by process " + "PID=%d, waiting for process to " + "release lock.\n", + fn, efl.l_pid); + ret = fcntl(fd, F_SETLKW, &fl); + } + fl.l_type = F_UNLCK; + dbg_journal("journal: locked journal %s (returned %d)\n", fn, ret); + + /* Read maximum number of entries. */ + uint16_t max_nodes = 512; + if (!sfread(&max_nodes, sizeof(uint16_t), fd)) { + fcntl(fd, F_SETLK, &fl); + close(fd); + return 0; + } + + /* Allocate journal structure. */ + const size_t node_len = sizeof(journal_node_t); + journal_t *j = malloc(sizeof(journal_t) + max_nodes * node_len); + if (!j) { + fcntl(fd, F_SETLK, &fl); + close(fd); + return 0; + } + j->qhead = j->qtail = 0; + j->fd = fd; + j->max_nodes = max_nodes; + j->bflags = bflags; + + /* Load node queue state. */ + if (!sfread(&j->qhead, sizeof(uint16_t), fd)) { + fcntl(fd, F_SETLK, &fl); + close(fd); + free(j); + return 0; + } + + /* Load queue tail. */ + if (!sfread(&j->qtail, sizeof(uint16_t), fd)) { + fcntl(fd, F_SETLK, &fl); + close(fd); + free(j); + return 0; + } + + /* Load empty segment descriptor. */ + if (!sfread(&j->free, node_len, fd)) { + fcntl(fd, F_SETLK, &fl); + close(fd); + free(j); + return 0; + } + + /* Read journal descriptors table. */ + if (!sfread(&j->nodes, max_nodes * node_len, fd)) { + fcntl(fd, F_SETLK, &fl); + close(fd); + free(j); + return 0; + } + + /* Set file size. */ + j->fsize = st.st_size; + if (fslimit == 0) { + j->fslimit = FSLIMIT_INF; + } else { + j->fslimit = (size_t)fslimit; + } + + dbg_journal("journal: opened journal size=%u, queue=<%u, %u>, fd=%d\n", + max_nodes, j->qhead, j->qtail, j->fd); + + /* Check node queue. */ + unsigned qtail_free = (jnode_flags(j, j->qtail) <= JOURNAL_FREE); + unsigned qhead_free = j->max_nodes - 1; /* Left of qhead must be free.*/ + if (j->qhead > 0) { + qhead_free = (j->qhead - 1); + } + qhead_free = (jnode_flags(j, qhead_free) <= JOURNAL_FREE); + if ((j->qhead != j->qtail) && (!qtail_free || !qhead_free)) { + log_server_warning("Recovering journal '%s' metadata " + "after crash.\n", + fn); + ret = journal_recover(j); + if (ret != KNOTD_EOK) { + log_server_error("Journal file '%s' is unrecoverable, " + "metadata corrupted - %s\n", + fn, knotd_strerror(ret)); + fcntl(fd, F_SETLK, &fl); + close(fd); + free(j); + return 0; + } + } + + /* Save file lock. */ + fl.l_type = F_WRLCK; + memcpy(&j->fl, &fl, sizeof(struct flock)); + + return j; +} + +int journal_fetch(journal_t *journal, uint64_t id, + journal_cmp_t cf, journal_node_t** dst) +{ + if (journal == 0 || dst == 0) { + return KNOTD_EINVAL; + } + + /* Check compare function. */ + if (!cf) { + cf = journal_cmp_eq; + } + + /*! \todo Organize journal descriptors in btree? */ + size_t i = jnode_prev(journal, journal->qtail); + size_t endp = jnode_prev(journal, journal->qhead); + for(; i != endp; i = jnode_prev(journal, i)) { + if (cf(journal->nodes[i].id, id) == 0) { + *dst = journal->nodes + i; + return KNOTD_EOK; + } + } + + return KNOTD_ENOENT; +} + +int journal_read(journal_t *journal, uint64_t id, journal_cmp_t cf, char *dst) +{ + if (journal == 0 || dst == 0) { + return KNOTD_EINVAL; + } + + journal_node_t *n = 0; + if(journal_fetch(journal, id, cf, &n) != 0) { + dbg_journal("journal: failed to fetch node with id=%llu\n", + (unsigned long long)id); + return KNOTD_ENOENT; + } + + /* Check valid flag. */ + if (!(n->flags & JOURNAL_VALID)) { + dbg_journal("journal: node with id=%llu is invalid " + "(flags=0x%hx)\n", (unsigned long long)id, n->flags); + return KNOTD_EINVAL; + } + + dbg_journal("journal: reading node with id=%llu, data=<%u, %u>, flags=0x%hx\n", + (unsigned long long)id, n->pos, n->pos + n->len, n->flags); + + /* Seek journal node. */ + lseek(journal->fd, n->pos, SEEK_SET); + + /* Read journal node content. */ + if (!sfread(dst, n->len, journal->fd)) { + return KNOTD_ERROR; + } + + return KNOTD_EOK; +} + +int journal_write(journal_t *journal, uint64_t id, const char *src, size_t size) +{ + if (journal == 0 || src == 0) { + return KNOTD_EINVAL; + } + + const size_t node_len = sizeof(journal_node_t); + + /* Find next free node. */ + uint16_t jnext = (journal->qtail + 1) % journal->max_nodes; + + dbg_journal("journal: will write id=%llu, node=%u, size=%zu, fsize=%zu\n", + (unsigned long long)id, journal->qtail, size, journal->fsize); + + /* Calculate remaining bytes to reach file size limit. */ + size_t fs_remaining = journal->fslimit - journal->fsize; + + /* Increase free segment if on the end of file. */ + journal_node_t *n = journal->nodes + journal->qtail; + if (journal->free.pos + journal->free.len == journal->fsize) { + + dbg_journal_verb("journal: * is last node\n"); + + /* Grow journal file until the size limit. */ + if(journal->free.len < size && size <= fs_remaining) { + size_t diff = size - journal->free.len; + dbg_journal("journal: * growing by +%zu, pos=%u, " + "new fsize=%zu\n", + diff, journal->free.pos, + journal->fsize + diff); + journal->fsize += diff; /* Appending increases file size. */ + journal->free.len += diff; + + } + + /* Rewind if resize is needed, but the limit is reached. */ + if(journal->free.len < size && size > fs_remaining) { + journal_node_t *head = journal->nodes + journal->qhead; + journal->fsize = journal->free.pos; + journal->free.pos = head->pos; + journal->free.len = 0; + dbg_journal_verb("journal: * fslimit reached, " + "rewinding to %u\n", + head->pos); + dbg_journal_verb("journal: * file size trimmed to %zu\n", + journal->fsize); + } + } + + /* Evict occupied nodes if necessary. */ + while (journal->free.len < size || + journal->nodes[jnext].flags > JOURNAL_FREE) { + + /* Evict least recent node if not empty. */ + journal_node_t *head = journal->nodes + journal->qhead; + + /* Check if it has been synced to disk. */ + if (head->flags & JOURNAL_DIRTY) { + return KNOTD_EAGAIN; + } + + /* Write back evicted node. */ + head->flags = JOURNAL_FREE; + lseek(journal->fd, JOURNAL_HSIZE + (journal->qhead + 1) * node_len, SEEK_SET); + if (!sfwrite(head, node_len, journal->fd)) { + return KNOTD_ERROR; + } + + dbg_journal("journal: * evicted node=%u, growing by +%u\n", + journal->qhead, head->len); + + /* Write back query state. */ + journal->qhead = (journal->qhead + 1) % journal->max_nodes; + uint16_t qstate[2] = {journal->qhead, journal->qtail}; + lseek(journal->fd, JOURNAL_HSIZE - 2 * sizeof(uint16_t), SEEK_SET); + if (!sfwrite(qstate, 2 * sizeof(uint16_t), journal->fd)) { + return KNOTD_ERROR; + } + + /* Increase free segment. */ + journal->free.len += head->len; + } + + /* Invalidate node and write back. */ + n->id = id; + n->pos = journal->free.pos; + n->len = size; + n->flags = JOURNAL_FREE; + journal_update(journal, n); + + /* Write data to permanent storage. */ + lseek(journal->fd, n->pos, SEEK_SET); + if (!sfwrite(src, size, journal->fd)) { + return KNOTD_ERROR; + } + + /* Mark node as valid and write back. */ + n->flags = JOURNAL_VALID | journal->bflags; + journal_update(journal, n); + + /* Handle free segment on node rotation. */ + if (journal->qtail > jnext && journal->fslimit == FSLIMIT_INF) { + /* Trim free space. */ + journal->fsize -= journal->free.len; + dbg_journal_verb("journal: * trimmed filesize to %zu\n", + journal->fsize); + + /* Rewind free segment. */ + journal_node_t *n = journal->nodes + jnext; + journal->free.pos = n->pos; + journal->free.len = 0; + + } else { + /* Mark used space. */ + journal->free.pos += size; + journal->free.len -= size; + } + dbg_journal("journal: finished node=%u, data=<%u, %u> free=<%u, %u>\n", + journal->qtail, n->pos, n->pos + n->len, + journal->free.pos, + journal->free.pos + journal->free.len); + + /* Write back free segment state. */ + lseek(journal->fd, JOURNAL_HSIZE, SEEK_SET); + if (!sfwrite(&journal->free, node_len, journal->fd)) { + /* Node is marked valid and failed to shrink free space, + * node will be overwritten on the next write. Return error. + */ + dbg_journal("journal: failed to write back " + "free segment descriptor\n"); + return KNOTD_ERROR; + } + + /* Node write successful. */ + journal->qtail = jnext; + + /* Write back queue state, not essential as it may be recovered. + * qhead - lowest valid node identifier (least recent) + * qtail - highest valid node identifier (most recently used) + */ + uint16_t qstate[2] = {journal->qhead, journal->qtail}; + lseek(journal->fd, JOURNAL_HSIZE - 2 * sizeof(uint16_t), SEEK_SET); + if (!sfwrite(qstate, 2 * sizeof(uint16_t), journal->fd)) { + dbg_journal("journal: failed to write back queue state\n"); + return KNOTD_ERROR; + } + + /*! \todo Delayed write-back? */ + dbg_journal_verb("journal: write of finished, nqueue=<%u, %u>\n", + journal->qhead, journal->qtail); + + return KNOTD_EOK; +} + +int journal_walk(journal_t *journal, journal_apply_t apply) +{ + int ret = KNOTD_EOK; + size_t i = journal->qhead; + for(; i != journal->qtail; i = (i + 1) % journal->max_nodes) { + /* Apply function. */ + ret = apply(journal, journal->nodes + i); + } + + return ret; +} + +int journal_update(journal_t *journal, journal_node_t *n) +{ + if (!journal || !n) { + return KNOTD_EINVAL; + } + + /* Calculate node offset. */ + const size_t node_len = sizeof(journal_node_t); + size_t i = n - journal->nodes; + if (i > journal->max_nodes) { + return KNOTD_EINVAL; + } + + /* Calculate node position in permanent storage. */ + long jn_fpos = JOURNAL_HSIZE + (i + 1) * node_len; + + dbg_journal("journal: syncing journal node=%zu at %ld\n", + i, jn_fpos); + + /* Write back. */ + lseek(journal->fd, jn_fpos, SEEK_SET); + if (!sfwrite(n, node_len, journal->fd)) { + dbg_journal("journal: failed to writeback node=%llu to %ld\n", + (unsigned long long)n->id, jn_fpos); + return KNOTD_ERROR; + } + + return KNOTD_EOK; +} + +int journal_close(journal_t *journal) +{ + /* Check journal. */ + if (!journal) { + return KNOTD_EINVAL; + } + + /* Unlock journal file. */ + journal->fl.l_type = F_UNLCK; + fcntl(journal->fd, F_SETLK, &journal->fl); + dbg_journal("journal: unlocked journal %p\n", journal); + + /* Close file. */ + close(journal->fd); + + dbg_journal("journal: closed journal %p\n", journal); + + /* Free allocated resources. */ + free(journal); + + return KNOTD_EOK; +} diff --git a/src/knot/server/journal.h b/src/knot/server/journal.h new file mode 100644 index 0000000..321b591 --- /dev/null +++ b/src/knot/server/journal.h @@ -0,0 +1,243 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file journal.h + * + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Journal for storing transactions on permanent storage. + * + * Journal stores entries on a permanent storage. + * Each written entry is guaranteed to persist until + * the maximum file size or node count is reached. + * Entries are removed from the least recent. + * + * Journal file structure + * <pre> + * uint16_t node_count + * uint16_t node_queue_head + * uint16_t node_queue_tail + * journal_entry_t free_segment + * node_count *journal_entry_t + * ...data... + * </pre> + * \addtogroup utils + * @{ + */ + +#ifndef _KNOTD_JOURNAL_H_ +#define _KNOTD_JOURNAL_H_ + +#include <stdint.h> +#include <fcntl.h> + +/*! + * \brief Journal entry flags. + */ +typedef enum journal_flag_t { + JOURNAL_NULL = 0 << 0, /*!< Invalid journal entry. */ + JOURNAL_FREE = 1 << 0, /*!< Free journal entry. */ + JOURNAL_VALID = 1 << 1, /*!< Valid journal entry. */ + JOURNAL_DIRTY = 1 << 2 /*!< Journal entry cannot be evicted. */ +} journal_flag_t; + +/*! + * \brief Journal node structure. + * + * Each node represents journal entry and points + * to position of the data in the permanent storage. + */ +typedef struct journal_node_t +{ + uint64_t id; /*!< Node ID. */ + uint16_t flags; /*!< Node flags. */ + uint32_t pos; /*!< Position in journal file. */ + uint32_t len; /*!< Entry data length. */ +} journal_node_t; + +/*! + * \brief Journal structure. + * + * Journal organizes entries as nodes. + * Nodes are stored in-memory for fast lookup and also + * backed by a permanent storage. + * Each journal has a fixed number of nodes. + * + * \todo Organize nodes in an advanced structure, like + * btree or hash table to improve lookup time. + */ +typedef struct journal_t +{ + int fd; + struct flock fl; /*!< File lock. */ + uint16_t max_nodes; /*!< Number of nodes. */ + uint16_t qhead; /*!< Node queue head. */ + uint16_t qtail; /*!< Node queue tail. */ + uint16_t bflags; /*!< Initial flags for each written node. */ + size_t fsize; /*!< Journal file size. */ + size_t fslimit; /*!< File size limit. */ + journal_node_t free; /*!< Free segment. */ + journal_node_t nodes[]; /*!< Array of nodes. */ +} journal_t; + +/*! + * \brief Entry identifier compare function. + * + * \retval -n if k1 < k2 + * \retval +n if k1 > k2 + * \retval 0 if k1 == k2 + */ +typedef int (*journal_cmp_t)(uint64_t k1, uint64_t k2); + +/*! + * \brief Function prototype for journal_walk() function. + * + * \param j Associated journal. + * \param n Pointer to target node. + */ +typedef int (*journal_apply_t)(journal_t *j, journal_node_t *n); + +/* + * Journal defaults and constants. + */ +#define JOURNAL_NCOUNT 1024 /*!< Default node count. */ +#define JOURNAL_HSIZE (sizeof(uint16_t) * 3) /*!< max_entries, qhead, qtail */ + +/*! + * \brief Create new journal. + * + * \param fn Journal file name, will be created if not exist. + * \param max_nodes Maximum number of nodes in journal. + * + * \retval KNOTD_EOK if successful. + * \retval KNOTD_EINVAL if the file with given name cannot be created. + * \retval KNOTD_ERROR on I/O error. + */ +int journal_create(const char *fn, uint16_t max_nodes); + +/*! + * \brief Open journal file for read/write. + * + * \param fn Journal file name. + * \param fslimit File size limit (0 for no limit). + * \param bflags Initial flags for each written node. + * + * \retval new journal instance if successful. + * \retval NULL on error. + */ +journal_t* journal_open(const char *fn, size_t fslimit, uint16_t bflags); + +/*! + * \brief Fetch entry node for given identifier. + * + * \param journal Associated journal. + * \param id Entry identifier. + * \param cf Compare function (NULL for equality). + * \param dst Destination for journal entry. + * + * \retval KNOTD_EOK if successful. + * \retval KNOTD_ENOENT if not found. + */ +int journal_fetch(journal_t *journal, uint64_t id, + journal_cmp_t cf, journal_node_t** dst); + +/*! + * \brief Read journal entry data. + * + * \param journal Associated journal. + * \param id Entry identifier. + * \param cf Compare function (NULL for equality). + * \param dst Pointer to destination memory. + * + * \retval KNOTD_EOK if successful. + * \retval KNOTD_ENOENT if the entry cannot be found. + * \retval KNOTD_EINVAL if the entry is invalid. + * \retval KNOTD_ERROR on I/O error. + */ +int journal_read(journal_t *journal, uint64_t id, journal_cmp_t cf, char *dst); + +/*! + * \brief Write journal entry data. + * + * \param journal Associated journal. + * \param id Entry identifier. + * \param src Pointer to source data. + * + * \retval KNOTD_EOK if successful. + * \retval KNOTD_EAGAIN if no free node is available, need to remove dirty nodes. + * \retval KNOTD_ERROR on I/O error. + */ +int journal_write(journal_t *journal, uint64_t id, const char *src, size_t size); + +/*! + * \brief Return least recent node (journal head). + * + * \param journal Associated journal. + * + * \retval node if successful. + * \retval NULL if empty. + */ +static inline journal_node_t *journal_head(journal_t *journal) { + return journal->nodes + journal->qhead; +} + +/*! + * \brief Return node after most recent node (journal tail). + * + * \param journal Associated journal. + * + * \retval node if successful. + * \retval NULL if empty. + */ +static inline journal_node_t *journal_end(journal_t *journal) { + return journal->nodes + journal->qtail; +} + +/*! + * \brief Apply function to each node. + * + * \param journal Associated journal. + * \param apply Function to apply to each node. + * + * \retval KNOTD_EOK if successful. + * \retval KNOTD_EINVAL on invalid parameters. + */ +int journal_walk(journal_t *journal, journal_apply_t apply); + +/*! + * \brief Sync node state to permanent storage. + * + * \note May be used for journal_walk(). + * + * \param journal Associated journal. + * \param n Pointer to node (must belong to associated journal). + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + */ +int journal_update(journal_t *journal, journal_node_t *n); + +/*! + * \brief Close journal file. + * + * \param journal Associated journal. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameter. + */ +int journal_close(journal_t *journal); + +#endif /* _KNOTD_JOURNAL_H_ */ diff --git a/src/knot/server/notify.c b/src/knot/server/notify.c new file mode 100644 index 0000000..3966b26 --- /dev/null +++ b/src/knot/server/notify.c @@ -0,0 +1,327 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "knot/server/notify.h" + +#include "libknot/dname.h" +#include "libknot/packet/packet.h" +#include "libknot/rrset.h" +#include "libknot/packet/response.h" +#include "libknot/packet/query.h" +#include "libknot/consts.h" +#include "knot/other/error.h" +#include "libknot/zone/zonedb.h" +#include "libknot/common.h" +#include "libknot/util/error.h" +#include "libknot/util/wire.h" +#include "knot/server/zones.h" +#include "common/acl.h" +#include "common/evsched.h" +#include "knot/other/debug.h" +#include "knot/server/server.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ + +static int notify_request(const knot_rrset_t *rrset, + uint8_t *buffer, size_t *size) +{ + knot_packet_t *pkt = knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + CHECK_ALLOC_LOG(pkt, KNOTD_ENOMEM); + + /*! \todo Get rid of the numeric constant. */ + int rc = knot_packet_set_max_size(pkt, 512); + if (rc != KNOT_EOK) { + knot_packet_free(&pkt); + return KNOTD_ERROR; + } + + rc = knot_query_init(pkt); + if (rc != KNOT_EOK) { + knot_packet_free(&pkt); + return KNOTD_ERROR; + } + + knot_question_t question; + + // this is ugly!! + question.qname = rrset->owner; + question.qtype = rrset->type; + question.qclass = rrset->rclass; + + rc = knot_query_set_question(pkt, &question); + if (rc != KNOT_EOK) { + knot_packet_free(&pkt); + return KNOTD_ERROR; + } + + /* Set random query ID. */ + knot_packet_set_random_id(pkt); + knot_wire_set_id(pkt->wireformat, pkt->header.id); + + /*! \todo add the SOA RR to the Answer section as a hint */ + /*! \todo this should not use response API!! */ +// rc = knot_response_add_rrset_answer(pkt, rrset, 0, 0, 0); +// if (rc != KNOT_EOK) { +// knot_packet_free(&pkt); +// return rc; +// } + + /*! \todo this should not use response API!! */ + knot_response_set_aa(pkt); + + knot_query_set_opcode(pkt, KNOT_OPCODE_NOTIFY); + + /*! \todo OPT RR ?? */ + + uint8_t *wire = NULL; + size_t wire_size = 0; + rc = knot_packet_to_wire(pkt, &wire, &wire_size); + if (rc != KNOT_EOK) { + knot_packet_free(&pkt); + return KNOTD_ERROR; + } + + if (wire_size > *size) { + knot_packet_free(&pkt); + return KNOTD_ESPACE; + } + + memcpy(buffer, wire, wire_size); + *size = wire_size; + + knot_packet_dump(pkt); + + knot_packet_free(&pkt); + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int notify_create_response(knot_packet_t *request, uint8_t *buffer, + size_t *size) +{ + knot_packet_t *response = + knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + CHECK_ALLOC_LOG(response, KNOTD_ENOMEM); + + /* Set maximum packet size. */ + knot_packet_set_max_size(response, *size); + knot_response_init_from_query(response, request); + + // TODO: copy the SOA in Answer section + uint8_t *wire = NULL; + size_t wire_size = 0; + int rc = knot_packet_to_wire(response, &wire, &wire_size); + if (rc != KNOT_EOK) { + knot_packet_free(&response); + return rc; + } + + if (wire_size > *size) { + knot_packet_free(&response); + return KNOTD_ESPACE; + } + + memcpy(buffer, wire, wire_size); + *size = wire_size; + + knot_packet_dump(response); + knot_packet_free(&response); + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +int notify_create_request(const knot_zone_contents_t *zone, uint8_t *buffer, + size_t *size) +{ + const knot_rrset_t *soa_rrset = knot_node_rrset( + knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA); + if (soa_rrset == NULL) { + return KNOTD_ERROR; + } + + return notify_request(soa_rrset, buffer, size); +} + +/*----------------------------------------------------------------------------*/ + +static int notify_check_and_schedule(knot_nameserver_t *nameserver, + const knot_zone_t *zone, + sockaddr_t *from) +{ + if (zone == NULL || from == NULL || knot_zone_data(zone) == NULL) { + return KNOTD_EINVAL; + } + + /* Check ACL for notify-in. */ + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + if (from) { + if (acl_match(zd->notify_in, from) == ACL_DENY) { + /* rfc1996: Ignore request and report incident. */ + char straddr[SOCKADDR_STRLEN]; + sockaddr_tostr(from, straddr, sizeof(straddr)); + log_zone_notice("Unauthorized NOTIFY query " + "from %s:%d to zone '%s'.\n", + straddr, sockaddr_portnum(from), + zd->conf->name); + return KNOT_ERROR; + } else { + dbg_notify("notify: authorized NOTIFY query.\n"); + } + } + + /*! \todo Packet may contain updated RRs. */ + + /* Cancel REFRESH/RETRY timer. */ + evsched_t *sched = ((server_t *)knot_ns_get_data(nameserver))->sched; + event_t *refresh_ev = zd->xfr_in.timer; + if (refresh_ev) { + dbg_notify("notify: expiring REFRESH timer\n"); + evsched_cancel(sched, refresh_ev); + + /* Set REFRESH timer for now. */ + evsched_schedule(sched, refresh_ev, 0); + } + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int notify_process_request(knot_nameserver_t *ns, + knot_packet_t *notify, + sockaddr_t *from, + uint8_t *buffer, size_t *size) +{ + /*! \todo Most of this function is identical to xfrin_transfer_needed() + * - it will be fine to merge the code somehow. + */ + + if (notify == NULL || ns == NULL || buffer == NULL + || size == NULL || from == NULL) { + dbg_notify("notify: invalid parameters for %s()\n", + "notify_process_request"); + return KNOTD_EINVAL; + } + + int ret = KNOTD_EOK; + + dbg_notify("notify: parsing rest of the packet\n"); + if (notify->parsed < notify->size) { + ret = knot_packet_parse_rest(notify); + if (ret != KNOT_EOK) { + dbg_notify("notify: failed to parse NOTIFY query\n"); + knot_ns_error_response(ns, knot_packet_id(notify), + KNOT_RCODE_FORMERR, buffer, + size); + return KNOTD_EOK; + } + } + + // create NOTIFY response + dbg_notify("notify: creating response\n"); + ret = notify_create_response(notify, buffer, size); + if (ret != KNOTD_EOK) { + dbg_notify("notify: failed to create NOTIFY response\n"); + knot_ns_error_response(ns, knot_packet_id(notify), + KNOT_RCODE_SERVFAIL, buffer, + size); + return KNOTD_EOK; + } + + // find the zone + const knot_dname_t *qname = knot_packet_qname(notify); + const knot_zone_t *z = knot_zonedb_find_zone_for_name( + ns->zone_db, qname); + if (z == NULL) { + dbg_notify("notify: failed to find zone by name\n"); + knot_ns_error_response(ns, knot_packet_id(notify), + KNOT_RCODE_REFUSED, buffer, + size); + return KNOTD_EOK; + } + + notify_check_and_schedule(ns, z, from); + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int notify_process_response(knot_nameserver_t *nameserver, + knot_packet_t *notify, + sockaddr_t *from, + uint8_t *buffer, size_t *size) +{ + if (nameserver == NULL || notify == NULL || from == NULL + || buffer == NULL || size == NULL) { + return KNOTD_EINVAL; + } + + /* Assert no response size. */ + *size = 0; + + /* Find matching zone. */ + const knot_dname_t *zone_name = knot_packet_qname(notify); + knot_zone_t *zone = knot_zonedb_find_zone(nameserver->zone_db, + zone_name); + if (!zone) { + return KNOTD_ENOENT; + } + if (!knot_zone_data(zone)) { + return KNOTD_ENOENT; + } + + /* Match ID against awaited. */ + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + pthread_mutex_lock(&zd->lock); + uint16_t pkt_id = knot_packet_id(notify); + notify_ev_t *ev = 0, *match = 0; + WALK_LIST(ev, zd->notify_pending) { + if ((int)pkt_id == ev->msgid) { + match = ev; + break; + } + } + + /* Found waiting NOTIFY query? */ + if (!match) { + log_server_notice("No pending NOTIFY query found for ID=%u\n", + pkt_id); + return KNOTD_ERROR; + } + + /* NOTIFY is now finished. */ + zones_cancel_notify(zd, match); + + /* Zone was removed/reloaded. */ + pthread_mutex_unlock(&zd->lock); + + log_server_info("Received response for pending NOTIFY query ID=%u\n", + pkt_id); + + return KNOTD_EOK; +} + diff --git a/src/knot/server/notify.h b/src/knot/server/notify.h new file mode 100644 index 0000000..c1bebb8 --- /dev/null +++ b/src/knot/server/notify.h @@ -0,0 +1,128 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file notify.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief NOTIFY request/reply API. + * + * \addtogroup query_processing + * @{ + */ + +#ifndef _KNOTD_NOTIFY_H_ +#define _KNOTD_NOTIFY_H_ + +#include <stdint.h> +#include <string.h> + +#include "libknot/zone/zone.h" +#include "libknot/packet/packet.h" +#include "libknot/zone/zonedb.h" +#include "common/lists.h" +#include "common/sockaddr.h" +#include "libknot/nameserver/name-server.h" + +/*! + * \brief Pending NOTIFY event. + * \see knot_zone_t.notify_pending + */ +typedef struct notify_ev_t { + node n; + int timeout; /*!< Timeout for events. */ + int retries; /*!< Number of retries. */ + int msgid; /*!< ID of pending NOTIFY. */ + sockaddr_t addr; /*!< Slave server address. */ + struct event_t *timer; /*!< Event timer. */ + knot_zone_t *zone; /*!< Associated zone. */ +} notify_ev_t; + +/*! + * \brief Creates a NOTIFY request message for SOA RR of the given zone. + * + * \param zone Zone from which to take the SOA RR. + * \param buffer Buffer to fill the message in. + * \param size In: available space in the buffer. Out: actual size of the + * message in bytes. + * + * \retval KNOTD_EOK + * \retval KNOTD_ESPACE + * \retval KNOTD_ERROR + */ +int notify_create_request(const knot_zone_contents_t *zone, uint8_t *buffer, + size_t *size); + +/*! + * \brief Creates a response for NOTIFY query. + * + * Valid NOTIFY query expires REFRESH timer for received qname. + * + * \see RFC1996 for query and response format. + * + * \param nameserver Name server structure to provide the needed data. + * \param query Response structure with parsed query. + * \param response_wire Place for the response in wire format. + * \param rsize Input: maximum acceptable size of the response. Output: real + * size of the response. + * + * \retval KNOTD_EOK if a valid response was created. + * \retval KNOTD_EACCES sender is not authorized to request NOTIFY. + * \retval KNOTD_EMALF if an error occured and the response is not valid. + */ +/*! + * \brief Evaluates incoming NOTIFY request and produces a reply. + * + * \param notify (Partially) parsed packet with the NOTIFY request. + * \param zonedb Zone database of the server. + * \param zone Zone which is probably out-of-date or NULL if there either is no + * zone corresponding to the request or if the zone is up-to-date. + * \param buffer Buffer to fill the message in. + * \param size In: available space in the buffer. Out: actual size of the + * response message in bytes. + * + * \retval KNOTD_EOK + * \retval KNOTD_EINVAL + * \retval KNOTD_EMALF + * \retval KNOTD_ERROR + */ +int notify_process_request(knot_nameserver_t *nameserver, + knot_packet_t *notify, + sockaddr_t *from, + uint8_t *buffer, size_t *size); + +/*! + * \brief Processes NOTIFY response packet. + * + * \param nameserver Name server structure to provide the needed data. + * \param from Address of the response sender. + * \param packet Parsed response packet. + * \param response_wire Place for the response in wire format. + * \param rsize Input: maximum acceptable size of the response. Output: real + * size of the response. + * + * \retval KNOTD_EOK if a valid response was created. + * \retval KNOTD_EINVAL on invalid parameters or packet. + * \retval KNOTD_EMALF if an error occured and the response is not valid. + */ +int notify_process_response(knot_nameserver_t *nameserver, + knot_packet_t *notify, + sockaddr_t *from, + uint8_t *buffer, size_t *size); + +#endif /* _KNOTD_NOTIFY_H_ */ + +/*! @} */ diff --git a/src/knot/server/server.c b/src/knot/server/server.c new file mode 100644 index 0000000..80db35d --- /dev/null +++ b/src/knot/server/server.c @@ -0,0 +1,730 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <errno.h> +#include <openssl/evp.h> +#include <assert.h> + +#include "knot/common.h" +#include "knot/other/error.h" +#include "knot/server/server.h" +#include "knot/server/udp-handler.h" +#include "knot/server/tcp-handler.h" +#include "knot/server/xfr-handler.h" +#include "libknot/nameserver/name-server.h" +#include "knot/stat/stat.h" +#include "libknot/zone/zonedb.h" +#include "knot/zone/zone-load.h" +#include "libknot/dname.h" +#include "knot/conf/conf.h" +#include "knot/server/zones.h" + +/*! \brief Event scheduler loop. */ +static int evsched_run(dthread_t *thread) +{ + iohandler_t *sched_h = (iohandler_t *)thread->data; + evsched_t *s = (evsched_t*)sched_h->data; + if (!s) { + return KNOTD_EINVAL; + } + + /* Run event loop. */ + event_t *ev = 0; + while((ev = evsched_next(s))) { + + /* Error. */ + if (!ev) { + return KNOTD_ERROR; + } + + /* Process termination event. */ + if (ev->type == EVSCHED_TERM) { + evsched_event_finished(s); + evsched_event_free(s, ev); + break; + } + + /* Process event. */ + if (ev->type == EVSCHED_CB && ev->cb) { + ev->cb(ev); + evsched_event_finished(s); + } else { + evsched_event_finished(s); + evsched_event_free(s, ev); + } + + /* Check for thread cancellation. */ + if (dt_is_cancelled(thread)) { + break; + } + } + + return KNOTD_EOK; +} + +/*! \brief List item for generic pointers. */ +typedef struct pnode_t { + struct node *next, *prev; /* Keep the ordering for lib/lists.h */ + void *p; /*!< \brief Useful data pointer. */ +} pnode_t; + +/*! \brief Unbind and dispose given interface. */ +static void server_remove_iface(iface_t *iface) +{ + /* Free UDP handler. */ + iohandler_t *handler = iface->handler[UDP_ID]; + if (handler) { + server_remove_handler(handler->server, handler); + } else { + if (iface->fd[UDP_ID] > -1) { + close(iface->fd[UDP_ID]); + } + } + + /* Free TCP handler. */ + handler = iface->handler[TCP_ID]; + if (handler) { + server_remove_handler(handler->server, handler); + } else { + if (iface->fd[TCP_ID] > -1) { + close(iface->fd[TCP_ID]); + } + } + + /* Free interface. */ + free(iface->addr); + free(iface); +} + +/*! + * \brief Initialize new interface from config value. + * + * Both TCP and UDP sockets will be created for the interface. + * + * \param new_if Allocated memory for the interface. + * \param cfg_if Interface template from config. + * + * \retval 0 if successful (EOK). + * \retval <0 on errors (EACCES, EINVAL, ENOMEM, EADDRINUSE). + */ +static int server_init_iface(iface_t *new_if, conf_iface_t *cfg_if) +{ + /* Initialize interface. */ + char errbuf[128]; + int opt = 1024 * 1024; + int snd_opt = 1024 * 1024; + memset(new_if, 0, sizeof(iface_t)); + + /* Create UDP socket. */ + int sock = socket_create(cfg_if->family, SOCK_DGRAM); + if (sock <= 0) { + strerror_r(errno, errbuf, sizeof(errbuf)); + log_server_error("Could not create UDP socket: %s.\n", + errbuf); + return sock; + } + if (socket_bind(sock, cfg_if->family, + cfg_if->address, cfg_if->port) < 0) { + socket_close(sock); + log_server_error("Could not bind to " + "UDP interface %s port %d.\n", + cfg_if->address, cfg_if->port); + return knot_map_errno(EACCES, EINVAL, ENOMEM); + } + + new_if->fd[UDP_ID] = sock; + new_if->type[UDP_ID] = cfg_if->family; + + /* Set socket options - voluntary. */ + if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &snd_opt, sizeof(snd_opt)) < 0) { + // log_server_warning("Failed to configure socket " + // "write buffers.\n"); + } + if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) { + // log_server_warning("Failed to configure socket read buffers.\n"); + } + + /* Create TCP socket. */ + int ret = 0; + sock = socket_create(cfg_if->family, SOCK_STREAM); + if (sock <= 0) { + socket_close(new_if->fd[UDP_ID]); + strerror_r(errno, errbuf, sizeof(errbuf)); + log_server_error("Could not create TCP socket: %s.\n", + errbuf); + return sock; + } + + ret = socket_bind(sock, cfg_if->family, cfg_if->address, cfg_if->port); + if (ret < 0) { + socket_close(new_if->fd[UDP_ID]); + socket_close(sock); + log_server_error("Could not bind to " + "TCP interface %s port %d.\n", + cfg_if->address, cfg_if->port); + return ret; + } + + ret = socket_listen(sock, TCP_BACKLOG_SIZE); + if (ret < 0) { + socket_close(new_if->fd[UDP_ID]); + socket_close(sock); + log_server_error("Failed to listen on " + "TCP interface %s port %d.\n", + cfg_if->address, cfg_if->port); + return ret; + } + + new_if->fd[TCP_ID] = sock; + new_if->type[TCP_ID] = cfg_if->family; + new_if->port = cfg_if->port; + new_if->addr = strdup(cfg_if->address); + return KNOTD_EOK; +} + +/*! + * \brief Update bound sockets according to configuration. + * + * \param server Server instance. + * \return number of added sockets. + */ +static int server_bind_sockets(server_t *server) +{ + /*! \todo This requires locking to disable parallel updates. */ + + /* Lock configuration. */ + conf_read_lock(); + + /* Prepare helper lists. */ + int bound = 0; + node *m = 0; + list *newlist, unmatched; + newlist = malloc(sizeof(list)); + init_list(newlist); + init_list(&unmatched); + + /* Duplicate current list. */ + /*! \note Pointers to addr, handlers etc. will be shared. */ + list_dup(&unmatched, server->ifaces, sizeof(iface_t)); + + /* Update pointers. */ + WALK_LIST(m, unmatched) { + + /* Interfaces. */ + iface_t *m_if = (iface_t*)m; + for (int i = 0; i <= TCP_ID; ++i) { + iohandler_t *h = m_if->handler[i]; + if (h) { + h->iface = m_if; + } + + } + } + + /* Update bound interfaces. */ + node *n = 0; + WALK_LIST(n, conf()->ifaces) { + + /* Find already matching interface. */ + int found_match = 0; + conf_iface_t *cfg_if = (conf_iface_t*)n; + WALK_LIST(m, unmatched) { + iface_t *srv_if = (iface_t*)m; + + /* Matching port and address. */ + if (cfg_if->port == srv_if->port) { + if (strcmp(cfg_if->address, srv_if->addr) == 0) { + found_match = 1; + break; + } + } + } + + /* Found already bound interface. */ + if (found_match) { + rem_node(m); + } else { + + /* Create new interface. */ + m = malloc(sizeof(iface_t)); + if (server_init_iface((iface_t*)m, cfg_if) < 0) { + free(m); + m = 0; + } + + log_server_info("Binding to interface %s port %d.\n", + cfg_if->address, cfg_if->port); + } + + /* Move to new list. */ + if (m) { + add_tail(newlist, m); + ++bound; + } + } + + /* Unlock configuration. */ + conf_read_unlock(); + + /* Publish new list. */ + list* oldlist = rcu_xchg_pointer(&server->ifaces, newlist); + + /* Ensure no one is reading old interfaces. */ + synchronize_rcu(); + + /* Remove deprecated interfaces. */ + WALK_LIST_DELSAFE(n, m, unmatched) { + iface_t *rm_if = (iface_t*)n; + log_server_info("Removing interface %s port %d.\n", + rm_if->addr, rm_if->port); + server_remove_iface(rm_if); + } + + /* Free original list. */ + WALK_LIST_DELSAFE(n, m, *oldlist) { + /*! \note Need to keep internal pointers, as they are shared + * with the newly published list. */ + free(n); + } + free(oldlist); + + return bound; +} + +/*! + * \brief Update socket handlers according to configuration. + * + * \param server Server instance. + * \retval 0 if successful (EOK). + * \retval <0 on errors (EINVAL). + */ +static int server_bind_handlers(server_t *server) +{ + if (!server || !server->ifaces) { + return KNOTD_EINVAL; + } + + /* Lock config. */ + conf_read_lock(); + + /* Estimate number of threads/manager. */ + int thr_count = 0; + int tcp_unit_size = 0; + if (conf()->workers < 1) { + thr_count = dt_optimal_size(); + tcp_unit_size = (thr_count * 2) + 1; /* Will be always odd. */ + } else { + thr_count = conf()->workers; + tcp_unit_size = thr_count + 1; /* Force configured value. */ + } + + dbg_server("server: configured %d worker%s per UDP iface\n", + thr_count, thr_count > 1 ? "s" : ""); + dbg_server("server: configured %d worker%s per TCP iface\n", + tcp_unit_size - 1, (tcp_unit_size - 1) > 1 ? "s" : ""); + + /* Create socket handlers. */ + node *n = 0; + iohandler_t* h = 0; + WALK_LIST(n, *server->ifaces) { + + iface_t *iface = (iface_t*)n; + + /* Create UDP handlers. */ + dt_unit_t *unit = 0; + if (!iface->handler[UDP_ID]) { + unit = dt_create_coherent(thr_count, &udp_master, 0); + h = server_create_handler(server, iface->fd[UDP_ID], unit); + h->type = iface->type[UDP_ID]; + h->iface = iface; + + /* Save pointer. */ + rcu_set_pointer(&iface->handler[UDP_ID], h); + dbg_server("server: creating UDP socket handlers for '%s:%d'\n", + iface->addr, iface->port); + + } + + /* Create TCP handlers. */ + if (!iface->handler[TCP_ID]) { + unit = dt_create(tcp_unit_size); + h = server_create_handler(server, iface->fd[TCP_ID], unit); + tcp_loop_unit(h, unit); + h->type = iface->type[TCP_ID]; + h->iface = iface; + + /* Save pointer. */ + rcu_set_pointer(&iface->handler[TCP_ID], h); + dbg_server("server: creating TCP socket handlers for '%s:%d'\n", + iface->addr, iface->port); + } + + } + + /* Unlock config. */ + conf_read_unlock(); + + return KNOTD_EOK; +} + +server_t *server_create() +{ + // Create server structure + server_t *server = malloc(sizeof(server_t)); + if (server == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + server->state = ServerIdle; + init_list(&server->handlers); + server->ifaces = malloc(sizeof(list)); + init_list(server->ifaces); + + // Create event scheduler + dbg_server("server: creating event scheduler\n"); + server->sched = evsched_new(); + dt_unit_t *unit = dt_create_coherent(1, evsched_run, 0); + iohandler_t *h = server_create_handler(server, -1, unit); + h->data = server->sched; + + // Create name server + dbg_server("server: creating Name Server structure\n"); + server->nameserver = knot_ns_create(); + if (server->nameserver == NULL) { + free(server); + return NULL; + } + knot_ns_set_data(server->nameserver, server); + dbg_server("server: initializing OpenSSL\n"); + OpenSSL_add_all_digests(); + + // Create XFR handler + server->xfr_h = xfr_create(XFR_THREADS_COUNT, server->nameserver); + if (!server->xfr_h) { + knot_ns_destroy(&server->nameserver); + free(server); + return NULL; + } + + dbg_server("server: created server instance\n"); + return server; +} + +iohandler_t *server_create_handler(server_t *server, int fd, dt_unit_t *unit) +{ + // Create new worker + iohandler_t *handler = malloc(sizeof(iohandler_t)); + if (handler == 0) { + ERR_ALLOC_FAILED; + return 0; + } + + // Initialize + handler->fd = fd; + handler->type = 0; + handler->state = ServerIdle; + handler->server = server; + handler->unit = unit; + handler->iface = 0; + handler->data = 0; + handler->interrupt = 0; + + // Update unit data object + for (int i = 0; i < unit->size; ++i) { + dthread_t *thread = unit->threads[i]; + if (thread->run) { + dt_repurpose(thread, thread->run, handler); + } + } + + /*! \todo This requires either RCU compatible ptr swap or locking. */ + + /* Lock RCU. */ + rcu_read_lock(); + + // Update list + add_tail(&server->handlers, (node*)handler); + + /* Unlock RCU. */ + rcu_read_unlock(); + + return handler; +} + +int server_remove_handler(server_t *server, iohandler_t *h) +{ + // Check + if (h == 0) { + return KNOTD_EINVAL; + } + + /* Lock RCU. */ + rcu_read_lock(); + + /*! \todo This requires either RCU compatible ptr swap or locking. */ + + // Remove node + rem_node((node*)h); + + // Wait for dispatcher to finish + if (h->state & ServerRunning) { + h->state = ServerIdle; + dt_stop(h->unit); + + /* Call interrupt handler. */ + if (h->interrupt) { + h->interrupt(h); + } + + dt_join(h->unit); + } + + // Close socket + if (h->fd >= 0) { + socket_close(h->fd); + h->fd = -1; + } + + // Update interface + if (h->iface) { + int id = UDP_ID; + if (h->iface->handler[TCP_ID] == h) { + id = TCP_ID; + } + + h->iface->fd[id] = h->fd; + h->iface->handler[id] = 0; + } + + /* Unlock RCU. */ + rcu_read_unlock(); + + /* RCU synchronize. */ + synchronize_rcu(); + + // Destroy dispatcher and worker + dt_delete(&h->unit); + free(h); + return KNOTD_EOK; +} + +int server_start(server_t *server) +{ + // Check server + if (server == 0) { + return KNOTD_EINVAL; + } + + dbg_server("server: starting server instance\n"); + + /* Start XFR handler. */ + xfr_start(server->xfr_h); + + /* Lock configuration. */ + conf_read_lock(); + + // Start dispatchers + int ret = KNOTD_EOK; + server->state |= ServerRunning; + iohandler_t *h = 0; + WALK_LIST(h, server->handlers) { + + /* Already running. */ + if (h->state & ServerRunning) { + continue; + } + + h->state = ServerRunning; + ret = dt_start(h->unit); + if (ret < 0) { + break; + } + } + + /* Unlock configuration. */ + conf_read_unlock(); + + dbg_server("server: server started\n"); + + return ret; +} + +int server_wait(server_t *server) +{ + /* Join threading unit. */ + xfr_join(server->xfr_h); + + /* Lock RCU. */ + rcu_read_lock(); + + // Wait for handlers to finish + int ret = 0; + iohandler_t *h = 0, *nxt = 0; + WALK_LIST_DELSAFE(h, nxt, server->handlers) { + + /* Unlock RCU. */ + rcu_read_unlock(); + + /* Remove handler. */ + int dret = dt_join(h->unit); + if (dret < 0) { + ret = dret; + } + server_remove_handler(server, h); + + /* Relock RCU. */ + rcu_read_lock(); + } + + /* Unlock RCU. */ + rcu_read_unlock(); + + return ret; +} + +void server_stop(server_t *server) +{ + dbg_server("server: stopping server\n"); + + /* Wait for XFR master. */ + xfr_stop(server->xfr_h); + + /* Interrupt XFR handler execution. */ + if (server->xfr_h->interrupt) { + server->xfr_h->interrupt(server->xfr_h); + } + + /* Send termination event. */ + evsched_schedule_term(server->sched, 0); + + /* Lock RCU. */ + rcu_read_lock(); + + /* Notify servers to stop. */ + log_server_info("Stopping server...\n"); + server->state &= ~ServerRunning; + iohandler_t *h = 0; + WALK_LIST(h, server->handlers) { + h->state = ServerIdle; + dt_stop(h->unit); + + /* Call interrupt handler. */ + if (h->interrupt) { + h->interrupt(h); + } + } + + /* Unlock RCU. */ + rcu_read_unlock(); +} + +void server_destroy(server_t **server) +{ + // Check server + if (!server) { + return; + } + if (!*server) { + return; + } + + dbg_server("server: destroying server instance\n"); + + // Free XFR master + xfr_free((*server)->xfr_h); + + // Free interfaces + node *n = 0, *nxt = 0; + if ((*server)->ifaces) { + WALK_LIST_DELSAFE(n, nxt, *(*server)->ifaces) { + iface_t *iface = (iface_t*)n; + server_remove_iface(iface); + } + free((*server)->ifaces); + } + + stat_static_gath_free(); + knot_ns_destroy(&(*server)->nameserver); + + // Delete event scheduler + evsched_delete(&(*server)->sched); + + free(*server); + + EVP_cleanup(); + + *server = NULL; +} + +int server_conf_hook(const struct conf_t *conf, void *data) +{ + server_t *server = (server_t *)data; + + if (!server) { + return KNOTD_EINVAL; + } + + /* Update bound sockets. */ + int ret = KNOTD_EOK; + if ((ret = server_bind_sockets(server)) < 0) { + log_server_error("Failed to bind configured " + "interfaces.\n"); + return KNOTD_ERROR; + } + + /* Update handlers. */ + if ((ret = server_bind_handlers(server)) < 0) { + log_server_error("Failed to create handlers for " + "configured interfaces.\n"); + return ret; + } + + /* Exit if the server is not running. */ + if (!(server->state & ServerRunning)) { + return KNOTD_ENOTRUNNING; + } + + /* Lock configuration. */ + conf_read_lock(); + + /* Start new handlers. */ + iohandler_t *h = 0; + WALK_LIST(h, server->handlers) { + if (!(h->state & ServerRunning)) { + h->state = ServerRunning; + ret = dt_start(h->unit); + if (ret < 0) { + log_server_error("Handler for %s:%d " + "has failed to start.\n", + h->iface->addr, + h->iface->port); + break; + } + } + } + + /* Unlock config. */ + conf_read_unlock(); + + return ret; +} + diff --git a/src/knot/server/server.h b/src/knot/server/server.h new file mode 100644 index 0000000..480219b --- /dev/null +++ b/src/knot/server/server.h @@ -0,0 +1,211 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file server.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Core server functions. + * + * Contains the main high-level server structure (server_t) and interface + * to functions taking care of proper initialization of the server and clean-up + * when terminated. + * + * As of now, the server supports only one zone file and only in a special + * format. + * + * \see zone-parser.h + * + * \addtogroup server + * @{ + */ + +#ifndef _KNOTD_SERVER_H_ +#define _KNOTD_SERVER_H_ + +#include "knot/common.h" +#include "libknot/nameserver/name-server.h" +#include "knot/server/xfr-handler.h" +#include "knot/server/socket.h" +#include "knot/server/dthreads.h" +#include "libknot/zone/zonedb.h" +#include "common/evsched.h" +#include "common/lists.h" + +/* Forwad declarations. */ +struct iface_t; +struct iohandler_t; +struct server_t; +struct conf_t; + +/*! \brief I/O handler structure. + */ +typedef struct iohandler_t { + struct node *next, *prev; + int fd; /*!< I/O filedescriptor */ + int type; /*!< Descriptor type/family. */ + unsigned state; /*!< Handler state */ + dt_unit_t *unit; /*!< Threading unit */ + struct iface_t *iface; /*!< Reference to associated interface. */ + struct server_t *server; /*!< Reference to server */ + void *data; /*!< Persistent data for I/O handler. */ + void (*interrupt)(struct iohandler_t *h); /*!< Interrupt handler. */ + +} iohandler_t; + +/*! \brief Round-robin mechanism of switching. + */ +#define get_next_rr(current, count) \ + (((current) + 1) % (count)) + +/*! \brief Server state flags. + */ +typedef enum { + ServerIdle = 0 << 0, /*!< Server is idle. */ + ServerRunning = 1 << 0 /*!< Server is running. */ +} server_state; + +/*! + * \brief Server interface structure. + */ +typedef struct iface_t { + struct node *next, *prev; + int fd[2]; /*!< \brief Socket filedescriptors (UDP, TCP). */ + int type[2]; /*!< \brief Socket type. */ + int port; /*!< \brief Socket port. */ + char* addr; /*!< \brief Socket address. */ + iohandler_t* handler[2]; /*!< \brief Associated I/O handlers. */ +} iface_t; + +/* Interface indexes. */ +#define UDP_ID 0 +#define TCP_ID 1 + +/*! + * \brief Main server structure. + * + * Keeps references to all important structures needed for operation. + */ +typedef struct server_t { + + /*! \brief Server state tracking. */ + volatile unsigned state; + + /*! \brief Reference to the name server structure. */ + knot_nameserver_t *nameserver; + + /*! \brief XFR handler. */ + xfrhandler_t *xfr_h; + + /*! \brief Event scheduler. */ + evsched_t *sched; + + /*! \brief I/O handlers list. */ + list handlers; + + /*! \brief List of interfaces. */ + list* ifaces; + +} server_t; + +/*! + * \brief Allocates and initializes the server structure. + * + * Creates all other main structures. + * + * \retval New instance if successful. + * \retval NULL If an error occured. + */ +server_t *server_create(); + +/*! + * \brief Create and bind handler to given filedescriptor. + * + * Pointer to handler instance is used as native unique identifier. + * This requests instance not to be reallocated. + * + * \param server Server structure to be used for operation. + * \param fd I/O filedescriptor. + * \param unit Threading unit to serve given filedescriptor. + * + * \retval Handler instance if successful. + * \retval NULL If an error occured. + */ +iohandler_t *server_create_handler(server_t *server, int fd, dt_unit_t *unit); + +/*! + * \brief Delete handler. + * + * \param server Server structure to be used for operation. + * \param ref I/O handler instance. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + */ +int server_remove_handler(server_t *server, iohandler_t *ref); + +/*! + * \brief Starts the server. + * + * \param server Server structure to be used for operation. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + * + * \todo When a module for configuration is added, the filename parameter will + * be removed. + */ +int server_start(server_t *server); + +/*! + * \brief Waits for the server to finish. + * + * \param server Server structure to be used for operation. + * + * \retval 0 On success (EOK). + * \retval <0 If an error occured (EINVAL). + */ +int server_wait(server_t *server); + +/*! + * \brief Requests server to stop. + * + * \param server Server structure to be used for operation. + */ +void server_stop(server_t *server); + +/*! + * \brief Properly destroys the server structure. + * + * \param server Server structure to be used for operation. + */ +void server_destroy(server_t **server); + +/*! + * \brief Server config hook. + * + * Routine for dynamic server reconfiguration. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_ENOTRUNNING if the server is not running. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_ERROR unspecified error. + */ +int server_conf_hook(const struct conf_t *conf, void *data); + +#endif // _KNOTD_SERVER_H_ + +/*! @} */ diff --git a/src/knot/server/socket.c b/src/knot/server/socket.c new file mode 100644 index 0000000..d3dd664 --- /dev/null +++ b/src/knot/server/socket.c @@ -0,0 +1,192 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <netdb.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <arpa/inet.h> + +#include "knot/other/error.h" +#include "knot/common.h" +#include "knot/server/socket.h" + +int socket_create(int family, int type) +{ + /* Create socket. */ + int ret = socket(family, type, 0); + if (ret < 0) { + return knot_map_errno(EACCES, EINVAL, ENOMEM); + } + + return ret; +} + +int socket_connect(int fd, const char *addr, unsigned short port) +{ + /* NULL address => any */ + if (!addr) { + addr = "0.0.0.0"; + } + + /* Resolve address. */ + int ret = KNOTD_EOK; + struct addrinfo hints, *res; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if ((ret = getaddrinfo(addr, NULL, &hints, &res)) != 0) { + return KNOTD_EINVAL; + } + + /* Evaluate address type. */ + struct sockaddr *saddr = 0; + socklen_t addrlen = 0; +#ifndef DISABLE_IPV6 + if (res->ai_family == AF_INET6) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6*)res->ai_addr; + ipv6->sin6_port = htons(port); + saddr = (struct sockaddr*)ipv6; + addrlen = sizeof(struct sockaddr_in6); + } +#endif + if (res->ai_family == AF_INET) { + struct sockaddr_in *ipv4 = (struct sockaddr_in*)res->ai_addr; + ipv4->sin_port = htons(port); + saddr = (struct sockaddr*)ipv4; + addrlen = sizeof(struct sockaddr_in); + } + + /* Connect. */ + ret = -1; + if (addr) { + ret = connect(fd, saddr, addrlen); + if (ret < 0) { + ret = knot_map_errno(EACCES, EADDRINUSE, EAGAIN, + ECONNREFUSED, EISCONN); + } + } else { + ret = KNOTD_EINVAL; + } + + + /* Free addresses. */ + freeaddrinfo(res); + + return ret; +} + +int socket_bind(int socket, int family, const char *addr, unsigned short port) +{ + /* Check address family. */ + struct sockaddr* paddr = 0; + socklen_t addrlen = 0; + struct sockaddr_in saddr; +#ifndef DISABLE_IPV6 + struct sockaddr_in6 saddr6; +#endif + if (family == AF_INET) { + + /* Initialize socket address. */ + paddr = (struct sockaddr*)&saddr; + addrlen = sizeof(saddr); + if (getsockname(socket, paddr, &addrlen) < 0) { + return KNOTD_EINVAL; + } + + /* Set address and port. */ + saddr.sin_port = htons(port); + if (inet_pton(family, addr, &saddr.sin_addr) < 0) { + saddr.sin_addr.s_addr = INADDR_ANY; + char buf[INET_ADDRSTRLEN]; + inet_ntop(family, &saddr.sin_addr, buf, sizeof(buf)); + log_server_error("Address '%s' is invalid, " + "using '%s' instead.\n", + addr, buf); + + } + + } else { + +#ifdef DISABLE_IPV6 + log_server_error("ipv6 support disabled\n"); + return KNOTD_ENOIPV6; +#else + /* Initialize socket address. */ + paddr = (struct sockaddr*)&saddr6; + addrlen = sizeof(saddr6); + if (getsockname(socket, paddr, &addrlen) < 0) { + return KNOTD_EINVAL; + } + + /* Set address and port. */ + saddr6.sin6_port = htons(port); + if (inet_pton(family, addr, &saddr6.sin6_addr) < 0) { + memcpy(&saddr6.sin6_addr, &in6addr_any, sizeof(in6addr_any)); + char buf[INET6_ADDRSTRLEN]; + inet_ntop(family, &saddr6.sin6_addr, buf, sizeof(buf)); + log_server_error("Address '%s' is invalid, " + "using '%s' instead\n", + addr, buf); + + } +#endif + } + + /* Reuse old address if taken. */ + int flag = 1; + int ret = setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, + &flag, sizeof(flag)); + if (ret < 0) { + return KNOTD_EINVAL; + } + + /* Bind to specified address. */ + int res = bind(socket, paddr, addrlen); + if (res < 0) { + log_server_error("Cannot bind to socket (%d).\n", + errno); + return knot_map_errno(EADDRINUSE, EINVAL, EACCES, ENOMEM); + } + + return KNOTD_EOK; +} + +int socket_listen(int socket, int backlog_size) +{ + int ret = listen(socket, backlog_size); + if (ret < 0) { + return knot_map_errno(EADDRINUSE); + } + + return KNOTD_EOK; +} + +int socket_close(int socket) +{ + if (close(socket) < 0) { + return KNOTD_EINVAL; + } + + return KNOTD_EOK; +} + diff --git a/src/knot/server/socket.h b/src/knot/server/socket.h new file mode 100644 index 0000000..dff5216 --- /dev/null +++ b/src/knot/server/socket.h @@ -0,0 +1,120 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file socket.h + * + * \author Marek Vavrusa <marek.vavusa@nic.cz> + * + * \brief Generic sockets APIs. + * + * This file provides platform-independent sockets. + * Functions work on sockets created via system socket(2) functions. + * + * You can use standard I/O functions send(), sendto(), recv(), recvfrom() + * like you would with a normal sockets. + * + * \addtogroup network + * @{ + */ + +#ifndef _KNOTD_SOCKET_H_ +#define _KNOTD_SOCKET_H_ + +/* POSIX only. */ +#include <sys/socket.h> +#include "common/sockaddr.h" + +/*! \brief Socket-related constants. */ +typedef enum { + SOCKET_MTU_SZ = 8192, /*!< \todo Determine UDP MTU size. */ +} socket_const_t; + +/*! + * \brief Create socket. + * + * \param family Socket family (PF_INET, PF_IPX, PF_PACKET, PF_UNIX). + * \param type Socket type (SOCK_STREAM, SOCK_DGRAM, SOCK_RAW). + * + * \retval new socket filedescriptor on success. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_ENOMEM out of memory error. + * \retval KNOTD_EACCES process does not have appropriate privileges. + * \retval KNOTD_ERROR unspecified error. + */ +int socket_create(int family, int type); + +/*! + * \brief Connect to remote host. + * + * \param fd Socket filedescriptor. + * \param addr Requested address. + * \param port Requested port. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL invalid parameters. + * \retval KNOTD_EACCES process does not have appropriate privileges. + * \retval KNOTD_EAGAIN lack of resources, try again. + * \retval KNOTD_EADDRINUSE address already in use. + * \retval KNOTD_ECONNREFUSED connection refused. + * \retval KNOTD_EISCONN already connected. + * \retval KNOTD_ERROR unspecified error. + */ +int socket_connect(int fd, const char *addr, unsigned short port); + +/*! + * \brief Listen on given socket. + * + * \param fd Socket filedescriptor. + * \param family Socket family. + * \param addr Requested address. + * \param port Requested port. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL invalid parameters. + * \retval KNOTD_EACCES process does not have appropriate privileges. + * \retval KNOTD_EADDRINUSE address already in use. + * \retval KNOTD_ENOMEM out of memory error. + * \retval KNOTD_ENOIPV6 IPv6 support is not available. + * \retval KNOTD_ERROR unspecified error. + */ +int socket_bind(int fd, int family, const char *addr, unsigned short port); + +/*! + * \brief Listen on given TCP socket. + * + * \param fd Socket filedescriptor. + * \param backlog_size Requested TCP backlog size. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EADDRINUSE address already in use. + * \retval KNOTD_ERROR unspecified error. + */ +int socket_listen(int fd, int backlog_size); + +/*! + * \brief Close and deinitialize socket. + * + * \param fd Socket filedescriptor. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL invalid parameters. + */ +int socket_close(int fd); + + +#endif // _KNOTD_SOCKET_H_ + +/*! @} */ diff --git a/src/knot/server/tcp-handler.c b/src/knot/server/tcp-handler.c new file mode 100644 index 0000000..db58fef --- /dev/null +++ b/src/knot/server/tcp-handler.c @@ -0,0 +1,511 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <netinet/tcp.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "common/sockaddr.h" +#include "common/skip-list.h" +#include "common/fdset.h" +#include "knot/common.h" +#include "knot/server/tcp-handler.h" +#include "knot/server/xfr-handler.h" +#include "libknot/nameserver/name-server.h" +#include "knot/other/error.h" +#include "knot/stat/stat.h" +#include "libknot/util/wire.h" +#include "knot/server/zones.h" + +/*! \brief TCP worker data. */ +typedef struct tcp_worker_t { + iohandler_t *ioh; /*!< Shortcut to I/O handler. */ + fdset_t *fdset; /*!< File descriptor set. */ + int pipe[2]; /*!< Master-worker signalization pipes. */ +} tcp_worker_t; + +/* + * Forward decls. + */ + +/*! \brief Wrapper for TCP send. */ +static int xfr_send_cb(int session, sockaddr_t *addr, uint8_t *msg, size_t msglen) +{ + UNUSED(addr); + return tcp_send(session, msg, msglen); +} + +/*! + * \brief TCP event handler function. + * + * Handle single TCP event. + * + * \param w Associated I/O event. + * \param revents Returned events. + */ +static void tcp_handle(tcp_worker_t *w, int fd) +{ + if (fd < 0 || !w || !w->ioh) { + dbg_net("tcp: tcp_handle(%p, %d) - invalid parameters\n", w, fd); + return; + } + + dbg_net("tcp: handling TCP event on fd=%d in thread %p.\n", + fd, (void*)pthread_self()); + + knot_nameserver_t *ns = w->ioh->server->nameserver; + xfrhandler_t *xfr_h = w->ioh->server->xfr_h; + + /* Check address type. */ + sockaddr_t addr; + if (sockaddr_init(&addr, w->ioh->type) != KNOTD_EOK) { + log_server_error("Socket type %d is not supported, " + "IPv6 support is probably disabled.\n", + w->ioh->type); + return; + } + + /* Receive data. */ + uint8_t qbuf[65535]; /*! \todo This may be problematic. */ + size_t qbuf_maxlen = sizeof(qbuf); + int n = tcp_recv(fd, qbuf, qbuf_maxlen, &addr); + if (n <= 0) { + dbg_net("tcp: client on fd=%d disconnected\n", fd); + fdset_remove(w->fdset, fd); + close(fd); + return; + } + + /* Parse query. */ +// knot_response_t *resp = knot_response_new(qbuf_maxlen); + size_t resp_len = qbuf_maxlen; // 64K + + /* Parse query. */ + knot_packet_type_t qtype = KNOT_QUERY_NORMAL; + + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + if (packet == NULL) { + uint16_t pkt_id = knot_wire_get_id(qbuf); + knot_ns_error_response(ns, pkt_id, KNOT_RCODE_SERVFAIL, + qbuf, &resp_len); + return; + } + + int res = knot_ns_parse_packet(qbuf, n, packet, &qtype); + if (unlikely(res != KNOTD_EOK)) { + + /* Send error response on dnslib RCODE. */ + if (res > 0) { + uint16_t pkt_id = knot_wire_get_id(qbuf); + knot_ns_error_response(ns, pkt_id, res, + qbuf, &resp_len); + } + +// knot_response_free(&resp); + knot_packet_free(&packet); + return; + } + + /* Handle query. */ + knot_ns_xfr_t xfr; + res = KNOTD_ERROR; + switch(qtype) { + + /* Query types. */ + case KNOT_QUERY_NORMAL: + res = knot_ns_answer_normal(ns, packet, qbuf, &resp_len); + break; + case KNOT_QUERY_IXFR: + res = xfr_request_init(&xfr, XFR_TYPE_IOUT, XFR_FLAG_TCP, packet); + if (res != KNOTD_EOK) { + knot_ns_error_response(ns, knot_packet_id(packet), + KNOT_RCODE_SERVFAIL, qbuf, + &resp_len); + res = KNOTD_EOK; + break; + } + xfr.send = xfr_send_cb; + xfr.session = fd; + memcpy(&xfr.addr, &addr, sizeof(sockaddr_t)); + xfr_request(xfr_h, &xfr); + dbg_net("tcp: enqueued IXFR query on fd=%d\n", fd); + return; + case KNOT_QUERY_AXFR: + res = xfr_request_init(&xfr, XFR_TYPE_AOUT, XFR_FLAG_TCP, packet); + if (res != KNOTD_EOK) { + knot_ns_error_response(ns, knot_packet_id(packet), + KNOT_RCODE_SERVFAIL, qbuf, + &resp_len); + res = KNOTD_EOK; + break; + } + xfr.send = xfr_send_cb; + xfr.session = fd; + memcpy(&xfr.addr, &addr, sizeof(sockaddr_t)); + xfr_request(xfr_h, &xfr); + dbg_net("tcp: enqueued AXFR query on fd=%d\n", fd); + return; + + /*! \todo Implement query notify/update. */ + case KNOT_QUERY_UPDATE: + knot_ns_error_response(ns, knot_packet_id(packet), + KNOT_RCODE_NOTIMPL, qbuf, + &resp_len); + res = KNOTD_EOK; + break; + + /* Unhandled opcodes. */ + case KNOT_QUERY_NOTIFY: /*!< Only in UDP. */ + case KNOT_RESPONSE_NOTIFY: /*!< Only in UDP. */ + case KNOT_RESPONSE_NORMAL: /*!< TCP handler doesn't send queries. */ + case KNOT_RESPONSE_AXFR: /*!< Processed in XFR handler. */ + case KNOT_RESPONSE_IXFR: /*!< Processed in XFR handler. */ + knot_ns_error_response(ns, knot_packet_id(packet), + KNOT_RCODE_REFUSED, qbuf, + &resp_len); + res = KNOTD_EOK; + break; + + /* Unknown opcodes. */ + default: + knot_ns_error_response(ns, knot_packet_id(packet), + KNOT_RCODE_FORMERR, qbuf, + &resp_len); + res = KNOTD_EOK; + break; + } + + knot_packet_free(&packet); + + /* Send answer. */ + if (res == KNOTD_EOK) { + + dbg_net("tcp: got answer of size %zd.\n", + resp_len); + + assert(resp_len > 0); + res = tcp_send(fd, qbuf, resp_len); + + /* Check result. */ + if (res != (int)resp_len) { + dbg_net("tcp: %s: failed: %d - %d.\n", + "socket_send()", + res, errno); + } + } else { + dbg_net("tcp: failed to respond to query type=%d on fd=%d - %s\n", + qtype, fd, knotd_strerror(res));; + } + + return; +} + +static int tcp_accept(int fd) +{ + /* Accept incoming connection. */ + int incoming = accept(fd, 0, 0); + + /* Evaluate connection. */ + if (incoming < 0) { + if (errno != EINTR) { + log_server_error("Cannot accept connection " + "(%d).\n", errno); + } + } else { + dbg_net("tcp: accepted connection fd=%d\n", incoming); + } + + return incoming; +} + +tcp_worker_t* tcp_worker_create() +{ + tcp_worker_t *w = malloc(sizeof(tcp_worker_t)); + if (!w) { + dbg_net("tcp: out of memory when creating worker\n"); + return 0; + } + + /* Create signal pipes. */ + memset(w, 0, sizeof(tcp_worker_t)); + if (pipe(w->pipe) < 0) { + free(w); + return 0; + } + + /* Create fdset. */ + w->fdset = fdset_new(); + if (!w->fdset) { + close(w->pipe[0]); + close(w->pipe[1]); + free(w); + } + + fdset_add(w->fdset, w->pipe[0], OS_EV_READ); + + return w; +} + +void tcp_worker_free(tcp_worker_t* w) +{ + if (!w) { + return; + } + + /* Destroy fdset. */ + fdset_destroy(w->fdset); + + /* Close pipe write end and worker. */ + close(w->pipe[0]); + close(w->pipe[1]); + free(w); +} + +/* + * Public APIs. + */ + +int tcp_send(int fd, uint8_t *msg, size_t msglen) +{ + + /*! \brief TCP corking. + * \see http://vger.kernel.org/~acme/unbehaved.txt + */ +#ifdef TCP_CORK + int cork = 1; + setsockopt(fd, SOL_TCP, TCP_CORK, &cork, sizeof(cork)); +#endif + + /* Send message size. */ + unsigned short pktsize = htons(msglen); + int sent = send(fd, &pktsize, sizeof(pktsize), 0); + if (sent < 0) { + return KNOTD_ERROR; + } + + /* Send message data. */ + sent = send(fd, msg, msglen, 0); + if (sent < 0) { + return KNOTD_ERROR; + } + +#ifdef TCP_CORK + /* Uncork. */ + cork = 0; + setsockopt(fd, SOL_TCP, TCP_CORK, &cork, sizeof(cork)); +#endif + return sent; +} + +int tcp_recv(int fd, uint8_t *buf, size_t len, sockaddr_t *addr) +{ + /* Receive size. */ + unsigned short pktsize = 0; + int n = recv(fd, &pktsize, sizeof(unsigned short), MSG_WAITALL); + if (n < 0) { + return KNOTD_ERROR; + } + + pktsize = ntohs(pktsize); + + // Check packet size for NULL + if (pktsize == 0) { + return KNOTD_ERROR; + } + + dbg_net("tcp: incoming packet size=%hu on fd=%d\n", + pktsize, fd); + + // Check packet size + if (len < pktsize) { + return KNOTD_ENOMEM; + } + + /* Receive payload. */ + n = recv(fd, buf, pktsize, MSG_WAITALL); + + /* Get peer name. */ + if (addr) { + socklen_t alen = addr->len; + getpeername(fd, addr->ptr, &alen); + } + + dbg_net("tcp: received packet size=%d on fd=%d\n", + n, fd); + + return n; +} + +int tcp_loop_master(dthread_t *thread) +{ + iohandler_t *handler = (iohandler_t *)thread->data; + dt_unit_t *unit = thread->unit; + tcp_worker_t **workers = handler->data; + + /* Check socket. */ + if (!handler || handler->fd < 0 || !workers) { + dbg_net("tcp: failed to initialize master thread\n"); + return KNOTD_EINVAL; + } + + /* Accept connections. */ + int id = 0; + dbg_net("tcp: created 1 master with %d workers, backend is '%s' \n", + unit->size - 1, fdset_method()); + while(1) { + /* Check for cancellation. */ + if (dt_is_cancelled(thread)) { + break; + } + + /* Accept client. */ + int client = tcp_accept(handler->fd); + if (client < 0) { + continue; + } + + /* Add to worker in RR fashion. */ + if (write(workers[id]->pipe[1], &client, sizeof(int)) < 0) { + dbg_net("tcp: failed to register fd=%d to worker=%d\n", + client, id); + close(client); + continue; + } + id = get_next_rr(id, unit->size - 1); + } + + dbg_net("tcp: master thread finished\n"); + free(workers); + + return KNOTD_EOK; +} + +int tcp_loop_worker(dthread_t *thread) +{ + tcp_worker_t *w = thread->data; + if (!w) { + return KNOTD_EINVAL; + } + + /* Accept clients. */ + dbg_net_verb("tcp: worker %p started\n", w); + for (;;) { + + /* Cancellation point. */ + if (dt_is_cancelled(thread)) { + break; + } + + /* Wait for events. */ + int nfds = fdset_wait(w->fdset); + if (nfds <= 0) { + continue; + } + + /* Process incoming events. */ + dbg_net_verb("tcp: worker %p registered %d events\n", + w, nfds); + fdset_it_t it; + fdset_begin(w->fdset, &it); + while(1) { + + /* Handle incoming clients. */ + if (it.fd == w->pipe[0]) { + int client = 0; + if (read(it.fd, &client, sizeof(int)) < 0) { + continue; + } + + dbg_net_verb("tcp: worker %p registered " + "client %d\n", + w, client); + fdset_add(w->fdset, client, OS_EV_READ); + } else { + /* Handle other events. */ + tcp_handle(w, it.fd); + } + + /* Check if next exists. */ + if (fdset_next(w->fdset, &it) != 0) { + break; + } + } + + } + + /* Stop whole unit. */ + dbg_net_verb("tcp: worker %p finished\n", w); + tcp_worker_free(w); + return KNOTD_EOK; +} + +int tcp_loop_unit(iohandler_t *ioh, dt_unit_t *unit) +{ + if (unit->size < 1) { + return KNOTD_EINVAL; + } + + /* Create unit data. */ + tcp_worker_t **workers = malloc((unit->size - 1) * + sizeof(tcp_worker_t *)); + if (!workers) { + dbg_net("tcp: cannot allocate list of workers\n"); + return KNOTD_EINVAL; + } + + /* Prepare worker data. */ + unsigned allocated = 0; + for (unsigned i = 0; i < unit->size - 1; ++i) { + workers[i] = tcp_worker_create(); + if (workers[i] == 0) { + break; + } + workers[i]->ioh = ioh; + ++allocated; + } + + /* Check allocated workers. */ + if (allocated != unit->size - 1) { + for (unsigned i = 0; i < allocated; ++i) { + tcp_worker_free(workers[i]); + } + + free(workers); + dbg_net("tcp: cannot create workers\n"); + return KNOTD_EINVAL; + } + + /* Store worker data. */ + ioh->data = workers; + + /* Repurpose workers. */ + for (unsigned i = 0; i < allocated; ++i) { + dt_repurpose(unit->threads[i + 1], tcp_loop_worker, workers[i]); + } + + /* Repurpose first thread as master (unit controller). */ + dt_repurpose(unit->threads[0], tcp_loop_master, ioh); + + return KNOTD_EOK; +} diff --git a/src/knot/server/tcp-handler.h b/src/knot/server/tcp-handler.h new file mode 100644 index 0000000..f5fd17a --- /dev/null +++ b/src/knot/server/tcp-handler.h @@ -0,0 +1,103 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file tcp-handler.h + * + * \author Marek Vavrusa <marek.vavusa@nic.cz> + * + * \brief TCP sockets threading model. + * + * The master socket distributes incoming connections among + * the worker threads ("buckets"). Each threads processes it's own + * set of sockets, and eliminates mutual exclusion problem by doing so. + * + * \todo Improve documentation of TCP pool API and use proper error codes. + * + * \addtogroup server + * @{ + */ + +#ifndef _KNOTD_TCPHANDLER_H_ +#define _KNOTD_TCPHANDLER_H_ + +#include <stdint.h> + +#include "knot/server/socket.h" +#include "knot/server/server.h" +#include "knot/server/dthreads.h" + +/*! + * \brief Send TCP message. + * + * \param fd Associated socket. + * \param msg Buffer for a query wireformat. + * \param msglen Buffer maximum size. + * + * \retval Number of sent data on success. + * \retval KNOTD_ERROR on error. + */ +int tcp_send(int fd, uint8_t *msg, size_t msglen); + +/*! + * \brief Send TCP message. + * + * \param fd Associated socket. + * \param buf Buffer for incoming bytestream. + * \param len Buffer maximum size. + * \param addr Source address. + * + * \retval Number of read bytes on success. + * \retval KNOTD_ERROR on error. + * \retval KNOTD_ENOMEM on potential buffer overflow. + */ +int tcp_recv(int fd, uint8_t *buf, size_t len, sockaddr_t *addr); + +/*! + * \brief TCP event loop for accepting connections. + * + * \param thread Associated thread from DThreads unit. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL invalid parameters. + */ +int tcp_loop_master(dthread_t *thread); + +/*! + * \brief TCP event loop for processing requests. + * + * \param thread Associated thread from DThreads unit. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL invalid parameters. + */ +int tcp_loop_worker(dthread_t *thread); + +/*! + * \brief Create TCP event handler from threading unit. + * + * Set-up threading unit for processing TCP requests. + * + * \param ioh Associated I/O handler. + * \param thread Associated thread from DThreads unit. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL invalid parameters. + */ +int tcp_loop_unit(iohandler_t *ioh, dt_unit_t *unit); + +#endif // _KNOTD_TCPHANDLER_H_ + +/*! @} */ diff --git a/src/knot/server/udp-handler.c b/src/knot/server/udp-handler.c new file mode 100644 index 0000000..f7ae550 --- /dev/null +++ b/src/knot/server/udp-handler.c @@ -0,0 +1,438 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <sys/poll.h> +#include <netinet/in.h> +#include <string.h> +#include <assert.h> +#include <errno.h> + +#include "common/sockaddr.h" +#include "knot/common.h" +#include "knot/other/error.h" +#include "knot/server/udp-handler.h" +#include "libknot/nameserver/name-server.h" +#include "knot/stat/stat.h" +#include "knot/server/server.h" +#include "libknot/util/wire.h" +#include "libknot/consts.h" +#include "libknot/packet/packet.h" +#include "knot/server/zones.h" +#include "knot/server/notify.h" + +///*! \brief Wrapper for UDP send. */ +//static int xfr_send_udp(int session, sockaddr_t *addr, uint8_t *msg, size_t msglen) +//{ +// return sendto(session, msg, msglen, 0, addr->ptr, addr->len); +//} + +int udp_handle(int fd, uint8_t *qbuf, size_t qbuflen, size_t *resp_len, + sockaddr_t* addr, knot_nameserver_t *ns) +{ + dbg_net("udp: fd=%d received %zd bytes.\n", fd, qbuflen); + + knot_packet_type_t qtype = KNOT_QUERY_NORMAL; + *resp_len = SOCKET_MTU_SZ; + + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + if (packet == NULL) { + dbg_net("udp: failed to create packet on fd=%d\n", fd); + uint16_t pkt_id = knot_wire_get_id(qbuf); + knot_ns_error_response(ns, pkt_id, KNOT_RCODE_SERVFAIL, + qbuf, resp_len); + return KNOTD_EOK; /* Created error response. */ + } + + /* Parse query. */ + int res = knot_ns_parse_packet(qbuf, qbuflen, packet, &qtype); + if (unlikely(res != KNOTD_EOK)) { + dbg_net("udp: failed to parse packet on fd=%d\n", fd); + /* Send error response on dnslib RCODE. */ + if (res > 0) { + uint16_t pkt_id = knot_wire_get_id(qbuf); + knot_ns_error_response(ns, pkt_id, res, + qbuf, resp_len); + } + + knot_packet_free(&packet); + return KNOTD_EOK; /* Created error response. */ + } + + /* Handle query. */ +// server_t *srv = (server_t *)knot_ns_get_data(ns); +// knot_ns_xfr_t xfr; + res = KNOTD_ERROR; + switch(qtype) { + + /* Response types. */ + case KNOT_RESPONSE_NORMAL: + res = zones_process_response(ns, addr, packet, + qbuf, resp_len); + break; + case KNOT_RESPONSE_NOTIFY: + res = notify_process_response(ns, packet, addr, + qbuf, resp_len); + break; + + /* Query types. */ + case KNOT_QUERY_NORMAL: + res = knot_ns_answer_normal(ns, packet, qbuf, + resp_len); + break; + case KNOT_QUERY_AXFR: + /* RFC1034, p.28 requires reliable transfer protocol. + * Bind responds with FORMERR. + */ + /*! \todo Draft exists for AXFR/UDP, but has not been standardized. */ + knot_ns_error_response(ns, knot_packet_id(packet), + KNOT_RCODE_FORMERR, qbuf, + resp_len); + res = KNOTD_EOK; + break; + +// /* Process AXFR over UDP. */ +// res = xfr_request_init(&xfr, XFR_TYPE_AOUT, XFR_FLAG_UDP, packet); +// if (res != KNOTD_EOK) { +// knot_ns_error_response(ns, knot_packet_id(packet), +// KNOT_RCODE_SERVFAIL, qbuf, +// resp_len); +// res = KNOTD_EOK; +// break; +// } +// xfr.send = xfr_send_udp; +// xfr.session = dup(fd); +// memcpy(&xfr.addr, addr, sizeof(sockaddr_t)); +// xfr_request(srv->xfr_h, &xfr); +// dbg_net("udp: enqueued AXFR query on fd=%d\n", xfr.session); +// *resp_len = 0; +// return KNOTD_EOK; + case KNOT_QUERY_IXFR: + /* According to RFC1035, respond with SOA. + * Draft proposes trying to fit response into one packet, + * but I have found no tool or slave server to actually attempt + * IXFR/UDP. + */ + knot_packet_set_qtype(packet, KNOT_RRTYPE_SOA); + res = knot_ns_answer_normal(ns, packet, qbuf, + resp_len); + break; +// /* Process IXFR over UDP. */ +// res = xfr_request_init(&xfr, XFR_TYPE_IOUT, XFR_FLAG_UDP, packet); +// if (res != KNOTD_EOK) { +// knot_ns_error_response(ns, knot_packet_id(packet), +// KNOT_RCODE_SERVFAIL, qbuf, +// resp_len); +// res = KNOTD_EOK; +// break; +// } +// xfr.send = xfr_send_udp; +// xfr.session = dup(fd); +// memcpy(&xfr.addr, addr, sizeof(sockaddr_t)); +// xfr_request(srv->xfr_h, &xfr); +// dbg_net("udp: enqueued IXFR query on fd=%d\n", xfr.session); +// *resp_len = 0; +// return KNOTD_EOK; + case KNOT_QUERY_NOTIFY: + res = notify_process_request(ns, packet, addr, + qbuf, resp_len); + break; + + /*! \todo Implement query notify/update. */ + case KNOT_QUERY_UPDATE: + dbg_net("udp: UPDATE query on fd=%d not implemented\n", fd); + knot_ns_error_response(ns, knot_packet_id(packet), + KNOT_RCODE_NOTIMPL, qbuf, + resp_len); + res = KNOTD_EOK; + break; + + /* Unhandled opcodes. */ + case KNOT_RESPONSE_AXFR: /*!< Processed in XFR handler. */ + case KNOT_RESPONSE_IXFR: /*!< Processed in XFR handler. */ + knot_ns_error_response(ns, knot_packet_id(packet), + KNOT_RCODE_REFUSED, qbuf, + resp_len); + res = KNOTD_EOK; + break; + + /* Unknown opcodes */ + default: + knot_ns_error_response(ns, knot_packet_id(packet), + KNOT_RCODE_FORMERR, qbuf, + resp_len); + res = KNOTD_EOK; + break; + } + + knot_packet_free(&packet); + + return res; +} + +static inline int udp_master_recvfrom(dthread_t *thread, stat_t *thread_stat) +{ + iohandler_t *h = (iohandler_t *)thread->data; + knot_nameserver_t *ns = h->server->nameserver; + int sock = dup(h->fd); + + sockaddr_t addr; + if (sockaddr_init(&addr, h->type) != KNOTD_EOK) { + log_server_error("Socket type %d is not supported, " + "IPv6 support is probably disabled.\n", + h->type); + return KNOTD_ENOTSUP; + } + + uint8_t qbuf[SOCKET_MTU_SZ]; + struct msghdr msg; + memset(&msg, 0, sizeof(struct msghdr)); + struct iovec iov; + memset(&iov, 0, sizeof(struct iovec)); + iov.iov_base = qbuf; + iov.iov_len = SOCKET_MTU_SZ; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_name = addr.ptr; + msg.msg_namelen = addr.len; + + /* Loop until all data is read. */ + ssize_t n = 0; + while (n >= 0) { + + /* Receive packet. */ + n = recvmsg(sock, &msg, 0); + + /* Cancellation point. */ + if (dt_is_cancelled(thread)) { + break; + } + + /* Error and interrupt handling. */ + if (unlikely(n <= 0)) { + if (errno != EINTR && errno != 0) { + dbg_net("udp: recvmsg() failed: %d\n", + errno); + } + + if (!(h->state & ServerRunning)) { + break; + } else { + continue; + } + } + + /* Handle received pkt. */ + size_t resp_len = 0; + int rc = udp_handle(sock, qbuf, n, &resp_len, &addr, ns); + + /* Send response. */ + if (rc == KNOTD_EOK && resp_len > 0) { + + dbg_net("udp: on fd=%d, sending answer size=%zd.\n", + sock, resp_len); + + // Send datagram + rc = sendto(sock, qbuf, resp_len, + 0, addr.ptr, addr.len); + + // Check result + if (rc != (int)resp_len) { + dbg_net("udp: sendto(): failed: %d - %d.\n", + rc, errno); + } + } + } + + /* Free allocd resources. */ + close(sock); + + return KNOTD_EOK; +} + +#ifdef ENABLE_RECVMMSG +#ifdef MSG_WAITFORONE +static inline int udp_master_recvmmsg(dthread_t *thread, stat_t *thread_stat) +{ + iohandler_t *h = (iohandler_t *)thread->data; + knot_nameserver_t *ns = h->server->nameserver; + int sock = dup(h->fd); + + /* Allocate batch for N packets. */ + char *iobuf = malloc(SOCKET_MTU_SZ * RECVMMSG_BATCHLEN); + sockaddr_t *addrs = malloc(sizeof(sockaddr_t) * RECVMMSG_BATCHLEN); + struct iovec *iov = malloc(sizeof(struct iovec) * RECVMMSG_BATCHLEN); + struct mmsghdr *msgs = malloc(sizeof(struct mmsghdr) * RECVMMSG_BATCHLEN); + + /* Prepare batch. */ + memset(msgs, 0, sizeof(struct mmsghdr) * RECVMMSG_BATCHLEN); + for (unsigned i = 0; i < RECVMMSG_BATCHLEN; ++i) { + sockaddr_init(addrs + i, h->type); + iov[i].iov_base = iobuf + i * SOCKET_MTU_SZ; + iov[i].iov_len = SOCKET_MTU_SZ; + msgs[i].msg_hdr.msg_iov = iov + i; + msgs[i].msg_hdr.msg_iovlen = 1; + msgs[i].msg_hdr.msg_name = addrs[i].ptr; + msgs[i].msg_hdr.msg_namelen = addrs[i].len; + } + + /* Loop until all data is read. */ + ssize_t n = 0; + while (n >= 0) { + + /* Receive multiple messages. */ + n = recvmmsg(sock, msgs, RECVMMSG_BATCHLEN, MSG_WAITFORONE, 0); + + /* Cancellation point. */ + if (dt_is_cancelled(thread)) { + break; + } + + /* Error and interrupt handling. */ + if (unlikely(n <= 0)) { + if (errno != EINTR && errno != 0) { + dbg_net("udp: recvmmsg() failed: %d\n", + errno); + } + + if (!(h->state & ServerRunning)) { + break; + } else { + continue; + } + } + + /* Handle each received msg. */ + int ret = 0; + for (unsigned i = 0; i < n; ++i) { + struct iovec *cvec = msgs[i].msg_hdr.msg_iov; + size_t resp_len = msgs[i].msg_len; + ret = udp_handle(sock, cvec->iov_base, resp_len, &resp_len, + addrs + i, ns); + if (ret == KNOTD_EOK) { + msgs[i].msg_len = resp_len; + } else { + msgs[i].msg_len = 0; + } + + } + + /* Gather results. */ + /*! \todo Implement with sendmmsg() when it's ready. */ + for (unsigned i = 0; i < n; ++i) { + const size_t resp_len = msgs[i].msg_len; + if (resp_len > 0) { + dbg_net("udp: on fd=%d, sending answer size=%zd.\n", + sock, resp_len); + + // Send datagram + sockaddr_t *addr = addrs + i; + struct iovec *cvec = msgs[i].msg_hdr.msg_iov; + int res = sendto(sock, cvec->iov_base, resp_len, + 0, addr->ptr, addr->len); + + // Check result + if (res != (int)resp_len) { + dbg_net("udp: sendto(): failed: %d - %d.\n", + res, errno); + } + } + } + } + + /* Free allocd resources. */ + free(iobuf); + free(addrs); + free(iov); + free(msgs); + close(sock); + return KNOTD_EOK; +} +#endif +#endif + +int udp_master(dthread_t *thread) +{ + iohandler_t *handler = (iohandler_t *)thread->data; + int sock = handler->fd; + + /* Check socket. */ + if (sock < 0) { + dbg_net("udp: null socket recevied, finishing.\n"); + return KNOTD_EINVAL; + } + + /* Set socket options. */ + int flag = 1; +#ifndef DISABLE_IPV6 +# + if (handler->type == AF_INET6) { + /* Disable dual-stack for performance reasons. */ + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)); + + /* UDP packets will not exceed a minimum MTU size. */ + /*flag = IPV6_MIN_MTU; + setsockopt(fd, IPPROTO_IPV6, IPV6_MTU, &flag, sizeof(flag)); + flag = 1; */ + } +#endif + if (handler->type == AF_INET) { + +//#ifdef IP_PMTUDISC_DONT +// /* Disable fragmentation. */ +// flag = IP_PMTUDISC_DONT; +// setsockopt(sock, IPPROTO_IP, IP_MTU_DISCOVER, &flag, sizeof(flag)); +// flag = 1; +//#endif + } + + /* in case of STAT_COMPILE the following code will declare thread_stat + * variable in following fashion: stat_t *thread_stat; + */ + + stat_t *thread_stat = 0; + STAT_INIT(thread_stat); //XXX new stat instance every time. + stat_set_protocol(thread_stat, stat_UDP); + + /* Execute proper handler. */ + dbg_net_verb("udp: thread started (worker %p).\n", thread); + int ret = KNOTD_EOK; + +#ifdef ENABLE_RECVMMSG + +/* Check if MSG_WAITFORONE is supported. */ +#ifdef MSG_WAITFORONE + ret = udp_master_recvmmsg(thread, thread_stat); +#else /* Don't have MSG_WAITFORONE */ + ret = udp_master_recvfrom(thread, thread_stat); +#endif + +#else /* Don't have ENABLE_RECVMMSG */ + ret = udp_master_recvfrom(thread, thread_stat); +#endif + + + stat_free(thread_stat); + dbg_net_verb("udp: worker %p finished.\n", thread); + return ret; +} + diff --git a/src/knot/server/udp-handler.h b/src/knot/server/udp-handler.h new file mode 100644 index 0000000..f5fcd04 --- /dev/null +++ b/src/knot/server/udp-handler.h @@ -0,0 +1,74 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file udp-handler.h + * + * \author Marek Vavrusa <marek.vavusa@nic.cz> + * + * \brief UDP sockets threading model. + * + * The master socket locks one worker thread at a time + * and saves events in it's own backing store for asynchronous processing. + * The worker threads work asynchronously in thread pool. + * + * \addtogroup server + * @{ + */ + +#ifndef _KNOTD_UDPHANDLER_H_ +#define _KNOTD_UDPHANDLER_H_ + +#include "knot/server/socket.h" +#include "knot/server/server.h" +#include "knot/server/dthreads.h" + +/*! + * \brief Handle single packet. + * + * Function processses packet and prepares answer to qbuf, + * response length is set to resp_len. + * + * \param sock + * \param qbuf + * \param qbuflen + * \param resp_len + * \param addr + * \param ns + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_ERROR + * \retval KNOTD_ENOMEM + */ +int udp_handle(int sock, uint8_t *qbuf, size_t qbuflen, size_t *resp_len, + sockaddr_t* addr, knot_nameserver_t *ns); + +/*! + * \brief UDP handler thread runnable. + * + * Listen to DNS datagrams in a loop on a UDP socket and + * reply to them. This runnable is designed to be used as coherent + * and implements cancellation point. + * + * \param thread Associated thread from DThreads unit. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL invalid parameters. + */ +int udp_master(dthread_t *thread); + +#endif + +/*! @} */ diff --git a/src/knot/server/xfr-handler.c b/src/knot/server/xfr-handler.c new file mode 100644 index 0000000..45817cb --- /dev/null +++ b/src/knot/server/xfr-handler.c @@ -0,0 +1,1296 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <netinet/tcp.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <assert.h> +#include <urcu.h> + +#include "knot/common.h" +#include "knot/server/xfr-handler.h" +#include "libknot/nameserver/name-server.h" +#include "knot/other/error.h" +#include "knot/server/socket.h" +#include "knot/server/udp-handler.h" +#include "knot/server/tcp-handler.h" +#include "libknot/updates/xfr-in.h" +#include "knot/server/zones.h" +#include "libknot/util/error.h" +#include "libknot/tsig-op.h" +#include "common/evsched.h" + +void xfr_interrupt(xfrhandler_t *h) +{ + for(unsigned i = 0; i < h->unit->size; ++i) { + evqueue_write(h->workers[i]->q, "", 1); + } +} + +/*! + * \brief SOA query timeout handler. + */ +static int xfr_udp_timeout(event_t *e) +{ + knot_ns_xfr_t *data = (knot_ns_xfr_t *)e->data; + if (!data) { + return KNOTD_EINVAL; + } + + sockaddr_update(&data->addr); + char r_addr[SOCKADDR_STRLEN]; + sockaddr_tostr(&data->addr, r_addr, sizeof(r_addr)); + int r_port = sockaddr_portnum(&data->addr); + + /* Close socket. */ + knot_zone_t *z = data->zone; + if (z && knot_zone_get_contents(z) && knot_zone_data(z)) { + zonedata_t *zd = (zonedata_t *)knot_zone_data(z); + log_zone_info("%s '%s' query to %s:%d - timeout exceeded.\n", + data->type == XFR_TYPE_SOA ? "SOA" : "NOTIFY", + zd->conf->name, + r_addr, r_port); + } + + knot_ns_xfr_t cr = {}; + cr.type = XFR_TYPE_CLOSE; + cr.session = data->session; + cr.data = data; + cr.zone = data->zone; + xfrworker_t *w = (xfrworker_t *)data->owner; + if (w) { + evqueue_write(w->q, &cr, sizeof(knot_ns_xfr_t)); + } + + return KNOTD_EOK; +} + +/*! + * \brief Query reponse event handler function. + * + * Handle single query response event. + * + * \param loop Associated event pool. + * \param w Associated socket watcher. + * \param revents Returned events. + */ +static int xfr_process_udp_query(xfrworker_t *w, int fd, knot_ns_xfr_t *data) +{ + /* Prepare msg header. */ + struct msghdr msg; + memset(&msg, 0, sizeof(struct msghdr)); + struct iovec iov; + memset(&iov, 0, sizeof(struct iovec)); + iov.iov_base = data->wire; + iov.iov_len = data->wire_size; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_name = data->addr.ptr; + msg.msg_namelen = data->addr.len; + + /* Receive msg. */ + ssize_t n = recvmsg(data->session, &msg, 0); + size_t resp_len = data->wire_size; + if (n > 0) { + udp_handle(fd, data->wire, n, &resp_len, &data->addr, w->ns); + } + + /* Disable timeout. */ + evsched_t *sched = + ((server_t *)knot_ns_get_data(w->ns))->sched; + event_t *ev = (event_t *)data->data; + if (ev) { + dbg_xfr("xfr: cancelling UDP query timeout\n"); + evsched_cancel(sched, ev); + ev = (event_t *)data->data; + if (ev) { + evsched_event_free(sched, ev); + data->data = 0; + } + + /* Close after receiving response. */ + knot_ns_xfr_t cr = {}; + cr.type = XFR_TYPE_CLOSE; + cr.session = data->session; + cr.data = data; + cr.zone = data->zone; + evqueue_write(w->q, &cr, sizeof(knot_ns_xfr_t)); + } + + return KNOTD_EOK; +} + +/*! \todo Document me. */ +static void xfr_free_task(knot_ns_xfr_t *task) +{ + if (!task) { + return; + } + + xfrworker_t *w = (xfrworker_t *)task->owner; + if (!w) { + free(task); + return; + } + + /* Remove from fdset. */ + if (w->fdset) { + dbg_xfr("xfr_free_task: freeing fd=%d.\n", task->session); + fdset_remove(w->fdset, task->session); + } + + /* Unlock if XFR/IN.*/ + if (task->type == XFR_TYPE_AIN || task->type == XFR_TYPE_IIN) { + knot_zone_t *zone = task->zone; + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + if (zd) { + zd->xfr_in.wrkr = 0; + pthread_mutex_unlock(&zd->xfr_in.lock); + } + } + + /* Remove fd-related data. */ + xfrhandler_t *h = w->master; + pthread_mutex_lock(&h->tasks_mx); + skip_remove(h->tasks, (void*)((size_t)task->session), 0, 0); + pthread_mutex_unlock(&h->tasks_mx); + + /*! \todo Free data. */ + close(task->session); + free(task); +} + +/*! \todo Document me. */ +static knot_ns_xfr_t *xfr_register_task(xfrworker_t *w, knot_ns_xfr_t *req) +{ + knot_ns_xfr_t *t = malloc(sizeof(knot_ns_xfr_t)); + if (!t) { + return 0; + } + + memcpy(t, req, sizeof(knot_ns_xfr_t)); + sockaddr_update(&t->addr); + + /* Update request. */ + t->wire = 0; /* Invalidate shared buffer. */ + t->wire_size = 0; + t->data = 0; /* New zone will be built. */ + + /* Register data. */ + xfrhandler_t * h = w->master; + pthread_mutex_lock(&h->tasks_mx); + skip_insert(h->tasks, (void*)((ssize_t)t->session), t, 0); + pthread_mutex_unlock(&h->tasks_mx); + + /* Add to set. */ + fdset_add(w->fdset, t->session, OS_EV_READ); + t->owner = w; + return t; +} + +/*! + * \brief Clean pending transfer data. + */ +static int xfr_xfrin_cleanup(xfrworker_t *w, knot_ns_xfr_t *data) +{ + int ret = KNOTD_EOK; + knot_changesets_t *chs = 0; + + switch(data->type) { + case XFR_TYPE_AIN: + if (data->data) { + knot_zone_contents_deep_free( + (knot_zone_contents_t **)&data->data, 0); + data->data = 0; + } + break; + case XFR_TYPE_IIN: + if (data->data) { + chs = (knot_changesets_t *)data->data; + knot_free_changesets(&chs); + } + break; + } + + return ret; +} + +/*! + * \brief Finalize XFR/IN transfer. + * + * \param w XFR worker. + * \param data Associated data. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_ERROR + */ +static int xfr_xfrin_finalize(xfrworker_t *w, knot_ns_xfr_t *data) +{ + knot_zone_t *zone = (knot_zone_t *)data->zone; + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + const char *zorigin = zd->conf->name; + + /* CLEANUP */ +// // get the zone name from Question +// dbg_xfr_verb("Query: %p, response: %p\n", data->query, data->response); +// const knot_dname_t *qname = knot_packet_qname(data->query); +// char *zorigin = "(unknown)"; +// if (qname != NULL) { +// zorigin = knot_dname_to_str(qname); +// } + + int ret = KNOTD_EOK; + + switch(data->type) { + case XFR_TYPE_AIN: + dbg_xfr("xfr: AXFR/IN saving new zone\n"); + ret = zones_save_zone(data); + if (ret != KNOTD_EOK) { + xfr_xfrin_cleanup(w, data); + log_zone_error("AXFR failed to save " + "transferred zone '%s/IN' - %s\n", + zorigin, knotd_strerror(ret)); + } else { + dbg_xfr("xfr: AXFR/IN new zone saved.\n"); + ret = knot_ns_switch_zone(w->ns, data); + if (ret != KNOTD_EOK) { + log_zone_error("AXFR failed to " + "switch in-memory zone " + "'%s/IN' - %s\n", + zorigin, + knotd_strerror(ret)); + } + } + log_zone_info("AXFR transfer of zone '%s/IN' " + "%s.\n", zorigin, + ret == KNOTD_EOK ? "finished" : "failed"); + break; + case XFR_TYPE_IIN: + /* Save changesets. */ + dbg_xfr("xfr: IXFR/IN saving changesets\n"); + ret = zones_store_changesets(data); + if (ret != KNOTD_EOK) { + log_zone_error("IXFR failed to save " + "transferred changesets " + "for zone '%s/IN' - %s\n", + zorigin, knotd_strerror(ret)); + } else { + /* Update zone. */ + ret = zones_apply_changesets(data); + if (ret != KNOTD_EOK) { + log_zone_error("IXFR failed to " + "apply changesets to " + "zone '%s/IN' - %s\n", + zorigin, + knotd_strerror(ret)); + } + } + /* Free changesets, but not the data. */ + knot_changesets_t *chs = (knot_changesets_t *)data->data; + knot_free_changesets(&chs); + /* CLEANUP */ +// free(chs->sets); +// free(chs); + data->data = 0; + log_zone_info("IXFR transfer of zone '%s/IN' " + "%s.\n", zorigin, + ret == KNOTD_EOK ? "finished" : "failed"); + break; + default: + ret = KNOTD_EINVAL; + break; + } + + /* CLEANUP */ +// if (qname != NULL) { +// free(zorigin); +// } + + return ret; +} + +/*! + * \brief Prepare TSIG for XFR. + */ +static int xfr_prepare_tsig(knot_ns_xfr_t *xfr, knot_key_t *key) +{ + int ret = KNOT_EOK; + xfr->tsig_key = 0; /*key;*/ /*!< \todo [TSIG] DISABLED */ +// xfr->tsig_size = tsig_wire_maxsize(key); +// xfr->digest_max_size = tsig_alg_digest_length( +// key->algorithm); +// xfr->digest = malloc(xfr->digest_max_size); +// memset(xfr->digest, 0 , xfr->digest_max_size); +// dbg_xfr("xfr: found TSIG key (MAC len=%zu), adding to transfer\n", +// xfr->digest_max_size); + + return ret; +} + +/*! + * \brief Check TSIG if exists. + */ +static int xfr_check_tsig(knot_ns_xfr_t *xfr, knot_rcode_t *rcode) +{ + /*!< \todo [TSIG] DISABLED */ + return knot_packet_parse_rest(xfr->query); + +// /* Parse rest of the packet. */ +// int ret = KNOT_EOK; +// knot_packet_t *qry = xfr->query; +// knot_key_t *key = 0; +// const knot_rrset_t *tsig_rr = 0; +// ret = knot_packet_parse_rest(qry); +// if (ret == KNOT_EOK) { + +// /* Find TSIG key name from query. */ +// const knot_dname_t* kname = 0; +// int tsig_pos = knot_packet_additional_rrset_count(qry) - 1; +// if (tsig_pos >= 0) { +// tsig_rr = knot_packet_additional_rrset(qry, tsig_pos); +// if (knot_rrset_type(tsig_rr) == KNOT_RRTYPE_TSIG) { +// dbg_xfr("xfr: found TSIG in AR\n"); +// kname = knot_rrset_owner(tsig_rr); +// ret = KNOT_TSIG_EBADKEY; +// } +// } +// if (!kname) { +// dbg_xfr("xfr: TSIG not found in AR\n"); +// } + +// /* Find configured key for claimed key name. */ +// conf_key_t *ck = 0; +// WALK_LIST(ck, conf()->keys) { +// if (!kname) { +// break; +// } +// /* Compare stored keys to claimed. */ +// if (knot_dname_compare(ck->k.name, +// kname) == 0) { +// dbg_xfr("xfr: found claimed " +// "TSIG key for " +// "comparison\n"); +// key = &ck->k; +// break; +// } +// } + +// /* Validate with TSIG. */ +// if (key) { +// /* Prepare variables for TSIG */ +// xfr_prepare_tsig(xfr, key); + +// /* Copy MAC from query. */ +// dbg_xfr("xfr: validating TSIG from query\n"); +// const uint8_t* mac = tsig_rdata_mac(tsig_rr); +// size_t mac_len = tsig_rdata_mac_length(tsig_rr); +// if (mac_len > xfr->digest_max_size) { +// ret = KNOT_EMALF; +// dbg_xfr("xfr: MAC length %zu exceeds digest " +// "maximum size %zu\n", +// mac_len, xfr->digest_max_size); +// } else { +// memcpy(xfr->digest, mac, mac_len); +// xfr->digest_size = mac_len; + +// /* Check query TSIG. */ +// ret = knot_tsig_server_check( +// tsig_rr, +// knot_packet_wireformat(qry), +// knot_packet_size(qry), +// key); +// dbg_xfr("knot_tsig_server_check() returned %s\n", +// knot_strerror(ret)); +// } + +// /* Evaluate TSIG check results. */ +// switch(ret) { +// case KNOT_EOK: +// *rcode = KNOT_RCODE_NOERROR; +// break; +// case KNOT_TSIG_EBADKEY: +// case KNOT_TSIG_EBADSIG: +// // delete the TSIG key so that the error +// // response is not signed +// xfr->tsig_key = NULL; +// case KNOT_TSIG_EBADTIME: +// /*! \note [TSIG] Set TSIG rcode in TSIG RR. */ + +// *rcode = KNOT_RCODE_NOTAUTH; +// break; +// case KNOT_EMALF: +// *rcode = KNOT_RCODE_FORMERR; +// break; +// default: +// *rcode = KNOT_RCODE_SERVFAIL; +// } +// } else { +// dbg_xfr("xfr: no claimed key configured, " +// "treating as bad key\n"); +// } +// } else { +// dbg_xfr("xfr: failed to parse rest of the packet\n"); +// *rcode = KNOT_RCODE_FORMERR; +// } + +// return ret; +} + +/*! + * \brief XFR-IN event handler function. + * + * Handle single XFR client event. + * + * \param w Associated XFR worker. + * \param fd Associated file descriptor. + * \param data Transfer data. + */ +int xfr_process_event(xfrworker_t *w, int fd, knot_ns_xfr_t *data) +{ + /* Buffer for answering. */ + uint8_t buf[65535]; + + /* Update xfer state. */ + data->wire = buf; + data->wire_size = sizeof(buf); + + /* Handle SOA/NOTIFY responses. */ + if (data->type == XFR_TYPE_NOTIFY || data->type == XFR_TYPE_SOA) { + return xfr_process_udp_query(w, fd, data); + } + + /* Read DNS/TCP packet. */ + int ret = 0; + int rcvd = tcp_recv(fd, buf, sizeof(buf), 0); + data->wire_size = rcvd; + if (rcvd <= 0) { + data->wire_size = 0; + ret = KNOT_ECONN; + } else { + + /*! + * \todo [TSIG] Somewhere before this fetch the query digest and the TSIG + * associated with this transfer and save them to 'data'. + */ + + /* Process incoming packet. */ + switch(data->type) { + case XFR_TYPE_AIN: + ret = knot_ns_process_axfrin(w->ns, data); + break; + case XFR_TYPE_IIN: + ret = knot_ns_process_ixfrin(w->ns, data); + break; + default: + ret = KNOT_EBADARG; + break; + } + } + + /* AXFR-style IXFR. */ + if (ret == KNOT_ENOIXFR) { + dbg_xfr("xfr: Fallback to AXFR/IN.\n"); + assert(data->type == XFR_TYPE_IIN); + data->type = XFR_TYPE_AIN; + ret = knot_ns_process_axfrin(w->ns, data); + } + + /* Check return code for errors. */ + dbg_xfr_verb("xfr: processed incoming XFR packet (res = %d)\n", ret); + + /* Finished xfers. */ + int xfer_finished = 0; + if (ret != KNOT_EOK) { + xfer_finished = 1; + } + + /* IXFR refused, try again with AXFR. */ + knot_zone_t *zone = (knot_zone_t *)data->zone; + if (zone && data->type == XFR_TYPE_IIN && ret == KNOT_EXFRREFUSED) { + log_server_notice("IXFR/IN failed, attempting to use " + "AXFR/IN instead.\n"); + size_t bufsize = sizeof(buf); + data->wire_size = sizeof(buf); /* Reset maximum bufsize */ + ret = xfrin_create_axfr_query(zone->name, data, + &bufsize, 1); + /* Send AXFR/IN query. */ + if (ret == KNOTD_EOK) { + ret = data->send(data->session, &data->addr, + data->wire, bufsize); + /* Switch to AIN type XFR and return now. */ + if (ret == bufsize) { + data->type = XFR_TYPE_AIN; + return KNOTD_EOK; + } + } + } + + /* Handle errors. */ + if (ret < 0 && ret != KNOT_ENOXFR) { + log_server_error("%cXFR/IN request failed - %s\n", + data->type == XFR_TYPE_AIN ? 'A' : 'I', + knot_strerror(ret)); + } + + /* Check finished zone. */ + if (xfer_finished) { + + knot_zone_t *zone = (knot_zone_t *)data->zone; + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + const char *zorigin = zd->conf->name; + + /* Only for successful xfers. */ + if (ret > 0) { + ret = xfr_xfrin_finalize(w, data); + + /* AXFR bootstrap timeout. */ + rcu_read_lock(); + if (!knot_zone_contents(zone) && data->type == XFR_TYPE_AIN) { + /* Schedule request (60 - 90s random delay). */ + int tmr_s = AXFR_BOOTSTRAP_RETRY; + tmr_s += (30.0 * 1000) * (rand() / (RAND_MAX + 1.0)); + zd->xfr_in.bootstrap_retry = tmr_s; + log_zone_info("Another attempt to AXFR bootstrap " + "zone '%s' in %d seconds.\n", + zorigin, tmr_s/1000); + } + rcu_read_unlock(); + + /* Update timers. */ + server_t *server = (server_t *)knot_ns_get_data(w->ns); + zones_timers_update(zone, zd->conf, server->sched); + + } else { + /* Cleanup */ + xfr_xfrin_cleanup(w, data); + } + + /* Free TSIG buffers. */ + if (data->digest) { + free(data->digest); + data->digest = 0; + data->digest_size = 0; + } + if (data->tsig_data) { + free(data->tsig_data); + data->tsig_data = 0; + data->tsig_data_size = 0; + } + + /* Disconnect. */ + ret = KNOTD_ECONNREFUSED; /* Make it disconnect. */ + } else { + ret = KNOTD_EOK; + } + + return ret; +} + +/*! \todo Document me. + */ +static int xfr_client_start(xfrworker_t *w, knot_ns_xfr_t *data) +{ + /* Fetch associated zone. */ + knot_zone_t *zone = (knot_zone_t *)data->zone; + if (!zone) { + return KNOTD_EINVAL; + } + + /* Check if not already processing. */ + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + if (!zd) { + return KNOTD_EINVAL; + } + + /* Enqueue to worker that has zone locked for XFR/IN. */ + int ret = pthread_mutex_trylock(&zd->xfr_in.lock); + if (ret != 0) { + dbg_xfr_verb("xfr: XFR/IN switching to another thread, " + "zone '%s' is already in transfer\n", + zd->conf->name); + xfrworker_t *nextw = (xfrworker_t *)zd->xfr_in.wrkr; + if (nextw == 0) { + nextw = w; + } + evqueue_write(nextw->q, data, sizeof(knot_ns_xfr_t)); + return KNOTD_EOK; + } else { + zd->xfr_in.wrkr = w; + } + + /* Update address. */ + sockaddr_update(&data->addr); + char r_addr[SOCKADDR_STRLEN]; + sockaddr_tostr(&data->addr, r_addr, sizeof(r_addr)); + int r_port = sockaddr_portnum(&data->addr); + + /* Connect to remote. */ + if (data->session <= 0) { + int fd = socket_create(data->addr.family, SOCK_STREAM); + if (fd < 0) { + log_server_warning("Failed to create socket " + "(type=%s, family=%s).\n", + "SOCK_STREAM", + data->addr.family == AF_INET ? + "AF_INET" : "AF_INET6"); + return KNOTD_ERROR; + } + ret = connect(fd, data->addr.ptr, data->addr.len); + if (ret < 0) { + log_server_warning("Failed to connect to %cXFR master " + "at %s:%d.\n", + data->type == XFR_TYPE_AIN ? 'A' : 'I', + r_addr, r_port); + if (!knot_zone_contents(zone)) { + /* Reschedule request (120 - 240s random delay). */ + int tmr_s = AXFR_BOOTSTRAP_RETRY * 2; /* Malus x2 */ + tmr_s += (int)((120.0 * 1000) * + (rand() / (RAND_MAX + 1.0))); + event_t *ev = zd->xfr_in.timer; + if (ev) { + evsched_cancel(ev->parent, ev); + evsched_schedule(ev->parent, ev, tmr_s); + } + log_zone_notice("Zone AXFR bootstrap failed, " + "another attempt in %d seconds." + "\n", tmr_s / 1000); + } + return KNOTD_ERROR; + } + + /* Store new socket descriptor. */ + data->session = fd; + } else { + /* Duplicate existing socket descriptor. */ + data->session = dup(data->session); + } + + /* Fetch zone contents. */ + rcu_read_lock(); + const knot_zone_contents_t *contents = knot_zone_contents(zone); + if (!contents && data->type == XFR_TYPE_IIN) { + rcu_read_unlock(); + log_server_warning("Failed start IXFR on zone with no " + "contents\n"); + return KNOTD_ERROR; + } + + /*! \todo [TSIG] Somewhere before this determine if the server should + * use TSIG for this transfer and set appropriate fields + * in 'data'. + */ + int add_tsig = 0; + if (data->tsig_key) { + if (xfr_prepare_tsig(data, data->tsig_key) == KNOT_EOK) { + size_t data_bufsize = KNOT_NS_TSIG_DATA_MAX_SIZE; + data->tsig_data = malloc(data_bufsize); + if (data->tsig_data) { + dbg_xfr("xfr: using TSIG for XFR/IN\n"); + add_tsig = 1; + data->tsig_data_size = data_bufsize; + } else { + dbg_xfr("xfr: failed to allocate TSIG data " + "buffer (%zu kB)\n", + data_bufsize / 1024); + } + } + } + + /* Create XFR query. */ + size_t bufsize = data->wire_size; + switch(data->type) { + case XFR_TYPE_AIN: + ret = xfrin_create_axfr_query(zone->name, data, &bufsize, add_tsig); + break; + case XFR_TYPE_IIN: + ret = xfrin_create_ixfr_query(contents, data, &bufsize, add_tsig); + break; + default: + ret = KNOTD_EINVAL; + break; + } + + /* Handle errors. */ + if (ret != KNOT_EOK) { + dbg_xfr("xfr: failed to create XFR query type %d: %s\n", + data->type, knot_strerror(ret)); + return ret; + } + + /* Unlock zone contents. */ + rcu_read_unlock(); + + /* Add to pending transfers. */ + knot_ns_xfr_t *task = xfr_register_task(w, data); + + ret = data->send(data->session, &data->addr, data->wire, bufsize); + if (ret != bufsize) { + log_server_notice("Failed to send %cXFR query.", + data->type == XFR_TYPE_AIN ? 'A' : 'I'); + xfr_free_task(task); + return KNOTD_ERROR; + } + + /* Send XFR query. */ + log_server_info("%cXFR transfer of zone '%s/IN' with %s:%d started.\n", + data->type == XFR_TYPE_AIN ? 'A' : 'I', + zd->conf->name, + r_addr, r_port); + + return KNOTD_EOK; +} + +static int xfr_fd_compare(void *k1, void *k2) +{ + if (k1 < k2) { + return -1; + } + + if (k1 > k2) { + return 1; + } + + return 0; +} + +/* + * Public APIs. + */ + +static xfrworker_t* xfr_worker_create(xfrhandler_t *h, knot_nameserver_t *ns) +{ + xfrworker_t *w = malloc(sizeof(xfrworker_t)); + if(!w) { + return 0; + } + + /* Set nameserver and master. */ + w->ns = ns; + w->master = h; + + /* Create event queue. */ + w->q = evqueue_new(); + if (!w->q) { + free(w); + return 0; + } + + /* Create fdset. */ + w->fdset = fdset_new(); + if (!w->fdset) { + evqueue_free(&w->q); + free(w); + return 0; + } + + /* Add evqueue to fdset. */ + fdset_add(w->fdset, evqueue_pollfd(w->q), OS_EV_READ); + + return w; +} + +static void xfr_worker_free(xfrworker_t *w) { + if (w) { + evqueue_free(&w->q); + fdset_destroy(w->fdset); + free(w); + } +} + +xfrhandler_t *xfr_create(size_t thrcount, knot_nameserver_t *ns) +{ + /* Create XFR handler data. */ + xfrhandler_t *data = malloc(sizeof(xfrhandler_t)); + if (!data) { + return 0; + } + memset(data, 0, sizeof(xfrhandler_t)); + + /* Create RR mutex. */ + pthread_mutex_init(&data->rr_mx, 0); + + /* Create tasks structure and mutex. */ + pthread_mutex_init(&data->tasks_mx, 0); + data->tasks = skip_create_list(xfr_fd_compare); + + /* Initialize threads. */ + data->workers = malloc(thrcount * sizeof(xfrhandler_t*)); + if(data->workers == 0) { + pthread_mutex_destroy(&data->rr_mx); + free(data); + } + + /* Create threading unit. */ + dt_unit_t *unit = dt_create(thrcount); + if (!unit) { + pthread_mutex_destroy(&data->rr_mx); + free(data->workers); + free(data); + return 0; + } + data->unit = unit; + + /* Create worker threads. */ + unsigned initialized = 0; + for (unsigned i = 0; i < thrcount; ++i) { + data->workers[i] = xfr_worker_create(data, ns); + if(data->workers[i] == 0) { + break; + } + ++initialized; + } + + /* Check for initialized. */ + if (initialized != thrcount) { + for (unsigned i = 0; i < initialized; ++i) { + xfr_worker_free(data->workers[i]); + } + pthread_mutex_destroy(&data->rr_mx); + free(data->workers); + free(data->unit); + free(data); + return 0; + } + + /* Assign worker threads. */ + for (unsigned i = 0; i < thrcount; ++i) { + dt_repurpose(unit->threads[i], xfr_worker, data->workers[i]); + } + + data->interrupt = xfr_interrupt; + + return data; +} + +int xfr_free(xfrhandler_t *handler) +{ + if (!handler) { + return KNOTD_EINVAL; + } + + /* Free RR mutex. */ + pthread_mutex_destroy(&handler->rr_mx); + + /* Free tasks and mutex. */ + skip_destroy_list(&handler->tasks, 0, + (void(*)(void*))xfr_free_task); + pthread_mutex_destroy(&handler->tasks_mx); + + /* Free workers. */ + for (unsigned i = 0; i < handler->unit->size; ++i) { + xfr_worker_free(handler->workers[i]); + } + free(handler->workers); + + /* Delete unit. */ + dt_delete(&handler->unit); + free(handler); + + return KNOTD_EOK; +} + +int xfr_stop(xfrhandler_t *handler) +{ + /* Break loop. */ + dt_stop(handler->unit); + return KNOTD_EOK; +} + +int xfr_join(xfrhandler_t *handler) { + return dt_join(handler->unit); +} + +int xfr_request_init(knot_ns_xfr_t *r, int type, int flags, knot_packet_t *pkt) +{ + if (!r || type < 0 || flags < 0) { + return KNOTD_EINVAL; + } + + /* Blank and init. */ + memset(r, 0, sizeof(knot_ns_xfr_t)); + r->type = type; + r->flags = flags; + + /* Copy packet if applicable. */ + if (pkt != 0) { + uint8_t *wire_copy = malloc(sizeof(uint8_t) * pkt->size); + if (!wire_copy) { + ERR_ALLOC_FAILED; + return KNOTD_ENOMEM; + } + memcpy(wire_copy, pkt->wireformat, pkt->size); + pkt->wireformat = wire_copy; + r->query = pkt; + } + + return KNOTD_EOK; +} + +int xfr_request(xfrhandler_t *handler, knot_ns_xfr_t *req) +{ + if (!handler || !req) { + return KNOTD_EINVAL; + } + + /* Get next worker in RR fashion */ + pthread_mutex_lock(&handler->rr_mx); + evqueue_t *q = handler->workers[handler->rr]->q; + handler->rr = get_next_rr(handler->rr, handler->unit->size); + pthread_mutex_unlock(&handler->rr_mx); + + /* Delegate request. */ + int ret = evqueue_write(q, req, sizeof(knot_ns_xfr_t)); + if (ret < 0) { + return KNOTD_ERROR; + } + + return KNOTD_EOK; +} + +static int xfr_process_request(xfrworker_t *w, uint8_t *buf, size_t buflen) +{ + /* Read single request. */ + knot_ns_xfr_t xfr = {}; + int ret = evqueue_read(w->q, &xfr, sizeof(knot_ns_xfr_t)); + if (ret != sizeof(knot_ns_xfr_t)) { + dbg_xfr_verb("xfr: evqueue_read() returned %d.\n", ret); + return KNOTD_ENOTRUNNING; + } + + /* Update request. */ + sockaddr_update(&xfr.addr); + xfr.wire = buf; + xfr.wire_size = buflen; + char r_addr[SOCKADDR_STRLEN]; + sockaddr_tostr(&xfr.addr, r_addr, sizeof(r_addr)); + int r_port = sockaddr_portnum(&xfr.addr); + + conf_read_lock(); + + /* Handle request. */ + knot_ns_xfr_t *task = 0; + evsched_t *sch = 0; + const char *req_type = ""; + knot_rcode_t rcode = 0; + char *zname = "(unknown)"; + + /* XFR request state tracking. */ + int init_failed = 0; + const char *errstr = ""; + const knot_dname_t *qname = NULL; + + dbg_xfr_verb("Query ptr: %p\n", xfr.query); + + dbg_xfr_verb("xfr: processing request type '%d'\n", xfr.type); + switch(xfr.type) { + case XFR_TYPE_AOUT: + req_type = "AXFR/OUT"; + ret = knot_ns_init_xfr(w->ns, &xfr); + init_failed = (ret != KNOT_EOK); + errstr = knot_strerror(ret); + + // use the QNAME as the zone name to get names also for + // zones that are not in the server + qname = knot_packet_qname(xfr.query); + if (qname != NULL) { + zname = knot_dname_to_str(qname); + } + + /* Check requested zone. */ + if (!init_failed) { + ret = zones_xfr_check_zone(&xfr, &rcode); + init_failed = (ret != KNOTD_EOK); + errstr = knotd_strerror(ret); + } + + /* Check TSIG. */ + if (!init_failed) { + ret = xfr_check_tsig(&xfr, &rcode); + init_failed = (ret != KNOT_EOK); + errstr = knot_strerror(ret); + } + + /* Evaluate progress and answer if passed. */ + if (init_failed) { + knot_ns_xfr_send_error(w->ns, &xfr, rcode); + socket_close(xfr.session); + log_server_notice("AXFR transfer of zone '%s/OUT' " + "%s:%d failed: %s\n", + zname, + r_addr, r_port, + errstr); + } else { + ret = knot_ns_answer_axfr(w->ns, &xfr); + dbg_xfr("xfr: ns_answer_axfr() = %d.\n", ret); + if (ret != KNOTD_EOK) { + socket_close(xfr.session); + } else { + log_server_info("AXFR transfer of zone '%s/OUT' " + "to %s:%d successful.\n", + zname, + r_addr, r_port); + } + } + + if (xfr.digest) { + free(xfr.digest); + xfr.digest_max_size = 0; + xfr.digest = 0; + } + free(xfr.query->wireformat); + xfr.query->wireformat = 0; + knot_packet_free(&xfr.query); /* Free query. */ + + if (qname != NULL) { + free(zname); + } + + break; + case XFR_TYPE_IOUT: + req_type = "IXFR/OUT"; + ret = knot_ns_init_xfr(w->ns, &xfr); + init_failed = (ret != KNOT_EOK); + errstr = knot_strerror(ret); + + qname = knot_packet_qname(xfr.query); + if (qname != NULL) { + zname = knot_dname_to_str(qname); + } + + /* Check requested zone. */ + if (!init_failed) { + ret = zones_xfr_check_zone(&xfr, &rcode); + init_failed = (ret != KNOTD_EOK); + errstr = knotd_strerror(ret); + } + + /* Check TSIG. */ + if (!init_failed) { + ret = xfr_check_tsig(&xfr, &rcode); + init_failed = (ret != KNOT_EOK); + errstr = knot_strerror(ret); + } + + uint32_t serial_from = 0; + uint32_t serial_to = 0; + + // Check serial differeces + if (!init_failed) { + dbg_xfr_verb("Loading serials for IXFR.\n"); + ret = ns_ixfr_load_serials(&xfr, &serial_from, + &serial_to); + dbg_xfr_detail("Loaded serials: from: %u, to: %u\n", + serial_from, serial_to); + init_failed = (ret != KNOT_EOK); + errstr = knot_strerror(ret); + } + + /* Load changesets from journal. */ + if (!init_failed) { + dbg_xfr_verb("Loading changesets from journal.\n"); + ret = zones_xfr_load_changesets(&xfr, serial_from, + serial_to); + if (ret != KNOTD_EOK) { + /* History cannot be reconstructed, fallback to AXFR. */ + if (ret == KNOTD_ERANGE || ret == KNOTD_ENOENT) { + log_server_info("IXFR transfer of zone '%s/OUT'" + " - failed to load data from journal: %s." + " Fallback to AXFR.\n", + knotd_strerror(ret), zname); + xfr.type = XFR_TYPE_AOUT; + xfr_request(w->master, &xfr); + conf_read_unlock(); + return KNOTD_EOK; + } else if (ret == KNOTD_EMALF) { + rcode = KNOT_RCODE_FORMERR; + } else { + rcode = KNOT_RCODE_SERVFAIL; + } + init_failed = (ret != KNOTD_EOK); + errstr = knotd_strerror(ret); + } + } + + /* Evaluate progress and answer if passed. */ + if (init_failed) { + knot_ns_xfr_send_error(w->ns, &xfr, rcode); + log_server_notice("IXFR transfer of zone '%s/OUT' " + "%s:%d failed: %s\n", + zname, + r_addr, r_port, + errstr); + ret = KNOTD_ERROR; + } else { + ret = knot_ns_answer_ixfr(w->ns, &xfr); + dbg_xfr("xfr: ns_answer_ixfr() = %d.\n", ret); + if (ret != KNOTD_EOK) { + socket_close(xfr.session); + } else { + log_server_info("IXFR transfer of zone '%s/OUT'" + " - not enough data in journal," + " fallback to AXFR.\n", + zname); + xfr.type = XFR_TYPE_AOUT; + xfr_request(w->master, &xfr); + return KNOTD_EOK; + } + } + if (xfr.digest) { + free(xfr.digest); + xfr.digest = 0; + xfr.digest_max_size = 0; + } + free(xfr.query->wireformat); + knot_packet_free(&xfr.query); /* Free query. */ + + if (qname) { + free(zname); + } + + break; + case XFR_TYPE_AIN: + req_type = "AXFR/IN"; + case XFR_TYPE_IIN: + if (xfr.type == XFR_TYPE_IIN) { + req_type = "IXFR/IN"; + } + + ret = xfr_client_start(w, &xfr); + + /* Report. */ + if (ret != KNOTD_EOK && ret != KNOTD_EACCES) { + log_server_error("%s request from %s:%d failed: %s\n", + req_type, r_addr, r_port, + knotd_strerror(ret)); + } + break; + case XFR_TYPE_SOA: + case XFR_TYPE_NOTIFY: + /* Register task. */ + task = xfr_register_task(w, &xfr); + if (!task) { + ret = KNOTD_ENOMEM; + break; + } + + req_type = "SOA or NOTIFY"; + dbg_xfr("xfr: waiting for %s query response\n", + xfr.type == XFR_TYPE_SOA ? "SOA" : "NOTIFY"); + + /* Add timeout. */ + sch = ((server_t *)knot_ns_get_data(w->ns))->sched; + task->data = evsched_schedule_cb(sch, xfr_udp_timeout, + task, SOA_QRY_TIMEOUT); + ret = KNOTD_EOK; + break; + /* Socket close event. */ + case XFR_TYPE_CLOSE: + xfr_free_task((knot_ns_xfr_t *)xfr.data); + ret = KNOTD_EOK; + default: + break; + } + + conf_read_unlock(); + + return ret; +} + +int xfr_worker(dthread_t *thread) +{ + xfrworker_t *w = (xfrworker_t *)thread->data; + + /* Check data. */ + if (w < 0) { + dbg_xfr("xfr: NULL worker data, worker cancelled\n"); + return KNOTD_EINVAL; + } + + /* Buffer for answering. */ + uint8_t buf[65535]; + + /* Accept requests. */ + int ret = 0; + dbg_xfr_verb("xfr: worker=%p starting\n", w); + for (;;) { + + /* Check for cancellation. */ + if (dt_is_cancelled(thread)) { + break; + } + + /* Poll fdset. */ + int nfds = fdset_wait(w->fdset); + if (nfds <= 0) { + continue; + } + + /* Check for cancellation. */ + if (dt_is_cancelled(thread)) { + break; + } + + /* Iterate fdset. */ + xfrhandler_t *h = w->master; + knot_ns_xfr_t *data = 0; + int rfd = evqueue_pollfd(w->q); + fdset_it_t it; + fdset_begin(w->fdset, &it); + while(1) { + + /* Check if it request. */ + if (it.fd == rfd) { + dbg_xfr_verb("xfr: worker=%p processing request\n", + w); + ret = xfr_process_request(w, buf, sizeof(buf)); + if (ret == KNOTD_ENOTRUNNING) { + break; + } + } else { + /* Find data. */ + pthread_mutex_lock(&h->tasks_mx); + data = skip_find(h->tasks, (void*)((size_t)it.fd)); + pthread_mutex_unlock(&h->tasks_mx); + dbg_xfr_verb("xfr: worker=%p processing event on " + "fd=%d data=%p.\n", + w, it.fd, data); + ret = xfr_process_event(w, it.fd, data); + if (ret != KNOTD_EOK) { + xfr_free_task(data); + } + } + + /* Next fd. */ + if (fdset_next(w->fdset, &it) < 0) { + break; + } + } + } + + + /* Stop whole unit. */ + dbg_xfr_verb("xfr: worker=%p finished.\n", w); + thread->data = 0; + return KNOTD_EOK; +} diff --git a/src/knot/server/xfr-handler.h b/src/knot/server/xfr-handler.h new file mode 100644 index 0000000..3587d93 --- /dev/null +++ b/src/knot/server/xfr-handler.h @@ -0,0 +1,163 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file xfr-handler.h + * + * \author Marek Vavrusa <marek.vavusa@nic.cz> + * + * \brief XFR requests handler. + * + * \addtogroup server + * @{ + */ + +#ifndef _KNOTD_XFRHANDLER_H_ +#define _KNOTD_XFRHANDLER_H_ + +#include "knot/server/dthreads.h" +#include "libknot/nameserver/name-server.h" +#include "common/evqueue.h" +#include "common/fdset.h" +#include "common/skip-list.h" /*!< \todo Consider another data struct. */ + +struct xfrhandler_t; + +/*! + * \brief XFR worker structure. + */ +typedef struct xfrworker_t +{ + knot_nameserver_t *ns; /*!< \brief Pointer to nameserver.*/ + evqueue_t *q; /*!< \brief Shared XFR requests queue.*/ + fdset_t *fdset; /*!< \brief File descriptor set. */ + struct xfrhandler_t *master; /*! \brief Worker master. */ +} xfrworker_t; + +/*! + * \brief XFR handler structure. + */ +typedef struct xfrhandler_t +{ + dt_unit_t *unit; /*!< \brief Threading unit. */ + xfrworker_t **workers; /*!< \brief Workers. */ + skip_list_t *tasks; /*!< \brief Pending tasks. */ + pthread_mutex_t tasks_mx; /*!< \brief Tasks synchronisation. */ + void (*interrupt)(struct xfrhandler_t *h); /*!< Interrupt handler. */ + unsigned rr; /*!< \brief Round-Robin counter. */ + pthread_mutex_t rr_mx; /*!< \brief RR mutex. */ +} xfrhandler_t; + +/*! + * \brief Create XFR threading unit. + * + * Unit can be controlled by standard DThreads API. + * Unit is created in Idle mode. + * + * \param thrcount Requested number of threads. + * \param ns Pointer to nameserver. + * + * \retval New handler on success. + * \retval NULL on error. + */ +xfrhandler_t *xfr_create(size_t thrcount, knot_nameserver_t *ns); + +/*! + * \brief Delete XFR handler. + * + * \warning Threading unit must be stopped and joined. + * + * \param handler XFR handler. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on NULL handler. + * \retval KNOTD_ERROR on error. + */ +int xfr_free(xfrhandler_t *handler); + +/*! + * \brief Start XFR handler. + * + * \param handler XFR handler. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_ERROR on error. + */ +static inline int xfr_start(xfrhandler_t *handler) { + return dt_start(handler->unit); +} + +/*! + * \brief Stop XFR handler. + * + * \param handler XFR handler. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_ERROR on error. + */ +int xfr_stop(xfrhandler_t *handler); + +/*! + * \brief Wait for XFR handler to finish. + * + * \param handler XFR handler. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_ERROR on error. + */ +int xfr_join(xfrhandler_t *handler); + +/*! + * \brief Prepare XFR request. + * + * \param r XFR request. + * \param type Request type. + * \param flags Request flags. + * \param pkt Query packet or NULL. + * + * \retval KNOTD_EOK + * \retval KNOTD_ENOMEM + * \retval KNOTD_EINVAL + */ +int xfr_request_init(knot_ns_xfr_t *r, int type, int flags, knot_packet_t *pkt); + +/*! + * \brief Enqueue XFR request. + * + * \param handler XFR handler instance. + * \param req XFR request. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on NULL handler or request. + * \retval KNOTD_ERROR on error. + */ +int xfr_request(xfrhandler_t *handler, knot_ns_xfr_t *req); + +/*! + * \brief XFR master runnable. + * + * Processes incoming AXFR/IXFR requests asynchonously. + * When no thread is available at the moment, request is enqueued. + * + * \param thread Associated thread from DThreads unit. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL invalid parameters. + */ +int xfr_worker(dthread_t *thread); + +#endif // _KNOTD_XFRHANDLER_H_ + +/*! @} */ diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c new file mode 100644 index 0000000..4456dac --- /dev/null +++ b/src/knot/server/zones.c @@ -0,0 +1,2352 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <sys/stat.h> + +#include "common/lists.h" +#include "libknot/dname.h" +#include "libknot/util/wire.h" +#include "knot/zone/zone-dump-text.h" +#include "knot/zone/zone-load.h" +#include "libknot/zone/zone.h" +#include "libknot/zone/zonedb.h" +#include "knot/conf/conf.h" +#include "knot/other/debug.h" +#include "knot/other/error.h" +#include "knot/other/log.h" +#include "knot/server/notify.h" +#include "knot/server/server.h" +#include "libknot/updates/xfr-in.h" +#include "knot/server/zones.h" +#include "libknot/util/error.h" +#include "knot/zone/zone-dump.h" +#include "libknot/nameserver/name-server.h" +#include "libknot/updates/changesets.h" + +static const size_t XFRIN_CHANGESET_BINARY_SIZE = 100; +static const size_t XFRIN_CHANGESET_BINARY_STEP = 100; + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Wrapper for TCP send. + * \todo Implement generic fd pool properly with callbacks. + */ +#include "knot/server/tcp-handler.h" +static int zones_send_cb(int fd, sockaddr_t *addr, uint8_t *msg, size_t msglen) +{ + return tcp_send(fd, msg, msglen); +} + +/*----------------------------------------------------------------------------*/ + +/*! \brief Zone data destructor function. */ +static int zonedata_destroy(knot_zone_t *zone) +{ + dbg_zones_verb("zones: zonedata_destroy(%p) called\n", zone); + + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + if (!zd) { + return KNOTD_EINVAL; + } + + /* Cancel REFRESH timer. */ + if (zd->xfr_in.timer) { + evsched_t *sch = zd->xfr_in.timer->parent; + evsched_cancel(sch, zd->xfr_in.timer); + evsched_event_free(sch, zd->xfr_in.timer); + zd->xfr_in.timer = 0; + } + + /* Cancel EXPIRE timer. */ + if (zd->xfr_in.expire) { + evsched_t *sch = zd->xfr_in.expire->parent; + evsched_cancel(sch, zd->xfr_in.expire); + evsched_event_free(sch, zd->xfr_in.expire); + zd->xfr_in.expire = 0; + } + + /* Remove list of pending NOTIFYs. */ + pthread_mutex_lock(&zd->lock); + notify_ev_t *ev = 0, *evn = 0; + WALK_LIST_DELSAFE(ev, evn, zd->notify_pending) { + zones_cancel_notify(zd, ev); + } + pthread_mutex_unlock(&zd->lock); + + /* Cancel IXFR DB sync timer. */ + if (zd->ixfr_dbsync) { + evsched_t *sch = zd->ixfr_dbsync->parent; + evsched_cancel(sch, zd->ixfr_dbsync); + evsched_event_free(sch, zd->ixfr_dbsync); + zd->ixfr_dbsync = 0; + } + + /* Destroy mutex. */ + pthread_mutex_destroy(&zd->lock); + pthread_mutex_destroy(&zd->xfr_in.lock); + + acl_delete(&zd->xfr_in.acl); + acl_delete(&zd->xfr_out); + acl_delete(&zd->notify_in); + acl_delete(&zd->notify_out); + + /* Close IXFR db. */ + journal_close(zd->ixfr_db); + + free(zd); + + /* Invalidate. */ + zone->dtor = 0; + zone->data = 0; + + return KNOTD_EOK; +} + +/*! \brief Zone data constructor function. */ +static int zonedata_init(conf_zone_t *cfg, knot_zone_t *zone) +{ + zonedata_t *zd = malloc(sizeof(zonedata_t)); + if (!zd) { + return KNOTD_ENOMEM; + } + + /* Link to config. */ + zd->conf = cfg; + zd->server = 0; + + /* Initialize mutex. */ + pthread_mutex_init(&zd->lock, 0); + + /* Initialize ACLs. */ + zd->xfr_out = 0; + zd->notify_in = 0; + zd->notify_out = 0; + + /* Initialize XFR-IN. */ + sockaddr_init(&zd->xfr_in.master, -1); + zd->xfr_in.timer = 0; + zd->xfr_in.expire = 0; + zd->xfr_in.next_id = -1; + zd->xfr_in.acl = 0; + zd->xfr_in.wrkr = 0; + zd->xfr_in.bootstrap_retry = 0; + pthread_mutex_init(&zd->xfr_in.lock, 0); + + /* Initialize NOTIFY. */ + init_list(&zd->notify_pending); + + /* Initialize IXFR database. */ + zd->ixfr_db = journal_open(cfg->ixfr_db, cfg->ixfr_fslimit, + JOURNAL_DIRTY); + if (!zd->ixfr_db) { + int ret = journal_create(cfg->ixfr_db, JOURNAL_NCOUNT); + if (ret != KNOTD_EOK) { + log_server_error("Failed to create journal file " + "'%s'\n", cfg->ixfr_db); + } + zd->ixfr_db = journal_open(cfg->ixfr_db, cfg->ixfr_fslimit, + JOURNAL_DIRTY); + } + + if (zd->ixfr_db == 0) { + log_server_error("Failed to open journal file " + "'%s'\n", cfg->ixfr_db); + } + + /* Initialize IXFR database syncing event. */ + zd->ixfr_dbsync = 0; + + /* Set and install destructor. */ + zone->data = zd; + zone->dtor = zonedata_destroy; + + /* Set zonefile SOA serial. */ + const knot_rrset_t *soa_rrs = 0; + const knot_rdata_t *soa_rr = 0; + + /* Load serial. */ + zd->zonefile_serial = 0; + const knot_zone_contents_t *contents = knot_zone_contents(zone); + if (contents) { + soa_rrs = knot_node_rrset(knot_zone_contents_apex(contents), + KNOT_RRTYPE_SOA); + soa_rr = knot_rrset_rdata(soa_rrs); + int64_t serial = knot_rdata_soa_serial(soa_rr); + zd->zonefile_serial = (uint32_t)serial; + if (serial < 0) { + return KNOTD_EINVAL; + } + } + + return KNOTD_EOK; +} + +/*! + * \brief Return SOA timer value. + * + * \param zone Pointer to zone. + * \param rr_func RDATA specificator. + * \return Timer in miliseconds. + */ +static uint32_t zones_soa_timer(knot_zone_t *zone, + uint32_t (*rr_func)(const knot_rdata_t*)) +{ + if (!zone) { + dbg_zones_verb("zones: zones_soa_timer() called " + "with NULL zone\n"); + } + + uint32_t ret = 0; + + /* Retrieve SOA RDATA. */ + const knot_rrset_t *soa_rrs = 0; + const knot_rdata_t *soa_rr = 0; + knot_zone_contents_t * zc = knot_zone_get_contents((zone)); + if (!zc) { + return 0; + } + + soa_rrs = knot_node_rrset(knot_zone_contents_apex(zc), + KNOT_RRTYPE_SOA); + soa_rr = knot_rrset_rdata(soa_rrs); + ret = rr_func(soa_rr); + + /* Convert to miliseconds. */ + return ret * 1000; +} + +/*! + * \brief Return SOA REFRESH timer value. + * + * \param zone Pointer to zone. + * \return REFRESH timer in miliseconds. + */ +static uint32_t zones_soa_refresh(knot_zone_t *zone) +{ + return zones_soa_timer(zone, knot_rdata_soa_refresh); +} + +/*! + * \brief Return SOA RETRY timer value. + * + * \param zone Pointer to zone. + * \return RETRY timer in miliseconds. + */ +static uint32_t zones_soa_retry(knot_zone_t *zone) +{ + return zones_soa_timer(zone, knot_rdata_soa_retry); +} + +/*! + * \brief Return SOA EXPIRE timer value. + * + * \param zone Pointer to zone. + * \return EXPIRE timer in miliseconds. + */ +static uint32_t zones_soa_expire(knot_zone_t *zone) +{ + return zones_soa_timer(zone, knot_rdata_soa_expire); +} + +/*! + * \brief XFR/IN expire event handler. + */ +static int zones_expire_ev(event_t *e) +{ + rcu_read_lock(); + dbg_zones("zones: EXPIRE timer event\n"); + knot_zone_t *zone = (knot_zone_t *)e->data; + if (!zone) { + return KNOTD_EINVAL; + } + if (!zone->data) { + return KNOTD_EINVAL; + } + + zonedata_t *zd = (zonedata_t *)zone->data; + + /* Won't accept any pending SOA responses. */ + zd->xfr_in.next_id = -1; + + /* Mark the zone as expired. This will remove the zone contents. */ + knot_zone_contents_t *contents = knot_zonedb_expire_zone( + zd->server->nameserver->zone_db, zone->name); + + if (contents == NULL) { + log_server_warning("Non-existent zone expired. Ignoring.\n"); + rcu_read_unlock(); + return 0; + } + + + rcu_read_unlock(); + + dbg_zones_verb("zones: zone %s expired, waiting for xfers to finish\n", + zd->conf->name); + pthread_mutex_lock(&zd->xfr_in.lock); + dbg_zones_verb("zones: zone %s locked, no xfers are running\n", + zd->conf->name); + + synchronize_rcu(); + pthread_mutex_unlock(&zd->xfr_in.lock); + + log_server_info("Zone '%s' expired.\n", zd->conf->name); + + /* Early finish this event to prevent lockup during cancellation. */ + dbg_zones("zones: zone expired, removing from database\n"); + evsched_event_finished(e->parent); + + /* Cancel REFRESH timer. */ + if (zd->xfr_in.timer) { + evsched_cancel(e->parent, zd->xfr_in.timer); + evsched_event_free(e->parent, zd->xfr_in.timer); + zd->xfr_in.timer = 0; + } + + /* Free EXPIRE timer. */ + if (zd->xfr_in.expire) { + evsched_event_free(e->parent, zd->xfr_in.expire); + zd->xfr_in.expire = 0; + } + + knot_zone_contents_deep_free(&contents, 0); + + return 0; +} + +/*! + * \brief Zone REFRESH or RETRY event. + */ +static int zones_refresh_ev(event_t *e) +{ + dbg_zones("zones: REFRESH or RETRY timer event\n"); + knot_zone_t *zone = (knot_zone_t *)e->data; + if (!zone) { + return KNOTD_EINVAL; + } + + /* Cancel pending timers. */ + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + if (!zd) { + return KNOTD_EINVAL; + } + + /* Prepare buffer for query. */ + uint8_t qbuf[SOCKET_MTU_SZ]; + size_t buflen = SOCKET_MTU_SZ; + + /* Lock RCU. */ + rcu_read_lock(); + + /* Check for contents. */ + if (!knot_zone_contents(zone)) { + + /* Bootstrap from XFR master. */ + knot_ns_xfr_t xfr_req; + memset(&xfr_req, 0, sizeof(knot_ns_xfr_t)); + memcpy(&xfr_req.addr, &zd->xfr_in.master, sizeof(sockaddr_t)); + xfr_req.data = (void *)zone; + xfr_req.send = zones_send_cb; + + /* Select transfer method. */ + xfr_req.type = XFR_TYPE_AIN; + xfr_req.zone = zone; + + /* Select TSIG key. */ + /*!< \todo [TSIG] DISABLED */ + xfr_req.tsig_key = 0; +// if (zd->xfr_in.tsig_key.name) { +// xfr_req.tsig_key = &zd->xfr_in.tsig_key; +// } + + /* Unlock zone contents. */ + rcu_read_unlock(); + + /* Enqueue XFR request. */ + int locked = pthread_mutex_trylock(&zd->xfr_in.lock); + if (locked != 0) { + dbg_zones("zones: already bootstrapping '%s'\n", + zd->conf->name); + return KNOTD_EOK; + } else { + log_zone_info("Attempting to bootstrap zone %s from master\n", + zd->conf->name); + pthread_mutex_unlock(&zd->xfr_in.lock); + } + + return xfr_request(zd->server->xfr_h, &xfr_req); + } + + /* Do not issue SOA query if transfer is pending. */ + int locked = pthread_mutex_trylock(&zd->xfr_in.lock); + if (locked != 0) { + dbg_zones("zones: zone '%s' is being transferred, " + "deferring SOA query\n", + zd->conf->name); + + /* Reschedule as RETRY timer. */ + evsched_schedule(e->parent, e, zones_soa_retry(zone)); + dbg_zones("zones: RETRY of '%s' after %u seconds\n", + zd->conf->name, zones_soa_retry(zone) / 1000); + + /* Unlock RCU. */ + rcu_read_unlock(); + return KNOTD_EOK; + } else { + pthread_mutex_unlock(&zd->xfr_in.lock); + } + + /* Create query. */ + /*! \todo API for retrieval of name. */ + + /*! \todo [TSIG] CHANGE!!! only for compatibility now. */ + knot_ns_xfr_t xfr_req; + memset(&xfr_req, 0, sizeof(knot_ns_xfr_t)); + xfr_req.wire = qbuf; + + int ret = xfrin_create_soa_query(zone->name, &xfr_req, &buflen); + if (ret == KNOT_EOK) { + + sockaddr_t *master = &zd->xfr_in.master; + + /* Create socket on random port. */ + int sock = socket_create(master->family, SOCK_DGRAM); + + /* Send query. */ + ret = -1; + if (sock > -1) { + ret = sendto(sock, qbuf, buflen, 0, + master->ptr, master->len); + } + + /* Store ID of the awaited response. */ + if (ret == buflen) { + zd->xfr_in.next_id = knot_wire_get_id(qbuf); + dbg_zones("zones: expecting SOA response " + "ID=%d for '%s'\n", + zd->xfr_in.next_id, zd->conf->name); + } + + /* Watch socket. */ + knot_ns_xfr_t req; + memset(&req, 0, sizeof(req)); + req.session = sock; + req.type = XFR_TYPE_SOA; + req.zone = zone; + memcpy(&req.addr, master, sizeof(sockaddr_t)); + sockaddr_update(&req.addr); + xfr_request(zd->server->xfr_h, &req); + } else { + ret = KNOTD_ERROR; + } + + /* Schedule EXPIRE timer on first attempt. */ + if (!zd->xfr_in.expire) { + uint32_t expire_tmr = zones_soa_expire(zone); + zd->xfr_in.expire = evsched_schedule_cb( + e->parent, + zones_expire_ev, + zone, expire_tmr); + dbg_zones("zones: EXPIRE of '%s' after %u seconds\n", + zd->conf->name, expire_tmr / 1000); + } + + /* Reschedule as RETRY timer. */ + evsched_schedule(e->parent, e, zones_soa_retry(zone)); + dbg_zones("zones: RETRY of '%s' after %u seconds\n", + zd->conf->name, zones_soa_retry(zone) / 1000); + + /* Unlock RCU. */ + rcu_read_unlock(); + + return ret; +} + +/*! + * \brief Send NOTIFY to slave server. + */ +static int zones_notify_send(event_t *e) +{ + dbg_notify("notify: NOTIFY timer event\n"); + + notify_ev_t *ev = (notify_ev_t *)e->data; + knot_zone_t *zone = ev->zone; + if (!zone) { + log_zone_error("notify: NOTIFY invalid event received\n"); + evsched_event_free(e->parent, e); + free(ev); + return KNOTD_EINVAL; + } + + /* Check for answered/cancelled query. */ + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + knot_zone_contents_t *contents = knot_zone_get_contents(zone); + + /* Reduce number of available retries. */ + --ev->retries; + + /* Check number of retries. */ + if (ev->retries < 0) { + log_server_notice("NOTIFY query maximum number of retries " + "for zone %s exceeded.\n", + zd->conf->name); + pthread_mutex_lock(&zd->lock); + rem_node(&ev->n); + evsched_event_free(e->parent, e); + free(ev); + pthread_mutex_unlock(&zd->lock); + return KNOTD_EMALF; + } + + /* Prepare buffer for query. */ + uint8_t qbuf[SOCKET_MTU_SZ]; + size_t buflen = sizeof(qbuf); + + /* RFC suggests 60s, but it is configurable. */ + int retry_tmr = ev->timeout * 1000; + + /* Reschedule. */ + conf_read_lock(); + evsched_schedule(e->parent, e, retry_tmr); + dbg_notify("notify: Query RETRY after %u secs (zone '%s')\n", + retry_tmr / 1000, zd->conf->name); + conf_read_unlock(); + + /* Create query. */ + int ret = notify_create_request(contents, qbuf, &buflen); + if (ret == KNOTD_EOK && zd->server) { + + /* Lock RCU. */ + rcu_read_lock(); + + /* Create socket on random port. */ + int sock = socket_create(ev->addr.family, SOCK_DGRAM); + + /* Send query. */ + ret = -1; + if (sock > -1) { + ret = sendto(sock, qbuf, buflen, 0, + ev->addr.ptr, ev->addr.len); + } + + /* Store ID of the awaited response. */ + if (ret == buflen) { + char r_addr[SOCKADDR_STRLEN]; + sockaddr_tostr(&ev->addr, r_addr, sizeof(r_addr)); + int r_port = sockaddr_portnum(&ev->addr); + ev->msgid = knot_wire_get_id(qbuf); + log_server_info("Issued NOTIFY query to %s:%d, expecting " + "response ID=%d\n", + r_addr, r_port, + ev->msgid); + + } + + /* Watch socket. */ + knot_ns_xfr_t req; + memset(&req, 0, sizeof(req)); + req.session = sock; + req.type = XFR_TYPE_NOTIFY; + req.zone = zone; + memcpy(&req.addr, &ev->addr, sizeof(sockaddr_t)); + xfr_request(zd->server->xfr_h, &req); + + /* Unlock RCU */ + rcu_read_unlock(); + } + + return ret; +} + +/*! \brief Function for marking nodes as synced and updated. */ +static int zones_ixfrdb_sync_apply(journal_t *j, journal_node_t *n) +{ + /* Check for dirty bit (not synced to permanent storage). */ + if (n->flags & JOURNAL_DIRTY) { + + /* Remove dirty bit. */ + n->flags = n->flags & ~JOURNAL_DIRTY; + + /* Sync. */ + journal_update(j, n); + } + + return KNOTD_EOK; +} + +/*! + * \brief Sync chagnes in zone to zonefile. + */ +static int zones_zonefile_sync_ev(event_t *e) +{ + dbg_zones("zones: IXFR database SYNC timer event\n"); + + /* Fetch zone. */ + knot_zone_t *zone = (knot_zone_t *)e->data; + if (!zone) { + return KNOTD_EINVAL; + } + + /* Fetch zone data. */ + zonedata_t *zd = (zonedata_t *)zone->data; + if (!zd) { + return KNOTD_EINVAL; + } + + /* Execute zonefile sync. */ + int ret = zones_zonefile_sync(zone); + if (ret == KNOTD_EOK) { + log_zone_info("Applied differences of '%s' to zonefile.\n", + zd->conf->name); + } else { + log_zone_warning("Failed to apply differences of '%s' " + "to zonefile.\n", + zd->conf->name); + } + + /* Reschedule. */ + conf_read_lock(); + evsched_schedule(e->parent, e, zd->conf->dbsync_timeout * 1000); + dbg_zones("zones: next IXFR database SYNC of '%s' in %d seconds\n", + zd->conf->name, zd->conf->dbsync_timeout); + conf_read_unlock(); + + return ret; +} + +/*! + * \brief Update ACL list from configuration. + * + * \param acl Pointer to existing or NULL ACL. + * \param acl_list List of remotes from configuration. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_ENOMEM on failed memory allocation. + */ +static int zones_set_acl(acl_t **acl, list* acl_list) +{ + if (!acl || !acl_list) { + return KNOTD_EINVAL; + } + + /* Truncate old ACL. */ + acl_delete(acl); + + /* Create new ACL. */ + *acl = acl_new(ACL_DENY, 0); + if (!*acl) { + return KNOTD_ENOMEM; + } + + /* Load ACL rules. */ + conf_remote_t *r = 0; + WALK_LIST(r, *acl_list) { + + /* Initialize address. */ + /*! Port matching disabled, port = 0. */ + sockaddr_t addr; + conf_iface_t *cfg_if = r->remote; + int ret = sockaddr_set(&addr, cfg_if->family, + cfg_if->address, 0); + + /* Load rule. */ + if (ret > 0) { + acl_create(*acl, &addr, ACL_ACCEPT); + } + } + + return KNOTD_EOK; +} + +/*! + * \brief Load zone to zone database. + * + * \param zonedb Zone database to load the zone into. + * \param zone_name Zone name (owner of the apex node). + * \param source Path to zone file source. + * \param filename Path to requested compiled zone file. + * + * \retval KNOTD_EOK + * \retval KNOTD_EINVAL + * \retval KNOTD_EZONEINVAL + */ +static int zones_load_zone(knot_zonedb_t *zonedb, const char *zone_name, + const char *source, const char *filename) +{ + knot_zone_t *zone = NULL; + + /* Check path */ + if (filename) { + dbg_zones("zones: parsing zone database '%s'\n", filename); + zloader_t *zl = 0; + int ret = knot_zload_open(&zl, filename); + switch(ret) { + case KNOT_EOK: + /* OK */ + break; + case KNOT_EFEWDATA: + log_server_error("Compiled zone db '%s' not exists.\n", + filename); + return KNOTD_EZONEINVAL; + case KNOT_ECRC: + log_server_error("Compiled zone db CRC mismatches, " + "db is corrupted or .crc file is " + "deleted.\n"); + return KNOTD_EZONEINVAL; + case KNOT_EMALF: + log_server_error("Compiled db '%s' is too old, " + " please recompile.\n", + filename); + return KNOTD_EZONEINVAL; + case KNOT_ERROR: + case KNOT_ENOMEM: + default: + log_server_error("Failed to read zone db file '%s'.\n", + filename); + return KNOTD_EZONEINVAL; + } + + /* Check if the db is up-to-date */ + int src_changed = strcmp(source, zl->source) != 0; + if (src_changed || knot_zload_needs_update(zl)) { + log_server_warning("Database for zone '%s' is not " + "up-to-date. Please recompile.\n", + zone_name); + } + + zone = knot_zload_load(zl); + + /* Check loaded name. */ + const knot_dname_t *dname = knot_zone_name(zone); + knot_dname_t *dname_req = 0; + dname_req = knot_dname_new_from_str(zone_name, + strlen(zone_name), 0); + if (knot_dname_compare(dname, dname_req) != 0) { + log_server_warning("Origin of the zone db file is " + "different than '%s'\n", + zone_name); + knot_zone_deep_free(&zone, 0); + zone = 0; + + } + knot_dname_free(&dname_req); + + /* CLEANUP */ + //knot_zone_contents_dump(zone->contents, 1); + + if (zone) { + /* save the timestamp from the zone db file */ + struct stat s; + stat(filename, &s); + knot_zone_set_version(zone, s.st_mtime); + + if (knot_zonedb_add_zone(zonedb, zone) != 0){ + knot_zone_deep_free(&zone, 0); + zone = 0; + } + } + + knot_zload_close(zl); + + if (!zone) { + log_server_error("Failed to load " + "db '%s' for zone '%s'.\n", + filename, zone_name); + return KNOTD_EZONEINVAL; + } + } else { + /* db is null. */ + return KNOTD_EINVAL; + } + + /* CLEANUP */ +// knot_zone_dump(zone, 1); + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +/*! \brief Return 'serial_from' part of the key. */ +static inline uint32_t ixfrdb_key_from(uint64_t k) +{ + /* 64 32 0 + * key = [TO | FROM] + * Need: Least significant 32 bits. + */ + return (uint32_t)(k & ((uint64_t)0x00000000ffffffff)); +} + +/*----------------------------------------------------------------------------*/ + +/*! \brief Return 'serial_to' part of the key. */ +static inline uint32_t ixfrdb_key_to(uint64_t k) +{ + /* 64 32 0 + * key = [TO | FROM] + * Need: Most significant 32 bits. + */ + return (uint32_t)(k >> (uint64_t)32); +} + +/*----------------------------------------------------------------------------*/ + +/*! \brief Compare function to match entries with target serial. */ +static inline int ixfrdb_key_to_cmp(uint64_t k, uint64_t to) +{ + /* 64 32 0 + * key = [TO | FROM] + * Need: Most significant 32 bits. + */ + return ((uint64_t)ixfrdb_key_to(k)) - to; +} + +/*----------------------------------------------------------------------------*/ + +/*! \brief Compare function to match entries with starting serial. */ +static inline int ixfrdb_key_from_cmp(uint64_t k, uint64_t from) +{ + /* 64 32 0 + * key = [TO | FROM] + * Need: Least significant 32 bits. + */ + return ((uint64_t)ixfrdb_key_from(k)) - from; +} + +/*----------------------------------------------------------------------------*/ + +/*! \brief Make key for journal from serials. */ +static inline uint64_t ixfrdb_key_make(uint32_t from, uint32_t to) +{ + /* 64 32 0 + * key = [TO | FROM] + */ + return (((uint64_t)to) << ((uint64_t)32)) | ((uint64_t)from); +} + +/*----------------------------------------------------------------------------*/ + +static int zones_changesets_from_binary(knot_changesets_t *chgsets) +{ + assert(chgsets != NULL); + assert(chgsets->allocated >= chgsets->count); + /* + * Parses changesets from the binary format stored in chgsets->data + * into the changeset_t structures. + */ + knot_rrset_t *rrset = 0; + int ret = 0; + + for (int i = 0; i < chgsets->count; ++i) { + + /* Read initial changeset RRSet - SOA. */ + knot_changeset_t* chs = chgsets->sets + i; + size_t remaining = chs->size; + ret = knot_zload_rrset_deserialize(&rrset, chs->data, &remaining); + if (ret != KNOT_EOK) { + dbg_xfr("xfr: SOA: failed to deserialize data " + "from changeset, %s\n", knot_strerror(ret)); + return KNOTD_EMALF; + } + + /* in this special case (changesets loaded + * from journal) the SOA serial should already + * be set, check it. + */ + assert(knot_rrset_type(rrset) == KNOT_RRTYPE_SOA); + assert(chs->serial_from == + knot_rdata_soa_serial(knot_rrset_rdata(rrset))); + knot_changeset_store_soa(&chs->soa_from, &chs->serial_from, + rrset); + + dbg_xfr_verb("xfr: reading RRSets to REMOVE\n"); + + /* Read remaining RRSets */ + int in_remove_section = 1; + while (remaining > 0) { + + /* Parse next RRSet. */ + rrset = 0; + uint8_t *stream = chs->data + (chs->size - remaining); + ret = knot_zload_rrset_deserialize(&rrset, stream, &remaining); + if (ret != KNOT_EOK) { + dbg_xfr("xfr: failed to deserialize data " + "from changeset, %s\n", + knot_strerror(ret)); + return KNOTD_EMALF; + } + + /* Check for next SOA. */ + if (knot_rrset_type(rrset) == KNOT_RRTYPE_SOA) { + + /* Move to ADD section if in REMOVE. */ + if (in_remove_section) { + knot_changeset_store_soa( + &chgsets->sets[i].soa_to, + &chgsets->sets[i].serial_to, + rrset); + dbg_xfr_verb("xfr: reading RRSets" + " to ADD\n"); + in_remove_section = 0; + } else { + /* Final SOA. */ + dbg_xfr_verb("xfr: extra SOA\n"); + knot_rrset_free(&rrset); + break; + } + } else { + /* Remove RRSets. */ + if (in_remove_section) { + ret = knot_changeset_add_rrset( + &chgsets->sets[i].remove, + &chgsets->sets[i].remove_count, + &chgsets->sets[i] + .remove_allocated, + rrset); + } else { + /* Add RRSets. */ + ret = knot_changeset_add_rrset( + &chgsets->sets[i].add, + &chgsets->sets[i].add_count, + &chgsets->sets[i].add_allocated, + rrset); + } + + /* Check result. */ + if (ret != KNOT_EOK) { + dbg_xfr("xfr: failed to add/remove " + "RRSet to changeset: %s\n", + knot_strerror(ret)); + return KNOTD_ERROR; + } + } + } + + dbg_xfr_verb("xfr: read all RRSets in changeset\n"); + } + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int zones_load_changesets(const knot_zone_t *zone, + knot_changesets_t *dst, + uint32_t from, uint32_t to) +{ + if (!zone || !dst) { + dbg_zones_detail("Bad arguments: zone=%p, dst=%p\n", zone, dst); + return KNOTD_EINVAL; + } + if (!zone->data) { + dbg_zones_detail("Bad arguments: zone->data=%p\n", zone->data); + return KNOTD_EINVAL; + } + + dbg_xfr("Loading changesets from serial %u to %u\n", from, to); + + /* Fetch zone-specific data. */ + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + if (!zd->ixfr_db) { + dbg_zones_detail("Bad arguments: zd->ixfr_db=%p\n", zone->data); + return KNOTD_EINVAL; + } + + /* Read entries from starting serial until finished. */ + uint32_t found_to = from; + journal_node_t *n = 0; + int ret = journal_fetch(zd->ixfr_db, from, ixfrdb_key_from_cmp, &n); + if (ret != KNOTD_EOK) { + dbg_xfr("xfr: failed to fetch starting changeset: %s\n", + knotd_strerror(ret)); + return ret; + } + + while (n != 0 && n != journal_end(zd->ixfr_db)) { + + /* Check for history end. */ + if (to == found_to) { + break; + } + + /*! \todo Increment and decrement to reserve +1, + * but not incremented counter.*/ + /* Check changesets size if needed. */ + ++dst->count; + ret = knot_changesets_check_size(dst); + --dst->count; + if (ret != KNOT_EOK) { + --dst->count; + dbg_xfr("xfr: failed to check changesets size: %s\n", + knot_strerror(ret)); + return KNOTD_ERROR; + } + + /* Initialize changeset. */ + dbg_xfr_detail("xfr: reading entry #%zu id=%llu\n", + dst->count, (unsigned long long)n->id); + knot_changeset_t *chs = dst->sets + dst->count; + chs->serial_from = ixfrdb_key_from(n->id); + chs->serial_to = ixfrdb_key_to(n->id); + chs->data = malloc(n->len); + if (!chs->data) { + return KNOTD_ENOMEM; + } + + /* Read journal entry. */ + ret = journal_read(zd->ixfr_db, n->id, + 0, (char*)chs->data); + if (ret != KNOTD_EOK) { + dbg_xfr("xfr: failed to read data from journal\n"); + free(chs->data); + return KNOTD_ERROR; + } + + /* Update changeset binary size. */ + chs->size = chs->allocated = n->len; + + /* Next node. */ + found_to = chs->serial_to; + ++dst->count; + ++n; + + /*! \todo Check consistency. */ + } + + dbg_xfr_detail("xfr: Journal entries read.\n"); + + /* Unpack binary data. */ + ret = zones_changesets_from_binary(dst); + if (ret != KNOT_EOK) { + dbg_xfr("xfr: failed to unpack changesets " + "from binary, %s\n", knot_strerror(ret)); + return KNOTD_ERROR; + } + + /* Check for complete history. */ + if (to != found_to) { + dbg_xfr_detail("Returning ERANGE\n"); + return KNOTD_ERANGE; + } + + /* History reconstructed. */ + dbg_xfr_detail("Returning EOK\n"); + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Apply changesets to zone from journal. + * + * \param zone Specified zone. + * + * \retval KNOTD_EOK if successful. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_ENOENT if zone has no contents. + * \retval KNOTD_ERROR on unspecified error. + */ +static int zones_journal_apply(knot_zone_t *zone) +{ + /* Fetch zone. */ + if (!zone) { + return KNOTD_EINVAL; + } + + knot_zone_contents_t *contents = knot_zone_get_contents(zone); + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + if (!contents || !zd) { + return KNOTD_ENOENT; + } + + /* Fetch SOA serial. */ + const knot_rrset_t *soa_rrs = 0; + const knot_rdata_t *soa_rr = 0; + soa_rrs = knot_node_rrset(knot_zone_contents_apex(contents), + KNOT_RRTYPE_SOA); + soa_rr = knot_rrset_rdata(soa_rrs); + int64_t serial_ret = knot_rdata_soa_serial(soa_rr); + if (serial_ret < 0) { + return KNOTD_EINVAL; + } + uint32_t serial = (uint32_t)serial_ret; + + /* Load all pending changesets. */ + dbg_zones_verb("zones: loading all changesets of '%s' from SERIAL %u\n", + zd->conf->name, serial); + knot_changesets_t* chsets = malloc(sizeof(knot_changesets_t)); + memset(chsets, 0, sizeof(knot_changesets_t)); + /*! \todo Check what should be the upper bound. */ + int ret = zones_load_changesets(zone, chsets, serial, serial - 1); + if (ret == KNOTD_EOK || ret == KNOTD_ERANGE) { + if (chsets->count > 0) { + /* Apply changesets. */ + log_server_info("Applying '%zu' changesets from journal " + "to zone '%s'.\n", + chsets->count, zd->conf->name); + ret = xfrin_apply_changesets_to_zone(zone, chsets); + if (ret != KNOT_EOK) { + log_server_error("Failed to apply changesets to " + "'%s' - %s\n", + zd->conf->name, + knot_strerror(ret)); + ret = KNOTD_ERROR; + } + } + } else { + dbg_zones("zones: failed to load changesets - %s\n", + knotd_strerror(ret)); + } + + /* Free changesets and return. */ + knot_free_changesets(&chsets); + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Fill the new database with zones. + * + * Zones that should be retained are just added from the old database to the + * new. New zones are loaded. + * + * \param ns Name server instance. + * \param zone_conf Zone configuration. + * \param db_old Old zone database. + * \param db_new New zone database. + * + * \return Number of inserted zones. + */ +static int zones_insert_zones(knot_nameserver_t *ns, + const list *zone_conf, + const knot_zonedb_t *db_old, + knot_zonedb_t *db_new) +{ + /*! \todo Change to zone contents. */ + + node *n = 0; + int inserted = 0; + /* for all zones in the configuration */ + WALK_LIST(n, *zone_conf) { + conf_zone_t *z = (conf_zone_t *)n; + + /* Convert the zone name into a domain name. */ + /* Local allocation, will be discarded. */ + knot_dname_t *zone_name = knot_dname_new_from_str(z->name, + strlen(z->name), NULL); + if (zone_name == NULL) { + log_server_error("Error creating domain name from zone" + " name\n"); + return inserted; + } + + dbg_zones_verb("zones: inserting zone %s into the new database.\n", + z->name); + + /* try to find the zone in the current zone db */ + knot_zone_t *zone = knot_zonedb_find_zone(db_old, + zone_name); + int reload = 0; + + /* Attempt to bootstrap if db or source does not exist. */ + struct stat s; + int stat_ret = stat(z->file, &s); + if (zone != NULL) { + /* if found, check timestamp of the file against the + * loaded zone + */ + if (knot_zone_version(zone) < s.st_mtime) { + /* the file is newer, reload! */ + reload = 1; + } + } else { + reload = 1; + } + + /* Reload zone file. */ + int ret = KNOTD_ERROR; + if (reload) { + /* Zone file not exists and has master set. */ + if (stat_ret < 0 && !EMPTY_LIST(z->acl.xfr_in)) { + + /* Create stub database. */ + dbg_zones_verb("zones: loading stub zone '%s' " + "for bootstrap.\n", + z->name); + knot_dname_t *owner = 0; + owner = knot_dname_deep_copy(zone_name); + knot_zone_t* sz = knot_zone_new_empty(owner); + if (sz) { + /* Add stub zone to db_new. */ + ret = knot_zonedb_add_zone(db_new, sz); + if (ret != KNOT_EOK) { + dbg_zones("zones: failed to add " + "stub zone '%s'.\n", + z->name); + knot_zone_deep_free(&sz, 0); + sz = 0; + ret = KNOTD_ERROR; + } else { + log_server_info("Will attempt to " + "bootstrap zone " + "%s from AXFR " + "master.\n", + z->name); + --inserted; + } + + } else { + dbg_zones("zones: failed to create " + "stub zone '%s'.\n", + z->name); + ret = KNOTD_ERROR; + } + + } else { + dbg_zones_verb("zones: loading zone '%s' " + "from '%s'\n", + z->name, + z->db); + ret = zones_load_zone(db_new, z->name, + z->file, z->db); + if (ret == KNOTD_EOK) { + log_server_info("Loaded zone '%s'\n", + z->name); + } else { + log_server_error("Failed to load zone " + "'%s' - %s\n", + z->name, + knotd_strerror(ret)); + } + } + + /* Find zone. */ + if (ret == KNOTD_EOK) { + /* Find the new zone */ + zone = knot_zonedb_find_zone(db_new, + zone_name); + ++inserted; + + dbg_zones_verb("zones: inserted '%s' into " + "database, initializing data\n", + z->name); + + /* Initialize zone-related data. */ + zonedata_init(z, zone); + + } + /* unused return value, if not loaded, just continue */ + } else { + /* just insert the zone into the new zone db */ + dbg_zones_verb("zones: found '%s' in old database, " + "copying to new.\n", + z->name); + log_server_info("Zone '%s' is up-to-date, no need " + "for reload.\n", z->name); + int ret = knot_zonedb_add_zone(db_new, zone); + if (ret != KNOT_EOK) { + log_server_error("Error adding known zone '%s' to" + " the new database - %s\n", + z->name, knot_strerror(ret)); + ret = KNOTD_ERROR; + } else { + ++inserted; + } + } + + /* Update zone data. */ + if (zone) { + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + + /* Update refs. */ + zd->conf = z; + + /* Update ACLs. */ + dbg_zones("Updating zone ACLs.\n"); + zones_set_acl(&zd->xfr_in.acl, &z->acl.xfr_in); + zones_set_acl(&zd->xfr_out, &z->acl.xfr_out); + zones_set_acl(&zd->notify_in, &z->acl.notify_in); + zones_set_acl(&zd->notify_out, &z->acl.notify_out); + + /* Update server pointer. */ + zd->server = (server_t *)knot_ns_get_data(ns); + + /* Update master server address. */ + memset(&zd->xfr_in.tsig_key, 0, sizeof(knot_key_t)); + sockaddr_init(&zd->xfr_in.master, -1); + if (!EMPTY_LIST(z->acl.xfr_in)) { + conf_remote_t *r = HEAD(z->acl.xfr_in); + conf_iface_t *cfg_if = r->remote; + sockaddr_set(&zd->xfr_in.master, + cfg_if->family, + cfg_if->address, + cfg_if->port); + /*!< \todo [TSIG] DISABLED */ +// if (cfg_if->key) { +// memcpy(&zd->xfr_in.tsig_key, +// cfg_if->key, +// sizeof(knot_key_t)); +// } + + dbg_zones("zones: using %s:%d as XFR master " + "for '%s'\n", + cfg_if->address, + cfg_if->port, + z->name); + } + + /* Apply changesets from journal. */ + zones_journal_apply(zone); + + /* Update events scheduled for zone. */ + zones_timers_update(zone, z, + ((server_t *)knot_ns_get_data(ns))->sched); + } + + /* CLEANUP */ +// knot_zone_contents_dump(knot_zone_get_contents(zone), 1); + + /* Directly discard zone. */ + knot_dname_free(&zone_name); + } + return inserted; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Remove zones present in the configuration from the old database. + * + * After calling this function, the old zone database should contain only zones + * that should be completely deleted. + * + * \param zone_conf Zone configuration. + * \param db_old Old zone database to remove zones from. + * + * \retval KNOTD_EOK + * \retval KNOTD_ERROR + */ +static int zones_remove_zones(const knot_zonedb_t *db_new, + knot_zonedb_t *db_old) +{ + const knot_zone_t **new_zones = knot_zonedb_zones(db_new); + if (new_zones == NULL) { + return KNOTD_ENOMEM; + } + + for (int i = 0; i < knot_zonedb_zone_count(db_new); ++i) { + /* try to find the new zone in the old DB + * if the pointers match, remove the zone from old DB + */ + /*! \todo Find better way of removing zone with given pointer.*/ + knot_zone_t *old_zone = knot_zonedb_find_zone( + db_old, knot_zone_name(new_zones[i])); + if (old_zone == new_zones[i]) { +dbg_zones_exec( + char *name = knot_dname_to_str(knot_zone_name(old_zone)); + dbg_zones_verb("zones: zone pointers match, removing zone %s " + "from database.\n", name); + free(name); +); + knot_zone_t * rm = knot_zonedb_remove_zone(db_old, + knot_zone_name(old_zone)); + assert(rm == old_zone); + } + } + + free(new_zones); + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +int zones_update_db_from_config(const conf_t *conf, knot_nameserver_t *ns, + knot_zonedb_t **db_old) +{ + /* Check parameters */ + if (conf == NULL || ns == NULL) { + return KNOTD_EINVAL; + } + + /* Lock RCU to ensure none will deallocate any data under our hands. */ + rcu_read_lock(); + + /* Grab a pointer to the old database */ + *db_old = ns->zone_db; + if (*db_old == NULL) { + log_server_error("Missing zone database in nameserver structure" + ".\n"); + return KNOTD_ERROR; + } + + /* Create new zone DB */ + knot_zonedb_t *db_new = knot_zonedb_new(); + if (db_new == NULL) { + return KNOTD_ERROR; + } + + log_server_info("Loading %d compiled zones...\n", conf->zones_count); + + /* Insert all required zones to the new zone DB. */ + int inserted = zones_insert_zones(ns, &conf->zones, *db_old, db_new); + + log_server_info("Loaded %d out of %d zones.\n", inserted, + conf->zones_count); + + if (inserted != conf->zones_count) { + log_server_warning("Not all the zones were loaded.\n"); + } + + dbg_zones_detail("zones: old db in nameserver: %p, old db stored: %p, new db: %p\n", + ns->zone_db, *db_old, db_new); + + /* Switch the databases. */ + (void)rcu_xchg_pointer(&ns->zone_db, db_new); + + dbg_zones_detail("db in nameserver: %p, old db stored: %p, new db: %p\n", + ns->zone_db, *db_old, db_new); + + /* + * Remove all zones present in the new DB from the old DB. + * No new thread can access these zones in the old DB, as the + * databases are already switched. + * + * Beware - only the exact same zones (same pointer) may be removed. + * All other have been loaded again so that the old must be destroyed. + */ + int ret = zones_remove_zones(db_new, *db_old); + if (ret != KNOTD_EOK) { + return ret; + } + + /* Unlock RCU, messing with any data will not affect us now */ + rcu_read_unlock(); + + return KNOTD_EOK; +} + +int zones_zonefile_sync(knot_zone_t *zone) +{ + if (!zone) { + return KNOTD_EINVAL; + } + if (!zone->data) { + return KNOTD_EINVAL; + } + + /* Fetch zone data. */ + zonedata_t *zd = (zonedata_t *)zone->data; + + /* Lock zone data. */ + pthread_mutex_lock(&zd->lock); + + knot_zone_contents_t *contents = knot_zone_get_contents(zone); + if (!contents) { + pthread_mutex_unlock(&zd->lock); + return KNOTD_EINVAL; + } + + /* Latest zone serial. */ + const knot_rrset_t *soa_rrs = 0; + const knot_rdata_t *soa_rr = 0; + soa_rrs = knot_node_rrset(knot_zone_contents_apex(contents), + KNOT_RRTYPE_SOA); + soa_rr = knot_rrset_rdata(soa_rrs); + int64_t serial_ret = knot_rdata_soa_serial(soa_rr); + if (serial_ret < 0) { + return KNOTD_EINVAL; + } + uint32_t serial_to = (uint32_t)serial_ret; + + /* Check for difference against zonefile serial. */ + if (zd->zonefile_serial != serial_to) { + + /* Save zone to zonefile. */ + conf_read_lock(); + dbg_zones("zones: syncing '%s' differences to '%s' " + "(SOA serial %u)\n", + zd->conf->name, zd->conf->file, serial_to); + zone_dump_text(contents, zd->conf->file); + conf_read_unlock(); + + /* Update journal entries. */ + dbg_zones_verb("zones: unmarking all dirty nodes " + "in '%s' journal\n", + zd->conf->name); + journal_walk(zd->ixfr_db, zones_ixfrdb_sync_apply); + + /* Update zone file serial. */ + dbg_zones("zones: new '%s' zonefile serial is %u\n", + zd->conf->name, serial_to); + zd->zonefile_serial = serial_to; + } else { + dbg_zones_verb("zones: '%s' zonefile is in sync " + "with differences\n", zd->conf->name); + } + + /* Unlock zone data. */ + pthread_mutex_unlock(&zd->lock); + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int zones_xfr_check_zone(knot_ns_xfr_t *xfr, knot_rcode_t *rcode) +{ + if (xfr == NULL || rcode == NULL) { + *rcode = KNOT_RCODE_SERVFAIL; + return KNOTD_EINVAL; + } + + /* Check if the zone is found. */ + if (xfr->zone == NULL) { + *rcode = KNOT_RCODE_REFUSED; + return KNOTD_EACCES; + } + + /* Check zone data. */ + zonedata_t *zd = (zonedata_t *)xfr->zone->data; + if (zd == NULL) { + dbg_zones("zones: invalid zone data for zone %p\n", xfr->zone); + *rcode = KNOT_RCODE_SERVFAIL; + return KNOTD_ERROR; + } + + /* Check zone contents. */ + if (knot_zone_contents(xfr->zone) == NULL) { + dbg_zones("zones: invalid zone contents for zone %p\n", xfr->zone); + *rcode = KNOT_RCODE_SERVFAIL; + return KNOTD_EEXPIRED; + } + + // Check xfr-out ACL + if (acl_match(zd->xfr_out, &xfr->addr) == ACL_DENY) { + log_answer_warning("Unauthorized request for XFR '%s/OUT'.\n", + zd->conf->name); + *rcode = KNOT_RCODE_REFUSED; + return KNOTD_EACCES; + } else { + dbg_zones("zones: authorized XFR '%s/OUT'\n", + zd->conf->name); + } + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int zones_process_response(knot_nameserver_t *nameserver, + sockaddr_t *from, + knot_packet_t *packet, uint8_t *response_wire, + size_t *rsize) +{ + if (!packet || !rsize || nameserver == NULL || from == NULL || + response_wire == NULL) { + return KNOTD_EINVAL; + } + + /* Handle SOA query response, cancel EXPIRE timer + * and start AXFR transfer if needed. + * Reset REFRESH timer on finish. + */ + if (knot_packet_qtype(packet) == KNOT_RRTYPE_SOA) { + + if (knot_packet_rcode(packet) != KNOT_RCODE_NOERROR) { + /*! \todo Handle error response. */ + return KNOTD_ERROR; + } + + /* No response. */ + *rsize = 0; + + /* Find matching zone and ID. */ + const knot_dname_t *zone_name = knot_packet_qname(packet); + /*! \todo Change the access to the zone db. */ + knot_zone_t *zone = knot_zonedb_find_zone( + nameserver->zone_db, + zone_name); + + /* Get zone contents. */ + rcu_read_lock(); + const knot_zone_contents_t *contents = + knot_zone_contents(zone); + + if (!zone || !knot_zone_data(zone) || !contents) { + rcu_read_unlock(); + return KNOTD_EINVAL; + } + + /* Match ID against awaited. */ + zonedata_t *zd = (zonedata_t *)knot_zone_data(zone); + uint16_t pkt_id = knot_packet_id(packet); + if ((int)pkt_id != zd->xfr_in.next_id) { + rcu_read_unlock(); + return KNOTD_ERROR; + } + + /* Check SOA SERIAL. */ + int ret = xfrin_transfer_needed(contents, packet); + dbg_zones_verb("xfrin_transfer_needed() returned %d\n", ret); + if (ret < 0) { + /* RETRY/EXPIRE timers running, do not interfere. */ + return KNOTD_ERROR; + } + + /* No updates available. */ + evsched_t *sched = + ((server_t *)knot_ns_get_data(nameserver))->sched; + if (ret == 0) { + log_zone_info("SOA query for zone '%s' answered, no " + "transfer needed.\n", zd->conf->name); + rcu_read_unlock(); + + /* Reinstall timers. */ + zones_timers_update(zone, zd->conf, sched); + return KNOTD_EOK; + } + + assert(ret > 0); + + /* Already transferring. */ + if (pthread_mutex_trylock(&zd->xfr_in.lock) != 0) { + /* Unlock zone contents. */ + dbg_zones("zones: SOA response received, but zone is " + "being transferred, refusing to start another " + "transfer\n"); + rcu_read_unlock(); + return KNOTD_EOK; + } else { + pthread_mutex_unlock(&zd->xfr_in.lock); + } + + /* Prepare XFR client transfer. */ + knot_ns_xfr_t xfr_req; + memset(&xfr_req, 0, sizeof(knot_ns_xfr_t)); + memcpy(&xfr_req.addr, from, sizeof(sockaddr_t)); + xfr_req.zone = (void *)zone; + xfr_req.send = zones_send_cb; + + /* Select transfer method. */ + xfr_req.type = zones_transfer_to_use(contents); + + /* Select TSIG key. */ + /*!< \todo [TSIG] DISABLED */ + xfr_req.tsig_key = 0; + /* CLEANUP */ +// if (zd->xfr_in.tsig_key.name) { +// xfr_req.tsig_key = &zd->xfr_in.tsig_key; +// } + + /* Unlock zone contents. */ + rcu_read_unlock(); + + /* Enqueue XFR request. */ + return xfr_request(((server_t *)knot_ns_get_data( + nameserver))->xfr_h, &xfr_req); + } + + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +knot_ns_xfr_type_t zones_transfer_to_use(const knot_zone_contents_t *zone) +{ + /*! \todo Implement. */ + return XFR_TYPE_IIN; +} + +/*----------------------------------------------------------------------------*/ + +static int zones_find_zone_for_xfr(const knot_zone_contents_t *zone, + const char **zonefile, const char **zonedb) +{ + /* find the zone file name and zone db file name for the zone */ + conf_t *cnf = conf(); + node *n = NULL; + WALK_LIST(n, cnf->zones) { + conf_zone_t *zone_conf = (conf_zone_t *)n; + knot_dname_t *zone_name = knot_dname_new_from_str( + zone_conf->name, strlen(zone_conf->name), NULL); + if (zone_name == NULL) { + return KNOTD_ENOMEM; + } + + int r = knot_dname_compare(zone_name, knot_node_owner( + knot_zone_contents_apex(zone))); + + /* Directly discard dname, won't be needed. */ + knot_dname_free(&zone_name); + + if (r == 0) { + /* found the right zone */ + *zonefile = zone_conf->file; + *zonedb = zone_conf->db; + return KNOTD_EOK; + } + } + + char *name = knot_dname_to_str(knot_node_owner( + knot_zone_contents_apex(zone))); + dbg_zones("zones: no zone found for the zone received by transfer " + "(%s).\n", name); + free(name); + + return KNOTD_ENOENT; +} + +/*----------------------------------------------------------------------------*/ + +static char *zones_find_free_filename(const char *old_name) +{ + /* find zone name not present on the disk */ + int free_name = 0; + size_t name_size = strlen(old_name); + + char *new_name = malloc(name_size + 3); + if (new_name == NULL) { + return NULL; + } + memcpy(new_name, old_name, name_size); + new_name[name_size] = '.'; + new_name[name_size + 2] = 0; + + dbg_zones_verb("zones: finding free name for the zone file.\n"); + int c = 48; + FILE *file; + while (!free_name && c < 58) { + new_name[name_size + 1] = c; + dbg_zones_verb("zones: trying file name %s\n", new_name); + if ((file = fopen(new_name, "r")) != NULL) { + fclose(file); + ++c; + } else { + free_name = 1; + } + } + + if (free_name) { + return new_name; + } else { + free(new_name); + return NULL; + } +} + +/*----------------------------------------------------------------------------*/ + +static int zones_dump_xfr_zone_text(knot_zone_contents_t *zone, + const char *zonefile) +{ + assert(zone != NULL && zonefile != NULL); + + /*! \todo new_zonefile may be created by another process, + * until the zone_dump_text is called. Needs to be opened in + * this function for writing. + * Use open() for exclusive open and fcntl() for locking. + */ + + char *new_zonefile = zones_find_free_filename(zonefile); + + if (new_zonefile == NULL) { + dbg_zones("zones: failed to find free filename for temporary " + "storage of the zone text file.\n"); + return KNOTD_ERROR; /*! \todo New error code? */ + } + + int rc = zone_dump_text(zone, new_zonefile); + + if (rc != KNOTD_EOK) { + dbg_zones("Failed to save the zone to text zone file '%s'.\n", + new_zonefile); + free(new_zonefile); + return KNOTD_ERROR; + } + + /*! \todo this would also need locking as well. */ + struct stat s; + rc = stat(zonefile, &s); + if (rc < 0 || remove(zonefile) == 0) { + if (rename(new_zonefile, zonefile) != 0) { + dbg_zones("Failed to replace old zonefile '%s'' with new" + " zone file '%s'.\n", zonefile, new_zonefile); + /*! \todo with proper locking, this shouldn't happen, + * revise it later on. + */ + zone_dump_text(zone, zonefile); + return KNOTD_ERROR; + } + } else { + dbg_zones("zones: failed to replace old zonefile '%s'.\n", + zonefile); + return KNOTD_ERROR; + } + + free(new_zonefile); + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int ns_dump_xfr_zone_binary(knot_zone_contents_t *zone, + const char *zonedb, + const char *zonefile) +{ + assert(zone != NULL && zonedb != NULL); + + /*! \todo new_zonedb may be created by another process, + * until the zone_dump_text is called. Needs to be opened in + * this function for writing. + * Use open() for exclusive open and fcntl() for locking. + */ + char *new_zonedb = zones_find_free_filename(zonedb); + + if (new_zonedb == NULL) { + dbg_zones("zones: failed to find free filename for temporary " + "storage of the zone binary file '%s'\n", + zonedb); + return KNOTD_ERROR; /*! \todo New error code? */ + } + + /*! \todo this would also need locking as well. */ + int rc = knot_zdump_dump_and_swap(zone, new_zonedb, zonedb, zonefile); + free(new_zonedb); + + if (rc != KNOT_EOK) { + return KNOTD_ERROR; + } + + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int zones_save_zone(const knot_ns_xfr_t *xfr) +{ + if (xfr == NULL || xfr->data == NULL) { + return KNOTD_EINVAL; + } + + knot_zone_contents_t *zone = + (knot_zone_contents_t *)xfr->data; + + const char *zonefile = NULL; + const char *zonedb = NULL; + + int ret = zones_find_zone_for_xfr(zone, &zonefile, &zonedb); + if (ret != KNOTD_EOK) { + return ret; + } + + assert(zonefile != NULL && zonedb != NULL); + + /* dump the zone into text zone file */ + ret = zones_dump_xfr_zone_text(zone, zonefile); + if (ret != KNOTD_EOK) { + return KNOTD_ERROR; + } + /* dump the zone into binary db file */ + ret = ns_dump_xfr_zone_binary(zone, zonedb, zonefile); + if (ret != KNOTD_EOK) { + return KNOTD_ERROR; + } + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int zones_ns_conf_hook(const struct conf_t *conf, void *data) +{ + knot_nameserver_t *ns = (knot_nameserver_t *)data; + dbg_zones_verb("zones: reconfiguring name server.\n"); + + knot_zonedb_t *old_db = 0; + + int ret = zones_update_db_from_config(conf, ns, &old_db); + if (ret != KNOTD_EOK) { + return ret; + } + /* Wait until all readers finish with reading the zones. */ + synchronize_rcu(); + + dbg_zones_verb("zones: nameserver's zone db: %p, old db: %p\n", + ns->zone_db, old_db); + + /* Delete all deprecated zones and delete the old database. */ + knot_zonedb_deep_free(&old_db); + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int zones_check_binary_size(uint8_t **data, size_t *allocated, + size_t required) +{ + if (required <= *allocated) { + return KNOTD_EOK; + } + + /* Allocate new memory block. */ + size_t new_count = required; + uint8_t *new_data = malloc(new_count * sizeof(uint8_t)); + if (new_data == NULL) { + return KNOTD_ENOMEM; + } + + /* Clear memory block and copy old data. */ + memset(new_data, 0, new_count * sizeof(uint8_t)); + memcpy(new_data, *data, *allocated); + + /* Switch pointers and free old pointer. */ + free(*data); + *data = new_data; + *allocated = new_count; + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int zones_changeset_rrset_to_binary(uint8_t **data, size_t *size, + size_t *allocated, + knot_rrset_t *rrset) +{ + assert(data != NULL); + assert(size != NULL); + assert(allocated != NULL); + + /* + * In *data, there is the whole changeset in the binary format, + * the actual RRSet will be just appended to it + */ + + uint8_t *binary = NULL; + size_t actual_size = 0; + int ret = knot_zdump_rrset_serialize(rrset, &binary, &actual_size); + if (ret != KNOT_EOK) { + return KNOTD_ERROR; /*! \todo Other code? */ + } + + ret = zones_check_binary_size(data, allocated, *size + actual_size); + if (ret != KNOT_EOK) { + free(binary); + return KNOTD_ERROR; + } + + memcpy(*data + *size, binary, actual_size); + *size += actual_size; + free(binary); + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int zones_changesets_to_binary(knot_changesets_t *chgsets) +{ + assert(chgsets != NULL); + assert(chgsets->allocated >= chgsets->count); + + /* + * Converts changesets to the binary format stored in chgsets->data + * from the changeset_t structures. + */ + int ret; + + for (int i = 0; i < chgsets->count; ++i) { + knot_changeset_t *ch = &chgsets->sets[i]; + assert(ch->data == NULL); + assert(ch->size == 0); + + /* 1) origin SOA */ + ret = zones_changeset_rrset_to_binary(&ch->data, &ch->size, + &ch->allocated, ch->soa_from); + if (ret != KNOT_EOK) { + free(ch->data); + ch->data = NULL; + dbg_zones("zones_changeset_rrset_to_binary(): %s\n", + knot_strerror(ret)); + return KNOTD_ERROR; + } + + int j; + + /* 2) remove RRsets */ + assert(ch->remove_allocated >= ch->remove_count); + for (j = 0; j < ch->remove_count; ++j) { + ret = zones_changeset_rrset_to_binary(&ch->data, + &ch->size, + &ch->allocated, + ch->remove[j]); + if (ret != KNOT_EOK) { + free(ch->data); + ch->data = NULL; + dbg_zones("zones_changeset_rrset_to_binary(): %s\n", + knot_strerror(ret)); + return KNOTD_ERROR; + } + } + + /* 3) new SOA */ + ret = zones_changeset_rrset_to_binary(&ch->data, &ch->size, + &ch->allocated, ch->soa_to); + if (ret != KNOT_EOK) { + free(ch->data); + ch->data = NULL; + dbg_zones("zones_changeset_rrset_to_binary(): %s\n", + knot_strerror(ret)); + return KNOTD_ERROR; + } + + /* 4) add RRsets */ + assert(ch->add_allocated >= ch->add_count); + for (j = 0; j < ch->add_count; ++j) { + ret = zones_changeset_rrset_to_binary(&ch->data, + &ch->size, + &ch->allocated, + ch->add[j]); + if (ret != KNOT_EOK) { + free(ch->data); + ch->data = NULL; + dbg_zones("zones_changeset_rrset_to_binary(): %s\n", + knot_strerror(ret)); + return KNOTD_ERROR; + } + } + } + + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int zones_store_changesets(knot_ns_xfr_t *xfr) +{ + if (xfr == NULL || xfr->data == NULL || xfr->zone == NULL) { + return KNOTD_EINVAL; + } + + knot_zone_t *zone = xfr->zone; + knot_changesets_t *src = (knot_changesets_t *)xfr->data; + + /*! \todo Convert to binary format. */ + + int ret = zones_changesets_to_binary(src); + if (ret != KNOTD_EOK) { + return ret; + } + + /* Fetch zone-specific data. */ + zonedata_t *zd = (zonedata_t *)zone->data; + if (!zd->ixfr_db) { + return KNOTD_EINVAL; + } + + /* Begin writing to journal. */ + for (unsigned i = 0; i < src->count; ++i) { + + /* Make key from serials. */ + knot_changeset_t* chs = src->sets + i; + uint64_t k = ixfrdb_key_make(chs->serial_from, chs->serial_to); + + /* Write entry. */ + int ret = journal_write(zd->ixfr_db, k, (const char*)chs->data, + chs->size); + + /* Check for errors. */ + while (ret != KNOTD_EOK) { + + /* Sync to zonefile may be needed. */ + if (ret == KNOTD_EAGAIN) { + + /* Cancel sync timer. */ + event_t *tmr = zd->ixfr_dbsync; + if (tmr) { + dbg_xfr_verb("xfr: cancelling zonefile " + "SYNC timer of '%s'\n", + zd->conf->name); + evsched_cancel(tmr->parent, tmr); + } + + /* Synchronize. */ + dbg_xfr_verb("xfr: forcing zonefile SYNC " + "of '%s'\n", + zd->conf->name); + ret = zones_zonefile_sync(zone); + if (ret != KNOTD_EOK) { + continue; + } + + /* Reschedule sync timer. */ + if (tmr) { + /* Fetch sync timeout. */ + conf_read_lock(); + int timeout = zd->conf->dbsync_timeout; + timeout *= 1000; /* Convert to ms. */ + conf_read_unlock(); + + /* Reschedule. */ + dbg_xfr_verb("xfr: resuming SYNC " + "of '%s'\n", + zd->conf->name); + evsched_schedule(tmr->parent, tmr, + timeout); + + } + + /* Attempt to write again. */ + ret = journal_write(zd->ixfr_db, k, + (const char*)chs->data, + chs->size); + } else { + /* Other errors. */ + return KNOTD_ERROR; + } + } + + /* Free converted binary data. */ + free(chs->data); + chs->data = 0; + chs->size = 0; + } + + /* Written changesets to journal. */ + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int zones_xfr_load_changesets(knot_ns_xfr_t *xfr, uint32_t serial_from, + uint32_t serial_to) +{ + if (!xfr || !xfr->zone || !knot_zone_contents(xfr->zone)) { + dbg_zones_detail("Wrong parameters: xfr=%p," + " xfr->zone = %p\n", xfr, xfr->zone); + return KNOTD_EINVAL; + } + + knot_changesets_t *chgsets = (knot_changesets_t *) + calloc(1, sizeof(knot_changesets_t)); + CHECK_ALLOC_LOG(chgsets, KNOTD_ENOMEM); + + int ret = ns_serial_compare(serial_to, serial_from); + dbg_zones_verb("Compared serials, result: %d\n", ret); + + /* if serial_to is not larger than serial_from, do not load anything */ + if (ret <= 0) { + xfr->data = chgsets; + return KNOTD_EOK; + } + + dbg_zones("Loading changesets...\n"); + + ret = zones_load_changesets(xfr->zone, chgsets, + serial_from, serial_to); + if (ret != KNOTD_EOK) { + dbg_zones_verb("Loading changesets failed: %s\n", + knotd_strerror(ret)); + return ret; + } + + xfr->data = chgsets; + return KNOTD_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int zones_apply_changesets(knot_ns_xfr_t *xfr) +{ + if (xfr == NULL || xfr->zone == NULL || xfr->data == NULL) { + return KNOTD_EINVAL; + } + + return xfrin_apply_changesets_to_zone(xfr->zone, + (knot_changesets_t *)xfr->data); +} + +/*----------------------------------------------------------------------------*/ + +int zones_timers_update(knot_zone_t *zone, conf_zone_t *cfzone, evsched_t *sch) +{ + if (!sch || !zone) { + return KNOTD_EINVAL; + } + + /* Fetch zone data. */ + zonedata_t *zd = (zonedata_t *)zone->data; + if (!zd) { + return KNOTD_EINVAL; + } + + /* Cancel REFRESH timer. */ + if (zd->xfr_in.timer) { + evsched_cancel(sch, zd->xfr_in.timer); + evsched_event_free(sch, zd->xfr_in.timer); + zd->xfr_in.timer = 0; + } + + /* Cancel EXPIRE timer. */ + if (zd->xfr_in.expire) { + evsched_cancel(sch, zd->xfr_in.expire); + evsched_event_free(sch, zd->xfr_in.expire); + zd->xfr_in.expire = 0; + } + + /* Remove list of pending NOTIFYs. */ + pthread_mutex_lock(&zd->lock); + notify_ev_t *ev = 0, *evn = 0; + WALK_LIST_DELSAFE(ev, evn, zd->notify_pending) { + zones_cancel_notify(zd, ev); + } + pthread_mutex_unlock(&zd->lock); + + /* Check XFR/IN master server. */ + if (zd->xfr_in.master.ptr) { + + /* Schedule REFRESH timer. */ + uint32_t refresh_tmr = 0; + if (knot_zone_contents(zone)) { + refresh_tmr = zones_soa_refresh(zone); + } else { + refresh_tmr = zd->xfr_in.bootstrap_retry; + } + zd->xfr_in.timer = evsched_schedule_cb(sch, zones_refresh_ev, + zone, refresh_tmr); + dbg_zones("zone: REFRESH set to %u\n", refresh_tmr); + } + + /* Schedule IXFR database syncing. */ + /*! \todo Sync timer should not be reset after each xfr. */ + int sync_timeout = cfzone->dbsync_timeout * 1000; /* Convert to ms. */ + if (zd->ixfr_dbsync) { + evsched_cancel(sch, zd->ixfr_dbsync); + evsched_event_free(sch, zd->ixfr_dbsync); + zd->ixfr_dbsync = 0; + } + if (zd->ixfr_db) { + zd->ixfr_dbsync = evsched_schedule_cb(sch, + zones_zonefile_sync_ev, + zone, sync_timeout); + } + + /* Do not issue NOTIFY queries if stub. */ + if (!knot_zone_contents(zone)) { + conf_read_unlock(); + return KNOTD_EOK; + } + + /* Schedule NOTIFY to slaves. */ + conf_remote_t *r = 0; + conf_read_lock(); + WALK_LIST(r, cfzone->acl.notify_out) { + + /* Fetch remote. */ + conf_iface_t *cfg_if = r->remote; + + /* Create request. */ + notify_ev_t *ev = malloc(sizeof(notify_ev_t)); + if (!ev) { + free(ev); + dbg_zones("notify: out of memory to create " + "NOTIFY query for %s\n", cfg_if->name); + continue; + } + + /* Parse server address. */ + int ret = sockaddr_set(&ev->addr, cfg_if->family, + cfg_if->address, + cfg_if->port); + if (ret < 1) { + free(ev); + dbg_zones("notify: NOTIFY slave %s has invalid " + "address\n", cfg_if->name); + continue; + } + + /* Prepare request. */ + ev->retries = cfzone->notify_retries + 1; /* first + N retries*/ + ev->msgid = 0; + ev->zone = zone; + ev->timeout = cfzone->notify_timeout; + + /* Schedule request (30 - 60s random delay). */ + int tmr_s = 30 + (int)(30.0 * (rand() / (RAND_MAX + 1.0))); + pthread_mutex_lock(&zd->lock); + ev->timer = evsched_schedule_cb(sch, zones_notify_send, ev, + tmr_s * 1000); + add_tail(&zd->notify_pending, &ev->n); + pthread_mutex_unlock(&zd->lock); + + log_server_info("Scheduled NOTIFY query after %d s to %s:%d\n", + tmr_s, cfg_if->address, cfg_if->port); + } + + conf_read_unlock(); + + return KNOTD_EOK; +} + +int zones_cancel_notify(zonedata_t *zd, notify_ev_t *ev) +{ + if (!zd || !ev || !ev->timer) { + return KNOTD_EINVAL; + } + + /* Wait for event to finish running. */ +#ifdef KNOTD_NOTIFY_DEBUG + int pkt_id = ev->msgid; /*< Do not optimize! */ +#endif + event_t *tmr = ev->timer; + ev->timer = 0; + pthread_mutex_unlock(&zd->lock); + evsched_cancel(tmr->parent, tmr); + + /* Re-lock and find again (if not deleted). */ + pthread_mutex_lock(&zd->lock); + int match_exists = 0; + notify_ev_t *tmpev = 0; + WALK_LIST(tmpev, zd->notify_pending) { + if (tmpev == ev) { + match_exists = 1; + break; + } + } + + /* Event deleted before cancelled. */ + if (!match_exists) { + dbg_notify("notify: NOTIFY event for query ID=%u was " + "deleted before cancellation.\n", + pkt_id); + return KNOTD_EOK; + + } + + /* Free event (won't be scheduled again). */ + dbg_notify("notify: NOTIFY query ID=%u event cancelled.\n", + pkt_id); + rem_node(&ev->n); + evsched_event_free(tmr->parent, tmr); + free(ev); + return KNOTD_EOK; +} diff --git a/src/knot/server/zones.h b/src/knot/server/zones.h new file mode 100644 index 0000000..525a78a --- /dev/null +++ b/src/knot/server/zones.h @@ -0,0 +1,264 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file zones.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * Contains functions for updating zone database from configuration. + * + * \addtogroup server + * @{ + */ + +#ifndef _KNOTD_ZONES_H_ +#define _KNOTD_ZONES_H_ + +#include <stddef.h> + +#include "common/lists.h" +#include "common/acl.h" +#include "common/evsched.h" +#include "libknot/nameserver/name-server.h" +#include "libknot/zone/zonedb.h" +#include "knot/conf/conf.h" +#include "knot/server/notify.h" +#include "knot/server/server.h" +#include "knot/server/journal.h" +#include "libknot/zone/zone.h" +#include "libknot/updates/xfr-in.h" + +/* Constants. */ +#define SOA_QRY_TIMEOUT 10000 /*!< SOA query timeout (ms). */ +#define IXFR_DBSYNC_TIMEOUT (60*1000) /*!< Database sync timeout = 60s. */ +#define AXFR_BOOTSTRAP_RETRY (60*1000) /*!< Interval between AXFR BS retries. */ + +/*! + * \brief Zone-related data. + */ +typedef struct zonedata_t +{ + /*! \brief Shortcut to zone config entry. */ + conf_zone_t *conf; + + /*! \brief Shortcut to server instance. */ + server_t *server; + + /*! \brief Zone data lock for exclusive access. */ + pthread_mutex_t lock; + + /*! \brief Access control lists. */ + acl_t *xfr_out; /*!< ACL for xfr-out.*/ + acl_t *notify_in; /*!< ACL for notify-in.*/ + acl_t *notify_out; /*!< ACL for notify-out.*/ + + /*! \brief XFR-IN scheduler. */ + struct { + acl_t *acl; /*!< ACL for xfr-in.*/ + sockaddr_t master; /*!< Master server for xfr-in.*/ + knot_key_t tsig_key; /*!< Master TSIG key. */ + struct event_t *timer; /*!< Timer for REFRESH/RETRY. */ + struct event_t *expire; /*!< Timer for REFRESH. */ + int next_id; /*!< ID of the next awaited SOA resp.*/ + pthread_mutex_t lock; /*!< Pending XFR/IN lock. */ + void *wrkr; /*!< Pending XFR/IN worker. */ + uint32_t bootstrap_retry;/*!< AXFR/IN bootstrap retry. */ + } xfr_in; + + /*! \brief List of pending NOTIFY events. */ + list notify_pending; + + /*! \brief Zone IXFR history. */ + journal_t *ixfr_db; + struct event_t *ixfr_dbsync; /*!< Syncing IXFR db to zonefile. */ + uint32_t zonefile_serial; +} zonedata_t; + +/*! + * \brief Update zone database according to configuration. + * + * Creates a new database, copies references those zones from the old database + * which are still in the configuration, loads any new zones required and + * replaces the database inside the namserver. + * + * It also creates a list of deprecated zones that should be deleted once the + * function finishes. + * + * This function uses RCU mechanism to guard the access to the config and + * nameserver and to publish the new database in the nameserver. + * + * \param[in] conf Configuration. + * \param[in] ns Nameserver which holds the zone database. + * \param[out] db_old Old database, containing only zones which should be + * deleted afterwards. + * + * \retval KNOTD_EOK + * \retval KNOTD_EINVAL + * \retval KNOTD_ERROR + */ +int zones_update_db_from_config(const conf_t *conf, knot_nameserver_t *ns, + knot_zonedb_t **db_old); + +/*! + * \brief Sync zone data back to text zonefile. + * + * In case when SOA serial of the zonefile differs from the SOA serial of the + * loaded zone, zonefile needs to be updated. + * + * \note Current implementation rewrites the zone file. + * + * \param zone Evaluated zone. + * + * \retval KNOTD_EOK if successful. + * \retval KNOTD_EINVAL on invalid parameter. + * \retval KNOTD_ERROR on unspecified error during processing. + */ +int zones_zonefile_sync(knot_zone_t *zone); + +int zones_xfr_check_zone(knot_ns_xfr_t *xfr, knot_rcode_t *rcode); + +/*! + * \brief Processes normal response packet. + * + * \param nameserver Name server structure to provide the needed data. + * \param from Address of the response sender. + * \param packet Parsed response packet. + * \param response_wire Place for the response in wire format. + * \param rsize Input: maximum acceptable size of the response. Output: real + * size of the response. + * + * \retval KNOTD_EOK if a valid response was created. + * \retval KNOTD_EINVAL on invalid parameters or packet. + * \retval KNOTD_EMALF if an error occured and the response is not valid. + */ +int zones_process_response(knot_nameserver_t *nameserver, + sockaddr_t *from, + knot_packet_t *packet, uint8_t *response_wire, + size_t *rsize); + +/*! + * \brief Decides what type of transfer should be used to update the given zone. + * + * \param nameserver Name server structure that uses the zone. + * \param zone Zone to be updated by the transfer. + * + * \retval + */ +knot_ns_xfr_type_t zones_transfer_to_use(const knot_zone_contents_t *zone); + +int zones_save_zone(const knot_ns_xfr_t *xfr); + +/*! + * \brief Name server config hook. + * + * Routine for dynamic name server reconfiguration. + * + * \param conf Current configuration. + * \param data Instance of the nameserver structure to update. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL + * \retval KNOTD_ERROR + */ +int zones_ns_conf_hook(const struct conf_t *conf, void *data); + +/*! + * \brief Store changesets in journal. + * + * Changesets will be stored on a permanent storage. + * Journal may be compacted, resulting in flattening changeset history. + * + * \param zone Zone associated with the changeset. + * \param src Changesets. + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_EAGAIN if journal needs to be synced with zonefile first. + * + * \todo Expects the xfr structure to be initialized in some way. + */ +int zones_store_changesets(knot_ns_xfr_t *xfr); + +/*! + * \brief Load changesets from journal. + * + * Changesets will be stored on a permanent storage. + * Journal may be compacted, resulting in flattening changeset history. + * + * In case of KNOTD_ERANGE error, whole zone content should be sent instead, + * as the changeset history cannot be recovered. + * + * \param zone Zone containing a changeset journal. + * \param dst Container to be loaded. + * \param from Starting SOA serial (oldest). + * \param to Ending SOA serial (newest). + * + * \retval KNOTD_EOK on success. + * \retval KNOTD_EINVAL on invalid parameters. + * \retval KNOTD_ERANGE when changeset history cannot be reconstructed. + * + * \todo Expects the xfr structure to be initialized in some way. + */ +int zones_xfr_load_changesets(knot_ns_xfr_t *xfr, uint32_t serial_from, + uint32_t serial_to); + +/*! + * \brief Apply changesets to zone. + * + * Applies a list of XFR-style changesets to the given zone. Also checks if the + * changesets are applicable (i.e. zone is right and has the right serial). + * + * \param zone Zone to which the changesets should be applied. + * \param chsets Changesets to be applied to the zone. + * + * \retval KNOTD_EOK + * \retval KNOTD_EINVAL + */ +int zones_apply_changesets(knot_ns_xfr_t *xfr); + +/*! + * \brief Update zone timers. + * + * REFRESH/RETRY/EXPIRE timers are updated according to SOA. + * + * \param sched Event scheduler. + * \param zone Related zone. + * \param cfzone Related zone contents. If NULL, configuration is + * reused. + * + * \retval KNOTD_EOK + * \retval KNOTD_EINVAL + * \retval KNOTD_ERROR + */ +int zones_timers_update(knot_zone_t *zone, conf_zone_t *cfzone, evsched_t *sch); + +/*! + * \brief Cancel pending NOTIFY timer. + * + * \warning Expects locked zonedata lock. + * + * \param zd Zone data. + * \param ev NOTIFY event. + * + * \retval KNOTD_EOK + * \retval KNOTD_ERROR + * \retval KNOTD_EINVAL + */ +int zones_cancel_notify(zonedata_t *zd, notify_ev_t *ev); + +#endif // _KNOTD_ZONES_H_ + +/*! @} */ diff --git a/src/knot/stat/gatherer.c b/src/knot/stat/gatherer.c new file mode 100644 index 0000000..e8048a1 --- /dev/null +++ b/src/knot/stat/gatherer.c @@ -0,0 +1,77 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <pthread.h> + +#include "knot/stat/stat-common.h" +#include "common/slab/malloc.h" +#include "knot/stat/gatherer.h" + +gatherer_t *new_gatherer() +{ + gatherer_t *ret; + + if ((ret = malloc(sizeof(gatherer_t))) == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + pthread_mutex_init(&ret->mutex_read, NULL); + + /* TODO check success */ + + for (int i = 0; i < FREQ_BUFFER_SIZE; i++) { + ret->freq_array[i] = 0; + ret->flow_array[i] = NULL; + } + + ret->qps = 0.0; + ret->udp_qps = 0.0; + ret->tcp_qps = 0.0; + + /* CLEANUP */ + /* currently disabled */ + /* ret->mean_latency = 0.0; + ret->udp_mean_latency = 0.0; + ret->tcp_mean_latency = 0.0; + + ret->udp_latency = 0; + ret->tcp_latency = 0; */ + + ret->udp_queries = 0; + ret->tcp_queries = 0; + + return ret; +} + +void gatherer_free(gatherer_t *gath) +{ + for (int i = 0; i < FREQ_BUFFER_SIZE; i++) { + if (gath->flow_array[i] != NULL) { + free(gath->flow_array[i]->addr); + free(gath->flow_array[i]); + } + } + + pthread_mutex_destroy(&(gath->mutex_read)); + + pthread_cancel(gath->sleeper_thread); + + pthread_join((gath->sleeper_thread), NULL); + + free(gath); +} diff --git a/src/knot/stat/gatherer.h b/src/knot/stat/gatherer.h new file mode 100644 index 0000000..62b3939 --- /dev/null +++ b/src/knot/stat/gatherer.h @@ -0,0 +1,110 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file gatherer.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * \brief Contains gatherer structure and its API. + * + * \addtogroup statistics + * @{ + */ + +#ifndef _KNOTD_GATHERER_H_ +#define _KNOTD_GATHERER_H_ + +#include <stdint.h> + +/* The bigger this number, the better the performance of hashing. */ +enum fbs { FREQ_BUFFER_SIZE = 100000 }; + +/*! + * \brief Enum storing protocol codes. + */ +enum protocol { + stat_UDP, + stat_TCP +}; + +typedef enum protocol protocol_t; + +/*! + * \brief Structure used for backward mapping from a simple + * hash back to string representation. + */ +struct flow_data { + char *addr; /*!< IP adress in string format (IP4 only at this time). */ + uint16_t port; /*!< TCP/UDP port number. */ + protocol_t protocol; +}; + +typedef struct flow_data flow_data_t; + +/*! + * \brief Gatherer structure, used for gathering statistics from + * multiple threads. + */ +struct gatherer { + pthread_mutex_t mutex_read; /*!< Mutex used when reading values. */ + double qps; /*!< Queries per second. */ + double udp_qps; /*!< Queries per second - UDP. */ + double tcp_qps; /*!< Queries per second - TCP. */ + + /*!< \note latency currently disabled */ + /* double mean_latency; + double udp_mean_latency; + double tcp_mean_latency; + unsigned udp_latency; + unsigned tcp_latency; */ + + unsigned udp_queries; /*!< Total number of UDP queries for SLEEP_TIME. */ + unsigned tcp_queries; /*!< Total number of TCP queries for SLEEP_TIME. */ + /*! + * \brief this variable should be much bigger, preferably sparse array + * with 2**32 elements (for IPv4). It is an array with query + * query frequencies. + */ + unsigned freq_array[FREQ_BUFFER_SIZE]; + /*! + * \brief Used for backward mapping. + */ + flow_data_t *flow_array[FREQ_BUFFER_SIZE]; + /*! + * \brief Thread used for computation of statistics. + */ + pthread_t sleeper_thread; +}; + +typedef struct gatherer gatherer_t; + +/*! + * \brief Creates a new gatherer instance. + * + * \return Pointer to created structure, NULL otherwise. + */ +gatherer_t *new_gatherer(); + +/*! + * \brief Frees a gatherer instance. + * + * \param gatherer Gatherer instance to be freed. + */ +void gatherer_free(gatherer_t *gatherer); + +#endif /* _KNOTD_STAT_GATHERER_H_ */ + +/*! @} */ diff --git a/src/knot/stat/stat-common.h b/src/knot/stat/stat-common.h new file mode 100644 index 0000000..032e32b --- /dev/null +++ b/src/knot/stat/stat-common.h @@ -0,0 +1,46 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file stat-common.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Common macros for stat. + * + * \addtogroup statistics + * @{ + */ + +#ifndef _KNOTD_STAT_COMMON_H_ +#define _KNOTD_STAT_COMMON_H_ + +#include <stdio.h> + +//#define STAT_COMPILE +#define ST_DEBUG + +#define ERR_ALLOC_FAILED fprintf(stderr, "Allocation failed at %s:%d\n", \ + __FILE__, __LINE__) + +#ifdef ST_DEBUG +#define dbg_st(msg...) fprintf(stderr, msg) +#else +#define dbg_st(msg...) +#endif + +#endif /* _KNOTD_STAT_COMMON_H_ */ + +/*! @} */ diff --git a/src/knot/stat/stat.c b/src/knot/stat/stat.c new file mode 100644 index 0000000..a473085 --- /dev/null +++ b/src/knot/stat/stat.c @@ -0,0 +1,270 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <time.h> +#include <pthread.h> +#include <unistd.h> +#include <stdbool.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <string.h> +#include <stdlib.h> + +#include "knot/stat/stat-common.h" +#include "knot/stat/stat.h" +#include "knot/stat/gatherer.h" + +#ifdef STAT_COMPILE + +/* Static local gatherer variable, to be used with all functions. */ +static gatherer_t *local_gath; + +/* CLEANUP */ +/* +static void stat_inc_latency( stat_t *stat, uint increment ) +{ + if (stat->protocol==stat_UDP) { + local_gath->udp_latency+=increment; + } else { + local_gath->tcp_latency+=increment; + } +}*/ +/* +static uint stat_last_query_time( stat_t *stat ) +{ + return (stat->t2).tv_nsec-(stat->t1).tv_nsec; +}*/ + +/*! + * \brief Increases query count in the local data gatherer. + * + * \param stat Current stat instance. + */ +static void stat_inc_query(stat_t *stat) +{ + if (stat->protocol == stat_UDP) { + local_gath->udp_queries++; + } else { + local_gath->tcp_queries++; + } +} + +/*! + * \brief Calculates very simple hash from IPv4 address and returns index to + * array. + * + * \param s_addr Socket address structure. + * \param protocol Used protocol. + * + * \return uint Calculated index. + */ +static uint return_index(struct sockaddr_in *s_addr , protocol_t protocol) +{ + /* TODO IPv6 */ + /* This is the first "hash" I could think of quickly. */ + uint ret = 0; + + char str[24]; + inet_ntop(AF_INET, &s_addr->sin_addr, str, 24); + + for (int i = 0; i < strlen(str); i++) { + if (str[i] != '.') { + ret += str[i]; + ret *= (i + 1); + } + } + + ret += s_addr->sin_port * 7; + if (protocol == stat_UDP) { + ret *= 3; + } else { + ret *= 7; + } + ret %= FREQ_BUFFER_SIZE; + /* Effectively uses only end of the hash, maybe hash the + * resulting number once again to get 0 <= n < 10000. */ + return ret; +} + +/*! + * \brief Adds data to local gatherer structure. + * + * \param stat Current stat variable. + * + * \retval 0 on success. + * \retval -1 on memory error. + */ +static int stat_gatherer_add_data(stat_t *stat) +{ + /* TODO IPv6*/ + uint index = return_index(stat->s_addr, stat->protocol); + if (!local_gath->freq_array[index]) { + char addr[24]; + inet_ntop(AF_INET, &stat->s_addr->sin_addr, addr, 24); + flow_data_t *tmp; + tmp = malloc(sizeof(flow_data_t)); + if (tmp == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + tmp->addr = malloc(sizeof(char) * 24); + if (tmp->addr == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + strcpy(tmp->addr, addr); + tmp->port = stat->s_addr->sin_port; + tmp->protocol = stat->protocol; + local_gath->flow_array[index] = tmp; + } + + //TODO add a check here, whether hashing fction performs well enough + + local_gath->freq_array[index] += 1; + + return 0; +} + +/*! + * \brief Resets logging array. + */ +static void stat_reset_gatherer_array() +{ + for (int i = 0; i < FREQ_BUFFER_SIZE; i++) { + local_gath->freq_array[i] = 0; + } +} + +/*! + * \brief Sleeps for given time and then runs all the computations, + * results of which are stored in local gatherer. + */ +static void stat_sleep_compute() +{ + while (1) { + sleep(SLEEP_TIME); + + for (int i = 0; i < FREQ_BUFFER_SIZE; i++) { + if (local_gath->freq_array[i] > + ACTIVE_FLOW_THRESHOLD) { + dbg_st("too much activity at index %d:" + " %d queries adress: %s port %d" + " protocol %d\n", + i, local_gath->freq_array[i], + local_gath->flow_array[i]->addr, + local_gath->flow_array[i]->port, + local_gath->flow_array[i]->protocol); + } + } + + pthread_mutex_lock(&(local_gath->mutex_read)); + + local_gath->udp_qps = local_gath->udp_queries / + (double)SLEEP_TIME; + local_gath->tcp_qps = local_gath->tcp_queries / + (double)SLEEP_TIME; + local_gath->qps = local_gath->udp_qps + local_gath->tcp_qps; + + /* following code needs usage of + * gettimeofday, which is currently disabled */ + /* CLEANUP */ +/* local_gath->udp_mean_latency=((double)local_gath->udp_latency/ + (double)local_gath->udp_queries); + local_gath->tcp_mean_latency=((double)local_gath->tcp_latency/ + (double)local_gath->tcp_queries); + local_gath->mean_latency = (local_gath->udp_mean_latency + + local_gath->tcp_mean_latency)/2; */ + + local_gath->udp_queries = 0; + local_gath->tcp_queries = 0; + + /* same thing as above applies here */ + +/* local_gath->tcp_latency = 0; + local_gath->udp_latency = 0; */ + + pthread_mutex_unlock(&(local_gath->mutex_read)); + + stat_reset_gatherer_array(local_gath); + + dbg_st("qps_udp: %f\n", local_gath->udp_qps); +/* dbg_st("mean_lat_udp: %f\n", local_gath->udp_mean_latency); */ + + dbg_st("qps_tcp: %f\n", local_gath->tcp_qps); +/* dbg_st("mean_lat_tcp: %f\n", local_gath->tcp_mean_latency); */ + + dbg_st("UDP/TCP ratio %f\n", + local_gath->udp_qps / local_gath->tcp_qps); + } +} + +stat_t *stat_new() +{ + stat_t *ret; + + if ((ret = malloc(sizeof(stat_t))) == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + return ret; +} + +void stat_set_protocol(stat_t *stat, int protocol) +{ + stat->protocol = protocol; +} + +void stat_get_first(stat_t *stat , struct sockaddr_in *s_addr) +{ + /* CLEANUP */ +// gettimeofday(&stat->t2, NULL); + stat->s_addr = s_addr; +// check if s_addr does not get overwritten +} + +void stat_get_second(stat_t *stat) +{ + /* CLEANUP */ +// gettimeofday(&stat->t2, NULL); + stat_inc_query(stat); +// stat_inc_latency(stat, stat_last_query_time(stat)); + stat_gatherer_add_data(stat); +} + +void stat_free(stat_t *stat) +{ + free(stat); +} + +void stat_static_gath_init() +{ + local_gath = new_gatherer(); +} + +void stat_static_gath_start() +{ + pthread_create(&(local_gath->sleeper_thread), NULL, + (void *) &stat_sleep_compute, NULL); +} + +void stat_static_gath_free() +{ + gatherer_free(local_gath); +} + +#endif /* STAT_COMPILE */ diff --git a/src/knot/stat/stat.h b/src/knot/stat/stat.h new file mode 100644 index 0000000..a82f130 --- /dev/null +++ b/src/knot/stat/stat.h @@ -0,0 +1,157 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file stat.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * \brief Contains statistics structure and its API. + * + * \addtogroup statistics + * @{ + */ + +#ifndef _KNOTD_STAT_H_ +#define _KNOTD_STAT_H_ + +#include <time.h> +#include <stdbool.h> +#include <pthread.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include "knot/stat/gatherer.h" + +#ifdef STAT_COMPILE +#define STAT_INIT(x) x = stat_new() +#else +#define STAT_INIT(x) x = NULL //UNUSED(x) +#endif /* STAT_COMPILE */ + +/* Determines how long until the sleeper thread + * wakes up and runs computations. + */ +static uint const SLEEP_TIME = 15; + +/* Sets threshold for active flow detection, will + * probably have to be changed. */ +static uint const ACTIVE_FLOW_THRESHOLD = 10; + +/*! + * \brief Statistics structure, unique for each UDP/TCP thread. + */ +struct stat_stat { +// struct timespec t1, t2; /* Currently disabled */ + protocol_t protocol; /*!< Flags. */ + struct sockaddr_in *s_addr; +// gatherer_t *gatherer; / * not needed when using static gatherer. */ +}; + +typedef struct stat_stat stat_t; + +/*! + * \brief Creates new stat_t structure. + * + * \return Newly allocated and initialized stat structure, NULL on errror. + */ +#ifdef STAT_COMPILE +stat_t *stat_new(); +#else +inline stat_t *stat_new() +{ + return NULL; +} +#endif /* STAT_COMPILE */ + +/*! + * \brief Sets a protocol for stat_t structure. Options are stat_UDP, stat_TCP. + * + * \param stat Stat_t instance (usually newly created). + * \param protocol Protocol to be assigned to stat structure. + */ +#ifdef STAT_COMPILE +void stat_set_protocol(stat_t *stat, int protocol); +#else +static inline void stat_set_protocol(stat_t *stat, int protocol) {} +#endif /* STAT_COMPILE */ + +/*! + * \brief Gets the time from a processing function. + * + * \param stat Current instance of stat_t. + * \param s_addr Sockaddr structure to be used later for statistics. + */ +#ifdef STAT_COMPILE +#warning "stat fixme: pass sockaddr* for generic _in and _in6 support" +void stat_get_first(stat_t *stat, struct sockaddr_in *s_addr); +#else +static inline void stat_get_first(stat_t *stat, struct sockaddr *s_addr) {} +#endif /* STAT_COMPILE */ + +/*! + * \brief Gets time from a processing fuction and changes + * the corresponding variables. + * + * \param stat Current stat_t instance. + */ +#ifdef STAT_COMPILE +void stat_get_second(stat_t *stat); +#else +static inline void stat_get_second(stat_t *stat) {} +#endif /* STAT_COMPILE */ + +/*! + * \brief Frees stat_t structure. + * + * \param stat Pointer to stat structure to be deallocated. + */ +#ifdef STAT_COMPILE +void stat_free(stat_t *stat); +#else +static inline void stat_free(stat_t *stat) {} +#endif /* STAT_COMPILE */ + +/*! + * \brief Initializes static gatherer. + */ +#ifdef STAT_COMPILE +void stat_static_gath_init(); +#else +static inline void stat_static_gath_init() {} +#endif /* STAT_COMPILE */ + +/*! + * \brief Starts static gatherer's sleeper thread. + */ +#ifdef STAT_COMPILE +void stat_static_gath_start(); +#else +static inline void stat_static_gath_start() {} +#endif /* STAT_COMPILE */ + +/*! + * \brief Frees static gatherer, calls gatherer_free(). + */ +#ifdef STAT_COMPILE +void stat_static_gath_free(); +#else +static inline void stat_static_gath_free() {} +#endif /* STAT_COMPILE */ + +#endif /* _KNOTD_STAT_H_ */ + +/*! @} */ diff --git a/src/knot/zone/zone-dump-text.c b/src/knot/zone/zone-dump-text.c new file mode 100644 index 0000000..f7899a1 --- /dev/null +++ b/src/knot/zone/zone-dump-text.c @@ -0,0 +1,1083 @@ +/*! + * \file zone-dump-text.c + * + * \author modifications (non-buffer implementation, zone-specific functions) + * by Jan Kadlec <jan.kadlec@nic.cz>, + * conversion functions by NLnet Labs, + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * b64ntop by ISC. + */ + +/* + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <config.h> + +#include <ctype.h> +#include <assert.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "libknot/libknot.h" +#include "libknot/common.h" +#include "common/skip-list.h" +#include "common/base32hex.h" + +/* TODO max length of alg */ + +enum uint_max_length { + U8_MAX_STR_LEN = 4, U16_MAX_STR_LEN = 6, + U32_MAX_STR_LEN = 11, MAX_RR_TYPE_LEN = 20, + MAX_NSEC_BIT_STR_LEN = 4096, + }; + +#define APL_NEGATION_MASK 0x80U +#define APL_LENGTH_MASK (~APL_NEGATION_MASK) + +/* RFC 4025 - codes for different types that IPSECKEY can hold. */ +#define IPSECKEY_NOGATEWAY 0 +#define IPSECKEY_IP4 1 +#define IPSECKEY_IP6 2 +#define IPSECKEY_DNAME 3 + +/* Following copyrights are only valid for b64_ntop function */ +/* + * Copyright (c) 1996, 1998 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* + * Portions Copyright (c) 1995 by International Business Machines, Inc. + * + * International Business Machines, Inc. (hereinafter called IBM) grants + * permission under its copyrights to use, copy, modify, and distribute this + * Software with or without fee, provided that the above copyright notice and + * all paragraphs of this notice appear in all copies, and that the name of IBM + * not be used in connection with the marketing of any product incorporating + * the Software or modifications thereof, without specific, written prior + * permission. + * + * To the extent it has a right to do so, IBM grants an immunity from suit + * under its patents, if any, for the use, sale or manufacture of products to + * the extent that such products are used for performing Domain Name System + * dynamic updates in TCP/IP networks by means of the Software. No immunity is + * granted for any product per se or for any other function of any product. + * + * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, + * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN + * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES. + */ +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define Assert(Cond) if (!(Cond)) abort() + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt) + The following encoding technique is taken from RFC 1521 by Borenstein + and Freed. It is reproduced here in a slightly edited form for + convenience. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8-bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a quantity. When fewer than 24 input + bits are available in an input group, zero bits are added (on the + right) to form an integral number of 6-bit groups. Padding at the + end of the data is performed using the '=' character. + + Since all base64 input is an integral number of octets, only the + ------------------------------------------------- + following cases can arise: + + (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded + output will be an integral multiple of 4 characters + with no "=" padding, + (2) the final quantum of encoding input is exactly 8 bits; + here, the final unit of encoded output will be two + characters followed by two "=" padding characters, or + (3) the final quantum of encoding input is exactly 16 bits; + here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + +int b64_ntop(uint8_t const *src, size_t srclength, char *target, + size_t targsize) { + size_t datalength = 0; + uint8_t input[3]; + uint8_t output[4]; + size_t i; + + while (2 < srclength) { + input[0] = *src++; + input[1] = *src++; + input[2] = *src++; + srclength -= 3; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + output[3] = input[2] & 0x3f; + Assert(output[0] < 64); + Assert(output[1] < 64); + Assert(output[2] < 64); + Assert(output[3] < 64); + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + target[datalength++] = Base64[output[2]]; + target[datalength++] = Base64[output[3]]; + } + + /* Now we worry about padding. */ + if (0 != srclength) { + /* Get what's left. */ + input[0] = input[1] = input[2] = '\0'; + for (i = 0; i < srclength; i++) + input[i] = *src++; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + Assert(output[0] < 64); + Assert(output[1] < 64); + Assert(output[2] < 64); + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + if (srclength == 1) + target[datalength++] = Pad64; + else + target[datalength++] = Base64[output[2]]; + target[datalength++] = Pad64; + } + if (datalength >= targsize) + return (-1); + target[datalength] = '\0'; /* Returned value doesn't count \0. */ + return (datalength); +} + +/* Taken from RFC 4398, section 2.1. */ +knot_lookup_table_t knot_dns_certificate_types[] = { +/* 0 Reserved */ + { 1, "PKIX" }, /* X.509 as per PKIX */ + { 2, "SPKI" }, /* SPKI cert */ + { 3, "PGP" }, /* OpenPGP packet */ + { 4, "IPKIX" }, /* The URL of an X.509 data object */ + { 5, "ISPKI" }, /* The URL of an SPKI certificate */ + { 6, "IPGP" }, /* The fingerprint and URL of an OpenPGP packet */ + { 7, "ACPKIX" }, /* Attribute Certificate */ + { 8, "IACPKIX" }, /* The URL of an Attribute Certificate */ + { 253, "URI" }, /* URI private */ + { 254, "OID" }, /* OID private */ +/* 255 Reserved */ +/* 256-65279 Available for IANA assignment */ +/* 65280-65534 Experimental */ +/* 65535 Reserved */ + { 0, NULL } +}; + +/* Taken from RFC 2535, section 7. */ +knot_lookup_table_t knot_dns_algorithms[] = { + { 1, "RSAMD5" }, /* RFC 2537 */ + { 2, "DH" }, /* RFC 2539 */ + { 3, "DSA" }, /* RFC 2536 */ + { 4, "ECC" }, + { 5, "RSASHA1" }, /* RFC 3110 */ + { 252, "INDIRECT" }, + { 253, "PRIVATEDNS" }, + { 254, "PRIVATEOID" }, + { 0, NULL } +}; + +static int get_bit(uint8_t bits[], size_t index) +{ + /* + * The bits are counted from left to right, so bit #0 is the + * left most bit. + */ + return bits[index / 8] & (1 << (7 - index % 8)); +} + +static inline uint8_t * rdata_item_data(knot_rdata_item_t item) +{ + return (uint8_t *)(item.raw_data + 1); +} + +static inline uint16_t rdata_item_size(knot_rdata_item_t item) +{ + return item.raw_data[0]; +} + +char *rdata_dname_to_string(knot_rdata_item_t item) +{ + return knot_dname_to_str(item.dname); +} + +char *rdata_dns_name_to_string(knot_rdata_item_t item) +{ + return knot_dname_to_str(item.dname); +} + +static char *rdata_txt_data_to_string(const uint8_t *data) +{ + uint8_t length = data[0]; + size_t i; + + if (length == 0) { + return NULL; + } + + /* + * 3 because: opening '"', closing '"', and \0 at the end. + * Times 2 because string can be all "double chars". + */ + size_t current_length = sizeof(char) * (length * 2 + 3); + char *ret = malloc(current_length); + if (ret == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + memset(ret, 0, sizeof(char) * (length * 2 + 3)); + + + strcat(ret, "\""); + + for (i = 1; i <= length; i++) { + char ch = (char) data[i]; + if (isprint((int)ch)) { + if (ch == '"' || ch == '\\') { + strcat(ret, "\""); + } + /* for the love of god, how to this better, + but w/o obscure self-made functions */ + char tmp_str[2]; + tmp_str[0] = ch; + tmp_str[1] = 0; + strcat(ret, tmp_str); + } else { + strcat(ret, "\\"); + char tmp_str[2]; + tmp_str[0] = ch - '0'; + tmp_str[1] = 0; + + strcat(ret, tmp_str); + } + } + strcat(ret, "\""); + + return ret; +} + +char *rdata_text_to_string(knot_rdata_item_t item) +{ + uint16_t size = item.raw_data[0]; + char *ret = malloc(sizeof(char) * size * 2) ; + if (ret == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + memset(ret, 0, sizeof(char) * size); + const uint8_t *data = (uint8_t *)(item.raw_data + 1); + size_t read_count = 0; + while (read_count < size) { + assert(read_count <= size); + char *txt = rdata_txt_data_to_string(data + read_count); + if (txt == NULL) { + free(ret); + return NULL; + } + read_count += strlen(txt) - 1; + /* Create delimiter. */ + char del[2]; + del[0] = ' '; + del[1] = '\0'; + strcat(ret, txt); + strcat(ret, del); + free(txt); + } + + return ret; +} + +char *rdata_byte_to_string(knot_rdata_item_t item) +{ + assert(item.raw_data[0] == 1); + uint8_t data = item.raw_data[1]; + char *ret = malloc(sizeof(char) * U8_MAX_STR_LEN); + snprintf(ret, U8_MAX_STR_LEN, "%d", (char) data); + return ret; +} + +char *rdata_short_to_string(knot_rdata_item_t item) +{ + uint16_t data = knot_wire_read_u16(rdata_item_data(item)); + char *ret = malloc(sizeof(char) * U16_MAX_STR_LEN); + snprintf(ret, U16_MAX_STR_LEN, "%u", data); + /* XXX Use proper macros - see response tests*/ + /* XXX check return value, return NULL on failure */ + return ret; +} + +char *rdata_long_to_string(knot_rdata_item_t item) +{ + uint32_t data = knot_wire_read_u32(rdata_item_data(item)); + char *ret = malloc(sizeof(char) * U32_MAX_STR_LEN); + /* u should be enough */ + snprintf(ret, U32_MAX_STR_LEN, "%u", data); + return ret; +} + +char *rdata_a_to_string(knot_rdata_item_t item) +{ + /* 200 seems like a little too much */ + char *ret = malloc(sizeof(char) * 200); + if (inet_ntop(AF_INET, rdata_item_data(item), ret, 200)) { + return ret; + } else { + return NULL; + } +} + +char *rdata_aaaa_to_string(knot_rdata_item_t item) +{ + char *ret = malloc(sizeof(char) * 200); + if (inet_ntop(AF_INET6, rdata_item_data(item), ret, 200)) { + return ret; + } else { + return NULL; + } +} + +char *rdata_rrtype_to_string(knot_rdata_item_t item) +{ + uint16_t type = knot_wire_read_u16(rdata_item_data(item)); + const char *tmp = knot_rrtype_to_string(type); + char *ret = malloc(sizeof(char) * MAX_RR_TYPE_LEN); + strncpy(ret, tmp, MAX_RR_TYPE_LEN); + return ret; +} + +char *rdata_algorithm_to_string(knot_rdata_item_t item) +{ + uint8_t id = *rdata_item_data(item); + char *ret = malloc(sizeof(char) * MAX_RR_TYPE_LEN); + knot_lookup_table_t *alg + = knot_lookup_by_id(knot_dns_algorithms, id); + if (alg) { + strncpy(ret, alg->name, MAX_RR_TYPE_LEN); + } else { + snprintf(ret, U8_MAX_STR_LEN, "%u", id); + } + + return ret; +} + +char *rdata_certificate_type_to_string(knot_rdata_item_t item) +{ + uint16_t id = knot_wire_read_u16(rdata_item_data(item)); + char *ret = malloc(sizeof(char) * MAX_RR_TYPE_LEN); + knot_lookup_table_t *type + = knot_lookup_by_id(knot_dns_certificate_types, id); + if (type) { + strncpy(ret, type->name, MAX_RR_TYPE_LEN); + } else { + snprintf(ret, U16_MAX_STR_LEN, "%u", id); + } + + return ret; +} + +char *rdata_period_to_string(knot_rdata_item_t item) +{ + /* uint32 but read 16 XXX */ + uint32_t period = knot_wire_read_u32(rdata_item_data(item)); + char *ret = malloc(sizeof(char) * U32_MAX_STR_LEN); + snprintf(ret, U32_MAX_STR_LEN, "%u", period); + return ret; +} + +char *rdata_time_to_string(knot_rdata_item_t item) +{ + time_t time = (time_t) knot_wire_read_u32(rdata_item_data(item)); + struct tm tm_conv; + if (gmtime_r(&time, &tm_conv) == 0) { + return 0; + } + char *ret = malloc(sizeof(char) * 15); + if (strftime(ret, 15, "%Y%m%d%H%M%S", &tm_conv)) { + return ret; + } else { + free(ret); + return 0; + } +} + +char *rdata_base32_to_string(knot_rdata_item_t item) +{ + int length; + size_t size = rdata_item_size(item); + if (size == 0) { + char *ret = malloc(sizeof(char) * 2); + ret[0] = '-'; + ret[1] = '\0'; + return ret; + } + + size -= 1; // remove length byte from count + char *ret = NULL; + length = base32hex_encode_alloc((char *)rdata_item_data(item) + 1, + size, &ret); + if (length > 0) { + return ret; + } else { + free(ret); + return NULL; + } +} + +char *rdata_base64_to_string(knot_rdata_item_t item) +{ + int length; + size_t size = rdata_item_size(item); + char *ret = malloc((sizeof(char) * 2 * size) + 1 * sizeof(char)); + length = b64_ntop(rdata_item_data(item), size, + ret, (sizeof(char)) * (size * 2 + 1)); + if (length > 0) { + return ret; + } else { + free(ret); + return NULL; + } +} + +char *hex_to_string(const uint8_t *data, size_t size) +{ + static const char hexdigits[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + size_t i; + + char *ret = malloc(sizeof(char) * (size * 2 + 1)); + + for (i = 0; i < size * 2; i += 2) { + uint8_t octet = *data++; + ret[i] = hexdigits [octet >> 4]; + ret[i + 1] = hexdigits [octet & 0x0f]; + } + + ret[i] = '\0'; + + return ret; +} + +char *rdata_hex_to_string(knot_rdata_item_t item) +{ + return hex_to_string(rdata_item_data(item), rdata_item_size(item)); +} + +char *rdata_hexlen_to_string(knot_rdata_item_t item) +{ + if(rdata_item_size(item) <= 1) { + // NSEC3 salt hex can be empty + char *ret = malloc(sizeof(char) * 2); + ret[0] = '-'; + ret[1] = '\0'; + return ret; + } else { + return hex_to_string(rdata_item_data(item) + 1, + rdata_item_size(item) - 1); + } +} + +char *rdata_nsap_to_string(knot_rdata_item_t item) +{ + char *ret = malloc(sizeof(char) * (rdata_item_size(item) + 3)); + memcpy(ret, "0x", strlen("0x")); + char *converted = hex_to_string(rdata_item_data(item), + rdata_item_size(item)); + strcat(ret, converted); + free(converted); + return ret; +} + +char *rdata_apl_to_string(knot_rdata_item_t item) +{ + uint8_t *data = rdata_item_data(item); + uint16_t address_family = knot_wire_read_u16(data); + uint8_t prefix = data[2]; + uint8_t length = data[3]; + int negated = length & APL_NEGATION_MASK; + int af = -1; + + char *ret = malloc(sizeof(char) * MAX_NSEC_BIT_STR_LEN); + + memset(ret, 0, MAX_NSEC_BIT_STR_LEN); + + length &= APL_LENGTH_MASK; + switch (address_family) { + case 1: af = AF_INET; break; + case 2: af = AF_INET6; break; + } + + if (af != -1) { + char text_address[1000]; + uint8_t address[128]; + memset(address, 0, sizeof(address)); + memcpy(address, data + 4, length); + if (inet_ntop(af, address, + text_address, + sizeof(text_address))) { + snprintf(ret, sizeof(text_address) + + U32_MAX_STR_LEN * 2, + "%s%d:%s/%d", + negated ? "!" : "", + (int) address_family, + text_address, + (int) prefix); + } + } + + return ret; + + /* + int result = 0; + buffer_type packet; + + buffer_create_from( + &packet, rdata_item_data(rdata), rdata_atom_size(rdata)); + + if (buffer_available(&packet, 4)) { + uint16_t address_family = buffer_read_u16(&packet); + uint8_t prefix = buffer_read_u8(&packet); + uint8_t length = buffer_read_u8(&packet); + int negated = length & APL_NEGATION_MASK; + int af = -1; + + length &= APL_LENGTH_MASK; + switch (address_family) { + case 1: af = AF_INET; break; + *case 2: af = AF_INET6; break; + } + if (af != -1 && buffer_available(&packet, length)) { + char text_address[1000]; + uint8_t address[128]; + memset(address, 0, sizeof(address)); + buffer_read(&packet, address, length); + if (inet_ntop(af, address, text_address, + sizeof(text_address))) { + buffer_printf(output, "%s%d:%s/%d", + negated ? "!" : "", + (int) address_family, + text_address, + (int) prefix); + result = 1; + } + } + } + return result; + */ + + +} + +char *rdata_services_to_string(knot_rdata_item_t item) +{ + uint8_t *data = rdata_item_data(item); + uint8_t protocol_number = data[0]; + ssize_t bitmap_size = rdata_item_size(item) - 1; + uint8_t *bitmap = data + 1; + struct protoent *proto = getprotobynumber(protocol_number); + + char *ret = malloc(sizeof(char) * MAX_NSEC_BIT_STR_LEN); + + memset(ret, 0, MAX_NSEC_BIT_STR_LEN); + + if (proto) { + int i; + + strcpy(ret, proto->p_name); + + strcat(ret, " "); + + for (i = 0; i < bitmap_size * 8; ++i) { + if (get_bit(bitmap, i)) { + struct servent *service = + getservbyport((int)htons(i), + proto->p_name); + if (service) { + strcat(ret, service->s_name); + strcat(ret, " "); + } else { + char tmp[U32_MAX_STR_LEN]; + snprintf(tmp, U32_MAX_STR_LEN, + "%d ", i); + strcat(ret, tmp); + } + } + } + } + + return ret; + + /* + int result = 0; + uint8_t protocol_number = buffer_read_u8(&packet); + ssize_t bitmap_size = buffer_remaining(&packet); + uint8_t *bitmap = buffer_current(&packet); + struct protoent *proto = getprotobynumber(protocol_number); + + + if (proto) { + int i; + + strcpy(ret, proto->p_name); + + for (i = 0; i < bitmap_size * 8; ++i) { + if (get_bit(bitmap, i)) { + struct servent *service = + getservbyport((int)htons(i), + proto->p_name); + if (service) { + buffer_printf(output, " %s", + service->s_name); + } else { + buffer_printf(output, " %d", i); + } + } + } + result = 1; + } + return ret; + */ +} + +char *rdata_ipsecgateway_to_string(knot_rdata_item_t item, + const knot_rrset_t *rrset) +{ + const knot_rdata_item_t *gateway_type_item = + knot_rdata_item(knot_rrset_rdata(rrset), 1); + if (gateway_type_item == NULL) { + return NULL; + } + /* First two bytes store length. */ + int gateway_type = ((uint8_t *)(gateway_type_item->raw_data))[2]; + switch(gateway_type) { + case IPSECKEY_NOGATEWAY: { + char *ret = malloc(sizeof(char) * 4); + if (ret == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + memset(ret, 0, sizeof(char) * 4); + memcpy(ret, ".", 4); +/* ret[0] = '\"'; + ret[1] = '.'; + ret[2] = '\"'; + ret[3] = '\0'; */ + return ret; + } + case IPSECKEY_IP4: + return rdata_a_to_string(item); + case IPSECKEY_IP6: + return rdata_aaaa_to_string(item); + case IPSECKEY_DNAME: + return rdata_dname_to_string(item); + default: + return NULL; + } + + /* Flow *should* not get here. */ + return NULL; +} + +char *rdata_nxt_to_string(knot_rdata_item_t item) +{ + size_t i; + uint8_t *bitmap = rdata_item_data(item); + size_t bitmap_size = rdata_item_size(item); + + char *ret = malloc(sizeof(char) * MAX_NSEC_BIT_STR_LEN); + + memset(ret, 0, MAX_NSEC_BIT_STR_LEN); + + for (i = 0; i < bitmap_size * 8; ++i) { + if (get_bit(bitmap, i)) { + strcat(ret, knot_rrtype_to_string(i)); + strcat(ret, " "); + } + } + + return ret; +} + + +char *rdata_nsec_to_string(knot_rdata_item_t item) +{ + /* CLEANUP */ +// int insert_space = 0; + + char *ret = malloc(sizeof(char) * MAX_NSEC_BIT_STR_LEN); + + memset(ret, 0, MAX_NSEC_BIT_STR_LEN); + + uint8_t *data = rdata_item_data(item); + + int increment = 0; + + for (int i = 0; i < rdata_item_size(item); i += increment) { + increment = 0; + uint8_t window = data[i]; + increment++; + + uint8_t bitmap_size = data[i + increment]; + increment++; + + uint8_t *bitmap = malloc(sizeof(uint8_t) * bitmap_size); + + memcpy(bitmap, data + i + increment, + bitmap_size); + + increment += bitmap_size; + + for (int j = 0; j < bitmap_size * 8; j++) { + if (get_bit(bitmap, j)) { + strcat(ret, + knot_rrtype_to_string(j + + window * 256)); + strcat(ret, " "); + } + } + + free(bitmap); + } + + return ret; + + /* CLEANUP */ +/* while (buffer_available(&packet, 2)) { + uint8_t window = buffer_read_u8(&packet); + uint8_t bitmap_size = buffer_read_u8(&packet); + uint8_t *bitmap = buffer_current(&packet); + int i; + + if (!buffer_available(&packet, bitmap_size)) { + buffer_set_position(output, saved_position); + return 0; + } + + for (i = 0; i < bitmap_size * 8; ++i) { + if (get_bit(bitmap, i)) { + buffer_printf(output, + "%s%s", + insert_space ? " " : "", + rrtype_to_string( + window * 256 + i)); + insert_space = 1; + } + } + buffer_skip(&packet, bitmap_size); + } + + return 1; */ +} + +char *rdata_unknown_to_string(knot_rdata_item_t item) +{ + uint16_t size = rdata_item_size(item); + char *ret = + malloc(sizeof(char) * (rdata_item_size(item) + + strlen("\\# ") + U16_MAX_STR_LEN)); + snprintf(ret, strlen("\\# ") + U16_MAX_STR_LEN, "%lu", + (unsigned long) size); + char *converted = hex_to_string(rdata_item_data(item), size); + strcat(ret, converted); + free(converted); + return ret; +} + +char *rdata_loc_to_string(knot_rdata_item_t item) +{ + return rdata_unknown_to_string(item); +} + +typedef char * (*item_to_string_t)(knot_rdata_item_t); + +static item_to_string_t item_to_string_table[KNOT_RDATA_ZF_UNKNOWN + 1] = { + rdata_dname_to_string, + rdata_dns_name_to_string, + rdata_text_to_string, + rdata_byte_to_string, + rdata_short_to_string, + rdata_long_to_string, + rdata_a_to_string, + rdata_aaaa_to_string, + rdata_rrtype_to_string, + rdata_algorithm_to_string, + rdata_certificate_type_to_string, + rdata_period_to_string, + rdata_time_to_string, + rdata_base64_to_string, + rdata_base32_to_string, + rdata_hex_to_string, + rdata_hexlen_to_string, + rdata_nsap_to_string, + rdata_apl_to_string, + NULL, //rdata_ipsecgateway_to_string, + rdata_services_to_string, + rdata_nxt_to_string, + rdata_nsec_to_string, + rdata_loc_to_string, + rdata_unknown_to_string +}; + +char *rdata_item_to_string(knot_rdata_zoneformat_t type, + knot_rdata_item_t item) +{ + return item_to_string_table[type](item); +} + +/* CLEANUP */ +/*void knot_zone_tree_apply_inorder(knot_zone_t *zone, + void (*function)(knot_node_t *node, void *data), + void *data); */ + +void rdata_dump_text(const knot_rdata_t *rdata, uint16_t type, FILE *f, + const knot_rrset_t *rrset) +{ + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(type); + char *item_str = NULL; + for (int i = 0; i < rdata->count; i++) { + /* Workaround for IPSec gateway. */ + if (desc->zoneformat[i] == KNOT_RDATA_ZF_IPSECGATEWAY) { + item_str = rdata_ipsecgateway_to_string(rdata->items[i], + rrset); + } else { + item_str = rdata_item_to_string(desc->zoneformat[i], + rdata->items[i]); + } + if (item_str == NULL) { + item_str = + rdata_item_to_string(KNOT_RDATA_ZF_UNKNOWN, + rdata->items[i]); + } + if (i != rdata->count - 1) { + fprintf(f, "%s ", item_str); + } else { + fprintf(f, "%s", item_str); + } + free(item_str); + } + fprintf(f, "\n"); +} + +void dump_rrset_header(const knot_rrset_t *rrset, FILE *f) +{ + char *name = knot_dname_to_str(rrset->owner); + fprintf(f, "%-20s ", name); + free(name); + fprintf(f, "%-5u ", rrset->ttl); + fprintf(f, "%-2s ", knot_rrclass_to_string(rrset->rclass)); + fprintf(f, "%-5s ", knot_rrtype_to_string(rrset->type)); +} + +void rrsig_set_dump_text(knot_rrset_t *rrsig, FILE *f) +{ + dump_rrset_header(rrsig, f); + knot_rdata_t *tmp = rrsig->rdata; + + while (tmp->next != rrsig->rdata) { + rdata_dump_text(tmp, KNOT_RRTYPE_RRSIG, f, rrsig); + dump_rrset_header(rrsig, f); + tmp = tmp->next; + } + + rdata_dump_text(tmp, KNOT_RRTYPE_RRSIG, f, rrsig); +} + + +void rrset_dump_text(const knot_rrset_t *rrset, FILE *f) +{ + dump_rrset_header(rrset, f); + knot_rdata_t *tmp = rrset->rdata; + + while (tmp->next != rrset->rdata) { + rdata_dump_text(tmp, rrset->type, f, rrset); + dump_rrset_header(rrset, f); + tmp = tmp->next; + } + + rdata_dump_text(tmp, rrset->type, f, rrset); + knot_rrset_t *rrsig_set = rrset->rrsigs; + if (rrsig_set != NULL) { + rrsig_set_dump_text(rrsig_set, f); + } +} + +struct dump_param { + FILE *f; + const knot_dname_t *origin; +}; + +void apex_node_dump_text(knot_node_t *node, FILE *f) +{ + knot_rrset_t dummy_rrset; + dummy_rrset.type = KNOT_RRTYPE_SOA; + knot_rrset_t *tmp_rrset = + (knot_rrset_t *)gen_tree_find(node->rrset_tree, + &dummy_rrset); + assert(tmp_rrset); + rrset_dump_text(tmp_rrset, f); + + const knot_rrset_t **rrsets = + knot_node_rrsets(node); + + for (int i = 0; i < node->rrset_count; i++) { + if (rrsets[i]->type != KNOT_RRTYPE_SOA) { + rrset_dump_text(rrsets[i], f); + } + } + + free(rrsets); +} + +void node_dump_text(knot_node_t *node, void *data) +{ + struct dump_param *param; + param = (struct dump_param *)data; + FILE *f = param->f; + const knot_dname_t *origin = param->origin; + + /* pointers should do in this case */ + if (node->owner == origin) { + apex_node_dump_text(node, f); + return; + } + + const knot_rrset_t **rrsets = + knot_node_rrsets(node); + + for (int i = 0; i < node->rrset_count; i++) { + rrset_dump_text(rrsets[i], f); + } + + free(rrsets); +} + +int zone_dump_text(knot_zone_contents_t *zone, const char *filename) +{ + FILE *f = fopen(filename, "w"); + if (f == NULL) { + return KNOT_EBADARG; + } + + fprintf(f, ";Dumped using %s v. %s\n", PACKAGE_NAME, PACKAGE_VERSION); + + struct dump_param param; + param.f = f; + assert(zone->apex != NULL && zone->apex->owner != NULL); + param.origin = knot_node_owner(knot_zone_contents_apex(zone)); + knot_zone_contents_tree_apply_inorder(zone, node_dump_text, ¶m); + knot_zone_contents_nsec3_apply_inorder(zone, node_dump_text, ¶m); + fclose(f); + + return KNOT_EOK; +} diff --git a/src/knot/zone/zone-dump-text.h b/src/knot/zone/zone-dump-text.h new file mode 100644 index 0000000..70dcff4 --- /dev/null +++ b/src/knot/zone/zone-dump-text.h @@ -0,0 +1,46 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file zone-dump-text.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * \brief Functions for dumping zone to text file. + * + * \addtogroup dnslib + * @{ + */ + +#ifndef _KNOT_ZONE_DUMP_TEXT_H_ +#define _KNOT_ZONE_DUMP_TEXT_H_ + +#include "libknot/util/descriptor.h" +#include "libknot/zone/zone.h" + +/*! + * \brief Dumps given zone to text (BIND-like) file. + * + * \param zone Zone to be saved. + * \param filename Name of file to be created. + * + * \retval KNOT_EOK on success. + * \retval KNOT_EBADARG if the specified file is not valid for writing. + */ +int zone_dump_text(knot_zone_contents_t *zone, const char *filename); + +#endif // _KNOT_ZONE_DUMP_TEXT_H_ + +/*! @} */ diff --git a/src/knot/zone/zone-dump.c b/src/knot/zone/zone-dump.c new file mode 100644 index 0000000..afc577d --- /dev/null +++ b/src/knot/zone/zone-dump.c @@ -0,0 +1,2301 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdint.h> +#include <assert.h> +#include <netinet/in.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +#include "libknot/common.h" +#include "knot/zone/zone-dump.h" +#include "libknot/libknot.h" +#include "knot/other/debug.h" +#include "common/skip-list.h" +#include "common/base32hex.h" +#include "common/crc.h" +#include "libknot/util/error.h" + +#define ZONECHECKS_VERBOSE + +/*! \note Contents of a dump file: + * MAGIC(knotxx) db_filename dname_table + * NUMBER_OF_NORMAL_NODES NUMBER_OF_NSEC3_NODES + * [normal_nodes] [nsec3_nodes] + * -------------------------------------------- + * dname_table is dumped as follows: + * NUMBER_OF_DNAMES [dname_wire_length dname_wire label_count dname_labels ID] + * node has following format: + * owner_id + * node_flags node_rrset_count [node_rrsets] + * rrset has following format: + * rrset_type rrset_class rrset_ttl rrset_rdata_count rrset_rrsig_count + * [rrset_rdata] [rrset_rrsigs] + * rdata can contain either dname ID, + * or raw data stored like this: data_len [data] + */ + +static const uint MAX_CNAME_CYCLE_DEPTH = 15; + +/*! + *\brief Internal error constants. General errors are added for convenience, + * so that code does not have to change if new errors are added. + */ +enum zonechecks_errors { + ZC_ERR_ALLOC = -40, + ZC_ERR_UNKNOWN, + + ZC_ERR_MISSING_SOA, + + ZC_ERR_GENERIC_GENERAL_ERROR, /* isn't there a better name? */ + + ZC_ERR_RRSIG_RDATA_TYPE_COVERED, + ZC_ERR_RRSIG_RDATA_TTL, + ZC_ERR_RRSIG_RDATA_LABELS, + ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER, + ZC_ERR_RRSIG_RDATA_SIGNED_WRONG, + ZC_ERR_RRSIG_NO_RRSIG, + ZC_ERR_RRSIG_SIGNED, + ZC_ERR_RRSIG_OWNER, + ZC_ERR_RRSIG_CLASS, + ZC_ERR_RRSIG_TTL, + ZC_ERR_RRSIG_NOT_ALL, + + ZC_ERR_RRSIG_GENERAL_ERROR, + + ZC_ERR_NO_NSEC, + ZC_ERR_NSEC_RDATA_BITMAP, + ZC_ERR_NSEC_RDATA_MULTIPLE, + ZC_ERR_NSEC_RDATA_CHAIN, + ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC, + + ZC_ERR_NSEC_GENERAL_ERROR, + + ZC_ERR_NSEC3_UNSECURED_DELEGATION, + ZC_ERR_NSEC3_NOT_FOUND, + ZC_ERR_NSEC3_UNSECURED_DELEGATION_OPT, + ZC_ERR_NSEC3_RDATA_TTL, + ZC_ERR_NSEC3_RDATA_CHAIN, + ZC_ERR_NSEC3_RDATA_BITMAP, + + ZC_ERR_NSEC3_GENERAL_ERROR, + + ZC_ERR_CNAME_CYCLE, + ZC_ERR_DNAME_CYCLE, + ZC_ERR_CNAME_EXTRA_RECORDS, + ZC_ERR_DNAME_EXTRA_RECORDS, + ZC_ERR_CNAME_EXTRA_RECORDS_DNSSEC, + ZC_ERR_CNAME_MULTIPLE, + ZC_ERR_DNAME_MULTIPLE, + + ZC_ERR_CNAME_GENERAL_ERROR, + + ZC_ERR_GLUE_NODE, + ZC_ERR_GLUE_RECORD, + + ZC_ERR_GLUE_GENERAL_ERROR, +}; + +static char *error_messages[(-ZC_ERR_ALLOC) + 1] = { + [-ZC_ERR_ALLOC] = "Memory allocation error!\n", + + [-ZC_ERR_MISSING_SOA] = "SOA record missing in zone!\n", + + [-ZC_ERR_RRSIG_RDATA_TYPE_COVERED] = + "RRSIG: Type covered rdata field is wrong!\n", + [-ZC_ERR_RRSIG_RDATA_TTL] = + "RRSIG: TTL rdata field is wrong!\n", + [-ZC_ERR_RRSIG_RDATA_LABELS] = + "RRSIG: Labels rdata field is wrong!\n", + [-ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER] = + "RRSIG: Signer name is different than in DNSKEY!\n", + [-ZC_ERR_RRSIG_RDATA_SIGNED_WRONG] = + "RRSIG: Key error!\n", + [-ZC_ERR_RRSIG_NO_RRSIG] = + "RRSIG: No RRSIG!\n", + [-ZC_ERR_RRSIG_SIGNED] = + "RRSIG: Signed RRSIG!\n", + [-ZC_ERR_RRSIG_OWNER] = + "RRSIG: Owner name rdata field is wrong!\n", + [-ZC_ERR_RRSIG_CLASS] = + "RRSIG: Class is wrong!\n", + [-ZC_ERR_RRSIG_TTL] = + "RRSIG: TTL is wrong!\n", + [-ZC_ERR_RRSIG_NOT_ALL] = + "RRSIG: Not all RRs are signed!\n", + + [-ZC_ERR_NO_NSEC] = + "NSEC: Missing NSEC record\n", + [-ZC_ERR_NSEC_RDATA_BITMAP] = + "NSEC: Wrong NSEC bitmap!\n", + [-ZC_ERR_NSEC_RDATA_MULTIPLE] = + "NSEC: Multiple NSEC records!\n", + [-ZC_ERR_NSEC_RDATA_CHAIN] = + "NSEC: NSEC chain is not coherent!\n", + [-ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC] = + "NSEC: NSEC chain is not cyclic!\n", + + [-ZC_ERR_NSEC3_UNSECURED_DELEGATION] = + "NSEC3: Zone contains unsecured delegation!\n", + [-ZC_ERR_NSEC3_NOT_FOUND] = + "NSEC3: Could not find previous NSEC3 record in the zone!\n", + [-ZC_ERR_NSEC3_UNSECURED_DELEGATION_OPT] = + "NSEC3: Unsecured delegation is not part " + "of the Opt-Out span!\n", + [-ZC_ERR_NSEC3_RDATA_TTL] = + "NSEC3: Original TTL rdata field is wrong!\n", + [-ZC_ERR_NSEC3_RDATA_CHAIN] = + "NSEC3: NSEC3 chain is not coherent!\n", + [-ZC_ERR_NSEC3_RDATA_BITMAP] = + "NSEC3: NSEC3 bitmap error!\n", + + [-ZC_ERR_CNAME_CYCLE] = + "CNAME: CNAME cycle!\n", + [-ZC_ERR_DNAME_CYCLE] = + "CNAME: DNAME cycle!\n", + [-ZC_ERR_CNAME_EXTRA_RECORDS] = + "CNAME: Node with CNAME record has other records!\n", + [-ZC_ERR_DNAME_EXTRA_RECORDS] = + "DNAME: Node with DNAME record has other records!\n", + [-ZC_ERR_CNAME_EXTRA_RECORDS_DNSSEC] = + "CNAME: Node with CNAME record has other " + "records than RRSIG and NSEC/NSEC3!\n", + [-ZC_ERR_CNAME_MULTIPLE] = "CNAME: Multiple CNAME records!\n", + [-ZC_ERR_DNAME_MULTIPLE] = "DNAME: Multiple DNAME records!\n", + + /* ^ + | Important errors (to be logged on first occurence and counted) */ + + + /* Below are errors of lesser importance, to be counted unless + specified otherwise */ + + [-ZC_ERR_GLUE_NODE] = + "GLUE: Node with Glue record missing!\n", + [-ZC_ERR_GLUE_RECORD] = + "GLUE: Record with Glue address missing\n", +}; + +/*! + * \brief Structure representing handle options. + */ +struct handler_options { + char log_cname; /*!< Log all CNAME related semantic errors. */ + char log_glue; /*!< Log all glue related semantic errors. */ + char log_rrsigs; /*!< Log all RRSIG related semantic errors. */ + char log_nsec; /*!< Log all NSEC related semantic errors. */ + char log_nsec3; /*!< Log all NSEC3 related semantic errors. */ +}; + +/*! + * \brief Structure for handling semantic errors. + */ +struct err_handler { + /* Consider moving error messages here */ + struct handler_options options; /*!< Handler options. */ + uint errors[(-ZC_ERR_ALLOC) + 1]; /*!< Array with error messages */ +}; + +typedef struct err_handler err_handler_t; + +/*! + * \brief Creates new semantic error handler. + * + * \param log_cname If true, log all CNAME related events. + * \param log_glue If true, log all Glue related events. + * \param log_rrsigs If true, log all RRSIGs related events. + * \param log_nsec If true, log all NSEC related events. + * \param log_nsec3 If true, log all NSEC3 related events. + * + * \return err_handler_t * Created error handler. + */ +static err_handler_t *handler_new(char log_cname, char log_glue, + char log_rrsigs, char log_nsec, + char log_nsec3) +{ + err_handler_t *handler = malloc(sizeof(err_handler_t)); + CHECK_ALLOC_LOG(handler, NULL); + + /* It should be initialized, but to be safe */ + memset(handler->errors, 0, sizeof(uint) * (-ZC_ERR_ALLOC + 1)); + + handler->options.log_cname = log_cname; + handler->options.log_glue = log_glue; + handler->options.log_rrsigs = log_rrsigs; + handler->options.log_nsec = log_nsec; + handler->options.log_nsec3 = log_nsec3; + + return handler; +} + +/*! + * \brief Prints error message with node information. + * + * \note If \a node is NULL, only total number of errors is printed. + * + * \param handler Error handler. + * \param node Node with semantic error in it. + * \param error Type of error. + */ +static void log_error_from_node(err_handler_t *handler, + const knot_node_t *node, + uint error) +{ + if (node != NULL) { + char *name = + knot_dname_to_str(knot_node_owner(node)); + fprintf(stderr, "Semantic warning in node: %s: ", name); + fprintf(stderr, "%s", error_messages[-error]); + free(name); + } else { + fprintf(stderr, "Total number of warnings is: %d for error: %s", + handler->errors[-error], + error_messages[-error]); + } +} + +/*! + * \brief Called when error has been encountered in node. Will either log error + * or print it, depending on handler's options. + * + * \param handler Error handler. + * \param node Node with semantic error in it. + * \param error Type of error. + * + * \retval KNOT_EOK on success. + * \retval ZC_ERR_UNKNOWN if unknown error. + * \retval ZC_ERR_ALLOC if memory error. + */ +static int err_handler_handle_error(err_handler_t *handler, + const knot_node_t *node, + uint error) +{ + assert(handler && node); + if ((error != 0) && + (error > ZC_ERR_GLUE_GENERAL_ERROR)) { + return ZC_ERR_UNKNOWN; + } + + if (error == ZC_ERR_ALLOC) { + ERR_ALLOC_FAILED; + return ZC_ERR_ALLOC; + } + + /* missing SOA can only occur once, so there + * needn't to be an option for it */ + + if ((error != 0) && + (error < ZC_ERR_GENERIC_GENERAL_ERROR)) { + /* The two errors before SOA were handled */ + log_error_from_node(handler, node, error); + + } else if ((error < ZC_ERR_RRSIG_GENERAL_ERROR) && + ((handler->errors[-error] == 0) || + (handler->options.log_rrsigs))) { + + log_error_from_node(handler, node, error); + + } else if ((error > ZC_ERR_RRSIG_GENERAL_ERROR) && + (error < ZC_ERR_NSEC_GENERAL_ERROR) && + ((handler->errors[-error] == 0) || + (handler->options.log_nsec))) { + + log_error_from_node(handler, node, error); + + } else if ((error > ZC_ERR_NSEC_GENERAL_ERROR) && + (error < ZC_ERR_NSEC3_GENERAL_ERROR) && + ((handler->errors[-error] == 0) || + (handler->options.log_nsec3))) { + + log_error_from_node(handler, node, error); + + } else if ((error > ZC_ERR_NSEC3_GENERAL_ERROR) && + (error < ZC_ERR_CNAME_GENERAL_ERROR) && + ((handler->errors[-error] == 0) || + (handler->options.log_cname))) { + + log_error_from_node(handler, node, error); + + } else if ((error > ZC_ERR_CNAME_GENERAL_ERROR) && + (error < ZC_ERR_GLUE_GENERAL_ERROR) && + handler->options.log_glue) { + + log_error_from_node(handler, node, error); + + } + + handler->errors[-error]++; + + return KNOT_EOK; +} + +/*! + * \brief This function prints all errors that occured in zone. + * + * \param handler Error handler containing found errors. + */ +static void err_handler_log_all(err_handler_t *handler) +{ + if (handler == NULL) { + return; + } + + for (int i = ZC_ERR_ALLOC; i < ZC_ERR_GLUE_GENERAL_ERROR; i++) { + if (handler->errors[-i] > 0) { + log_error_from_node(handler, NULL, i); + } + } +} + +/*! + * \brief Arguments to be used with tree traversal functions. Uses void pointers + * to be more versatile. + * + */ +struct arg { + void *arg1; /* FILE *f / zone */ + void *arg2; /* skip_list_t */ + void *arg3; /* zone */ + void *arg4; /* first node */ + void *arg5; /* last node */ + void *arg6; /* error handler */ + void *arg7; /* CRC */ +}; + +typedef struct arg arg_t; + +/*! + * \brief Semantic check - CNAME cycles. Uses constant value with maximum + * allowed CNAME chain depth. + * + * \param zone Zone containing the RRSet. + * \param rrset RRSet to be tested. + * + * \retval KNOT_EOK when there is no cycle. + * \retval ZC_ERR_CNAME_CYCLE when cycle is present. + */ +static int check_cname_cycles_in_zone(knot_zone_contents_t *zone, + const knot_rrset_t *rrset) +{ + const knot_rrset_t *next_rrset = rrset; + assert(rrset); + const knot_rdata_t *tmp_rdata = knot_rrset_rdata(next_rrset); + const knot_node_t *next_node = NULL; + + uint i = 0; + + assert(tmp_rdata); + + const knot_dname_t *next_dname = + knot_rdata_cname_name(tmp_rdata); + + assert(next_dname); + + while (i < MAX_CNAME_CYCLE_DEPTH && next_dname != NULL) { + next_node = knot_zone_contents_get_node(zone, next_dname); + if (next_node == NULL) { + next_node = + knot_zone_contents_get_nsec3_node(zone, next_dname); + } + + if (next_node != NULL) { + next_rrset = knot_node_rrset(next_node, + KNOT_RRTYPE_CNAME); + if (next_rrset != NULL) { + next_dname = + knot_rdata_cname_name(next_rrset->rdata); + } else { + next_node = NULL; + next_dname = NULL; + } + } else { + next_dname = NULL; + } + i++; + } + + /* even if the length is 0, i will be 1 */ + if (i >= MAX_CNAME_CYCLE_DEPTH) { + return ZC_ERR_CNAME_CYCLE; + } + + return KNOT_EOK; +} + +/*! + * \brief Return raw data from rdata item structure (without length). + * + * \param item Item to get rdata from. + * \return uint16_t * raw data without length. + */ +static inline uint16_t *rdata_item_data(const knot_rdata_item_t *item) +{ + return (uint16_t *)(item->raw_data + 1); +} + +/*! + * \brief Returns type covered field from RRSIG RRSet's rdata. + * + * \param rdata RRSIG rdata. + * \return uint16_t Type covered. + */ +uint16_t type_covered_from_rdata(const knot_rdata_t *rdata) +{ + return ntohs(*(uint16_t *) rdata_item_data(&(rdata->items[0]))); +} + +/*! + * \brief Check whether DNSKEY rdata are valid. + * + * \param rdata DNSKEY rdata to be checked. + * + * \retval KNOT_EOK when rdata are OK. + * \retval ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER when rdata are not OK. + */ +static int check_dnskey_rdata(const knot_rdata_t *rdata) +{ + /* check that Zone key bit it set - position 7 in net order */ + /*! \todo FIXME: endian? I swear I've fixed this already, it was 7 i guesss*/ + uint16_t mask = 1 << 8; //0b0000000100000000; + + uint16_t flags = + knot_wire_read_u16((uint8_t *)rdata_item_data + (knot_rdata_item(rdata, 0))); + + if (flags & mask) { + return KNOT_EOK; + } else { + /* This error does not exactly fit, but it's better + * than a new one */ + return ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER; + } +} + +/*! + * \brief Calculates keytag for RSA/SHA algorithm. + * + * \param key Key wireformat. + * \param keysize Wireformat size. + * + * \return uint16_t Calculated keytag. + */ +static uint16_t keytag_1(uint8_t *key, uint16_t keysize) +{ + uint16_t ac = 0; + if (keysize > 4) { + memmove(&ac, key + keysize - 3, 2); + } + + ac = ntohs(ac); + return ac; +} + +/*! + * \brief Calculates keytag from key wire. + * + * \param key Key wireformat. + * \param keysize Wireformat size. + * + * \return uint16_t Calculated keytag. + */ +static uint16_t keytag(uint8_t *key, uint16_t keysize ) +{ + uint32_t ac = 0; /* assumed to be 32 bits or larger */ + + /* algorithm RSA/SHA */ + if (key[3] == 1) { + return keytag_1(key, keysize); + } else { + for(int i = 0; i < keysize; i++) { + ac += (i & 1) ? key[i] : key[i] << 8; + } + + ac += (ac >> 16) & 0xFFFF; + return (uint16_t)ac & 0xFFFF; + } +} + +/*! + * \brief Returns size of raw data item. + * + * \param item Raw data item. + * + * \return uint16_t Size of raw data item. + */ +static inline uint16_t rdata_item_size(const knot_rdata_item_t *item) +{ + return item->raw_data[0]; +} + +/*! + * \brief Converts DNSKEY rdata to wireformat. + * + * \param rdata DNSKEY rdata to be converted. + * \param wire Created wire. + * \param size Size of created wire. + * + * \retval KNOT_EOK on success. + * \retval KNOT_ENOMEM on memory error. + */ +static int dnskey_to_wire(const knot_rdata_t *rdata, uint8_t **wire, + uint *size) +{ + assert(*wire == NULL); + /* flags + algorithm + protocol + keysize */ + *size = 2 + 1 + 1 + knot_rdata_item(rdata, 3)->raw_data[0]; + *wire = malloc(sizeof(uint8_t) * *size); + CHECK_ALLOC_LOG(*wire, KNOT_ENOMEM); + + /* copy the wire octet by octet */ + + /* TODO check if we really have that many items */ + if (rdata->count < 4) { + return KNOT_ERROR; + } + + (*wire)[0] = ((uint8_t *)(knot_rdata_item(rdata, 0)->raw_data))[2]; + (*wire)[1] = ((uint8_t *)(knot_rdata_item(rdata, 0)->raw_data))[3]; + + (*wire)[2] = ((uint8_t *)(knot_rdata_item(rdata, 1)->raw_data))[2]; + (*wire)[3] = ((uint8_t *)(knot_rdata_item(rdata, 2)->raw_data))[2]; + + memcpy(*wire + 4, knot_rdata_item(rdata, 3)->raw_data + 1, + knot_rdata_item(rdata, 3)->raw_data[0]); + + return KNOT_EOK; +} + +/*! + * \brief Semantic check - RRSIG rdata. + * + * \param rdata_rrsig RRSIG rdata to be checked. + * \param rrset RRSet containing the rdata. + * \param dnskey_rrset RRSet containing zone's DNSKEY + * + * \retval KNOT_EOK if rdata are OK. + * + * \return Appropriate error code if error was found. + */ +static int check_rrsig_rdata(const knot_rdata_t *rdata_rrsig, + const knot_rrset_t *rrset, + const knot_rrset_t *dnskey_rrset) +{ + if (rdata_rrsig == NULL) { + return ZC_ERR_RRSIG_NO_RRSIG; + } + + if (type_covered_from_rdata(rdata_rrsig) != + knot_rrset_type(rrset)) { + /* zoneparser would not let this happen + * but to be on the safe side + */ + return ZC_ERR_RRSIG_RDATA_TYPE_COVERED; + } + + /* label number at the 2nd index should be same as owner's */ + uint16_t *raw_data = + rdata_item_data(knot_rdata_item(rdata_rrsig, 2)); + + uint8_t labels_rdata = ((uint8_t *)raw_data)[0]; + + int tmp = knot_dname_label_count(knot_rrset_owner(rrset)) - + labels_rdata; + + if (tmp != 0) { + /* if name has wildcard, label must not be included */ + if (!knot_dname_is_wildcard(knot_rrset_owner(rrset))) { + return ZC_ERR_RRSIG_RDATA_LABELS; + } else { + if (abs(tmp) != 1) { + return ZC_ERR_RRSIG_RDATA_LABELS; + } + } + } + + /* check original TTL */ + uint32_t original_ttl = + knot_wire_read_u32((uint8_t *)rdata_item_data( + knot_rdata_item(rdata_rrsig, 3))); + + if (original_ttl != knot_rrset_ttl(rrset)) { + return ZC_ERR_RRSIG_RDATA_TTL; + } + + /* signer's name is same as in the zone apex */ + knot_dname_t *signer_name = + knot_rdata_item(rdata_rrsig, 7)->dname; + + /* dnskey is in the apex node */ + if (knot_dname_compare(signer_name, + knot_rrset_owner(dnskey_rrset)) != 0) { + return ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER; + } + + /* Compare algorithm, key tag and signer's name with DNSKEY rrset + * one of the records has to match. Signer name has been checked + * before */ + char match = 0; + const knot_rdata_t *tmp_dnskey_rdata = + knot_rrset_rdata(dnskey_rrset); + do { + uint8_t alg = + ((uint8_t *)(knot_rdata_item(rdata_rrsig, 1)->raw_data))[2]; + uint8_t alg_dnskey = + ((uint8_t *)(knot_rdata_item(tmp_dnskey_rdata, + 2)->raw_data))[2]; + + raw_data = rdata_item_data(knot_rdata_item(rdata_rrsig, 6)); + uint16_t key_tag_rrsig = + knot_wire_read_u16((uint8_t *)raw_data); + +/* raw_data = + rdata_item_data(knot_rdata_item( + tmp_dnskey_rdata, 3)); + + uint16_t raw_length = rdata_item_size(knot_rdata_item( + tmp_dnskey_rdata, 3)); */ + + uint8_t *dnskey_wire = NULL; + uint dnskey_wire_size = 0; + + int ret = 0; + if ((ret = dnskey_to_wire(tmp_dnskey_rdata, &dnskey_wire, + &dnskey_wire_size)) != KNOT_EOK) { + return ret; + } + + uint16_t key_tag_dnskey = + keytag(dnskey_wire, dnskey_wire_size); + + free(dnskey_wire); + + match = (alg == alg_dnskey) && + (key_tag_rrsig == key_tag_dnskey) && + !check_dnskey_rdata(tmp_dnskey_rdata); + + } while (!match && + ((tmp_dnskey_rdata = + knot_rrset_rdata_next(dnskey_rrset, + tmp_dnskey_rdata)) + != NULL)); + + if (!match) { + return ZC_ERR_RRSIG_RDATA_SIGNED_WRONG; + } + + return KNOT_EOK; +} + +/*! + * \brief Semantic check - RRSet's RRSIG. + * + * \param rrset RRSet containing RRSIG. + * \param dnskey_rrset + * \param nsec3 NSEC3 active. + * + * \retval KNOT_EOK on success. + * + * \return Appropriate error code if error was found. + */ +static int check_rrsig_in_rrset(const knot_rrset_t *rrset, + const knot_rrset_t *dnskey_rrset, + char nsec3) +{ + assert(dnskey_rrset && rrset); + + const knot_rrset_t *rrsigs = knot_rrset_rrsigs(rrset); + + if (rrsigs == NULL) { + return ZC_ERR_RRSIG_NO_RRSIG; + } + + /* signed rrsig - nonsense */ + if (knot_rrset_rrsigs(rrsigs) != NULL) { + return ZC_ERR_RRSIG_SIGNED; + } + + /* Different owner, class, ttl */ + + if (knot_dname_compare(knot_rrset_owner(rrset), + knot_rrset_owner(rrsigs)) != 0) { + return ZC_ERR_RRSIG_OWNER; + } + + if (knot_rrset_class(rrset) != knot_rrset_class(rrsigs)) { + return ZC_ERR_RRSIG_CLASS; + } + + if (knot_rrset_ttl(rrset) != knot_rrset_ttl(rrset)) { + return ZC_ERR_RRSIG_TTL; + } + + /* Check whether all rrsets have their rrsigs */ + const knot_rdata_t *tmp_rdata = knot_rrset_rdata(rrset); + const knot_rdata_t *tmp_rrsig_rdata = knot_rrset_rdata(rrsigs); + + assert(tmp_rdata); + assert(tmp_rrsig_rdata); + int ret = 0; + char all_signed = tmp_rdata && tmp_rrsig_rdata; + do { + if ((ret = check_rrsig_rdata(tmp_rrsig_rdata, + rrset, + dnskey_rrset)) != 0) { + return ret; + } + + all_signed = tmp_rdata && tmp_rrsig_rdata; + } while (((tmp_rdata = knot_rrset_rdata_next(rrset, tmp_rdata)) + != NULL) && + ((tmp_rrsig_rdata = + knot_rrset_rdata_next(rrsigs, tmp_rrsig_rdata)) + != NULL)); + + if (!all_signed) { + return ZC_ERR_RRSIG_NOT_ALL; + } + + return KNOT_EOK; +} + +/*! + * \brief Returns bit on index from array in network order. Taken from NSD. + * + * \param bits Array in network order. + * \param index Index to return from array. + * + * \return int Bit on given index. + */ +static int get_bit(uint8_t *bits, size_t index) +{ + /* + * The bits are counted from left to right, so bit #0 is the + * leftmost bit. + */ + return bits[index / 8] & (1 << (7 - index % 8)); +} + +/*! + * \brief Converts NSEC bitmap to array of integers. (Inspired by NSD code) + * + * \param item Item containing the bitmap. + * \param array Array to be created. + * \param count Count of items in array. + * + * \retval KNOT_OK on success. + * \retval KNOT_NOMEM on memory error. + */ +static int rdata_nsec_to_type_array(const knot_rdata_item_t *item, + uint16_t **array, + uint *count) +{ + assert(*array == NULL); + + uint8_t *data = (uint8_t *)rdata_item_data(item); + + int increment = 0; + *count = 0; + + for (int i = 0; i < rdata_item_size(item); i += increment) { + increment = 0; + uint8_t window = data[i]; + increment++; + + uint8_t bitmap_size = data[i + increment]; + increment++; + + uint8_t *bitmap = + malloc(sizeof(uint8_t) * (bitmap_size)); + if (bitmap == NULL) { + ERR_ALLOC_FAILED; + free(*array); + return KNOT_ENOMEM; + } + + memcpy(bitmap, data + i + increment, + bitmap_size); + + increment += bitmap_size; + + for (int j = 0; j < bitmap_size * 8; j++) { + if (get_bit(bitmap, j)) { + (*count)++; + void *tmp = realloc(*array, + sizeof(uint16_t) * + *count); + if (tmp == NULL) { + ERR_ALLOC_FAILED; + free(bitmap); + free(*array); + return KNOT_ENOMEM; + } + *array = tmp; + (*array)[*count - 1] = j + window * 256; + } + } + free(bitmap); + } + + return KNOT_EOK; +} + +/* should write error, not return values !!! */ + +/*! + * \brief Semantic check - check node's NSEC node. + * + * \param zone Current zone. + * \param node Node to be checked. + * \param handler Error handler + * + * \retval KNOT_EOK if no error was found. + * + * \return Appropriate error code if error was found. + */ +static int check_nsec3_node_in_zone(knot_zone_contents_t *zone, knot_node_t *node, + err_handler_t *handler) +{ + assert(handler); + const knot_node_t *nsec3_node = knot_node_nsec3_node(node, 0); + + if (nsec3_node == NULL) { + /* I know it's probably not what RFCs say, but it will have to + * do for now. */ + if (knot_node_rrset(node, KNOT_RRTYPE_DS) != NULL) { + err_handler_handle_error(handler, node, + ZC_ERR_NSEC3_UNSECURED_DELEGATION); + } else { + /* Unsecured delegation, check whether it is part of + * opt-out span */ + const knot_node_t *nsec3_previous; + const knot_node_t *nsec3_node; + + if (knot_zone_contents_find_nsec3_for_name(zone, + knot_node_owner(node), + &nsec3_node, + &nsec3_previous, 0) != 0) { + err_handler_handle_error(handler, node, + ZC_ERR_NSEC3_NOT_FOUND); + } + + if (nsec3_node == NULL) { + /* Probably should not ever happen */ + return ZC_ERR_NSEC3_NOT_FOUND; + } + + assert(nsec3_previous); + + const knot_rrset_t *previous_rrset = + knot_node_rrset(nsec3_previous, + KNOT_RRTYPE_NSEC3); + + assert(previous_rrset); + + /* check for Opt-Out flag */ + uint8_t flags = + ((uint8_t *)(previous_rrset->rdata->items[1].raw_data))[2]; + + uint8_t opt_out_mask = 1; + + if (!(flags & opt_out_mask)) { + err_handler_handle_error(handler, node, + ZC_ERR_NSEC3_UNSECURED_DELEGATION_OPT); + } + } + } + + const knot_rrset_t *nsec3_rrset = + knot_node_rrset(nsec3_node, KNOT_RRTYPE_NSEC3); + + assert(nsec3_rrset); + + const knot_rrset_t *soa_rrset = + knot_node_rrset(knot_zone_contents_apex(zone), + KNOT_RRTYPE_SOA); + assert(soa_rrset); + + uint32_t minimum_ttl = + knot_wire_read_u32((uint8_t *) + rdata_item_data( + knot_rdata_item( + knot_rrset_rdata( + knot_node_rrset( + knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA)), 6))); + /* Are those getters even worth this? + * Now I have no idea what this code does. */ + + if (knot_rrset_ttl(nsec3_rrset) != minimum_ttl) { + err_handler_handle_error(handler, node, + ZC_ERR_NSEC3_RDATA_TTL); + } + + /* check that next dname is in the zone */ + uint8_t *next_dname_decoded = NULL; + size_t real_size = 0; + + if (((real_size = base32hex_encode_alloc(((char *) + rdata_item_data(&(nsec3_rrset->rdata->items[4]))) + 1, + rdata_item_size(&nsec3_rrset->rdata->items[4]) - 1, + (char **)&next_dname_decoded)) <= 0) || + (next_dname_decoded == NULL)) { + fprintf(stderr, "Could not encode base32 string!\n"); + return KNOT_ERROR; + } + + /* This is why we allocate maximum length of decoded string + 1 */ + memmove(next_dname_decoded + 1, next_dname_decoded, real_size); + next_dname_decoded[0] = real_size; + + /* Local allocation, will be discarded. */ + knot_dname_t *next_dname = + knot_dname_new_from_wire(next_dname_decoded, + real_size + 1, NULL); + CHECK_ALLOC_LOG(next_dname, KNOT_ENOMEM); + + free(next_dname_decoded); + + if (knot_dname_cat(next_dname, + knot_node_owner(knot_zone_contents_apex(zone))) == NULL) { + fprintf(stderr, "Could not concatenate dnames!\n"); + return KNOT_ERROR; + + } + + if (knot_zone_contents_find_nsec3_node(zone, next_dname) == NULL) { + err_handler_handle_error(handler, node, + ZC_ERR_NSEC3_RDATA_CHAIN); + } + + /* Directly discard. */ + knot_dname_free(&next_dname); + + /* This is probably not sufficient, but again, it is covered in + * zone load time */ + + uint count; + uint16_t *array = NULL; + if (rdata_nsec_to_type_array( + knot_rdata_item( + knot_rrset_rdata(nsec3_rrset), 5), + &array, &count) != 0) { + err_handler_handle_error(handler, node, + ZC_ERR_ALLOC); + return KNOT_ERROR; + } + + uint16_t type = 0; + for (int j = 0; j < count; j++) { + /* test for each type's presence */ + type = array[j]; + if (type == KNOT_RRTYPE_RRSIG) { + continue; + } + if (knot_node_rrset(node, + type) == NULL) { + err_handler_handle_error(handler, node, + ZC_ERR_NSEC3_RDATA_BITMAP); + break; +/* char *name = + knot_dname_to_str( + log_zone_error("Node %s does " + "not contain RRSet of type %s " + "but NSEC bitmap says " + "it does!\n", name, + knot_rrtype_to_string(type)); + free(name); */ + } + } + + free(array); + + return KNOT_EOK; +} + +/*! + * \brief Run semantic checks for node without DNSSEC-related types. + * + * \param zone Current zone. + * \param node Node to be checked. + * \param do_checks Level of checks to be done. + * \param handler Error handler. + * + * \retval KNOT_EOK if no error was found. + * + * \return Appropriate error code if error was found. + */ +static int semantic_checks_plain(knot_zone_contents_t *zone, + knot_node_t *node, + char do_checks, + err_handler_t *handler) +{ + assert(handler); + const knot_rrset_t *cname_rrset = + knot_node_rrset(node, KNOT_RRTYPE_CNAME); + if (cname_rrset != NULL) { + if (check_cname_cycles_in_zone(zone, cname_rrset) != + KNOT_EOK) { + err_handler_handle_error(handler, node, + ZC_ERR_CNAME_CYCLE); + } + + /* No DNSSEC and yet there is more than one rrset in node */ + if (do_checks == 1 && + knot_node_rrset_count(node) != 1) { + err_handler_handle_error(handler, node, + ZC_ERR_CNAME_EXTRA_RECORDS); + } else if (knot_node_rrset_count(node) != 1) { + /* With DNSSEC node can contain RRSIG or NSEC */ + if (!(knot_node_rrset(node, KNOT_RRTYPE_RRSIG) || + knot_node_rrset(node, KNOT_RRTYPE_NSEC)) || + knot_node_rrset_count(node) > 3) { + err_handler_handle_error(handler, node, + ZC_ERR_CNAME_EXTRA_RECORDS_DNSSEC); + } + } + + if (knot_rrset_rdata(cname_rrset)->next != + knot_rrset_rdata(cname_rrset)) { + err_handler_handle_error(handler, node, + ZC_ERR_CNAME_MULTIPLE); + } + } + + const knot_rrset_t *dname_rrset = + knot_node_rrset(node, KNOT_RRTYPE_DNAME); + if (dname_rrset != NULL) { + if (check_cname_cycles_in_zone(zone, dname_rrset) != + KNOT_EOK) { + err_handler_handle_error(handler, node, + ZC_ERR_DNAME_CYCLE); + } + + if (knot_node_rrset(node, KNOT_RRTYPE_CNAME)) { + err_handler_handle_error(handler, node, + ZC_ERR_DNAME_EXTRA_RECORDS); + } + + if (knot_rrset_rdata(dname_rrset)->next != + knot_rrset_rdata(dname_rrset)) { + err_handler_handle_error(handler, node, + ZC_ERR_DNAME_MULTIPLE); + } + } + + /* check for glue records at zone cuts */ + if (knot_node_is_deleg_point(node)) { + const knot_rrset_t *ns_rrset = + knot_node_rrset(node, KNOT_RRTYPE_NS); + assert(ns_rrset); + //FIXME this should be an error as well ! (i guess) + + const knot_dname_t *ns_dname = + knot_rdata_get_item(knot_rrset_rdata + (ns_rrset), 0)->dname; + + assert(ns_dname); + + const knot_node_t *glue_node = + knot_zone_contents_find_node(zone, ns_dname); + + if (knot_dname_is_subdomain(ns_dname, + knot_node_owner(knot_zone_contents_apex(zone)))) { + if (glue_node == NULL) { + err_handler_handle_error(handler, node, + ZC_ERR_GLUE_NODE); + } else { + if ((knot_node_rrset(glue_node, + KNOT_RRTYPE_A) == NULL) && + (knot_node_rrset(glue_node, + KNOT_RRTYPE_AAAA) == NULL)) { + err_handler_handle_error(handler, node, + ZC_ERR_GLUE_RECORD); + } + } + } + } + return KNOT_EOK; +} + +/*! + * \brief Run semantic checks for node without DNSSEC-related types. + * + * \param zone Current zone. + * \param node Node to be checked. + * \param first_node First node in canonical order. + * \param last_node Last node in canonical order. + * \param handler Error handler. + * \param nsec3 NSEC3 used. + * + * \retval KNOT_EOK if no error was found. + * + * \return Appropriate error code if error was found. + */ +static int semantic_checks_dnssec(knot_zone_contents_t *zone, + knot_node_t *node, + knot_node_t *first_node, + knot_node_t **last_node, + err_handler_t *handler, + char nsec3) +{ + assert(handler); + assert(node); + char auth = !knot_node_is_non_auth(node); + char deleg = knot_node_is_deleg_point(node); + uint rrset_count = knot_node_rrset_count(node); + const knot_rrset_t **rrsets = knot_node_rrsets(node); + const knot_rrset_t *dnskey_rrset = + knot_node_rrset(knot_zone_contents_apex(zone), + KNOT_RRTYPE_DNSKEY); + + int ret = 0; + + for (int i = 0; i < rrset_count; i++) { + const knot_rrset_t *rrset = rrsets[i]; + if (auth && !deleg && + (ret = check_rrsig_in_rrset(rrset, dnskey_rrset, + nsec3)) != 0) { + /* CLEANUP */ +/* log_zone_error("RRSIG %d node %s\n", ret, + knot_dname_to_str(node->owner));*/ + + err_handler_handle_error(handler, node, ret); + } + + if (!nsec3 && auth) { + /* check for NSEC record */ + const knot_rrset_t *nsec_rrset = + knot_node_rrset(node, + KNOT_RRTYPE_NSEC); + + if (nsec_rrset == NULL) { + err_handler_handle_error(handler, node, + ZC_ERR_NO_NSEC); + /* CLEANUP */ +/* char *name = + knot_dname_to_str(node->owner); + log_zone_error("Missing NSEC in node: " + "%s\n", name); + free(name); + return; */ + } else { + + /* check NSEC/NSEC3 bitmap */ + + uint count; + + uint16_t *array = NULL; + + if (rdata_nsec_to_type_array( + knot_rdata_item( + knot_rrset_rdata(nsec_rrset), + 1), + &array, &count) != 0) { + err_handler_handle_error(handler, + NULL, + ZC_ERR_ALLOC); + return ZC_ERR_ALLOC; /* ... */ + /*return; */ + } + + uint16_t type = 0; + for (int j = 0; j < count; j++) { + /* test for each type's presence */ + type = array[j]; + if (type == KNOT_RRTYPE_RRSIG) { + continue; + } + if (knot_node_rrset(node, + type) == NULL) { + err_handler_handle_error( + handler, + node, + ZC_ERR_NSEC_RDATA_BITMAP); + /* CLEANUP */ + /* char *name = + knot_dname_to_str( + knot_node_owner(node)); + + log_zone_error("Node %s does " + "not contain RRSet of type %s " + "but NSEC bitmap says " + "it does!\n", name, + knot_rrtype_to_string(type)); + + free(name); */ + } + } + free(array); + } + + /* Test that only one record is in the + * NSEC RRSet */ + + if ((nsec_rrset != NULL) && + knot_rrset_rdata(nsec_rrset)->next != + knot_rrset_rdata(nsec_rrset)) { + err_handler_handle_error(handler, + node, + ZC_ERR_NSEC_RDATA_MULTIPLE); + /* CLEANUP */ +/* char *name = + knot_dname_to_str( + knot_node_owner(node)); + log_zone_error("Node %s contains more " + "than one NSEC " + "record!\n", name); + knot_rrset_dump(nsec_rrset, 0); + free(name); */ + } + + /* + * Test that NSEC chain is coherent. + * We have already checked that every + * authoritative node contains NSEC record + * so checking should only be matter of testing + * the next link in each node. + */ + + if (nsec_rrset != NULL) { + knot_dname_t *next_domain = + knot_rdata_item( + knot_rrset_rdata(nsec_rrset), + 0)->dname; + + assert(next_domain); + + if (knot_zone_contents_find_node(zone, next_domain) == + NULL) { + err_handler_handle_error(handler, + node, + ZC_ERR_NSEC_RDATA_CHAIN); + /* CLEANUP */ +/* log_zone_error("NSEC chain is not " + "coherent!\n"); */ + } + + if (knot_dname_compare(next_domain, + knot_node_owner(knot_zone_contents_apex(zone))) + == 0) { + /* saving the last node */ + *last_node = node; + } + + } + } else if (nsec3 && (auth || deleg)) { /* nsec3 */ + int ret = check_nsec3_node_in_zone(zone, node, + handler); + if (ret != KNOT_EOK) { + free(rrsets); + return ret; + } + } + } + free(rrsets); + + return KNOT_EOK; +} + +/*! + * \brief Function called by zone traversal function. Used to call + * knot_zone_save_enclosers. + * + * \param node Node to be searched. + * \param data Arguments. + */ +static void do_checks_in_tree(knot_node_t *node, void *data) +{ + assert(data != NULL); + arg_t *args = (arg_t *)data; + + knot_rrset_t **rrsets = knot_node_get_rrsets(node); + short count = knot_node_rrset_count(node); + + assert(count == 0 || rrsets != NULL); + + knot_zone_contents_t *zone = (knot_zone_contents_t *)args->arg1; + + assert(zone); + + +/* for (int i = 0; i < count; ++i) { + assert(rrsets[i] != NULL); + knot_zone_save_enclosers_rrset(rrsets[i], + zone, + (skip_list_t *)args->arg2); + } */ + + knot_node_t *first_node = (knot_node_t *)args->arg4; + knot_node_t **last_node = (knot_node_t **)args->arg5; + + err_handler_t *handler = (err_handler_t *)args->arg6; + + char do_checks = *((char *)(args->arg3)); + + if (do_checks) { + semantic_checks_plain(zone, node, do_checks, handler); + } + + if (do_checks > 1) { + semantic_checks_dnssec(zone, node, first_node, last_node, + handler, do_checks == 3); + } + + free(rrsets); +} + +/*! + * \brief Helper function - wraps its arguments into arg_t structure and + * calls function that does the actual work. + * + * \param zone Zone to be searched / checked + * \param list Skip list of closests enclosers. + * \param do_checks Level of semantic checks. + * \param handler Semantic error handler. + * \param last_node Last checked node, which is part of NSEC(3) chain. + */ +void zone_do_sem_checks(knot_zone_contents_t *zone, char do_checks, + err_handler_t *handler, + knot_node_t **last_node) +{ + if (!do_checks) { + return; + } + + arg_t arguments; + arguments.arg1 = zone; + arguments.arg3 = &do_checks; + arguments.arg4 = NULL; + arguments.arg5 = last_node; + arguments.arg6 = handler; + + knot_zone_contents_tree_apply_inorder(zone, + do_checks_in_tree, + (void *)&arguments); +} + +static inline int fwrite_to_file_crc(const void *src, + size_t size, size_t n, FILE *f, + crc_t *crc) +{ + size_t rc = fwrite(src, size, n, f); + if (rc != n) { + fprintf(stderr, "fwrite: invalid write %zu (expected %zu)\n", rc, + n); + } + /* \todo this seems to be wrong, if you fwrite less than n items, you probably should not continue */ + + if (size * n > 0) { + *crc = + crc_update(*crc, (unsigned char *)src, + size * n); + } + + /* \todo the rc return is certainly wrong as it is used in the caller function */ +// return rc == n; + return (int)rc; + +} + +static inline int fwrite_to_stream(const void *src, + size_t size, size_t n, + uint8_t **stream, + size_t *stream_size) +{ + /* Resize the stream */ + void *tmp = realloc(*stream, + (*stream_size + (size * n)) * sizeof(uint8_t)); + if (tmp != NULL) { + *stream = tmp; + memcpy(*stream + *stream_size, src, + size * n); + *stream_size += (size * n) * sizeof(uint8_t); + return KNOT_EOK; + } else { + free(*stream); + return KNOT_ENOMEM; + } + + return KNOT_EOK; +} + +static int fwrite_wrapper(const void *src, + size_t size, size_t n, FILE *fp, + uint8_t **stream, size_t *stream_size, crc_t *crc) +{ + if (fp == NULL) { + assert(stream && stream_size); + assert(crc == NULL); + return fwrite_to_stream(src, size, n, stream, stream_size); + } else { + assert(stream == NULL && stream_size == NULL); + return fwrite_to_file_crc(src, size, n, fp, crc); + } +} + +/*! + * \brief Dumps dname labels in binary format to given file. + * + * \param dname Dname whose labels are to be dumped. + * \param f Output file. + */ +static void knot_labels_dump_binary(const knot_dname_t *dname, FILE *f, + uint8_t **stream, size_t *stream_size, + crc_t *crc) +{ + dbg_zdump("label count: %d\n", dname->label_count); + uint16_t label_count = dname->label_count; + /* \todo check the return value */ + fwrite_wrapper(&label_count, sizeof(label_count), 1, f, stream, + stream_size, crc); + /* \todo check the return value */ + fwrite_wrapper(dname->labels, sizeof(uint8_t), dname->label_count, f, + stream, stream_size, crc); +} + +/*! + * \brief Dumps dname in binary format to given file. + * + * \param dname Dname to be dumped. + * \param f Output file. + */ +static void knot_dname_dump_binary(const knot_dname_t *dname, FILE *f, + uint8_t **stream, size_t *stream_size, + crc_t *crc) +{ + uint32_t dname_size = dname->size; + /* \todo check the return value */ + fwrite_wrapper(&dname_size, sizeof(dname_size), 1, f, stream, + stream_size, crc); + /* \todo check the return value */ + fwrite_wrapper(dname->name, sizeof(uint8_t), dname->size, f, + stream, stream_size, crc); + dbg_zdump("dname size: %d\n", dname->size); + knot_labels_dump_binary(dname, f, stream, stream_size, crc); +} + +/*!< \todo some global variable indicating error! */ +static void dump_dname_with_id(const knot_dname_t *dname, FILE *f, + uint8_t **stream, size_t *stream_size, + crc_t *crc) +{ + uint32_t id = dname->id; + /* \todo check the return value */ + fwrite_wrapper(&id, sizeof(id), 1, f, stream, stream_size, crc); + knot_dname_dump_binary(dname, f, stream, stream_size, crc); +/* if (!fwrite_wrapper_safe(&dname->id, sizeof(dname->id), 1, f)) { + return KNOT_ERROR; + } */ +} + +/*! + * \brief Dumps given rdata in binary format to given file. + * + * \param rdata Rdata to be dumped. + * \param type Type of rdata. + * \param data Arguments to be propagated. + */ +static void knot_rdata_dump_binary(knot_rdata_t *rdata, + uint32_t type, void *data, int use_ids, + uint8_t **stream, size_t *stream_size, + crc_t *crc) +{ + FILE *f = (FILE *)((arg_t *)data)->arg1; + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(type); + assert(desc != NULL); + + dbg_zdump("Dumping type: %d\n", type); + + if (desc->fixed_items) { + assert(desc->length == rdata->count); + } + + /* Write rdata count. */ + /* \todo check the return value */ + fwrite_wrapper(&(rdata->count), + sizeof(rdata->count), 1, f, stream, stream_size, crc); + + for (int i = 0; i < rdata->count; i++) { + if (&(rdata->items[i]) == NULL) { + dbg_zdump("Item n. %d is not set!\n", i); + continue; + } + dbg_zdump("Item n: %d\n", i); + if (desc->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME ) { + /* some temp variables - this is way too long */ + assert(rdata->items[i].dname != NULL); + knot_dname_t *wildcard = NULL; + + if (rdata->items[i].dname->node != NULL && + rdata->items[i].dname->node->owner != + rdata->items[i].dname) { + wildcard = rdata->items[i].dname->node->owner; + } + + if (use_ids) { + /* Write ID. */ + dbg_zload("%s \n", + knot_dname_to_str(rdata->items[i].dname)); + assert(rdata->items[i].dname->id != 0); + + uint32_t id = rdata->items[i].dname->id; + /* \todo check the return value */ + fwrite_wrapper(&id, + sizeof(id), 1, f, stream, stream_size, + crc); + } else { +// assert(rdata->items[i].dname->id != 0); + dump_dname_with_id(rdata->items[i].dname, + f, stream, + stream_size, crc); + } + + /* Write in the zone bit */ + if (rdata->items[i].dname->node != NULL && !wildcard) { + /* \todo check the return value */ + fwrite_wrapper((uint8_t *)"\1", + sizeof(uint8_t), 1, f, stream, + stream_size, crc); + } else { + /* \todo check the return value */ + fwrite_wrapper((uint8_t *)"\0", sizeof(uint8_t), + 1, f, stream, stream_size, crc); + } + + if (use_ids && wildcard) { + /* \todo check the return value */ + fwrite_wrapper((uint8_t *)"\1", + sizeof(uint8_t), 1, f, stream, + stream_size, crc); + uint32_t wildcard_id = wildcard->id; + /* \todo check the return value */ + fwrite_wrapper(&wildcard_id, + sizeof(wildcard_id), 1, f, stream, + stream_size, crc); + } else { + /* \todo check the return value */ + fwrite_wrapper((uint8_t *)"\0", sizeof(uint8_t), + 1, f, stream, + stream_size, crc); + } + + } else { + dbg_zdump("Writing raw data. Item nr.: %d\n", + i); + assert(rdata->items[i].raw_data != NULL); + /* \todo check the return value */ + fwrite_wrapper(rdata->items[i].raw_data, + sizeof(uint8_t), + rdata->items[i].raw_data[0] + 2, f, + stream, stream_size, crc); + + dbg_zdump("Written %d long raw data\n", + rdata->items[i].raw_data[0]); + } + } +} + +/*! + * \brief Dumps RRSIG in binary format to given file. + * + * \param rrsig RRSIG to be dumped. + * \param data Arguments to be propagated. + */ +static void knot_rrsig_set_dump_binary(knot_rrset_t *rrsig, arg_t *data, + int use_ids, + uint8_t **stream, size_t *stream_size, + crc_t *crc) +{ + dbg_zdump("Dumping rrset \\w owner: %s\n", + knot_dname_to_str(rrsig->owner)); + assert(rrsig->type == KNOT_RRTYPE_RRSIG); + assert(rrsig->rdata); + FILE *f = (FILE *)((arg_t *)data)->arg1; + /* \todo check the return value */ + fwrite_wrapper(&rrsig->type, sizeof(rrsig->type), 1, f, + stream, stream_size, crc); + fwrite_wrapper(&rrsig->rclass, sizeof(rrsig->rclass), 1, f, + stream, stream_size, crc); + fwrite_wrapper(&rrsig->ttl, sizeof(rrsig->ttl), 1, f, + stream, stream_size, crc); + + uint32_t rdata_count = 1; + /* Calculate rrset rdata count. */ + knot_rdata_t *tmp_rdata = rrsig->rdata; + while(tmp_rdata->next != rrsig->rdata) { + tmp_rdata = tmp_rdata->next; + rdata_count++; + } + + fwrite_wrapper(&rdata_count, sizeof(rdata_count), 1, f, + stream, stream_size, crc); + + tmp_rdata = rrsig->rdata; + while (tmp_rdata->next != rrsig->rdata) { + knot_rdata_dump_binary(tmp_rdata, KNOT_RRTYPE_RRSIG, data, + use_ids, stream, stream_size, crc); + tmp_rdata = tmp_rdata->next; + } + knot_rdata_dump_binary(tmp_rdata, KNOT_RRTYPE_RRSIG, data, use_ids, + stream, stream_size, crc); +} + +/*! + * \brief Dumps RRSet in binary format to given file. + * + * \param rrset RRSSet to be dumped. + * \param data Arguments to be propagated. + */ +static void knot_rrset_dump_binary(const knot_rrset_t *rrset, void *data, + int use_ids, + uint8_t **stream, size_t *stream_size, + crc_t *crc) +{ + FILE *f = (FILE *)((arg_t *)data)->arg1; + + if (!use_ids) { + dump_dname_with_id(rrset->owner, f, stream, stream_size, crc); + } + + /* \todo check the return value */ + fwrite_wrapper(&rrset->type, sizeof(rrset->type), 1, f, + stream, stream_size, crc); + fwrite_wrapper(&rrset->rclass, sizeof(rrset->rclass), 1, f, + stream, stream_size, crc); + fwrite_wrapper(&rrset->ttl, sizeof(rrset->ttl), 1, f, + stream, stream_size, crc); + + uint32_t rdata_count = 1; + uint8_t has_rrsig = rrset->rrsigs != NULL; + + /* Calculate rrset rdata count. */ + knot_rdata_t *tmp_rdata = rrset->rdata; + while(tmp_rdata->next != rrset->rdata) { + tmp_rdata = tmp_rdata->next; + rdata_count++; + } + + fwrite_wrapper(&rdata_count, sizeof(rdata_count), 1, f, + stream, stream_size, crc); + fwrite_wrapper(&has_rrsig, sizeof(has_rrsig), 1, f, + stream, stream_size, crc); + + tmp_rdata = rrset->rdata; + + while (tmp_rdata->next != rrset->rdata) { + knot_rdata_dump_binary(tmp_rdata, rrset->type, data, use_ids, + stream, stream_size, crc); + tmp_rdata = tmp_rdata->next; + } + knot_rdata_dump_binary(tmp_rdata, rrset->type, data, use_ids, + stream, stream_size, crc); + + /* This is now obsolete, although I'd rather not use recursion - that + * would probably not work */ + + if (rrset->rrsigs != NULL) { + knot_rrsig_set_dump_binary(rrset->rrsigs, data, use_ids, + stream, stream_size, crc); + } +} + +/*! + * \brief Dumps all RRSets in node to file in binary format. + * + * \param node Node to dumped. + * \param data Arguments to be propagated. + */ +static void knot_node_dump_binary(knot_node_t *node, void *data, + uint8_t **stream, size_t *stream_size, + crc_t *crc) +{ + arg_t *args = (arg_t *)data; + FILE *f = (FILE *)args->arg1; + +// node_count++; + /* first write dname */ + assert(node->owner != NULL); + + /* Write owner ID. */ + dbg_zdump("Dumping node owned by %s\n", + knot_dname_to_str(node->owner)); + assert(node->owner->id != 0); + uint32_t owner_id = node->owner->id; + /* \todo check the return value */ + fwrite_wrapper(&owner_id, sizeof(owner_id), 1, f, stream, stream_size, + crc); + + if (knot_node_parent(node, 0) != NULL) { + uint32_t parent_id = knot_dname_id( + knot_node_owner(knot_node_parent(node, 0))); + fwrite_wrapper(&parent_id, sizeof(parent_id), 1, f, + stream, stream_size, crc); + } else { + uint32_t parent_id = 0; + fwrite_wrapper(&parent_id, sizeof(parent_id), 1, f, + stream, stream_size, crc); + } + + fwrite_wrapper(&(node->flags), sizeof(node->flags), 1, f, + stream, stream_size, crc); + + dbg_zdump("Written flags: %u\n", node->flags); + + if (knot_node_nsec3_node(node, 0) != NULL) { + uint32_t nsec3_id = + knot_node_owner(knot_node_nsec3_node(node, 0))->id; + fwrite_wrapper(&nsec3_id, sizeof(nsec3_id), 1, f, + stream, stream_size, crc); + dbg_zdump("Written nsec3 node id: %u\n", + knot_node_owner(knot_node_nsec3_node(node, 0))->id); + } else { + uint32_t nsec3_id = 0; + fwrite_wrapper(&nsec3_id, sizeof(nsec3_id), 1, f, + stream, stream_size, crc); + } + + /* Now we need (or do we?) count of rrsets to be read + * but that number is yet unknown */ + + uint16_t rrset_count = node->rrset_count; + fwrite_wrapper(&rrset_count, sizeof(rrset_count), 1, f, + stream, stream_size, crc); + + /* CLEANUP */ +// const skip_node_t *skip_node = skip_first(node->rrsets); + + const knot_rrset_t **node_rrsets = knot_node_rrsets(node); + for (int i = 0; i < rrset_count; i++) + { + knot_rrset_dump_binary(node_rrsets[i], data, 1, + stream, stream_size, crc); + } + + /* CLEANUP */ +// if (skip_node == NULL) { +// /* we can return, count is set to 0 */ +// return; +// } + +// knot_rrset_t *tmp; + +// do { +// tmp = (knot_rrset_t *)skip_node->value; +// knot_rrset_dump_binary(tmp, data, 1); +// } while ((skip_node = skip_next(skip_node)) != NULL); + + free(node_rrsets); + + dbg_zdump("Position after all rrsets: %ld\n", ftell(f)); + dbg_zdump("Writing here: %ld\n", ftell(f)); + dbg_zdump("Function ends with: %ld\n\n", ftell(f)); +} + +/*! + * \brief Checks if zone uses DNSSEC and/or NSEC3 + * + * \param zone Zone to be checked. + * + * \retval 0 if zone is not secured. + * \retval 2 if zone uses NSEC3 + * \retval 1 if zone uses NSEC + */ +static int zone_is_secure(knot_zone_contents_t *zone) +{ + if (knot_node_rrset(knot_zone_contents_apex(zone), + KNOT_RRTYPE_DNSKEY) == NULL) { + return 0; + } else { + if (knot_node_rrset(knot_zone_contents_apex(zone), + KNOT_RRTYPE_NSEC3PARAM) != NULL) { + return 2; + } else { + return 1; + } + } +} + +/*! + * \brief Checks if last node in NSEC/NSEC3 chain points to first node in the + * chain and prints possible errors. + * + * \param handler Semantic error handler. + * \param zone Current zone. + * \param last_node Last node in NSEC/NSEC3 chain. + * \param do_checks Level of semantic checks. + */ +static void log_cyclic_errors_in_zone(err_handler_t *handler, + knot_zone_contents_t *zone, + knot_node_t *last_node, + const knot_node_t *first_nsec3_node, + const knot_node_t *last_nsec3_node, + char do_checks) +{ + if (do_checks == 3) { + /* Each NSEC3 node should only contain one RRSET. */ + assert(last_nsec3_node && first_nsec3_node); + const knot_rrset_t *nsec3_rrset = + knot_node_rrset(last_nsec3_node, + KNOT_RRTYPE_NSEC3); + if (nsec3_rrset == NULL) { + err_handler_handle_error(handler, last_nsec3_node, + ZC_ERR_NSEC3_RDATA_CHAIN); + return; + } + + /* check that next dname is in the zone */ + uint8_t *next_dname_decoded = NULL; + size_t real_size = 0; + + if (((real_size = base32hex_encode_alloc(((char *) + rdata_item_data(&(nsec3_rrset->rdata->items[4]))) + 1, + rdata_item_size(&nsec3_rrset->rdata->items[4]) - 1, + (char **)&next_dname_decoded)) <= 0) || + (next_dname_decoded == NULL)) { + fprintf(stderr, "Could not encode base32 string!\n"); + err_handler_handle_error(handler, last_nsec3_node, + ZC_ERR_NSEC3_RDATA_CHAIN); + return; + } + + /* This is why allocate maximum length of decoded string + 1 */ + memmove(next_dname_decoded + 1, next_dname_decoded, real_size); + next_dname_decoded[0] = real_size; + + /* Local allocation, will be discarded. */ + knot_dname_t *next_dname = + knot_dname_new_from_wire(next_dname_decoded, + real_size + 1, NULL); + if (next_dname == NULL) { + fprintf(stderr, "Could not allocate dname!\n"); + err_handler_handle_error(handler, last_nsec3_node, + ZC_ERR_ALLOC); + return; + } + + free(next_dname_decoded); + + /*! \todo Free result and dname! */ + if (knot_dname_cat(next_dname, + knot_node_owner(knot_zone_contents_apex(zone))) == + NULL) { + fprintf(stderr, "Could not concatenate dnames!\n"); + err_handler_handle_error(handler, last_nsec3_node, + ZC_ERR_NSEC3_RDATA_CHAIN); + return; + } + + /* Check it points somewhere first. */ + if (knot_zone_contents_find_nsec3_node(zone, next_dname) == NULL) { + err_handler_handle_error(handler, last_nsec3_node, + ZC_ERR_NSEC3_RDATA_CHAIN); + } + + /* Compare with the actual first NSEC3 node. */ + if (knot_dname_compare(first_nsec3_node->owner, + next_dname) != 0) { + err_handler_handle_error(handler, last_nsec3_node, + ZC_ERR_NSEC3_RDATA_CHAIN); + } + + /* Directly discard. */ + knot_dname_free(&next_dname); + + } else if (do_checks == 2 ) { + if (last_node == NULL) { + err_handler_handle_error(handler, last_node, + ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC); + return; + } else { + const knot_rrset_t *nsec_rrset = + knot_node_rrset(last_node, + KNOT_RRTYPE_NSEC); + + if (nsec_rrset == NULL) { + err_handler_handle_error(handler, last_node, + ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC); + return; + } + + const knot_dname_t *next_dname = + knot_rdata_item( + knot_rrset_rdata(nsec_rrset), 0)->dname; + assert(next_dname); + + const knot_dname_t *apex_dname = + knot_node_owner(knot_zone_contents_apex(zone)); + assert(apex_dname); + + if (knot_dname_compare(next_dname, apex_dname) !=0) { + err_handler_handle_error(handler, last_node, + ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC); + } + } + } +} + +/*! + * \brief Safe wrapper around fwrite. + * + * \param dst Destination pointer. + * \param size Size of element to be written. + * \param n Number of elements to be written. + * \param fp File to write to. + * + * \retval > 0 if succesfull. + * \retval 0 if failed. + */ +//static inline int fwrite_wrapper_safe(const void *src, +// size_t size, size_t n, FILE *fp) +//{ +// int rc = fwrite_wrapper(src, size, n, fp); +// if (rc != n) { +// fprintf(stderr, "fwrite_wrapper: invalid write %d (expected %zu)\n", rc, +// n); +// } + +// return rc == n; +//} + +static void dump_dname_from_tree(knot_dname_t *dname, + void *data) +{ + arg_t *arg = (arg_t *)data; + FILE *f = (FILE *)arg->arg1; + crc_t *crc = (crc_t*)arg->arg2; + dump_dname_with_id(dname, f, NULL, NULL, crc); +} + +static int knot_dump_dname_table(const knot_dname_table_t *dname_table, + FILE *f, crc_t *crc) +{ + arg_t arg; + arg.arg1 = f; + arg.arg2 = crc; + /* Go through the tree and dump each dname along with its ID. */ + knot_dname_table_tree_inorder_apply(dname_table, + dump_dname_from_tree, &arg); + /* CLEANUP */ +// TREE_FORWARD_APPLY(dname_table->tree, dname_table_node, avl, +// dump_dname_from_tree, (void *)f); + + return KNOT_EOK; +} + +static void save_node_from_tree(knot_node_t *node, void *data) +{ + arg_t *arg = (arg_t *)data; + /* Increment node count */ + (*((uint32_t *)(arg->arg1)))++; + /* Save the first node only */ + if (arg->arg2 == NULL) { + arg->arg2 = (void *)node; + } + arg->arg3 = (void *)node; +} + +static void dump_node_to_file(knot_node_t *node, void *data) +{ + arg_t *arg = (arg_t *)data; + knot_node_dump_binary(node, data, NULL, NULL, (crc_t *)arg->arg7); +} + +int knot_zdump_binary(knot_zone_contents_t *zone, const char *filename, + int do_checks, const char *sfilename) +{ + /* Open .new file. */ + char new_path[strlen(filename) + strlen(".new") + 1]; + memcpy(new_path, filename, strlen(filename) + 1); + strcat(new_path, ".new"); + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + int fd = open(new_path, O_WRONLY | O_CREAT | O_TRUNC, mode); + if (fd == -1) { + return KNOT_EBADARG; + } + + FILE *f = fdopen(fd, "wb"); + assert(f); + + /* CLEANUP */ +// skip_list_t *encloser_list = skip_create_list(compare_pointers); + arg_t arguments; + /* Memory to be derefenced in the save_node_from_tree function. */ + uint32_t node_count = 0; + arguments.arg1 = &node_count; + arguments.arg2 = NULL; + + /* Count number of normal nodes. */ + knot_zone_contents_tree_apply_inorder(zone, save_node_from_tree, &arguments); + /* arg1 is now count of normal nodes */ + uint32_t normal_node_count = *((uint32_t *)arguments.arg1); + + node_count = 0; + arguments.arg1 = &node_count; + arguments.arg2 = NULL; + + /* Count number of NSEC3 nodes. */ + knot_zone_contents_nsec3_apply_inorder(zone, save_node_from_tree, &arguments); + uint32_t nsec3_node_count = *((uint32_t *)arguments.arg1); + /* arg2 is the first NSEC3 node - used in sem checks. */ + /* arg3 is the last NSEC3 node - used in sem checks. */ + const knot_node_t *first_nsec3_node = (knot_node_t *)arguments.arg2; + const knot_node_t *last_nsec3_node = (knot_node_t *)arguments.arg3; + + if (do_checks && zone_is_secure(zone)) { + do_checks += zone_is_secure(zone); + } + + err_handler_t *handler = NULL; + + if (do_checks) { + handler = handler_new(1, 0, 1, 1, 1); + if (handler == NULL) { + /* disable checks and we can continue */ + do_checks = 0; + } else { /* Do check for SOA right now */ + if (knot_node_rrset(knot_zone_contents_apex(zone), + KNOT_RRTYPE_SOA) == NULL) { + err_handler_handle_error(handler, + knot_zone_contents_apex(zone), + ZC_ERR_MISSING_SOA); + } + } + + knot_node_t *last_node = NULL; + + zone_do_sem_checks(zone, do_checks, handler, &last_node); + log_cyclic_errors_in_zone(handler, zone, last_node, + first_nsec3_node, last_nsec3_node, + do_checks); + err_handler_log_all(handler); + free(handler); + } + + crc_t crc = crc_init(); + + /* Start writing header - magic bytes. */ + static const uint8_t MAGIC[MAGIC_LENGTH] = MAGIC_BYTES; + fwrite_wrapper(&MAGIC, sizeof(uint8_t), MAGIC_LENGTH, f, NULL, NULL, + &crc); + + /* Write source file length. */ + uint32_t sflen = 0; + if (sfilename) { + sflen = strlen(sfilename) + 1; + } + fwrite_wrapper(&sflen, sizeof(uint32_t), 1, f, NULL, NULL, &crc); + + /* Write source file. */ + fwrite_wrapper(sfilename, sflen, 1, f, NULL, NULL, &crc); + + /* Notice: End of header, + */ + + /* Start writing compiled data. */ + fwrite_wrapper(&normal_node_count, sizeof(normal_node_count), 1, f, + NULL, NULL, &crc); + fwrite_wrapper(&nsec3_node_count, sizeof(nsec3_node_count), 1, f, + NULL, NULL, &crc); + uint32_t auth_node_count = zone->node_count; + fwrite_wrapper(&auth_node_count, + sizeof(auth_node_count), 1, f, NULL, NULL, &crc); + + /* Write total number of dnames */ + assert(zone->dname_table); + uint32_t total_dnames = zone->dname_table->id_counter; + fwrite_wrapper(&total_dnames, + sizeof(total_dnames), 1, f, NULL, NULL, &crc); + + /* Write dname table. */ + if (knot_dump_dname_table(zone->dname_table, f, &crc) + != KNOT_EOK) { + return KNOT_ERROR; + } + + arguments.arg1 = (void *)f; + arguments.arg3 = zone; + arguments.arg7 = &crc; + + /* TODO is there a way how to stop the traversal upon error? */ + knot_zone_contents_tree_apply_inorder(zone, dump_node_to_file, + (void *)&arguments); + + knot_zone_contents_nsec3_apply_inorder(zone, dump_node_to_file, + (void *)&arguments); + fclose(f); + + crc = crc_finalize(crc); + /* Write CRC to separate .crc file. */ + /*!< \todo There is now function doing this. */ + char *crc_path = + malloc(sizeof(char) * (strlen(filename) + strlen(".crc") + 1)); + if (unlikely(!crc_path)) { + close(fd); + return KNOT_ENOMEM; + } + memset(crc_path, 0, + sizeof(char) * (strlen(filename) + strlen(".crc") + 1)); + memcpy(crc_path, filename, sizeof(char) * strlen(filename)); + + crc_path = strcat(crc_path, ".crc"); + FILE *f_crc = fopen(crc_path, "w"); + if (unlikely(!f_crc)) { + dbg_zload("knot_zload_open: failed to open '%s'\n", + crc_path); + close(fd); + free(crc_path); + return ENOENT; + } + free(crc_path); + + fprintf(f_crc, "%lu\n", (unsigned long)crc); + fclose(f_crc); + + close(fd); + mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + fd = open(filename, O_WRONLY | O_CREAT, mode); + if (fd == -1) { + fprintf(stderr, "%s\n", strerror(errno)); + fprintf(stderr, "Could not open destination file! Use '%s' " + "file instead.\n", new_path); + return KNOT_ERROR; + } + + /* Try to obtain exclusive lock for originally given file. */ + if (fcntl(fd, F_SETLK, knot_file_lock(F_WRLCK, SEEK_SET)) == -1) { + fprintf(stderr, "Could not lock destination file for write! " + "Use '%s' file instead.\n", new_path); + close(fd); + return KNOT_ERROR; + } + + /* Move .new file to original file. */ + if (rename(new_path, filename) != 0) { + fprintf(stderr, "Could not move to originally given file! " + "Use '%s' file instead.\n", new_path); + close(fd); + return KNOT_ERROR; + } + + /* Release the lock. */ + if (fcntl(fd, F_SETLK, knot_file_lock(F_UNLCK, SEEK_SET)) == -1) { + fprintf(stderr, "Could not unlock destination file!\n"); + return KNOT_ERROR; + } + + close(fd); + + return KNOT_EOK; +} + +int knot_zdump_rrset_serialize(const knot_rrset_t *rrset, uint8_t **stream, + size_t *size) +{ + if (stream == NULL || *stream != NULL || rrset == NULL || + size == NULL) { + return KNOT_EBADARG; + } + + *size = 0; + arg_t arguments; + memset(&arguments, 0, sizeof(arg_t)); + + knot_rrset_dump_binary(rrset, &arguments, 0, stream, size, NULL); + + return KNOT_EOK; +} + +static char *knot_zdump_crc_file(const char* filename) +{ + char *crc_path = + malloc(sizeof(char) * (strlen(filename) + + strlen(".crc") + 1)); + CHECK_ALLOC_LOG(crc_path, NULL); + memset(crc_path, 0, + sizeof(char) * (strlen(filename) + + strlen(".crc") + 1)); + memcpy(crc_path, filename, + sizeof(char) * strlen(filename)); + crc_path = strcat(crc_path, ".crc"); + return crc_path; +} + +int knot_zdump_dump_and_swap(knot_zone_contents_t *zone, + const char *temp_zonedb, + const char *destination_zonedb, + const char *sfilename) +{ + int rc = knot_zdump_binary(zone, temp_zonedb, 0, sfilename); + + if (rc != KNOT_EOK) { + dbg_zdump("Failed to save the zone to binary zone db %s." + "\n", temp_zonedb); + return KNOT_ERROR; + } + + /*! \todo this would also need locking as well. */ + rc = remove(destination_zonedb); + if (rc == 0 || (rc != 0 && errno == ENOENT)) { + + /* Delete old CRC file. */ + char *destination_zonedb_crc = + knot_zdump_crc_file(destination_zonedb); + if (destination_zonedb_crc == NULL) { + return KNOT_ENOMEM; + } + remove(destination_zonedb_crc); + + /* Move CRC file. */ + char *temp_zonedb_crc = + knot_zdump_crc_file(temp_zonedb); + if (temp_zonedb_crc == NULL) { + return KNOT_ENOMEM; + } + + if (rename(temp_zonedb_crc, destination_zonedb_crc) != 0) { + dbg_zdump("Failed to replace old zonedb CRC %s " + "with new CRC zone file %s.\n", + destination_zonedb_crc, + temp_zonedb_crc); + return KNOT_ERROR; + } + free(temp_zonedb_crc); + free(destination_zonedb_crc); + + /* Rename zonedb. */ + if (rename(temp_zonedb, destination_zonedb) != 0) { + dbg_zdump("Failed to replace old zonedb %s " + "with new zone file %s.\n", + temp_zonedb, + destination_zonedb); + /*! \todo with proper locking, this shouldn't happen, + * revise it later on. + */ + return KNOT_ERROR; + } + } else { + dbg_zdump("Failed to replace old zonedb '%s'', %s.\n", + destination_zonedb, strerror(errno)); + return KNOT_ERROR; + } + + return KNOT_EOK; +} diff --git a/src/knot/zone/zone-dump.h b/src/knot/zone/zone-dump.h new file mode 100644 index 0000000..daf6c18 --- /dev/null +++ b/src/knot/zone/zone-dump.h @@ -0,0 +1,94 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file zone-dump.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * \brief Functions for dumping zone to binary file. + * + * \addtogroup dnslib + * @{ + */ + +#ifndef _KNOT_ZONEDUMP_H_ +#define _KNOT_ZONEDUMP_H_ + +#include "common/crc.h" +#include "libknot/zone/zone.h" + +/*! + * \brief Zone loader enums. + */ +enum { + MAGIC_LENGTH = 7 /*!< Compiled zone magic length. */ +}; + +/*! \brief Magic identifier: { "knot", maj_ver, min_ver, revision } */ +#define MAGIC_BYTES {'k', 'n', 'o', 't', '0', '8', '0'} + +/*! + * \brief Dumps given zone to binary file. + * + * \param zone Zone to be saved. + * \param filename Name of file to be created. + * \param do_checks Set to 1 to enable checking the zone for semantic errors. + * \param sfilename Source filename of the text zone file. + * + * \retval KNOT_EOK on success. + * \retval KNOT_EBADARG if the file cannot be opened for writing. + */ +int knot_zdump_binary(knot_zone_contents_t *zone, const char *filename, + int do_checks, const char *sfilename); + +/*! + * \brief Serializes RRSet into binary stream. Expects NULL pointer, memory + * is handled inside function. + * + * \param rrset RRSet to be serialized. + * \param stream Stream containing serialized RRSet. + * \param size Length of created stream. + * + * \retval KNOT_EOK on success. + * \retval KNOT_EBADARG if wrong arguments are supplied. + * \retval KNOT_ENOMEM on memory error. + */ +int knot_zdump_rrset_serialize(const knot_rrset_t *rrset, uint8_t **stream, + size_t *size); + +/*! + * \brief Serializes RRSet into binary stream. Expects NULL pointer, memory + * is handled inside function. + * + * \param rrset RRSet to be serialized. + * \param stream Stream containing serialized RRSet. + * \param size Length of created stream. + * + * \retval KNOT_EOK on success. + * \retval KNOT_EBADARG if wrong arguments are supplied. + * \retval KNOT_ENOMEM on memory error. + */ +int knot_zdump_rrset_serialize(const knot_rrset_t *rrset, uint8_t **stream, + size_t *size); + +int knot_zdump_dump_and_swap(knot_zone_contents_t *zone, + const char *temp_zonedb, + const char *destination_zonedb, + const char *sfilename); + +#endif /* _KNOT_ZONEDUMP_H_ */ + +/*! @} */ diff --git a/src/knot/zone/zone-load.c b/src/knot/zone/zone-load.c new file mode 100644 index 0000000..9ab0e8d --- /dev/null +++ b/src/knot/zone/zone-load.c @@ -0,0 +1,1209 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/stat.h> +#include <time.h> + +#include "common/crc.h" +#include "libknot/common.h" +#include "knot/other/debug.h" +#include "knot/zone/zone-load.h" +#include "knot/zone/zone-dump.h" +#include "libknot/libknot.h" + +/*! + * \brief Compares two time_t values. + * + * \param x First time_t value to be compared. + * \param y Second time_t value to be compared. + * + * \retval 0 when times are the some. + * \retval 1 when y < x. + * \retval -1 when x > y. + */ +static int timet_cmp(time_t x, time_t y) +{ + /* Calculate difference in the scale of seconds. */ + long diff = x - y; + + /* X and Y are equal. */ + if (diff == 0) { + return 0; + } + + /* X is newer. */ + if (diff > 0) { + return 1; + } + + /* Y is newer. */ + return -1; +} + +/*! + * \brief Safe wrapper around fread. + * + * \param dst Destination pointer. + * \param size Size of element to be read. + * \param n Number of elements to be read. + * \param fp File to read from. + * + * \retval > 0 if succesfull. + * \retval 0 if failed. + */ +static inline int fread_safe_from_file(void *dst, + size_t size, size_t n, FILE *fp) +{ + int rc = fread(dst, size, n, fp); + if (rc != n) { + fprintf(stderr, "fread: invalid read %d (expected %zu)\n", rc, + n); + } + + return rc == n; +} + +static uint8_t *knot_zload_stream = NULL; +static size_t knot_zload_stream_remaining = 0; +static size_t knot_zload_stream_size = 0; + +static inline int read_from_stream(void *dst, + size_t size, size_t n, FILE *fp) +{ + if (knot_zload_stream_remaining < (size * n)) { + return 0; + } + + memcpy(dst, + knot_zload_stream + + (knot_zload_stream_size - knot_zload_stream_remaining), + size * n); + knot_zload_stream_remaining -= size * n; + + return 1; +} + +static int (*fread_wrapper)(void *dst, size_t size, size_t n, FILE *fp); + +/*! \note Contents of dump file: + * MAGIC(knotxx) NUMBER_OF_NORMAL_NODES NUMBER_OF_NSEC3_NODES + * [normal_nodes] [nsec3_nodes] + * node has following format: + * owner_size owner_wire owner_label_size owner_labels owner_id + * node_flags node_rrset_count [node_rrsets] + * rrset has following format: + * rrset_type rrset_class rrset_ttl rrset_rdata_count rrset_rrsig_count + * [rrset_rdata] [rrset_rrsigs] + * rdata can either contain full dnames (that is with labels but without ID) + * or dname ID, if dname is in the zone + * or raw data stored like this: data_len [data] + */ + +enum { DNAME_MAX_WIRE_LENGTH = 256 }; + +/*! + * \brief Helper function. Frees rdata items and temporary array of items. + * + * \param rdata Rdata to be freed. + * \param items Items to be freed. + * \param count Current count of rdata items. + * \param type RRSet type. + */ +static void load_rdata_purge(knot_rdata_t *rdata, + knot_rdata_item_t *items, + int count, + knot_rrtype_descriptor_t *desc, + uint16_t type) +{ + /* Increase refcount manually, as the set_items() doesn't see the dname + * type and thus is unable to increment refcounter. + */ + for (int i = 0; i < count; ++i) { + switch(desc->wireformat[i]) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: + knot_dname_retain(items[i].dname); + break; + default: + break; + } + } + + /* Copy items to rdata and free the temporary rdata. */ + knot_rdata_set_items(rdata, items, count); + knot_rdata_deep_free(&rdata, type, 1); + free(items); +} + +static knot_dname_t *read_dname_with_id(FILE *f) +{ + knot_dname_t *ret = knot_dname_new(); + CHECK_ALLOC_LOG(ret, NULL); + + /* Read ID. */ + uint32_t dname_id = 0; + if (!fread_wrapper(&dname_id, sizeof(dname_id), 1, f)) { + knot_dname_release(ret); + return NULL; + } + + ret->id = dname_id; + dbg_zload("loaded: dname id: %u\n", dname_id); + + /* Read size of dname. */ + uint32_t dname_size = 0; + if (!fread_wrapper(&dname_size, sizeof(dname_size), 1, f)) { + knot_dname_release(ret); + return NULL; + } + ret->size = dname_size; + dbg_zload("loaded: dname length: %u\n", ret->size); + + assert(ret->size <= DNAME_MAX_WIRE_LENGTH); + + /* Read wireformat of dname. */ + ret->name = malloc(sizeof(uint8_t) * ret->size); + if (ret->name == NULL) { + ERR_ALLOC_FAILED; + knot_dname_release(ret); + return NULL; + } + + if (!fread_wrapper(ret->name, sizeof(uint8_t), ret->size, f)) { + knot_dname_release(ret); + return NULL; + } + + /* Read labels. */ + uint16_t label_count = 0; + if (!fread_wrapper(&label_count, sizeof(label_count), 1, f)) { + knot_dname_release(ret); + return NULL; + } + + ret->label_count = label_count; + + ret->labels = malloc(sizeof(uint8_t) * ret->label_count); + if (ret->labels == NULL) { + ERR_ALLOC_FAILED; + knot_dname_release(ret); + return NULL; + } + + if (!fread_wrapper(ret->labels, sizeof(uint8_t), ret->label_count, f)) { + free(ret->name); + free(ret); + return NULL; + } + + dbg_zload("loaded: %s (id: %d)\n", knot_dname_to_str(ret), + ret->id); + + return ret; +} + +/*! + * \brief Load rdata in binary format from file. + * + * \param type Type of RRSet containing read rdata. + * \param f File to read binary data from. + * + * \return Pointer to read and created rdata on success, NULL otherwise. + */ +static knot_rdata_t *knot_load_rdata(uint16_t type, FILE *f, + knot_dname_t **id_array, + int use_ids) +{ + knot_rdata_t *rdata = knot_rdata_new(); + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(type); + assert(desc != NULL); + + + /* First, should read rdata count. */ + + uint32_t rdata_count = 0; + + if(!fread_wrapper(&rdata_count, sizeof(rdata_count), 1, f)) { + return NULL; + } + + knot_rdata_item_t *items = + malloc(sizeof(knot_rdata_item_t) * rdata_count); + + if (desc->fixed_items) { + assert(desc->length == rdata_count); + } + + uint16_t raw_data_length; + + dbg_zload("Reading %d items\n", rdata_count); + + dbg_zload("current type: %s\n", knot_rrtype_to_string(type)); + + for (int i = 0; i < rdata_count; i++) { + if (desc->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME ) { + + /* TODO maybe this does not need to be stored this big*/ + + uint32_t dname_id = 0; + uint8_t has_wildcard = 0; + uint8_t in_the_zone = 0; + + if (use_ids) { + if(!fread_wrapper(&dname_id, sizeof(dname_id), 1, f)) { + load_rdata_purge(rdata, items, i, desc, type); + return NULL; + } + + /* Store reference do dname. */ + knot_dname_retain(id_array[dname_id]); + items[i].dname = id_array[dname_id]; + } else { + items[i].dname = read_dname_with_id(f); + } + + if(!fread_wrapper(&in_the_zone, sizeof(in_the_zone), + 1, f)) { + load_rdata_purge(rdata, items, i, desc, type); + return NULL; + } + + if(!fread_wrapper(&has_wildcard, sizeof(uint8_t), + 1, f)) { + load_rdata_purge(rdata, items, i, desc, type); + return NULL; + } + + if (use_ids && has_wildcard) { + if(!fread_wrapper(&dname_id, sizeof(dname_id), + 1, f)) { + load_rdata_purge(rdata, items, + i, desc, type); + return NULL; + } + items[i].dname->node = + id_array[dname_id]->node; + } else if (use_ids && !in_the_zone) { /* destroy the node */ + if (id_array[dname_id]->node != NULL) { + knot_node_free(&id_array[dname_id]-> + node, 0, 0); + } + /* Also sets node to NULL! */ + } + assert(items[i].dname); + } else { + if (!fread_wrapper(&raw_data_length, + sizeof(raw_data_length), 1, f)) { + load_rdata_purge(rdata, items, i, desc, type); + return NULL; + } + + items[i].raw_data = + malloc(sizeof(uint8_t) * (raw_data_length + 2)); + items[i].raw_data[0] = raw_data_length; + + if (!fread_wrapper(items[i].raw_data + 1, sizeof(uint8_t), + raw_data_length, f)) { + load_rdata_purge(rdata, items, i + 1, desc, type); + return NULL; + } + dbg_zload("read raw_data len %d\n", raw_data_length); + } + } + + /* Each item has refcount already incremented for saving in rdata. */ + if (knot_rdata_set_items(rdata, items, rdata_count) != 0) { + fprintf(stderr, "zoneload: Could not set items " + "when loading rdata.\n"); + } + + free(items); + + dbg_zload("knot_load_rdata: all %d items read\n", + desc->length); + + assert(rdata->count == rdata_count); + + rdata->count = rdata_count; + + return rdata; +} + +/*! + * \brief Loads RRSIG from binary file. + * + * \param f File to read from. + * + * \return pointer to created and read RRSIG on success, NULL otherwise. + */ +static knot_rrset_t *knot_load_rrsig(FILE *f, knot_dname_t **id_array, + int use_ids) +{ + knot_rrset_t *rrsig; + + uint16_t rrset_type; + uint16_t rrset_class; + uint32_t rrset_ttl; + + uint32_t rdata_count; + + if (!fread_wrapper(&rrset_type, sizeof(rrset_type), 1, f)) { + return NULL; + } + + if (rrset_type != KNOT_RRTYPE_RRSIG) { + fprintf(stderr, "!! Error: rrsig has wrong type\n"); + return NULL; + } + dbg_zload("rrset type: %d\n", rrset_type); + if (!fread_wrapper(&rrset_class, sizeof(rrset_class), 1, f)) { + return NULL; + } + dbg_zload("rrset class %d\n", rrset_class); + + if (!fread_wrapper(&rrset_ttl, sizeof(rrset_ttl), 1, f)) { + return NULL; + } + dbg_zload("rrset ttl %d\n", rrset_ttl); + + if (!fread_wrapper(&rdata_count, sizeof(rdata_count), 1, f)) { + return NULL; + } + + rrsig = knot_rrset_new(NULL, rrset_type, rrset_class, rrset_ttl); + + knot_rdata_t *tmp_rdata; + + dbg_zload("loading %d rdata entries\n", rdata_count); + + for (int i = 0; i < rdata_count; i++) { + tmp_rdata = knot_load_rdata(KNOT_RRTYPE_RRSIG, f, + id_array, use_ids); + if (tmp_rdata) { + knot_rrset_add_rdata(rrsig, tmp_rdata); + } else { + knot_rrset_deep_free(&rrsig, 0, 1, 1); + return NULL; + } + } + + return rrsig; +} + +/*! + * \brief Loads RRSet from binary file. + * + * \param f File to read from. + * + * \return pointer to created and read RRSet on success, NULL otherwise. + */ +static knot_rrset_t *knot_load_rrset(FILE *f, knot_dname_t **id_array, + int use_ids) +{ + knot_rrset_t *rrset = NULL; + + uint16_t rrset_type = 0; + uint16_t rrset_class = 0; + uint32_t rrset_ttl = 0; + + uint32_t rdata_count = 0; + uint8_t rrsig_count = 0; + + knot_dname_t *owner = NULL; + + if (!use_ids) { + dbg_zload("Loading owner of new RRSet from wire.\n"); + owner = read_dname_with_id(f); + } + + if (!fread_wrapper(&rrset_type, sizeof(rrset_type), 1, f)) { + return NULL; + } + dbg_zload("Zone load: rrset load: type: %u\n", rrset_type); + if (!fread_wrapper(&rrset_class, sizeof(rrset_class), 1, f)) { + return NULL; + } + dbg_zload("Zone load: rrset class: type: %u\n", rrset_class); + if (!fread_wrapper(&rrset_ttl, sizeof(rrset_ttl), 1, f)) { + return NULL; + } + dbg_zload("Zone load: rrset ttl: type: %u\n", rrset_ttl); + if (!fread_wrapper(&rdata_count, sizeof(rdata_count), 1, f)) { + return NULL; + } + dbg_zload("Zone load: rrset load: rdata count: %u\n", rdata_count); + if (!fread_wrapper(&rrsig_count, sizeof(rrsig_count), 1, f)) { + return NULL; + } + dbg_zload("Zone load: rrset load: type: %u\n", rrset_type); + + dbg_zload("Loading RRSet owned by: %s\n", + knot_dname_to_str(owner)); + + rrset = knot_rrset_new(owner, rrset_type, rrset_class, rrset_ttl); + + if (!use_ids) { + /* Directly release if allocated locally. */ + knot_dname_release(owner); + owner = 0; + } + + dbg_zload("RRSet type: %d\n", rrset->type); + + knot_rdata_t *tmp_rdata = NULL; + + for (int i = 0; i < rdata_count; i++) { + tmp_rdata = knot_load_rdata(rrset->type, f, + id_array, use_ids); + if (tmp_rdata) { + knot_rrset_add_rdata(rrset, tmp_rdata); + } else { + knot_rrset_deep_free(&rrset, 0, 1, 1); + return NULL; + } + } + + knot_rrset_t *tmp_rrsig = NULL; + + dbg_zload("Reading: %d RRSIGs\n", rrsig_count); + if (rrsig_count) { + tmp_rrsig = knot_load_rrsig(f, id_array, use_ids); + if (!use_ids) { + knot_rrset_set_owner(tmp_rrsig, rrset->owner); + } + } + + knot_rrset_set_rrsigs(rrset, tmp_rrsig); + + dbg_zload("Finished loading RRSet %p\n", rrset); + + return rrset; +} + +/*! + * \brief Loads node from binary file. + * + * \param f File to read from. + * + * \return Pointer to created and read node on success, NULL otherwise. + */ +static knot_node_t *knot_load_node(FILE *f, knot_dname_t **id_array) +{ + uint8_t flags = 0; + knot_node_t *node = NULL; + uint32_t parent_id = 0; + uint32_t nsec3_node_id = 0; + uint16_t rrset_count = 0; + uint32_t dname_id = 0; + + /* At the beginning of node - just dname_id !!!.*/ + if (!fread_wrapper(&dname_id, sizeof(dname_id), 1, f)) { + return NULL; + } + + if (!fread_wrapper(&parent_id, sizeof(parent_id), 1, f)) { + return NULL; + } + + if (!fread_wrapper(&flags, sizeof(flags), 1, f)) { + return NULL; + } + + if (!fread_wrapper(&nsec3_node_id, sizeof(nsec3_node_id), 1, f)) { + return NULL; + } + + if (!fread_wrapper(&rrset_count, sizeof(rrset_count), 1, f)) { + return NULL; + } + knot_dname_t *owner = id_array[dname_id]; + + dbg_zload("Node owner id: %d\n", dname_id); + dbg_zload("Node owned by: %s\n", knot_dname_to_str(owner)); + dbg_zload("Number of RRSets in a node: %d\n", rrset_count); + + node = owner->node; + + if (node == NULL) { + fprintf(stderr, "zone: Could not create node.\n"); + return NULL; + } + /* XXX can it be 0, ever? I think not. */ + if (nsec3_node_id != 0) { + knot_node_set_nsec3_node(node, id_array[nsec3_node_id]->node); + /* CLEANUP */ +// node->nsec3_node = id_array[nsec3_node_id]->node; + } else { + knot_node_set_nsec3_node(node, NULL); + /* CLEANUP */ +// node->nsec3_node = NULL; + } + + /* Retain new owner while releasing replaced owner. */ + knot_node_set_owner(node, owner); + node->flags = flags; + + //XXX will have to be set already...canonical order should do it + + if (parent_id != 0) { + knot_node_set_parent(node, id_array[parent_id]->node); + assert(knot_node_parent(node, 0) != NULL); + } else { + knot_node_set_parent(node, NULL); + } + + knot_rrset_t *tmp_rrset; + + for (int i = 0; i < rrset_count; i++) { + if ((tmp_rrset = knot_load_rrset(f, id_array, 1)) == NULL) { + knot_node_free(&node, 1, 0); + //TODO what else to free? + fprintf(stderr, "zone: Could not load rrset.\n"); + return NULL; + } + /* Retain new owner while releasing replaced owner. */ + knot_rrset_set_owner(tmp_rrset, node->owner); + if (tmp_rrset->rrsigs != NULL) { + knot_rrset_set_owner(tmp_rrset->rrsigs, node->owner); + } + if (knot_node_add_rrset(node, tmp_rrset, 0) < 0) { + fprintf(stderr, "zone: Could not add rrset.\n"); + return NULL; + } + } + assert(node != NULL); + dbg_zload("Node loaded: %p\n", node); + return node; +} + +/*! + * \brief Finds and sets wildcard child for given node's owner. + * + * \param zone Current zone. + * \param node Node to be used. + * \param nsec3 Is NSEC3 node. + */ +static void find_and_set_wildcard_child(knot_zone_contents_t *zone, + knot_node_t *node, int nsec3) +{ + knot_dname_t *chopped = knot_dname_left_chop(node->owner); + assert(chopped); + knot_node_t *wildcard_parent; + if (!nsec3) { + wildcard_parent = + knot_zone_contents_get_node(zone, chopped); + } else { + wildcard_parent = + knot_zone_contents_get_nsec3_node(zone, chopped); + } + + /* Directly discard. */ + knot_dname_free(&chopped); + + assert(wildcard_parent); /* it *has* to be there */ + + knot_node_set_wildcard_child(wildcard_parent, node); +} + +/*! + * \brief Checks if magic string at the beginning of the file is the same + * as defined. + * + * \param f File to read magic string from. + * \param MAGIC Magic string. + * \param MAGIC_LENGTH Length of magic string. + * + * \retval 1 if magic is the same. + * \retval 0 otherwise. + */ +static int knot_check_magic(FILE *f, const uint8_t* MAGIC, uint MAGIC_LENGTH) +{ + uint8_t tmp_magic[MAGIC_LENGTH]; + + if (!fread_wrapper(&tmp_magic, sizeof(uint8_t), MAGIC_LENGTH, f)) { + return 0; + } + + for (int i = 0; i < MAGIC_LENGTH; i++) { + if (tmp_magic[i] != MAGIC[i]) { + return 0; + } + } + + return 1; +} + +static unsigned long calculate_crc(FILE *f) +{ + crc_t crc = crc_init(); + /* Get file size. */ + fseek(f, 0L, SEEK_END); + size_t file_size = ftell(f); + fseek(f, 0L, SEEK_SET); + + const size_t chunk_size = 1024; + /* read chunks of 1 kB */ + size_t read_bytes = 0; + /* Prealocate chunk */ + unsigned char *chunk = malloc(sizeof(unsigned char) * chunk_size); + CHECK_ALLOC_LOG(chunk, 0); + while ((file_size - read_bytes) > chunk_size) { + if (!fread_wrapper(chunk, sizeof(unsigned char), chunk_size, f)) { + free(chunk); + return 0; + } + crc = crc_update(crc, chunk, + sizeof(unsigned char) * chunk_size); + read_bytes += chunk_size; + } + + /* Read the rest of the file */ + if (!fread_wrapper(chunk, sizeof(unsigned char), file_size - read_bytes, + f)) { + free(chunk); + return 0; + } + + crc = crc_update(crc, chunk, + sizeof(unsigned char) * (file_size - read_bytes)); + free(chunk); + + fseek(f, 0L, SEEK_SET); + return (unsigned long)crc_finalize(crc); +} + +int knot_zload_open(zloader_t **dst, const char *filename) +{ + *dst = 0; + if (!dst || !filename) { + return KNOT_EBADARG; + } + + fread_wrapper = fread_safe_from_file; + + /* Open file for binary read. */ + FILE *f = fopen(filename, "rb"); + if (unlikely(!f)) { + dbg_zload("knot_zload_open: failed to open '%s'\n", + filename); + return KNOT_EFEWDATA; // No such file or directory (POSIX.1) + } + + /* Calculate CRC and compare with filename.crc file */ + unsigned long crc_calculated = calculate_crc(f); + + /* Read CRC from filename.crc file */ + char *crc_path = + malloc(sizeof(char) * (strlen(filename) + strlen(".crc") + 1)); + if (unlikely(!crc_path)) { + fclose(f); + return KNOT_ENOMEM; + } + memset(crc_path, 0, + sizeof(char) * (strlen(filename) + strlen(".crc") + 1)); + + memcpy(crc_path, filename, sizeof(char) * strlen(filename)); + + crc_path = strcat(crc_path, ".crc"); + FILE *f_crc = fopen(crc_path, "r"); + if (unlikely(!f_crc)) { + dbg_zload("knot_zload_open: failed to open '%s'\n", + crc_path); + fclose(f); + free(crc_path); + return KNOT_ECRC; + } + + unsigned long crc_from_file = 0; + if (fscanf(f_crc, "%lu\n", &crc_from_file) != 1) { + dbg_zload("knot_zload_open: could not read " + "CRC from file '%s'\n", + crc_path); + fclose(f_crc); + fclose(f); + free(crc_path); + return KNOT_ERROR; + } + free(crc_path); + fclose(f_crc); + + /* Compare calculated and read CRCs. */ + if (crc_from_file != crc_calculated) { + dbg_zload("knot_zload_open: CRC failed for " + "file '%s'\n", + filename); + fclose(f); + return KNOT_ECRC; + } + + /* Check magic sequence. */ + static const uint8_t MAGIC[MAGIC_LENGTH] = MAGIC_BYTES; + if (!knot_check_magic(f, MAGIC, MAGIC_LENGTH)) { + fclose(f); + dbg_zload("knot_zload_open: magic bytes " + "in don't match '%*s' " + "(%s)\n", + (int)MAGIC_LENGTH, (const char*)MAGIC, filename); + return KNOT_EMALF; // Illegal byte sequence (POSIX.1, C99) + } + + /* Read source file length. */ + uint32_t sflen = 0; + if (!fread_wrapper(&sflen, 1, sizeof(uint32_t), f)) { + dbg_zload("knot_zload_open: failed to read " + "sfile length\n"); + fclose(f); + return KNOT_ERROR; + } + + /* Read source file. */ + char *sfile = malloc(sflen); + if (!sfile) { + dbg_zload("knot_zload_open: invalid sfile " + "length %u\n", sflen); + fclose(f); + return KNOT_ENOMEM; + } + if (!fread_wrapper(sfile, 1, sflen, f)) { + dbg_zload("knot_zload_open: failed to read %uB " + "source file\n", + sflen); + free(sfile); + fclose(f); + return KNOT_ERROR; + } + + /* Allocate new loader. */ + zloader_t *zl = malloc(sizeof(zloader_t)); + if (!zl) { + free(sfile); + fclose(f); + return KNOT_ENOMEM; + } + + dbg_zload("knot_zload_open: opened '%s' as fp %p " + "(source is '%s')\n", + filename, f, sfile); + zl->filename = strdup(filename); + zl->source = sfile; + zl->fp = f; + *dst = zl; + + return KNOT_EOK; +} + +static void cleanup_id_array(knot_dname_t **id_array, + const uint from, const uint to) +{ + for (uint i = from; i < to; i++) { + knot_dname_release(id_array[i]); + } + + free(id_array); +} + +//static knot_dname_table_t *create_dname_table(FILE *f, uint max_id) +//{ +// if (f == NULL ) { +// return NULL; +// } + +// if (!fread_wrapper(&max_id, sizeof(max_id), 1, f)) { +// return NULL; +// } + +// knot_dname_table_t *dname_table = knot_dname_table_new(); +// if (dname_table == NULL) { +// return NULL; +// } + +// /* Create nodes containing dnames. */ +// for (uint i = 1; i < max_id; i++) { +// knot_dname_t *loaded_dname = read_dname_with_id(f); +// if (loaded_dname == NULL) { +// knot_dname_table_deep_free(&dname_table); +// return NULL; +// } +// if (knot_dname_table_add_dname(dname_table, +// loaded_dname) != KNOT_EOK) { + +// } +// } + +// return dname_table; +//} + +static knot_dname_table_t *create_dname_table_from_array( + knot_dname_t **array, uint max_id) +{ + if (array == NULL) { + /* should I set errno or what ... ? */ + dbg_zload("No array passed\n"); + return NULL; + } + + assert(array[0] == NULL); + + knot_dname_table_t *ret = knot_dname_table_new(); + CHECK_ALLOC_LOG(ret, NULL); + + /* Table will have max_id entries */ + for (uint i = 1; i < max_id; i++) { + assert(array[i]); + if (knot_dname_table_add_dname(ret, + array[i]) != KNOT_EOK) { + dbg_zload("Could not add: %s\n", + knot_dname_to_str(array[i])); + knot_dname_table_deep_free(&ret); + return NULL; + } + } + + return ret; +} + +static knot_dname_t **create_dname_array(FILE *f, uint max_id) +{ + if (f == NULL) { + return NULL; + } + + knot_dname_t **array = + malloc(sizeof(knot_dname_t *) * ( max_id + 1)); + memset(array, 0, sizeof(knot_dname_t *) * (max_id + 1)); + if (array == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + for (uint i = 0; i < max_id - 1; i++) { + knot_dname_t *read_dname = read_dname_with_id(f); + if (read_dname == NULL) { + cleanup_id_array(array, 0, i); + return NULL; + } + + if (read_dname->id < max_id) { + + /* Create new node from dname. */ + read_dname->node = knot_node_new(read_dname, NULL, 0); + + if (read_dname->node == NULL) { + ERR_ALLOC_FAILED; + + /* Release read dname. */ + knot_dname_release(read_dname); + cleanup_id_array(array, 0, i); + return NULL; + } + + /* Store reference to dname in array. */ + array[read_dname->id] = read_dname; + } else { + /* Release read dname. */ + knot_dname_release(read_dname); + cleanup_id_array(array, 0, i); + return NULL; + } + + } + + return array; +} + +knot_zone_t *knot_zload_load(zloader_t *loader) +{ + dbg_zload("Loading zone, loader: %p\n", loader); + if (!loader) { + dbg_zload("NULL loader!\n"); + return NULL; + } + + fread_wrapper = fread_safe_from_file; + + FILE *f = loader->fp; + + knot_node_t *tmp_node; + + /* Load the dname table. */ + /* CLEANUP */ +// const knot_dname_table_t *dname_table = +// create_dname_table(f, total_dnames); +// if (dname_table == NULL) { +// return NULL; +// } + + uint32_t node_count; + uint32_t nsec3_node_count; + uint32_t auth_node_count; + + if (!fread_wrapper(&node_count, sizeof(node_count), 1, f)) { + dbg_zload("wrong read!\n"); + return NULL; + } + + if (!fread_wrapper(&nsec3_node_count, sizeof(nsec3_node_count), 1, f)) { + dbg_zload("wrong read!\n"); + return NULL; + } + if (!fread_wrapper(&auth_node_count, + sizeof(auth_node_count), 1, f)) { + dbg_zload("wrong read!\n"); + return NULL; + } + dbg_zload("authoritative nodes: %u\n", auth_node_count); + + dbg_zload("loading %u nodes\n", node_count); + + uint32_t total_dnames = 0; + /* First, read number of dnames in dname table. */ + if (!fread_wrapper(&total_dnames, sizeof(total_dnames), 1, f)) { + return NULL; + } + + dbg_zload("total dname count: %d\n", total_dnames); + + /* Create id array. */ + knot_dname_t **id_array = create_dname_array(f, total_dnames); + if (id_array == NULL) { + return NULL; + } + + knot_dname_table_t *dname_table = + create_dname_table_from_array(id_array, total_dnames); + if (dname_table == NULL) { + ERR_ALLOC_FAILED; + cleanup_id_array(id_array, 1, total_dnames); + return NULL; + } + + knot_node_t *apex = knot_load_node(f, id_array); + + if (!apex) { + fprintf(stderr, "zone: Could not load apex node (in %s)\n", + loader->filename); + cleanup_id_array(id_array, 1, + node_count + nsec3_node_count + 1); + return NULL; + } + + dbg_zload("Apex node loaded: %p\n", apex); + + knot_zone_t *zone = knot_zone_new(apex, auth_node_count, 0); + if (zone == NULL) { + cleanup_id_array(id_array, 1, + node_count + nsec3_node_count + 1); + dbg_zload("Failed to create new zone from apex!\n"); + return NULL; + } + + knot_zone_contents_t *contents = knot_zone_get_contents(zone); + assert(contents); + + /* Assign dname table to the new zone. */ + contents->dname_table = dname_table; + + /* CLEANUP */ +// apex->prev = NULL; + knot_node_set_previous(apex, NULL); + + knot_node_t *last_node = 0; + + last_node = apex; + + for (uint i = 1; i < node_count; i++) { + tmp_node = knot_load_node(f, id_array); + + if (tmp_node != NULL) { + if (knot_zone_contents_add_node(contents, tmp_node, + 0, 0, 0) != 0) { + fprintf(stderr, "!! cannot add node\n"); + continue; + } + if (knot_dname_is_wildcard(tmp_node->owner)) { + find_and_set_wildcard_child(contents, + tmp_node, 0); + } + + knot_node_set_previous(tmp_node, last_node); + /* CLEANUP */ +// tmp_node->prev = last_node; + + if (tmp_node->rrset_count && + (knot_node_is_deleg_point(tmp_node) || + !knot_node_is_non_auth(tmp_node))) { + last_node = tmp_node; + } + + } else { + fprintf(stderr, "zone: Node error (in %s).\n", + loader->filename); + } + } + + assert(knot_node_previous(knot_zone_contents_apex(contents), 0) + == NULL); + + knot_node_set_previous(knot_zone_contents_get_apex(contents), + last_node); + + dbg_zload("loading %u nsec3 nodes\n", nsec3_node_count); + + knot_node_t *nsec3_first = NULL; + + if (nsec3_node_count > 0) { + nsec3_first = knot_load_node(f, id_array); + + assert(nsec3_first != NULL); + + if (knot_zone_contents_add_nsec3_node(contents, nsec3_first, 0, 0, 0) + != 0) { + fprintf(stderr, "!! cannot add first nsec3 node, " + "exiting.\n"); + knot_zone_deep_free(&zone, 0); + free(id_array); + /* TODO this will leak dnames from id_array that were + * not assigned. */ + return NULL; + } + + knot_node_set_previous(nsec3_first, NULL); + /* CLEANUP */ +// nsec3_first->prev = NULL; + + last_node = nsec3_first; + } + + for (uint i = 1; i < nsec3_node_count; i++) { + tmp_node = knot_load_node(f, id_array); + + if (tmp_node != NULL) { + if (knot_zone_contents_add_nsec3_node(contents, + tmp_node, 0, 0, 0) != 0) { + fprintf(stderr, "!! cannot add nsec3 node\n"); + continue; + } + + knot_node_set_previous(tmp_node, last_node); + /* CLEANUP */ +// tmp_node->prev = last_node; + + last_node = tmp_node; + } else { + fprintf(stderr, "zone: Node error (in %s).\n", + loader->filename); + } + } + + if (nsec3_node_count) { + assert(knot_node_previous(nsec3_first, 0) == NULL); + knot_node_set_previous(nsec3_first, last_node); + /* CLEANUP */ +// nsec3_first->prev = last_node; + } + + /* ID array is now useless */ + for (uint i = 1; i < total_dnames; i++) { + /* Added to table, may discard now. */ + knot_dname_release(id_array[i]); + } + free(id_array); + + dbg_zload("zone loaded, returning: %p\n", zone); + return zone; +} + +int knot_zload_needs_update(zloader_t *loader) +{ + if (!loader) { + return 1; + } + + /* Check if the source still exists. */ + struct stat st_src; + if (stat(loader->source, &st_src) != 0) { + return 1; + } + + /* Check if the compiled file still exists. */ + struct stat st_bin; + if (stat(loader->filename, &st_bin) != 0) { + return 1; + } + + /* Compare the mtime of the source and file. */ + /*! \todo Inspect types on Linux. */ + if (timet_cmp(st_bin.st_mtime, st_src.st_mtime) < 0) { + return 1; + } + + return 0; +} + +void knot_zload_close(zloader_t *loader) +{ + if (!loader) { + return; + } + + free(loader->filename); + free(loader->source); + fclose(loader->fp); + free(loader); +} + +int knot_zload_rrset_deserialize(knot_rrset_t **rrset, + uint8_t *stream, size_t *size) +{ + if (stream == NULL || size == 0) { + return KNOT_EBADARG; + } + + fread_wrapper = read_from_stream; + + knot_zload_stream = stream; + knot_zload_stream_remaining = knot_zload_stream_size = *size; + + knot_rrset_t *ret = knot_load_rrset(NULL, NULL, 0); + + if (ret == NULL) { + knot_zload_stream = NULL; + knot_zload_stream_remaining = 0; + knot_zload_stream_size = 0; + return KNOT_EMALF; + } + + *size = knot_zload_stream_remaining; + *rrset = ret; + + knot_zload_stream = NULL; + knot_zload_stream_remaining = 0; + knot_zload_stream_size = 0; + + return KNOT_EOK; +} + diff --git a/src/knot/zone/zone-load.h b/src/knot/zone/zone-load.h new file mode 100644 index 0000000..2fe318f --- /dev/null +++ b/src/knot/zone/zone-load.h @@ -0,0 +1,104 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file zone-load.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * \brief Loader of previously parsed zone + * + * \addtogroup dnslib + * @{ + */ + +#ifndef _KNOT_ZONELOAD_H_ +#define _KNOT_ZONELOAD_H_ + +#include <stdio.h> + +#include "libknot/zone/zone.h" + +/*! + * \brief Zone loader structure. + */ +typedef struct zloader_t +{ + char *filename; /*!< Compiled zone filename. */ + char *source; /*!< Zone source file. */ + FILE *fp; /*!< Open filepointer to compiled zone. */ + +} zloader_t; + +/*! + * \brief Initializes zone loader from file.. + * + * \param filename File containing the compiled zone. + * \param loader Will create new loader in *loader. + * + * \retval Initialized loader on success. + * \retval NULL on error. + */ +int knot_zload_open(zloader_t **loader, const char *filename); + +/*! + * \brief Loads zone from a compiled and serialized zone file. + * + * \param loader Zone loader instance. + * + * \retval Loaded zone on success. + * \retval NULL otherwise. + */ +knot_zone_t *knot_zload_load(zloader_t *loader); + +/*! + * \brief Checks whether the compiled zone needs a recompilation. + * + * \param loader Zone loader instance. + * + * \retval 1 is if needs to be recompiled. + * \retval 0 if it is up to date. + */ +int knot_zload_needs_update(zloader_t *loader); + + +/*! + * \brief Free zone loader. + * + * \param loader Zone loader instance. + */ +void knot_zload_close(zloader_t *loader); + +/*! + * \brief Loads RRSet serialized by knot_zdump_rrset_serialize(). + * + * \param stream Stream containing serialized RRSet. + * \param size Size of stream. This variable will contain remaining length of + * stream, once the function has ended. + * \param rrset Place for created RRSet. + * + * \note If RRSet contains RRSIGs, their owners are not copies, but only links + * to the owner of RRSet. All RDATA dnames are copied. + * + * \retval KNOT_EOK on success. + * \retval KNOT_EBADAG on wrong arguments. + * \retval KNOT_EMALF when stream is malformed. + */ +int knot_zload_rrset_deserialize(knot_rrset_t **rrset, + uint8_t *stream, size_t *size); + +#endif /* _KNOTD_ZONELOAD_H_ */ + +/*! @} */ diff --git a/src/knotc.8 b/src/knotc.8 new file mode 100644 index 0000000..f2e7e40 --- /dev/null +++ b/src/knotc.8 @@ -0,0 +1,63 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. +.TH KNOT "1" "November 2011" "Knot DNS, version 0.8" "User Commands" +.SH NAME +Knot \- manual page for Knot DNS, version 0.8 +.SH SYNOPSIS +.B knotc +[\fIparameters\fR] \fIstart|stop|restart|reload|running|compile\fR +.SH DESCRIPTION +.SS "Parameters:" +.HP +\fB\-c\fR [file], \fB\-\-config\fR=\fI[file]\fR Select configuration file. +.TP +\fB\-j\fR [num], \fB\-\-jobs\fR=\fI[num]\fR +Number of parallel tasks to run (only for 'compile'). +.TP +\fB\-f\fR, \fB\-\-force\fR +Force operation \- override some checks. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Verbose mode \- additional runtime information. +.TP +\fB\-V\fR, \fB\-\-version\fR +Print knot server version. +.TP +\fB\-w\fR, \fB\-\-wait\fR +Wait for the server to finish start/stop operations. +.TP +\fB\-i\fR, \fB\-\-interactive\fR +Interactive mode (do not daemonize). +.TP +\fB\-h\fR, \fB\-\-help\fR +Print help and usage. +.SS "Actions:" +.TP +start +Start knot server zone (no\-op if running). +.TP +stop +Stop knot server (no\-op if not running). +.TP +restart +Stops and then starts knot server. +.TP +reload +Reload knot configuration and compiled zones. +.TP +running +check if server is running. +.TP +compile +Compile zone file. +.SH "SEE ALSO" +The full documentation for +.B Knot +is maintained as a Texinfo manual. If the +.B info +and +.B Knot +programs are properly installed at your site, the command +.IP +.B info Knot +.PP +should give you access to the complete manual. diff --git a/src/knotd.8 b/src/knotd.8 new file mode 100644 index 0000000..3275e9b --- /dev/null +++ b/src/knotd.8 @@ -0,0 +1,35 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. +.TH KNOT "1" "November 2011" "Knot DNS, version 0.8" "User Commands" +.SH NAME +Knot \- manual page for Knot DNS, version 0.8 +.SH SYNOPSIS +.B knotd +[\fIparameters\fR] +.SH DESCRIPTION +.SS "Parameters:" +.HP +\fB\-c\fR, \fB\-\-config\fR [file] Select configuration file. +.TP +\fB\-d\fR, \fB\-\-daemonize\fR +Run server as a daemon. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Verbose mode \- additional runtime information. +.TP +\fB\-V\fR, \fB\-\-version\fR +Print version of the server. +.TP +\fB\-h\fR, \fB\-\-help\fR +Print help and usage. +.SH "SEE ALSO" +The full documentation for +.B Knot +is maintained as a Texinfo manual. If the +.B info +and +.B Knot +programs are properly installed at your site, the command +.IP +.B info Knot +.PP +should give you access to the complete manual. diff --git a/src/libknot/common.h b/src/libknot/common.h new file mode 100644 index 0000000..9b2d8ae --- /dev/null +++ b/src/libknot/common.h @@ -0,0 +1,105 @@ +/*! + * \file common.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Common macros, includes and utilities. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#ifdef HAVE_LIBLDNS +#define TEST_WITH_LDNS +#endif + +#ifndef _KNOT_COMMON_H_ +#define _KNOT_COMMON_H_ + +#define KNOT_NAME "lib" PACKAGE_NAME // Project name +#define KNOT_VER PACKAGE_VERSION // 0xMMIIRR (MAJOR,MINOR,REVISION) + +#ifndef UINT_DEFINED +typedef unsigned int uint; /*!< \brief Unsigned. */ +#define UINT_DEFINED +#endif + +/*! \brief If defined, zone structures will use hash table for lookup. */ +#define USE_HASH_TABLE + +/*! \brief Eliminate compiler warning with unused parameters. */ +#define UNUSED(param) (void)(param) + +/*! \brief Type-safe minimum macro. */ +#define MIN(a, b) \ + ({ typeof (a) _a = (a); typeof (b) _b = (b); _a < _b ? _a : _b; }) + +/*! \brief Type-safe maximum macro. */ +#define MAX(a, b) \ + ({ typeof (a) _a = (a); typeof (b) _b = (b); _a > _b ? _a : _b; }) + +/* Optimisation macros. */ +#ifndef likely +/*! \brief Optimize for x to be true value. */ +#define likely(x) __builtin_expect((x),1) +#endif +#ifndef unlikely +/*! \brief Optimize for x to be false value. */ +#define unlikely(x) __builtin_expect((x),0) +#endif + +/* Optimisation macros. */ +#ifndef likely +/*! \brief Optimize for x to be true value. */ +#define likely(x) __builtin_expect((x),1) +#endif +#ifndef unlikely +/*! \brief Optimize for x to be false value. */ +#define unlikely(x) __builtin_expect((x),0) +#endif + +/*! \todo Refactor theese. We should have an allocator function handling this.*/ +#ifndef ERR_ALLOC_FAILED +#define ERR_ALLOC_FAILED fprintf(stderr, "Allocation failed at %s:%d (%s ver.%s)\n", \ + __FILE__, __LINE__, KNOT_NAME, KNOT_VER) +#endif + +#ifndef CHECK_ALLOC_LOG +#define CHECK_ALLOC_LOG(var, ret) \ + do { \ + if ((var) == NULL) { \ + ERR_ALLOC_FAILED; \ + return (ret); \ + } \ + } while (0) +#endif + +#ifndef CHECK_ALLOC +#define CHECK_ALLOC(var, ret) \ + do { \ + if ((var) == NULL) { \ + return (ret); \ + } \ + } while (0) +#endif + +#endif /* _KNOT_COMMON_H_ */ + +/*! @} */ diff --git a/src/libknot/consts.h b/src/libknot/consts.h new file mode 100644 index 0000000..4249763 --- /dev/null +++ b/src/libknot/consts.h @@ -0,0 +1,108 @@ +/*! + * \file consts.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Contains some DNS-related constants. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_CONSTS_H_ +#define _KNOT_CONSTS_H_ + +#include <stdint.h> +#include "util/descriptor.h" + +/* + * OPCODEs + */ +typedef enum knot_opcode { + KNOT_OPCODE_QUERY = 0, /* a standard query (QUERY) */ + KNOT_OPCODE_IQUERY = 1, /* an inverse query (IQUERY) */ + KNOT_OPCODE_STATUS = 2, /* a server status request (STATUS) */ + KNOT_OPCODE_NOTIFY = 4, /* NOTIFY */ + KNOT_OPCODE_UPDATE = 5, /* Dynamic update */ + KNOT_OPCODE_OFFSET = 14 +} knot_opcode_t; + +/*! + * \brief Query types (internal use only). + * + * This type encompasses the different query types distinguished by both the + * OPCODE and the QTYPE. + */ +typedef enum knot_packet_type { + KNOT_QUERY_NORMAL, /*!< Normal query. */ + KNOT_QUERY_AXFR, /*!< Request for AXFR transfer. */ + KNOT_QUERY_IXFR, /*!< Request for IXFR transfer. */ + KNOT_QUERY_NOTIFY, /*!< NOTIFY query. */ + KNOT_QUERY_UPDATE, /*!< Dynamic update. */ + KNOT_RESPONSE_NORMAL, /*!< Normal response. */ + KNOT_RESPONSE_AXFR, /*!< AXFR transfer response. */ + KNOT_RESPONSE_IXFR, /*!< IXFR transfer response. */ + KNOT_RESPONSE_NOTIFY /*!< NOTIFY response. */ +} knot_packet_type_t; + +/* + * RCODEs + */ +typedef enum knot_rcode { + KNOT_RCODE_NOERROR = 0, /* No error condition */ + KNOT_RCODE_FORMERR = 1, /* Format error */ + KNOT_RCODE_SERVFAIL = 2, /* Server failure */ + KNOT_RCODE_NXDOMAIN = 3, /* Name Error */ + KNOT_RCODE_NOTIMPL = 4, /* Not implemented */ + KNOT_RCODE_REFUSED = 5, /* Refused */ + KNOT_RCODE_YXDOMAIN = 6, /* name should not exist */ + KNOT_RCODE_YXRRSET = 7, /* rrset should not exist */ + KNOT_RCODE_NXRRSET = 8, /* rrset does not exist */ + KNOT_RCODE_NOTAUTH = 9, /* server not authoritative */ + KNOT_RCODE_NOTZONE = 10, /* name not inside zone */ +} knot_rcode_t; + +typedef enum knot_tsig_rcode { + KNOT_TSIG_RCODE_BADSIG = 16, + KNOT_TSIG_RCODE_BADKEY = 17, + KNOT_TSIG_RCODE_BADTIME = 18 +} knot_tsig_rcode_t; + +/* + * CLASSes + */ +//typedef enum knot_class { +// KNOT_CLASS_IN = 1, /* Class IN */ +// KNOT_CLASS_CS = 2, /* Class CS */ +// KNOT_CLASS_CH = 3, /* Class CHAOS */ +// KNOT_CLASS_HS = 4, /* Class HS */ +// KNOT_CLASS_NONE = 254, /* Class NONE rfc2136 */ +// KNOT_CLASS_ANY = 255 /* Class ANY */ +//} knot_class_t; + +/* + * Other + */ +typedef enum knot_const { + KNOT_MAX_DNAME_LENGTH = 255, + KNOT_MAX_DNAME_LABELS = 127 // 1-char labels +} knot_const_t; + +#endif /* _KNOT_CONSTS_H_ */ + +/*! @} */ diff --git a/src/libknot/dname.c b/src/libknot/dname.c new file mode 100644 index 0000000..869b342 --- /dev/null +++ b/src/libknot/dname.c @@ -0,0 +1,1070 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdint.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <ctype.h> // tolower() + +#include "common.h" +#include "util/error.h" +#include "dname.h" +#include "consts.h" +#include "util/tolower.h" +#include "util/debug.h" +#include "util/utils.h" +#include "util/wire.h" + +/*! \todo dnames allocated from TLS cache will be discarded after thread + * termination. This shouldn't happpen. + */ +#if 0 +/* + * Memory cache. + */ +#include "common/slab/slab.h" +#include <stdio.h> +#include <pthread.h> + +/*! \brief TLS unique key for each thread cache. */ +static pthread_key_t dname_ckey; +static pthread_once_t dname_once = PTHREAD_ONCE_INIT; + +/*! \brief Destroy thread dname cache (automatically called). */ +static void knot_dname_cache_free(void *ptr) +{ + slab_cache_t* cache = (slab_cache_t*)ptr; + if (cache) { + slab_cache_destroy(cache); + free(cache); + } +} + +/*! \brief Cleanup for main() TLS. */ +static void knot_dname_cache_main_free() +{ + knot_dname_cache_free(pthread_getspecific(dname_ckey)); +} + +static void knot_dname_cache_init() +{ + (void) pthread_key_create(&dname_ckey, knot_dname_cache_free); + atexit(knot_dname_cache_main_free); // Main thread cleanup +} +#endif + +/*! + * \brief Allocate item from thread cache. + * \retval Allocated dname instance on success. + * \retval NULL on error. + */ +static knot_dname_t* knot_dname_alloc() +{ + return malloc(sizeof(knot_dname_t)); + + /*! \todo dnames allocated from TLS cache will be discarded after thread + * termination. This shouldn't happpen. + */ +#if 0 + /* Initialize dname cache TLS key. */ + (void)pthread_once(&dname_once, knot_dname_cache_init); + + /* Create cache if not exists. */ + slab_cache_t* cache = pthread_getspecific(dname_ckey); + if (unlikely(!cache)) { + cache = malloc(sizeof(slab_cache_t)); + if (!cache) { + return 0; + } + + /* Initialize cache. */ + slab_cache_init(cache, sizeof(knot_dname_t)); + (void)pthread_setspecific(dname_ckey, cache); + } + + return slab_cache_alloc(cache); +#endif +} + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ + +static int knot_dname_set(knot_dname_t *dname, uint8_t *wire, + short wire_size, const uint8_t *labels, + short label_count) +{ + dname->name = wire; + dname->size = wire_size; + dname->label_count = label_count; + + assert(label_count >= 0); + + dname->labels = (uint8_t *)malloc(dname->label_count * sizeof(uint8_t)); + CHECK_ALLOC_LOG(dname->labels, -1); + memcpy(dname->labels, labels, dname->label_count); + + return 0; +} + +/*! + * \brief Converts domain name from string representation to wire format. + * + * This function also allocates the space for the wire format. + * + * \param name Domain name in string representation (presentation format). + * \param size Size of the given domain name in characters (not counting the + * terminating 0 character. + * \param dname Domain name where to store the wire format. + * + * \return Size of the wire format of the domain name in octets. If 0, no + * space has been allocated. + * + * \todo handle \X and \DDD (RFC 1035 5.1) or it can be handled by the parser? + */ +static int knot_dname_str_to_wire(const char *name, uint size, + knot_dname_t *dname) +{ + if (size > KNOT_MAX_DNAME_LENGTH) { + return -1; + } + + uint wire_size; + int root = (*name == '.' && size == 1); + // root => different size + if (root) { + wire_size = 1; + } else { + wire_size = size + 1; + } + + uint8_t *wire; + uint8_t labels[KNOT_MAX_DNAME_LABELS]; + short label_count = 0; + + // signed / unsigned issues?? + wire = (uint8_t *)malloc(wire_size * sizeof(uint8_t)); + if (wire == NULL) { + return -1; + } + + dbg_dname("Allocated space for wire format of dname: %p\n", + wire); + + if (root) { + *wire = '\0'; + label_count = 0; + return knot_dname_set(dname, wire, wire_size, labels, + label_count); + } + + const uint8_t *ch = (const uint8_t *)name; + uint8_t *label_start = wire; + uint8_t *w = wire + 1; + uint8_t label_length = 0; + + while (ch - (const uint8_t *)name < size) { + assert(w - wire - 1 == ch - (const uint8_t *)name); + + if (*ch == '.') { + dbg_dname("Position %zd (%p): " + "label length: %u\n", + label_start - wire, + label_start, label_length); + *label_start = label_length; + labels[label_count++] = label_start - wire; + label_start = w; + label_length = 0; + } else { + assert(w - wire < wire_size); + dbg_dname("Position %zd (%p): character: %c\n", + w - wire, w, *ch); + *w = *ch; + ++label_length; + } + + ++w; + ++ch; + assert(ch >= (const uint8_t *)name); + } + + --ch; + if (*ch == '.') { // put 0 for root label if the name ended with . + --w; + dbg_dname("Position %zd (%p): character: (null)\n", + w - wire, w); + *w = 0; + } else { // otherwise we did not save the last label length + dbg_dname("Position %zd (%p): " + "label length: %u\n", + label_start - wire, + label_start, label_length); + *label_start = label_length; + labels[label_count++] = label_start - wire; + } + + return knot_dname_set(dname, wire, wire_size, labels, label_count); +} + +/*----------------------------------------------------------------------------*/ + +static inline int knot_dname_tolower(uint8_t c, int cs) +{ + return (cs) ? c : knot_tolower(c); +} + +/*----------------------------------------------------------------------------*/ + +static int knot_dname_compare_labels(const uint8_t *label1, + const uint8_t *label2, int cs) +{ + const uint8_t *pos1 = label1; + const uint8_t *pos2 = label2; + + int label_length = (*pos1 < *pos2) ? *pos1 : *pos2; + int i = 0; + + while (i < label_length + && knot_dname_tolower(*(++pos1), cs) + == knot_dname_tolower(*(++pos2), cs)) { + ++i; + } + + if (i < label_length) { // difference in some octet + return (knot_dname_tolower(*pos1, cs) + - knot_dname_tolower(*pos2, cs)); + } + + return (label1[0] - label2[0]); +} + +/*----------------------------------------------------------------------------*/ + +static int knot_dname_find_labels(knot_dname_t *dname, int alloc) +{ + const uint8_t *name = dname->name; + const uint8_t *pos = name; + const uint size = dname->size; + + uint8_t labels[KNOT_MAX_DNAME_LABELS]; + short label_count = 0; + + while (pos - name < size && *pos != '\0') { + labels[label_count++] = pos - name; + pos += *pos + 1; + } + + // TODO: how to check if the domain name has right format? + if (pos - name > size || *pos != '\0') { + dbg_dname("Wrong wire format of domain name!\n"); + dbg_dname("Position: %d, character: %d, expected" + " size: %d\n", pos - name, *pos, size); + return -1; + } + + if (alloc) { + dname->labels + = (uint8_t *)malloc(label_count * sizeof(uint8_t)); + CHECK_ALLOC_LOG(dname->labels, KNOT_ENOMEM); + } + + memcpy(dname->labels, labels, label_count); + dname->label_count = label_count; + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_dname_cmp(const knot_dname_t *d1, const knot_dname_t *d2, + int cs) +{ +dbg_dname_exec( + char *name1 = knot_dname_to_str(d1); + char *name2 = knot_dname_to_str(d2); + + dbg_dname("Comparing dnames %s and %s\n", + name1, name2); + + for (int i = 0; i < strlen(name1); ++i) { + name1[i] = knot_tolower(name1[i]); + } + for (int i = 0; i < strlen(name2); ++i) { + name2[i] = knot_tolower(name2[i]); + } + + dbg_dname("After to lower: %s and %s\n", + name1, name2); + + free(name1); + free(name2); +); + + if (!cs && d1 == d2) { + return 0; + } + + int l1 = d1->label_count; + int l2 = d2->label_count; + dbg_dname("Label counts: %d and %d\n", l1, l2); + assert(l1 >= 0); + assert(l2 >= 0); + + // compare labels from last to first + while (l1 > 0 && l2 > 0) { + dbg_dname("Comparing labels %d and %d\n", + l1 - 1, l2 - 1); + dbg_dname(" at offsets: %d and %d\n", + d1->labels[l1 - 1], d2->labels[l2 - 1]); + int res = knot_dname_compare_labels( + &d1->name[d1->labels[--l1]], + &d2->name[d2->labels[--l2]], + cs); + if (res != 0) { + return res; + } // otherwise the labels are identical, continue with previous + } + + // if all labels matched, the shorter name is first + if (l1 == 0 && l2 > 0) { + return -1; + } + + if (l1 > 0 && l2 == 0) { + return 1; + } + + return 0; +} + +/*! \brief Destructor for reference counter. */ +static void knot_dname_dtor(struct ref_t *p) +{ + knot_dname_t *dname = (knot_dname_t *)p; + knot_dname_free(&dname); +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_new() +{ + knot_dname_t *dname = knot_dname_alloc(); + dname->name = NULL; + dname->size = 0; + dname->node = NULL; + dname->labels = NULL; + dname->label_count = -1; + dname->id = 0; + + /* Initialize reference counting. */ + ref_init(&dname->ref, knot_dname_dtor); + + /* Set reference counter to 1, caller should release it after use. */ + knot_dname_retain(dname); + + return dname; +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_new_from_str(const char *name, uint size, + struct knot_node *node) +{ + if (name == NULL || size == 0) { + return NULL; + } + +// knot_dname_t *dname = knot_dname_alloc(); + knot_dname_t *dname = knot_dname_new(); + + if (dname == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + knot_dname_str_to_wire(name, size, dname); + dbg_dname("Created dname with size: %d\n", dname->size); + dbg_dname("Label offsets: "); + for (int i = 0; i < dname->label_count; ++i) { + dbg_dname("%d, ", dname->labels[i]); + } + dbg_dname("\n"); + + if (dname->size <= 0) { + fprintf(stderr, "Could not parse domain name " + "from string: '%.*s'\n", size, name); + } + assert(dname->name != NULL); + + dname->node = node; + dname->id = 0; + + return dname; +} + +/*----------------------------------------------------------------------------*/ + +//int knot_dname_from_wire(knot_dname_t *dname, const uint8_t *name, +// uint size) +//{ +// int i = 0; +// uint8_t labels[KNOT_MAX_DNAME_LABELS]; +// int label_i = 0; + +// while (name[i] != 0) { +// labels[label_i++] = i; +// uint8_t label_length = name[i]; +// if (i + label_length >= size) { +// return -2; +// } +// for (int j = 1; j <= label_length; ++j) { +// } +// } +//} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_new_from_wire(const uint8_t *name, uint size, + struct knot_node *node) +{ + if (name == NULL) { /* && size != 0) { !OS: Nerozumjaju */ + dbg_dname("No name given!\n"); + return NULL; + } + + knot_dname_t *dname = knot_dname_new(); + + if (dname == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + dname->name = (uint8_t *)malloc(size * sizeof(uint8_t)); + if (dname->name == NULL) { + ERR_ALLOC_FAILED; + knot_dname_free(&dname); + return NULL; + } + + memcpy(dname->name, name, size); + dname->size = size; + + if (knot_dname_find_labels(dname, 1) != 0) { + knot_dname_free(&dname); + return NULL; + } + + dname->node = node; + dname->id = 0; + + return dname; +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_parse_from_wire(const uint8_t *wire, + size_t *pos, size_t size, + knot_node_t *node) +{ + uint8_t name[KNOT_MAX_DNAME_LENGTH]; + uint8_t labels[KNOT_MAX_DNAME_LABELS]; + + short l = 0; + size_t i = 0, p = *pos; + int pointer_used = 0; + + while (p < size && wire[p] != 0) { + labels[l] = i; + dbg_dname("Next label (%d.) position: %zu\n", l, i); + + if (knot_wire_is_pointer(wire + p)) { + // pointer. +// printf("Pointer.\n"); + p = knot_wire_get_pointer(wire + p); + if (!pointer_used) { + *pos += 2; + pointer_used = 1; + } + if (p >= size) { + return NULL; + } + } else { + // label; first byte is label length + uint8_t length = *(wire + p); +// printf("Label, length: %u.\n", length); + memcpy(name + i, wire + p, length + 1); + p += length + 1; + i += length + 1; + if (!pointer_used) { + *pos += length + 1; + } + ++l; + } + } + if (p >= size) { + return NULL; + } + + name[i] = 0; + if (!pointer_used) { + *pos += 1; + } + + knot_dname_t *dname = knot_dname_new(); + + if (dname == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + dname->name = (uint8_t *)malloc((i + 1) * sizeof(uint8_t)); + if (dname->name == NULL) { + ERR_ALLOC_FAILED; + knot_dname_free(&dname); + return NULL; + } + + memcpy(dname->name, name, i + 1); + dname->size = i + 1; + + dname->labels = (uint8_t *)malloc((l + 1) * sizeof(uint8_t)); + if (dname->labels == NULL) { + ERR_ALLOC_FAILED; + knot_dname_free(&dname); + return NULL; + } + memcpy(dname->labels, labels, l + 1); + + dname->label_count = l; + + dname->node = node; + + return dname; +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_from_wire(const uint8_t *name, uint size, + struct knot_node *node, knot_dname_t *target) +{ + if (name == NULL || target == NULL) { + return KNOT_EBADARG; + } + + memcpy(target->name, name, size); + target->size = size; + target->node = node; + target->id = 0; + + return knot_dname_find_labels(target, 0); +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_deep_copy(const knot_dname_t *dname) +{ + return knot_dname_new_from_wire(dname->name, dname->size, + dname->node); +} + +/*----------------------------------------------------------------------------*/ + +char *knot_dname_to_str(const knot_dname_t *dname) +{ + if (!dname || dname->size == 0) { + return 0; + } + + char *name; + + // root => special treatment + if (dname->size == 1) { + assert(dname->name[0] == 0); + name = (char *)malloc(2 * sizeof(char)); + name[0] = '.'; + name[1] = '\0'; + return name; + } + + name = (char *)malloc(dname->size * sizeof(char)); + if (name == NULL) { + return NULL; + } + + uint8_t *w = dname->name; + char *ch = name; + int i = 0; + + do { + assert(*w != 0); + int label_size = *(w++); + // copy the label + memcpy(ch, w, label_size); + i += label_size; + ch += label_size; + w += label_size; + if (w - dname->name < dname->size) { // another label following + *(ch++) = '.'; + ++i; + } + } while (i < dname->size - 1); + + *ch = 0; + assert(ch - name == dname->size - 1); + + return name; +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_to_lower(knot_dname_t *dname) +{ + if (dname == NULL) { + return KNOT_EBADARG; + } + + for (int i = 0; i < dname->size; ++i) { + dname->name[i] = knot_tolower(dname->name[i]); + } + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_to_lower_copy(const knot_dname_t *dname, char *name, + size_t size) +{ + if (dname == NULL || name == NULL || size < dname->size) { + return KNOT_EBADARG; + } + + for (int i = 0; i < dname->size; ++i) { + name[i] = knot_tolower(dname->name[i]); + } + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +const uint8_t *knot_dname_name(const knot_dname_t *dname) +{ + return dname->name; +} + +/*----------------------------------------------------------------------------*/ + +uint knot_dname_size(const knot_dname_t *dname) +{ + return dname->size; +} + +/*----------------------------------------------------------------------------*/ + +unsigned int knot_dname_id(const knot_dname_t *dname) +{ + return dname->id; +} + +/*----------------------------------------------------------------------------*/ + +uint8_t knot_dname_size_part(const knot_dname_t *dname, int labels) +{ + assert(labels < dname->label_count); + assert(dname->labels != NULL); + return (dname->labels[labels]); +} + +/*----------------------------------------------------------------------------*/ + +const struct knot_node *knot_dname_node(const knot_dname_t *dname, + int check_version) + +{ + if (check_version) { + return knot_node_current(dname->node); + } else { + return dname->node; + } +} + +/*----------------------------------------------------------------------------*/ + +struct knot_node *knot_dname_get_node(knot_dname_t *dname, + int check_version) +{ + if (check_version) { + return knot_node_get_current(dname->node); + } else { + return dname->node; + } +} + +/*----------------------------------------------------------------------------*/ + +void knot_dname_set_node(knot_dname_t *dname, knot_node_t *node) +{ + dname->node = node; +} + +/*----------------------------------------------------------------------------*/ + +void knot_dname_update_node(knot_dname_t *dname) +{ + knot_node_update_ref(&dname->node); +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_is_fqdn(const knot_dname_t *dname) +{ + return (dname->name[dname->size - 1] == '\0'); +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_left_chop(const knot_dname_t *dname) +{ + knot_dname_t *parent = knot_dname_new(); + if (parent == NULL) { + return NULL; + } + + parent->size = dname->size - dname->name[0] - 1; + parent->name = (uint8_t *)malloc(parent->size); + if (parent->name == NULL) { + ERR_ALLOC_FAILED; + knot_dname_free(&parent); + return NULL; + } + + parent->labels = (uint8_t *)malloc(dname->label_count - 1); + if (parent->labels == NULL) { + ERR_ALLOC_FAILED; + free(parent->name); + knot_dname_free(&parent); + return NULL; + } + + memcpy(parent->name, &dname->name[dname->name[0] + 1], parent->size); + + short first_label_length = dname->labels[1]; + + for (int i = 0; i < dname->label_count - 1; ++i) { + parent->labels[i] = dname->labels[i + 1] - first_label_length; + } + parent->label_count = dname->label_count - 1; + + return parent; +} + +/*----------------------------------------------------------------------------*/ + +void knot_dname_left_chop_no_copy(knot_dname_t *dname) +{ + // copy the name + short first_label_length = dname->labels[1]; + + if (dname->label_count > 1) { + memmove(dname->name, &dname->name[dname->labels[1]], + dname->size - first_label_length); + // adjust labels + for (int i = 0; i < dname->label_count - 1; ++i) { + dname->labels[i] = dname->labels[i + 1] + - first_label_length; + } + dname->label_count = dname->label_count - 1; + dname->size -= first_label_length; + } else { + dname->name[0] = '\0'; + dname->size = 1; + dname->label_count = 0; + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_is_subdomain(const knot_dname_t *sub, + const knot_dname_t *domain) +{ +dbg_dname_exec( + char *name1 = knot_dname_to_str(sub); + char *name2 = knot_dname_to_str(domain); + + dbg_dname("Checking if %s is subdomain of %s\n", + name1, name2); + free(name1); + free(name2); +); + + if (sub == domain) { + return 0; + } + + // if one of the names is fqdn and the other is not + if ((sub->name[sub->size - 1] == '\0' + && domain->name[domain->size - 1] != '\0') + || (sub->name[sub->size - 1] != '\0' + && domain->name[domain->size - 1] == '\0')) { + return 0; + } + + int l1 = sub->label_count; + int l2 = domain->label_count; + + dbg_dname("Label counts: %d and %d\n", l1, l2); + + if (l1 <= l2) { // if sub does not have more labes than domain + return 0; // it is not its subdomain + } + + // compare labels from last to first + while (l1 > 0 && l2 > 0) { + dbg_dname("Comparing labels %d and %d\n", + l1 - 1, l2 - 1); + dbg_dname(" at offsets: %d and %d\n", + sub->labels[l1 - 1], domain->labels[l2 - 1]); + // if some labels do not match + if (knot_dname_compare_labels(&sub->name[sub->labels[--l1]], + &domain->name[domain->labels[--l2]], 0) + != 0) { + return 0; // sub is not a subdomain of domain + } // otherwise the labels are identical, continue with previous + } + + // if all labels matched, it should be subdomain (more labels) + assert(l1 > l2); + + return 1; +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_is_wildcard(const knot_dname_t *dname) +{ + return (dname->size >= 2 + && dname->name[0] == 1 + && dname->name[1] == '*'); +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_matched_labels(const knot_dname_t *dname1, + const knot_dname_t *dname2) +{ + int l1 = dname1->label_count; + int l2 = dname2->label_count; + + // compare labels from last to first + int matched = 0; + while (l1 > 0 && l2 > 0) { + int res = knot_dname_compare_labels( + &dname1->name[dname1->labels[--l1]], + &dname2->name[dname2->labels[--l2]], 0); + if (res == 0) { + ++matched; + } else { + break; + } + } + + return matched; +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_label_count(const knot_dname_t *dname) +{ + return dname->label_count; +} + +/*----------------------------------------------------------------------------*/ + +uint8_t knot_dname_label_size(const knot_dname_t *dname, int i) +{ +// printf("Returning size of %d. label starting on %d\n", +// i, dname->labels[i]); +// printf("Label count: %d, size of %d. label: %d, size of %d.label: %d\n", +// dname->label_count, i, dname->labels[i], i + 1, +// dname->labels[i+1]); +// printf("Size from the name: %u\n", dname->name[dname->labels[i]]); +// printf("Size from label offsets: %u\n", +// dname->labels[i + 1] - dname->labels[i]); + + assert(i >= 0); + assert(dname->size == 1 || i + 1 == dname->label_count + || dname->labels[i + 1] - dname->labels[i] - 1 + == dname->name[dname->labels[i]]); + return dname->name[dname->labels[i]]; +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *dname, + int size, + const knot_dname_t *suffix) +{ +dbg_dname_exec( + char *name = knot_dname_to_str(dname); + dbg_dname("Replacing suffix of name %s, size %d with ", name, + size); + free(name); + name = knot_dname_to_str(suffix); + dbg_dname("%s (size %d)\n", name, suffix->size); + free(name); +); + knot_dname_t *res = knot_dname_new(); + CHECK_ALLOC(res, NULL); + + res->size = dname->size - size + suffix->size; + + dbg_dname("Allocating %d bytes...\n", res->size); + res->name = (uint8_t *)malloc(res->size); + if (res->name == NULL) { + knot_dname_free(&res); + return NULL; + } + + dbg_dname_hex((char *)res->name, res->size); + + dbg_dname("Copying %d bytes from the original name.\n", + dname->size - size); + memcpy(res->name, dname->name, dname->size - size); + dbg_dname_hex((char *)res->name, res->size); + + dbg_dname("Copying %d bytes from the suffix.\n", suffix->size); + memcpy(res->name + dname->size - size, suffix->name, suffix->size); + + dbg_dname_hex((char *)res->name, res->size); + + knot_dname_find_labels(res, 1); + + return res; +} + +/*----------------------------------------------------------------------------*/ + +void knot_dname_free(knot_dname_t **dname) +{ + if (dname == NULL || *dname == NULL) { + return; + } + +// char *name = knot_dname_to_str((*dname)); + +// printf("freeing in dname: %s %p\n", name, *dname); + +// free(name); + + + if ((*dname)->name != NULL) { + free((*dname)->name); + } + + if((*dname)->labels != NULL) { + free((*dname)->labels); + } + + +// slab_free(*dname); + free(*dname); + *dname = NULL; +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_compare(const knot_dname_t *d1, const knot_dname_t *d2) +{ + return knot_dname_cmp(d1, d2, 0); +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_compare_cs(const knot_dname_t *d1, const knot_dname_t *d2) +{ + return knot_dname_cmp(d1, d2, 1); +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_cat(knot_dname_t *d1, const knot_dname_t *d2) +{ + if (d2->size == 0) { + return d1; + } + + if (knot_dname_is_fqdn(d1)) { + return NULL; + } + + // allocate new space + uint8_t *new_dname = (uint8_t *)malloc(d1->size + d2->size); + CHECK_ALLOC_LOG(new_dname, NULL); + + uint8_t *new_labels = (uint8_t *)malloc(d1->label_count + + d2->label_count); + if (new_labels == NULL) { + ERR_ALLOC_FAILED; + free(new_dname); + return NULL; + } + + dbg_dname("1: copying %d bytes from adress %p to %p\n", + d1->size, d1->name, new_dname); + + memcpy(new_dname, d1->name, d1->size); + + dbg_dname("2: copying %d bytes from adress %p to %p\n", + d2->size, d2->name, new_dname + d1->size); + + memcpy(new_dname + d1->size, d2->name, d2->size); + + // update labels + memcpy(new_labels, d1->labels, d1->label_count); + for (int i = 0; i < d2->label_count; ++i) { + new_labels[d1->label_count + i] = d2->labels[i] + d1->size; + } + + uint8_t *old_labels = d1->labels; + d1->labels = new_labels; + free(old_labels); + d1->label_count += d2->label_count; + + uint8_t *old_name = d1->name; + d1->name = new_dname; + free(old_name); + + d1->size += d2->size; + + return d1; +} + +void knot_dname_set_id(knot_dname_t *dname, unsigned int id) +{ + dname->id = id; +} + +unsigned int knot_dname_get_id(const knot_dname_t *dname) +{ + if (dname != NULL) { + return dname->id; + } else { + return 0; /* 0 should never be used and is reserved for err. */ + } +} diff --git a/src/libknot/dname.h b/src/libknot/dname.h new file mode 100644 index 0000000..c0e3f35 --- /dev/null +++ b/src/libknot/dname.h @@ -0,0 +1,428 @@ +/*! + * \file dname.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Domain name structure and API for manipulating it. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_DNAME_H_ +#define _KNOT_DNAME_H_ + +#include <stdint.h> +#include <string.h> +#include "common/ref.h" + +struct knot_node; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure for representing a domain name. + * + * Stores the domain name in wire format. + * + * \todo Consider restricting to FQDN only (see knot_dname_new_from_str()). + */ +struct knot_dname { + ref_t ref; /*!< Reference counting. */ + uint8_t *name; /*!< Wire format of the domain name. */ + /*! + * \brief Size of the domain name in octets. + * \todo Is this needed? Every dname should end with \0 or pointer. + */ + unsigned int size; + uint8_t *labels; + unsigned short label_count; + struct knot_node *node; /*!< Zone node the domain name belongs to. */ + unsigned int id; /*!< ID of domain name used in zone dumping. */ +}; + +typedef struct knot_dname knot_dname_t; + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Creates empty dname structure (no name, no owner node). + * + * \note Newly created dname is referenced, caller is responsible for releasing + * it after use. + * + * \return Newly allocated and initialized dname structure. + * + * \todo Possibly useless. + */ +knot_dname_t *knot_dname_new(); + +/*! + * \brief Creates a dname structure from domain name given in presentation + * format. + * + * The resulting domain name is stored in wire format, but it may not end with + * root label (0). + * + * \note Newly created dname is referenced, caller is responsible for releasing + * it after use. + * + * \param name Domain name in presentation format (labels separated by dots). + * \param size Size of the domain name (count of characters with all dots). + * \param node Zone node the domain name belongs to. Set to NULL if not + * applicable. + * + * \return Newly allocated and initialized dname structure representing the + * given domain name. + */ +knot_dname_t *knot_dname_new_from_str(const char *name, unsigned int size, + struct knot_node *node); + +/*! + * \brief Creates a dname structure from domain name given in wire format. + * + * \note The name is copied into the structure. + * \note If the given name is not a FQDN, the result will be neither. + * \note Newly created dname is referenced, caller is responsible for releasing + * it after use. + * + * \param name Domain name in wire format. + * \param size Size of the domain name in octets. + * \param node Zone node the domain name belongs to. Set to NULL if not + * applicable. + * + * \return Newly allocated and initialized dname structure representing the + * given domain name. + * + * \todo This function does not check if the given data is in correct wire + * format at all. It thus creates a invalid domain name, which if passed + * e.g. to knot_dname_to_str() may result in crash. Decide whether it + * is OK to retain this and check the data in other functions before + * calling this one, or if it should verify the given data. + */ +knot_dname_t *knot_dname_new_from_wire(const uint8_t *name, + unsigned int size, + struct knot_node *node); + +knot_dname_t *knot_dname_parse_from_wire(const uint8_t *wire, + size_t *pos, size_t size, + struct knot_node *node); + +/*! + * \brief Initializes domain name by the name given in wire format. + * + * \note The name is copied into the structure. + * \note If there is any name in the structure, it will be replaced. + * \note If the given name is not a FQDN, the result will be neither. + * + * \param name Domain name in wire format. + * \param size Size of the domain name in octets. + * \param node Zone node the domain name belongs to. Set to NULL if not + * applicable. + * \param target Domain name structure to initialize. + * + * \retval KNOT_EOK on success. + * \retval KNOT_ENOMEM if allocation of labels info failed. + * \retval KNOT_EBADARG if name or target is null. + * + * \todo This function does not check if the given data is in correct wire + * format at all. It thus creates a invalid domain name, which if passed + * e.g. to knot_dname_to_str() may result in crash. Decide whether it + * is OK to retain this and check the data in other functions before + * calling this one, or if it should verify the given data. + */ +int knot_dname_from_wire(const uint8_t *name, unsigned int size, + struct knot_node *node, knot_dname_t *target); + +/*! + * \brief Duplicates the given domain name. + * + * \note Copied dname referense count is reset to 1, caller is responsible + * for releasing it after use. + * + * \param dname Domain name to be copied. + * + * \return New domain name which is an exact copy of \a dname. + */ +knot_dname_t *knot_dname_deep_copy(const knot_dname_t *dname); + +/*! + * \brief Converts the given domain name to string representation. + * + * \note Allocates new memory, remember to free it. + * + * \param dname Domain name to be converted. + * + * \return 0-terminated string representing the given domain name in + * presentation format. + */ +char *knot_dname_to_str(const knot_dname_t *dname); + +int knot_dname_to_lower(knot_dname_t *dname); + +int knot_dname_to_lower_copy(const knot_dname_t *dname, char *name, + size_t size); + +/*! + * \brief Returns the domain name in wire format. + * + * \param dname Domain name. + * + * \return Wire format of the domain name. + */ +const uint8_t *knot_dname_name(const knot_dname_t *dname); + +/*! + * \brief Returns size of the given domain name. + * + * \param dname Domain name to get the size of. + * + * \return Size of the domain name in wire format in octets. + */ +unsigned int knot_dname_size(const knot_dname_t *dname); + +unsigned int knot_dname_id(const knot_dname_t *dname); + +/*! + * \brief Returns size of a part of domain name. + * + * \param dname Domain name. + * \param labels Count of labels to get the size of (counted from left). + * + * \return Size of first \a labels labels of \a dname, counted from left. + */ +uint8_t knot_dname_size_part(const knot_dname_t *dname, int labels); + +/*! + * \brief Returns the zone node the domain name belongs to. + * + * \param dname Domain name to get the zone node of. + * + * \return Zone node the domain name belongs to or NULL if none. + */ +const struct knot_node *knot_dname_node(const knot_dname_t *dname, + int check_version); + +struct knot_node *knot_dname_get_node(knot_dname_t *dname, + int check_version); + +void knot_dname_set_node(knot_dname_t *dname, struct knot_node *node); + +void knot_dname_update_node(knot_dname_t *dname); + +void knot_dname_set_node(knot_dname_t *dname, struct knot_node *node); + +/*! + * \brief Checks if the given domain name is a fully-qualified domain name. + * + * \param dname Domain name to check. + * + * \retval <> 0 if \a dname is a FQDN. + * \retval 0 otherwise. + */ +int knot_dname_is_fqdn(const knot_dname_t *dname); + +/*! + * \brief Creates new domain name by removing leftmost label from \a dname. + * + * \note Newly created dname reference count is set to 1, caller is responsible + * for releasing it after use. + * + * \param dname Domain name to remove the first label from. + * + * \return New domain name with the same labels as \a dname, except for the + * leftmost label, which is removed. + */ +knot_dname_t *knot_dname_left_chop(const knot_dname_t *dname); + +/*! + * \brief Removes leftmost label from \a dname. + * + * \param dname Domain name to remove the first label from. + */ +void knot_dname_left_chop_no_copy(knot_dname_t *dname); + +/*! + * \brief Checks if one domain name is a subdomain of other. + * + * \param sub Domain name to be the possible subdomain. + * \param domain Domain name to be the possible parent domain. + * + * \retval <> 0 if \a sub is a subdomain of \a domain. + * \retval 0 otherwise. + */ +int knot_dname_is_subdomain(const knot_dname_t *sub, + const knot_dname_t *domain); + +/*! + * \brief Checks if the domain name is a wildcard. + * + * \param dname Domain name to check. + * + * \retval <> 0 if \a dname is a wildcard domain name. + * \retval 0 otherwise. + */ +int knot_dname_is_wildcard(const knot_dname_t *dname); + +/*! + * \brief Returns the number of labels common for the two domain names (counted + * from the rightmost label. + * + * \param dname1 First domain name. + * \param dname2 Second domain name. + * + * \return Number of labels common for the two domain names. + */ +int knot_dname_matched_labels(const knot_dname_t *dname1, + const knot_dname_t *dname2); + +/*! + * \brief Returns the number of labels in the domain name. + * + * \param dname Domain name to get the label count of. + * + * \return Number of labels in \a dname. + * + * \todo Find out if this counts the root label also. + */ +int knot_dname_label_count(const knot_dname_t *dname); + +/*! + * \brief Returns the size of the requested label in the domain name. + * + * \param dname Domain name to get the label size from. + * \param i Index of the label (0 is the leftmost label). + * + * \return Size of \a i-th label in \a dname (counted from left). + */ +uint8_t knot_dname_label_size(const knot_dname_t *dname, int i); + +/*! + * \brief Replaces the suffix of given size in one domain name with other domain + * name. + * + * \param dname Domain name where to replace the suffix. + * \param size Size of the suffix to be replaced. + * \param suffix New suffix to be used as a replacement. + * + * \return New domain name created by replacing suffix of \a dname of size + * \a size with \a suffix. + */ +knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *dname, + int size, + const knot_dname_t *suffix); + +/*! + * \brief Destroys the given domain name. + * + * Frees also the data within the struct. This is somewhat different behaviour + * than that of RDATA and RRSet structures which do not deallocate their + * contents. + * + * Sets the given pointer to NULL. + * + * \param dname Domain name to be destroyed. + */ +void knot_dname_free(knot_dname_t **dname); + +/*! + * \brief Compares two domain names (case insensitive). + * + * \param d1 First domain name. + * \param d2 Second domain name. + * + * \retval < 0 if \a d1 goes before \a d2 in canonical order. + * \retval > 0 if \a d1 goes after \a d2 in canonical order. + * \retval 0 if the domain names are identical. + */ +int knot_dname_compare(const knot_dname_t *d1, const knot_dname_t *d2); + +/*! + * \brief Compares two domain names (case sensitive). + * + * \param d1 First domain name. + * \param d2 Second domain name. + * + * \retval < 0 if \a d1 goes before \a d2 in canonical order. + * \retval > 0 if \a d1 goes after \a d2 in canonical order. + * \retval 0 if the domain names are identical. + */ +int knot_dname_compare_cs(const knot_dname_t *d1, const knot_dname_t *d2); + +/*! + * \brief Concatenates two domain names. + * + * \note Member \a node is ignored, i.e. preserved. + * + * \param d1 First domain name (will be modified). + * \param d2 Second domain name (will not be modified). + * + * \return The concatenated domain name (i.e. modified \a d1) or NULL if + * the operation is not valid (e.g. \a d1 is a FQDN). + */ +knot_dname_t *knot_dname_cat(knot_dname_t *d1, const knot_dname_t *d2); + +void knot_dname_set_id(knot_dname_t *dname, unsigned int id); + +unsigned int knot_dname_get_id(const knot_dname_t *dname); + +/*! + * \brief Increment reference counter for dname. + * + * Function makes shallow copy (reference). + * + * \param dname Referenced dname. + */ +static inline void knot_dname_retain(knot_dname_t *dname) { + if (dname) { + ref_retain(&dname->ref); +// char *name = knot_dname_to_str(dname); +// printf("retain: %s %p %d\n", name, dname, dname->ref.count); +// free(name); + + } +} + +/*#define knot_dname_retain(d) \ + knot_dname_retain_((d));\ + if ((d))\ + printf("dname_retain: %s() at %s:%d, %p refcount=%zu\n",\ + __func__, __FILE__, __LINE__, d, (d)->ref.count) */ + +/*! + * \brief Decrement reference counter for dname. + * + * \param dname Referenced dname. + */ +static inline void knot_dname_release(knot_dname_t *dname) { + if (dname) { +// char *name = knot_dname_to_str(dname); +// printf("releasing: %p %s %d\n", dname, name, dname->ref.count - 1); +// free(name); + ref_release(&dname->ref); + } +} + +/*#define knot_dname_release(d) \ + if ((d))\ + printf("dname_release: %s() at %s:%d, %p refcount=%zu\n",\ + __func__, __FILE__, __LINE__, d, (d)->ref.count-1);\ + knot_dname_release_((d)) */ + +#endif /* _KNOT_DNAME_H_ */ + +/*! @} */ diff --git a/src/libknot/edns.c b/src/libknot/edns.c new file mode 100644 index 0000000..05ebd7b --- /dev/null +++ b/src/libknot/edns.c @@ -0,0 +1,416 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <assert.h> + +#include "edns.h" +#include "common.h" +#include "util/descriptor.h" +#include "util/debug.h" +#include "util/error.h" + +/*! \brief Various EDNS constatns. */ +enum knot_edns_consts { + /*! \brief Mask for the DO bit. */ + KNOT_EDNS_DO_MASK = (uint16_t)0x8000, + /*! \brief Step for allocation of space for option entries. */ + KNOT_EDNS_OPTION_STEP = 1 +}; + +/*! \brief Minimum size of EDNS OPT RR in wire format. */ +static const short KNOT_EDNS_MIN_SIZE = 11; + +/*----------------------------------------------------------------------------*/ + +knot_opt_rr_t *knot_edns_new() +{ + knot_opt_rr_t *opt_rr = (knot_opt_rr_t *)malloc( + sizeof(knot_opt_rr_t)); + CHECK_ALLOC_LOG(opt_rr, NULL); + opt_rr->size = KNOT_EDNS_MIN_SIZE; + opt_rr->option_count = 0; + opt_rr->options_max = 0; + + opt_rr->ext_rcode = 0; + opt_rr->flags = 0; + opt_rr->version = 0; + + return opt_rr; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_new_from_wire(knot_opt_rr_t *opt_rr, const uint8_t *wire, + size_t max_size) +{ + const uint8_t *pos = wire; + int parsed = 0; + + if (pos == NULL || max_size == 0 || opt_rr == NULL) { + return KNOT_EBADARG; + } + + if (max_size < KNOT_EDNS_MIN_SIZE) { + dbg_edns("Not enough data to parse OPT RR header.\n"); + return KNOT_EFEWDATA; + } + + // owner of EDNS OPT RR must be root (0) + if (*pos != 0) { + dbg_edns("EDNS packet malformed (expected root " + "domain as owner).\n"); + return KNOT_EMALF; + } + pos += 1; + + // check the type of the record (must be OPT) + if (knot_wire_read_u16(pos) != KNOT_RRTYPE_OPT) { + dbg_edns("EDNS packet malformed (expected OPT type" + ".\n"); + return KNOT_EMALF; + } + pos += 2; + + opt_rr->payload = knot_wire_read_u16(pos); + dbg_edns("Parsed payload: %u\n", opt_rr->payload); + + pos += 2; + opt_rr->ext_rcode = *(pos++); + opt_rr->version = *(pos++); + opt_rr->flags = knot_wire_read_u16(pos); + pos += 2; + + parsed = KNOT_EDNS_MIN_SIZE; + + // ignore RDATA, but move pos behind them + uint16_t rdlength = knot_wire_read_u16(pos); + pos += 2; + + if (max_size - parsed < rdlength) { + dbg_edns("Not enough data to parse OPT RR.\n"); + return KNOT_EFEWDATA; + } + + while (parsed < rdlength + KNOT_EDNS_MIN_SIZE) { + if (max_size - parsed < 4) { + dbg_edns("Not enough data to parse OPT RR" + " OPTION header.\n"); + return KNOT_EFEWDATA; + } + uint16_t code = knot_wire_read_u16(pos); + pos += 2; + uint16_t length = knot_wire_read_u16(pos); + pos += 2; + dbg_edns("EDNS OPTION: Code: %u, Length: %u\n", + code, length); + if (max_size - parsed - 4 < length) { + dbg_edns("Not enough data to parse OPT RR" + " OPTION data.\n"); + return KNOT_EFEWDATA; + } + int ret; + if ((ret = + knot_edns_add_option(opt_rr, code, length, pos)) != 0) { + dbg_edns("Error parsing OPT option field.\n"); + return ret; + } + pos += length; + parsed += length + 4; + } + + return parsed; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_new_from_rr(knot_opt_rr_t *opt_rr, + const knot_rrset_t *rrset) +{ + if (opt_rr == NULL || rrset == NULL + || knot_rrset_type(rrset) != KNOT_RRTYPE_OPT) { + return KNOT_EBADARG; + } + + dbg_edns("Parsing payload.\n"); + opt_rr->payload = knot_rrset_class(rrset); + + // the TTL has switched bytes + uint32_t ttl; + dbg_edns("TTL: %u\n", knot_rrset_ttl(rrset)); + knot_wire_write_u32((uint8_t *)&ttl, knot_rrset_ttl(rrset)); + // first byte of TTL is extended RCODE + dbg_edns("TTL: %u\n", ttl); + memcpy(&opt_rr->ext_rcode, &ttl, 1); + dbg_edns("Parsed extended RCODE: %u.\n", opt_rr->ext_rcode); + // second is the version + memcpy(&opt_rr->version, (const uint8_t *)(&ttl) + 1, 1); + dbg_edns("Parsed version: %u.\n", opt_rr->version); + // third and fourth are flags + opt_rr->flags = knot_wire_read_u16((const uint8_t *)(&ttl) + 2); + dbg_edns("Parsed flags: %u.\n", opt_rr->flags); + // size of the header, options are counted elsewhere + opt_rr->size = 11; + + int rc = 0; + dbg_edns("Parsing options.\n"); + const knot_rdata_t *rdata = knot_rrset_rdata(rrset); + + // in OPT RR, all RDATA are in one RDATA item stored as BINARY data, + // i.e. preceded by their length + if (rdata != NULL) { + assert(knot_rdata_item_count(rdata) == 1); + const uint8_t *raw = (const uint8_t *) + knot_rdata_item(rdata, 0)->raw_data; + uint16_t size = knot_wire_read_u16(raw); + int pos = 2; + assert(size > 0); + while (pos - 2 < size) { + // ensure there is enough data to parse the OPTION CODE + // and OPTION LENGTH + if (size - pos + 2 < 4) { + return KNOT_EMALF; + } + uint16_t opt_code = knot_wire_read_u16(raw + pos); + uint16_t opt_size = knot_wire_read_u16(raw + pos + 2); + + // there should be enough data for parsing the OPTION + // data + if (size - pos - 2 < opt_size) { + return KNOT_EMALF; + } + rc = knot_edns_add_option(opt_rr, opt_code, opt_size, + raw + pos + 4); + if (rc != KNOT_EOK) { + return rc; + } + pos += 4 + opt_size; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_edns_get_payload(const knot_opt_rr_t *opt_rr) +{ + assert(opt_rr != NULL); + return opt_rr->payload; +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_set_payload(knot_opt_rr_t *opt_rr, + uint16_t payload) +{ + assert(opt_rr != NULL); + opt_rr->payload = payload; +} + +/*----------------------------------------------------------------------------*/ + +uint8_t knot_edns_get_ext_rcode(const knot_opt_rr_t *opt_rr) +{ + return opt_rr->ext_rcode; +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_set_ext_rcode(knot_opt_rr_t *opt_rr, + uint8_t ext_rcode) +{ + assert(opt_rr != NULL); + opt_rr->ext_rcode = ext_rcode; +} + +/*----------------------------------------------------------------------------*/ + +uint8_t knot_edns_get_version(const knot_opt_rr_t *opt_rr) +{ + assert(opt_rr != NULL); + return opt_rr->version; +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_set_version(knot_opt_rr_t *opt_rr, + uint8_t version) +{ + assert(opt_rr != NULL); + opt_rr->version = version; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_edns_get_flags(const knot_opt_rr_t *opt_rr) +{ + assert(opt_rr != NULL); + return opt_rr->flags; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_do(const knot_opt_rr_t *opt_rr) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + dbg_edns("Flags: %u\n", opt_rr->flags); + return (opt_rr->flags & KNOT_EDNS_DO_MASK); +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_set_do(knot_opt_rr_t *opt_rr) +{ + if (opt_rr == NULL) { + return; + } + + opt_rr->flags |= KNOT_EDNS_DO_MASK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_add_option(knot_opt_rr_t *opt_rr, uint16_t code, + uint16_t length, const uint8_t *data) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + if (opt_rr->option_count == opt_rr->options_max) { + knot_opt_option_t *options_new = + (knot_opt_option_t *)calloc( + (opt_rr->options_max + KNOT_EDNS_OPTION_STEP), + sizeof(knot_opt_option_t)); + CHECK_ALLOC_LOG(options_new, KNOT_ENOMEM); + memcpy(options_new, opt_rr->options, opt_rr->option_count); + opt_rr->options = options_new; + opt_rr->options_max += KNOT_EDNS_OPTION_STEP; + } + + dbg_edns("Adding option.\n"); + dbg_edns("Code: %u.\n", code); + dbg_edns("Length: %u.\n", length); + dbg_edns("Data: %p.\n", data); + + opt_rr->options[opt_rr->option_count].data = (uint8_t *)malloc(length); + CHECK_ALLOC_LOG(opt_rr->options[opt_rr->option_count].data, KNOT_ENOMEM); + memcpy(opt_rr->options[opt_rr->option_count].data, data, length); + + opt_rr->options[opt_rr->option_count].code = code; + opt_rr->options[opt_rr->option_count].length = length; + + ++opt_rr->option_count; + opt_rr->size += 4 + length; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_has_option(const knot_opt_rr_t *opt_rr, uint16_t code) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + int i = 0; + while (i < opt_rr->option_count && opt_rr->options[i].code != code) { + ++i; + } + + assert(i >= opt_rr->option_count || opt_rr->options[i].code == code); + + return (i < opt_rr->option_count); +} + +/*----------------------------------------------------------------------------*/ + +short knot_edns_to_wire(const knot_opt_rr_t *opt_rr, uint8_t *wire, + size_t max_size) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + assert(KNOT_EDNS_MIN_SIZE <= max_size); + + if (max_size < opt_rr->size) { + dbg_edns("Not enough place for OPT RR wire format.\n"); + return KNOT_ESPACE; + } + + uint8_t *pos = wire; + *(pos++) = 0; + knot_wire_write_u16(pos, KNOT_RRTYPE_OPT); + pos += 2; + knot_wire_write_u16(pos, opt_rr->payload); + pos += 2; + *(pos++) = opt_rr->ext_rcode; + *(pos++) = opt_rr->version; + knot_wire_write_u16(pos, opt_rr->flags); + pos += 2; + + uint8_t *rdlen = pos; + uint16_t len = 0; + pos += 2; + + // OPTIONs + for (int i = 0; i < opt_rr->option_count; ++i) { + knot_wire_write_u16(pos, opt_rr->options[i].code); + pos += 2; + knot_wire_write_u16(pos, opt_rr->options[i].length); + pos += 2; + memcpy(pos, opt_rr->options[i].data, opt_rr->options[i].length); + pos += opt_rr->options[i].length; + len += 4 + opt_rr->options[i].length; + } + + knot_wire_write_u16(rdlen, len); + + return opt_rr->size; +} + +/*----------------------------------------------------------------------------*/ + +short knot_edns_size(knot_opt_rr_t *opt_rr) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + return opt_rr->size; +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_free(knot_opt_rr_t **opt_rr) +{ + if (opt_rr == NULL || *opt_rr == NULL) { + return; + } + + if ((*opt_rr)->option_count > 0) { + free((*opt_rr)->options); + } + free(*opt_rr); + *opt_rr = NULL; +} diff --git a/src/libknot/edns.h b/src/libknot/edns.h new file mode 100644 index 0000000..010d155 --- /dev/null +++ b/src/libknot/edns.h @@ -0,0 +1,273 @@ +/*! + * \file edns.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Functions for manipulating and parsing EDNS OPT pseudo-RR. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_EDNS_H_ +#define _KNOT_EDNS_H_ + +#include <stdint.h> + +#include "util/utils.h" +#include "rrset.h" + +/*----------------------------------------------------------------------------*/ +/*! \brief Structure representing one OPT RR Option. */ +struct knot_opt_option { + uint16_t code; + uint16_t length; + uint8_t *data; +}; + +/*! \brief Structure representing one OPT RR Option. */ +typedef struct knot_opt_option knot_opt_option_t; + +/*! + * \brief Structure for holding EDNS parameters. + * + * \todo NSID + */ +struct knot_opt_rr { + uint16_t payload; /*!< UDP payload. */ + uint8_t ext_rcode; /*!< Extended RCODE. */ + + /*! + * \brief Supported version of EDNS. + * + * Set to EDNS_NOT_SUPPORTED if not supported. + */ + uint8_t version; + + uint16_t flags; /*!< EDNS flags. */ + knot_opt_option_t *options; /*!< EDNS options. */ + short option_count; /*!< Count of EDNS options in this OPT RR.*/ + short options_max; /*!< Maximum count of options. */ + short size; /*!< Total size of the OPT RR in wire format. */ +}; + +/*! \brief Structure for holding EDNS parameters. */ +typedef struct knot_opt_rr knot_opt_rr_t; + +/*----------------------------------------------------------------------------*/ +/*! \brief Constants for supported versions of EDNS. */ +enum knot_edns_versions { + EDNS_VERSION_0 = (uint8_t)0, /*!< EDNS version 0. */ + EDNS_NOT_SUPPORTED = (uint8_t)255 /*!< EDNS not supported. */ +}; + +/*! \brief Constants for EDNS option codes. */ +enum knot_edns_option_codes { + EDNS_OPTION_NSID = (uint16_t)3 /*!< NSID option code. */ +}; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates new empty OPT RR structure for holding EDNS parameters. + * + * \return New empty knot_opt_rr_t structure, or NULL if not successful. + */ +knot_opt_rr_t *knot_edns_new(); + +/*! + * \brief Initializes OPT RR structure from given OPT RR in wire format. + * + * \param opt_rr OPT RR structure to initialize. + * \param wire Wire format of the OPT RR to parse. + * \param max_size Maximum size of the wire format in bytes (may be more + * than acutal size of the OPT RR). + * + * \return Size of the parserd OPT RR in bytes if successful (always > 0). + * \retval KNOT_EBADARG + * \retval KNOT_EFEWDATA + * \retval KNOT_EMALF + * \retval KNOT_ENOMEM + */ +int knot_edns_new_from_wire(knot_opt_rr_t *opt_rr, const uint8_t *wire, + size_t max_size); + +int knot_edns_new_from_rr(knot_opt_rr_t *opt_rr, + const knot_rrset_t *rrset); + +/*! + * \brief Returns the UDP payload stored in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to get the payload from. + * + * \return UDP payload in bytes. + */ +uint16_t knot_edns_get_payload(const knot_opt_rr_t *opt_rr); + +/*! + * \brief Sets the UDP payload field in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to set the payload to. + * \param payload UDP payload in bytes. + */ +void knot_edns_set_payload(knot_opt_rr_t *opt_rr, uint16_t payload); + +/*! + * \brief Returns the Extended RCODE stored in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to get the Extended RCODE from. + * + * \return Extended RCODE. + */ +uint8_t knot_edns_get_ext_rcode(const knot_opt_rr_t *opt_rr); + +/*! + * \brief Sets the Extended RCODE field in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to set the Extended RCODE to. + * \param ext_rcode Extended RCODE to set. + */ +void knot_edns_set_ext_rcode(knot_opt_rr_t *opt_rr, uint8_t ext_rcode); + +/*! + * \brief Returns the EDNS version stored in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to get the EDNS version from. + * + * \return EDNS version. + */ +uint8_t knot_edns_get_version(const knot_opt_rr_t *opt_rr); + +/*! + * \brief Sets the EDNS version field in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to set the EDNS version to. + * \param version EDNS version to set. + */ +void knot_edns_set_version(knot_opt_rr_t *opt_rr, uint8_t version); + +/*! + * \brief Returns the flags stored in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to get the flags from. + * + * \return EDNS flags. + */ +uint16_t knot_edns_get_flags(const knot_opt_rr_t *opt_rr); + +/*! + * \brief Returns the state of the DO bit in the OPT RR flags. + * + * \param opt_rr OPT RR structure to get the DO bit from. + * + * \return <> 0 if the DO bit is set. + * \return 0 if the DO bit is not set. + */ +int knot_edns_do(const knot_opt_rr_t *opt_rr); + +/*! + * \brief Sets the DO bit in the OPT RR. + * + * \param opt_rr OPT RR structure to set the DO bit in. + */ +void knot_edns_set_do(knot_opt_rr_t *opt_rr); + +/*! + * \brief Adds EDNS Option to the OPT RR. + * + * \param opt_rr OPT RR structure to add the Option to. + * \param code Option code. + * \param length Option data length in bytes. + * \param data Option data. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +int knot_edns_add_option(knot_opt_rr_t *opt_rr, uint16_t code, + uint16_t length, const uint8_t *data); + +/*! + * \brief Checks if the OPT RR contains Option with the specified code. + * + * \param opt_rr OPT RR structure to check for the Option in. + * \param code Option code to check for. + * + * \retval <> 0 if the OPT RR contains Option with Option code \a code. + * \retval 0 otherwise. + */ +int knot_edns_has_option(const knot_opt_rr_t *opt_rr, uint16_t code); + +/*! + * \brief Converts the given OPT RR into wire format. + * + * \param opt_rr OPT RR structure to convert into wire format. + * \param wire Place to put the wire format to. + * \param max_size Maximum space available for the wire format in bytes. + * + * \return Size of the wire format in bytes if successful. + * \retval KNOT_ESPACE + */ +short knot_edns_to_wire(const knot_opt_rr_t *opt_rr, uint8_t *wire, + size_t max_size); + +/*! + * \brief Returns size of the OPT RR in wire format. + * + * \param opt_rr OPT RR to get the size of. + * + * \return Size of the OPT RR in bytes. + */ +short knot_edns_size(knot_opt_rr_t *opt_rr); + +/*! + * \brief Properly destroys the OPT RR structure. + * + * \note Also sets the given pointer to NULL. + */ +void knot_edns_free(knot_opt_rr_t **opt_rr); + +#endif /* _KNOT_EDNS_H_ */ + +/*! @} */ diff --git a/src/libknot/hash/cuckoo-hash-table.c b/src/libknot/hash/cuckoo-hash-table.c new file mode 100644 index 0000000..c5d1c4f --- /dev/null +++ b/src/libknot/hash/cuckoo-hash-table.c @@ -0,0 +1,1688 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> /* defines uint32_t etc */ +#include <assert.h> +#include <pthread.h> +#include <math.h> + +#include <urcu.h> + +#include "util/utils.h" +#include "common.h" +#include "util/debug.h" +#include "hash/cuckoo-hash-table.h" +#include "hash/hash-functions.h" +#include "common/dynamic-array.h" + +/*----------------------------------------------------------------------------*/ +/* Macros and inline functions */ +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Default size table holding information about used hash table cells + * when hashing. + */ +#define RELOCATIONS_DEFAULT 200 + +/*! + * \brief Maximum size table holding information about used hash table cells + * when hashing (just for debug issues). + */ +#define RELOCATIONS_MAX 1000 + +/*! + * \brief Macro for hashing the given key using the universal system. + * + * \param system Universal system to use for the hashing. + * \param key Key to hash. + * \param length Size of the key in bytes. + * \param exp Exponent of the hash table size (the size is a power of 2). + * \param table Hash table index. + * \param gen Universal system generation. + * + * \return Hashed key. + */ +#define HASH(system, key, length, exp, gen, table) \ + us_hash(system, fnv_32_buf(key, length, FNV1_32_INIT), exp, table, gen) + +/*! + * \brief Approximate ratio of hash table size to number of hashed items when 2 + * tables are used. + */ +static const float SIZE_RATIO_2 = 2; + +/*! + * \brief Approximate ratio of hash table size to number of hashed items when 3 + * tables are used. + */ +static const float SIZE_RATIO_3 = 1.15; + +/*! + * \brief Approximate ratio of hash table size to number of hashed items when 4 + * tables are used. + */ +static const float SIZE_RATIO_4 = 1.08; + +/*----------------------------------------------------------------------------*/ + +/*! \brief Flag marking the generation of hash table or its item to be 1. */ +static const uint8_t FLAG_GENERATION1 = 0x1; // 00000001 +/*! \brief Flag marking the generation of hash table or its item to be 2. */ +static const uint8_t FLAG_GENERATION2 = 0x2; // 00000010 +/*! \brief Flag marking both generations. */ +static const uint8_t FLAG_GENERATION_BOTH = 0x3; // 00000011 + +/*! \brief Flag used to mark the table when it's being rehashed. */ +static const uint8_t FLAG_REHASH = 0x4; // 00000100 + +/*----------------------------------------------------------------------------*/ +/*! \brief Clears the table / item flags. */ +static inline void CLEAR_FLAGS(uint8_t *flags) +{ + *flags = (uint8_t)0x0; +} + +/*! \brief Returns the generation stored in the flags. */ +static inline uint8_t GET_GENERATION(uint8_t flags) +{ + return (flags & FLAG_GENERATION_BOTH); +} + +/*! \brief Checks if the generation stored in both flags are the same. */ +static inline int EQUAL_GENERATIONS(uint8_t flags1, uint8_t flags2) +{ + return (GET_GENERATION(flags1) == GET_GENERATION(flags2)); +} + +/*! \brief Checks if the generation stored in the flags is 1. */ +static inline int IS_GENERATION1(uint8_t flags) +{ + return ((flags & FLAG_GENERATION1) != 0); +} + +/*! \brief Sets the generation stored in the flags to 1. */ +static inline void SET_GENERATION1(uint8_t *flags) +{ + *flags = ((*flags) & ~FLAG_GENERATION2) | FLAG_GENERATION1; +} + +/*! \brief Checks if the generation stored in the flags is 2. */ +static inline int IS_GENERATION2(uint8_t flags) +{ + return ((flags & FLAG_GENERATION2) != 0); +} + +/*! \brief Sets the generation stored in the flags to 2. */ +static inline void SET_GENERATION2(uint8_t *flags) +{ + *flags = ((*flags) & ~FLAG_GENERATION1) | FLAG_GENERATION2; +} + +/*! \brief Sets the generation stored in the flags to the given generation. */ +static inline void SET_GENERATION(uint8_t *flags, uint8_t generation) +{ + *flags = ((*flags) & ~FLAG_GENERATION_BOTH) | generation; +} + +/*! \brief Sets the generation stored in the flags to the next one (cyclic). */ +static inline uint8_t SET_NEXT_GENERATION(uint8_t *flags) +{ + return ((*flags) ^= FLAG_GENERATION_BOTH); +} + +/*! \brief Returns the next generation to the one stored in flags (cyclic). */ +static inline uint8_t NEXT_GENERATION(uint8_t flags) +{ + return ((flags & FLAG_GENERATION_BOTH) ^ FLAG_GENERATION_BOTH); +} + +/*! \brief Sets the rehashing flag to the flags. */ +static inline void SET_REHASHING_ON(uint8_t *flags) +{ + *flags = (*flags | FLAG_REHASH); +} + +/*! \brief Removes the rehashing flag from the flags. */ +static inline void SET_REHASHING_OFF(uint8_t *flags) +{ + *flags = (*flags & ~FLAG_REHASH); +} + +/*! \brief Checks if the rehashing flag is set in the flags. */ +static inline int IS_REHASHING(uint8_t flags) +{ + return ((flags & FLAG_REHASH) != 0); +} + +/*----------------------------------------------------------------------------*/ +/* Private functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Returns the exponent of the nearest larger power of two. + */ +static uint get_larger_exp(uint n) +{ + uint res = 0; + while (hashsize(++res) < n) {} + + return res; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Counts the ideal table count and the exponent of those tables' sizes. + * + * Only 3 or 4 hash tables are considered. The setup in which less items are + * wasted is recommended. + * + * \param items Number of items to hash. + * \param table_count Recommended number of tables will be saved here. + * + * \return Exponent of the tables' sizes. + */ +static uint get_table_exp_and_count(uint items, uint *table_count) +{ + // considering only 3 or 4 tables + int exp3 = get_larger_exp((items * SIZE_RATIO_3) / 3); + int exp4 = get_larger_exp(items * SIZE_RATIO_4) - 2; + + if (exp4 < 0) { + exp4 = 1; + } + + dbg_ck("Determining ideal table size...\n"); + dbg_ck("\tNumber of items: %u\n", items); + dbg_ck("\tThree tables: size of one table: %u, total size: %u\n", + hashsize(exp3), 3 * hashsize(exp3)); + dbg_ck("\tFour tables: size of one table: %u, total size: %u\n", + hashsize(exp4), 4 * hashsize(exp4)); + + // we need exponent at least 1 (this is quite ugly..) + if (exp3 == 0) { + exp3 = 1; + } + if (exp4 == 0) { + exp4 = 1; + } + + if (exp3 >= 32 || exp4 >= 32) { + return 0; + } + + if (((hashsize(exp3) * 3) - (items)) < ((hashsize(exp4) * 4) - items)) { + *table_count = 3; + return exp3; + } else { + *table_count = 4; + return exp4; + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Counts the maximum effective item count based on size of the tables. + * + * For 3 tables, the effective utilization should be around 91%. + * For 4 tables it is 97%. + * + * See Fotakis, Dimitris, et al. - Space Efficient Hash Tables with Worst Case + * Constant Access Time. CiteSeerX. 2003 + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.14.5337 + */ +static uint get_max_table_items(uint table_count, int table_exponent) +{ + assert(table_count == 3 || table_count == 4); + + float coef; + + if (table_count == 3) { + coef = 0.91; + } else { + coef = 0.97; + } + + return (uint)floor((table_count * hashsize(table_exponent)) * coef); +} + +/*----------------------------------------------------------------------------*/ + +static int ck_is_full(const ck_hash_table_t *table) +{ + return (table->items >= get_max_table_items(table->table_count, + table->table_size_exp)); +} + +/*----------------------------------------------------------------------------*/ + +static int ck_stash_is_full(const ck_hash_table_t *table) +{ + return (table->items_in_stash >= STASH_SIZE_MAX); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Clears the given item by assigning a NULL pointer to it. + */ +static inline void ck_clear_item(ck_hash_table_item_t **item) +{ + *item = NULL; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Insert given contents to the hash table item. + */ +static void ck_fill_item(const char *key, size_t key_length, void *value, + uint generation, ck_hash_table_item_t *item) +{ + // must allocate new space for key and value, otherwise it will be lost! + item->key = key; + item->key_length = key_length; + item->value = value; + CLEAR_FLAGS(&item->timestamp); + item->timestamp = generation; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Swaps two hash table items. + */ +static inline void ck_swap_items(ck_hash_table_item_t **item1, + ck_hash_table_item_t **item2) +{ + ck_hash_table_item_t *tmp = *item1; + *item1 = *item2; + *item2 = tmp; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets the \a item pointer to the \a to pointer. + */ +static inline void ck_put_item(ck_hash_table_item_t **to, + ck_hash_table_item_t *item) +{ + *to = item; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Checks if the hash was already used twice. + * + * If yes, it means we entered a loop in the hashing process, so we must stop. + * Otherwise it remembers that we used the hash. + * + * \note According to Kirsch, et al. a check that at most one hash was used + * twice should be sufficient. We will retain our version for now. + * + * \param used Array of used table indices (hashes). + * \param hash Hash to check. + * + * \retval -1 if the hash was already used twice. + * \retval -2 if an error occured. + * \retval 0 if the hash was not used twice yet. + */ +static uint ck_check_used_twice(da_array_t *used, uint32_t hash) +{ + uint i = 0, found = 0; + while (i <= da_get_count(used) && found < 2) { + ++i; + if (((uint *)(da_get_items(used)))[i] == hash) { + ++found; + } + } + + if (i <= da_get_count(used) && found == 2) { + dbg_ck_hash("Hashing entered infinite loop.\n"); + return -1; + } else { + if (da_reserve(used, 1) < 0) { + ERR_ALLOC_FAILED; + return -2; + } + ((uint *)da_get_items(used))[da_get_count(used)] = hash; + da_occupy(used, 1); + assert(da_get_count(used) < RELOCATIONS_MAX); + return 0; + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Compares the key of item with the given key. + * + * \param item Item to compare with. + * \param key Key to compare. + * \param length Size of the key in bytes. + * + * \return <> 0 if the keys match. + * \return 0 if they don't. + */ +static inline uint ck_items_match(const ck_hash_table_item_t *item, + const char *key, size_t length) +{ + return (length == item->key_length + && (strncmp(item->key, key, length) == 0)); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Switches the given table number to a randomly chosen other table + * number. + */ +static inline void ck_next_table(uint *table, uint table_count) +{ + uint next; + while ((*table) == (next = knot_quick_rand() % table_count)) {} + *table = next; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tries to find the given key in the hash table's stash. + * + * \param table Hash table to search in. + * \param key Key to find. + * \param length Size of the key in bytes. + * + * \return Hash table item matching the key or NULL if not found in the stash. + */ +static ck_hash_table_item_t **ck_find_in_stash(const ck_hash_table_t *table, + const char *key, uint length) +{ + ck_stash_item_t *item = table->stash; + while (item != NULL) { + dbg_ck("Comparing item in stash (key: %.*s (size %zu))" + "with searched item (key %.*s (size %u)).\n", + (int)item->item->key_length, item->item->key, + item->item->key_length, (int)length, key, length); + if (ck_items_match(item->item, key, length)) { + return &item->item; + } + item = item->next; + } + + return NULL; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tries to find item with given key using hash functions from the given + * generation. + * + * \param table Hash table to search in. + * \param key Key to find. + * \param length Size of the key in bytes. + * \param generation Generation of items (table) to use. Items having other + * generation are ignored. + */ +static ck_hash_table_item_t **ck_find_gen(const ck_hash_table_t *table, + const char *key, + size_t length, uint8_t generation) +{ + uint32_t hash; + dbg_ck("Finding item in generation: %u\n", generation); + + // check hash tables + for (uint t = 0; t < table->table_count; ++t) { + hash = HASH(&table->hash_system, key, length, + table->table_size_exp, generation, t); + + dbg_ck("Hash: %u, key: %.*s\n", hash, (int)length, key); + dbg_ck("Table %d, hash: %u, item: %p\n", t + 1, hash, + table->tables[t][hash]); + if (table->tables[t][hash] != NULL) { + dbg_ck("Table %u, key: %.*s, value: %p, key " + "length: %zu\n", + t + 1, (int)table->tables[t][hash]->key_length, + table->tables[t][hash]->key, + table->tables[t][hash]->value, + table->tables[t][hash]->key_length); + } + + if (table->tables[t][hash] && + ck_items_match(table->tables[t][hash], key, length)) { + // found + return &table->tables[t][hash]; + } + } + + // try to find in stash + dbg_ck("Searching in stash...\n"); + + ck_hash_table_item_t **found = + ck_find_in_stash(table, key, length); + + dbg_ck("Found pointer: %p\n", found); + if (found != NULL) { + dbg_ck("Stash, key: %.*s, value: %p, key length: %zu\n", + (int)(*found)->key_length, (*found)->key, + (*found)->value, (*found)->key_length); + } + + // ck_find_in_buffer returns NULL if not found, otherwise pointer to + // item + return found; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Finds item with given key and returns non-constant pointer to pointer + * to the appropriate hash table item. + * + * \param table Hash table to search in. + * \param key Key to find. + * \param length Size of the key in bytes. + */ +static ck_hash_table_item_t **ck_find_item_nc(const ck_hash_table_t *table, + const char *key, size_t length) +{ + // get the generation of the table so that we use the same value + uint8_t generation = table->generation; + + // find item using the table generation's hash functions + ck_hash_table_item_t **found = ck_find_gen(table, key, length, + GET_GENERATION(generation)); + // if rehashing is in progress, try the next generation's functions + if (!found && IS_REHASHING(generation)) { + found = ck_find_gen(table, key, length, + NEXT_GENERATION(generation)); + } + + return found; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Hashes the given item using the given generation. + * + * \param table Hash table where to put the item. + * \param to_hash In: Item to hash. Out: NULL if successful, item that failed + * to hash if not. + * \param free Free place where to put the last moved item when the hasing + * is unsuccessful. + * \param generation Generation of items (table) to be used for hashing. + * + * \retval 0 if successful and no loop occured. + * \retval 1 if a loop occured and the item was inserted to the \a free place. + */ +static int ck_hash_item(ck_hash_table_t *table, ck_hash_table_item_t **to_hash, + ck_hash_table_item_t **free, uint8_t generation) +{ + da_array_t used[table->table_count]; + for (uint i = 0; i < table->table_count; ++i) { + da_initialize(&used[i], RELOCATIONS_DEFAULT, sizeof(uint)); + } + + // hash until empty cell is encountered or until loop appears + + dbg_ck_hash("Hashing key: %.*s of size %zu.\n", + (int)(*to_hash)->key_length, (*to_hash)->key, + (*to_hash)->key_length); + + uint next_table = 0; + + uint32_t hash = HASH(&table->hash_system, (*to_hash)->key, + (*to_hash)->key_length, table->table_size_exp, + generation, next_table); + + dbg_ck_hash("New hash: %u.\n", hash); + assert(hash < hashsize(table->table_size_exp)); + + ((uint *)da_get_items(&used[next_table])) + [da_get_count(&used[next_table])] = hash; + ck_hash_table_item_t **next = &table->tables[next_table][hash]; + dbg_ck_hash("Item to be moved: %p, place in table: %p\n", + *next, next); + ck_hash_table_item_t **moving = to_hash; + + int loop = 0; + + while (*next != NULL) { + dbg_ck_hash("Swapping items to hash: %p and Moving: %p\n", + to_hash, moving); + ck_swap_items(to_hash, moving); // first time it's unnecessary + + // set the generation of the inserted item to the next + SET_GENERATION(&(*moving)->timestamp, generation); + + moving = next; + + dbg_ck_hash("Moving item from table %u, key: %.*s, hash %u ", + next_table + 1, (int)(*moving)->key_length, + (*moving)->key, hash); + + // if rehashing and the 'next' item is from the old generation, + // start from table 1 + if (generation != table->generation && + EQUAL_GENERATIONS((*next)->timestamp, table->generation)) { + next_table = 0; + } else { + ck_next_table(&next_table, table->table_count); + } + + hash = HASH(&table->hash_system, (*next)->key, + (*next)->key_length, table->table_size_exp, + generation, next_table); + + next = &table->tables[next_table][hash]; + + dbg_ck_hash("to table %u, hash %u, item: %p, place: %p\n", + next_table + 1, hash, *next, next); + + if ((*next) != NULL) { + dbg_ck_hash("Table %u, hash: %u, key: %.*s\n", + next_table + 1, hash, + (int)(*next)->key_length, (*next)->key); + } + + // check if this cell wasn't already used in this item's hashing + if (ck_check_used_twice(&used[next_table], hash) != 0) { + next = free; + loop = -1; + break; + } + } + + dbg_ck_hash("Putting pointer %p (*moving) to item %p (next).\n", + *moving, next); + + ck_put_item(next, *moving); + // set the new generation for the inserted item + SET_GENERATION(&(*next)->timestamp, generation); + dbg_ck_hash("Putting pointer %p (*old) to item %p (moving).\n", + *to_hash, moving); + + ck_put_item(moving, *to_hash); + + // set the new generation for the inserted item + SET_GENERATION(&(*moving)->timestamp, generation); + *to_hash = NULL; + + for (uint i = 0; i < table->table_count; ++i) { + da_destroy(&used[i]); + } + + return loop; +} + +/*----------------------------------------------------------------------------*/ + +static void ck_rollback_rehash(ck_hash_table_t *table) +{ + // set old generation in tables + for (int i = 0; i < hashsize(table->table_size_exp); ++i) { + // no need for locking - timestamp is not used in lookup + // and two paralel insertions (and thus rehashings) are + // impossible + for (uint t = 0; t < table->table_count; ++t) { + if (table->tables[t][i] != NULL) { + SET_GENERATION(&table->tables[t][i]->timestamp, + table->generation); + } + } + } + + // set old generation in stash + ck_stash_item_t *item = table->stash; + while (item != NULL) { + assert(item->item != NULL); + SET_GENERATION(&item->item->timestamp, table->generation); + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds the given item to the hash table's stash. + * + * \param table Hash table to add the item to. + * \param item Item to add. + * + * \retval 0 if successful. + * \retval -1 if an error occured. + */ +int ck_add_to_stash(ck_hash_table_t *table, ck_hash_table_item_t *item) +{ + ck_stash_item_t *new_item + = (ck_stash_item_t *)malloc(sizeof(ck_stash_item_t)); + if (new_item == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + + new_item->item = item; + new_item->next = table->stash; + table->stash = new_item; + + dbg_ck_hash("First item in stash (now inserted): key: %.*s (size %zu)" + ", value: %p\n", (int)table->stash->item->key_length, + table->stash->item->key, table->stash->item->key_length, + table->stash->item->value); + + // increase count of items in stash + ++table->items_in_stash; + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +static int ck_new_table(ck_hash_table_item_t ***table, int exp) +{ + *table = (ck_hash_table_item_t **) + malloc(hashsize(exp) * sizeof(ck_hash_table_item_t *)); + if (*table == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + + // set to 0 + memset(*table, 0, hashsize(exp) * sizeof(ck_hash_table_item_t *)); + + return 0; +} + +/*----------------------------------------------------------------------------*/ +/* Public functions */ +/*----------------------------------------------------------------------------*/ + +ck_hash_table_t *ck_create_table(uint items) +{ + ck_hash_table_t *table = + (ck_hash_table_t *)malloc(sizeof(ck_hash_table_t)); + + if (table == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + memset(table, 0, sizeof(ck_hash_table_t)); + + // determine ideal size of one table in powers of 2 and save the + // exponent + table->table_size_exp = get_table_exp_and_count(items, + &table->table_count); + assert(table->table_size_exp <= 32); + + if (table->table_size_exp == 0) { + dbg_ck("Failed to count exponent of the hash table.\n"); + return NULL; + } + + dbg_ck("Creating hash table for %u items.\n", items); + dbg_ck("Exponent: %u, number of tables: %u\n ", + table->table_size_exp, table->table_count); + dbg_ck("Table size: %u items, each %zu bytes, total %zu bytes\n", + hashsize(table->table_size_exp), + sizeof(ck_hash_table_item_t *), + hashsize(table->table_size_exp) + * sizeof(ck_hash_table_item_t *)); + + // create tables + for (uint t = 0; t < table->table_count; ++t) { + dbg_ck("Creating table %u...\n", t); + if (ck_new_table(&table->tables[t], table->table_size_exp) + != 0) { + for (uint i = 0; i < t; ++i) { + free(table->tables[i]); + } + free(table); + return NULL; + } + } + + assert(table->stash == NULL); + assert(table->hashed == NULL); + assert(table->items == 0); + assert(table->items_in_stash == 0); + assert(table->table_count == MAX_TABLES + || table->tables[table->table_count] == NULL); + + // initialize rehash/insert mutex + pthread_mutex_init(&table->mtx_table, NULL); + + // set the generation to 1 and initialize the universal system + CLEAR_FLAGS(&table->generation); + SET_GENERATION1(&table->generation); + + us_initialize(&table->hash_system); + + return table; +} + +/*----------------------------------------------------------------------------*/ + +void ck_destroy_table(ck_hash_table_t **table, void (*dtor_value)(void *value), + int delete_key) +{ + assert(table); + assert(*table); + pthread_mutex_lock(&(*table)->mtx_table); + + // destroy items in tables + for (uint i = 0; i < hashsize((*table)->table_size_exp); ++i) { + for (uint t = 0; t < (*table)->table_count; ++t) { + if ((*table)->tables[t][i] != NULL) { + if (dtor_value) { + dtor_value( + (*table)->tables[t][i]->value); + } + if (delete_key != 0) { + free( + (void *)(*table)->tables[t][i]->key); + } + free((void *)(*table)->tables[t][i]); + } + } + } + + // destroy items in stash +// ck_hash_table_item_t **stash = +// ((ck_hash_table_item_t **)(da_get_items(&(*table)->stash))); +// for (uint i = 0; i < da_get_count(&(*table)->stash); ++i) { +// assert(stash[i] != NULL); +// if (dtor_value) { +// dtor_value(stash[i]->value); +// } +// if (delete_key != 0) { +// free((void *)stash[i]->key); +// } +// free((void *)stash[i]); +// } + ck_stash_item_t *item = (*table)->stash; + while (item != NULL) { + // disconnect the item + (*table)->stash = item->next; + /*! \todo Investigate this. */ + assert(item->item != NULL); + + if (dtor_value) { + dtor_value(item->item->value); + } + if (delete_key) { + free((void *)item->item->key); + } + + free((void *)item->item); + free(item); + item = (*table)->stash; + } + + // deallocate tables + for (uint t = 0; t < (*table)->table_count; ++t) { + free((*table)->tables[t]); + } + // destroy stash +// da_destroy(&(*table)->stash); + + pthread_mutex_unlock(&(*table)->mtx_table); + // destroy mutex, assuming that here noone will lock the mutex again + pthread_mutex_destroy(&(*table)->mtx_table); + + free(*table); + (*table) = NULL; +} + +void ck_table_free(ck_hash_table_t **table) +{ + if (table == NULL || *table == NULL) { + return; + } + + pthread_mutex_lock(&(*table)->mtx_table); + + ck_stash_item_t *item = (*table)->stash; + while (item != NULL) { + // disconnect the item + (*table)->stash = item->next; + free(item); + item = (*table)->stash; + } + + // deallocate tables + for (uint t = 0; t < (*table)->table_count; ++t) { + free((*table)->tables[t]); + } + + pthread_mutex_unlock(&(*table)->mtx_table); + pthread_mutex_destroy(&(*table)->mtx_table); + + free(*table); + (*table) = NULL; +} + +int ck_resize_table(ck_hash_table_t *table) +{ + dbg_ck("Resizing hash table.\n"); + + /* + * Easiest is just to increment the exponent, resulting in doubling + * the table sizes. This is not very memory-effective, but should do + * the job. + */ + + if (table->table_size_exp == 31) { + dbg_ck("Hash tables achieved max size (exponent 31).\n"); + return -1; + } + + ck_hash_table_item_t **tables_new[MAX_TABLES]; + ck_hash_table_item_t **tables_old[MAX_TABLES]; + int exp_new = table->table_size_exp + 1; + + dbg_ck("New tables exponent: %d\n", exp_new); + + for (int t = 0; t < table->table_count; ++t) { + if (ck_new_table(&tables_new[t], exp_new) != 0) { + dbg_ck("Failed to create new table.\n"); + for (int i = 0; i < t; ++i) { + free(tables_new[i]); + } + return -1; + } + } + + dbg_ck("Created new tables, copying data to them.\n"); + + for (int t = 0; t < table->table_count; ++t) { + size_t old_size = hashsize(table->table_size_exp) + * sizeof(ck_hash_table_item_t *); + + // copy the old table items + dbg_ck("Copying to: %p, from %p, size: %zu\n", + tables_new[t], table->tables[t], old_size); + memcpy(tables_new[t], table->tables[t], old_size); + // set the rest to 0 + dbg_ck("Setting to 0 from %p, size %zu\n", + tables_new[t] + hashsize(table->table_size_exp), + (hashsize(exp_new) * sizeof(ck_hash_table_item_t *)) + - old_size); + memset(tables_new[t] + hashsize(table->table_size_exp), 0, + (hashsize(exp_new) * sizeof(ck_hash_table_item_t *)) + - old_size); + } + + dbg_ck("Done, switching the tables and running rehash.\n"); + + + memcpy(tables_old, table->tables, + MAX_TABLES * sizeof(ck_hash_table_item_t **)); + memcpy(table->tables, tables_new, + MAX_TABLES * sizeof(ck_hash_table_item_t **)); + + table->table_size_exp = exp_new; + + // delete the old tables + for (int t = 0; t < table->table_count; ++t) { + free(tables_old[t]); + } + + return ck_rehash(table); + //return 0; +} + +int ck_insert_item(ck_hash_table_t *table, const char *key, + size_t length, void *value) +{ + // lock mutex to avoid write conflicts + pthread_mutex_lock(&table->mtx_table); + + assert(value != NULL); + + dbg_ck_hash("Inserting item with key: %.*s.\n", (int)length, key); + dbg_ck_hash_hex(key, length); + dbg_ck_hash("\n"); + + // create item structure and fill in the given data, key won't be copied + ck_hash_table_item_t *new_item = + (ck_hash_table_item_t *)malloc((sizeof(ck_hash_table_item_t))); + ck_fill_item(key, length, value, GET_GENERATION(table->generation), + new_item); + + // check if the table is not full; if yes, resize and rehash! + if (ck_is_full(table)) { + dbg_ck("Table is full, resize needed.\n"); + if (ck_resize_table(table) != 0) { + dbg_ck("Failed to resize hash table!\n"); + return -1; + } + } + + // there should be at least 2 free places + //assert(da_try_reserve(&table->stash, 2) == 0); + //da_reserve(&table->stash, 1); + ck_hash_table_item_t *free_place = NULL; + if (ck_hash_item(table, &new_item, &free_place, + table->generation) != 0) { + + dbg_ck("Adding item with key %.*s to stash.\n", + (int)free_place->key_length, free_place->key); + + // maybe some limit on the stash and rehash if full + if (ck_add_to_stash(table, free_place) != 0) { + dbg_ck_hash("Could not add item to stash!!\n"); + assert(0); + } + + if (ck_stash_is_full(table)) { + dbg_ck("Stash is full, resize needed.\n"); + if (ck_resize_table(table) != 0) { + dbg_ck("Failed to resize hash table!\n"); + return -1; + } + } + } + + ++table->items; + pthread_mutex_unlock(&table->mtx_table); + return 0; +} + +/*----------------------------------------------------------------------------*/ + +const ck_hash_table_item_t *ck_find_item(const ck_hash_table_t *table, + const char *key, size_t length) +{ + dbg_ck("ck_find_item(), key: %.*s, size: %zu\n", + (int)length, key, length); + + ck_hash_table_item_t **found = ck_find_item_nc(table, key, length); + + return (found == NULL) ? NULL : rcu_dereference(*found); +} + +/*----------------------------------------------------------------------------*/ + +int ck_update_item(const ck_hash_table_t *table, const char *key, size_t length, + void *new_value, void (*dtor_value)(void *value)) +{ + rcu_read_lock(); // is needed? + + assert(new_value != NULL); + + ck_hash_table_item_t **item = ck_find_item_nc(table, key, length); + + if (item == NULL || (*item) == NULL) { + return -1; + } + + void *old = rcu_xchg_pointer(&(*item)->value, new_value); + rcu_read_unlock(); + + synchronize_rcu(); + if (dtor_value) { + dtor_value(old); + } + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +int ck_delete_item(const ck_hash_table_t *table, const char *key, size_t length, + void (*dtor_value)(void *value), int delete_key) +{ + rcu_read_lock(); // is needed? + ck_hash_table_item_t **place = ck_find_item_nc(table, key, length); + + if (place == NULL) { + return -1; + } + + ck_hash_table_item_t *item = *place; + + assert(item != NULL); + + ck_put_item(place, NULL); + rcu_read_unlock(); + + synchronize_rcu(); + if (dtor_value) { + dtor_value(item->value); + } + item->value = NULL; + if (delete_key != 0) { + free((void *)item->key); + } + free(item); + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +ck_hash_table_item_t *ck_remove_item(ck_hash_table_t *table, const char *key, + size_t length) +{ + ck_hash_table_item_t **place = ck_find_item_nc(table, key, length); + if (place == NULL) { + return NULL; + } + + ck_hash_table_item_t *item = *place; + *place = NULL; + return item; +} + +/*----------------------------------------------------------------------------*/ + +int ck_shallow_copy(const ck_hash_table_t *from, ck_hash_table_t **to) +{ + if (from == NULL || to == NULL) { + return -1; + } + + *to = (ck_hash_table_t *)malloc(sizeof(ck_hash_table_t)); + + if (*to == NULL) { + ERR_ALLOC_FAILED; + return -2; + } + memset(*to, 0, sizeof(ck_hash_table_t)); + + // copy table count and table size exponent + (*to)->table_size_exp = from->table_size_exp; + (*to)->table_count = from->table_count; + assert((*to)->table_size_exp <= 32); + + dbg_ck("Creating hash table for %u items.\n", from->table_count); + dbg_ck("Exponent: %u, number of tables: %u\n ", + (*to)->table_size_exp, (*to)->table_count); + dbg_ck("Table size: %u items, each %zu bytes, total %zu bytes\n", + hashsize((*to)->table_size_exp), + sizeof(ck_hash_table_item_t *), + hashsize((*to)->table_size_exp) + * sizeof(ck_hash_table_item_t *)); + + // create tables + for (uint t = 0; t < (*to)->table_count; ++t) { + dbg_ck("Creating table %u...\n", t); + (*to)->tables[t] = (ck_hash_table_item_t **)malloc( + hashsize((*to)->table_size_exp) + * sizeof(ck_hash_table_item_t *)); + if ((*to)->tables[t] == NULL) { + ERR_ALLOC_FAILED; + for (uint i = 0; i < t; ++i) { + free((*to)->tables[i]); + } + free(*to); + return -2; + } + + // copy the table + memcpy((*to)->tables[t], from->tables[t], + hashsize((*to)->table_size_exp) + * sizeof(ck_hash_table_item_t *)); + } + + // copy the stash - we must explicitly copy each stash item, but do not + // copy the ck_hash_table_item_t within them. + ck_stash_item_t *si = from->stash; + ck_stash_item_t **pos = &(*to)->stash; + dbg_ck_verb(stderr, "Copying hash table stash.\n"); + while (si != NULL) { + ck_stash_item_t *si_new = (ck_stash_item_t *) + malloc(sizeof(ck_stash_item_t)); + if (si_new == NULL) { + ERR_ALLOC_FAILED; + // delete tables + for (uint i = 0; i < (*to)->table_count; ++i) { + free((*to)->tables[i]); + } + // delete created stash items + si_new = (*to)->stash; + while (si_new != NULL) { + ck_stash_item_t *prev = si_new; + si_new = si_new->next; + free(prev); + } + free(*to); + return -2; + } + + dbg_ck("Copying stash item: %p with item %p, ", si, si->item); + dbg_ck("key: %.*s\n", (int)si->item->key_length, si->item->key); + + si_new->item = si->item; + *pos = si_new; + pos = &si_new->next; + si = si->next; + + + dbg_ck("Old stash item: %p with item %p, ", si, + ((si == NULL) ? NULL : si->item)); + if (si != NULL) { + dbg_ck("key: %.*s\n", (int)si->item->key_length, si->item->key); + } else { + dbg_ck("\n"); + } + dbg_ck("New stash item: %p with item %p, ", si_new, + si_new->item); + dbg_ck("key: %.*s\n", (int)si_new->item->key_length, + si_new->item->key); + } + + *pos = NULL; + + // there should be no item being hashed right now + /*! \todo This operation should not be done while inserting / rehashing. + */ + assert(from->hashed == NULL); + (*to)->hashed = NULL; + + // initialize rehash/insert mutex + pthread_mutex_init(&(*to)->mtx_table, NULL); + + // copy the generation + (*to)->generation = from->generation; + + // copy the hash functions + memcpy(&(*to)->hash_system, &from->hash_system, sizeof(us_system_t)); + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +int ck_apply(ck_hash_table_t *table, + void (*function)(ck_hash_table_item_t *item, void *data), + void *data) +{ + if (table == NULL || function == NULL) { + return -1; + } + + /*! \todo Ensure that no insertion nor rehash is made during applying.*/ + + // apply the function to all items in all tables + for (int t = 0; t < table->table_count; ++t) { + for (int i = 0; i < hashsize(table->table_size_exp); ++i) { + function(table->tables[t][i], data); + } + } + + // apply the function to the stash items + ck_stash_item_t *si = table->stash; + while (si != NULL) { + function(si->item, data); + si = si->next; + } + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +int ck_rehash(ck_hash_table_t *table) +{ + dbg_ck_hash("Rehashing items in table.\n"); + SET_REHASHING_ON(&table->generation); + + ck_stash_item_t *free_stash_items = NULL; + + do { + // 1) Rehash items from stash + dbg_ck_rehash("Rehashing items from stash.\n"); + ck_stash_item_t *item = table->stash; + ck_stash_item_t **item_place = &table->stash; + // terminate when at the end; this way the newly added items + // (added to the beginning) will be properly ignored + while (item != NULL) { + dbg_ck_rehash("Rehashing item with " + "key (length %zu): %.*s, generation: %hu, " + "table generation: %hu.\n", item->item->key_length, + (int)item->item->key_length, item->item->key, + GET_GENERATION( + item->item->timestamp), + GET_GENERATION(table->generation)); + + // put the hashed item to the prepared space + table->hashed = item->item; + item->item = NULL; + // we may use the place in the stash item as the free + // place for rehashing + if (ck_hash_item(table, &table->hashed, &item->item, + NEXT_GENERATION(table->generation)) != 0) { + // the free place was used + assert(item->item != NULL); + // we may leave the item there (in the stash) + assert(EQUAL_GENERATIONS(item->item->timestamp, + NEXT_GENERATION(table->generation))); + //assert(item->item == table->hashed); + + item_place = &item->next; + item = item->next; + } else { + // the free place should be free + assert(item->item == NULL); + // and the item should be hashed too +// assert(table->hashed == NULL); + + // fix the pointer from the previous hash item + *item_place = item->next; + // and do not change the item place pointer + + // put the stash item into list of free stash + // items + item->next = free_stash_items; + free_stash_items = item; + + item = *item_place; + } + } + + // 2) Rehash items from tables + + // in case of failure, save the item in a temp variable + // which will be put to the stash + ck_hash_table_item_t *free = NULL; + assert(table->hashed == NULL); +// ck_hash_table_item_t *old = table->hashed; + + for (uint t = 0; t < table->table_count; ++t) { + uint rehashed = 0; + + dbg_ck_rehash("Rehashing table %d.\n", t); + + while (rehashed < hashsize(table->table_size_exp)) { + + // if item's generation is the new generation, + // skip + if (table->tables[t][rehashed] == NULL + || !(EQUAL_GENERATIONS( + table->tables[t][rehashed]->timestamp, + table->generation))) { + dbg_ck_rehash("Skipping item.\n"); + ++rehashed; + continue; + } + + dbg_ck_rehash("Rehashing item with hash %u, " + "key (length %zu): %.*s, generation: %hu, " + "table generation: %hu.\n", rehashed, + table->tables[t][rehashed]->key_length, + (int)(table->tables[t][rehashed]->key_length), + table->tables[t][rehashed]->key, + GET_GENERATION( + table->tables[t][rehashed]->timestamp), + GET_GENERATION(table->generation)); + + // otherwise copy the item for rehashing + ck_put_item(&table->hashed, table->tables[t][rehashed]); + // clear the place so that this item will not + // get rehashed again + ck_clear_item(&table->tables[t][rehashed]); + + dbg_ck_rehash("Table generation: %hu, next " + "generation: %hu.\n", + GET_GENERATION(table->generation), + NEXT_GENERATION(table->generation)); + + if (ck_hash_item(table, &table->hashed, &free, + NEXT_GENERATION(table->generation)) != 0) { + // loop occured + dbg_ck_hash("Hashing entered a loop." + "\n"); + dbg_ck_rehash("Item with key %.*s " + "inserted into the free slot.\n", + free->key_length, free->key); + + //assert(old == free); + + // put the item into the stash, but + // try the free stash items first + if (free_stash_items != NULL) { + // take first + ck_stash_item_t *item = + free_stash_items; + free_stash_items = item->next; + + item->item = free; + item->next = table->stash; + table->stash = item; + } else { + if (ck_add_to_stash(table, free) + != 0) { + ck_rollback_rehash( + table); + } + } + + free = NULL; + table->hashed = NULL; + } + ++rehashed; + } + } + + dbg_ck_rehash("Old table generation: %u\n", + GET_GENERATION(table->generation)); + // rehashing completed, switch generation of the table + SET_NEXT_GENERATION(&table->generation); + dbg_ck_rehash("New table generation: %u\n", + GET_GENERATION(table->generation)); + // generate new hash functions for the old generation + dbg_ck_rehash("Generating coeficients for generation: %u\n", + NEXT_GENERATION(table->generation)); + us_next(&table->hash_system, + NEXT_GENERATION(table->generation)); + + } while (false /*! \todo Add proper condition!! */); + + SET_REHASHING_OFF(&table->generation); + + assert(table->hashed == NULL); + + + while (free_stash_items != NULL) { + ck_stash_item_t *item = free_stash_items; + free_stash_items = item->next; + assert(item->item == NULL); + free(item); + } + + return 0; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Rehashes the whole table. + * + * \param table Hash table to be rehashed. + * + * \note While rehashing no item should be inserted as it will result in a + * deadlock. + * + * \retval 0 No error. + * \retval -1 Rehashing failed. Some items may have been already moved and the + * rehashing flag remains set. + * + * \todo What if the stash is reallocated during ck_hash_item()? We'd be using + * the old stash for saving items! The old stash would not get deallocated + * (due to RCU - maybe put some rcu_read_lock() here), but the item + * would not be saved into the new stash! + * Maybe add a function for getting a pointer to particular item from + * the dynamic array and protect it using rcu_read_lock(). + * Other option: Do not use pointer to an item in stash in the call to + * ck_hash_item(). Use some new place & put the item to the stash + * afterwards, protecting it using rcu_read_lock() and rcu_assign_pointer. + */ +//int ck_rehash(ck_hash_table_t *table) +//{ +// dbg_ck_rehash("Rehashing items in table.\n"); +// SET_REHASHING_ON(&table->generation); + +// // we already have functions for the next generation, begin rehashing +// // we wil use the last item in the buffer as free cell for hashing +// assert(da_try_reserve(&table->stash, 1) == 0); +// ck_hash_table_item_t *old = (ck_hash_table_item_t *) +// (malloc(sizeof(ck_hash_table_item_t))); + +// do { +// dbg_ck_hash("Rehash!\n"); + +// if (da_get_count(&table->stash) > STASH_SIZE) { +// dbg_ck_hash("STASH RESIZED!!! (new stash size: %d)\n", +// da_get_count(&table->stash)); +// } + +// // rehash items from stash, starting from the last old item +// int stash_i = da_get_count(&table->stash) - 1; +// while (stash_i >= 0) { +// // if item's generation is the new generation, skip +// if (STASH_ITEMS(&table->stash)[stash_i] == NULL +// || !(EQUAL_GENERATIONS(STASH_ITEMS(&table->stash) +// [stash_i]->timestamp, +// table->generation))) { +// dbg_ck_rehash("Skipping item.\n"); +// --stash_i; +// continue; +// } + +// dbg_ck_rehash("Rehashing item from buffer position %u" +// ", key (length %u): %.*s, generation: " +// "%hu, table generation: %hu.\n", +// stash_i, +// STASH_ITEMS(&table->stash)[stash_i]->key_length, +// (int)STASH_ITEMS(&table->stash)[stash_i]->key_length, +// STASH_ITEMS(&table->stash)[stash_i]->key, +// GET_GENERATION( +// STASH_ITEMS(&table->stash)[stash_i]->timestamp), +// GET_GENERATION(table->generation)); + +// // otherwise copy the item for rehashing +// ck_put_item(&old, STASH_ITEMS(&table->stash)[stash_i]); +// // clear the place so that this item will not get +// // rehashed again +// ck_clear_item(&STASH_ITEMS(&table->stash)[stash_i]); +// da_release(&table->stash, 1); + +// // there should be at least one place in the stash +// assert(da_try_reserve(&table->stash, 1) == 0); +// da_reserve(&table->stash, 1); + +// assert(STASH_ITEMS(&table->stash)[stash_i] == NULL); + +// // and start rehashing +// if (ck_hash_item(table, &old, +// &STASH_ITEMS(&table->stash)[stash_i], +// NEXT_GENERATION(table->generation)) != 0) { +// // loop occured +// dbg_ck_hash("Hashing entered a loop.\n"); + +// dbg_ck_rehash("Item with key %.*s inserted " +// "into the stash on position %d.\n", +// STASH_ITEMS(&table->stash) +// [stash_i]->key_length, +// STASH_ITEMS(&table->stash) +// [stash_i]->key, +// da_get_count(&table->stash)); + +// // hashing unsuccessful, the item was inserted +// // into the stash +// da_occupy(&table->stash, 1); +// assert(STASH_ITEMS(&table->stash)[stash_i] +// != NULL); + +// // if only one place left, resize the stash +// // TODO: Why??? +// if (da_reserve(&table->stash, 2) < 0) { +// // stash could not be resized => !!! +// dbg_ck_hash("Failed to rehash items " +// "from " +// "table, no other rehash possible!\n"); +// // so rollback +// ck_rollback_rehash(table); +// // clear the 'old' item +// ck_clear_item(&old); +// return -1; +// } +// } + +// // clear the 'old' item +// ck_clear_item(&old); +// // decrement the index +// --stash_i; +// } + +// uint i = 0; +// while (i < da_get_count(&table->stash)) { +// assert(STASH_ITEMS(&table->stash)[i] != NULL); +// ++i; +// } +// dbg_ck_hash("OK\n"); +// assert(da_try_reserve(&table->stash, 1) == 0); +// assert(STASH_ITEMS(&table->stash)[da_get_count(&table->stash)] +// == NULL); + +// // rehash items from hash tables +// for (uint t = TABLE_FIRST; +// t <= TABLE_LAST(table->table_count); ++t) { +// dbg_ck_rehash("Rehashing items from table %d.\n", +// t + 1); +// uint rehashed = 0; + +// while (rehashed < hashsize(table->table_size_exp)) { + +// // if item's generation is the new generation, +// // skip +// if (table->tables[t][rehashed] == NULL +// || !(EQUAL_GENERATIONS( +// table->tables[t][rehashed]->timestamp, +// table->generation))) { +// dbg_ck_rehash("Skipping item.\n"); +// ++rehashed; +// continue; +// } + +// dbg_ck_rehash("Rehashing item with hash %u, " +// "key (length %u): %.*s, generation: %hu, " +// "table generation: %hu.\n", rehashed, +// table->tables[t][rehashed]->key_length, +// (int)(table->tables[t][rehashed]->key_length), +// table->tables[t][rehashed]->key, +// GET_GENERATION( +// table->tables[t][rehashed]->timestamp), +// GET_GENERATION(table->generation)); + +// // otherwise copy the item for rehashing +// ck_put_item(&old, table->tables[t][rehashed]); +// // clear the place so that this item will not +// // get rehashed again +// ck_clear_item(&table->tables[t][rehashed]); + +// dbg_ck_rehash("Table generation: %hu, next " +// "generation: %hu.\n", +// GET_GENERATION(table->generation), +// NEXT_GENERATION(table->generation)); + +// // and start rehashing +// assert(&old != &STASH_ITEMS(&table->stash)[ +// da_get_count(&table->stash)]); +// assert(da_try_reserve(&table->stash, 1) == 0); +// da_reserve(&table->stash, 1); + +// if (ck_hash_item(table, &old, +// &STASH_ITEMS(&table->stash)[ +// da_get_count(&table->stash)], +// NEXT_GENERATION(table->generation)) != 0) { +// // loop occured +// dbg_ck_hash("Hashing entered a loop." +// "\n"); +// dbg_ck_rehash("Item with key %.*s " +// "inserted into the stash on position " +// "%d.\n", STASH_ITEMS(&table->stash)[ +// da_get_count(&table->stash)] +// ->key_length, +// STASH_ITEMS(&table->stash)[ +// da_get_count(&table->stash)]->key, +// da_get_count(&table->stash)); + +// assert(STASH_ITEMS(&table->stash)[ +// da_get_count(&table->stash)] != NULL); +// // loop occured, the item is already at +// // its new place in the buffer, so just +// // increment the index +// da_occupy(&table->stash, 1); + +// // if only one place left, resize the +// // stash TODO: Why? +// if (da_reserve(&table->stash, 2) < 0) { +// // stash could not be resized +// dbg_ck_hash("Failed to rehash" +// " items from table, no other " +// "rehash possible!\n"); +// // so rollback +// ck_rollback_rehash(table); +// // clear the 'old' item +// ck_clear_item(&old); +// return -1; +// } +// } +// ++rehashed; +// } +// } + +// dbg_ck_rehash("Old table generation: %u\n", +// GET_GENERATION(table->generation)); +// // rehashing completed, switch generation of the table +// SET_NEXT_GENERATION(&table->generation); +// dbg_ck_rehash("New table generation: %u\n", +// GET_GENERATION(table->generation)); +// // generate new hash functions for the old generation +// dbg_ck_rehash("Generating coeficients for generation: %u\n", +// NEXT_GENERATION(table->generation)); +// us_next(NEXT_GENERATION(table->generation)); + +// // repeat rehashing while there are more items in the stash than +// // its initial size +// if (da_get_count(&table->stash) > STASH_SIZE) { +// dbg_ck_rehash("Rehashing again!\n"); +// } +// } while (da_get_count(&table->stash) > STASH_SIZE); + +// SET_REHASHING_OFF(&table->generation); + +// return 0; +//} + +/*----------------------------------------------------------------------------*/ + +void ck_dump_table(const ck_hash_table_t *table) +{ +#ifdef CUCKOO_DEBUG + uint i = 0; + dbg_ck("----------------------------------------------\n"); + dbg_ck("Hash table dump:\n\n"); + dbg_ck("Size of each table: %u\n\n", hashsize(table->table_size_exp)); + + for (uint t = 0; t < table->table_count; ++t) { + dbg_ck("Table %d:\n", t + 1); + + for (i = 0; i < hashsize(table->table_size_exp); i++) { + dbg_ck("Hash: %u, Key: %.*s, Value: %p.\n", i, + (int)(table->tables[t])[i]->key_length, + (table->tables[t])[i]->key, + (table->tables[t])[i]->value); + } + } + + dbg_ck("Stash:\n"); +// for (i = 0; i < da_get_count(&table->stash); ++i) { +// dbg_ck("Index: %u, Key: %.*s Value: %p.\n", i, +// ((ck_hash_table_item_t **) +// da_get_items(&table->stash))[i]->key_length, +// ((ck_hash_table_item_t **) +// da_get_items(&table->stash))[i]->key, +// ((ck_hash_table_item_t **) +// da_get_items(&table->stash))[i]->value); +// } + ck_stash_item_t *item = table->stash; + while (item != NULL) { + dbg_ck("Hash: %u, Key: %.*s, Value: %p.\n", i, + (int)item->item->key_length, item->item->key, + item->item->value); + item = item->next; + } + + dbg_ck("\n"); +#endif +} diff --git a/src/libknot/hash/cuckoo-hash-table.h b/src/libknot/hash/cuckoo-hash-table.h new file mode 100644 index 0000000..dd78294 --- /dev/null +++ b/src/libknot/hash/cuckoo-hash-table.h @@ -0,0 +1,333 @@ +/*! + * \file cuckoo-hash-table.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Implementation of Cuckoo hashing scheme. + * + * Uses d-ary Cuckoo hashing with stash. + * + * \todo Maybe provide some way to resize the whole table if the number of items + * grows too much. + * \todo Check size of integers, the table size may be larger than unsigned int. + * \todo Maybe do not return ck_hash_table_item from ck_find_item(), but only + * its value. + * \todo When hashing an item, only the first table is tried for this item. + * We may try all tables. (But it is not neccessary.) + * + * \addtogroup hashing + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_CUCKOO_HASH_TABLE_H_ +#define _KNOT_CUCKOO_HASH_TABLE_H_ + +#include <stdint.h> /* uint32_t */ +#include <stdlib.h> /* size_t */ +#include <pthread.h> + +#include "hash/universal-system.h" +#include "common/dynamic-array.h" + +/*----------------------------------------------------------------------------*/ + +/*! \brief Macro for getting one hash table size. */ +#define hashsize(n) ((uint32_t)1 << (n)) + +/*! + * \brief Max number of hash tables - must be the same as number of the hash + * functions in each generation of the universal system. + */ +#define MAX_TABLES US_FNC_COUNT + +/*! \brief Default stash size. */ +static const uint STASH_SIZE = 10; + +/*! \brief Maximum stash size. When achieved, rehashing is needed. */ +static const uint STASH_SIZE_MAX = 30; + +/*----------------------------------------------------------------------------*/ +/* Public structures */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure for storing the hashed data. + */ +struct ck_hash_table_item { + const char *key; /*!< Key of the item, used for hashing. */ + + size_t key_length; /*!< Length of the key in octets. */ + + void *value; /*!< The actual item stored in the table. */ + + /*! + * \brief Flags. Currently used for keeping the generation of the item, + * i.e. the generation of the functions used for hashing this + * item. + * + * Form: 000000xy; + * xy - generation; may be 01 (1) or 10 (2). + */ + uint8_t timestamp; +}; + +typedef struct ck_hash_table_item ck_hash_table_item_t; + +struct ck_stash_item { + ck_hash_table_item_t *item; + struct ck_stash_item *next; +}; + +typedef struct ck_stash_item ck_stash_item_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Hash table structure which uses cuckoo hashing. + * + * Keys are expected to be strings of characters (char *), not necesarily + * null-terminated. It uses the Fowler/Noll/Vo (FNV) hash function to + * obtain a 32bit unsigned integer from the character data and a function + * randomly chosen from an universal system (see universal-system.h) to obtain + * the final hash. The FNV hash was taken from + * http://home.comcast.net/~bretm/hash/6.html and the universal system is + * constructed according to Katajainen J., Lykke M., Experiments with universal + * hashing (obtained from + * http://www.diku.dk/OLD/publikationer/tekniske.rapporter/rapporter/96-08.pdf). + * + * The table uses either 3-ary or 4-ary cuckoo hashing (and thus 3 or 4 tables) + * with stash, according to the number of items provided to ck_create_table() + * function. The number of table pointers is however set to be the larger value + * (4) always, so the \a tables array may be statically allocated. Size of one + * table is always a power of 2 (due to the character of the hash function). + * The stash has a default size STASH_SIZE, but can be resized if needed. + * However, the resizing is only done in rehashing process, if the items do not + * fit into the table and the original stash. + * + * Rehashing is done when the stash gets full (actually, last item is always + * free and is used in the rehashing process as a temporary variable). + */ +struct ck_hash_table { + uint table_count; /*!< Actual number of hash tables used. */ + + /*! + * \brief Exponent of one table's size (2^table_size_exp is table size). + */ + int table_size_exp; + + ck_hash_table_item_t **tables[MAX_TABLES]; /*!< Array of hash tables. */ + + //da_array_t stash; /*!< Stash implemented as a dynamic array. */ + ck_stash_item_t *stash; + + /*! \brief Temporary storage for item being hashed. */ + ck_hash_table_item_t *hashed; + + /*! \brief Mutex for avoiding multiple insertions / rehashes at once. */ + pthread_mutex_t mtx_table; + + /*! + * \brief Flags used for determining which hash functions are currently + * used + * + * Form: 00000xyz. + * x - rehash flag (1 if rehashing is in progress) + * yz - generation (may be 10 = 2, or 01 = 1) + * + * There are always two sets of hash functions available via the + * us_hash() function (see universal-hashing.h). Normally all items in + * the table are hashed using one set of functions. However, during + * rehash, the other set is used for rehashing. In this case the rehash + * flag (x) is set, so the lookup function (ck_find_item()) tries to use + * both sets of functions when searching for item. + */ + uint8_t generation; + + us_system_t hash_system; /*!< Universal system of hash functions. */ + + size_t items; + size_t items_in_stash; +}; + +typedef struct ck_hash_table ck_hash_table_t; + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates and initializes the hash table structure. + * + * All hash tables are allocated and their items initialized to 0 (NULL). + * A stash of default size is also created. The \a generation flags are set to + * 0. + * + * \param items Number of items to be hashed to the table. This number + * determines the size of the hash table that will be created. + * + * + * \return Pointer to the initialized hash table. + */ +ck_hash_table_t *ck_create_table(uint items); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Destroys the whole hash table together with the saved values. + * + * \param table Pointer to pointer to the hash table. + * \param dtor_value Destructor function for the values that are be stored in + * the hash table. Set to NULL if you do not want the values + * to be deleted. + * \param delete_key Set to 0 if you do not want the function to delete the + * key of the item (e.g. when used elsewhere). Set to any + * other value otherwise. + * + * \note Make sure the table and its items are not used anymore when calling + * this function. + */ +void ck_destroy_table(ck_hash_table_t **table, + void (*dtor_value)(void *value), int delete_key); + +/*! + * \brief Destroys the table structures, but does not remove the individual + * hash table items. + */ +void ck_table_free(ck_hash_table_t **table); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Inserts item into the hash table. + * + * Insertion starts always by trying to hash the item into the first table. The + * possible displaced item is then hashed into randomly chosen other table, + * etc., until a free place is found or a loop occured. A loop occurs when one + * position in one table is tried more than twice. + * + * \param table Hash table the item should be inserted into. + * \param key Item's key. It can be any string of octets. The key is not copied + * by the function. + * \param length Length of the key in bytes (octets). + * \param value Pointer to the actual item to be inserted into the hash table. + * + * \note This function does not copy the key. + * \note This function may trigger rehash of the whole table in case the stash + * gets full. + * + * \retval 0 No error. + * \retval -1 Insertion failed. This may occur only when the rehashing fails. + * In this case it is necessary to somehow manually force another + * rehash as no other rehash would be possible. + */ +int ck_insert_item(ck_hash_table_t *table, const char *key, size_t length, + void *value); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Finds item in table. + * + * \param table Hash table to search in. + * \param key Key of the item. It can be an arbitrary string of octets. + * \param length Length of the key in bytes (octets). + * + * \return Pointer to the item if found. NULL otherwise. + */ +const ck_hash_table_item_t *ck_find_item(const ck_hash_table_t *table, + const char *key, size_t length); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Updates item with the given key by replacing its value. + * + * The update process is synchronized using RCU mechanism, so the old item's + * value will not be deleted while some thread is using it. + * + * \param table Hash table where to search for the item. + * \param key Key of the item to be updated. It can be an arbitrary string of + * octets. + * \param length Length of the key in bytes (octets). + * \param new_value New value for the item with key \a key. + * \param dtor_value Destructor function for the values that are be stored in + * the hash table. Set to NULL if you do not want the values + * to be deleted. + * + * \retval 0 If successful. + * \retval -1 If the item was not found in the table. No changes are made. + */ +int ck_update_item(const ck_hash_table_t *table, const char *key, size_t length, + void *new_value, void (*dtor_value)(void *value)); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Removes item with the given key from table. + * + * The deletion process is synchronized using RCU mechanism, so the old item + * will not be deleted while some thread is using it. + * + * \param table Hash table where to search for the item. + * \param key Key of the item to be removed. It can be an arbitrary string of + * octets. + * \param length Length of the key in bytes (octets). + * \param dtor_value Destructor function for the values that are be stored in + * the hash table. Set to NULL if you do not want the values + * to be deleted. + * \param delete_key Set to 0 if you do not want the function to delete the + * key of the item (e.g. when used elsewhere). Set to any + * other value otherwise. + * + * \retval 0 If successful. + * \retval -1 If the item was not found in the table. + */ +int ck_delete_item(const ck_hash_table_t *table, const char *key, size_t length, + void (*dtor_value)(void *value), int delete_key); + +ck_hash_table_item_t *ck_remove_item(ck_hash_table_t *table, const char *key, + size_t length); + +/*! + * \brief Creates a shallow copy of the cuckoo hash table. + * + * This function creates just the ck_hash_table_t structure and its tables and + * stash. It does not copy individual ck_hash_table_item_t structures. + * + * \param from Table to copy. + * \param to The new copy will be stored here. + * + * \retval 0 if successful. + * \retval + */ +int ck_shallow_copy(const ck_hash_table_t *from, ck_hash_table_t **to); + +int ck_apply(ck_hash_table_t *table, + void (*function)(ck_hash_table_item_t *item, void *data), + void *data); + +/*----------------------------------------------------------------------------*/ + +int ck_rehash(ck_hash_table_t *table); + +// for testing purposes only +int ck_resize_table(ck_hash_table_t *table); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Dumps the whole hash table to the standard output. + */ +void ck_dump_table(const ck_hash_table_t *table); + +/*----------------------------------------------------------------------------*/ + +#endif /* _KNOT_CUCKOO_HASH_TABLE_H_ */ + +/*! @} */ diff --git a/src/libknot/hash/hash-functions.c b/src/libknot/hash/hash-functions.c new file mode 100644 index 0000000..a33dd6b --- /dev/null +++ b/src/libknot/hash/hash-functions.c @@ -0,0 +1,241 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include "hash-functions.h" + +/*--------------------------------- FNV HASH ---------------------------------*/ + +unsigned long int fnv_hash(const char *data, int size, int bits) +{ + int shift, i; + unsigned long int mask; + unsigned long int hash = 2166136261; + + if (bits == -1) { + shift = 0; + mask = 0xFFFFFFFF; + } else { + shift = 32 - bits; + mask = (1U << shift) - 1U; + } + + for (i = 0; i < size; i++) { + hash = (hash * 16777619) ^ data[i]; + } + + if (shift == 0) { + return hash; + } + + return (hash ^(hash >> shift)) & mask; +} + +/*------------------------------- JENKINS HASH -------------------------------*/ + +/* The mixing step */ +/* +#define mix(a,b,c) \ + { \ + a=a-b; a=a-c; a=a^(c>>13); \ + b=b-c; b=b-a; b=b^(a<<8); \ + c=c-a; c=c-b; c=c^(b>>13); \ + a=a-b; a=a-c; a=a^(c>>12); \ + b=b-c; b=b-a; b=b^(a<<16); \ + c=c-a; c=c-b; c=c^(b>>5); \ + a=a-b; a=a-c; a=a^(c>>3); \ + b=b-c; b=b-a; b=b^(a<<10); \ + c=c-a; c=c-b; c=c^(b>>15); \ + } +*/ + +///* The whole new hash function */ +//u4 jhash(register u1 *k, u4 length, u4 initval) +//{ +// register u4 a, b, c; /* the internal state */ +// u4 len; /* how many key bytes still need mixing */ + +// /* Set up the internal state */ +// len = length; +// a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ +// c = initval; /* variable initialization of internal state */ + +// /*---------------------------------------- handle most of the key */ +// while (len >= 12) { +// a = a + (k[0] + ((u4)k[1] << 8) +// + ((u4)k[2] << 16) + ((u4)k[3] << 24)); +// b = b + (k[4] + ((u4)k[5] << 8) +// + ((u4)k[6] << 16) + ((u4)k[7] << 24)); +// c = c + (k[8] + ((u4)k[9] << 8) +// + ((u4)k[10] << 16) + ((u4)k[11] << 24)); +// mix(a, b, c); +// k = k + 12; +// len = len - 12; +// } + +// /*------------------------------------- handle the last 11 bytes */ +// c = c + length; +// switch (len) { /* all the case statements fall through */ +// case 11: +// c = c + ((u4)k[10] << 24); +// case 10: +// c = c + ((u4)k[9] << 16); +// case 9 : +// c = c + ((u4)k[8] << 8); +// /* the first byte of c is reserved for the length */ +// case 8 : +// b = b + ((u4)k[7] << 24); +// case 7 : +// b = b + ((u4)k[6] << 16); +// case 6 : +// b = b + ((u4)k[5] << 8); +// case 5 : +// b = b + k[4]; +// case 4 : +// a = a + ((u4)k[3] << 24); +// case 3 : +// a = a + ((u4)k[2] << 16); +// case 2 : +// a = a + ((u4)k[1] << 8); +// case 1 : +// a = a + k[0]; +// /* case 0: nothing left to add */ +// } +// mix(a, b, c); +// /*-------------------------------------------- report the result */ +// return c; +//} + + + +#define hashsize(n) ((ub4)1<<(n)) +#define hashmask(n) (hashsize(n)-1) + +/* +-------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. +For every delta with one or two bits set, and the deltas of all three + high bits or all three low bits, whether the original value of a,b,c + is almost all zero or is uniformly distributed, +* If mix() is run forward or backward, at least 32 bits in a,b,c + have at least 1/4 probability of changing. +* If mix() is run forward, every bit of c will change between 1/3 and + 2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.) +mix() was built out of 36 single-cycle latency instructions in a + structure that could supported 2x parallelism, like so: + a -= b; + a -= c; x = (c>>13); + b -= c; a ^= x; + b -= a; x = (a<<8); + c -= a; b ^= x; + c -= b; x = (b>>13); + ... + Unfortunately, superscalar Pentiums and Sparcs can't take advantage + of that parallelism. They've also turned some of those single-cycle + latency instructions into multi-cycle latency instructions. Still, + this is the fastest good hash I could find. There were about 2^^68 + to choose from. I only looked at a billion or so. +-------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<<8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ +} + +/* +-------------------------------------------------------------------- +hash() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + len : the length of the key, counting by bytes + initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Every 1-bit and 2-bit delta achieves avalanche. +About 6*len+35 instructions. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (ub1 **)k, do it like this: + for (i=0, h=0; i<n; ++i) h = hash( k[i], len[i], h); + +By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. You may use this +code any way you wish, private, educational, or commercial. It's free. + +See http://burtleburtle.net/bob/hash/evahash.html +Use for hash table lookup, or anything where one collision in 2^^32 is +acceptable. Do NOT use for cryptographic purposes. +-------------------------------------------------------------------- +*/ + +ub4 jhash(k, length, initval) +register ub1 *k; /* the key */ +register ub4 length; /* the length of the key */ +register ub4 initval; /* the previous hash, or an arbitrary value */ +{ + register ub4 a,b,c,len; + + /* Set up the internal state */ + len = length; + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = initval; /* the previous hash value */ + + /*---------------------------------------- handle most of the key */ + while (len >= 12) + { + a += (k[0] +((ub4)k[1]<<8) +((ub4)k[2]<<16) +((ub4)k[3]<<24)); + b += (k[4] +((ub4)k[5]<<8) +((ub4)k[6]<<16) +((ub4)k[7]<<24)); + c += (k[8] +((ub4)k[9]<<8) +((ub4)k[10]<<16)+((ub4)k[11]<<24)); + mix(a,b,c); + k += 12; len -= 12; + } + + /*------------------------------------- handle the last 11 bytes */ + c += length; + switch(len) /* all the case statements fall through */ + { + case 11: c+=((ub4)k[10]<<24); + case 10: c+=((ub4)k[9]<<16); + case 9 : c+=((ub4)k[8]<<8); + /* the first byte of c is reserved for the length */ + case 8 : b+=((ub4)k[7]<<24); + case 7 : b+=((ub4)k[6]<<16); + case 6 : b+=((ub4)k[5]<<8); + case 5 : b+=k[4]; + case 4 : a+=((ub4)k[3]<<24); + case 3 : a+=((ub4)k[2]<<16); + case 2 : a+=((ub4)k[1]<<8); + case 1 : a+=k[0]; + /* case 0: nothing left to add */ + } + mix(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} + +#undef hashsize +#undef hashmask + diff --git a/src/libknot/hash/hash-functions.h b/src/libknot/hash/hash-functions.h new file mode 100644 index 0000000..f23730b --- /dev/null +++ b/src/libknot/hash/hash-functions.h @@ -0,0 +1,85 @@ +/*! + * \file hash-functions.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Various hash functions. + * + * All of the hash functions are downloaded from various sources. + * + * \todo Add references to sources. + * + * \addtogroup hashing + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_HASH_FUNCTIONS_H_ +#define _KNOT_HASH_FUNCTIONS_H_ + +#include <stdint.h> +#include <string.h> + +/* + * Fowler / Noll / Vo Hash (FNV Hash) + * http://www.isthe.com/chongo/tech/comp/fnv/ + * + * This is an implementation of the algorithms posted above. + * This file is placed in the public domain by Peter Wemm. + * + * $FreeBSD: src/sys/sys/fnv_hash.h,v 1.2.2.1 2001/03/21 10:50:59 peter Exp $ + */ + +typedef uint32_t Fnv32_t; + +#define FNV1_32_INIT ((Fnv32_t) 33554467UL) + +#define FNV_32_PRIME ((Fnv32_t) 0x01000193UL) + +static __inline Fnv32_t +fnv_32_buf(const void *buf, size_t len, Fnv32_t hval) +{ + const uint8_t *s = (const uint8_t *)buf; + + while (len-- != 0) { + hval *= FNV_32_PRIME; + hval ^= *s++; + } + return hval; +} + +/*! + * \brief Jenkins hash function. + * + * Downloaded from http://burtleburtle.net/bob/hash/evahash.html + * + * \param k Data to hash + * \param length Size of the data in bytes. + * \param initval The previous hash or an arbitrary value. + * + * \return Hash of the data. + * + * \todo Add source. + */ +typedef unsigned long int ub4; /* unsigned 4-byte quantities */ +typedef unsigned char ub1; /* unsigned 1-byte quantities */ + +ub4 jhash(register ub1 *k, register ub4 length, register ub4 initval); + +#endif /* _KNOT_HASH_FUNCTIONS_H_ */ + +/*! @} */ diff --git a/src/libknot/hash/universal-system.c b/src/libknot/hash/universal-system.c new file mode 100644 index 0000000..096974c --- /dev/null +++ b/src/libknot/hash/universal-system.c @@ -0,0 +1,116 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <limits.h> +#include <stdint.h> +#include <time.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "universal-system.h" +#include "common.h" +#include "util/utils.h" + +/*----------------------------------------------------------------------------*/ + +const uint MAX_UINT_EXP = 32; +const unsigned long MAX_UINT_MY = UINT32_MAX; /* 4294967295 */ + +/*----------------------------------------------------------------------------*/ +/* Private functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Generates new set of coeficients. + * + * \param system Universal system to generate the coeficients for. + * \param from First coeficient to be replaced. + * \param to Up to this the coeficients will be replaced. + */ +static void us_generate_coefs(us_system_t *system, uint from, uint to) +{ + assert(system != NULL); + + for (uint i = from; i < to; ++i) { + int used = 0; + + do { + // generate random odd number + system->coefs[i] = knot_quick_rand() % MAX_UINT_MY; + if (system->coefs[i] % 2 == 0) { + system->coefs[i] = (system->coefs[i] == 0) + ? 1 + : system->coefs[i] - 1; + } + // check if this coeficient is already used + uint j = from; + while (used == 0 && j < i) { + if (system->coefs[j++] == system->coefs[i]) { + used = 1; + } + } + // if already used, generate again + } while (used != 0); + } +} + +/*----------------------------------------------------------------------------*/ +/* Public functions */ +/*----------------------------------------------------------------------------*/ + +void us_initialize(us_system_t *system) +{ + assert(system != NULL); + assert(UINT_MAX == MAX_UINT_MY); + + // Initialize both generations of functions by generating random odd + // numbers + us_generate_coefs(system, 0, US_FNC_COUNT * GEN_COUNT); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \note \a generation starts from 1 + */ +int us_next(us_system_t *system, uint generation) +{ + assert(system != NULL); + // generate new coeficients for the new generation + us_generate_coefs(system, (generation - 1) * US_FNC_COUNT, + generation * US_FNC_COUNT); + return 0; +} + +/*----------------------------------------------------------------------------*/ + +uint32_t us_hash(const us_system_t *system, uint32_t value, uint table_exp, + uint fnc, uint generation) +{ + /* + * multiplication should overflow if larger than MAX_UINT + * this is the same as (coef * value) mod MAX_UINT + * + * TODO: maybe we should not rely on this + */ + assert(system != NULL); + assert(table_exp <= 32); + assert(fnc < US_FNC_COUNT); + assert(generation <= GEN_COUNT); + + return ((system->coefs[((generation - 1) * US_FNC_COUNT) + fnc] * value) + >> (MAX_UINT_EXP - table_exp)); +} diff --git a/src/libknot/hash/universal-system.h b/src/libknot/hash/universal-system.h new file mode 100644 index 0000000..25330de --- /dev/null +++ b/src/libknot/hash/universal-system.h @@ -0,0 +1,109 @@ +/*! + * \file universal-system.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * This file provides interface to a 2-universal system of hash functions that + * hash from 32-bit unsigned integer to a 32-bit unsigned integer within a given + * range. The range is always a power of two and is given by the exponent (see + * function us_hash(). + * + * Before using the system, it must be initialized by calling us_initialize(). + * The system stores 2 sets (generations), each of US_FNC_COUNT functions. + * For generating a new set of coeficients (i.e. hash functions) use the + * us_next() function. + * + * For hashing use the us_hash() function. + * + * \todo What if all numbers are tried and still need rehash? + * (that means 2mld rehashes - we can probably live with that ;) + * \todo Consider counting generations from 0, will be easier! + * \todo Check out some better random number generator. + * + * \addtogroup hashing + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_UNIVERSAL_SYSTEM_H_ +#define _KNOT_UNIVERSAL_SYSTEM_H_ + +#include <stdint.h> +#include "common.h" + + +enum { US_FNC_COUNT = 4 /*!< Number of functions for one generation. */ }; + +enum { GEN_COUNT = 2 /*!< Number of generations. */ }; + +/*----------------------------------------------------------------------------*/ +/*! \brief Analytically defined universal system of hashing functions. */ +struct us_system { + /*! \brief Coeficients for the functions */ + uint coefs[US_FNC_COUNT * GEN_COUNT]; +}; + +typedef struct us_system us_system_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Initializes the universal system by generating coeficients for all + * hash functions and all generations. + * + * \param system Universal system to be used. + */ +void us_initialize(us_system_t *system); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Generates new hash functions' coeficients for the given \a generation. + * + * \param system Universal system to be used. + * \param generation Generation for which to generate the new coeficients. + * + * \return 0 + */ +int us_next(us_system_t *system, uint generation); + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Hashes the \a value using the given \a exponent and function. + * + * The actual formula of the hash is: + * h = ((coef * value) mod 2^32) / 2^(32 - table_exp) + * where \a coef is the proper coeficient. + * + * \param system Universal system to be used. + * \param value Value to be hashed. + * \param table_exp Determines the upper bound for the result - the hash will + * be between 0 and 2^(32 - table_exp). + * \param fnc Which function from the set should be used. + * \param generation Which set (generation) of functions should be used. + * + * \todo Make inline? + * + * \return Hash value (32bit unsigned). + */ +uint32_t us_hash(const us_system_t *system, uint32_t value, uint table_exp, + uint fnc, uint generation); + +/*----------------------------------------------------------------------------*/ + +#endif /* _KNOT_UNIVERSAL_SYSTEM_H_ */ + +/*! @} */ diff --git a/src/libknot/libknot.h b/src/libknot/libknot.h new file mode 100644 index 0000000..a401be7 --- /dev/null +++ b/src/libknot/libknot.h @@ -0,0 +1,48 @@ +/*! + * \file libknot.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * \brief Convenience header for including whole library. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_LIBKNOT_H_ +#define _KNOT_LIBKNOT_H_ + +#include "consts.h" +#include "util/descriptor.h" +#include "dname.h" +#include "edns.h" +#include "zone/node.h" +#include "nsec3.h" +#include "util/wire.h" +#include "rdata.h" +#include "packet/response.h" +#include "rrset.h" +#include "util/tolower.h" +#include "util/utils.h" +#include "zone/zone.h" +#include "zone/zonedb.h" +#include "util/error.h" + +#endif + +/*! @} */ diff --git a/src/libknot/nameserver/name-server.c b/src/libknot/nameserver/name-server.c new file mode 100644 index 0000000..f88f802 --- /dev/null +++ b/src/libknot/nameserver/name-server.c @@ -0,0 +1,3663 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <assert.h> +#include <sys/time.h> + +#include <urcu.h> + +#include "nameserver/name-server.h" +#include "updates/xfr-in.h" + +#include "util/error.h" +#include "libknot.h" +#include "util/debug.h" +#include "packet/packet.h" +#include "packet/response.h" +#include "packet/query.h" +#include "consts.h" +#include "updates/changesets.h" +#include "updates/ddns.h" +#include "tsig-op.h" + +/*----------------------------------------------------------------------------*/ + +/*! \brief Maximum UDP payload with EDNS enabled. */ +static const uint16_t MAX_UDP_PAYLOAD_EDNS = 4096; +/*! \brief Maximum UDP payload with EDNS disabled. */ +static const uint16_t MAX_UDP_PAYLOAD = 504; // 512 - 8B header +/*! \brief Maximum size of one AXFR response packet. */ +static const uint16_t MAX_AXFR_PAYLOAD = 65535; +/*! \brief Supported EDNS version. */ +static const uint8_t EDNS_VERSION = 0; +/*! \brief Determines whether EDNS is enabled. */ +static const int EDNS_ENABLED = 1; + +/*! \brief TTL of a CNAME synthetized from a DNAME. */ +static const uint32_t SYNTH_CNAME_TTL = 0; + +/*! \brief Determines whether DNSSEC is enabled. */ +static const int DNSSEC_ENABLED = 1; + +/*! \brief Determines whether NSID is enabled. */ +static const int NSID_ENABLED = 1; + +/*! \brief Length of NSID option data. */ +static const uint16_t NSID_LENGTH = 6; +/*! \brief NSID option data. */ +static const uint8_t NSID_DATA[6] = {0x46, 0x6f, 0x6f, 0x42, 0x61, 0x72}; + +/*! \brief Internal error code to propagate need for SERVFAIL response. */ +static const int NS_ERR_SERVFAIL = -999; + +/*----------------------------------------------------------------------------*/ +/* Private functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Finds zone where to search for the QNAME. + * + * \note As QTYPE DS requires special handling, this function finds a zone for + * a direct predecessor of QNAME in such case. + * + * \param zdb Zone database where to search for the proper zone. + * \param qname QNAME. + * \param qtype QTYPE. + * + * \return Zone to which QNAME belongs (according to QTYPE), or NULL if no such + * zone was found. + */ +static const knot_zone_t *ns_get_zone_for_qname(knot_zonedb_t *zdb, + const knot_dname_t *qname, + uint16_t qtype) +{ + const knot_zone_t *zone; + /* + * Find a zone in which to search. + * + * In case of DS query, we strip the leftmost label when searching for + * the zone (but use whole qname in search for the record), as the DS + * records are only present in a parent zone. + */ + if (qtype == KNOT_RRTYPE_DS) { + /*! \todo Optimize, do not deep copy dname. */ + knot_dname_t *name = knot_dname_left_chop(qname); + zone = knot_zonedb_find_zone_for_name(zdb, name); + /* Directly discard. */ + knot_dname_free(&name); + } else { + zone = knot_zonedb_find_zone_for_name(zdb, qname); + } + + return zone; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Synthetizes RRSet from a wildcard RRSet using the given QNAME. + * + * The synthetized RRSet is identical to the wildcard RRSets, except that the + * owner name is replaced by \a qname. + * + * \param wildcard_rrset Wildcard RRSet to synthetize from. + * \param qname Domain name to be used as the owner of the synthetized RRset. + * + * \return The synthetized RRSet (this is a newly created RRSet, remember to + * free it). + */ +static knot_rrset_t *ns_synth_from_wildcard( + const knot_rrset_t *wildcard_rrset, const knot_dname_t *qname) +{ + dbg_ns("Synthetizing RRSet from wildcard...\n"); + + knot_dname_t *owner = knot_dname_deep_copy(qname); +// printf("Copied owner ptr: %p\n", owner); + + knot_rrset_t *synth_rrset = knot_rrset_new( + owner, knot_rrset_type(wildcard_rrset), + knot_rrset_class(wildcard_rrset), + knot_rrset_ttl(wildcard_rrset)); + + /* Release owner, as it's retained in rrset. */ + knot_dname_release(owner); + + if (synth_rrset == NULL) { + return NULL; + } + + dbg_ns("Created RRSet header:\n"); + knot_rrset_dump(synth_rrset, 1); + + // copy all RDATA + const knot_rdata_t *rdata = knot_rrset_rdata(wildcard_rrset); + while (rdata != NULL) { + // we could use the RDATA from the wildcard rrset + // but there is no way to distinguish it when deleting + // temporary RRSets + knot_rdata_t *rdata_copy = knot_rdata_deep_copy(rdata, + knot_rrset_type(synth_rrset)); + if (rdata_copy == NULL) { + knot_rrset_deep_free(&synth_rrset, 1, 1, 0); + return NULL; + } + + dbg_ns("Copied RDATA:\n"); + knot_rdata_dump(rdata_copy, + knot_rrset_type(synth_rrset), 1); + + knot_rrset_add_rdata(synth_rrset, rdata_copy); + rdata = knot_rrset_rdata_next(wildcard_rrset, rdata); + } + +// printf("Synthetized RRSet pointer: %p\n", synth_rrset); + return synth_rrset; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Checks if the given RRSet is a wildcard RRSet and replaces it with + * a synthetized RRSet if required. + * + * \param name Domain name to be used as the owner of the possibly synthetized + * RRSet + * \param resp Response to which the synthetized RRSet should be stored (as a + * temporary RRSet). + * \param rrset RRSet to check (and possibly replace). + */ +static void ns_check_wildcard(const knot_dname_t *name, knot_packet_t *resp, + const knot_rrset_t **rrset) +{ + assert(name != NULL); + assert(resp != NULL); + assert(rrset != NULL); + assert(*rrset != NULL); + + if (knot_dname_is_wildcard((*rrset)->owner)) { + knot_rrset_t *synth_rrset = + ns_synth_from_wildcard(*rrset, name); + dbg_ns("Synthetized RRSet:\n"); + knot_rrset_dump(synth_rrset, 1); + knot_packet_add_tmp_rrset(resp, synth_rrset); + *rrset = synth_rrset; + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds signatures (RRSIGs) for the given RRSet to the response. + * + * This function first checks if DNSSEC is enabled and if it was requested in + * the response (DO bit set). If not, it does nothing and returns 0. If yes, + * it retrieves RRSIGs stored in the RRSet, deals with possible wildcard owner + * and adds the RRSIGs to response using the given function (that determines + * to which section of the response they will be added). + * + * \param rrset RRSet to get the RRSIGs from. + * \param resp Response where to add the RRSIGs. + * \param name Actual name to be used as owner in case of wildcard RRSet. + * \param add_rrset_to_resp Function for adding the RRSIG RRset to the response. + * \param tc Set to 1 if omitting the RRSIG RRSet should result in setting the + * TC bit in the response. + * + * \return KNOT_EOK + * \return KNOT_ENOMEM + * \return KNOT_ESPACE + */ +static int ns_add_rrsigs(const knot_rrset_t *rrset, knot_packet_t *resp, + const knot_dname_t *name, + int (*add_rrset_to_resp)(knot_packet_t *, + const knot_rrset_t *, + int, int, int), + int tc) +{ + const knot_rrset_t *rrsigs; + + dbg_ns("Adding RRSIGs for RRSet, type: %s.\n", + knot_rrtype_to_string(knot_rrset_type(rrset))); + + assert(resp != NULL); + assert(add_rrset_to_resp != NULL); + + dbg_ns("DNSSEC requested: %d\n", + knot_query_dnssec_requested(knot_packet_query(resp))); + dbg_ns("RRSIGS: %p\n", knot_rrset_rrsigs(rrset)); + + if (DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp)) + && (rrsigs = knot_rrset_rrsigs(rrset)) != NULL) { + if (name != NULL) { + ns_check_wildcard(name, resp, &rrsigs); + } + return add_rrset_to_resp(resp, rrsigs, tc, 0, 0); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Resolves CNAME chain starting in \a node, stores all the CNAMEs in the + * response and updates \a node and \a qname to the last node in the + * chain. + * + * \param node Node (possibly) containing a CNAME RR. + * \param qname Searched name. Will be updated to the canonical name. + * \param resp Response where to add the CNAME RRs. + * \param add_rrset_to_resp Function for adding the CNAME RRs to the response. + * \param tc Set to 1 if omitting the RRSIG RRSet should result in setting the + * TC bit in the response. + */ +static void ns_follow_cname(const knot_node_t **node, + const knot_dname_t **qname, + knot_packet_t *resp, + int (*add_rrset_to_resp)(knot_packet_t *, + const knot_rrset_t *, + int, int, int), + int tc) +{ + dbg_ns("Resolving CNAME chain...\n"); + const knot_rrset_t *cname_rrset; + + while (*node != NULL + && (cname_rrset = knot_node_rrset(*node, KNOT_RRTYPE_CNAME)) + != NULL) { + /* put the CNAME record to answer, but replace the possible + wildcard name with qname */ + + assert(cname_rrset != NULL); + + dbg_ns("CNAME RRSet: %p, owner: %p\n", cname_rrset, + cname_rrset->owner); + + const knot_rrset_t *rrset = cname_rrset; + + // ignoring other than the first record + if (knot_dname_is_wildcard(knot_node_owner(*node))) { + /* if wildcard node, we must copy the RRSet and + replace its owner */ + rrset = ns_synth_from_wildcard(cname_rrset, *qname); + knot_packet_add_tmp_rrset(resp, (knot_rrset_t *)rrset); + add_rrset_to_resp(resp, rrset, tc, 0, 0); + ns_add_rrsigs(cname_rrset, resp, *qname, + add_rrset_to_resp, tc); + } else { + add_rrset_to_resp(resp, rrset, tc, 0, 0); + ns_add_rrsigs(rrset, resp, *qname, add_rrset_to_resp, + tc); + } + + dbg_ns("Using RRSet: %p, owner: %p\n", rrset, rrset->owner); + +dbg_ns_exec( + char *name = knot_dname_to_str(knot_rrset_owner(rrset)); + dbg_ns("CNAME record for owner %s put to response.\n", name); + free(name); +); + + // get the name from the CNAME RDATA + const knot_dname_t *cname = knot_rdata_cname_name( + knot_rrset_rdata(cname_rrset)); + dbg_ns("CNAME name from RDATA: %p\n", cname); + // change the node to the node of that name + *node = knot_dname_node(cname, 1); + dbg_ns("This name's node: %p\n", *node); +// // it is not an old node and if yes, skip it +// if (knot_node_is_old(*node)) { +// *node = knot_node_new_node(*node); +// } + + // save the new name which should be used for replacing wildcard + *qname = cname; + }; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Retrieves RRSet(s) of given type from the given node and adds them to + * the response's Answer section. + * + * \param node Node where to take the RRSet from. + * \param name Actual searched name (used in case of wildcard RRSet(s)). + * \param type Type of the RRSet(s). If set to KNOT_RRTYPE_ANY, all RRSets + * from the node will be added to the answer. + * \param resp Response where to add the RRSets. + * + * \return Number of RRSets added. + */ +static int ns_put_answer(const knot_node_t *node, const knot_dname_t *name, + uint16_t type, knot_packet_t *resp) +{ + int added = 0; +dbg_ns_exec( + char *name_str = knot_dname_to_str(node->owner); + dbg_ns("Putting answers from node %s.\n", name_str); + free(name_str); +); + + switch (type) { + case KNOT_RRTYPE_ANY: { + dbg_ns("Returning all RRTYPES.\n"); + const knot_rrset_t **rrsets = knot_node_rrsets(node); + if (rrsets == NULL) { + break; + } + int i = 0; + int ret = 0; + const knot_rrset_t *rrset; + while (i < knot_node_rrset_count(node)) { + assert(rrsets[i] != NULL); + rrset = rrsets[i]; + + dbg_ns(" Type: %s\n", + knot_rrtype_to_string(knot_rrset_type(rrset))); + + ns_check_wildcard(name, resp, &rrset); + ret = knot_response_add_rrset_answer(resp, rrset, 1, + 0, 0); + if (ret >= 0 && (added += 1) + && (ret = ns_add_rrsigs(rrset, resp, name, + knot_response_add_rrset_answer, 1)) + >=0 ) { + added += 1; + } else { + free(rrsets); + rrsets = NULL; + break; + } + + ++i; + } + if (rrsets != NULL) { + free(rrsets); + } + break; + } + case KNOT_RRTYPE_RRSIG: { + dbg_ns("Returning all RRSIGs.\n"); + const knot_rrset_t **rrsets = knot_node_rrsets(node); + if (rrsets == NULL) { + break; + } + int i = 0; + int ret = 0; + const knot_rrset_t *rrset; + while (i < knot_node_rrset_count(node)) { + assert(rrsets[i] != NULL); + rrset = knot_rrset_rrsigs(rrsets[i]); + + if (rrset == NULL) { + ++i; + continue; + } + + ns_check_wildcard(name, resp, &rrset); + ret = knot_response_add_rrset_answer(resp, rrset, 1, + 0, 0); + + if (ret < 0) { + break; + } + + added += 1; + ++i; + } + free(rrsets); + break; + } + default: { + int ret = 0; + const knot_rrset_t *rrset = knot_node_rrset(node, type); + const knot_rrset_t *rrset2 = rrset; + if (rrset != NULL) { + dbg_ns("Found RRSet of type %s\n", + knot_rrtype_to_string(type)); + ns_check_wildcard(name, resp, &rrset2); + ret = knot_response_add_rrset_answer(resp, rrset2, 1, + 0, 0); + if (ret >= 0 && (added += 1) + && (ret = ns_add_rrsigs(rrset, resp, name, + knot_response_add_rrset_answer, 1)) > 0) { + added += 1; + } + } + } + } + + knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); + return added; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds RRSets to Additional section of the response. + * + * This function uses knot_rdata_get_name() to get the domain name from the + * RDATA of the RRSet according to its type. It also does not search for the + * retrieved domain name, but just uses its node field. Thus to work correctly, + * the zone where the RRSet is from should be adjusted using + * knot_zone_adjust_dnames(). + * + * A and AAAA RRSets (and possible CNAMEs) for the found domain names are added. + * + * \warning Use this function only with types containing some domain name, + * otherwise it will crash (or behave strangely). + * + * \param resp Response where to add the Additional data. + * \param rrset RRSet to get the Additional data for. + */ +static void ns_put_additional_for_rrset(knot_packet_t *resp, + const knot_rrset_t *rrset) +{ + const knot_node_t *node = NULL; + const knot_rdata_t *rdata = NULL; + const knot_dname_t *dname = NULL; + + // for all RRs in the RRset + rdata = knot_rrset_rdata(rrset); + while (rdata != NULL) { + dbg_ns("Getting name from RDATA, type %s..\n", + knot_rrtype_to_string(knot_rrset_type(rrset))); + dname = knot_rdata_get_name(rdata, + knot_rrset_type(rrset)); + assert(dname != NULL); + node = knot_dname_node(dname, 1); +// // check if the node is not old and if yes, take the new one +// if (knot_node_is_old(node)) { +// node = knot_node_new_node(node); +// } + + dbg_ns_detail("Node saved in RDATA dname: %p\n", node); + + if (node != NULL && node->owner != dname) { + // the stored node should be the closest encloser + assert(knot_dname_is_subdomain(dname, node->owner)); + // try the wildcard child, if any + node = knot_node_wildcard_child(node, 1); +// // this should not be old node!! +// assert(!knot_node_is_old(node)); + } + + const knot_rrset_t *rrset_add; + + if (node != NULL) { +dbg_ns_exec( + char *name = knot_dname_to_str(node->owner); + dbg_ns("Putting additional from node %s\n", name); + free(name); +); + dbg_ns("Checking CNAMEs...\n"); + if (knot_node_rrset(node, KNOT_RRTYPE_CNAME) + != NULL) { + dbg_ns("Found CNAME in node, following...\n"); + const knot_dname_t *dname + = knot_node_owner(node); + ns_follow_cname(&node, &dname, resp, + knot_response_add_rrset_additional, 0); + } + + // A RRSet + dbg_ns("A RRSets...\n"); + rrset_add = knot_node_rrset(node, KNOT_RRTYPE_A); + if (rrset_add != NULL) { + dbg_ns("Found A RRsets.\n"); + const knot_rrset_t *rrset_add2 = rrset_add; + ns_check_wildcard(dname, resp, &rrset_add2); + knot_response_add_rrset_additional( + resp, rrset_add2, 0, 1, 0); + ns_add_rrsigs(rrset_add, resp, dname, + knot_response_add_rrset_additional, 0); + } + + // AAAA RRSet + dbg_ns("AAAA RRSets...\n"); + rrset_add = knot_node_rrset(node, KNOT_RRTYPE_AAAA); + if (rrset_add != NULL) { + dbg_ns("Found AAAA RRsets.\n"); + const knot_rrset_t *rrset_add2 = rrset_add; + ns_check_wildcard(dname, resp, &rrset_add2); + knot_response_add_rrset_additional( + resp, rrset_add2, 0, 1, 0); + ns_add_rrsigs(rrset_add, resp, dname, + knot_response_add_rrset_additional, 0); + } + } + + assert(rrset != NULL); + assert(rdata != NULL); + rdata = knot_rrset_rdata_next(rrset, rdata); + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Checks whether the given type requires additional processing. + * + * Only MX, NS and SRV types require additional processing. + * + * \param qtype Type to check. + * + * \retval <> 0 if additional processing is needed for \a qtype. + * \retval 0 otherwise. + */ +static int ns_additional_needed(uint16_t qtype) +{ + return (qtype == KNOT_RRTYPE_MX || + qtype == KNOT_RRTYPE_NS || + qtype == KNOT_RRTYPE_SRV); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds whatever Additional RRSets are required for the response. + * + * For each RRSet in Answer and Authority sections this function checks if + * additional processing is needed and if yes, it puts any Additional RRSets + * available to the Additional section of the response. + * + * \param resp Response to process. + */ +static void ns_put_additional(knot_packet_t *resp) +{ + dbg_ns("ADDITIONAL SECTION PROCESSING\n"); + + const knot_rrset_t *rrset = NULL; + + for (int i = 0; i < knot_packet_answer_rrset_count(resp); ++i) { + rrset = knot_packet_answer_rrset(resp, i); + assert(rrset != NULL); + if (ns_additional_needed(knot_rrset_type(rrset))) { + ns_put_additional_for_rrset(resp, rrset); + } + } + + for (int i = 0; i < knot_packet_authority_rrset_count(resp); ++i) { + rrset = knot_packet_authority_rrset(resp, i); + if (ns_additional_needed(knot_rrset_type(rrset))) { + ns_put_additional_for_rrset(resp, rrset); + } + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts authority NS RRSet to the Auhority section of the response. + * + * \param zone Zone to take the authority NS RRSet from. + * \param resp Response where to add the RRSet. + */ +static void ns_put_authority_ns(const knot_zone_contents_t *zone, + knot_packet_t *resp) +{ + const knot_rrset_t *ns_rrset = knot_node_rrset( + knot_zone_contents_apex(zone), KNOT_RRTYPE_NS); + + if (ns_rrset != NULL) { + knot_response_add_rrset_authority(resp, ns_rrset, 0, 1, 0); + ns_add_rrsigs(ns_rrset, resp, knot_node_owner( + knot_zone_contents_apex(zone)), + knot_response_add_rrset_authority, 1); + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts SOA RRSet to the Auhority section of the response. + * + * \param zone Zone to take the SOA RRSet from. + * \param resp Response where to add the RRSet. + */ +static void ns_put_authority_soa(const knot_zone_contents_t *zone, + knot_packet_t *resp) +{ + const knot_rrset_t *soa_rrset = knot_node_rrset( + knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA); + assert(soa_rrset != NULL); + + knot_response_add_rrset_authority(resp, soa_rrset, 0, 0, 0); + ns_add_rrsigs(soa_rrset, resp, + knot_node_owner(knot_zone_contents_apex(zone)), + knot_response_add_rrset_authority, 1); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a 'next closer name' to the given domain name. + * + * For definition of 'next closer name', see RFC5155, Page 6. + * + * \param closest_encloser Closest encloser of \a name. + * \param name Domain name to create the 'next closer' name to. + * + * \return 'Next closer name' to the given domain name or NULL if an error + * occured. + */ +static knot_dname_t *ns_next_closer(const knot_dname_t *closest_encloser, + const knot_dname_t *name) +{ + int ce_labels = knot_dname_label_count(closest_encloser); + int qname_labels = knot_dname_label_count(name); + + assert(ce_labels < qname_labels); + + // the common labels should match + assert(knot_dname_matched_labels(closest_encloser, name) + == ce_labels); + + // chop some labels from the qname + knot_dname_t *next_closer = knot_dname_deep_copy(name); + if (next_closer == NULL) { + return NULL; + } + + for (int i = 0; i < (qname_labels - ce_labels - 1); ++i) { + knot_dname_left_chop_no_copy(next_closer); + } + + return next_closer; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds NSEC3 RRSet (together with corresponding RRSIGs) from the given + * node into the response. + * + * \param node Node to get the NSEC3 RRSet from. + * \param resp Response where to add the RRSets. + */ +static void ns_put_nsec3_from_node(const knot_node_t *node, + knot_packet_t *resp) +{ + assert(DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp))); + + const knot_rrset_t *rrset = knot_node_rrset(node, + KNOT_RRTYPE_NSEC3); + assert(rrset != NULL); + + int res = knot_response_add_rrset_authority(resp, rrset, 1, 1, 0); + // add RRSIG for the RRSet + if (res == 0 && (rrset = knot_rrset_rrsigs(rrset)) != NULL) { + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Finds and adds NSEC3 covering the given domain name (and their + * associated RRSIGs) to the response. + * + * \param zone Zone used for answering. + * \param name Domain name to cover. + * \param resp Response where to add the RRSets. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL if a runtime collision occured. The server should + * respond with SERVFAIL in such case. + */ +static int ns_put_covering_nsec3(const knot_zone_contents_t *zone, + const knot_dname_t *name, + knot_packet_t *resp) +{ + const knot_node_t *prev, *node; + /*! \todo Check version. */ + int match = knot_zone_contents_find_nsec3_for_name(zone, name, + &node, &prev, 1); + assert(match >= 0); + node = knot_node_current(node); + prev = knot_node_current(prev); + + if (match == KNOT_ZONE_NAME_FOUND){ + // run-time collision => SERVFAIL + return KNOT_EOK; + } + +// // check if the prev node is not old and if yes, take the new one +// if (knot_node_is_old(prev)) { +// prev = knot_node_new_node(prev); +// assert(prev != NULL); +// } + +dbg_ns_exec( + char *name = knot_dname_to_str(prev->owner); + dbg_ns("Covering NSEC3 node: %s\n", name); + free(name); +); + + ns_put_nsec3_from_node(prev, resp); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds NSEC3s comprising the 'closest encloser proof' for the given + * (non-existent) domain name (and their associated RRSIGs) to the + * response. + * + * For definition of 'closest encloser proof', see RFC5155, section 7.2.1, + * Page 18. + * + * \note This function does not check if DNSSEC is enabled, nor if it is + * requested by the query. + * + * \param zone Zone used for answering. + * \param closest_encloser Closest encloser of \a qname in the zone. + * \param qname Searched (non-existent) name. + * \param resp Response where to add the NSEC3s. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec3_closest_encloser_proof( + const knot_zone_contents_t *zone, + const knot_node_t **closest_encloser, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + assert(zone != NULL); + assert(closest_encloser != NULL); + assert(*closest_encloser != NULL); + assert(qname != NULL); + assert(resp != NULL); + + if (knot_zone_contents_nsec3params(zone) == NULL) { +dbg_ns_exec( + char *name = knot_dname_to_str(knot_node_owner( + knot_zone_contents_apex(zone))); + dbg_ns("No NSEC3PARAM found in zone %s.\n", name); + free(name); +); + return KNOT_EOK; + } + +dbg_ns_exec( + char *name = knot_dname_to_str(knot_node_owner(*closest_encloser)); + dbg_ns("Closest encloser: %s\n", name); + free(name); +); + + /* + * 1) NSEC3 that matches closest provable encloser. + */ + const knot_node_t *nsec3_node = NULL; + const knot_dname_t *next_closer = NULL; + while ((nsec3_node = knot_node_nsec3_node((*closest_encloser), 1)) + == NULL) { + next_closer = knot_node_owner((*closest_encloser)); + *closest_encloser = knot_node_parent(*closest_encloser, 1); + if (*closest_encloser == NULL) { + // there are no NSEC3s to add + return KNOT_EOK; + } + } + + assert(nsec3_node != NULL); + +dbg_ns_exec( + char *name = knot_dname_to_str(nsec3_node->owner); + dbg_ns("NSEC3 node: %s\n", name); + free(name); + name = knot_dname_to_str((*closest_encloser)->owner); + dbg_ns("Closest provable encloser: %s\n", name); + free(name); + if (next_closer != NULL) { + name = knot_dname_to_str(next_closer); + dbg_ns("Next closer name: %s\n", name); + free(name); + } else { + dbg_ns("Next closer name: none\n"); + } +); + + ns_put_nsec3_from_node(nsec3_node, resp); + + /* + * 2) NSEC3 that covers the "next closer" name. + */ + int ret = 0; + if (next_closer == NULL) { + // create the "next closer" name by appending from qname + next_closer = ns_next_closer( + knot_node_owner(*closest_encloser), qname); + + if (next_closer == NULL) { + return NS_ERR_SERVFAIL; + } +dbg_ns_exec( + char *name = knot_dname_to_str(next_closer); + dbg_ns("Next closer name: %s\n", name); + free(name); +); + ret = ns_put_covering_nsec3(zone, next_closer, resp); + + // the cast is ugly, but no better way around it + knot_dname_release((knot_dname_t *)next_closer); + } else { + ret = ns_put_covering_nsec3(zone, next_closer, resp); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a name of a wildcard child of \a name. + * + * \param name Domain name to get the wildcard child name of. + * + * \return Wildcard child name or NULL if an error occured. + */ +static knot_dname_t *ns_wildcard_child_name(const knot_dname_t *name) +{ + assert(name != NULL); + + knot_dname_t *wildcard = knot_dname_new_from_str("*", 1, NULL); + if (wildcard == NULL) { + return NULL; + } + + if (knot_dname_cat(wildcard, name) == NULL) { + /* Directly discard dname. */ + knot_dname_free(&wildcard); + return NULL; + } + +dbg_ns_exec( + char *name = knot_dname_to_str(wildcard); + dbg_ns("Wildcard: %s\n", name); + free(name); +); + return wildcard; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSEC3s covering the non-existent wildcard child of a node + * (and their associated RRSIGs) into the response. + * + * \note This function does not check if DNSSEC is enabled, nor if it is + * requested by the query. + * + * \param zone Zone used for answering. + * \param node Node whose non-existent wildcard child should be covered. + * \param resp Response where to add the NSEC3s. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec3_no_wildcard_child(const knot_zone_contents_t *zone, + const knot_node_t *node, + knot_packet_t *resp) +{ + assert(node != NULL); + assert(resp != NULL); + assert(node->owner != NULL); + + int ret = 0; + knot_dname_t *wildcard = ns_wildcard_child_name(node->owner); + if (wildcard == NULL) { + ret = NS_ERR_SERVFAIL; + } else { + ret = ns_put_covering_nsec3(zone, wildcard, resp); + + /* Directly discard wildcard. */ + knot_dname_free(&wildcard); + } + + return ret; +} +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSECs or NSEC3s for NODATA error (and their associated RRSIGs) + * to the response. + * + * \note This function first checks if DNSSEC is enabled and requested by the + * query. + * \note Note that for each zone there are either NSEC or NSEC3 records used. + * + * \param node Node which generated the NODATA response (i.e. not containing + * RRSets of the requested type). + * \param resp Response where to add the NSECs or NSEC3s. + */ +static void ns_put_nsec_nsec3_nodata(const knot_node_t *node, + knot_packet_t *resp) +{ + if (!DNSSEC_ENABLED || + !knot_query_dnssec_requested(knot_packet_query(resp))) { + return; + } + + const knot_node_t *nsec3_node = knot_node_nsec3_node(node, 1); + const knot_rrset_t *rrset = NULL; + if ((rrset = knot_node_rrset(node, KNOT_RRTYPE_NSEC)) != NULL + || (nsec3_node != NULL && (rrset = + knot_node_rrset(nsec3_node, KNOT_RRTYPE_NSEC3)) != NULL)) { + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + // add RRSIG for the RRSet + if ((rrset = knot_rrset_rrsigs(rrset)) != NULL) { + knot_response_add_rrset_authority(resp, rrset, 1, + 0, 0); + } + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSECs for NXDOMAIN error to the response. + * + * \note This function does not check if DNSSEC is enabled, nor if it is + * requested by the query. + * + * \param qname QNAME which generated the NXDOMAIN error (i.e. not found in the + * zone). + * \param zone Zone used for answering. + * \param previous Previous node to \a qname in the zone. May also be NULL. In + * such case the function finds the previous node in the zone. + * \param closest_encloser Closest encloser of \a qname. Must not be NULL. + * \param resp Response where to put the NSECs. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec_nxdomain(const knot_dname_t *qname, + const knot_zone_contents_t *zone, + const knot_node_t *previous, + const knot_node_t *closest_encloser, + knot_packet_t *resp) +{ + const knot_rrset_t *rrset = NULL; + + // check if we have previous; if not, find one using the tree + if (previous == NULL) { + /*! \todo Check version. */ + previous = knot_zone_contents_find_previous(zone, qname); + + while (!knot_node_is_auth(previous)) { + previous = knot_node_previous(previous, 1); + } + + previous = knot_node_current(previous); + assert(previous != NULL); + } + + char *name = knot_dname_to_str(previous->owner); + dbg_ns("Previous node: %s\n", name); + free(name); + + // 1) NSEC proving that there is no node with the searched name + rrset = knot_node_rrset(previous, KNOT_RRTYPE_NSEC); + if (rrset == NULL) { + // no NSEC records + //return NS_ERR_SERVFAIL; + return KNOT_EOK; + + } + + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + rrset = knot_rrset_rrsigs(rrset); + assert(rrset != NULL); + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + + // 2) NSEC proving that there is no wildcard covering the name + // this is only different from 1) if the wildcard would be + // before 'previous' in canonical order, i.e. we can + // search for previous until we find name lesser than wildcard + assert(closest_encloser != NULL); + + knot_dname_t *wildcard = + ns_wildcard_child_name(closest_encloser->owner); + if (wildcard == NULL) { + return NS_ERR_SERVFAIL; + } + + const knot_node_t *prev_new = previous; + + while (knot_dname_compare(knot_node_owner(prev_new), + wildcard) > 0) { + dbg_ns("Previous node: %s\n", + knot_dname_to_str(knot_node_owner(prev_new))); + assert(prev_new != knot_zone_contents_apex(zone)); + prev_new = knot_node_previous(prev_new, 1); + } + assert(knot_dname_compare(knot_node_owner(prev_new), + wildcard) < 0); + + dbg_ns("Previous node: %s\n", + knot_dname_to_str(knot_node_owner(prev_new))); + + /* Directly discard dname. */ + knot_dname_free(&wildcard); + + if (prev_new != previous) { + rrset = knot_node_rrset(prev_new, KNOT_RRTYPE_NSEC); + assert(rrset != NULL); + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + rrset = knot_rrset_rrsigs(rrset); + assert(rrset != NULL); + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSEC3s for NXDOMAIN error to the response. + * + * \note This function does not check if DNSSEC is enabled, nor if it is + * requested by the query. + * + * \param zone Zone used for answering. + * \param closest_encloser Closest encloser of \a qname. + * \param qname Domain name which generated the NXDOMAIN error (i.e. not found + * in the zone. + * \param resp Response where to put the NSEC3s. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec3_nxdomain(const knot_zone_contents_t *zone, + const knot_node_t *closest_encloser, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + // 1) Closest encloser proof + dbg_ns("Putting closest encloser proof.\n"); + int ret = ns_put_nsec3_closest_encloser_proof(zone, &closest_encloser, + qname, resp); + // 2) NSEC3 covering non-existent wildcard + if (ret == KNOT_EOK && closest_encloser != NULL) { + dbg_ns("Putting NSEC3 for no wildcard child of closest " + "encloser.\n"); + ret = ns_put_nsec3_no_wildcard_child(zone, closest_encloser, + resp); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSECs or NSEC3s for the NXDOMAIN error to the response. + * + * \note This function first checks if DNSSEC is enabled and requested by the + * query. + * \note Note that for each zone there are either NSEC or NSEC3 records used. + * + * \param zone Zone used for answering. + * \param previous Previous node to \a qname in the zone. May also be NULL. In + * such case the function finds the previous node in the zone. + * \param closest_encloser Closest encloser of \a qname. Must not be NULL. + * \param qname QNAME which generated the NXDOMAIN error (i.e. not found in the + * zone). + * \param resp Response where to put the NSECs. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec_nsec3_nxdomain(const knot_zone_contents_t *zone, + const knot_node_t *previous, + const knot_node_t *closest_encloser, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + int ret = 0; + if (DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp))) { + if (knot_zone_contents_nsec3_enabled(zone)) { + ret = ns_put_nsec3_nxdomain(zone, closest_encloser, + qname, resp); + } else { + ret = ns_put_nsec_nxdomain(qname, zone, previous, + closest_encloser, resp); + } + } + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSEC3s for wildcard answer into the response. + * + * \note This function does not check if DNSSEC is enabled, nor if it is + * requested by the query. + * + * \param zone Zone used for answering. + * \param closest_encloser Closest encloser of \a qname in the zone. In this + * case it is the parent of the source of synthesis. + * \param qname Domain name covered by the wildcard used for answering the + * query. + * \param resp Response to put the NSEC3s into. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec3_wildcard(const knot_zone_contents_t *zone, + const knot_node_t *closest_encloser, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + assert(closest_encloser != NULL); + assert(qname != NULL); + assert(resp != NULL); + assert(DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp))); + + if (!knot_zone_contents_nsec3_enabled(zone)) { + return KNOT_EOK; + } + + /* + * NSEC3 that covers the "next closer" name. + */ + // create the "next closer" name by appending from qname + dbg_ns("Finding next closer name for wildcard NSEC3.\n"); + knot_dname_t *next_closer = + ns_next_closer(closest_encloser->owner, qname); + + if (next_closer == NULL) { + return NS_ERR_SERVFAIL; + } +dbg_ns_exec( + char *name = knot_dname_to_str(next_closer); + dbg_ns("Next closer name: %s\n", name); + free(name); +); + int ret = ns_put_covering_nsec3(zone, next_closer, resp); + + + /* Duplicate from ns_next_close(), safe to discard. */ + knot_dname_release(next_closer); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSECs for wildcard answer into the response. + * + * \note This function does not check if DNSSEC is enabled, nor if it is + * requested by the query. + * + * \param zone Zone used for answering. + * \param qname Domain name covered by the wildcard used for answering the + * query. + * \param previous Previous node of \a qname in canonical order. + * \param resp Response to put the NSEC3s into. + */ +static void ns_put_nsec_wildcard(const knot_zone_contents_t *zone, + const knot_dname_t *qname, + const knot_node_t *previous, + knot_packet_t *resp) +{ + assert(DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp))); + + // check if we have previous; if not, find one using the tree + if (previous == NULL) { + previous = knot_zone_contents_find_previous(zone, qname); + + while (!knot_node_is_auth(previous)) { + previous = knot_node_previous(previous, 1); + } + + previous = knot_node_current(previous); + assert(previous != NULL); + } + + const knot_rrset_t *rrset = + knot_node_rrset(previous, KNOT_RRTYPE_NSEC); + if (rrset != NULL) { + // NSEC proving that there is no node with the searched name + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + rrset = knot_rrset_rrsigs(rrset); + assert(rrset != NULL); + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSECs or NSEC3s for wildcard NODATA answer into the response. + * + * \note This function first checks if DNSSEC is enabled and requested by the + * query. + * + * \param node Node used for answering. + * \param closest_encloser Closest encloser of \a qname in the zone. + * \param previous Previous node of \a qname in canonical order. + * \param zone Zone used for answering. + * \param qname Actual searched domain name. + * \param resp Response where to put the NSECs and NSEC3s. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec_nsec3_wildcard_nodata(const knot_node_t *node, + const knot_node_t *closest_encloser, + const knot_node_t *previous, + const knot_zone_contents_t *zone, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + int ret = KNOT_EOK; + if (DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp))) { + if (knot_zone_contents_nsec3_enabled(zone)) { + ret = ns_put_nsec3_closest_encloser_proof(zone, + &closest_encloser, + qname, resp); + + const knot_node_t *nsec3_node; + if (ret == KNOT_EOK + && (nsec3_node = knot_node_nsec3_node(node, 1)) + != NULL) { + ns_put_nsec3_from_node(nsec3_node, resp); + } + } else { + ns_put_nsec_wildcard(zone, qname, previous, resp); + } + } + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSECs or NSEC3s for wildcard answer into the response. + * + * \note This function first checks if DNSSEC is enabled and requested by the + * query and if the node's owner is a wildcard. + * + * \param node Node used for answering. + * \param closest_encloser Closest encloser of \a qname in the zone. + * \param previous Previous node of \a qname in canonical order. + * \param zone Zone used for answering. + * \param qname Actual searched domain name. + * \param resp Response where to put the NSECs and NSEC3s. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec_nsec3_wildcard_answer(const knot_node_t *node, + const knot_node_t *closest_encloser, + const knot_node_t *previous, + const knot_zone_contents_t *zone, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + int r = KNOT_EOK; + if (DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp)) + && knot_dname_is_wildcard(knot_node_owner(node))) { + if (knot_zone_contents_nsec3_enabled(zone)) { + r = ns_put_nsec3_wildcard(zone, closest_encloser, qname, + resp); + } else { + ns_put_nsec_wildcard(zone, qname, previous, resp); + } + } + return r; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a referral response. + * + * This function puts the delegation NS RRSet to the Authority section of the + * response, possibly adds DS and their associated RRSIGs (if DNSSEC is enabled + * and requested by the query) and adds any available additional data (A and + * AAAA RRSets for the names in the NS RRs) with their associated RRSIGs + * to the Additional section. + * + * \param node Delegation point node. + * \param zone Parent zone (the one from which the response is generated). + * \param qname Searched name (which caused the referral). + * \param resp Response. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static inline int ns_referral(const knot_node_t *node, + const knot_zone_contents_t *zone, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + dbg_ns("Referral response.\n"); + + while (!knot_node_is_deleg_point(node)) { + assert(knot_node_parent(node, 1) != NULL); + node = knot_node_parent(node, 1); + } + + const knot_rrset_t *rrset = knot_node_rrset(node, KNOT_RRTYPE_NS); + assert(rrset != NULL); + + // TODO: wildcards?? + //ns_check_wildcard(name, resp, &rrset); + + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + ns_add_rrsigs(rrset, resp, node->owner, + knot_response_add_rrset_authority, 1); + + int ret = KNOT_EOK; + // add DS records + dbg_ns("DNSSEC requested: %d\n", + knot_query_dnssec_requested(knot_packet_query(resp))); + dbg_ns("DS records: %p\n", knot_node_rrset(node, KNOT_RRTYPE_DS)); + if (DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp))) { + rrset = knot_node_rrset(node, KNOT_RRTYPE_DS); + if (rrset != NULL) { + knot_response_add_rrset_authority(resp, rrset, 1, 0, + 0); + ns_add_rrsigs(rrset, resp, node->owner, + knot_response_add_rrset_authority, 1); + } else { + // no DS, add NSEC3 or NSEC + // if NSEC3 enabled, search for NSEC3 + if (knot_zone_contents_nsec3_enabled(zone)) { + const knot_node_t *nsec3_node = + knot_node_nsec3_node(node, 1); + dbg_ns("There is no DS, putting NSEC3s...\n"); + if (nsec3_node != NULL) { + dbg_ns("Putting NSEC3s from the node.\n"); + ns_put_nsec3_from_node(nsec3_node, resp); + } else { + dbg_ns("Putting Opt-Out NSEC3s.\n"); + // no NSEC3 (probably Opt-Out) + // TODO: check if the zone is Opt-Out + ret = ns_put_nsec3_closest_encloser_proof(zone, + &node, qname, resp); + } + } else { + const knot_rrset_t *nsec = knot_node_rrset( + node, KNOT_RRTYPE_NSEC); + if (nsec) { + /*! \todo Check return value? */ + knot_response_add_rrset_authority( + resp, nsec, 1, 1, 0); + if ((nsec = knot_rrset_rrsigs(nsec)) != NULL) { + knot_response_add_rrset_authority(resp, nsec, 1, + 1, 0); + } + } + } + } + } + + if (ret == KNOT_EOK) { + ns_put_additional(resp); + knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); + } + return ret; +} + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Tries to answer the query from the given node. + * + * Tries to put RRSets of requested type (\a qtype) to the Answer section of the + * response. If successful, it also adds authority NS RRSet to the Authority + * section and it may add NSEC or NSEC3s in case of a wildcard answer (\a node + * is a wildcard node). If not successful (there are no such RRSets), it adds + * the SOA record to the Authority section and may add NSEC or NSEC3s according + * to the type of the response (NXDOMAIN if \a node is an empty non-terminal, + * NODATA if it is a regular node). It also adds any additional data that may + * be required. + * + * \param node Node to answer from. + * \param closest_encloser Closest encloser of \a qname in the zone. + * \param previous Previous domain name of \a qname in canonical order. + * \param zone Zone used for answering. + * \param qname Searched domain name. + * \param qtype Searched RR type. + * \param resp Response. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_answer_from_node(const knot_node_t *node, + const knot_node_t *closest_encloser, + const knot_node_t *previous, + const knot_zone_contents_t *zone, + const knot_dname_t *qname, uint16_t qtype, + knot_packet_t *resp) +{ + dbg_ns("Putting answers from found node to the response...\n"); + int answers = ns_put_answer(node, qname, qtype, resp); + + int ret = KNOT_EOK; + if (answers == 0) { // if NODATA response, put SOA + if (knot_node_rrset_count(node) == 0 + && !knot_zone_contents_nsec3_enabled(zone)) { + // node is an empty non-terminal => NSEC for NXDOMAIN + //assert(knot_node_rrset_count(closest_encloser) > 0); + dbg_ns("Adding NSEC/NSEC3 for NXDOMAIN.\n"); + ret = ns_put_nsec_nsec3_nxdomain(zone, + knot_node_previous(node, 1), closest_encloser, + qname, resp); + } else { + dbg_ns("Adding NSEC/NSEC3 for NODATA.\n"); + ns_put_nsec_nsec3_nodata(node, resp); + if (knot_dname_is_wildcard(node->owner)) { + dbg_ns("Putting NSEC/NSEC3 for wildcard" + " NODATA\n"); + ret = ns_put_nsec_nsec3_wildcard_nodata(node, + closest_encloser, previous, zone, qname, + resp); + } + } + ns_put_authority_soa(zone, resp); + } else { // else put authority NS + // if wildcard answer, add NSEC / NSEC3 + dbg_ns("Adding NSEC/NSEC3 for wildcard answer.\n"); + ret = ns_put_nsec_nsec3_wildcard_answer(node, closest_encloser, + previous, zone, qname, resp); + ns_put_authority_ns(zone, resp); + } + + if (ret == KNOT_EOK) { + ns_put_additional(resp); + } + return ret; +} + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Synthetizes a CNAME RR from a DNAME. + * + * \param dname_rrset DNAME RRSet to synthetize from (only the first RR is + * used). + * \param qname Name to be used as the owner name of the synthetized CNAME. + * + * \return Synthetized CNAME RRset (this is a newly created RRSet, remember to + * free it). + */ +static knot_rrset_t *ns_cname_from_dname(const knot_rrset_t *dname_rrset, + const knot_dname_t *qname) +{ + dbg_ns("Synthetizing CNAME from DNAME...\n"); + + // create new CNAME RRSet + + knot_dname_t *owner = knot_dname_deep_copy(qname); + if (owner == NULL) { + return NULL; + } + + knot_rrset_t *cname_rrset = knot_rrset_new( + owner, KNOT_RRTYPE_CNAME, KNOT_CLASS_IN, SYNTH_CNAME_TTL); + + /* Release owner, as it's retained in rrset. */ + knot_dname_release(owner); + + if (cname_rrset == NULL) { + return NULL; + } + + // replace last labels of qname with DNAME + knot_dname_t *cname = knot_dname_replace_suffix(qname, + knot_dname_size(knot_rrset_owner(dname_rrset)), + knot_rdata_get_item(knot_rrset_rdata(dname_rrset), 0)->dname); +dbg_ns_exec( + char *name = knot_dname_to_str(cname); + dbg_ns("CNAME canonical name: %s.\n", name); + free(name); +); + knot_rdata_t *cname_rdata = knot_rdata_new(); + knot_rdata_item_t cname_rdata_item; + cname_rdata_item.dname = cname; + knot_rdata_set_items(cname_rdata, &cname_rdata_item, 1); + + knot_rrset_add_rdata(cname_rrset, cname_rdata); + + return cname_rrset; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Checks if the name created by replacing the owner of \a dname_rrset + * in the \a qname by the DNAME's target would be longer than allowed. + * + * \param dname_rrset DNAME RRSet to be used for the check. + * \param qname Name whose part is to be replaced. + * + * \retval <>0 if the created domain name would be too long. + * \retval 0 otherwise. + */ +static int ns_dname_is_too_long(const knot_rrset_t *dname_rrset, + const knot_dname_t *qname) +{ + // TODO: add function for getting DNAME target + if (knot_dname_label_count(qname) + - knot_dname_label_count(knot_rrset_owner(dname_rrset)) + + knot_dname_label_count(knot_rdata_get_item( + knot_rrset_rdata(dname_rrset), 0)->dname) + > KNOT_MAX_DNAME_LENGTH) { + return 1; + } else { + return 0; + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief DNAME processing. + * + * This function adds the DNAME RRSet (and possibly its associated RRSIGs to the + * Answer section of the response, synthetizes CNAME record from the DNAME and + * adds it there too. It also stores the synthetized CNAME in the temporary + * RRSets of the response. + * + * \param dname_rrset DNAME RRSet to use. + * \param qname Searched name. + * \param resp Response. + */ +static void ns_process_dname(const knot_rrset_t *dname_rrset, + const knot_dname_t *qname, + knot_packet_t *resp) +{ +dbg_ns_exec( + char *name = knot_dname_to_str(knot_rrset_owner(dname_rrset)); + dbg_ns("Processing DNAME for owner %s...\n", name); + free(name); +); + // TODO: check the number of RRs in the RRSet?? + + // put the DNAME RRSet into the answer + knot_response_add_rrset_answer(resp, dname_rrset, 1, 0, 0); + ns_add_rrsigs(dname_rrset, resp, qname, + knot_response_add_rrset_answer, 1); + + if (ns_dname_is_too_long(dname_rrset, qname)) { + knot_response_set_rcode(resp, KNOT_RCODE_YXDOMAIN); + return; + } + + // synthetize CNAME (no way to tell that client supports DNAME) + knot_rrset_t *synth_cname = ns_cname_from_dname(dname_rrset, qname); + // add the synthetized RRSet to the Answer + knot_response_add_rrset_answer(resp, synth_cname, 1, 0, 0); + + // no RRSIGs for this RRSet + + // add the synthetized RRSet into list of temporary RRSets of response + knot_packet_add_tmp_rrset(resp, synth_cname); + + // do not search for the name in new zone (out-of-bailiwick) +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds DNSKEY RRSet from the apex of a zone to the response. + * + * \param apex Zone apex node. + * \param resp Response. + */ +static void ns_add_dnskey(const knot_node_t *apex, knot_packet_t *resp) +{ + const knot_rrset_t *rrset = + knot_node_rrset(apex, KNOT_RRTYPE_DNSKEY); + if (rrset != NULL) { + knot_response_add_rrset_additional(resp, rrset, 0, 0, 0); + ns_add_rrsigs(rrset, resp, apex->owner, + knot_response_add_rrset_additional, 0); + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Answers the query from the given zone. + * + * This function performs the actual answering logic. + * + * \param zone Zone to use for answering. + * \param qname QNAME from the query. + * \param qtype QTYPE from the query. + * \param resp Response to fill in. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + * + * \todo Describe the answering logic in detail. + */ +static int ns_answer_from_zone(const knot_zone_contents_t *zone, + const knot_dname_t *qname, uint16_t qtype, + knot_packet_t *resp) +{ + const knot_node_t *node = NULL, *closest_encloser = NULL, + *previous = NULL; + int cname = 0, auth_soa = 0, ret = 0, find_ret = 0; + +search: +#ifdef USE_HASH_TABLE + /*! \todo Check version. */ + find_ret = knot_zone_contents_find_dname_hash(zone, qname, &node, + &closest_encloser); +// node = knot_node_current(node); +// closest_encloser = knot_node_current(closest_encloser); +#else + /*! \todo Check version. */ + find_ret = knot_zone_contents_find_dname(zone, qname, &node, + &closest_encloser, &previous); + node = knot_node_current(node); + closest_encloser = knot_node_current(closest_encloser); + previous = knot_node_current(previous); +#endif + if (find_ret == KNOT_EBADARG) { + return NS_ERR_SERVFAIL; + } + +dbg_ns_exec( + char *name; + if (node) { + name = knot_dname_to_str(node->owner); + dbg_ns("zone_find_dname() returned node %s ", name); + free(name); + } else { + dbg_ns("zone_find_dname() returned no node,"); + } + + if (closest_encloser != NULL) { + name = knot_dname_to_str(closest_encloser->owner); + dbg_ns(" closest encloser %s.\n", name); + free(name); + } else { + dbg_ns(" closest encloser (nil).\n"); + } + if (previous != NULL) { + name = knot_dname_to_str(previous->owner); + dbg_ns(" and previous node: %s.\n", name); + free(name); + } else { + dbg_ns(" and previous node: (nil).\n"); + } +); + if (find_ret == KNOT_EBADZONE) { + // possible only if we followed cname + assert(cname != 0); + knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); + auth_soa = 1; + knot_response_set_aa(resp); + goto finalize; + } + +have_node: + dbg_ns("Closest encloser is deleg. point? %s\n", + (knot_node_is_deleg_point(closest_encloser)) ? "yes" : "no"); + + dbg_ns("Closest encloser is non authoritative? %s\n", + (knot_node_is_non_auth(closest_encloser)) ? "yes" : "no"); + + if (knot_node_is_deleg_point(closest_encloser) + || knot_node_is_non_auth(closest_encloser)) { + ret = ns_referral(closest_encloser, zone, qname, resp); + goto finalize; + } + + if (find_ret == KNOT_ZONE_NAME_NOT_FOUND) { + // DNAME? + const knot_rrset_t *dname_rrset = knot_node_rrset( + closest_encloser, KNOT_RRTYPE_DNAME); + if (dname_rrset != NULL) { + ns_process_dname(dname_rrset, qname, resp); + auth_soa = 1; + knot_response_set_aa(resp); + goto finalize; + } + // else check for a wildcard child + const knot_node_t *wildcard_node = + knot_node_wildcard_child(closest_encloser, 1); + + if (wildcard_node == NULL) { + dbg_ns("No wildcard node. (cname: %d)\n", + cname); + auth_soa = 1; + if (cname == 0) { + dbg_ns("Setting NXDOMAIN RCODE.\n"); + // return NXDOMAIN + knot_response_set_rcode(resp, + KNOT_RCODE_NXDOMAIN); + if (ns_put_nsec_nsec3_nxdomain(zone, previous, + closest_encloser, qname, resp) != 0) { + return NS_ERR_SERVFAIL; + } + } else { + knot_response_set_rcode(resp, + KNOT_RCODE_NOERROR); + } + knot_response_set_aa(resp); + goto finalize; + } + // else set the node from which to take the answers to wild.node + node = wildcard_node; + } + + // now we have the node for answering + if (knot_node_is_deleg_point(node) || knot_node_is_non_auth(node)) { + ret = ns_referral(node, zone, qname, resp); + goto finalize; + } + + if (knot_node_rrset(node, KNOT_RRTYPE_CNAME) != NULL) { +dbg_ns_exec( + char *name = knot_dname_to_str(node->owner); + dbg_ns("Node %s has CNAME record, resolving...\n", + name); + free(name); +); + const knot_dname_t *act_name = qname; + ns_follow_cname(&node, &act_name, resp, + knot_response_add_rrset_answer, 1); +dbg_ns_exec( + char *name = (node != NULL) ? knot_dname_to_str(node->owner) + : "(nil)"; + char *name2 = knot_dname_to_str(act_name); + dbg_ns("Canonical name: %s (%p), node found: %p\n", + name2, act_name, node); + dbg_ns("The node's owner: %s (%p)\n", name, (node != NULL) + ? node->owner : NULL); + if (node != NULL) { + free(name); + } + free(name2); +); + qname = act_name; + cname = 1; + + // otherwise search for the new name + if (node == NULL) { + goto search; + } else if (node->owner != act_name) { + // the stored node is closest encloser + find_ret = KNOT_ZONE_NAME_NOT_FOUND; + closest_encloser = node; + node = NULL; + goto have_node; + } // else do nothing, just continue + } + + ret = ns_answer_from_node(node, closest_encloser, previous, zone, qname, + qtype, resp); + if (ret == NS_ERR_SERVFAIL) { + // in this case we should drop the response and send an error + // for now, just send the error code with a non-complete answer +// knot_response_set_rcode(resp, KNOT_RCODE_SERVFAIL); +// goto finalize; + return ret; + } else if (ret != KNOT_EOK) { + /*! \todo Handle RCODE return values!!! */ + goto finalize; + } + knot_response_set_aa(resp); + knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); + + // this is the only case when the servers answers from + // particular node, i.e. the only case when it may return SOA + // or NS records in Answer section + if (DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp)) + && node == knot_zone_contents_apex(zone) + && (qtype == KNOT_RRTYPE_SOA || qtype == KNOT_RRTYPE_NS)) { + ns_add_dnskey(node, resp); + } + +finalize: + if (ret == KNOT_EOK && auth_soa) { + ns_put_authority_soa(zone, resp); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Answers the query from the given zone database. + * + * First it searches for a zone to answer from. If there is none, it sets + * RCODE REFUSED to the response and ends. Otherwise it tries to answer the + * query using the found zone (see ns_answer_from_zone()). + * + * \param db Zone database to use for answering. + * \param resp Response that holds the parsed query. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_answer(knot_zonedb_t *db, knot_packet_t *resp) +{ + const knot_dname_t *qname = knot_packet_qname(resp); + assert(qname != NULL); + + uint16_t qtype = knot_packet_qtype(resp); +dbg_ns_exec( + char *name_str = knot_dname_to_str(qname); + dbg_ns("Trying to find zone for QNAME %s\n", name_str); + free(name_str); +); + // find zone in which to search for the name + const knot_zone_t *zone = + ns_get_zone_for_qname(db, qname, qtype); + const knot_zone_contents_t *contents = knot_zone_contents(zone); + + // if no zone found, return REFUSED + if (zone == NULL) { + dbg_ns("No zone found.\n"); + knot_response_set_rcode(resp, KNOT_RCODE_REFUSED); + //knot_dname_free(&qname); + return KNOT_EOK; + } else if (contents == NULL) { + dbg_ns("Zone expired or not bootstrapped. Reply SERVFAIL.\n"); + knot_response_set_rcode(resp, KNOT_RCODE_SERVFAIL); + return KNOT_EOK; + } + +dbg_ns_exec( + char *name_str2 = knot_dname_to_str(zone->contents->apex->owner); + dbg_ns("Found zone for QNAME %s\n", name_str2); + free(name_str2); +); + + // take the zone contents and use only them for answering + + return ns_answer_from_zone(contents, qname, qtype, resp); + + //knot_dname_free(&qname); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Converts the response to wire format. + * + * \param resp Response to convert. + * \param wire Place for the wire format of the response. + * \param wire_size In: space available for the wire format in bytes. + * Out: actual size of the wire format in bytes. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_response_to_wire(knot_packet_t *resp, uint8_t *wire, + size_t *wire_size) +{ + uint8_t *rwire = NULL; + size_t rsize = 0; + int ret = 0; + + if ((ret = knot_packet_to_wire(resp, &rwire, &rsize)) + != KNOT_EOK) { + dbg_ns("Error converting response packet " + "to wire format (error %d).\n", ret); + return NS_ERR_SERVFAIL; + } + + if (rsize > *wire_size) { + dbg_ns("Reponse size (%zu) larger than allowed wire size " + "(%zu).\n", rsize, *wire_size); + return NS_ERR_SERVFAIL; + } + + if (rwire != wire) { + dbg_ns("Wire format reallocated, copying to place for " + "wire.\n"); + memcpy(wire, rwire, rsize); + } else { + dbg_ns("Using the same space or wire format.\n"); + } + + *wire_size = rsize; + //free(rwire); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a wire format of an error response from partially created + * response. + * + * \param resp Response to use. + * \param wire Place for the wire format of the response. + * \param wire_size In: space available for the wire format in bytes. + * Out: actual size of the wire format in bytes. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_error_response_to_wire(knot_packet_t *resp, uint8_t *wire, + size_t *wire_size) +{ + /* Do not call the packet conversion function + * wire format is assembled, but COUNTs in header are not set. + * This is ideal, we just truncate the packet after the question. + */ + dbg_ns("Creating error response.\n"); + + size_t rsize = knot_packet_question_size(knot_packet_query(resp)); + dbg_ns("Error response (~ query) size: %zu\n", rsize); + + // take 'qsize' from the current wireformat of the response + // it is already assembled - Header and Question section are copied + const uint8_t *rwire = knot_packet_wireformat(resp); + if (rsize > *wire_size) { + dbg_ns("Reponse size (%zu) larger than allowed wire size" + " (%zu).\n", rsize, *wire_size); + return NS_ERR_SERVFAIL; + } + + assert(rwire != wire); + + /*! \todo Why is this copied?? Why we cannot use resp->wireformat?? */ + memcpy(wire, rwire, rsize); + + *wire_size = rsize; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +typedef struct ns_axfr_params { + knot_ns_xfr_t *xfr; + int ret; +} ns_axfr_params_t; + +/*----------------------------------------------------------------------------*/ + +int knot_ns_tsig_required(int packet_nr) +{ + dbg_ns_detail("ns_tsig_required(%d): %d\n", packet_nr, + (packet_nr % KNOT_NS_TSIG_FREQ == 0)); + return (packet_nr % KNOT_NS_TSIG_FREQ == 0); +} + +/*----------------------------------------------------------------------------*/ + +static int ns_xfr_send_and_clear(knot_ns_xfr_t *xfr, int add_tsig) +{ + assert(xfr != NULL); + assert(xfr->query != NULL); + assert(xfr->response != NULL); + assert(xfr->wire != NULL); + assert(xfr->send != NULL); + + // Transform the packet into wire format + dbg_ns("Converting response to wire format..\n"); + size_t real_size = xfr->wire_size; + if (ns_response_to_wire(xfr->response, xfr->wire, &real_size) != 0) { + return NS_ERR_SERVFAIL; +// // send back SERVFAIL (as this is our problem) +// ns_error_response(nameserver, +// knot_wire_get_id(query_wire), +// KNOT_RCODE_SERVFAIL, response_wire, +// rsize); + } + + int res = 0; + + size_t digest_real_size = xfr->digest_max_size; + + dbg_ns_detail("xfr->tsig_key=%p\n", xfr->tsig_key); + /*! \note [TSIG] Generate TSIG if required (during XFR/IN). */ + if (xfr->tsig_key && add_tsig) { + if (xfr->packet_nr == 0) { + /* Add key, digest and digest length. */ + dbg_ns_detail("Calling tsig_sign(): %p, %zu, %zu, " + "%p, %zu, %p, %zu, %p\n", + xfr->wire, real_size, xfr->wire_size, + xfr->digest, xfr->digest_size, xfr->digest, + digest_real_size, xfr->tsig_key); + res = knot_tsig_sign(xfr->wire, &real_size, + xfr->wire_size, xfr->digest, + xfr->digest_size, xfr->digest, + &digest_real_size, + xfr->tsig_key); + } else { + /* Add key, digest and digest length. */ + dbg_ns_detail("Calling tsig_sign_next()\n"); + res = knot_tsig_sign_next(xfr->wire, &real_size, + xfr->wire_size, + xfr->digest, + xfr->digest_size, + xfr->digest, + &digest_real_size, + xfr->tsig_key); + } + + dbg_ns_detail("Sign function returned: %s\n", + knot_strerror(res)); + dbg_ns_detail("Real size of digest: %zu\n", digest_real_size); + + if (res != KNOT_EOK) { + return res; + } + + assert(digest_real_size > 0); + // save the new previous digest size + xfr->digest_size = digest_real_size; + } + + // Send the response + dbg_ns("Sending response (size %zu)..\n", real_size); + //dbg_ns_hex((const char *)xfr->wire, real_size); + res = xfr->send(xfr->session, &xfr->addr, xfr->wire, real_size); + if (res < 0) { + dbg_ns("Send returned %d\n", res); + return res; + } else if (res != real_size) { + dbg_ns("AXFR did not send right amount of bytes." + " Transfer size: %zu, sent: %d\n", + real_size, res); + } + + // Clean the response structure + dbg_ns("Clearing response structure..\n"); + knot_response_clear(xfr->response, 0); + + // increment the packet number + ++xfr->packet_nr; + if (xfr->tsig_key && add_tsig) { + knot_packet_set_tsig_size(xfr->response, xfr->tsig_size); + } else { + knot_packet_set_tsig_size(xfr->response, 0); + } + + dbg_ns("Response structure after clearing:\n"); + knot_packet_dump(xfr->response); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static void ns_axfr_from_node(knot_node_t *node, void *data) +{ + assert(node != NULL); + assert(data != NULL); + + ns_axfr_params_t *params = (ns_axfr_params_t *)data; + + if (params->ret != KNOT_EOK) { + // just skip (will be called on next node with the same params + dbg_ns("Params contain error: %s, skipping node...\n", + knot_strerror(params->ret)); + return; + } + + dbg_ns("Params OK, answering AXFR from node %p.\n", node); +dbg_ns_exec( + char *name = knot_dname_to_str(knot_node_owner(node)); + dbg_ns("Node ownerr: %s\n", name); + free(name); +); + + if (knot_node_rrset_count(node) == 0) { + return; + } + + const knot_rrset_t **rrsets = knot_node_rrsets(node); + if (rrsets == NULL) { + params->ret = KNOT_ENOMEM; + return; + } + + int i = 0; + int ret = 0; + const knot_rrset_t *rrset = NULL; + while (i < knot_node_rrset_count(node)) { + assert(rrsets[i] != NULL); + rrset = rrsets[i]; +rrset: + dbg_ns(" Type: %s\n", + knot_rrtype_to_string(knot_rrset_type(rrset))); + + // do not add SOA + if (knot_rrset_type(rrset) == KNOT_RRTYPE_SOA) { + ++i; + continue; + } + + ret = knot_response_add_rrset_answer(params->xfr->response, + rrset, 0, 0, 1); + + if (ret == KNOT_ESPACE) { + // TODO: send the packet and clean the structure + dbg_ns("Packet full, sending..\n"); + ret = ns_xfr_send_and_clear(params->xfr, + knot_ns_tsig_required(params->xfr->packet_nr)); + if (ret != KNOT_EOK) { + // some wierd problem, we should end + params->ret = KNOT_ERROR; + break; + } + // otherwise try once more with the same RRSet + goto rrset; + } else if (ret != KNOT_EOK) { + // some wierd problem, we should end + params->ret = KNOT_ERROR; + break; + } + + // we can send the RRSets in any order, so add the RRSIGs now + rrset = knot_rrset_rrsigs(rrset); +rrsigs: + if (rrset == NULL) { + ++i; + continue; + } + + ret = knot_response_add_rrset_answer(params->xfr->response, + rrset, 0, 0, 1); + + if (ret == KNOT_ESPACE) { + // TODO: send the packet and clean the structure + dbg_ns("Packet full, sending..\n"); + ret = ns_xfr_send_and_clear(params->xfr, + knot_ns_tsig_required(params->xfr->packet_nr)); + if (ret != KNOT_EOK) { + // some wierd problem, we should end + params->ret = KNOT_ERROR; + break; + } + // otherwise try once more with the same RRSet + goto rrsigs; + } else if (ret != KNOT_EOK) { + // some wierd problem, we should end + params->ret = KNOT_ERROR; + break; + } + + // this way only whole RRSets are always sent + // we guess it will not create too much overhead + + ++i; + } + if (rrsets != NULL) { + free(rrsets); + } + + /*! \todo maybe distinguish some error codes. */ + //params->ret = (ret == 0) ? KNOT_EOK : KNOT_ERROR; +} + +/*----------------------------------------------------------------------------*/ + +static int ns_axfr_from_zone(knot_zone_contents_t *zone, knot_ns_xfr_t *xfr) +{ + assert(xfr != NULL); + assert(xfr->query != NULL); + assert(xfr->response != NULL); + assert(xfr->wire != NULL); + assert(xfr->send != NULL); + + ns_axfr_params_t params; + memset(¶ms, 0, sizeof(ns_axfr_params_t)); + params.xfr = xfr; + params.ret = KNOT_EOK; + + xfr->packet_nr = 0; + + /* + * First SOA + */ + + // retrieve SOA - must be send as first and last RR + const knot_rrset_t *soa_rrset = knot_node_rrset( + knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA); + if (soa_rrset == NULL) { + // some really serious error + return KNOT_ERROR; + } + + int ret; + + // add SOA RR to the response + ret = knot_response_add_rrset_answer(xfr->response, soa_rrset, 0, 0, 1); + if (ret != KNOT_EOK) { + // something is really wrong + return KNOT_ERROR; + } + + // add the SOA's RRSIG + const knot_rrset_t *rrset = knot_rrset_rrsigs(soa_rrset); + if (rrset != NULL + && (ret = knot_response_add_rrset_answer(xfr->response, rrset, + 0, 0, 1)) != KNOT_EOK) { + // something is really wrong, these should definitely fit in + return KNOT_ERROR; + } + + knot_zone_contents_tree_apply_inorder(zone, ns_axfr_from_node, + ¶ms); + + if (params.ret != KNOT_EOK) { + return KNOT_ERROR; // maybe do something with the code + } + + knot_zone_contents_nsec3_apply_inorder(zone, ns_axfr_from_node, + ¶ms); + + if (params.ret != KNOT_EOK) { + return KNOT_ERROR; // maybe do something with the code + } + + /* + * Last SOA + */ + + // try to add the SOA to the response again (last RR) + ret = knot_response_add_rrset_answer(xfr->response, soa_rrset, 0, 0, 1); + if (ret == KNOT_ESPACE) { + + // if there is not enough space, send the response and + // add the SOA record to a new packet + dbg_ns("Packet full, sending..\n"); + ret = ns_xfr_send_and_clear(xfr, + knot_ns_tsig_required(xfr->packet_nr)); + if (ret != KNOT_EOK) { + return ret; + } + + ret = knot_response_add_rrset_answer(xfr->response, + soa_rrset, 0, 0, 1); + if (ret != KNOT_EOK) { + return KNOT_ERROR; + } + + } else if (ret != KNOT_EOK) { + // something is really wrong + return KNOT_ERROR; + } + + dbg_ns("Sending packet...\n"); + return ns_xfr_send_and_clear(xfr, 1); +} + +/*----------------------------------------------------------------------------*/ + +static int ns_ixfr_put_rrset(knot_ns_xfr_t *xfr, const knot_rrset_t *rrset) +{ + int res = knot_response_add_rrset_answer(xfr->response, rrset, + 0, 0, 0); + if (res == KNOT_ESPACE) { + knot_response_set_rcode(xfr->response, KNOT_RCODE_NOERROR); + /*! \todo Probably rename the function. */ + ns_xfr_send_and_clear(xfr, knot_ns_tsig_required(xfr->packet_nr)); + + res = knot_response_add_rrset_answer(xfr->response, + rrset, 0, 0, 0); + } + + if (res != KNOT_EOK) { + dbg_ns("Error putting origin SOA to IXFR reply: %s\n", + knot_strerror(res)); + /*! \todo Probably send back AXFR instead. */ + knot_response_set_rcode(xfr->response, + KNOT_RCODE_SERVFAIL); + /*! \todo Probably rename the function. */ + ns_xfr_send_and_clear(xfr, 1); + //socket_close(xfr->session); /*! \todo Remove for UDP.*/ + return KNOT_ERROR; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int ns_ixfr_put_changeset(knot_ns_xfr_t *xfr, const knot_changeset_t *chgset) +{ + // 1) put origin SOA + int res = ns_ixfr_put_rrset(xfr, chgset->soa_from); + if (res != KNOT_EOK) { + return res; + } + + // 2) put remove RRSets + for (int i = 0; i < chgset->remove_count; ++i) { + res = ns_ixfr_put_rrset(xfr, chgset->remove[i]); + if (res != KNOT_EOK) { + return res; + } + } + + // 1) put target SOA + res = ns_ixfr_put_rrset(xfr, chgset->soa_to); + if (res != KNOT_EOK) { + return res; + } + + // 2) put remove RRSets + for (int i = 0; i < chgset->add_count; ++i) { + res = ns_ixfr_put_rrset(xfr, chgset->add[i]); + if (res != KNOT_EOK) { + return res; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int ns_ixfr_from_zone(knot_ns_xfr_t *xfr) +{ + assert(xfr != NULL); + assert(xfr->zone != NULL); + assert(xfr->query != NULL); + assert(xfr->response != NULL); + assert(knot_packet_authority_rrset_count(xfr->query) > 0); + assert(xfr->data != NULL); + + /*! \todo REMOVE start */ +// const knot_rrset_t *zone_soa = +// knot_node_rrset(knot_zone_contents_apex( +// knot_zone_contents(xfr->zone)), +// KNOT_RRTYPE_SOA); +// // retrieve origin (xfr) serial and target (zone) serial +// uint32_t zone_serial = knot_rdata_soa_serial( +// knot_rrset_rdata(zone_soa)); +// uint32_t xfr_serial = knot_rdata_soa_serial(knot_rrset_rdata( +// knot_packet_authority_rrset(xfr->query, 0))); + +// // 3) load changesets from journal +// knot_changesets_t *chgsets = (knot_changesets_t *) +// calloc(1, sizeof(knot_changesets_t)); +// int res = xfr_load_changesets(xfr->zone, chgsets, xfr_serial, +// zone_serial); +// if (res != KNOT_EOK) { +// dbg_ns("IXFR query cannot be answered: %s.\n", +// knot_strerror(res)); +// /*! \todo Probably send back AXFR instead. */ +// knot_response_set_rcode(xfr->response, KNOT_RCODE_SERVFAIL); +// /*! \todo Probably rename the function. */ +// ns_axfr_send_and_clear(xfr); +// //socket_close(xfr->session); /*! \todo Remove for UDP. */ +// return 1; +// } + + /*! \todo REMOVE end */ + + knot_changesets_t *chgsets = (knot_changesets_t *)xfr->data; + knot_zone_contents_t* contents = knot_zone_get_contents(xfr->zone); + assert(contents); + const knot_rrset_t *zone_soa = + knot_node_rrset(knot_zone_contents_apex(contents), + KNOT_RRTYPE_SOA); + + // 4) put the zone SOA as the first Answer RR + int res = knot_response_add_rrset_answer(xfr->response, zone_soa, 0, + 0, 0); + if (res != KNOT_EOK) { + dbg_ns("IXFR query cannot be answered: %s.\n", + knot_strerror(res)); + knot_response_set_rcode(xfr->response, + KNOT_RCODE_SERVFAIL); + /*! \todo Probably rename the function. */ + ns_xfr_send_and_clear(xfr, 1); +// socket_close(xfr->session); /*! \todo Remove for UDP.*/ + return 1; + } + + // 5) put the changesets into the response while they fit in + for (int i = 0; i < chgsets->count; ++i) { + res = ns_ixfr_put_changeset(xfr, &chgsets->sets[i]); + if (res != KNOT_EOK) { + // answer is sent, socket is closed + return KNOT_EOK; + } + } + + if (chgsets->count > 0) { + res = ns_ixfr_put_rrset(xfr, zone_soa); + } + + if (res == KNOT_EOK) { + /*! \todo Probably rename the function. */ + ns_xfr_send_and_clear(xfr, 1); + //socket_close(xfr->session); /*! \todo Remove for UDP.*/ + return 1; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int ns_ixfr(knot_ns_xfr_t *xfr) +{ + assert(xfr != NULL); + assert(xfr->query != NULL); + assert(xfr->response != NULL); + assert(knot_packet_qtype(xfr->response) == KNOT_RRTYPE_IXFR); + + // check if there is the required authority record + if ((knot_packet_authority_rrset_count(xfr->query) <= 0)) { + // malformed packet + dbg_ns("IXFR query does not contain authority record.\n"); + knot_response_set_rcode(xfr->response, KNOT_RCODE_FORMERR); + /*! \todo Probably rename the function. */ + ns_xfr_send_and_clear(xfr, 1); + //socket_close(xfr->session); + return 1; + } + + const knot_rrset_t *soa = knot_packet_authority_rrset(xfr->query, 0); + const knot_dname_t *qname = knot_packet_qname(xfr->response); + + // check if XFR QNAME and SOA correspond + if (knot_packet_qtype(xfr->query) != KNOT_RRTYPE_IXFR + || knot_rrset_type(soa) != KNOT_RRTYPE_SOA + || knot_dname_compare(qname, knot_rrset_owner(soa)) != 0) { + // malformed packet + dbg_ns("IXFR query is malformed.\n"); + knot_response_set_rcode(xfr->response, KNOT_RCODE_FORMERR); + /*! \todo Probably rename the function. */ + ns_xfr_send_and_clear(xfr, 1); + //socket_close(xfr->session); /*! \todo Remove for UDP. */ + return 1; + } + + return ns_ixfr_from_zone(xfr); +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ns_prepare_response(knot_nameserver_t *nameserver, + knot_packet_t *query, knot_packet_t **resp, + size_t max_size) +{ + assert(max_size >= 500); + + // initialize response packet structure + *resp = knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + if (*resp == NULL) { + dbg_ns("Failed to create packet structure.\n"); + return KNOT_ENOMEM; + } + + int ret = knot_packet_set_max_size(*resp, max_size); + //(*resp)->wireformat = response_wire;; + //(*resp)->max_size = max_size; + + if (ret != KNOT_EOK) { + dbg_ns("Failed to init response structure.\n"); + knot_packet_free(resp); + return ret; + } + + ret = knot_response_init_from_query(*resp, query); + + if (ret != KNOT_EOK) { + dbg_ns("Failed to init response structure.\n"); + knot_packet_free(resp); + return ret; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int32_t ns_serial_difference(uint32_t s1, uint32_t s2) +{ + return (((int64_t)s1 - s2) % ((int64_t)1 << 32)); +} + +/*----------------------------------------------------------------------------*/ +/* Public functions */ +/*----------------------------------------------------------------------------*/ + +knot_nameserver_t *knot_ns_create() +{ + knot_nameserver_t *ns = malloc(sizeof(knot_nameserver_t)); + if (ns == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + ns->data = 0; + + // Create zone database structure + dbg_ns("Creating Zone Database structure...\n"); + ns->zone_db = knot_zonedb_new(); + if (ns->zone_db == NULL) { + ERR_ALLOC_FAILED; + free(ns); + return NULL; + } + + // prepare empty response with SERVFAIL error + knot_packet_t *err = knot_packet_new(KNOT_PACKET_PREALLOC_NONE); + if (err == NULL) { + ERR_ALLOC_FAILED; + free(ns); + return NULL; + } + + dbg_ns("Created default empty response...\n"); + + int rc = knot_packet_set_max_size(err, KNOT_WIRE_HEADER_SIZE); + if (rc != KNOT_EOK) { + dbg_ns("Error creating default error response: %s.\n", + knot_strerror(rc)); + free(ns); + knot_packet_free(&err); + return NULL; + } + + rc = knot_response_init(err); + if (rc != KNOT_EOK) { + dbg_ns("Error initializing default error response:" + " %s.\n", knot_strerror(rc)); + free(ns); + knot_packet_free(&err); + return NULL; + } + + knot_response_set_rcode(err, KNOT_RCODE_SERVFAIL); + ns->err_resp_size = 0; + + dbg_ns("Converting default empty response to wire format...\n"); + + uint8_t *error_wire = NULL; + + if (knot_packet_to_wire(err, &error_wire, &ns->err_resp_size) != 0) { + dbg_ns("Error while converting " + "default error response to " + "wire format \n"); + knot_packet_free(&err); + free(ns); + return NULL; + } + + ns->err_response = (uint8_t *)malloc(ns->err_resp_size); + if (ns->err_response == NULL) { + dbg_ns("Error while converting default " + "error response to wire format \n"); + knot_packet_free(&err); + free(ns); + return NULL; + } + + memcpy(ns->err_response, error_wire, ns->err_resp_size); + + dbg_ns("Done..\n"); + + knot_packet_free(&err); + + if (EDNS_ENABLED) { + ns->opt_rr = knot_edns_new(); + if (ns->opt_rr == NULL) { + dbg_ns("Error while preparing OPT RR of the" + " server.\n"); + knot_packet_free(&err); + free(ns); + return NULL; + } + knot_edns_set_version(ns->opt_rr, EDNS_VERSION); + knot_edns_set_payload(ns->opt_rr, MAX_UDP_PAYLOAD_EDNS); + } else { + ns->opt_rr = NULL; + } + + knot_packet_free(&err); + + return ns; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_parse_packet(const uint8_t *query_wire, size_t qsize, + knot_packet_t *packet, knot_packet_type_t *type) +{ + if (packet == NULL || query_wire == NULL || type == NULL) { + dbg_ns("Missing parameter to query parsing.\n"); + return KNOT_EBADARG; + } + + dbg_ns("ns_parse_packet() called with query size %zu.\n", qsize); + //dbg_ns_hex((char *)query_wire, qsize); + + if (qsize < 2) { + return KNOT_EMALF; + } + + // 1) create empty response + dbg_ns("Parsing packet...\n"); + //parsed = knot_response_new_empty(NULL); + + int ret = 0; + + if ((ret = knot_packet_parse_from_wire(packet, query_wire, + qsize, 1)) != 0) { + dbg_ns("Error while parsing packet, " + "libknot error '%s'.\n", knot_strerror(ret)); +// knot_response_free(&parsed); + return KNOT_RCODE_FORMERR; + } + + dbg_ns("Parsed packet header and Question:\n"); + knot_packet_dump(packet); + + // 3) determine the query type + switch (knot_packet_opcode(packet)) { + case KNOT_OPCODE_QUERY: + switch (knot_packet_qtype(packet)) { + case KNOT_RRTYPE_AXFR: + *type = (knot_packet_is_query(packet)) + ? KNOT_QUERY_AXFR : KNOT_RESPONSE_AXFR; + break; + case KNOT_RRTYPE_IXFR: + *type = (knot_packet_is_query(packet)) + ? KNOT_QUERY_IXFR : KNOT_RESPONSE_IXFR; + break; + default: + *type = (knot_packet_is_query(packet)) + ? KNOT_QUERY_NORMAL : KNOT_RESPONSE_NORMAL; + } + + break; + case KNOT_OPCODE_NOTIFY: + *type = (knot_packet_is_query(packet)) + ? KNOT_QUERY_NOTIFY : KNOT_RESPONSE_NOTIFY; + break; + case KNOT_OPCODE_UPDATE: + if(knot_packet_is_query(packet)) { + *type = KNOT_QUERY_UPDATE; + } else { + return KNOT_RCODE_FORMERR; + } + break; + default: + return KNOT_RCODE_NOTIMPL; + } + +// knot_packet_free(&packet); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_ns_error_response(const knot_nameserver_t *nameserver, uint16_t query_id, + uint8_t rcode, uint8_t *response_wire, size_t *rsize) +{ + //dbg_ns("Error response: \n"); + //dbg_ns_hex((const char *)nameserver->err_response, + // nameserver->err_resp_size); + + memcpy(response_wire, nameserver->err_response, + nameserver->err_resp_size); + // copy ID of the query + knot_wire_set_id(response_wire, query_id); + // set the RCODE + knot_wire_set_rcode(response_wire, rcode); + *rsize = nameserver->err_resp_size; +} + +/*----------------------------------------------------------------------------*/ + +void knot_ns_error_response_full(knot_nameserver_t *nameserver, + knot_packet_t *response, uint8_t rcode, + uint8_t *response_wire, size_t *rsize) +{ + knot_response_set_rcode(response, rcode); + + if (ns_error_response_to_wire(response, response_wire, rsize) != 0) { + knot_ns_error_response(nameserver, knot_packet_id( + knot_packet_query(response)), + KNOT_RCODE_SERVFAIL, response_wire, + rsize); + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_answer_normal(knot_nameserver_t *nameserver, knot_packet_t *query, + uint8_t *response_wire, size_t *rsize) +{ + dbg_ns("ns_answer_normal()\n"); + + // first, parse the rest of the packet + assert(knot_packet_is_query(query)); + dbg_ns("Query - parsed: %zu, total wire size: %zu\n", + knot_packet_parsed(query), knot_packet_size(query)); + int ret; + + ret = knot_packet_parse_rest(query); + if (ret != KNOT_EOK) { + dbg_ns("Failed to parse rest of the query: " + "%s.\n", knot_strerror(ret)); + knot_ns_error_response(nameserver, knot_packet_id(query), + (ret == KNOT_EMALF) + ? KNOT_RCODE_FORMERR + : KNOT_RCODE_SERVFAIL, response_wire, + rsize); + return KNOT_EOK; + } + + /* + * Semantic checks - if ANCOUNT > 0 or NSCOUNT > 0, return FORMERR. + * + * If any xxCOUNT is less or more than actual RR count + * the previously called knot_packet_parse_rest() will recognize this. + * + * Check the QDCOUNT and in case of anything but 1 send back + * FORMERR + */ + if (knot_packet_ancount(query) > 0 + || knot_packet_nscount(query) > 0 + || knot_packet_qdcount(query) != 1) { + dbg_ns("ANCOUNT or NSCOUNT not 0 in query, reply FORMERR.\n"); + knot_ns_error_response(nameserver, knot_packet_id(query), + KNOT_RCODE_FORMERR, response_wire, + rsize); + return KNOT_EOK; + } + + size_t resp_max_size = 0; + + assert(*rsize >= MAX_UDP_PAYLOAD); + + knot_packet_dump(query); + + if (knot_query_edns_supported(query)) { + if (knot_edns_get_payload(&query->opt_rr) < + knot_edns_get_payload(nameserver->opt_rr)) { + resp_max_size = knot_edns_get_payload(&query->opt_rr); + } else { + resp_max_size = knot_edns_get_payload( + nameserver->opt_rr); + } + } + + if (resp_max_size < MAX_UDP_PAYLOAD) { + resp_max_size = MAX_UDP_PAYLOAD; + } + + knot_packet_t *response; + ret = knot_ns_prepare_response(nameserver, query, &response, + resp_max_size); + if (ret != KNOT_EOK) { + knot_ns_error_response(nameserver, knot_packet_id(query), + KNOT_RCODE_SERVFAIL, response_wire, + rsize); + return KNOT_EOK; + } + + dbg_ns("Query - parsed: %zu, total wire size: %zu\n", + query->parsed, query->size); + dbg_ns("Opt RR: version: %d, payload: %d\n", + query->opt_rr.version, query->opt_rr.payload); + + // get the answer for the query + rcu_read_lock(); + knot_zonedb_t *zonedb = rcu_dereference(nameserver->zone_db); + + dbg_ns("EDNS supported in query: %d\n", + knot_query_edns_supported(query)); + + // set the OPT RR to the response + if (knot_query_edns_supported(query)) { + /*! \todo API. */ +// if (knot_edns_get_payload(&query->opt_rr) > MAX_UDP_PAYLOAD) { +// ret = knot_packet_set_max_size(response, +// knot_edns_get_payload(&query->opt_rr)); +// } else { +// ret = knot_packet_set_max_size(response, +// MAX_UDP_PAYLOAD); +// } + +// if (ret != KNOT_EOK) { +// dbg_ns("Failed to set max size.\n"); +// knot_ns_error_response_full(nameserver, response, +// KNOT_RCODE_SERVFAIL, +// response_wire, rsize); +// return KNOT_EOK; +// } + + ret = knot_response_add_opt(response, nameserver->opt_rr, 1); + if (ret != KNOT_EOK) { + dbg_ns("Failed to set OPT RR to the response" + ": %s\n",knot_strerror(ret)); + } else { + // copy the DO bit from the query + if (knot_query_dnssec_requested(query)) { + /*! \todo API for this. */ + knot_edns_set_do(&response->opt_rr); + } + } + }/* else { + dbg_ns("Setting max size to %u.\n", MAX_UDP_PAYLOAD); + ret = knot_packet_set_max_size(response, MAX_UDP_PAYLOAD); + if (ret != KNOT_EOK) { + dbg_ns("Failed to set max size to %u\n", + MAX_UDP_PAYLOAD); + knot_ns_error_response_full(nameserver, response, + KNOT_RCODE_SERVFAIL, + response_wire, rsize); + return KNOT_EOK; + } + }*/ + + dbg_ns("Response max size: %zu\n", response->max_size); + + ret = ns_answer(zonedb, response); + if (ret != 0) { + // now only one type of error (SERVFAIL), later maybe more + knot_ns_error_response_full(nameserver, response, + KNOT_RCODE_SERVFAIL, + response_wire, rsize); + } else { + dbg_ns("Created response packet.\n"); + //knot_response_dump(resp); + knot_packet_dump(response); + + // 4) Transform the packet into wire format + if (ns_response_to_wire(response, response_wire, rsize) != 0) { + // send back SERVFAIL (as this is our problem) + knot_ns_error_response_full(nameserver, response, + KNOT_RCODE_SERVFAIL, + response_wire, rsize); + } + } + + rcu_read_unlock(); + knot_packet_free(&response); + + dbg_ns("Returning response with wire size %zu\n", *rsize); + //dbg_ns_hex((char *)response_wire, *rsize); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_init_xfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) +{ + dbg_ns("knot_ns_init_xfr()\n"); + + if (nameserver == NULL || xfr == NULL) { + return KNOT_EBADARG; + } + + // no need to parse rest of the packet + /*! \todo Parse rest of packet because of EDNS. */ + int ret = knot_packet_parse_rest(xfr->query); + if (ret != KNOT_EOK) { + dbg_ns("Failed to parse rest of the query: %s\n", + knot_strerror(ret)); + knot_ns_error_response(nameserver, xfr->query->header.id, + (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR + : KNOT_RCODE_SERVFAIL, + xfr->wire, &xfr->wire_size); + ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, + xfr->wire_size); + return ret; + } + + dbg_packet("Parsed XFR query:\n"); + knot_packet_dump(xfr->query); + + // initialize response packet structure + knot_packet_t *response = knot_packet_new( + KNOT_PACKET_PREALLOC_RESPONSE); + if (response == NULL) { + dbg_ns("Failed to create packet structure.\n"); + /*! \todo xfr->wire is not NULL, will fail on assert! */ + knot_ns_error_response(nameserver, xfr->query->header.id, + KNOT_RCODE_SERVFAIL, xfr->wire, + &xfr->wire_size); + ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, + xfr->wire_size); + knot_packet_free(&response); + return ret; + } + + //int ret = knot_packet_set_max_size(response, xfr->wire_size); + response->wireformat = xfr->wire; + response->max_size = xfr->wire_size; + +// if (ret != KNOT_EOK) { +// dbg_ns("Failed to init response structure.\n"); +// /*! \todo xfr->wire is not NULL, will fail on assert! */ +// knot_ns_error_response(nameserver, xfr->query->header.id, +// KNOT_RCODE_SERVFAIL, xfr->wire, +// &xfr->wire_size); +// int res = xfr->send(xfr->session, &xfr->addr, xfr->wire, +// xfr->wire_size); +// knot_packet_free(&response); +// return res; +// } + + ret = knot_response_init_from_query(response, xfr->query); + + if (ret != KNOT_EOK) { + dbg_ns("Failed to init response structure.\n"); + /*! \todo xfr->wire is not NULL, will fail on assert! */ + knot_ns_error_response(nameserver, xfr->query->header.id, + KNOT_RCODE_SERVFAIL, xfr->wire, + &xfr->wire_size); + int res = xfr->send(xfr->session, &xfr->addr, xfr->wire, + xfr->wire_size); + knot_packet_free(&response); + return res; + } + + xfr->response = response; + + knot_zonedb_t *zonedb = rcu_dereference(nameserver->zone_db); + + const knot_dname_t *qname = knot_packet_qname(xfr->response); + + assert(knot_packet_qtype(xfr->response) == KNOT_RRTYPE_AXFR || + knot_packet_qtype(xfr->response) == KNOT_RRTYPE_IXFR); + +dbg_ns_exec( + char *name_str = knot_dname_to_str(qname); + dbg_ns("Trying to find zone with name %s\n", name_str); + free(name_str); +); + // find zone in which to search for the name + knot_zone_t *zone = knot_zonedb_find_zone(zonedb, qname); + + // if no zone found, return NotAuth + if (zone == NULL) { + dbg_ns("No zone found.\n"); + knot_response_set_rcode(xfr->response, KNOT_RCODE_NOTAUTH); + ns_xfr_send_and_clear(xfr, 1); + return KNOT_ENOZONE; + } + +dbg_ns_exec( + char *name2_str = knot_dname_to_str(qname); + dbg_ns("Found zone for name %s\n", name2_str); + free(name2_str); +); + xfr->zone = zone; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int ns_serial_compare(uint32_t s1, uint32_t s2) +{ + int32_t diff = ns_serial_difference(s1, s2); + return (s1 == s2) /* s1 equal to s2 */ + ? 0 + :((diff >= 1 && diff < ((uint32_t)1 << 31)) + ? 1 /* s1 larger than s2 */ + : -1); /* s1 less than s2 */ +} + +/*----------------------------------------------------------------------------*/ + +int ns_ixfr_load_serials(const knot_ns_xfr_t *xfr, uint32_t *serial_from, + uint32_t *serial_to) +{ + if (xfr == NULL || xfr->zone == NULL || serial_from == NULL + || serial_to == NULL) { + dbg_ns_detail("Wrong parameters: xfr=%p," + " xfr->zone = %p\n", xfr, xfr->zone); + return KNOT_EBADARG; + } + + const knot_zone_t *zone = xfr->zone; + const knot_zone_contents_t *contents = knot_zone_contents(zone); + if (!contents) { + dbg_ns_detail("Missing contents\n"); + return KNOT_EBADARG; + } + + if (knot_zone_contents_apex(contents) == NULL) { + dbg_ns_detail("No apex.\n"); + return KNOT_EBADARG; + } + + const knot_rrset_t *zone_soa = + knot_node_rrset(knot_zone_contents_apex(contents), + KNOT_RRTYPE_SOA); + if (zone_soa == NULL) { + dbg_ns_verb("No SOA.\n"); + return KNOT_EBADARG; + } + + if (knot_packet_nscount(xfr->query) < 1) { + dbg_ns_verb("No Authority record.\n"); + return KNOT_EMALF; + } + + if (knot_packet_authority_rrset(xfr->query, 0) == NULL) { + dbg_ns_verb("Authority record missing.\n"); + return KNOT_ERROR; + } + + // retrieve origin (xfr) serial and target (zone) serial + *serial_to = knot_rdata_soa_serial(knot_rrset_rdata(zone_soa)); + *serial_from = knot_rdata_soa_serial(knot_rrset_rdata( + knot_packet_authority_rrset(xfr->query, 0))); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_xfr_send_error(const knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr, knot_rcode_t rcode) +{ + /*! \todo Handle TSIG errors differently. */ + knot_response_set_rcode(xfr->response, rcode); + + /*! \todo Probably rename the function. */ + int ret = 0; + if ((ret = ns_xfr_send_and_clear(xfr, 1)) != KNOT_EOK) { + size_t size = 0; + knot_ns_error_response(nameserver, xfr->query->header.id, + KNOT_RCODE_SERVFAIL, xfr->wire, &size); + ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, size); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_answer_axfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) +{ + if (xfr == NULL || nameserver == NULL || xfr->zone == NULL) { + return KNOT_EBADARG; + } + + rcu_read_lock(); + + // take the contents and answer from them + int ret = 0; + knot_zone_contents_t *contents = knot_zone_get_contents(xfr->zone); + if (!contents) { + dbg_ns("AXFR failed on stub zone\n"); + /*! \todo replace with knot_ns_xfr_send_error() */ + knot_ns_error_response(nameserver, xfr->query->header.id, + KNOT_RCODE_SERVFAIL, xfr->wire, + &xfr->wire_size); + ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, + xfr->wire_size); + rcu_read_unlock(); + knot_packet_free(&xfr->response); + return KNOT_EOK; + } + + /*! + * \todo [TSIG] The TSIG data should already be stored in 'xfr'. + * Now just count the expected size of the TSIG RR and save it + * to the response structure. + */ + + /*! \todo [TSIG] Get the TSIG size from some API function. */ + if (xfr->tsig_size > 0) { + dbg_ns_detail("Setting TSIG size in packet: %zu\n", + xfr->tsig_size); + knot_packet_set_tsig_size(xfr->response, xfr->tsig_size); + } + + ret = ns_axfr_from_zone(contents, xfr); + + /*! \todo Somehow distinguish when it makes sense to send the SERVFAIL + * and when it does not. E.g. if there was problem in sending + * packet, it will probably fail when sending the SERVFAIL also. + */ + if (ret < 0) { + dbg_ns("AXFR failed, sending SERVFAIL.\n"); + // now only one type of error (SERVFAIL), later maybe more + /*! \todo xfr->wire is not NULL, will fail on assert! */ + /*! \todo replace with knot_ns_xfr_send_error() */ + knot_ns_error_response(nameserver, xfr->query->header.id, + KNOT_RCODE_SERVFAIL, xfr->wire, + &xfr->wire_size); + ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, + xfr->wire_size); + } else if (ret > 0) { + ret = KNOT_ERROR; + } + + rcu_read_unlock(); + + knot_packet_free(&xfr->response); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_answer_ixfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) +{ + if (nameserver == NULL || xfr == NULL || xfr->zone == NULL + || xfr->response == NULL) { + return KNOT_EBADARG; + } + + //uint8_t *wire = NULL; + //size_t size = xfr->wire_size; + + // parse rest of the packet (we need the Authority record) + int ret = knot_packet_parse_rest(xfr->query); + if (ret != KNOT_EOK) { + dbg_ns("Failed to parse rest of the packet. Reply FORMERR.\n"); +// knot_ns_error_response_full(nameserver, xfr->response, +// KNOT_RCODE_FORMERR, xfr->wire, +// &size); + knot_ns_xfr_send_error(nameserver, xfr, KNOT_RCODE_FORMERR); + + //ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, size); + knot_packet_free(&xfr->response); + return ret; + } + + // check if the zone has contents + if (knot_zone_contents(xfr->zone) == NULL) { + dbg_ns("Zone expired or not bootstrapped. Reply SERVFAIL.\n"); + ret = knot_ns_xfr_send_error(nameserver, xfr, KNOT_RCODE_SERVFAIL); +// knot_ns_error_response_full(nameserver, xfr->response, +// KNOT_RCODE_SERVFAIL, xfr->wire, +// &size); + +// ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, size); + knot_packet_free(&xfr->response); + return ret; + } + + /*! + * \todo [TSIG] The TSIG data should already be stored in 'xfr'. + * Now just count the expected size of the TSIG RR and save it + * to the response structure. This should be optional, only if + * the request contained TSIG, i.e. if there is the data in 'xfr'. + */ + + /*! \todo [TSIG] Get the TSIG size from some API function. */ + if (xfr->tsig_size > 0) { + knot_packet_set_tsig_size(xfr->response, xfr->tsig_size); + } + + ret = ns_ixfr(xfr); + + /*! \todo Somehow distinguish when it makes sense to send the SERVFAIL + * and when it does not. E.g. if there was problem in sending + * packet, it will probably fail when sending the SERVFAIL also. + */ + if (ret < 0) { + dbg_ns("IXFR failed, sending SERVFAIL.\n"); + // now only one type of error (SERVFAIL), later maybe more + + /*! \todo Extract this to some function. */ +// knot_response_set_rcode(xfr->response, KNOT_RCODE_SERVFAIL); +// uint8_t *wire = NULL; +// ret = knot_packet_to_wire(xfr->response, &wire, &size); +// if (ret != KNOT_EOK) { +//// knot_ns_error_response(nameserver, +//// xfr->query->header.id, +//// KNOT_RCODE_SERVFAIL, xfr->wire, +//// &size); +//// ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, +//// size); +// knot_ns_xfr_send_error(xfr, KNOT_RCODE_SERVFAIL); +// knot_packet_free(&xfr->response); +// return ret; +// } else { +// ret = xfr->send(xfr->session, &xfr->addr, wire, size); +// } + knot_ns_xfr_send_error(nameserver, xfr, KNOT_RCODE_SERVFAIL); + } /*else if (ret > 0) { + ret = KNOT_ERROR; + }*/ + + knot_packet_free(&xfr->response); + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_process_axfrin(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) +{ + /*! + * \todo [TSIG] Here we assume that 'xfr' contains TSIG information + * and the digest of the query sent to the master or the previous + * digest. + */ + + dbg_ns("ns_process_axfrin: incoming packet, wire size: %zu\n", + xfr->wire_size); + + int ret = xfrin_process_axfr_packet(/*xfr->wire, xfr->wire_size,*/ + /*(xfrin_constructed_zone_t **)(&xfr->data)*/ + xfr); + + if (ret > 0) { // transfer finished + dbg_ns("ns_process_axfrin: AXFR finished, zone created.\n"); + /* + * Adjust zone so that node count is set properly and nodes are + * marked authoritative / delegation point. + */ + xfrin_constructed_zone_t *constr_zone = + (xfrin_constructed_zone_t *)xfr->data; + knot_zone_contents_t *zone = constr_zone->contents; + assert(zone != NULL); + + dbg_ns("ns_process_axfrin: adjusting zone.\n"); + knot_zone_contents_adjust(zone, 0); + + /* Create and fill hash table */ + dbg_ns("ns_process_axfrin: filling hash table.\n"); + int rc = knot_zone_contents_create_and_fill_hash_table(zone); + if (rc != KNOT_EOK) { + return KNOT_ERROR; // TODO: change error code + } + + // save the zone contents to the xfr->data + xfr->data = zone; + + // free the structure used for processing XFR + assert(constr_zone->rrsigs == NULL); + free(constr_zone); + + //knot_zone_contents_dump(zone, 0); + } + + /*! + * \todo In case of error, shouldn't the zone be destroyed here? + */ + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_switch_zone(knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr) +{ + if (xfr == NULL || nameserver == NULL || xfr->data == NULL) { + return KNOT_EBADARG; + } + + knot_zone_contents_t *zone = (knot_zone_contents_t *)xfr->data; + + dbg_ns("Replacing zone by new one: %p\n", zone); + + // find the zone in the zone db + knot_zone_t *z = knot_zonedb_find_zone(nameserver->zone_db, + knot_node_owner(knot_zone_contents_apex(zone))); + if (z == NULL) { + char *name = knot_dname_to_str(knot_node_owner( + knot_zone_contents_apex(zone))); + dbg_ns("Failed to replace zone %s, old zone " + "not found\n", name); + free(name); + } else { + zone->zone = z; + } + + knot_zone_contents_t *old = rcu_xchg_pointer(&z->contents, zone); + +// knot_zone_t *old = knot_zonedb_replace_zone(nameserver->zone_db, +// zone); + dbg_ns("Old zone: %p\n", old); +// if (old == NULL) { +// char *name = knot_dname_to_str( +// knot_node_owner(knot_zone_apex(zone))); +// dbg_ns("Failed to replace zone %s\n", name); +// free(name); +// } + + // wait for readers to finish + dbg_ns("Waiting for readers to finish...\n"); + synchronize_rcu(); + // destroy the old zone + dbg_ns("Freeing old zone: %p\n", old); + knot_zone_contents_deep_free(&old, 0); + +dbg_ns_exec( + dbg_ns("Zone db contents: (zone count: %zu)\n", + nameserver->zone_db->zone_count); + + const knot_zone_t **zones = knot_zonedb_zones(nameserver->zone_db); + for (int i = 0; i < knot_zonedb_zone_count + (nameserver->zone_db); i++) { + dbg_ns("%d. zone: %p", i, zones[i]); + char *name = knot_dname_to_str(zones[i]->name); + dbg_ns(" zone name: %s\n", name); + free(name); + } + free(zones); +); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! \todo In this function, xfr->zone is properly set. If this is so, we do not + * have to search for the zone after the transfer has finished. + */ +int knot_ns_process_ixfrin(knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr) +{ + dbg_ns("ns_process_ixfrin: incoming packet\n"); + + /*! + * \todo [TSIG] Here we assume that 'xfr' contains TSIG information + * and the digest of the query sent to the master or the previous + * digest. + */ + + int ret = xfrin_process_ixfr_packet(xfr/*xfr->wire, xfr->wire_size, + (knot_changesets_t **)(&xfr->data)*/); + + if (ret == XFRIN_RES_FALLBACK) { + dbg_ns("ns_process_ixfrin: Fallback to AXFR.\n"); + assert(xfr->data == NULL); +// dbg_ns("xfr->zone = %p\n", xfr->zone); +// dbg_ns("Zone name: %.*s\n", +// xfr->zone->name->size, xfr->zone->name->name); +// assert(xfr->zone == NULL); + knot_packet_free(&xfr->query); + return KNOT_ENOIXFR; + } + + if (ret > 0) { + dbg_ns("ns_process_ixfrin: IXFR finished\n"); + + knot_changesets_t *chgsets = (knot_changesets_t *)xfr->data; + if (chgsets == NULL || chgsets->first_soa == NULL) { + // nothing to be done?? + dbg_ns("No changesets created for incoming IXFR!\n"); + return ret; + } + + // find zone associated with the changesets + knot_zone_t *zone = knot_zonedb_find_zone( + nameserver->zone_db, + knot_rrset_owner(chgsets->first_soa)); + if (zone == NULL) { + dbg_ns("No zone found for incoming IXFR!\n"); + knot_free_changesets( + (knot_changesets_t **)(&xfr->data)); + return KNOT_ENOZONE; /*! \todo Other error code? */ + } + + switch (ret) { + case XFRIN_RES_COMPLETE: + xfr->zone = zone; + break; + case XFRIN_RES_SOA_ONLY: { + // compare the SERIAL from the changeset with the zone's + // serial + const knot_node_t *apex = knot_zone_contents_apex( + knot_zone_contents(zone)); + if (apex == NULL) { + return KNOT_ERROR; + } + + const knot_rrset_t *zone_soa = knot_node_rrset( + apex, KNOT_RRTYPE_SOA); + if (zone_soa == NULL) { + return KNOT_ERROR; + } + + if (knot_rdata_soa_serial(knot_rrset_rdata( + chgsets->first_soa)) + != knot_rdata_soa_serial(knot_rrset_rdata( + zone_soa))) { + dbg_ns("Update did not fit.\n"); + return KNOT_EAGAIN; + } else { + // free changesets + dbg_ns("No update needed.\n"); + knot_free_changesets( + (knot_changesets_t **)(&xfr->data)); + return KNOT_ENOXFR; + } + } break; + } + } + + /*! + * \todo In case of error, shouldn't the zone be destroyed here? + */ + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_process_update(knot_nameserver_t *nameserver, knot_packet_t *query, + uint8_t *response_wire, size_t *rsize, + knot_zone_t **zone, knot_changeset_t **changeset) +{ + // 1) Parse the rest of the packet + assert(knot_packet_is_query(query)); + + knot_packet_t *response; + assert(*rsize >= MAX_UDP_PAYLOAD); + int ret = knot_ns_prepare_response(nameserver, query, &response, + MAX_UDP_PAYLOAD); + if (ret != KNOT_EOK) { + knot_ns_error_response(nameserver, knot_packet_id(query), + KNOT_RCODE_SERVFAIL, response_wire, + rsize); + return KNOT_EOK; + } + + assert(response != NULL); + + dbg_ns("Query - parsed: %zu, total wire size: %zu\n", + query->parsed, query->size); + + if (knot_packet_parsed(query) < knot_packet_size(query)) { + ret = knot_packet_parse_rest(query); + if (ret != KNOT_EOK) { + dbg_ns("Failed to parse rest of the query: " + "%s.\n", knot_strerror(ret)); + knot_ns_error_response_full(nameserver, response, + (ret == KNOT_EMALF) + ? KNOT_RCODE_FORMERR + : KNOT_RCODE_SERVFAIL, + response_wire, rsize); + knot_packet_free(&response); + return KNOT_EOK; + } + } + + dbg_ns("Query - parsed: %zu, total wire size: %zu\n", + knot_packet_parsed(query), knot_packet_size(query)); + + /*! \todo API for EDNS values. */ + dbg_ns("Opt RR: version: %d, payload: %d\n", + query->opt_rr.version, query->opt_rr.payload); + + // 2) Find zone for the query + // we do not check if there is only one entry in the Question section + // because the packet structure does not allow it + /*! \todo Check number of Question entries while parsing. */ + if (knot_packet_qtype(query) != KNOT_RRTYPE_SOA) { + dbg_ns("Question is not of type SOA.\n"); + knot_ns_error_response_full(nameserver, response, + KNOT_RCODE_FORMERR, + response_wire, rsize); + knot_packet_free(&response); + return KNOT_EOK; + } + + *zone = knot_zonedb_find_zone(nameserver->zone_db, + knot_packet_qname(query)); + if (*zone == NULL) { + dbg_ns("Zone not found for the update.\n"); + knot_ns_error_response_full(nameserver, response, + KNOT_RCODE_NOTAUTH, + response_wire, rsize); + knot_packet_free(&response); + return KNOT_EOK; + } + + uint8_t rcode = 0; + // 3) Check zone + ret = knot_ddns_check_zone(*zone, query, &rcode); + if (ret == KNOT_EBADZONE) { + // zone is slave, forward the request + /*! \todo Implement forwarding. */ + return KNOT_EBADZONE; + } else if (ret != KNOT_EOK) { + dbg_ns("Failed to check zone for update: " + "%s.\n", knot_strerror(ret)); + knot_ns_error_response_full(nameserver, response, rcode, + response_wire, rsize); + knot_packet_free(&response); + return KNOT_EOK; + } + + // 4) Convert prerequisities + knot_ddns_prereq_t *prereqs = NULL; + ret = knot_ddns_process_prereqs(query, &prereqs, &rcode); + if (ret != KNOT_EOK) { + dbg_ns("Failed to check zone for update: " + "%s.\n", knot_strerror(ret)); + knot_ns_error_response_full(nameserver, response, rcode, + response_wire, rsize); + knot_packet_free(&response); + return KNOT_EOK; + } + + assert(prereqs != NULL); + + // 5) Check prerequisities + /*! \todo Somehow ensure the zone will not be changed until the update + * is finished. + */ + ret = knot_ddns_check_prereqs(knot_zone_contents(*zone), &prereqs, + &rcode); + if (ret != KNOT_EOK) { + dbg_ns("Failed to check zone for update: " + "%s.\n", knot_strerror(ret)); + knot_ns_error_response_full(nameserver, response, rcode, + response_wire, rsize); + knot_ddns_prereqs_free(&prereqs); + knot_packet_free(&response); + return KNOT_EOK; + } + + // 6) Convert update to changeset + ret = knot_ddns_process_update(query, changeset, &rcode); + if (ret != KNOT_EOK) { + dbg_ns("Failed to check zone for update: " + "%s.\n", knot_strerror(ret)); + knot_ns_error_response_full(nameserver, response, rcode, + response_wire, rsize); + knot_ddns_prereqs_free(&prereqs); + knot_packet_free(&response); + return KNOT_EOK; + } + + assert(changeset != NULL); + + // 7) Create response + dbg_ns("Update converted successfuly.\n"); + + /*! \todo No response yet. Distinguish somehow in the caller. + * Maybe only this case will be EOK, other cases some error. + */ + + knot_packet_free(&response); + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_create_forward_query(const knot_packet_t *query, + uint8_t *query_wire, size_t *size) +{ + // just copy the wireformat of the query and set a new random ID to it + if (knot_packet_size(query) > *size) { + return KNOT_ESPACE; + } + + memcpy(query_wire, knot_packet_wireformat(query), + knot_packet_size(query)); + *size = knot_packet_size(query); + + knot_wire_set_id(query_wire, knot_random_id()); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_process_forward_response(const knot_packet_t *response, + uint16_t original_id, + uint8_t *response_wire, size_t *size) +{ + // just copy the wireformat of the response and set the original ID + + if (knot_packet_size(response) > *size) { + return KNOT_ESPACE; + } + + memcpy(response_wire, knot_packet_wireformat(response), + knot_packet_size(response)); + *size = knot_packet_size(response); + + knot_wire_set_id(response_wire, original_id); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void *knot_ns_data(knot_nameserver_t *nameserver) +{ + return nameserver->data; +} + +/*----------------------------------------------------------------------------*/ + +void *knot_ns_get_data(knot_nameserver_t *nameserver) +{ + return nameserver->data; +} + +/*----------------------------------------------------------------------------*/ + +void knot_ns_set_data(knot_nameserver_t *nameserver, void *data) +{ + nameserver->data = data; +} + +/*----------------------------------------------------------------------------*/ + +void knot_ns_destroy(knot_nameserver_t **nameserver) +{ + synchronize_rcu(); + + free((*nameserver)->err_response); + if ((*nameserver)->opt_rr != NULL) { + knot_edns_free(&(*nameserver)->opt_rr); + } + + // destroy the zone db + knot_zonedb_deep_free(&(*nameserver)->zone_db); + + free(*nameserver); + *nameserver = NULL; +} diff --git a/src/libknot/nameserver/name-server.h b/src/libknot/nameserver/name-server.h new file mode 100644 index 0000000..0d93df6 --- /dev/null +++ b/src/libknot/nameserver/name-server.h @@ -0,0 +1,358 @@ +/*! + * \file name-server.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * Contains the "name server" structure and interface for the main DNS + * functions. Currently only supports answering simple queries, without any + * extensions. + * + * \todo Consider saving pointer to the zdb_find_name() function in the + * nameserver structure. Probably not needed, these modules can be + * inter-connected. + * \todo Provide interface for other DNS functions - zone transfers, dynamic + * updates, etc. + * + * \addtogroup query_processing + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_NAME_SERVER_H_ +#define _KNOT_NAME_SERVER_H_ + +#include <stdint.h> +#include <string.h> + +#include "zone/zonedb.h" +#include "edns.h" +#include "consts.h" +#include "tsig.h" +#include "packet/packet.h" +#include "common/sockaddr.h" +#include "updates/changesets.h" + +struct conf_t; +struct server_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Name server structure. Holds all important data needed for the + * supported DNS functions. + * + * Currently only holds pointer to the zone database for answering queries. + */ +typedef struct knot_nameserver { + /*! + * \brief Pointer to the zone database structure used for answering + * queries. + */ + knot_zonedb_t *zone_db; + uint8_t *err_response; /*!< Prepared generic error response. */ + size_t err_resp_size; /*!< Size of the prepared error response. */ + knot_opt_rr_t *opt_rr; /*!< OPT RR with the server's EDNS0 info. */ + + void *data; +} knot_nameserver_t; + +/*! \brief Callback for sending one packet back through a TCP connection. */ +typedef int (*xfr_callback_t)(int session, sockaddr_t *addr, + uint8_t *packet, size_t size); + +/*! + * \brief Single XFR operation structure. + * + * Used for communication with XFR handler. + */ +typedef struct knot_ns_xfr { + int type; + int flags; + sockaddr_t addr; + knot_packet_t *query; + knot_packet_t *response; + xfr_callback_t send; + int session; + + /*! + * XFR-out: Output buffer. + * XFR-in: Buffer for query or incoming packet. + */ + uint8_t *wire; + + /*! + * XFR-out: Size of the output buffer. + * XFR-in: Size of the current packet. + */ + size_t wire_size; + void *data; + knot_zone_t *zone; + void *owner; + + /*! \note [TSIG] TSIG fields */ + /*! \brief Message(s) to sign in wireformat. + * + * This field should be allocated at the start of transfer and + * freed at the end. During the transfer it is only rewritten. + */ + uint8_t *tsig_data; + size_t tsig_data_size; /*!< Size of the message(s) in bytes */ +// const knot_rrset_t *tsig; /*!< Response TSIG. +// \todo [TSIG] Replace with separate data. */ + size_t tsig_size; /*!< Size of the TSIG RR wireformat in bytes.*/ + knot_key_t *tsig_key; /*!< Associated TSIG key for signing. */ + + uint8_t *digest; /*!< Buffer for counting digest. */ + size_t digest_size; /*!< Size of the digest. */ + size_t digest_max_size; /*!< Size of the buffer. */ + + /*! \brief Previous digest or request digest. + * + * Should be allocated before the transfer (known size). + */ +// uint8_t *prev_digest; +// size_t prev_digest_size; /*!< Size of previous digest in bytes. */ + + /*! + * \brief Number of the packet currently assembled. + * + * In case of XFR-in, this is not the overall number of packet, just + * number counted from last TSIG check. + */ + int packet_nr; +} knot_ns_xfr_t; + + +static const int KNOT_NS_TSIG_FREQ = 100; + +static const size_t KNOT_NS_TSIG_DATA_MAX_SIZE = 100 * 64 * 1024; + +/*! + * \brief XFR request flags. + */ +enum knot_ns_xfr_flag_t { + XFR_FLAG_TCP = 1 << 0, /*!< XFR request is on TCP. */ + XFR_FLAG_UDP = 1 << 1 /*!< XFR request is on UDP. */ +}; + +/*! + * \brief XFR request types. + */ +typedef enum knot_ns_xfr_type_t { + /* Special events. */ + XFR_TYPE_CLOSE = -1, /*!< Close connection event. */ + + /* DNS events. */ + XFR_TYPE_AIN = 0, /*!< AXFR-IN request (start transfer). */ + XFR_TYPE_AOUT, /*!< AXFR-OUT request (incoming transfer). */ + XFR_TYPE_IIN, /*!< IXFR-IN request (start transfer). */ + XFR_TYPE_IOUT, /*!< IXFR-OUT request (incoming transfer). */ + XFR_TYPE_SOA, /*!< Pending SOA request. */ + XFR_TYPE_NOTIFY /*!< Pending NOTIFY query. */ +} knot_ns_xfr_type_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Allocates and initializes the name server structure. + * + * \return Pointer to the name server structure. + */ +knot_nameserver_t *knot_ns_create(); + +/*! + * \brief Parses the given query into the response structure and recognizes + * type of the query. + * + * Some query types are distinguished by OPCODE (NOTIFY, UPDATE, etc.), some + * by QTYPE (AXFR, IXFR). As these information are needed on the same layer + * to decide what to do with the query, the knot_query_t type is used for this + * purpose. + * + * \param query_wire Wire format of the query. + * \param qsize Size of the query in octets. + * \param packet Packet structure to be filled with the parsed query. + * \param type Type of the query. + * + * \retval KNOT_EOK + * \retval KNOT_EMALF if the query is totally unusable. Such query must be + * ignored. + * \retval KNOT_RCODE_SERVFAIL if there was some internal error. Call + * ns_error_response() with \a rcode set to this + * value to get proper error response. + * \retval KNOT_RCODE_FORMERR if the query was malformed, but can be used to + * construct an error response. Call + * ns_error_response() with \a rcode set to this + * value to get proper error response. + * \retval KNOT_RCODE_NOTIMPL if the query has an unsupported type. Call + * ns_error_response() with \a rcode set to this + * value to get proper error response. + */ +int knot_ns_parse_packet(const uint8_t *query_wire, size_t qsize, + knot_packet_t *packet, knot_packet_type_t *type); + +/*! + * \brief Prepares wire format of an error response using generic error template + * stored in the nameserver structure. + * + * The error response will not contain the Question section from the query, just + * a header with ID copied from the query and the given RCODE. + * + * \param nameserver Nameserver structure containing the error template. + * \param query_id ID of the query. + * \param rcode RCODE to set in the response. + * \param response_wire Place for wire format of the response. + * \param rsize Size of the error response will be stored here. + */ +void knot_ns_error_response(const knot_nameserver_t *nameserver, uint16_t query_id, + uint8_t rcode, uint8_t *response_wire, size_t *rsize); + +/*! + * \brief Creates a response for the given normal query using the data of the + * nameserver. + * + * \param nameserver Name server structure to provide the needed data. + * \param resp Response structure with parsed query. + * \param response_wire Place for the response in wire format. + * \param rsize Input: maximum acceptable size of the response. Output: real + * size of the response. + * + * \retval KNOT_EOK if a valid response was created. + * \retval KNOT_EMALF if an error occured and the response is not valid. + */ +int knot_ns_answer_normal(knot_nameserver_t *nameserver, knot_packet_t *query, + uint8_t *response_wire, size_t *rsize); + +int knot_ns_init_xfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr); + +/*! + * \brief Compares two zone serials. + * + * \retval < 0 if s1 is less than s2. + * \retval > 0 if s1 is larger than s2. + * \retval == 0 if s1 is equal to s2. + */ +int ns_serial_compare(uint32_t s1, uint32_t s2); + +int ns_ixfr_load_serials(const knot_ns_xfr_t *xfr, uint32_t *serial_from, + uint32_t *serial_to); + +int knot_ns_xfr_send_error(const knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr, knot_rcode_t rcode); + +/*! + * \brief Processes an AXFR query. + * + * This function sequentially creates DNS packets to be sent as a response + * to the AXFR query and sends each packet using the given callback (\a + * send_packet). + * + * \param nameserver Name server structure to provide the data for answering. + * \param xfr Persistent transfer-specific data. + * + * \note Currently only a stub which sends one error response using the given + * callback. + * + * \retval KNOT_EOK + * \retval KNOT_EINVAL + * \retval KNOT_ENOMEM + * \retval KNOT_ERROR + * + * \todo Maybe the place for the wire format should be passed in as in + * the ns_answer_request() function...? + */ +int knot_ns_answer_axfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr); + +/*! + * \brief Processes an IXFR query. + * + * \param nameserver Name server structure to provide the data for answering. + * \param xfr Persistent transfer-specific data. + * + * \todo Document properly. + */ +int knot_ns_answer_ixfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr); + +/*! + * \brief Processes an AXFR-IN packet. + * + * \param nameserver Name server structure to provide the data for answering. + * \param xfr Persistent transfer-specific data. + * + * \todo Document me. + */ +int knot_ns_process_axfrin(knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr); + +int knot_ns_switch_zone(knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr); + +/*! + * \brief Processes an IXFR-IN packet. + * + * \param nameserver Name server structure to provide the data for answering. + * \param xfr Persistent transfer-specific data. + * + * \retval KNOT_EOK If this packet was processed successfuly and another packet + * is expected. (RFC1995bis, case c) + * \retval KNOT_ENOXFR If the transfer is not taking place because server's + * SERIAL is the same as this client's SERIAL. The client + * should close the connection and do no further processing. + * (RFC1995bis case a). + * \retval KNOT_EAGAIN If the server could not fit the transfer into the packet. + * This should happen only if UDP was used. In this case + * the client should retry the request via TCP. If UDP was + * not used, it should be considered that the transfer was + * malformed and the connection should be closed. + * (RFC1995bis case b). + * \retval >0 Transfer successully finished. Changesets are created and furter + * processing is needed. + * \retval Other If any other error occured. The connection should be closed. + * + * \todo Document me. + */ +int knot_ns_process_ixfrin(knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr); + +int knot_ns_process_update(knot_nameserver_t *nameserver, knot_packet_t *query, + uint8_t *response_wire, size_t *rsize, + knot_zone_t **zone, knot_changeset_t **changeset); + +int knot_ns_create_forward_query(const knot_packet_t *query, + uint8_t *query_wire, size_t *size); + +int knot_ns_process_forward_response(const knot_packet_t *response, + uint16_t original_id, + uint8_t *response_wire, size_t *size); + +void *knot_ns_data(knot_nameserver_t *nameserver); + +void *knot_ns_get_data(knot_nameserver_t *nameserver); + +void knot_ns_set_data(knot_nameserver_t *nameserver, void *data); + +int knot_ns_tsig_required(int packet_nr); + +/*! + * \brief Properly destroys the name server structure. + * + * \param nameserver Nameserver to destroy. + */ +void knot_ns_destroy(knot_nameserver_t **nameserver); + + +#endif /* _KNOTNAME_SERVER_H_ */ + +/*! @} */ diff --git a/src/libknot/nsec3.c b/src/libknot/nsec3.c new file mode 100644 index 0000000..303d2e6 --- /dev/null +++ b/src/libknot/nsec3.c @@ -0,0 +1,265 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <assert.h> +#include <stdlib.h> +#include <sys/time.h> + +#include <openssl/evp.h> +#include <openssl/sha.h> + +#include "nsec3.h" +#include "common.h" +#include "util/descriptor.h" +#include "util/utils.h" +#include "util/tolower.h" +#include "util/error.h" +#include "util/debug.h" + +/*----------------------------------------------------------------------------*/ + +int knot_nsec3_params_from_wire(knot_nsec3_params_t *params, + const knot_rrset_t *nsec3param) +{ + if (params == NULL || nsec3param == NULL) { + return KNOT_EBADARG; + } + + assert(knot_rrset_type(nsec3param) == KNOT_RRTYPE_NSEC3PARAM); + const knot_rdata_t *rdata = knot_rrset_rdata(nsec3param); + + assert(rdata->count == 4); + + params->algorithm = *(uint8_t *) + (&knot_rdata_item(rdata, 0)->raw_data[1]); + params->flags = *(uint8_t *) + (&knot_rdata_item(rdata, 1)->raw_data[1]); + params->iterations = knot_wire_read_u16( + (uint8_t *)(knot_rdata_item(rdata, 2)->raw_data + 1)); + + params->salt_length = + ((uint8_t *)knot_rdata_item(rdata, 3)->raw_data)[2]; + + if (params->salt_length > 0) { + params->salt = (uint8_t *)malloc(params->salt_length); + CHECK_ALLOC_LOG(params->salt, -1); + memcpy(params->salt, + (uint8_t *)knot_rdata_item(rdata, 3)->raw_data + 3, + params->salt_length); + } else { + params->salt = NULL; + } + + dbg_nsec3("Parsed NSEC3PARAM:\n"); + dbg_nsec3("Algorithm: %hu\n", params->algorithm); + dbg_nsec3("Flags: %hu\n", params->flags); + dbg_nsec3("Iterations: %hu\n", params->iterations); + dbg_nsec3("Salt length: %hu\n", params->salt_length); + dbg_nsec3("Salt: "); + if (params->salt != NULL) { + dbg_nsec3_hex((char *)params->salt, + params->salt_length); + dbg_nsec3("\n"); + } else { + dbg_nsec3("none\n"); + } + + return KNOT_EOK; +} + +static uint8_t *knot_nsec3_to_lowercase(const uint8_t *data, size_t size) +{ + uint8_t *out = (uint8_t *)malloc(size); + CHECK_ALLOC_LOG(out, NULL); + + for (int i = 0; i < size; ++i) { + out[i] = knot_tolower(data[i]); + } + + return out; +} + +/*----------------------------------------------------------------------------*/ +#if KNOT_NSEC3_SHA_USE_EVP +int knot_nsec3_sha1(const knot_nsec3_params_t *params, + const uint8_t *data, size_t size, uint8_t **digest, + size_t *digest_size) +{ + if (digest == NULL || digest_size == NULL || data == NULL) { + return KNOT_EBADARG; + } + + uint8_t *salt = params->salt; + uint8_t salt_length = params->salt_length; + uint16_t iterations = params->iterations; + + EVP_MD_CTX mdctx; + EVP_MD_CTX_init(&mdctx); + + *digest = (uint8_t *)malloc(EVP_MD_size(EVP_sha1())); + if (*digest == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + + uint8_t *data_low = knot_nsec3_to_lowercase(data, size); + if (data_low == NULL) { + free(*digest); + return -1; + } + + const uint8_t *in = data_low; + unsigned in_size = size; + + int res = 0; + +#ifdef KNOT_NSEC3_DEBUG + unsigned long long total_time = 0; + unsigned long calls = 0; + long time = 0; +#endif + + for (int i = 0; i <= iterations; ++i) { +#ifdef KNOT_NSEC3_DEBUG + perf_begin(); +#endif + + EVP_DigestInit_ex(&mdctx, EVP_sha1(), NULL); + + res = EVP_DigestUpdate(&mdctx, in, in_size); + + if (salt_length > 0) { + res = EVP_DigestUpdate(&mdctx, salt, salt_length); + } + + EVP_DigestFinal_ex(&mdctx, *digest, digest_size); + in = *digest; + in_size = *digest_size; + +#ifdef KNOT_NSEC3_DEBUG + perf_end(time); + total_time += time; + ++calls; +#endif + + if (res != 1) { + dbg_nsec3("Error calculating SHA-1 hash.\n"); + free(data_low); + free(*digest); + return -2; + } + } + + EVP_MD_CTX_cleanup(&mdctx); + + dbg_nsec3("NSEC3 hashing: calls: %lu, avg time per call: %f." + "\n", calls, (double)(total_time) / calls); + + free(data_low); + return 0; +} + +/*----------------------------------------------------------------------------*/ +#else + +int knot_nsec3_sha1(const knot_nsec3_params_t *params, + const uint8_t *data, size_t size, uint8_t **digest, + size_t *digest_size) +{ + if (params == NULL || digest == NULL || digest_size == NULL + || data == NULL) { + return KNOT_EBADARG; + } + + uint8_t *salt = params->salt; + uint8_t salt_length = params->salt_length; + uint16_t iterations = params->iterations; + + dbg_nsec3("Hashing: \n"); + dbg_nsec3(" Data: %.*s \n", size, data); + dbg_nsec3_hex((const char *)data, size); + dbg_nsec3(" (size %d)\n Iterations: %u\n", (int)size, iterations); + dbg_nsec3(" Salt length: %u\n", salt_length); + dbg_nsec3(" Salt: "); + if (salt_length > 0) { + dbg_nsec3_hex((char *)salt, salt_length); + dbg_nsec3("\n"); + } else { + dbg_nsec3("none\n"); + } + + SHA_CTX ctx; + + *digest = (uint8_t *)malloc(SHA_DIGEST_LENGTH); + if (*digest == NULL) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + uint8_t *data_low = knot_nsec3_to_lowercase(data, size); + if (data_low == NULL) { + free(*digest); + return KNOT_ENOMEM; + } + + const uint8_t *in = data_low; + unsigned in_size = size; + + int res = 0; + + // other iterations + for (int i = 0; i <= iterations; ++i) { + SHA1_Init(&ctx); + + res = SHA1_Update(&ctx, in, in_size); + + if (salt_length > 0) { + res = SHA1_Update(&ctx, salt, salt_length); + } + + SHA1_Final(*digest, &ctx); + + in = *digest; + in_size = SHA_DIGEST_LENGTH; + + if (res != 1) { + dbg_nsec3("Error calculating SHA-1 hash.\n"); + free(data_low); + free(*digest); + return KNOT_ECRYPTO; + } + } + + *digest_size = SHA_DIGEST_LENGTH; + + dbg_nsec3("Hash: %.*s\n", *digest_size, *digest); + dbg_nsec3_hex((const char *)*digest, *digest_size); + dbg_nsec3("\n"); + + free(data_low); + return KNOT_EOK; +} +#endif + +/*----------------------------------------------------------------------------*/ + +void knot_nsec3_params_free(knot_nsec3_params_t *params) +{ + if (params->salt != NULL) { + free(params->salt); + } +} diff --git a/src/libknot/nsec3.h b/src/libknot/nsec3.h new file mode 100644 index 0000000..0ce6899 --- /dev/null +++ b/src/libknot/nsec3.h @@ -0,0 +1,92 @@ +/*! + * \file nsec3.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Functions for calcularing NSEC3 hashes. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_NSEC3_H_ +#define _KNOT_NSEC3_H_ + +#include <stdint.h> +#include <string.h> + +#include "rrset.h" + +#define KNOT_NSEC3_SHA_USE_EVP 0 + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure representing the NSEC3PARAM resource record. + */ +struct knot_nsec3_params { + uint8_t algorithm; /*!< Hash algorithm. */ + uint8_t flags; /*!< Flags. */ + uint16_t iterations; /*!< Additional iterations of the hash function.*/ + uint8_t salt_length; /*!< Length of the salt field in bytes. */ + uint8_t *salt; /*!< Salt used in hashing. */ +}; + +typedef struct knot_nsec3_params knot_nsec3_params_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Initializes the NSEC3PARAM structure. + * + * \param params NSEC3PARAM structure to initialize. + * \param nsec3param The NSEC3PARAM RRset. + * + * \retval KNOT_EOK on success (always). + */ +int knot_nsec3_params_from_wire(knot_nsec3_params_t *params, + const knot_rrset_t *nsec3param); + +/*! + * \brief Hashes the given data using the SHA1 hash and the given parameters. + * + * \param[in] params NSEC3PARAM structure with the required parameters for + * hashing. + * \param[in] data Data to hash. + * \param[in] size Size of the data in bytes. + * \param[out] digest Result will be store here. + * \param[out] digest_size Size of the result in octets will be stored here. + * + * \retval KNOT_EOK if successful. + * \retval KNOT_ENOMEM + * \retval KNOT_EBADARG + * \retval KNOT_ECRYPTO + */ +int knot_nsec3_sha1(const knot_nsec3_params_t *params, const uint8_t *data, + size_t size, uint8_t **digest, size_t *digest_size); + +/*! + * \brief Properly cleans up (but does not deallocate) the NSEC3PARAM structure. + * + * \param params NSEC3PARAMS structure to clean up. + */ +void knot_nsec3_params_free(knot_nsec3_params_t *params); + +/*----------------------------------------------------------------------------*/ + +#endif /* _KNOT_NSEC3_H_ */ + +/*! @} */ diff --git a/src/libknot/packet/packet.c b/src/libknot/packet/packet.c new file mode 100644 index 0000000..82e818f --- /dev/null +++ b/src/libknot/packet/packet.c @@ -0,0 +1,1532 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "packet/packet.h" +#include "util/error.h" +#include "util/debug.h" +#include "common.h" +#include "util/descriptor.h" +#include "util/wire.h" + +/*----------------------------------------------------------------------------*/ + +#define DEFAULT_RRCOUNT_QUERY(type) DEFAULT_##type##COUNT_QUERY +#define DEFAULT_RRCOUNT(type) DEFAULT_##type##COUNT + +#define DEFAULT_RRSET_COUNT(type, packet) \ + ((packet->prealloc_type == KNOT_PACKET_PREALLOC_NONE) \ + ? 0 \ + : (packet->prealloc_type == KNOT_PACKET_PREALLOC_QUERY) \ + ? DEFAULT_##type##_QUERY \ + : DEFAULT_##type) + + + +typedef enum { + KNOT_PACKET_DUPL_IGNORE, + KNOT_PACKET_DUPL_SKIP, + KNOT_PACKET_DUPL_MERGE +} knot_packet_duplicate_handling_t; +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets all the pointers in the packet structure to the respective + * parts of the pre-allocated space. + */ +static void knot_packet_init_pointers_response(knot_packet_t *pkt) +{ + dbg_packet("Packet pointer: %p\n", pkt); + + char *pos = (char *)pkt + PREALLOC_PACKET; + + // put QNAME directly after the structure + pkt->question.qname = (knot_dname_t *)pos; + pos += PREALLOC_QNAME_DNAME; + + dbg_packet("QNAME: %p\n", pkt->question.qname); + + pkt->question.qname->name = (uint8_t *)pos; + pos += PREALLOC_QNAME_NAME; + pkt->question.qname->labels = (uint8_t *)pos; + pos += PREALLOC_QNAME_LABELS; + + pkt->owner_tmp = (uint8_t *)pos; + dbg_packet("Tmp owner: %p\n", pkt->owner_tmp); + pos += PREALLOC_RR_OWNER; + + // then answer, authority and additional sections + if (DEFAULT_ANCOUNT == 0) { + pkt->answer = NULL; + } else { + pkt->answer = (const knot_rrset_t **)pos; + pos += DEFAULT_ANCOUNT * sizeof(const knot_rrset_t *); + } + + if (DEFAULT_NSCOUNT == 0) { + pkt->authority = NULL; + } else { + pkt->authority = (const knot_rrset_t **)pos; + pos += DEFAULT_NSCOUNT * sizeof(const knot_rrset_t *); + } + + if (DEFAULT_ARCOUNT == 0) { + pkt->additional = NULL; + } else { + pkt->additional = (const knot_rrset_t **)pos; + pos += DEFAULT_ARCOUNT * sizeof(const knot_rrset_t *); + } + + dbg_packet("Answer section: %p\n", pkt->answer); + dbg_packet("Authority section: %p\n", pkt->authority); + dbg_packet("Additional section: %p\n", pkt->additional); + + pkt->max_an_rrsets = DEFAULT_ANCOUNT; + pkt->max_ns_rrsets = DEFAULT_NSCOUNT; + pkt->max_ar_rrsets = DEFAULT_ARCOUNT; + + // then domain names for compression and offsets + pkt->compression.dnames = (const knot_dname_t **)pos; + pos += DEFAULT_DOMAINS_IN_RESPONSE * sizeof(const knot_dname_t *); + pkt->compression.offsets = (size_t *)pos; + pos += DEFAULT_DOMAINS_IN_RESPONSE * sizeof(size_t); + + dbg_packet("Compression dnames: %p\n", pkt->compression.dnames); + dbg_packet("Compression offsets: %p\n", pkt->compression.offsets); + + pkt->compression.max = DEFAULT_DOMAINS_IN_RESPONSE; + + pkt->tmp_rrsets = (const knot_rrset_t **)pos; + pos += DEFAULT_TMP_RRSETS * sizeof(const knot_rrset_t *); + + dbg_packet("Tmp rrsets: %p\n", pkt->tmp_rrsets); + + pkt->tmp_rrsets_max = DEFAULT_TMP_RRSETS; + + assert((char *)pos == (char *)pkt + PREALLOC_RESPONSE); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets all the pointers in the packet structure to the respective + * parts of the pre-allocated space. + */ +static void knot_packet_init_pointers_query(knot_packet_t *pkt) +{ + dbg_packet("Packet pointer: %p\n", pkt); + + char *pos = (char *)pkt + PREALLOC_PACKET; + + // put QNAME directly after the structure + pkt->question.qname = (knot_dname_t *)pos; + pos += PREALLOC_QNAME_DNAME; + + dbg_packet("QNAME: %p (%zu after start of packet)\n", + pkt->question.qname, + (void *)pkt->question.qname - (void *)pkt); + + pkt->question.qname->name = (uint8_t *)pos; + pos += PREALLOC_QNAME_NAME; + pkt->question.qname->labels = (uint8_t *)pos; + pos += PREALLOC_QNAME_LABELS; + +// pkt->owner_tmp = (uint8_t *)((char *)pkt->question.qname->labels +// + PREALLOC_QNAME_LABELS); + + // then answer, authority and additional sections + if (DEFAULT_ANCOUNT_QUERY == 0) { + pkt->answer = NULL; + } else { + pkt->answer = (const knot_rrset_t **)pos; + pos += DEFAULT_ANCOUNT_QUERY * sizeof(const knot_rrset_t *); + } + + if (DEFAULT_NSCOUNT_QUERY == 0) { + pkt->authority = NULL; + } else { + pkt->authority = (const knot_rrset_t **)pos; + pos += DEFAULT_NSCOUNT_QUERY * sizeof(const knot_rrset_t *); + } + + if (DEFAULT_ARCOUNT_QUERY == 0) { + pkt->additional = NULL; + } else { + pkt->additional = (const knot_rrset_t **)pos; + pos += DEFAULT_ARCOUNT_QUERY * sizeof(const knot_rrset_t *); + } + + dbg_packet("Answer section: %p\n", pkt->answer); + dbg_packet("Authority section: %p\n", pkt->authority); + dbg_packet("Additional section: %p\n", pkt->additional); + + pkt->max_an_rrsets = DEFAULT_ANCOUNT_QUERY; + pkt->max_ns_rrsets = DEFAULT_NSCOUNT_QUERY; + pkt->max_ar_rrsets = DEFAULT_ARCOUNT_QUERY; + + pkt->tmp_rrsets = (const knot_rrset_t **)pos; + pos += DEFAULT_TMP_RRSETS_QUERY * sizeof(const knot_rrset_t *); + + dbg_packet("Tmp rrsets: %p\n", pkt->tmp_rrsets); + + pkt->tmp_rrsets_max = DEFAULT_TMP_RRSETS_QUERY; + +// dbg_packet("End of data: %p (%zu after start of packet)\n", +// pkt->tmp_rrsets + DEFAULT_TMP_RRSETS_QUERY, +// (void *)(pkt->tmp_rrsets + DEFAULT_TMP_RRSETS_QUERY) +// - (void *)pkt); + dbg_packet("Allocated total: %u\n", PREALLOC_QUERY); + + assert(pos == (char *)pkt + PREALLOC_QUERY); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Parses DNS header from the wire format. + * + * \note This function also adjusts the position (\a pos) and size of remaining + * bytes in the wire format (\a remaining) according to what was parsed + * (though it actually always parses the 12 bytes of the header). + * + * \param[in,out] pos Wire format to parse the header from. + * \param[in,out] remaining Remaining size of the wire format. + * \param[out] header Header structure to fill in. + * + * \retval KNOT_EOK + * \retval KNOT_EFEWDATA + */ +static int knot_packet_parse_header(const uint8_t *wire, size_t *pos, + size_t size, knot_header_t *header) +{ + assert(wire != NULL); + assert(pos != NULL); + assert(header != NULL); + + if (size - *pos < KNOT_WIRE_HEADER_SIZE) { + dbg_response("Not enough data to parse header.\n"); + return KNOT_EFEWDATA; + } + + header->id = knot_wire_get_id(wire); + // copy some of the flags: OPCODE and RD + // do this by copying flags1 and setting QR to 1, AA to 0 and TC to 0 + header->flags1 = knot_wire_get_flags1(wire); +// knot_wire_flags_set_qr(&header->flags1); +// knot_wire_flags_clear_aa(&header->flags1); +// knot_wire_flags_clear_tc(&header->flags1); + // do not copy flags2 (all set by server) + header->qdcount = knot_wire_get_qdcount(wire); + header->ancount = knot_wire_get_ancount(wire); + header->nscount = knot_wire_get_nscount(wire); + header->arcount = knot_wire_get_arcount(wire); + + *pos += KNOT_WIRE_HEADER_SIZE; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Parses DNS Question entry from the wire format. + * + * \note This function also adjusts the position (\a pos) and size of remaining + * bytes in the wire format (\a remaining) according to what was parsed. + * + * \param[in,out] pos Wire format to parse the Question from. + * \param[in,out] remaining Remaining size of the wire format. + * \param[out] question DNS Question structure to be filled. + * + * \retval KNOT_EOK + * \retval KNOT_EFEWDATA + * \retval KNOT_ENOMEM + */ +static int knot_packet_parse_question(const uint8_t *wire, size_t *pos, + size_t size, + knot_question_t *question, int alloc) +{ + assert(pos != NULL); + assert(wire != NULL); + assert(question != NULL); + + if (size - *pos < KNOT_WIRE_QUESTION_MIN_SIZE) { + dbg_response("Not enough data to parse question.\n"); + return KNOT_EFEWDATA; // malformed + } + + dbg_response("Parsing Question starting on position %zu.\n", + *pos); + + // domain name must end with 0, so just search for 0 + int i = *pos; + while (i < size && wire[i] != 0) { + ++i; + } + + if (size - i - 1 < 4) { + dbg_response("Not enough data to parse question.\n"); + return KNOT_EFEWDATA; // no 0 found or not enough data left + } + + dbg_response("Parsing dname starting on position %zu and " + "%zu bytes long.\n", *pos, i - *pos + 1); + dbg_response("Alloc: %d\n", alloc); + if (alloc) { + question->qname = knot_dname_new_from_wire( + wire + *pos, i - *pos + 1, NULL); + if (question->qname == NULL) { + return KNOT_ENOMEM; + } + } else { + int res = knot_dname_from_wire(wire + *pos, i - *pos + 1, + NULL, question->qname); + if (res != KNOT_EOK) { + assert(res != KNOT_EBADARG); + return res; + } + } + + *pos = i + 1; + question->qtype = knot_wire_read_u16(wire + i + 1); + //*pos += 2; + question->qclass = knot_wire_read_u16(wire + i + 3); + //*pos += 2; + + *pos += 4; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Reallocate space for RRSets. + * + * \param rrsets Space for RRSets. + * \param max_count Size of the space available for the RRSets. + * \param default_max_count Size of the space pre-allocated for the RRSets when + * the response structure was initialized. + * \param step How much the space should be increased. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +static int knot_packet_realloc_rrsets(const knot_rrset_t ***rrsets, + short *max_count, + short default_max_count, short step) +{ + dbg_packet("Max count: %d, default max count: %d\n", + *max_count, default_max_count); + int free_old = (*max_count) != default_max_count; + const knot_rrset_t **old = *rrsets; + + short new_max_count = *max_count + step; + const knot_rrset_t **new_rrsets = (const knot_rrset_t **)malloc( + new_max_count * sizeof(knot_rrset_t *)); + CHECK_ALLOC_LOG(new_rrsets, KNOT_ENOMEM); + + memcpy(new_rrsets, *rrsets, (*max_count) * sizeof(knot_rrset_t *)); + + *rrsets = new_rrsets; + *max_count = new_max_count; + + if (free_old) { + free(old); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static knot_rdata_t *knot_packet_parse_rdata(const uint8_t *wire, + size_t *pos, size_t total_size, size_t rdlength, + const knot_rrtype_descriptor_t *desc) +{ +// if (desc->type == 0) { +// dbg_packet("Unknown RR type.\n"); +// return NULL; +// } + + knot_rdata_t *rdata = knot_rdata_new(); + if (rdata == NULL) { + return NULL; + } + + int rc = knot_rdata_from_wire(rdata, wire, pos, total_size, rdlength, + desc); + + if (rc != KNOT_EOK) { + dbg_packet("rdata_from_wire() returned: %s\n", + knot_strerror(rc)); + knot_rdata_free(&rdata); + return NULL; + } + + return rdata; +} + +/*----------------------------------------------------------------------------*/ + +static knot_rrset_t *knot_packet_parse_rr(const uint8_t *wire, size_t *pos, + size_t size) +{ +// knot_rrset_t *rrset = +// (knot_rrset_t *)malloc(sizeof(knot_rrset_t)); +// CHECK_ALLOC_LOG(rrset, NULL); + + dbg_packet("Parsing RR from position: %zu, total size: %zu\n", + *pos, size); + + knot_dname_t *owner = knot_dname_parse_from_wire(wire, pos, size, + NULL); + dbg_packet("Created owner: %p, actual position: %zu\n", owner, + *pos); + if (owner == NULL) { + return NULL; + } + +dbg_packet_exec( + char *name = knot_dname_to_str(owner); + dbg_packet("Parsed name: %s\n", name); + free(name); +); + + //*remaining -= knot_dname_size(rrset->owner); + + /*! @todo Get rid of the numerical constant. */ + if (size - *pos < 10) { + dbg_packet("Malformed RR: Not enough data to parse RR" + " header.\n"); + knot_dname_release(owner); + return NULL; + } + + dbg_packet("Reading type from position %zu\n", *pos); + + uint16_t type = knot_wire_read_u16(wire + *pos); + uint16_t rclass = knot_wire_read_u16(wire + *pos + 2); + uint32_t ttl = knot_wire_read_u32(wire + *pos + 4); + + knot_rrset_t *rrset = knot_rrset_new(owner, type, rclass, ttl); + + /* Owner is either referenced in rrset or rrset creation failed. */ + knot_dname_release(owner); + + /* Check rrset allocation. */ + if (rrset == NULL) { + return NULL; + } + + uint16_t rdlength = knot_wire_read_u16(wire + *pos + 8); + + dbg_packet("Read RR header: type %u, class %u, ttl %u, " + "rdlength %u\n", rrset->type, rrset->rclass, + rrset->ttl, rdlength); + + *pos += 10; + + if (size - *pos < rdlength) { + dbg_packet("Malformed RR: Not enough data to parse RR" + " RDATA (size: %zu, position: %zu).\n", + size, *pos); + knot_rrset_deep_free(&rrset, 1, 1, 0); +// free(rrset); + return NULL; + } + + rrset->rrsigs = NULL; + + if (rdlength == 0) { + return rrset; + } + + // parse RDATA + knot_rdata_t *rdata = knot_packet_parse_rdata(wire, pos, size, + rdlength, + knot_rrtype_descriptor_by_type(rrset->type)); + if (rdata == NULL) { + dbg_packet("Malformed RR: Could not parse RDATA.\n"); + knot_rrset_deep_free(&rrset, 1, 1, 0); +// free(rrset); + return NULL; + } + + if (knot_rrset_add_rdata(rrset, rdata) != KNOT_EOK) { + dbg_packet("Malformed RR: Could not add RDATA to RRSet" + ".\n"); + knot_rdata_free(&rdata); + knot_rrset_deep_free(&rrset, 1, 1, 0); +// free(rrset); + return NULL; + } + + return rrset; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_packet_add_rrset(knot_rrset_t *rrset, + const knot_rrset_t ***rrsets, + short *rrset_count, + short *max_rrsets, + short default_rrsets, + const knot_packet_t *packet, + knot_packet_duplicate_handling_t dupl) +{ + + assert(rrset != NULL); + assert(rrsets != NULL); + assert(rrset_count != NULL); + assert(max_rrsets != NULL); + +dbg_packet_exec( + char *name = knot_dname_to_str(rrset->owner); + dbg_packet("packet_add_rrset(), owner: %s, type: %s\n", + name, knot_rrtype_to_string(rrset->type)); + free(name); +); + + if (*rrset_count == *max_rrsets + && knot_packet_realloc_rrsets(rrsets, max_rrsets, default_rrsets, + STEP_ANCOUNT) != KNOT_EOK) { + return KNOT_ENOMEM; + } + + if (dupl == KNOT_PACKET_DUPL_SKIP && + knot_packet_contains(packet, rrset, KNOT_RRSET_COMPARE_PTR)) { + /*! \todo This should also return > 0, as it means that the + RRSet was not used actually. */ + return KNOT_EOK; + } + + if (dupl == KNOT_PACKET_DUPL_MERGE) { + // try to find the RRSet in this array of RRSets + for (int i = 0; i < *rrset_count; ++i) { + +dbg_packet_exec( + char *name = knot_dname_to_str((*rrsets)[i]->owner); + dbg_packet("Comparing to RRSet: owner: %s, " + "type: %s\n", name, + knot_rrtype_to_string( + (*rrsets)[i]->type)); + free(name); +); + + if (knot_rrset_compare((*rrsets)[i], rrset, + KNOT_RRSET_COMPARE_HEADER)) { + //const knot_rrset_t *r = (*rrsets) + /*! \todo Test this!!! */ + int rc = knot_rrset_merge( + (void **)((*rrsets) + i), (void **)&rrset); + if (rc != KNOT_EOK) { + return rc; + } + return 1; + } + } + } + + (*rrsets)[*rrset_count] = rrset; + ++(*rrset_count); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_packet_parse_rrs(const uint8_t *wire, size_t *pos, + size_t size, uint16_t rr_count, + const knot_rrset_t ***rrsets, + short *rrset_count, short *max_rrsets, + short default_rrsets, + knot_packet_t *packet) +{ + assert(pos != NULL); + assert(wire != NULL); + assert(rrsets != NULL); + assert(rrset_count != NULL); + assert(max_rrsets != NULL); + assert(packet != NULL); + + dbg_packet("Parsing RRSets starting on position: %zu\n", + *pos); + +// if (*rrsets == NULL) { +// knot_packet_realloc_rrsets(rrsets, max_rrsets, 0, 1); +// } + + /* + * The RRs from one RRSet may be scattered in the current section. + * We must parse all RRs separately and try to add them to already + * parsed RRSets. + */ + int err = KNOT_EOK; + knot_rrset_t *rrset = NULL; + + for (int i = 0; i < rr_count; ++i) { + rrset = knot_packet_parse_rr(wire, pos, size); + if (rrset == NULL) { + dbg_packet("Failed to parse RR!\n"); + err = KNOT_EMALF; + break; + } + + err = knot_packet_add_rrset(rrset, rrsets, rrset_count, + max_rrsets, default_rrsets, packet, + KNOT_PACKET_DUPL_MERGE); + if (err < 0) { + break; + } else if (err > 0) { // merged + dbg_packet("RRSet merged, freeing.\n"); + knot_rrset_deep_free(&rrset, 1, 0, 0); // TODO: ok?? + continue; + } + + err = knot_packet_add_tmp_rrset(packet, rrset); + if (err != KNOT_EOK) { + // remove the last RRSet from the list of RRSets + // - just decrement the count + --(*rrset_count); + knot_rrset_deep_free(&rrset, 1, 1, 1); + break; + } + } + + return (err < 0) ? err : KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Deallocates all space which was allocated additionally to the + * pre-allocated space of the response structure. + * + * \param resp Response structure that holds pointers to the allocated space. + */ +static void knot_packet_free_allocated_space(knot_packet_t *pkt) +{ + dbg_packet("Freeing additional space in packet.\n"); + if (pkt->prealloc_type == KNOT_PACKET_PREALLOC_NONE) { + dbg_packet("Freeing QNAME.\n"); + knot_dname_release(pkt->question.qname); + } + + if (pkt->max_an_rrsets > DEFAULT_RRSET_COUNT(ANCOUNT, pkt)) { + free(pkt->answer); + } + if (pkt->max_ns_rrsets > DEFAULT_RRSET_COUNT(NSCOUNT, pkt)) { + free(pkt->authority); + } + if (pkt->max_ar_rrsets > DEFAULT_RRSET_COUNT(ARCOUNT, pkt)) { + free(pkt->additional); + } + + if (pkt->compression.max > DEFAULT_DOMAINS_IN_RESPONSE) { + free(pkt->compression.dnames); + free(pkt->compression.offsets); + } + + if (pkt->tmp_rrsets_max > DEFAULT_RRSET_COUNT(TMP_RRSETS, pkt)) { + free(pkt->tmp_rrsets); + } +} + +/*----------------------------------------------------------------------------*/ + +static int knot_packet_parse_rr_sections(knot_packet_t *packet, + size_t *pos) +{ + assert(packet != NULL); + assert(packet->wireformat != NULL); + assert(packet->size > 0); + assert(pos != NULL); + assert(*pos > 0); + + int err; + + dbg_packet("Parsing Answer RRs...\n"); + if ((err = knot_packet_parse_rrs(packet->wireformat, pos, + packet->size, packet->header.ancount, &packet->answer, + &packet->an_rrsets, &packet->max_an_rrsets, + DEFAULT_RRSET_COUNT(ANCOUNT, packet), packet)) != KNOT_EOK) { + return err; + } + + dbg_packet("Parsing Authority RRs...\n"); + if ((err = knot_packet_parse_rrs(packet->wireformat, pos, + packet->size, packet->header.nscount, &packet->authority, + &packet->ns_rrsets, &packet->max_ns_rrsets, + DEFAULT_RRSET_COUNT(NSCOUNT, packet), packet)) != KNOT_EOK) { + return err; + } + + dbg_packet("Parsing Additional RRs...\n"); + if ((err = knot_packet_parse_rrs(packet->wireformat, pos, + packet->size, packet->header.arcount, &packet->additional, + &packet->ar_rrsets, &packet->max_ar_rrsets, + DEFAULT_RRSET_COUNT(ARCOUNT, packet), packet)) != KNOT_EOK) { + return err; + } + + dbg_packet("Trying to find OPT RR in the packet.\n"); + + for (int i = 0; i < packet->ar_rrsets; ++i) { + assert(packet->additional[i] != NULL); + if (knot_rrset_type(packet->additional[i]) + == KNOT_RRTYPE_OPT) { + dbg_packet("Found OPT RR, filling.\n"); + err = knot_edns_new_from_rr(&packet->opt_rr, + packet->additional[i]); + if (err != KNOT_EOK) { + return err; + } + break; + } + } + + packet->parsed = *pos; + + if (*pos < packet->size) { + // some trailing garbage; ignore, but log + dbg_response("Packet: %zu bytes of trailing garbage " + "in packet.\n", packet->size - (*pos)); + return KNOT_EMALF; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_packet_t *knot_packet_new(knot_packet_prealloc_type_t prealloc) +{ + knot_packet_t *pkt; + void (*init_pointers)(knot_packet_t *pkt) = NULL; + size_t size = 0; + + switch (prealloc) { + case KNOT_PACKET_PREALLOC_NONE: + size = sizeof(knot_packet_t); + break; + case KNOT_PACKET_PREALLOC_QUERY: + size = PREALLOC_QUERY; + init_pointers = knot_packet_init_pointers_query; + break; + case KNOT_PACKET_PREALLOC_RESPONSE: + size = PREALLOC_RESPONSE; + init_pointers = knot_packet_init_pointers_response; + break; + } + + pkt = (knot_packet_t *)malloc(size); + CHECK_ALLOC_LOG(pkt, NULL); + memset(pkt, 0, size); + if (init_pointers != NULL) { + init_pointers(pkt); + } + + pkt->prealloc_type = prealloc; + + // set EDNS version to not supported + pkt->opt_rr.version = EDNS_NOT_SUPPORTED; + + return pkt; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_parse_from_wire(knot_packet_t *packet, + const uint8_t *wireformat, size_t size, + int question_only) +{ + if (packet == NULL || wireformat == NULL) { + return KNOT_EBADARG; + } + + int err; + + // save the wireformat in the packet + // TODO: can we just save the pointer, or we have to copy the data?? + assert(packet->wireformat == NULL); + packet->wireformat = (uint8_t*)wireformat; + packet->size = size; + packet->free_wireformat = 0; + + //uint8_t *pos = wireformat; + size_t pos = 0; + //size_t remaining = size; + + dbg_packet("Parsing wire format of packet (size %zu).\nHeader\n", + size); + if ((err = knot_packet_parse_header(wireformat, &pos, size, + &packet->header)) != KNOT_EOK) { + return err; + } + + packet->parsed = pos; + + dbg_packet("Question (prealloc type: %d)...\n", packet->prealloc_type); + + if (packet->header.qdcount > 1) { + dbg_packet("QDCOUNT larger than 1, FORMERR.\n"); + return KNOT_EMALF; + } + + knot_packet_dump(packet); + + if (packet->header.qdcount == 1) { + if ((err = knot_packet_parse_question(wireformat, &pos, size, + &packet->question, packet->prealloc_type + == KNOT_PACKET_PREALLOC_NONE) + ) != KNOT_EOK) { + return err; + } + packet->parsed = pos; + } + + knot_packet_dump(packet); + + if (question_only) { + return KNOT_EOK; + } + + /*! \todo Replace by call to parse_rest()? */ + err = knot_packet_parse_rr_sections(packet, &pos); + +#ifdef KNOT_PACKET_DEBUG + knot_packet_dump(packet); +#endif /* KNOT_RESPONSE_DEBUG */ + + return err; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_parse_rest(knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + +// if (packet->parsed >= packet->size) { +// return KNOT_EOK; +// } + + if (packet->parsed == packet->size) { + return KNOT_EOK; + } + + size_t pos = packet->parsed; + + return knot_packet_parse_rr_sections(packet, &pos); +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_parse_next_rr_answer(knot_packet_t *packet, + knot_rrset_t **rr) +{ + if (packet == NULL || rr == NULL) { + return KNOT_EBADARG; + } + + *rr = NULL; + + if (packet->parsed >= packet->size) { + assert(packet->an_rrsets <= packet->header.ancount); + if (packet->an_rrsets != packet->header.ancount) { + dbg_packet("Parsed less RRs than expected.\n"); + return KNOT_EMALF; + } else { + dbg_packet("Whole packet parsed\n"); + return KNOT_EOK; + } + } + + if (packet->an_rrsets == packet->header.ancount) { + assert(packet->parsed < packet->size); + //dbg_packet("Trailing garbage, ignoring...\n"); + // there may be other data in the packet + // (authority or additional). + return KNOT_EOK; + } + + size_t pos = packet->parsed; + + dbg_packet("Parsing next Answer RR (pos: %zu)...\n", pos); + *rr = knot_packet_parse_rr(packet->wireformat, &pos, packet->size); + if (*rr == NULL) { + dbg_packet("Failed to parse RR!\n"); + return KNOT_EMALF; + } + + dbg_packet("Parsed. Pos: %zu.\n", pos); + + packet->parsed = pos; + // increment the number of answer RRSets, though there are no saved + // in the packet; it is OK, because packet->answer is NULL + ++packet->an_rrsets; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_parse_next_rr_additional(knot_packet_t *packet, + knot_rrset_t **rr) +{ + /*! \todo Implement. */ + if (packet == NULL || rr == NULL) { + return KNOT_EBADARG; + } + + *rr = NULL; + + if (packet->parsed >= packet->size) { + assert(packet->ar_rrsets <= packet->header.arcount); + if (packet->ar_rrsets != packet->header.arcount) { + dbg_packet("Parsed less RRs than expected.\n"); + return KNOT_EMALF; + } else { + dbg_packet("Whole packet parsed\n"); + return KNOT_EOK; + } + } + + if (packet->ar_rrsets == packet->header.arcount) { + assert(packet->parsed < packet->size); + dbg_packet("Trailing garbage, ignoring...\n"); + /*! \todo Do not ignore. */ + return KNOT_EOK; + } + + size_t pos = packet->parsed; + + dbg_packet("Parsing next Additional RR (pos: %zu)...\n", pos); + *rr = knot_packet_parse_rr(packet->wireformat, &pos, packet->size); + if (*rr == NULL) { + dbg_packet("Failed to parse RR!\n"); + return KNOT_EMALF; + } + + dbg_packet("Parsed. Pos: %zu.\n", pos); + + packet->parsed = pos; + // increment the number of answer RRSets, though there are no saved + // in the packet; it is OK, because packet->answer is NULL + ++packet->ar_rrsets; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +size_t knot_packet_size(const knot_packet_t *packet) +{ + return packet->size; +} + +/*----------------------------------------------------------------------------*/ + +size_t knot_packet_question_size(const knot_packet_t *packet) +{ + return (KNOT_WIRE_HEADER_SIZE + 4 + + knot_dname_size(packet->question.qname)); +} + +/*----------------------------------------------------------------------------*/ + +size_t knot_packet_parsed(const knot_packet_t *packet) +{ + return packet->parsed; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_set_max_size(knot_packet_t *packet, int max_size) +{ + if (packet == NULL || max_size <= 0) { + return KNOT_EBADARG; + } + + if (packet->max_size < max_size) { + // reallocate space for the wire format (and copy anything + // that might have been there before + uint8_t *wire_new = (uint8_t *)malloc(max_size); + if (wire_new == NULL) { + return KNOT_ENOMEM; + } + + uint8_t *wire_old = packet->wireformat; + + memcpy(wire_new, packet->wireformat, packet->max_size); + packet->wireformat = wire_new; + + if (packet->max_size > 0 && packet->free_wireformat) { + free(wire_old); + } + + packet->free_wireformat = 1; + } + + // set max size + packet->max_size = max_size; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_packet_id(const knot_packet_t *packet) +{ + assert(packet != NULL); + return packet->header.id; +} + +/*----------------------------------------------------------------------------*/ + +void knot_packet_set_id(knot_packet_t *packet, uint16_t id) +{ + if (packet == NULL) { + return; + } + + packet->header.id = id; +} + +/*----------------------------------------------------------------------------*/ + +void knot_packet_set_random_id(knot_packet_t *packet) +{ + if (packet == NULL) { + return; + } + + packet->header.id = knot_random_id(); +} + +/*----------------------------------------------------------------------------*/ + +uint8_t knot_packet_opcode(const knot_packet_t *packet) +{ + assert(packet != NULL); + return knot_wire_flags_get_opcode(packet->header.flags1); +} + +/*----------------------------------------------------------------------------*/ + +const knot_dname_t *knot_packet_qname(const knot_packet_t *packet) +{ + if (packet == NULL) { + return NULL; + } + + return packet->question.qname; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_packet_qtype(const knot_packet_t *packet) +{ + assert(packet != NULL); + return packet->question.qtype; +} + +/*----------------------------------------------------------------------------*/ + +void knot_packet_set_qtype(knot_packet_t *packet, knot_rr_type_t qtype) +{ + assert(packet != NULL); + packet->question.qtype = qtype; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_packet_qclass(const knot_packet_t *packet) +{ + assert(packet != NULL); + return packet->question.qclass; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_is_query(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return (knot_wire_flags_get_qr(packet->header.flags1) == 0); +} + +/*----------------------------------------------------------------------------*/ + +const knot_packet_t *knot_packet_query(const knot_packet_t *packet) +{ + if (packet == NULL) { + return NULL; + } + + return packet->query; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_rcode(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return knot_wire_flags_get_rcode(packet->header.flags2); +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_tc(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return knot_wire_flags_get_tc(packet->header.flags1); +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_qdcount(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->header.qdcount; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_ancount(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->header.ancount; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_nscount(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->header.nscount; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_arcount(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->header.arcount; +} + +/*----------------------------------------------------------------------------*/ + +void knot_packet_set_tsig_size(knot_packet_t *packet, size_t tsig_size) +{ + packet->tsig_size = tsig_size; +} + +/*----------------------------------------------------------------------------*/ + +short knot_packet_answer_rrset_count(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->an_rrsets; +} + +/*----------------------------------------------------------------------------*/ + +short knot_packet_authority_rrset_count(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->ns_rrsets; +} + +/*----------------------------------------------------------------------------*/ + +short knot_packet_additional_rrset_count(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->ar_rrsets; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rrset_t *knot_packet_answer_rrset( + const knot_packet_t *packet, short pos) +{ + if (packet == NULL || pos > packet->an_rrsets) { + return NULL; + } + + return packet->answer[pos]; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rrset_t *knot_packet_authority_rrset( + knot_packet_t *packet, short pos) +{ + if (packet == NULL || pos > packet->ns_rrsets) { + return NULL; + } + + return packet->authority[pos]; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rrset_t *knot_packet_additional_rrset( + knot_packet_t *packet, short pos) +{ + if (packet == NULL || pos > packet->ar_rrsets) { + return NULL; + } + + return packet->additional[pos]; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_contains(const knot_packet_t *packet, + const knot_rrset_t *rrset, + knot_rrset_compare_type_t cmp) +{ + if (packet == NULL || rrset == NULL) { + return KNOT_EBADARG; + } + + for (int i = 0; i < packet->header.ancount; ++i) { + if (knot_rrset_compare(packet->answer[i], rrset, cmp)) { + return 1; + } + } + + for (int i = 0; i < packet->header.nscount; ++i) { + if (knot_rrset_compare(packet->authority[i], rrset, cmp)) { + return 1; + } + } + + for (int i = 0; i < packet->header.arcount; ++i) { + if (knot_rrset_compare(packet->additional[i], rrset, cmp)) { + return 1; + } + } + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_add_tmp_rrset(knot_packet_t *packet, + knot_rrset_t *tmp_rrset) +{ + if (packet == NULL || tmp_rrset == NULL) { + return KNOT_EBADARG; + } + + if (packet->tmp_rrsets_count == packet->tmp_rrsets_max + && knot_packet_realloc_rrsets(&packet->tmp_rrsets, + &packet->tmp_rrsets_max, + DEFAULT_RRSET_COUNT(TMP_RRSETS, packet), + STEP_TMP_RRSETS) != KNOT_EOK) { + return KNOT_ENOMEM; + } + + packet->tmp_rrsets[packet->tmp_rrsets_count++] = tmp_rrset; + dbg_packet("Current tmp RRSets count: %d, max count: %d\n", + packet->tmp_rrsets_count, packet->tmp_rrsets_max); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Frees all temporary RRSets stored in the response structure. + * + * \param resp Response structure to free the temporary RRSets from. + */ +void knot_packet_free_tmp_rrsets(knot_packet_t *pkt) +{ + if (pkt == NULL) { + return; + } + + for (int i = 0; i < pkt->tmp_rrsets_count; ++i) { +dbg_packet_exec( + char *name = knot_dname_to_str( + (((knot_rrset_t **)(pkt->tmp_rrsets))[i])->owner); + dbg_packet("Freeing tmp RRSet on ptr: %p (ptr to ptr:" + " %p, type: %s, owner: %s)\n", + (((knot_rrset_t **)(pkt->tmp_rrsets))[i]), + &(((knot_rrset_t **)(pkt->tmp_rrsets))[i]), + knot_rrtype_to_string( + (((knot_rrset_t **)(pkt->tmp_rrsets))[i])->type), + name); + free(name); +); + // TODO: this is quite ugly, but better than copying whole + // function (for reallocating rrset array) + knot_rrset_deep_free( + &(((knot_rrset_t **)(pkt->tmp_rrsets))[i]), 1, 1, 1); + } +} + +/*----------------------------------------------------------------------------*/ + +void knot_packet_header_to_wire(const knot_header_t *header, + uint8_t **pos, size_t *size) +{ + if (header == NULL || pos == NULL || *pos == NULL || size == NULL) { + return; + } + + knot_wire_set_id(*pos, header->id); + knot_wire_set_flags1(*pos, header->flags1); + knot_wire_set_flags2(*pos, header->flags2); + knot_wire_set_qdcount(*pos, header->qdcount); + knot_wire_set_ancount(*pos, header->ancount); + knot_wire_set_nscount(*pos, header->nscount); + knot_wire_set_arcount(*pos, header->arcount); + + *pos += KNOT_WIRE_HEADER_SIZE; + *size += KNOT_WIRE_HEADER_SIZE; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_question_to_wire(knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + if (packet->size > KNOT_WIRE_HEADER_SIZE) { + return KNOT_ERROR; + } + + // TODO: get rid of the numeric constants + size_t qsize = 4 + knot_dname_size(packet->question.qname); + if (qsize > packet->max_size - KNOT_WIRE_HEADER_SIZE) { + return KNOT_ESPACE; + } + + // create the wireformat of Question + uint8_t *pos = packet->wireformat + KNOT_WIRE_HEADER_SIZE; + memcpy(pos, knot_dname_name(packet->question.qname), + knot_dname_size(packet->question.qname)); + + pos += knot_dname_size(packet->question.qname); + knot_wire_write_u16(pos, packet->question.qtype); + pos += 2; + knot_wire_write_u16(pos, packet->question.qclass); + +// int err = 0; + // TODO: put the qname into the compression table +// // TODO: get rid of the numeric constants +// if ((err = knot_response_store_dname_pos(&packet->compression, +// packet->question.qname,0, 12, 12)) != KNOT_EOK) { +// return err; +// } + + packet->size += knot_dname_size(packet->question.qname) + 4; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_edns_to_wire(knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + packet->size += knot_edns_to_wire(&packet->opt_rr, + packet->wireformat + packet->size, + packet->max_size - packet->size); + + packet->header.arcount += 1; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_to_wire(knot_packet_t *packet, + uint8_t **wire, size_t *wire_size) +{ + if (packet == NULL || wire == NULL || wire_size == NULL + || *wire != NULL) { + return KNOT_EBADARG; + } + + assert(packet->size <= packet->max_size); + + // if there are no additional RRSets, add EDNS OPT RR + if (packet->header.arcount == 0 + && packet->opt_rr.version != EDNS_NOT_SUPPORTED) { + knot_packet_edns_to_wire(packet); + } + + // set QDCOUNT (in response it is already set, in query it is needed) + knot_wire_set_qdcount(packet->wireformat, packet->header.qdcount); + // set ANCOUNT to the packet + knot_wire_set_ancount(packet->wireformat, packet->header.ancount); + // set NSCOUNT to the packet + knot_wire_set_nscount(packet->wireformat, packet->header.nscount); + // set ARCOUNT to the packet + knot_wire_set_arcount(packet->wireformat, packet->header.arcount); + + //assert(response->size == size); + *wire = packet->wireformat; + *wire_size = packet->size; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +const uint8_t *knot_packet_wireformat(const knot_packet_t *packet) +{ + return packet->wireformat; +} + +/*----------------------------------------------------------------------------*/ + +void knot_packet_free(knot_packet_t **packet) +{ + if (packet == NULL || *packet == NULL) { + return; + } + + // free temporary domain names + dbg_packet("Freeing tmp RRSets...\n"); + knot_packet_free_tmp_rrsets(*packet); + + // check if some additional space was allocated for the packet + dbg_packet("Freeing additional allocated space...\n"); + knot_packet_free_allocated_space(*packet); + + // free the space for wireformat +// assert((*packet)->wireformat != NULL); +// free((*packet)->wireformat); + if ((*packet)->wireformat != NULL && (*packet)->free_wireformat) { + free((*packet)->wireformat); + } + + dbg_packet("Freeing packet structure\n"); + free(*packet); + *packet = NULL; +} + +/*----------------------------------------------------------------------------*/ +#ifdef KNOT_PACKET_DEBUG +static void knot_packet_dump_rrsets(const knot_rrset_t **rrsets, + int count) +{ + assert((rrsets != NULL && *rrsets != NULL) || count < 1); + + for (int i = 0; i < count; ++i) { + knot_rrset_dump(rrsets[i], 0); + } +} +#endif +/*----------------------------------------------------------------------------*/ + +void knot_packet_dump(const knot_packet_t *packet) +{ + if (packet == NULL) { + return; + } + +#ifdef KNOT_PACKET_DEBUG + dbg_packet("DNS packet:\n-----------------------------\n"); + + dbg_packet("\nHeader:\n"); + dbg_packet(" ID: %u", packet->header.id); + dbg_packet(" FLAGS: %s %s %s %s %s %s %s\n", + knot_wire_flags_get_qr(packet->header.flags1) ? "qr" : "", + knot_wire_flags_get_aa(packet->header.flags1) ? "aa" : "", + knot_wire_flags_get_tc(packet->header.flags1) ? "tc" : "", + knot_wire_flags_get_rd(packet->header.flags1) ? "rd" : "", + knot_wire_flags_get_ra(packet->header.flags2) ? "ra" : "", + knot_wire_flags_get_ad(packet->header.flags2) ? "ad" : "", + knot_wire_flags_get_cd(packet->header.flags2) ? "cd" : ""); + dbg_packet(" QDCOUNT: %u\n", packet->header.qdcount); + dbg_packet(" ANCOUNT: %u\n", packet->header.ancount); + dbg_packet(" NSCOUNT: %u\n", packet->header.nscount); + dbg_packet(" ARCOUNT: %u\n", packet->header.arcount); + + if (knot_packet_qdcount(packet) > 0) { + dbg_packet("\nQuestion:\n"); + char *qname = knot_dname_to_str(packet->question.qname); + dbg_packet(" QNAME: %s\n", qname); + free(qname); + dbg_packet(" QTYPE: %u (%s)\n", packet->question.qtype, + knot_rrtype_to_string(packet->question.qtype)); + dbg_packet(" QCLASS: %u (%s)\n", packet->question.qclass, + knot_rrclass_to_string(packet->question.qclass)); + } + + dbg_packet("\nAnswer RRSets:\n"); + knot_packet_dump_rrsets(packet->answer, packet->an_rrsets); + dbg_packet("\nAuthority RRSets:\n"); + knot_packet_dump_rrsets(packet->authority, packet->ns_rrsets); + dbg_packet("\nAdditional RRSets:\n"); + knot_packet_dump_rrsets(packet->additional, packet->ar_rrsets); + + /*! \todo Dumping of Answer, Authority and Additional sections. */ + + dbg_packet("\nEDNS:\n"); + dbg_packet(" Version: %u\n", packet->opt_rr.version); + dbg_packet(" Payload: %u\n", packet->opt_rr.payload); + dbg_packet(" Extended RCODE: %u\n", + packet->opt_rr.ext_rcode); + + dbg_packet("\nPacket size: %zu\n", packet->size); + dbg_packet("\n-----------------------------\n"); +#endif +} + diff --git a/src/libknot/packet/packet.h b/src/libknot/packet/packet.h new file mode 100644 index 0000000..1bf74a9 --- /dev/null +++ b/src/libknot/packet/packet.h @@ -0,0 +1,538 @@ +/*! + * \file packet.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Structure for holding DNS packet data and metadata. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_PACKET_H_ +#define _KNOT_PACKET_H_ + +#include <stdint.h> +#include <string.h> + +#include "dname.h" +#include "rrset.h" +#include "edns.h" + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure for holding information needed for compressing domain names. + * + * It's a simple table of domain names and their offsets in wire format of the + * packet. + * + * \todo Consider using some better lookup structure, such as skip-list. + */ +struct knot_compressed_dnames { + const knot_dname_t **dnames; /*!< Domain names present in packet. */ + size_t *offsets; /*!< Offsets of domain names in the packet. */ + short count; /*!< Count of items in the previous arrays. */ + short max; /*!< Capacity of the structure (allocated). */ +}; + +typedef struct knot_compressed_dnames knot_compressed_dnames_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure representing the DNS packet header. + */ +struct knot_header { + uint16_t id; /*!< ID stored in host byte order. */ + uint8_t flags1; /*!< First octet of header flags. */ + uint8_t flags2; /*!< Second octet of header flags. */ + uint16_t qdcount; /*!< Number of Question RRs, in host byte order. */ + uint16_t ancount; /*!< Number of Answer RRs, in host byte order. */ + uint16_t nscount; /*!< Number of Authority RRs, in host byte order. */ + uint16_t arcount; /*!< Number of Additional RRs, in host byte order. */ +}; + +typedef struct knot_header knot_header_t; + +/*! + * \brief Structure representing one Question entry in the DNS packet. + */ +struct knot_question { + knot_dname_t *qname; /*!< Question domain name. */ + uint16_t qtype; /*!< Question TYPE. */ + uint16_t qclass; /*!< Question CLASS. */ +}; + +typedef struct knot_question knot_question_t; + +enum knot_packet_prealloc_type { + KNOT_PACKET_PREALLOC_NONE, + KNOT_PACKET_PREALLOC_QUERY, + KNOT_PACKET_PREALLOC_RESPONSE +}; + +typedef enum knot_packet_prealloc_type knot_packet_prealloc_type_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure representing a DNS packet. + * + * \note QNAME, Answer, Authority and Additonal sections are by default put to + * preallocated space after the structure with default sizes. If the + * space is not enough, more space is allocated dynamically. + */ +struct knot_packet { + /*! \brief DNS header. */ + knot_header_t header; + + /*! + * \brief Question section. + * + * \note Only one Question is supported! + */ + knot_question_t question; + + uint8_t *owner_tmp; /*!< Allocated space for RRSet owner wire format.*/ + + const knot_rrset_t **answer; /*!< Answer RRSets. */ + const knot_rrset_t **authority; /*!< Authority RRSets. */ + const knot_rrset_t **additional; /*!< Additional RRSets. */ + + short an_rrsets; /*!< Count of Answer RRSets in the response. */ + short ns_rrsets; /*!< Count of Authority RRSets in the response. */ + short ar_rrsets; /*!< Count of Additional RRSets in the response. */ + + short max_an_rrsets; /*!< Allocated space for Answer RRsets. */ + short max_ns_rrsets; /*!< Allocated space for Authority RRsets. */ + short max_ar_rrsets; /*!< Allocated space for Additional RRsets. */ + + knot_opt_rr_t opt_rr; /*!< OPT RR included in the packet. */ + + uint8_t *wireformat; /*!< Wire format of the packet. */ + + short free_wireformat; + size_t parsed; + + size_t size; /*!< Current wire size of the packet. */ + size_t max_size; /*!< Maximum allowed size of the packet. */ + + /*! \brief Information needed for compressing domain names in packet. */ + knot_compressed_dnames_t compression; + + /*! \brief RRSets to be destroyed with the packet structure. */ + const knot_rrset_t **tmp_rrsets; + short tmp_rrsets_count; /*!< Count of temporary RRSets. */ + short tmp_rrsets_max; /*!< Allocated space for temporary RRSets. */ + + struct knot_packet *query; /*!< Associated query. */ + + knot_packet_prealloc_type_t prealloc_type; + + size_t tsig_size; /*!< Space to reserve for the TSIG RR. */ +}; + +typedef struct knot_packet knot_packet_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Default sizes for response structure parts and steps for increasing + * them. + */ +enum { + DEFAULT_ANCOUNT = 6, /*!< Default count of Answer RRSets. */ + DEFAULT_NSCOUNT = 8, /*!< Default count of Authority RRSets. */ + DEFAULT_ARCOUNT = 28, /*!< Default count of Additional RRSets. */ + + DEFAULT_ANCOUNT_QUERY = 1, /*!< Default count of Answer RRSets. */ + DEFAULT_NSCOUNT_QUERY = 0, /*!< Default count of Authority RRSets. */ + DEFAULT_ARCOUNT_QUERY = 1, /*!< Default count of Additional RRSets. */ + /*! + * \brief Default count of all domain names in response. + * + * Used for compression table. + */ + DEFAULT_DOMAINS_IN_RESPONSE = 22, + + /*! \brief Default count of temporary RRSets stored in response. */ + DEFAULT_TMP_RRSETS = 5, + + /*! \brief Default count of temporary RRSets stored in query. */ + DEFAULT_TMP_RRSETS_QUERY = 2, + + STEP_ANCOUNT = 6, /*!< Step for increasing space for Answer RRSets. */ + STEP_NSCOUNT = 8, /*!< Step for increasing space for Authority RRSets.*/ + STEP_ARCOUNT = 8,/*!< Step for increasing space for Additional RRSets.*/ + STEP_DOMAINS = 10, /*!< Step for resizing compression table. */ + STEP_TMP_RRSETS = 5 /*!< Step for increasing temorary RRSets count. */ +}; + +/*----------------------------------------------------------------------------*/ +#define PREALLOC_RRSETS(count) (count * sizeof(knot_rrset_t *)) + +/*! \brief Sizes for preallocated space in the response structure. */ +enum { + /*! \brief Size of the response structure itself. */ + PREALLOC_PACKET = sizeof(knot_packet_t), + /*! \brief Space for QNAME dname structure. */ + PREALLOC_QNAME_DNAME = sizeof(knot_dname_t), + /*! \brief Space for QNAME name (maximum domain name size). */ + PREALLOC_QNAME_NAME = 256, + /*! \brief Space for QNAME labels (maximum label count). */ + PREALLOC_QNAME_LABELS = 127, + /*! \brief Total space for QNAME. */ + PREALLOC_QNAME = PREALLOC_QNAME_DNAME + + PREALLOC_QNAME_NAME + + PREALLOC_QNAME_LABELS, + /*! + * \brief Space for RR owner wire format. + * + * Temporary buffer, used when putting RRSets to the response. + */ + PREALLOC_RR_OWNER = 256, + +// /*! \brief Space for Answer RRSets. */ +// PREALLOC_ANSWER = DEFAULT_ANCOUNT * sizeof(knot_dname_t *), +// /*! \brief Space for Authority RRSets. */ +// PREALLOC_AUTHORITY = DEFAULT_NSCOUNT * sizeof(knot_dname_t *), +// /*! \brief Space for Additional RRSets. */ +// PREALLOC_ADDITIONAL = DEFAULT_ARCOUNT * sizeof(knot_dname_t *), +// /*! \brief Total size for Answer, Authority and Additional RRSets. */ +// PREALLOC_RRSETS = PREALLOC_ANSWER +// + PREALLOC_AUTHORITY +// + PREALLOC_ADDITIONAL, + /*! \brief Space for one part of the compression table (domain names).*/ + PREALLOC_DOMAINS = + DEFAULT_DOMAINS_IN_RESPONSE * sizeof(knot_dname_t *), + /*! \brief Space for other part of the compression table (offsets). */ + PREALLOC_OFFSETS = + DEFAULT_DOMAINS_IN_RESPONSE * sizeof(size_t), + PREALLOC_COMPRESSION = PREALLOC_DOMAINS + PREALLOC_OFFSETS, + +// /*! \brief Space for temporary RRSets. */ +// PREALLOC_TMP_RRSETS = +// DEFAULT_TMP_RRSETS * sizeof(knot_rrset_t *), + + PREALLOC_QUERY = PREALLOC_PACKET + + PREALLOC_QNAME + + PREALLOC_RRSETS(DEFAULT_ANCOUNT_QUERY) + + PREALLOC_RRSETS(DEFAULT_NSCOUNT_QUERY) + + PREALLOC_RRSETS(DEFAULT_ARCOUNT_QUERY) + + PREALLOC_RRSETS(DEFAULT_TMP_RRSETS_QUERY), + + /*! \brief Total preallocated size for the response. */ + PREALLOC_RESPONSE = PREALLOC_PACKET + + PREALLOC_QNAME + + PREALLOC_RR_OWNER + + PREALLOC_RRSETS(DEFAULT_ANCOUNT) + + PREALLOC_RRSETS(DEFAULT_NSCOUNT) + + PREALLOC_RRSETS(DEFAULT_ARCOUNT) + + PREALLOC_COMPRESSION + + PREALLOC_RRSETS(DEFAULT_TMP_RRSETS) +}; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates new empty packet structure. + * + * \param prealloc What space should be preallocated in the structure. + * + * \return New packet structure or NULL if an error occured. + */ +knot_packet_t *knot_packet_new(knot_packet_prealloc_type_t prealloc); + +/*! + * \brief Parses the DNS packet from wire format. + * + * \param packet Packet structure to parse into. + * \param wireformat Wire format of the DNS packet. + * \param size Size of the wire format in bytes. + * \param question_only Set to <> 0 if you do not want to parse the whole + * packet. In such case the parsing will end after the + * Question section. Set to 0 to parse the whole packet. + * + * \retval KNOT_EOK + */ +int knot_packet_parse_from_wire(knot_packet_t *packet, + const uint8_t *wireformat, size_t size, + int question_only); + +int knot_packet_parse_rest(knot_packet_t *packet); + +int knot_packet_parse_next_rr_answer(knot_packet_t *packet, + knot_rrset_t **rr); + +int knot_packet_parse_next_rr_additional(knot_packet_t *packet, + knot_rrset_t **rr); + +size_t knot_packet_size(const knot_packet_t *packet); + +/*! \brief Returns size of the wireformat of Header and Question sections. */ +size_t knot_packet_question_size(const knot_packet_t *packet); + +size_t knot_packet_parsed(const knot_packet_t *packet); + +/*! + * \brief Sets the maximum size of the packet and allocates space for wire + * format (if needed). + * + * This function also allocates space for the wireformat of the packet, if + * the given max size is larger than the current maximum size of the packet + * and copies the current wireformat over to the new space. + * + * \warning Do not call this function if you are not completely sure that the + * current wire format of the packet fits into the new space. + * It does not update the current size of the wire format, so the + * produced packet may be larger than the given max size. + * + * \param packet Packet to set the maximum size of. + * \param max_size Maximum size of the packet in bytes. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + * + * \todo Needs test. + */ +int knot_packet_set_max_size(knot_packet_t *packet, int max_size); + +uint16_t knot_packet_id(const knot_packet_t *packet); + +void knot_packet_set_id(knot_packet_t *packet, uint16_t id); + +void knot_packet_set_random_id(knot_packet_t *packet); + +/*! + * \brief Returns the OPCODE of the packet. + * + * \param packet Packet (with parsed query) to get the OPCODE from. + * + * \return OPCODE stored in the packet. + */ +uint8_t knot_packet_opcode(const knot_packet_t *packet); + +/*! + * \brief Returns the QNAME from the packet. + * + * \param packet Packet (with parsed query) to get the QNAME from. + * + * \return QNAME stored in the packet. + */ +const knot_dname_t *knot_packet_qname(const knot_packet_t *packet); + +/*! + * \brief Returns the QTYPE from the packet. + * + * \param packet Packet (with parsed query) to get the QTYPE from. + * + * \return QTYPE stored in the packet. + */ +uint16_t knot_packet_qtype(const knot_packet_t *packet); + +/*! + * \brief Set the QTYPE of the packet. + * + * \param packet Packet containing question. + * \param qtype New QTYPE for question. + */ +void knot_packet_set_qtype(knot_packet_t *packet, knot_rr_type_t qtype); + + +/*! + * \brief Returns the QCLASS from the packet. + * + * \param response Packet (with parsed query) to get the QCLASS from. + * + * \return QCLASS stored in the packet. + */ +uint16_t knot_packet_qclass(const knot_packet_t *packet); + +int knot_packet_is_query(const knot_packet_t *packet); + +const knot_packet_t *knot_packet_query(const knot_packet_t *packet); + +int knot_packet_rcode(const knot_packet_t *packet); + +int knot_packet_tc(const knot_packet_t *packet); + +int knot_packet_qdcount(const knot_packet_t *packet); + +int knot_packet_ancount(const knot_packet_t *packet); + +int knot_packet_nscount(const knot_packet_t *packet); + +int knot_packet_arcount(const knot_packet_t *packet); + +void knot_packet_set_tsig_size(knot_packet_t *packet, size_t tsig_size); + +/*! + * \brief Returns number of RRSets in Answer section of the packet. + * + * \param response Packet to get the Answer RRSet count from. + */ +short knot_packet_answer_rrset_count(const knot_packet_t *packet); + +/*! + * \brief Returns number of RRSets in Authority section of the packet. + * + * \param response Packet to get the Authority RRSet count from. + */ +short knot_packet_authority_rrset_count(const knot_packet_t *packet); + +/*! + * \brief Returns number of RRSets in Additional section of the packet. + * + * \param response Packet to get the Additional RRSet count from. + */ +short knot_packet_additional_rrset_count(const knot_packet_t *packet); + +/*! + * \brief Returns the requested Answer RRset. + * + * \param packet Packet to get the RRSet from. + * \param pos Position of the RRSet in the Answer section (RRSets are stored + * in the order they were added to the response or parsed from the + * query). + * + * \return The RRSet on position \a pos in the Answer section of \a packet + * or NULL if there is no such RRSet. + */ +const knot_rrset_t *knot_packet_answer_rrset( + const knot_packet_t *packet, short pos); + +/*! + * \brief Returns the requested Authority RRset. + * + * \param packet Packet to get the RRSet from. + * \param pos Position of the RRSet in the Authority section (RRSets are stored + * in the order they were added to the response or parsed from the + * query). + * + * \return The RRSet on position \a pos in the Authority section of \a packet + * or NULL if there is no such RRSet. + */ +const knot_rrset_t *knot_packet_authority_rrset( + knot_packet_t *packet, short pos); + +/*! + * \brief Returns the requested Additional RRset. + * + * \param packet Packet to get the RRSet from. + * \param pos Position of the RRSet in the Additional section (RRSets are stored + * in the order they were added to the response or parsed from the + * query). + * + * \return The RRSet on position \a pos in the Additional section of \a packet + * or NULL if there is no such RRSet. + */ +const knot_rrset_t *knot_packet_additional_rrset( + knot_packet_t *packet, short pos); + +/*! + * \brief Checks if the packet already contains the given RRSet. + * + * It searches for the RRSet in the three lists of RRSets corresponding to + * Answer, Authority and Additional sections of the packet. + * + * \note Only pointers are compared, i.e. two instances of knot_rrset_t with + * the same data will be considered different. + * + * \param packet Packet to look for the RRSet in. + * \param rrset RRSet to look for. + * + * \retval 0 if \a resp does not contain \a rrset. + * \retval <> 0 if \a resp does contain \a rrset. + */ +int knot_packet_contains(const knot_packet_t *packet, + const knot_rrset_t *rrset, + knot_rrset_compare_type_t cmp); + +/*! + * \brief Adds RRSet to the list of temporary RRSets. + * + * Temporary RRSets are fully freed when the response structure is destroyed. + * + * \param response Response to which the temporary RRSet should be added. + * \param tmp_rrset Temporary RRSet to be stored in the response. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +int knot_packet_add_tmp_rrset(knot_packet_t *response, + knot_rrset_t *tmp_rrset); + +void knot_packet_free_tmp_rrsets(knot_packet_t *pkt); + +/*! + * \brief Converts the header structure to wire format. + * + * \note This function also adjusts the position (\a pos) according to + * the size of the converted wire format. + * + * \param[in] header DNS header structure to convert. + * \param[out] pos Position where to put the converted header. The space has + * to be allocated before calling this function. + * \param[out] size Size of the wire format of the header in bytes. + */ +void knot_packet_header_to_wire(const knot_header_t *header, + uint8_t **pos, size_t *size); + +int knot_packet_question_to_wire(knot_packet_t *packet); + +/*! + * \brief Converts the stored response OPT RR to wire format and adds it to + * the response wire format. + * + * \param resp Response structure. + */ +int knot_packet_edns_to_wire(knot_packet_t *packet); + +/*! + * \brief Converts the packet to wire format. + * + * \param packet Packet to be converted to wire format. + * \param wire Here the wire format of the packet will be stored. + * Space for the packet will be allocated. *resp_wire must + * be set to NULL (to avoid leaks). + * \param wire_size The size of the packet in wire format will be stored here. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_packet_to_wire(knot_packet_t *packet, uint8_t **wire, + size_t *wire_size); + +const uint8_t *knot_packet_wireformat(const knot_packet_t *packet); + +/*! + * \brief Properly destroys the packet structure. + * + * \param response Packet to be destroyed. + */ +void knot_packet_free(knot_packet_t **packet); + +/*! + * \brief Dumps the whole packet in human-readable form. + * + * \note This function is empty unless KNOT_PACKET_DEBUG is defined. + * + * \param resp Packet to dump. + */ +void knot_packet_dump(const knot_packet_t *packet); + +#endif /* _KNOT_PACKET_H_ */ + +/*! @} */ diff --git a/src/libknot/packet/query.c b/src/libknot/packet/query.c new file mode 100644 index 0000000..63e902a --- /dev/null +++ b/src/libknot/packet/query.c @@ -0,0 +1,228 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include "packet/query.h" + +#include "util/error.h" +#include "util/wire.h" + +/*----------------------------------------------------------------------------*/ + +int knot_query_rr_to_wire(const knot_rrset_t *rrset, const knot_rdata_t *rdata, + uint8_t **wire, uint8_t *endp) +{ + /* Store owner. */ + knot_dname_t *owner = rrset->owner; + if (*wire + owner->size > endp) { + return KNOT_ENOMEM; + } + memcpy(*wire, owner->name, owner->size); + *wire += owner->size; + + if (*wire + 10 > endp) { + return KNOT_ENOMEM; + } + + /* Write RR header. */ + knot_wire_write_u16(*wire, rrset->type); *wire += 2; + knot_wire_write_u16(*wire, rrset->rclass); *wire += 2; + knot_wire_write_u32(*wire, rrset->ttl); *wire += 4; + knot_wire_write_u16(*wire, 0); *wire += 2; /* RDLENGTH reserve. */ + uint8_t *rdlength_p = *wire - 2; + uint16_t rdlength = 0; + + /* Write data. */ + knot_dname_t *dname = 0; + uint16_t *raw_data = 0; + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrset->type); + + for (int i = 0; i < rdata->count; ++i) { + switch (desc->wireformat[i]) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: + + /* Check space for dname. */ + dname = knot_rdata_item(rdata, i)->dname; + if (*wire + dname->size > endp) { + return KNOT_ESPACE; + } + + /* Save domain name. */ + memcpy(*wire, dname->name, dname->size); + *wire += dname->size; + rdlength += dname->size; + break; + default: + raw_data = knot_rdata_item(rdata, i)->raw_data; + if (*wire + raw_data[0] > endp) { + return KNOT_ESPACE; + } + + /* Copy data. */ + memcpy(*wire, raw_data + 1, raw_data[0]); + *wire += raw_data[0]; + rdlength += raw_data[0]; + break; + + } + } + + /* Store rdlength. */ + knot_wire_write_u16(rdlength_p, rdlength); + + return KNOT_EOK; +} +/*----------------------------------------------------------------------------*/ + +int knot_query_dnssec_requested(const knot_packet_t *query) +{ + if (query == NULL) { + return KNOT_EBADARG; + } + + return ((knot_edns_get_version(&query->opt_rr) != EDNS_NOT_SUPPORTED) + && knot_edns_do(&query->opt_rr)); +} + +/*----------------------------------------------------------------------------*/ + +int knot_query_nsid_requested(const knot_packet_t *query) +{ + if (query == NULL) { + return KNOT_EBADARG; + } + + return ((knot_edns_get_version(&query->opt_rr) != EDNS_NOT_SUPPORTED) + && knot_edns_has_option(&query->opt_rr, EDNS_OPTION_NSID)); +} + +/*----------------------------------------------------------------------------*/ + +int knot_query_edns_supported(const knot_packet_t *query) +{ + if (query == NULL) { + return KNOT_EBADARG; + } + + return (knot_edns_get_version(&query->opt_rr) != EDNS_NOT_SUPPORTED); +} + +/*----------------------------------------------------------------------------*/ + +int knot_query_init(knot_packet_t *query) +{ + if (query == NULL) { + return KNOT_EBADARG; + } + // set the qr bit to 0 + knot_wire_flags_clear_qr(&query->header.flags1); + + uint8_t *pos = query->wireformat; + knot_packet_header_to_wire(&query->header, &pos, &query->size); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_query_set_question(knot_packet_t *query, + const knot_question_t *question) +{ + if (query == NULL || question == NULL) { + return KNOT_EBADARG; + } + + query->question.qname = question->qname; + query->question.qclass = question->qclass; + query->question.qtype = question->qtype; + query->header.qdcount = 1; + + // convert the Question to wire format right away + knot_packet_question_to_wire(query); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_query_set_opcode(knot_packet_t *query, uint8_t opcode) +{ + if (query == NULL) { + return KNOT_EBADARG; + } + // set the OPCODE in the structure + knot_wire_flags_set_opcode(&query->header.flags1, opcode); + // set the OPCODE in the wire format + knot_wire_set_opcode(query->wireformat, opcode); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_query_add_rrset_authority(knot_packet_t *query, + const knot_rrset_t *rrset) +{ + if (query == NULL || rrset == NULL) { + return KNOT_EBADARG; + } + + if (query->ns_rrsets == query->max_ns_rrsets) { + size_t oldsize = query->max_ns_rrsets * sizeof(knot_rrset_t *); + ++query->max_ns_rrsets; + size_t newsize = query->max_ns_rrsets * sizeof(knot_rrset_t *); + const knot_rrset_t ** na = malloc(newsize); + if (na == 0) { + query->max_ns_rrsets = 0; + return KNOT_ENOMEM; + } else { + memcpy(na, query->authority, oldsize); + free(query->authority); + query->authority = na; + } + } + + /* Append to packet. */ + query->authority[query->ns_rrsets] = rrset; + + /* Write to wire. */ + uint8_t *startp = query->wireformat + query->size; + uint8_t *endp = query->wireformat + query->max_size; + + assert(endp - startp > query->opt_rr.size + query->tsig_size); + // reserve space for OPT RR + endp -= query->opt_rr.size; + /*! \note [TSIG] reserve space for TSIG RR */ + endp -= query->tsig_size; + + uint8_t *pos = startp; + + const knot_rdata_t *rdata = 0; + while ((rdata = knot_rrset_rdata_next(rrset, rdata))) { + knot_query_rr_to_wire(rrset, rdata, &pos, endp); + } + + size_t written = (pos - startp); + query->size += written; + ++query->ns_rrsets; + ++query->header.nscount; + + return KNOT_EOK; +} + diff --git a/src/libknot/packet/query.h b/src/libknot/packet/query.h new file mode 100644 index 0000000..a979641 --- /dev/null +++ b/src/libknot/packet/query.h @@ -0,0 +1,93 @@ +/*! + * \file query.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief API for manipulating queries. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_QUERY_H_ +#define _KNOT_QUERY_H_ + +#include <stdint.h> +#include <string.h> + +#include "packet/packet.h" +#include "dname.h" +#include "rrset.h" +#include "edns.h" + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Checks if DNSSEC was requested in the query (i.e. the DO bit was set). + * + * \param query Packet where the parsed query is stored. + * + * \retval 0 if the DO bit was not set in the query, or the query is not yet + * parsed. + * \retval > 0 if DO bit was set in the query. + */ +int knot_query_dnssec_requested(const knot_packet_t *query); + +/*! + * \brief Checks if NSID was requested in the query (i.e. the NSID option was + * present in the query OPT RR). + * + * \param query Packet where the parsed query is stored. + * + * \retval 0 if the NSID option was not present in the query, or the query is + * not yet parsed. + * \retval > 0 if the NSID option was present in the query. + */ +int knot_query_nsid_requested(const knot_packet_t *query); + +int knot_query_edns_supported(const knot_packet_t *query); + +//int knot_query_set_qname(knot_packet_t *query, const knot_dname_t *qname); + +//int knot_query_set_qtype(knot_packet_t *query, uint16_t qtype); + +//int knot_query_set_qclass(knot_packet_t *query, uint16_t qclass); + +int knot_query_init(knot_packet_t *query); + +int knot_query_set_question(knot_packet_t *query, + const knot_question_t *question); + +int knot_query_set_opcode(knot_packet_t *query, uint8_t opcode); + +/*! + * \brief Adds a RRSet to the Authority section of the query. + * + * \param query Query to add the RRSet into. + * \param rrset RRSet to be added. + * + * \retval KNOT_EOK if successful, or the RRSet was already in the query. + * \retval KNOT_ENOMEM + * \retval KNOT_ESPACE + */ +int knot_query_add_rrset_authority(knot_packet_t *query, + const knot_rrset_t *rrset); + + +#endif /* _KNOT_QUERY_H_ */ + +/*! @} */ diff --git a/src/libknot/packet/response.c b/src/libknot/packet/response.c new file mode 100644 index 0000000..e6f89d0 --- /dev/null +++ b/src/libknot/packet/response.c @@ -0,0 +1,1170 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> + +#include "packet/response.h" +#include "util/wire.h" +#include "util/descriptor.h" +#include "common.h" +#include "util/error.h" +#include "util/debug.h" +#include "packet/packet.h" + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Holds information about compressed domain name. + * + * Used only to pass information between functions. + * + * \todo This description should be revised and clarified. + */ +struct knot_compr_owner { + /*! + * \brief Place where the name is stored in the wire format of the + * packet. + */ + uint8_t *wire; + short size; /*!< Size of the domain name in bytes. */ + /*! \brief Position of the name relative to the start of the packet. */ + size_t pos; +}; + +typedef struct knot_compr_owner knot_compr_owner_t; + +/*! + * \brief Holds information about compressed domain names in packet. + * + * Used only to pass information between functions. + * + * \todo This description should be revised and clarified. + */ +struct knot_compr { + knot_compressed_dnames_t *table; /*!< Compression table. */ + size_t wire_pos; /*!< Current position in the wire format. */ + knot_compr_owner_t owner; /*!< Information about the current name. */ +}; + +typedef struct knot_compr knot_compr_t; + +static const size_t KNOT_RESPONSE_MAX_PTR = 16383; + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Reallocates space for compression table. + * + * \param table Compression table to reallocate space for. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +static int knot_response_realloc_compr(knot_compressed_dnames_t *table) +{ + int free_old = table->max != DEFAULT_DOMAINS_IN_RESPONSE; + size_t *old_offsets = table->offsets; + const knot_dname_t **old_dnames = table->dnames; + + short new_max_count = table->max + STEP_DOMAINS; + + size_t *new_offsets = (size_t *)malloc(new_max_count * sizeof(size_t)); + CHECK_ALLOC_LOG(new_offsets, -1); + + const knot_dname_t **new_dnames = (const knot_dname_t **)malloc( + new_max_count * sizeof(knot_dname_t *)); + if (new_dnames == NULL) { + ERR_ALLOC_FAILED; + free(new_offsets); + return KNOT_ENOMEM; + } + + memcpy(new_offsets, table->offsets, table->max * sizeof(size_t)); + memcpy(new_dnames, table->dnames, + table->max * sizeof(knot_dname_t *)); + + table->offsets = new_offsets; + table->dnames = new_dnames; + table->max = new_max_count; + + if (free_old) { + free(old_offsets); + free(old_dnames); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Stores new mapping between domain name and offset in the compression + * table. + * + * If the domain name is already present in the table, it is not inserted again. + * + * \param table Compression table to save the mapping into. + * \param dname Domain name to insert. + * \param pos Position of the domain name in the packet's wire format. + */ +static void knot_response_compr_save(knot_compressed_dnames_t *table, + const knot_dname_t *dname, size_t pos) +{ + assert(table->count < table->max); + + for (int i = 0; i < table->count; ++i) { + if (table->dnames[i] == dname) { + dbg_response("Already present, skipping..\n"); + return; + } + } + + table->dnames[table->count] = dname; + table->offsets[table->count] = pos; + ++table->count; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Stores domain name position and positions of its parent domain names + * to the compression table. + * + * If part of the domain name (\a dname) was not found previously in the + * compression table, this part and all its parent domains is stored also, to + * maximize compression potential. + * + * \param table Compression table to save the information into. + * \param dname Domain name to save. + * \param not_matched Count of labels not matched when previously searching in + * the compression table for \a dname. + * \param pos Position of the domain name in the wire format of the packet. + * \param unmatched_offset Position of the unmatched parent domain of \a dname. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +static int knot_response_store_dname_pos(knot_compressed_dnames_t *table, + const knot_dname_t *dname, + int not_matched, size_t pos, + size_t unmatched_offset, + int compr_cs) +{ +dbg_response_exec( + char *name = knot_dname_to_str(dname); + dbg_response("Putting dname %s into compression table." + " Labels not matched: %d, position: %zu," + ", pointer: %p, unmatched off: %zu\n", name, + not_matched, pos, dname, unmatched_offset); + free(name); +); + if (pos > KNOT_RESPONSE_MAX_PTR) { + dbg_response("Pointer larger than it can be, not" + " saving\n"); + return KNOT_EDNAMEPTR; + } + + if (table->count == table->max && + knot_response_realloc_compr(table) != 0) { + return KNOT_ENOMEM; + } + + // store the position of the name +// table->dnames[table->count] = dname; +// table->offsets[table->count] = pos; +// ++table->count; + + /* + * Store positions of ancestors if more than 1 label was not matched. + * + * In case the name is not in the zone, the counting to not_matched + * may be limiting, because the search stopped before after the first + * label (i.e. not_matched == 1). So we do not store the parents in + * this case. However, storing them will require creating those domain + * names, as they do not exist. + * + * The same problem is with domain names synthetized from wildcards. + * These also do not have any node to follow. + * + * We accept this as performance has higher + * priority than the best possible compression. + */ + const knot_dname_t *to_save = dname; + size_t parent_pos = pos; + int i = 0; + + while (to_save != NULL && i < knot_dname_label_count(dname)) { + if (i == not_matched) { + parent_pos = unmatched_offset; + } + +dbg_response_exec( + char *name = knot_dname_to_str(to_save); + dbg_response("Putting dname %s into compression table." + " Position: %zu, pointer: %p\n", + name, parent_pos, to_save); + free(name); +); + + if (table->count == table->max && + knot_response_realloc_compr(table) != 0) { + dbg_response("Unable to realloc.\n"); + return KNOT_ENOMEM; + } + +// dbg_response("Saving..\n"); + knot_response_compr_save(table, to_save, parent_pos); + + /*! \todo Remove '!compr_cs'. */ + // This is a temporary hack to avoid the wrong behaviour + // when the wrong not_matched count is used to compare with i + // and resulting in using the 0 offset. + // If case-sensitive search is in place, we should not save the + // node's parent's positions. + + to_save = !compr_cs && (knot_dname_node(to_save, 1) != NULL + && knot_node_parent(knot_dname_node(to_save, 1), 1) + != NULL) ? knot_node_owner(knot_node_parent( + knot_dname_node(to_save, 1), 1)) + : NULL; + + dbg_response("i: %d\n", i); + parent_pos += knot_dname_label_size(dname, i) + 1; +// parent_pos += (i > 0) +// ? knot_dname_label_size(dname, i - 1) + 1 : 0; + ++i; + } + + return KNOT_EOK; +} + +/*---------------------------------------------------------------------------*/ +/*! + * \brief Tries to find offset of domain name in the compression table. + * + * \param table Compression table to search in. + * \param dname Domain name to search for. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \return Offset of \a dname stored in the compression table or -1 if the name + * was not found in the table. + */ +static size_t knot_response_find_dname_pos( + const knot_compressed_dnames_t *table, + const knot_dname_t *dname, int compr_cs) +{ + for (int i = 0; i < table->count; ++i) { +// dbg_response("Comparing dnames %p and %p\n", +// dname, table->dnames[i]); +//dbg_response_exec( +// char *name = knot_dname_to_str(dname); +// dbg_response("(%s and ", name); +// name = knot_dname_to_str(table->dnames[i]); +// dbg_response("%s)\n", name); +// free(name); +//); + //if (table->dnames[i] == dname) { + int ret = (compr_cs) + ? knot_dname_compare_cs(table->dnames[i], dname) + : knot_dname_compare(table->dnames[i], dname); + if (ret == 0) { + dbg_response("Found offset: %zu\n", + table->offsets[i]); + return table->offsets[i]; + } + } + return 0; +} + +/*---------------------------------------------------------------------------*/ +/*! + * \brief Put a compressed domain name to the wire format of the packet. + * + * Puts the not matched part of the domain name to the wire format and puts + * a pointer to the rest of the name after that. + * + * \param dname Domain name to put to the wire format. + * \param not_matched Size of the part of domain name that cannot be compressed. + * \param offset Position of the rest of the domain name in the packet's wire + * format. + * \param wire Place where to put the wire format of the name. + * \param max Maximum available size of the place for the wire format. + * + * \return Size of the compressed domain name put into the wire format or + * KNOT_ESPACE if it did not fit. + */ +static int knot_response_put_dname_ptr(const knot_dname_t *dname, + int not_matched, size_t offset, + uint8_t *wire, size_t max) +{ + // put the not matched labels + short size = knot_dname_size_part(dname, not_matched); + if (size + 2 > max) { + return KNOT_ESPACE; + } + + memcpy(wire, knot_dname_name(dname), size); + knot_wire_put_pointer(wire + size, offset); + + dbg_response("Size of the dname with ptr: %d\n", size + 2); + + return size + 2; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tries to compress domain name and creates its wire format. + * + * \param dname Domain name to convert and compress. + * \param compr Compression table holding information about offsets of domain + * names in the packet. + * \param dname_wire Place where to put the wire format of the name. + * \param max Maximum available size of the place for the wire format. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \return Size of the domain name's wire format or KNOT_ESPACE if it did not + * fit into the provided space. + */ +static int knot_response_compress_dname(const knot_dname_t *dname, + knot_compr_t *compr, uint8_t *dname_wire, size_t max, int compr_cs) +{ + int size = 0; + /*! + * \todo Compress!! + * + * if pos == 0, do not store the position! + */ + + // try to find the name or one of its ancestors in the compr. table +#ifdef COMPRESSION_PEDANTIC + //knot_dname_t *to_find = knot_dname_copy(dname); + knot_dname_t *to_find = (knot_dname_t *)dname; + int copied = 0; +#else + const knot_dname_t *to_find = dname; +#endif + size_t offset = 0; + int not_matched = 0; + + while (to_find != NULL && knot_dname_label_count(to_find) != 0) { +dbg_response_exec( + char *name = knot_dname_to_str(to_find); + dbg_response("Searching for name %s in the compression" + " table, not matched labels: %d\n", name, + not_matched); + free(name); +); + offset = knot_response_find_dname_pos(compr->table, to_find, + compr_cs); + if (offset == 0) { + ++not_matched; + } else { + break; + } +#ifdef COMPRESSION_PEDANTIC + if (compr_cs || to_find->node == NULL + || to_find->node->owner != to_find + || to_find->node->parent == NULL) { + if (!copied) { + to_find = knot_dname_left_chop(to_find); + copied = 1; + } else { + knot_dname_left_chop_no_copy(to_find); + } + } else { + assert(to_find->node != to_find->node->parent); + assert(to_find != to_find->node->parent->owner); + to_find = to_find->node->parent->owner; + } +#else + // if case-sensitive comparation, we cannot just take the parent + if (compr_cs || knot_dname_node(to_find, 1) == NULL + || knot_node_owner(knot_dname_node(to_find, 1)) != to_find + || knot_node_parent(knot_dname_node(to_find, 1), 1) + == NULL) { + dbg_response("compr_cs: %d\n", compr_cs); + dbg_response("knot_dname_node(to_find, 1) == %p" + "\n", knot_dname_node(to_find, 1)); + + if (knot_dname_node(to_find, 1) != NULL) { + dbg_response("knot_node_owner(knot_dname_node(" + "to_find, 1)) = %p, to_find = %p\n", + knot_node_owner(knot_dname_node(to_find, 1)), + to_find); + dbg_response("knot_node_parent(knot_dname_node(" + "to_find, 1), 1) = %p\n", + knot_node_parent(knot_dname_node(to_find, 1), 1)); + } + break; + } else { + assert(knot_dname_node(to_find, 1) != + knot_node_parent(knot_dname_node(to_find, 1), 1)); + assert(to_find != knot_node_owner( + knot_node_parent(knot_dname_node(to_find, 1), 1))); + to_find = knot_node_owner( + knot_node_parent(knot_dname_node(to_find, 1), 1)); + dbg_response("New to_find: %p\n", to_find); + } +#endif + } + +#ifdef COMPRESSION_PEDANTIC + if (copied) { + knot_dname_free(&to_find); + } +#endif + + dbg_response("Max size available for domain name: %zu\n", max); + + if (offset > 0) { // found such dname somewhere in the packet + dbg_response("Found name in the compression table.\n"); + assert(offset >= KNOT_WIRE_HEADER_SIZE); + size = knot_response_put_dname_ptr(dname, not_matched, offset, + dname_wire, max); + if (size <= 0) { + return KNOT_ESPACE; + } + } else { + dbg_response("Not found, putting whole name.\n"); + // now just copy the dname without compressing + if (dname->size > max) { + return KNOT_ESPACE; + } + + memcpy(dname_wire, dname->name, dname->size); + size = dname->size; + } + + // in either way, put info into the compression table + /*! \todo This is useless if the name was already in the table. + * It is meaningful only if the found name is the one from QNAME + * and thus its parents are not stored yet. + */ + assert(compr->wire_pos >= 0); + + if (knot_response_store_dname_pos(compr->table, dname, not_matched, + compr->wire_pos, offset, compr_cs) + != 0) { + dbg_response("Compression info could not be stored." + "\n"); + } + + return size; +} + +/*---------------------------------------------------------------------------*/ +/*! + * \brief Convert one RR into wire format. + * + * \param[in] rrset RRSet to which the RR belongs. + * \param[in] rdata The actual RDATA of this RR. + * \param[in] compr Information about compressed domain names in the packet. + * \param[out] rrset_wire Place to put the wire format of the RR into. + * \param[in] max_size Size of space available for the wire format. + * \param[in] compr_cs Set to <> 0 if dname compression should use case + * sensitive comparation. Set to 0 otherwise. + * + * \return Size of the RR's wire format or KNOT_ESPACE if it did not fit into + * the provided space. + */ +static int knot_response_rr_to_wire(const knot_rrset_t *rrset, + const knot_rdata_t *rdata, + knot_compr_t *compr, + uint8_t **rrset_wire, size_t max_size, + int compr_cs) +{ + int size = 0; + + dbg_response("Max size: %zu, owner pos: %zu, owner size: %d\n", + max_size, compr->owner.pos, compr->owner.size); + + if (size + ((compr->owner.pos == 0 + || compr->owner.pos > KNOT_RESPONSE_MAX_PTR) + ? compr->owner.size : 2) + 10 + > max_size) { + return KNOT_ESPACE; + } + + dbg_response("Owner position: %zu\n", compr->owner.pos); + + // put owner if needed (already compressed) + if (compr->owner.pos == 0 || compr->owner.pos > KNOT_RESPONSE_MAX_PTR) { + memcpy(*rrset_wire, compr->owner.wire, compr->owner.size); + compr->owner.pos = compr->wire_pos; + *rrset_wire += compr->owner.size; + size += compr->owner.size; + } else { + dbg_response("Putting pointer: %zu\n", + compr->owner.pos); + knot_wire_put_pointer(*rrset_wire, compr->owner.pos); + *rrset_wire += 2; + size += 2; + } + + dbg_response("Max size: %zu, size: %d\n", max_size, size); + + dbg_response("Wire format:\n"); + + // put rest of RR 'header' + knot_wire_write_u16(*rrset_wire, rrset->type); + dbg_response(" Type: %u\n", rrset->type); + dbg_response(" Type in wire: "); + dbg_response_hex((char *)*rrset_wire, 2); + *rrset_wire += 2; + + knot_wire_write_u16(*rrset_wire, rrset->rclass); + dbg_response(" Class: %u\n", rrset->rclass); + dbg_response(" Class in wire: "); + dbg_response_hex((char *)*rrset_wire, 2); + *rrset_wire += 2; + + knot_wire_write_u32(*rrset_wire, rrset->ttl); + dbg_response(" TTL: %u\n", rrset->ttl); + dbg_response(" TTL in wire: "); + dbg_response_hex((char *)*rrset_wire, 4); + *rrset_wire += 4; + + // save space for RDLENGTH + uint8_t *rdlength_pos = *rrset_wire; + *rrset_wire += 2; + + size += 10; + compr->wire_pos += size; + + dbg_response("Max size: %zu, size: %d\n", max_size, size); + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrset->type); + + uint16_t rdlength = 0; + + for (int i = 0; i < rdata->count; ++i) { + if (max_size < size + rdlength) { + return KNOT_ESPACE; + } + + switch (desc->wireformat[i]) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: { + int ret = knot_response_compress_dname( + knot_rdata_item(rdata, i)->dname, + compr, *rrset_wire, max_size - size - rdlength, + compr_cs); + + if (ret < 0) { + return KNOT_ESPACE; + } + + dbg_response("Compressed dname size: %d\n", + ret); + *rrset_wire += ret; + rdlength += ret; + compr->wire_pos += ret; + // TODO: compress domain name + break; + } + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: { + knot_dname_t *dname = + knot_rdata_item(rdata, i)->dname; + if (size + rdlength + dname->size > max_size) { + return KNOT_ESPACE; + } + + // save whole domain name + memcpy(*rrset_wire, dname->name, dname->size); + dbg_response("Uncompressed dname size: %d\n", + dname->size); + *rrset_wire += dname->size; + rdlength += dname->size; + compr->wire_pos += dname->size; + break; + } +// case KNOT_RDATA_WF_BINARYWITHLENGTH: { +// uint16_t *raw_data = +// knot_rdata_item(rdata, i)->raw_data; + +// if (size + raw_data[0] + 1 > max_size) { +// return KNOT_ESPACE; +// } + +// // copy also the rdata item size +// assert(raw_data[0] < 256); +// **rrset_wire = raw_data[0]; +// *rrset_wire += 1; +// memcpy(*rrset_wire, raw_data + 1, raw_data[0]); +// dbg_response("Raw data size: %d\n", +// raw_data[0] + 1); +// *rrset_wire += raw_data[0]; +// rdlength += raw_data[0] + 1; +// compr->wire_pos += raw_data[0] + 1; +// break; +// } + default: { + uint16_t *raw_data = + knot_rdata_item(rdata, i)->raw_data; + + if (size + rdlength + raw_data[0] > max_size) { + return KNOT_ESPACE; + } + + // copy just the rdata item data (without size) + memcpy(*rrset_wire, raw_data + 1, raw_data[0]); + dbg_response("Raw data size: %d\n", + raw_data[0]); + *rrset_wire += raw_data[0]; + rdlength += raw_data[0]; + compr->wire_pos += raw_data[0]; + break; + } + } + } + + dbg_response("Max size: %zu, size: %d\n", max_size, size); + + assert(size + rdlength <= max_size); + size += rdlength; + knot_wire_write_u16(rdlength_pos, rdlength); + + return size; +} + +/*---------------------------------------------------------------------------*/ +/*! + * \brief Convert whole RRSet into wire format. + * + * \param[in] rrset RRSet to convert + * \param[out] pos Place where to put the wire format. + * \param[out] size Size of the converted wire format. + * \param[in] max_size Maximum available space for the wire format. + * \param wire_pos Current position in the wire format of the whole packet. + * \param owner_tmp Wire format of the RRSet's owner, possibly compressed. + * \param compr Information about compressed domain names in the packet. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \return Size of the RRSet's wire format or KNOT_ESPACE if it did not fit + * into the provided space. + */ +static int knot_response_rrset_to_wire(const knot_rrset_t *rrset, + uint8_t **pos, size_t *size, + size_t max_size, size_t wire_pos, + uint8_t *owner_tmp, + knot_compressed_dnames_t *compr, + int compr_cs) +{ +dbg_response_exec( + char *name = knot_dname_to_str(rrset->owner); + dbg_response("Converting RRSet with owner %s, type %s\n", + name, knot_rrtype_to_string(rrset->type)); + free(name); + dbg_response(" Size before: %zu\n", *size); +); + + // if no RDATA in RRSet, return + if (rrset->rdata == NULL) { + return KNOT_EOK; + } + + //uint8_t *rrset_wire = (uint8_t *)malloc(PREALLOC_RRSET_WIRE); + //short rrset_size = 0; + + //uint8_t *owner_wire = (uint8_t *)malloc(rrset->owner->size); + /* + * We may pass the current position to the compression function + * because if the owner will be put somewhere, it will be on the + * current position (first item of a RR). If it will not be put into + * the wireformat, we may remove the dname (and possibly its parents) + * from the compression table. + */ + + knot_compr_t compr_info; + //compr_info.new_entries = 0; + compr_info.table = compr; + compr_info.wire_pos = wire_pos; + compr_info.owner.pos = 0; + compr_info.owner.wire = owner_tmp; + compr_info.owner.size = + knot_response_compress_dname(rrset->owner, &compr_info, + owner_tmp, max_size, compr_cs); + + dbg_response(" Owner size: %d, position: %zu\n", + compr_info.owner.size, compr_info.owner.pos); + if (compr_info.owner.size < 0) { + return KNOT_ESPACE; + } + + int rrs = 0; + short rrset_size = 0; + + const knot_rdata_t *rdata = rrset->rdata; + do { + int ret = knot_response_rr_to_wire(rrset, rdata, &compr_info, + pos, max_size - rrset_size, + compr_cs); + + assert(ret != 0); + + if (ret < 0) { + // some RR didn't fit in, so no RRs should be used + // TODO: remove last entries from compression table + dbg_response("Some RR didn't fit in.\n"); + return KNOT_ESPACE; + } + + dbg_response("RR of size %d added.\n", ret); + rrset_size += ret; + ++rrs; + } while ((rdata = knot_rrset_rdata_next(rrset, rdata)) != NULL); + + //memcpy(*pos, rrset_wire, rrset_size); + //*size += rrset_size; + //*pos += rrset_size; + + // the whole RRSet did fit in + assert (rrset_size <= max_size); + *size += rrset_size; + + dbg_response(" Size after: %zu\n", *size); + + return rrs; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tries to add RRSet to the response. + * + * This function tries to convert the RRSet to wire format and add it to the + * wire format of the response and if successful, adds the RRSet to the given + * list (and updates its size). If the RRSet did not fit into the available + * space (\a max_size), it is omitted as a whole and the TC bit may be set + * (according to \a tc). + * + * \param rrsets Lists of RRSets to which this RRSet should be added. + * \param rrset_count Number of RRSets in the list. + * \param resp Response structure where the RRSet should be added. + * \param max_size Maximum available space in wire format of the response. + * \param rrset RRSet to add. + * \param tc Set to <> 0 if omitting the RRSet should cause the TC bit to be + * set in the response. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \return Count of RRs added to the response or KNOT_ESPACE if the RRSet did + * not fit in the available space. + */ +static int knot_response_try_add_rrset(const knot_rrset_t **rrsets, + short *rrset_count, + knot_packet_t *resp, + size_t max_size, + const knot_rrset_t *rrset, int tc, + int compr_cs) +{ + //short size = knot_response_rrset_size(rrset, &resp->compression); + +dbg_response_exec( + char *name = knot_dname_to_str(rrset->owner); + dbg_response("\nAdding RRSet with owner %s and type %s: \n", + name, knot_rrtype_to_string(rrset->type)); + free(name); +); + + uint8_t *pos = resp->wireformat + resp->size; + size_t size = 0; + int rrs = knot_response_rrset_to_wire(rrset, &pos, &size, max_size, + resp->size, resp->owner_tmp, + &resp->compression, compr_cs); + + if (rrs >= 0) { + rrsets[(*rrset_count)++] = rrset; + resp->size += size; + dbg_response("RRset added, size: %zu, RRs: %d, total " + "size of response: %zu\n\n", size, rrs, + resp->size); + } else if (tc) { + dbg_response("Setting TC bit.\n"); + knot_wire_flags_set_tc(&resp->header.flags1); + knot_wire_set_tc(resp->wireformat); + } + + return rrs; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Reallocate space for RRSets. + * + * \param rrsets Space for RRSets. + * \param max_count Size of the space available for the RRSets. + * \param default_max_count Size of the space pre-allocated for the RRSets when + * the response structure was initialized. + * \param step How much the space should be increased. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +static int knot_response_realloc_rrsets(const knot_rrset_t ***rrsets, + short *max_count, + short default_max_count, short step) +{ + int free_old = (*max_count) != default_max_count; + const knot_rrset_t **old = *rrsets; + + short new_max_count = *max_count + step; + const knot_rrset_t **new_rrsets = (const knot_rrset_t **)malloc( + new_max_count * sizeof(knot_rrset_t *)); + CHECK_ALLOC_LOG(new_rrsets, KNOT_ENOMEM); + + memcpy(new_rrsets, *rrsets, (*max_count) * sizeof(knot_rrset_t *)); + + *rrsets = new_rrsets; + *max_count = new_max_count; + + if (free_old) { + free(old); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +int knot_response_init(knot_packet_t *response) +{ + if (response == NULL) { + return KNOT_EBADARG; + } + + if (response->max_size < KNOT_WIRE_HEADER_SIZE) { + return KNOT_ESPACE; + } + + // set the qr bit to 1 + knot_wire_flags_set_qr(&response->header.flags1); + + uint8_t *pos = response->wireformat; + knot_packet_header_to_wire(&response->header, &pos, + &response->size); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_response_init_from_query(knot_packet_t *response, + knot_packet_t *query) +{ + + if (response == NULL || query == NULL) { + return KNOT_EBADARG; + } + + // copy the header from the query + memcpy(&response->header, &query->header, sizeof(knot_header_t)); +// memmove(&response->header, &query->header, sizeof(knot_header_t)); + + // copy the Question section (but do not copy the QNAME) + memcpy(&response->question, &query->question, + sizeof(knot_question_t)); +// memmove(&response->question, &query->question, +// sizeof(knot_question_t)); + + int err = 0; + // put the qname into the compression table + // TODO: get rid of the numeric constants + if ((err = knot_response_store_dname_pos(&response->compression, + response->question.qname, 0, 12, 12, 0)) != KNOT_EOK) { + return err; + } + + // copy the wireformat of Header and Question from the query + // TODO: get rid of the numeric constants + size_t to_copy = 12 + 4 + knot_dname_size(response->question.qname); + + assert(response->max_size >= to_copy); +// printf("Resp init from query: Copying from: %p to: %p size: %d\n", +// response->wireformat, query->wireformat, +// to_copy); +// printf("Resp init from query: Question name size: %d Query name size: %d\n", +// knot_dname_size(response->question.qname), +// knot_dname_size(query->question.qname)); + memcpy(response->wireformat, query->wireformat, to_copy); + response->size = to_copy; + + // set the qr bit to 1 + knot_wire_flags_set_qr(&response->header.flags1); + knot_wire_set_qr(response->wireformat); + + // clear AD flag + knot_wire_flags_clear_ad(&response->header.flags2); + knot_wire_clear_ad(response->wireformat); + + // clear RA flag + knot_wire_flags_clear_ra(&response->header.flags2); + knot_wire_clear_ad(response->wireformat); + + // set counts to 0 + response->header.ancount = 0; + response->header.nscount = 0; + response->header.arcount = 0; + + response->query = query; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_response_clear(knot_packet_t *resp, int clear_question) +{ + if (resp == NULL) { + return; + } + + resp->size = (clear_question) ? KNOT_WIRE_HEADER_SIZE + : KNOT_WIRE_HEADER_SIZE + 4 + + knot_dname_size(resp->question.qname); + resp->an_rrsets = 0; + resp->ns_rrsets = 0; + resp->ar_rrsets = 0; + resp->compression.count = 0; + knot_packet_free_tmp_rrsets(resp); + resp->tmp_rrsets_count = 0; + resp->header.ancount = 0; + resp->header.nscount = 0; + resp->header.arcount = 0; +} + +/*----------------------------------------------------------------------------*/ + +int knot_response_add_opt(knot_packet_t *resp, + const knot_opt_rr_t *opt_rr, + int override_max_size) +{ + if (resp == NULL || opt_rr == NULL) { + return KNOT_EBADARG; + } + + // copy the OPT RR + resp->opt_rr.version = opt_rr->version; + resp->opt_rr.ext_rcode = opt_rr->ext_rcode; + resp->opt_rr.payload = opt_rr->payload; + resp->opt_rr.size = opt_rr->size; + + // if max size is set, it means there is some reason to be that way, + // so we can't just set it to higher value + + if (override_max_size && resp->max_size > 0 + && resp->max_size < opt_rr->payload) { + return KNOT_EPAYLOAD; + } + + // set max size (less is OK) + if (override_max_size) { + dbg_response("Overriding max size to: %u\n", + resp->opt_rr.payload); + return knot_packet_set_max_size(resp, resp->opt_rr.payload); + //resp->max_size = resp->opt_rr.payload; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_response_add_rrset_answer(knot_packet_t *response, + const knot_rrset_t *rrset, int tc, + int check_duplicates, int compr_cs) +{ + if (response == NULL || rrset == NULL) { + return KNOT_EBADARG; + } + + dbg_response("add_rrset_answer()\n"); + assert(response->header.arcount == 0); + assert(response->header.nscount == 0); + + if (response->an_rrsets == response->max_an_rrsets + && knot_response_realloc_rrsets(&response->answer, + &response->max_an_rrsets, DEFAULT_ANCOUNT, STEP_ANCOUNT) + != KNOT_EOK) { + return KNOT_ENOMEM; + } + + if (check_duplicates && knot_packet_contains(response, rrset, + KNOT_RRSET_COMPARE_PTR)) { + return KNOT_EOK; + } + + dbg_response("Trying to add RRSet to Answer section.\n"); + dbg_response("RRset: %p\n", rrset); + dbg_response("Owner: %p\n", rrset->owner); + + int rrs = knot_response_try_add_rrset(response->answer, + &response->an_rrsets, response, + response->max_size + - response->size + - response->opt_rr.size + - response->tsig_size, + rrset, tc, compr_cs); + + if (rrs >= 0) { + response->header.ancount += rrs; + return KNOT_EOK; + } + + return KNOT_ESPACE; +} + +/*----------------------------------------------------------------------------*/ + +int knot_response_add_rrset_authority(knot_packet_t *response, + const knot_rrset_t *rrset, int tc, + int check_duplicates, int compr_cs) +{ + if (response == NULL || rrset == NULL) { + return KNOT_EBADARG; + } + + assert(response->header.arcount == 0); + + if (response->ns_rrsets == response->max_ns_rrsets + && knot_response_realloc_rrsets(&response->authority, + &response->max_ns_rrsets, DEFAULT_NSCOUNT, STEP_NSCOUNT) + != 0) { + return KNOT_ENOMEM; + } + + if (check_duplicates && knot_packet_contains(response, rrset, + KNOT_RRSET_COMPARE_PTR)) { + return KNOT_EOK; + } + + dbg_response("Trying to add RRSet to Authority section.\n"); + + int rrs = knot_response_try_add_rrset(response->authority, + &response->ns_rrsets, response, + response->max_size + - response->size + - response->opt_rr.size + - response->tsig_size, + rrset, tc, compr_cs); + + if (rrs >= 0) { + response->header.nscount += rrs; + return KNOT_EOK; + } + + return KNOT_ESPACE; +} + +/*----------------------------------------------------------------------------*/ + +int knot_response_add_rrset_additional(knot_packet_t *response, + const knot_rrset_t *rrset, int tc, + int check_duplicates, int compr_cs) +{ + if (response == NULL || rrset == NULL) { + return KNOT_EBADARG; + } + + int ret; + + // if this is the first additional RRSet, add EDNS OPT RR first + if (response->header.arcount == 0 + && response->opt_rr.version != EDNS_NOT_SUPPORTED + && (ret = knot_packet_edns_to_wire(response)) != KNOT_EOK) { + return ret; + } + + if (response->ar_rrsets == response->max_ar_rrsets + && knot_response_realloc_rrsets(&response->additional, + &response->max_ar_rrsets, DEFAULT_ARCOUNT, STEP_ARCOUNT) + != 0) { + return KNOT_ENOMEM; + } + + if (check_duplicates && knot_packet_contains(response, rrset, + KNOT_RRSET_COMPARE_PTR)) { + return KNOT_EOK; + } + + dbg_response("Trying to add RRSet to Additional section.\n"); + + int rrs = knot_response_try_add_rrset(response->additional, + &response->ar_rrsets, response, + response->max_size + - response->size + - response->tsig_size, rrset, + tc, compr_cs); + + if (rrs >= 0) { + response->header.arcount += rrs; + return KNOT_EOK; + } + + return KNOT_ESPACE; +} + +/*----------------------------------------------------------------------------*/ + +void knot_response_set_rcode(knot_packet_t *response, short rcode) +{ + if (response == NULL) { + return; + } + + knot_wire_flags_set_rcode(&response->header.flags2, rcode); + knot_wire_set_rcode(response->wireformat, rcode); +} + +/*----------------------------------------------------------------------------*/ + +void knot_response_set_aa(knot_packet_t *response) +{ + if (response == NULL) { + return; + } + + knot_wire_flags_set_aa(&response->header.flags1); + knot_wire_set_aa(response->wireformat); +} + +/*----------------------------------------------------------------------------*/ + +void knot_response_set_tc(knot_packet_t *response) +{ + if (response == NULL) { + return; + } + + knot_wire_flags_set_tc(&response->header.flags1); + knot_wire_set_tc(response->wireformat); +} + +/*----------------------------------------------------------------------------*/ + +int knot_response_add_nsid(knot_packet_t *response, const uint8_t *data, + uint16_t length) +{ + if (response == NULL) { + return KNOT_EBADARG; + } + + return knot_edns_add_option(&response->opt_rr, + EDNS_OPTION_NSID, length, data); +} diff --git a/src/libknot/packet/response.h b/src/libknot/packet/response.h new file mode 100644 index 0000000..38bd9a8 --- /dev/null +++ b/src/libknot/packet/response.h @@ -0,0 +1,198 @@ +/*! + * \file response.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief API for response manipulation. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_response_H_ +#define _KNOT_response_H_ + +#include <stdint.h> +#include <string.h> + +#include "packet/packet.h" + +#include "dname.h" +#include "rrset.h" +#include "edns.h" + +/*! + * \brief Default maximum DNS response size + * + * This size must be supported by all servers and clients. + */ +static const short KNOT_MAX_RESPONSE_SIZE = 512; + +/*----------------------------------------------------------------------------*/ +int knot_response_init(knot_packet_t *response); + +/*! + * \brief Initializes response from the given query. + * + * Copies the header, Changes QR bit to 1, copies the Question section and + * stores pointer to the query packet structure in the response packet + * structure. + * + * \warning Never free the query packet structure after calling this function, + * it will be freed when the response structure is freed. + * + * \param response Packet structure representing the response. + * \param query Packet structure representing the query. + * + * \retval KNOT_EOK + */ +int knot_response_init_from_query(knot_packet_t *response, + knot_packet_t *query); + +/*! + * \brief Clears the response structure for reuse. + * + * After call to this function, the response will be in the same state as if + * knot_response_new() was called. The maximum wire size is retained. + * + * \param response Response structure to clear. + * + * \todo Replace the use of this function with something else maybe? + */ +void knot_response_clear(knot_packet_t *resp, int clear_question); + +/*! + * \brief Sets the OPT RR of the response. + * + * This function also allocates space for the wireformat of the response, if + * the payload in the OPT RR is larger than the current maximum size of the + * response and copies the current wireformat over to the new space. + * + * \note The contents of the OPT RR are copied. + * + * \param resp Response to set the OPT RR to. + * \param opt_rr OPT RR to set. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + * + * \todo Needs test. + */ +int knot_response_add_opt(knot_packet_t *resp, + const knot_opt_rr_t *opt_rr, + int override_max_size); + +/*! + * \brief Adds a RRSet to the Answer section of the response. + * + * \param response Response to add the RRSet into. + * \param rrset RRSet to be added. + * \param tc Set to <> 0 if omitting this RRSet should result in the TC bit set. + * Otherwise set to 0. + * \param check_duplicates Set to <> 0 if the RRSet should not be added to the + * response in case it is already there. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \retval KNOT_EOK if successful, or the RRSet was already in the answer. + * \retval KNOT_ENOMEM + * \retval KNOT_ESPACE + */ +int knot_response_add_rrset_answer(knot_packet_t *response, + const knot_rrset_t *rrset, int tc, + int check_duplicates, int compr_cs); + +/*! + * \brief Adds a RRSet to the Authority section of the response. + * + * \param response Response to add the RRSet into. + * \param rrset RRSet to be added. + * \param tc Set to <> 0 if omitting this RRSet should result in the TC bit set. + * Otherwise set to 0. + * \param check_duplicates Set to <> 0 if the RRSet should not be added to the + * response in case it is already there. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \retval KNOT_EOK if successful, or the RRSet was already in the answer. + * \retval KNOT_ENOMEM + * \retval KNOT_ESPACE + */ +int knot_response_add_rrset_authority(knot_packet_t *response, + const knot_rrset_t *rrset, int tc, + int check_duplicates, int compr_cs); + +/*! + * \brief Adds a RRSet to the Additional section of the response. + * + * \param response Response to add the RRSet into. + * \param rrset RRSet to be added. + * \param tc Set to <> 0 if omitting this RRSet should result in the TC bit set. + * Otherwise set to 0. + * \param check_duplicates Set to <> 0 if the RRSet should not be added to the + * response in case it is already there. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \retval KNOT_EOK if successful, or the RRSet was already in the answer. + * \retval KNOT_ENOMEM + * \retval KNOT_ESPACE + */ +int knot_response_add_rrset_additional(knot_packet_t *response, + const knot_rrset_t *rrset, int tc, + int check_duplicates, int compr_cs); + +/*! + * \brief Sets the RCODE of the response. + * + * \param response Response to set the RCODE in. + * \param rcode RCODE to set. + */ +void knot_response_set_rcode(knot_packet_t *response, short rcode); + +/*! + * \brief Sets the AA bit of the response to 1. + * + * \param response Response in which the AA bit should be set. + */ +void knot_response_set_aa(knot_packet_t *response); + +/*! + * \brief Sets the TC bit of the response to 1. + * + * \param response Response in which the TC bit should be set. + */ +void knot_response_set_tc(knot_packet_t *response); + +/*! + * \brief Adds NSID option to the response. + * + * \param response Response to add the NSID option into. + * \param data NSID data. + * \param length Size of NSID data in bytes. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +int knot_response_add_nsid(knot_packet_t *response, const uint8_t *data, + uint16_t length); + +#endif /* _KNOT_response_H_ */ + +/*! @} */ diff --git a/src/libknot/rdata.c b/src/libknot/rdata.c new file mode 100644 index 0000000..0c51f5b --- /dev/null +++ b/src/libknot/rdata.c @@ -0,0 +1,838 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdint.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <stdio.h> + +#include "common.h" +#include "rdata.h" +#include "util/descriptor.h" +#include "dname.h" +#include "util/error.h" +#include "zone/node.h" +#include "util/utils.h" +#include "util/debug.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Compares two RDATA items as binary data. + * + * \param d1 First item. + * \param d2 Second item. + * \param count1 Size of the first item in bytes. If set to < 0, the size will + * be taken from the first two bytes of \a d1. + * \param count2 Size of the second item in bytes. If set to < 0, the size will + * be taken from the first two bytes of \a d2. + * + * \retval 0 if the items are identical. + * \retval < 0 if \a d1 goes before \a d2 in canonical order. + * \retval > 0 if \a d1 goes after \a d2 in canonical order. + */ +static int knot_rdata_compare_binary(const uint8_t *d1, const uint8_t *d2, + int count1, int count2) +{ + int i1 = 0, i2 = 0; + + // length stored in the first octet + if (count1 < 0) { + // take count from the first two bytes + count1 = (int)(*(uint16_t *)d1); + // and start from the third byte + i1 = 2; + } + if (count2 < 0) { // dtto + count2 = (int)(*(uint16_t *)d2); + i2 = 2; + } + + + while (i1 < count1 && i2 < count2 && d1[i1] == d2[i2]) { + ++i1; + ++i2; + } + + if (i1 == count1 && i2 == count2) { + return 0; + } + + if (i1 == count1 && i2 < count2) { + return -1; + } else if (i2 == count2 && i1 < count1) { + return 1; + } else { + assert(i1 < count1 && i2 < count2); + return (d1[i1] < d2[i2]) ? -1 : 1; + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Retrieves the domain name from MX RDATA. + * + * \note This is only convenience function. It does not (and cannot) check if + * the given RDATA is of the right type, so it always returns the second + * RDATA item, even if it is not a domain name. + * + * \param rdata RDATA to get the MX domain name from. + * + * \return MX domain name stored in \a rdata or NULL if \a rdata has less than 2 + * items. + */ +static const knot_dname_t *knot_rdata_mx_name(const knot_rdata_t *rdata) +{ + if (rdata->count < 2) { + return NULL; + } + return rdata->items[1].dname; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Retrieves the domain name from NS RDATA. + * + * \note This is only convenience function. It does not (and cannot) check if + * the given RDATA is of the right type, so it always returns the first + * RDATA item, even if it is not a domain name. + * + * \param rdata RDATA to get the NS domain name from. + * + * \return NS domain name stored in \a rdata or NULL if \a rdata has no items. + */ +static const knot_dname_t *knot_rdata_ns_name(const knot_rdata_t *rdata) +{ + if (rdata->count < 1) { + return NULL; + } + return rdata->items[0].dname; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Retrieves the domain name from SRV RDATA. + * + * \note This is only convenience function. It does not (and cannot) check if + * the given RDATA is of the right type, so it always returns the fourth + * RDATA item, even if it is not a domain name. + * + * \param rdata RDATA to get the SRV domain name from. + * + * \return SRV domain name stored in \a rdata or NULL if \a rdata has less than + * 4 items. + */ +static const knot_dname_t *knot_rdata_srv_name(const knot_rdata_t *rdata) +{ + if (rdata->count < 4) { + return NULL; + } + return rdata->items[3].dname; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_rdata_t *knot_rdata_new() +{ + knot_rdata_t *rdata = + (knot_rdata_t *)malloc(sizeof(knot_rdata_t)); + if (rdata == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + rdata->items = NULL; + rdata->count = 0; + rdata->next = NULL; + + return rdata; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rdata_from_wire(knot_rdata_t *rdata, const uint8_t *wire, + size_t *pos, size_t total_size, size_t rdlength, + const knot_rrtype_descriptor_t *desc) +{ + int i = 0; + uint8_t item_type; + size_t parsed = 0; + + if (rdlength == 0) { + rdata->items = NULL; + return KNOT_EOK; + } + + knot_rdata_item_t *items = (knot_rdata_item_t *)malloc( + desc->length * sizeof(knot_rdata_item_t)); + CHECK_ALLOC_LOG(items, KNOT_ENOMEM); + + size_t item_size = 0; + uint8_t gateway_type = 0; // only to handle IPSECKEY record + knot_dname_t *dname = NULL; + + while (i < desc->length && (desc->fixed_items || parsed < rdlength)) { + + item_type = desc->wireformat[i]; + item_size = 0; + + size_t pos2; + + switch (item_type) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: + pos2 = *pos; + dname = knot_dname_parse_from_wire( + wire, &pos2, total_size, NULL); + if (dname == NULL) { + free(items); + return KNOT_ERROR; + } + items[i].dname = dname; + //*pos += dname->size; + parsed += pos2 - *pos; + *pos = pos2; + dname = 0; + break; + case KNOT_RDATA_WF_BYTE: + if (desc->type == KNOT_RRTYPE_IPSECKEY && i == 1) { + gateway_type = *(wire + *pos); + } + item_size = 1; + break; + case KNOT_RDATA_WF_SHORT: + item_size = 2; + break; + case KNOT_RDATA_WF_LONG: + item_size = 4; + break; + case KNOT_RDATA_WF_UINT48: + item_size = 6; + break; + case KNOT_RDATA_WF_TEXT: + item_size = rdlength - parsed; + break; + case KNOT_RDATA_WF_TEXT_SINGLE: + item_size = *(wire + *pos) + 1; + break; + case KNOT_RDATA_WF_A: + item_size = 4; + break; + case KNOT_RDATA_WF_AAAA: + item_size = 16; + break; + case KNOT_RDATA_WF_BINARY: + item_size = rdlength - parsed; + break; + case KNOT_RDATA_WF_BINARYWITHLENGTH: + item_size = *(wire + *pos) + 1; + break; + case KNOT_RDATA_WF_BINARYWITHSHORT: + item_size = knot_wire_read_u16(wire + *pos) + 2; + break; + case KNOT_RDATA_WF_APL: + // WTF? what to do with this?? + // Same as TXT, I guess. + item_size = rdlength - parsed; + break; + case KNOT_RDATA_WF_IPSECGATEWAY: + // determine size based on the 'gateway type' field + switch (gateway_type) { + case 0: + item_size = 0; + break; + case 1: + item_size = 4; + break; + case 2: + item_size = 16; + break; + case 3: + pos2 = *pos; + fprintf(stderr, "reading dname from pos: %zu\n", pos2); + dname = + knot_dname_parse_from_wire( + wire, &pos2, total_size, NULL); + if (dname == NULL) { + return KNOT_ERROR; + } + items[i].dname = dname; + //*pos += dname->size; + parsed += pos2 - *pos; + + fprintf(stderr, "read %zu bytes.\n", parsed); + *pos = pos2; + dname = 0; + break; + default: + assert(0); + } + + break; + default: + return KNOT_EMALF; + + } + + if (item_size != 0) { + if (parsed + item_size > rdlength) { + free(items); + return KNOT_EFEWDATA; + } + + items[i].raw_data = (uint16_t *)malloc(item_size + 2); + if (items[i].raw_data == NULL) { + free(items); + return KNOT_ENOMEM; + } + memcpy(items[i].raw_data, &item_size, 2); + memcpy(items[i].raw_data + 1, wire + *pos, item_size); + *pos += item_size; + parsed += item_size; + } else if (item_type == KNOT_RDATA_WF_BINARY + || item_type == KNOT_RDATA_WF_IPSECGATEWAY) { + fprintf(stderr, "item_size was 0, creating empty rdata item.\n"); + // in this case we are at the end of the RDATA + // and should create an empty RDATA item + items[i].raw_data = (uint16_t *)malloc(2); + if (items[i].raw_data == NULL) { + free(items); + return KNOT_ENOMEM; + } + memcpy(items[i].raw_data, &item_size, 2); + } else if (item_type != KNOT_RDATA_WF_COMPRESSED_DNAME + && item_type != KNOT_RDATA_WF_UNCOMPRESSED_DNAME + && item_type != KNOT_RDATA_WF_LITERAL_DNAME) { + fprintf(stderr, "RDATA item not set (i: %d), type: %u" + " RDATA item type: %d\n", i, desc->type ,item_type); + assert(0); + } + + ++i; + } + + assert(!desc->fixed_items || i == desc->length); + + // all items are parsed, insert into the RDATA + int rc; + rc = knot_rdata_set_items(rdata, items, i); + + for (int j = 0; j < i; ++j) { + assert(rdata->items[j].raw_data != NULL); + } + + free(items); + return rc; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rdata_set_item(knot_rdata_t *rdata, uint pos, + knot_rdata_item_t item) +{ + if (pos >= rdata->count) { + return KNOT_EBADARG; + } + + /*! \todo As in set_items() we should increment refcounter for dnames, + * but we don't know the item type. + */ + + rdata->items[pos] = item; // this should copy the union; or use memcpy? + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +unsigned int knot_rdata_item_count(const knot_rdata_t *rdata) +{ + return rdata->count; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rdata_set_items(knot_rdata_t *rdata, + const knot_rdata_item_t *items, uint count) +{ + if (rdata == NULL || items == NULL || count == 0 || + rdata->items != NULL) { + return KNOT_EBADARG; + } + + assert(rdata->count == 0); + if ((rdata->items = (knot_rdata_item_t *)malloc( + count * sizeof(knot_rdata_item_t))) == NULL) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + memcpy(rdata->items, items, count * sizeof(knot_rdata_item_t)); + rdata->count = count; + + /*! \todo Cannot determine items type, so the dname + * refcounters should be increased in caller. + */ + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rdata_item_t *knot_rdata_item(const knot_rdata_t *rdata, + uint pos) +{ + if (pos >= rdata->count) { + return NULL; + } else { + return &rdata->items[pos]; + } +} + +/*----------------------------------------------------------------------------*/ + +knot_rdata_item_t *knot_rdata_get_item(const knot_rdata_t *rdata, + uint pos) +{ + if (pos >= rdata->count) { + return NULL; + } else { + return &rdata->items[pos]; + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_rdata_item_set_dname(knot_rdata_t *rdata, uint pos, + knot_dname_t *dname) +{ + if (pos >= rdata->count) { + return KNOT_EBADARG; + } + + /* Retain dname. */ + knot_dname_retain(dname); + + rdata->items[pos].dname = dname; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rdata_item_set_raw_data(knot_rdata_t *rdata, uint pos, + uint16_t *raw_data) +{ + if (pos >= rdata->count) { + return KNOT_EBADARG; + } + + rdata->items[pos].raw_data = raw_data; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_rdata_free(knot_rdata_t **rdata) +{ + if (rdata == NULL || *rdata == NULL) { + return; + } + + if ((*rdata)->items) { + free((*rdata)->items); + } + free(*rdata); + *rdata = NULL; +} + +/*----------------------------------------------------------------------------*/ + +void knot_rdata_deep_free(knot_rdata_t **rdata, uint type, + int free_all_dnames) +{ + if (rdata == NULL || *rdata == NULL) { + return; + } + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(type); + assert(desc != NULL); + + assert((*rdata)->count <= desc->length); + + for (int i = 0; i < (*rdata)->count; i++) { + if (&((*rdata)->items[i]) == NULL) { + continue; + } + if (desc->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME + || desc->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME + || desc->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME ) { + if (((*rdata)->items[i].dname != NULL)) { + /*! \todo This is hack to prevent memory errors, + * as the rdata_set_items() cannot determine + * items type and so cannot increment + * reference count in case of dname type. + * Free would then release dnames that + * aren't referenced by the rdata. + */ + if (free_all_dnames) { + knot_dname_release((*rdata)->items[i].dname); + } + } + } else { + free((*rdata)->items[i].raw_data); + } + } + + if ((*rdata)->items) { + free((*rdata)->items); + } + free(*rdata); + *rdata = NULL; +} + +/*----------------------------------------------------------------------------*/ +/* CLEANUP */ +//uint knot_rdata_wire_size(const knot_rdata_t *rdata, +// const uint8_t *format) +//{ +// uint size = 0; + +// for (int i = 0; i < rdata->count; ++i) { +// switch (format[i]) { +// case KNOT_RDATA_WF_COMPRESSED_DNAME: +// case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: +// case KNOT_RDATA_WF_LITERAL_DNAME: +// size += knot_dname_size(rdata->items[i].dname); +// break; +// case KNOT_RDATA_WF_BYTE: +// size += 1; +// break; +// case KNOT_RDATA_WF_SHORT: +// size += 2; +// break; +// case KNOT_RDATA_WF_LONG: +// size += 4; +// break; +// case KNOT_RDATA_WF_A: +// size += 4; +// break; +// case KNOT_RDATA_WF_AAAA: +// size += 16; +// break; +// case KNOT_RDATA_WF_BINARY: +// case KNOT_RDATA_WF_APL: // saved as binary +// case KNOT_RDATA_WF_IPSECGATEWAY: // saved as binary +// size += rdata->items[i].raw_data[0]; +// break; +// case KNOT_RDATA_WF_TEXT: +// case KNOT_RDATA_WF_BINARYWITHLENGTH: +// size += rdata->items[i].raw_data[0] + 1; +// break; +// default: +// assert(0); +// } +// } +// return size; +//} + +/*----------------------------------------------------------------------------*/ + +//int knot_rdata_to_wire(const knot_rdata_t *rdata, const uint8_t *format, +// uint8_t *buffer, uint buf_size) +//{ +// uint copied = 0; +// uint8_t tmp[KNOT_MAX_RDATA_WIRE_SIZE]; +// uint8_t *to = tmp; + +// for (int i = 0; i < rdata->count; ++i) { +// assert(copied < KNOT_MAX_RDATA_WIRE_SIZE); + +// const uint8_t *from = (uint8_t *)rdata->items[i].raw_data; +// uint size = 0; + +// switch (format[i]) { +// case KNOT_RDATA_WF_COMPRESSED_DNAME: +// case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: +// case KNOT_RDATA_WF_LITERAL_DNAME: +// size = knot_dname_size(rdata->items[i].dname); +// from = knot_dname_name(rdata->items[i].dname); + +// break; +// case KNOT_RDATA_WF_BYTE: +// size = 1; +// break; +// case KNOT_RDATA_WF_SHORT: +// size = 2; +// break; +// case KNOT_RDATA_WF_LONG: +// size = 4; +// break; +// case KNOT_RDATA_WF_A: +// size = 4; +// break; +// case KNOT_RDATA_WF_AAAA: +// size = 16; +// break; +// case KNOT_RDATA_WF_TEXT: +// case KNOT_RDATA_WF_BINARYWITHLENGTH: +// // size stored in the first two bytes, but in little +// // endian and we need only the lower byte from it +// *to = *from; // lower byte is the first in little endian +// to += 1; +// case KNOT_RDATA_WF_BINARY: +// case KNOT_RDATA_WF_APL: // saved as binary +// case KNOT_RDATA_WF_IPSECGATEWAY: // saved as binary +// // size stored in the first two bytes, those bytes +// // must not be copied +// size = rdata->items[i].raw_data[0]; +// from += 2; // skip the first two bytes +// break; +// default: +// assert(0); +// } + +// assert(size != 0); +// assert(copied + size < KNOT_MAX_RDATA_WIRE_SIZE); + +// memcpy(to, from, size); +// to += size; +// copied += size; +// } + +// if (copied > buf_size) { +// dbg_rdata("Not enough place allocated for function " +// "knot_rdata_to_wire(). Allocated %u, need %u\n", +// buf_size, copied); +// return -1; +// } + +// memcpy(buffer, tmp, copied); +// return 0; +//} + +/*----------------------------------------------------------------------------*/ + +knot_rdata_t *knot_rdata_deep_copy(const knot_rdata_t *rdata, + uint16_t type) +{ + knot_rdata_t *copy = knot_rdata_new(); + CHECK_ALLOC_LOG(copy, NULL); + + + if ((copy->items = (knot_rdata_item_t *)malloc( + rdata->count * sizeof(knot_rdata_item_t))) == NULL) { + knot_rdata_free(©); + ERR_ALLOC_FAILED; + return NULL; + } + + copy->count = rdata->count; + + knot_rrtype_descriptor_t *d = knot_rrtype_descriptor_by_type(type); + + assert(copy->count <= d->length); + + // copy all items one by one + for (int i = 0; i < copy->count; ++i) { + if (d->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME + || d->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME + || d->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME) { + copy->items[i].dname = + knot_dname_deep_copy(rdata->items[i].dname); + } else { + copy->items[i].raw_data = (uint16_t *)malloc( + rdata->items[i].raw_data[0] + 2); + if (copy->items[i].raw_data == NULL) { + knot_rdata_deep_free(©, type, 1); + return NULL; + } + memcpy(copy->items[i].raw_data, + rdata->items[i].raw_data, + rdata->items[i].raw_data[0] + 2); + } + } + + return copy; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rdata_compare(const knot_rdata_t *r1, const knot_rdata_t *r2, + const uint8_t *format) +{ + uint count = (r1->count < r2->count) ? r1->count : r2->count; + + int cmp = 0; + + for (int i = 0; i < count; ++i) { + /* CLEANUP */ +// const uint8_t *data1, *data2; +// int size1, size2; + + if (format[i] == KNOT_RDATA_WF_COMPRESSED_DNAME || + format[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME || + format[i] == KNOT_RDATA_WF_LITERAL_DNAME) { + cmp = knot_dname_compare(r1->items[i].dname, + r2->items[i].dname); +// data1 = knot_dname_name(r1->items[i].dname); +// data2 = knot_dname_name(r2->items[i].dname); +// size1 = knot_dname_size(r2->items[i].dname); +// size2 = knot_dname_size(r2->items[i].dname); + } else { + cmp = knot_rdata_compare_binary( + (uint8_t *)(r1->items[i].raw_data + 1), + (uint8_t *)(r2->items[i].raw_data + 1), + r1->items[i].raw_data[0], + r1->items[i].raw_data[0]); +// data1 = (uint8_t *)(r1->items[i].raw_data + 1); +// data2 = (uint8_t *)(r2->items[i].raw_data + 1); +// size1 = r1->items[i].raw_data[0]; +// size2 = r1->items[i].raw_data[0]; + } + +// cmp = + + if (cmp != 0) { + return cmp; + } + } + + assert(cmp == 0); + return 0; +} + +/*----------------------------------------------------------------------------*/ + +const knot_dname_t *knot_rdata_cname_name(const knot_rdata_t *rdata) +{ + if (rdata->count < 1) { + return NULL; + } + return rdata->items[0].dname; +} + +/*----------------------------------------------------------------------------*/ + +const knot_dname_t *knot_rdata_dname_target(const knot_rdata_t *rdata) +{ + if (rdata->count < 1) { + return NULL; + } + return rdata->items[0].dname; +} + +/*---------------------------------------------------------------------------*/ + +const knot_dname_t *knot_rdata_get_name(const knot_rdata_t *rdata, + uint16_t type) +{ + // iterate over the rdata items or act as if we knew where the name is? + + switch (type) { + case KNOT_RRTYPE_NS: + return knot_rdata_ns_name(rdata); + case KNOT_RRTYPE_MX: + return knot_rdata_mx_name(rdata); + case KNOT_RRTYPE_SRV: + return knot_rdata_srv_name(rdata); + case KNOT_RRTYPE_CNAME: + return knot_rdata_cname_name(rdata); + } + + return NULL; +} + +/*---------------------------------------------------------------------------*/ +int64_t knot_rdata_soa_serial(const knot_rdata_t *rdata) +{ + if (!rdata) { + return -1; + } + + if (rdata->count < 3) { + return -1; + } + + // the number is in network byte order, transform it + return knot_wire_read_u32((uint8_t *)(rdata->items[2].raw_data + 1)); +} + +/*---------------------------------------------------------------------------*/ + +uint32_t knot_rdata_soa_refresh(const knot_rdata_t *rdata) +{ + if (!rdata) { + return 0; + } + + if (rdata->count < 4) { + return 0; /*! \todo Some other error value. */ + } + + // the number is in network byte order, transform it + return knot_wire_read_u32((uint8_t *)(rdata->items[3].raw_data + 1)); +} + +/*---------------------------------------------------------------------------*/ + +uint32_t knot_rdata_soa_retry(const knot_rdata_t *rdata) +{ + if (!rdata) { + return 0; + } + + if (rdata->count < 5) { + return 0; /*! \todo Some other error value. */ + } + + // the number is in network byte order, transform it + return knot_wire_read_u32((uint8_t *)(rdata->items[4].raw_data + 1)); +} + +/*---------------------------------------------------------------------------*/ + +uint32_t knot_rdata_soa_expire(const knot_rdata_t *rdata) +{ + if (!rdata) { + return -1; + } + + if (rdata->count < 6) { + return 0; /*! \todo Some other error value. */ + } + + // the number is in network byte order, transform it + return knot_wire_read_u32((uint8_t *)(rdata->items[5].raw_data + 1)); +} + +/*---------------------------------------------------------------------------*/ + +uint16_t knot_rdata_rrsig_type_covered(const knot_rdata_t *rdata) +{ + if (rdata->count < 1) { + return 0; + } + + return knot_wire_read_u16((uint8_t *)(rdata->items[0].raw_data + 1)); +} diff --git a/src/libknot/rdata.h b/src/libknot/rdata.h new file mode 100644 index 0000000..5d328c9 --- /dev/null +++ b/src/libknot/rdata.h @@ -0,0 +1,339 @@ +/*! + * \file rdata.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Structures representing RDATA and its items and API for manipulating + * both. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_RDATA_H_ +#define _KNOT_RDATA_H_ + +#include <stdint.h> +#include <string.h> + +#include "dname.h" +#include "util/descriptor.h" + +/*----------------------------------------------------------------------------*/ +/*! + * \brief RDATA item structure. + * + * Each RDATA may be logically divided into items, each of possible different + * type. This structure distinguishes between general data (\a raw_data) + * represented as an array of octets, and domain name (\a dname) as domain names + * require special treatment within some RDATA (e.g. compressing in packets). + */ +union knot_rdata_item { + knot_dname_t *dname; /*!< RDATA item represented as a domain name. */ + + /*! + * \brief RDATA item represented as raw array of octets. + * + * The first two bytes hold the length of the item in bytes. The length + * is stored in little endian. + * + * In some cases the stored length is also used in the wire format of + * RDATA (e.g. character-data as defined in RFC1035). In such case, + * the length should be less than 256, so that it fits into one byte + * in the wireformat. + * + * \todo Store the length in system byte order. + */ + uint16_t *raw_data; +}; + +typedef union knot_rdata_item knot_rdata_item_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief RDATA structure. + * + * Each RDATA may be logically divided into items, each of possible different + * type (see knot_rdata_item). This structure stores an array of such items. + * It is not dynamic, so any RDATA structure may hold either 0 or one specified + * number of items which cannot be changed later. However, the number of items + * may be different for each RDATA structure. The number of items should be + * given by descriptors for each RR type. Some types may have variable number + * of items. In such cases, the last item in the array will be set tu NULL + * to distinguish the actual count of items. + * + * This structure does not hold information about the RDATA items, such as + * what type is which item or how long are they. This information should be + * stored elsewhere (in descriptors) as it is RR-specific and given for each + * RR type. + * + * \todo Find out whether NULL is appropriate value. If it is a possible + * value for some of the items, we must find some other way to deal with + * this. + * \todo Add some function for freeing particular item? Or a non-const getter? + */ +struct knot_rdata { + knot_rdata_item_t *items; /*!< RDATA items comprising this RDATA. */ + unsigned int count; /*! < Count of RDATA items in this RDATA. */ + struct knot_rdata *next; /*!< Next RDATA item in a linked list. */ +}; + +typedef struct knot_rdata knot_rdata_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates an empty RDATA structure. + * + * \return Pointer to the new RDATA structure or NULL if an error occured. + */ +knot_rdata_t *knot_rdata_new(); + +/*! + * \brief Parses RDATA from the given data in wire format. + * + * \param rdata RDATA to fill. + * \param wire Wire format of the whole data in which the RDATA are present. + * \param pos Position in \a wire where to start parsing. + * \param total_size Size of the whole data. + * \param rdlength Size of the RDATA to parse in bytes. + * \param desc RR type descriptor for the RDATA type. + * + * \retval KNOT_ENOMEM + * \retval KNOT_EFEWDATA + * \retval KNOT_EMALF + * \retval KNOT_ERROR + * \retval KNOT_EOK + */ +int knot_rdata_from_wire(knot_rdata_t *rdata, const uint8_t *wire, + size_t *pos, size_t total_size, size_t rdlength, + const knot_rrtype_descriptor_t *desc); + +/*! + * \brief Sets the RDATA item on position \a pos. + * + * \param rdata RDATA structure in which the item should be set. + * \param pos Position of the RDATA item to be set. + * \param item RDATA item value to be set. + * + * \retval KNOT_EOK if successful. + * \retval KNOT_EBADARG if \a pos is not a valid position. + * + * \todo Use the union or a pointer to it as parameter? IMHO there is always + * only one pointer that is copied, so it doesn't matter. + */ +int knot_rdata_set_item(knot_rdata_t *rdata, unsigned int pos, + knot_rdata_item_t item); + +/*! + * \brief Sets all RDATA items within the given RDATA structure. + * + * \a rdata must be empty so far (\a rdata->count == 0). The necessary space + * is allocated. + * + * This function copies the array of RDATA items from \a items to \a rdata. + * + * \param rdata RDATA structure to store the items in. + * \param items An array of RDATA items to be stored in this RDATA structure. + * \param count Count of RDATA items to be stored. + * + * \retval 0 if successful. + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_rdata_set_items(knot_rdata_t *rdata, + const knot_rdata_item_t *items, + unsigned int count); + +unsigned int knot_rdata_item_count(const knot_rdata_t *rdata); + +/*! + * \brief Returns the RDATA item on position \a pos. + * + * \note Although returning union would be OK (no overhead), we need to be able + * to distinguish errors (in this case by returning NULL). + * + * \param rdata RDATA structure to get the item from. + * \param pos Position of the item to retrieve. + * + * \return The RDATA item on position \a pos, or NULL if such position does not + * exist within the given RDATA structure. + */ +knot_rdata_item_t *knot_rdata_get_item(const knot_rdata_t *rdata, + unsigned int pos); + +/*! + * \brief Returns the RDATA item on position \a pos. + * + * \note Although returning union would be OK (no overhead), we need to be able + * to distinguish errors (in this case by returning NULL). + * \note This function is identical to knot_rdata_get_item(), only it returns + * constant data. + * + * \param rdata RDATA structure to get the item from. + * \param pos Position of the item to retrieve. + * + * \return The RDATA item on position \a pos, or NULL if such position does not + * exist within the given RDATA structure. + */ +const knot_rdata_item_t *knot_rdata_item(const knot_rdata_t *rdata, + unsigned int pos); + +/*! + * \brief Sets the given domain name as a value of RDATA item on position + * \a pos. + * + * \param rdata RDATA structure to set the item in. + * \param pos Position of the RDATA item to set. + * \param dname Domain name to set to the item. + * + * \retval KNOT_EOK if successful. + * \retval KNOT_EBADARG + */ +int knot_rdata_item_set_dname(knot_rdata_t *rdata, unsigned int pos, + knot_dname_t *dname); + +/*! + * \brief Sets the given raw data as a value of RDATA item on position \a pos. + * + * \param rdata RDATA structure to set the item in. + * \param pos Position of the RDATA item to set. + * \param raw_data Raw data to set to the item. + * + * \retval KNOT_EOK if successful. + * \retval KNOT_EBADARG + */ +int knot_rdata_item_set_raw_data(knot_rdata_t *rdata, unsigned int pos, + uint16_t *raw_data); + +/*! + * \brief Copies the given RDATA. + * + * \param rdata RDATA to copy. + * \param type RR type of the RDATA. + * + * \return Copy of \a rdata. + */ +knot_rdata_t *knot_rdata_deep_copy(const knot_rdata_t *rdata, + uint16_t type); + +/*! + * \brief Destroys the RDATA structure without deleting RDATA items. + * + * Also sets the given pointer to NULL. + * + * \param rdata RDATA structure to be destroyed. + */ +void knot_rdata_free(knot_rdata_t **rdata); + +/*! + * \brief Destroys the RDATA structure and all its RDATA items. + * + * RDATA items are deleted according to the given RR Type. In case of domain + * name, it is deallocated only if either the free_all_dnames parameter is set + * to <> 0 or the name does not contain reference to a node (i.e. it is not an + * owner of some node) or if it does contain a reference to a node, but is + * not equal to its owner. (If free_all_dnames is set to <> 0, no other + * condition is evaluated.) + * + * Also sets the given pointer to NULL. + * + * \param rdata RDATA structure to be destroyed. + * \param type RR Type of the RDATA. + * \param free_all_dnames Set to <> 0 if you want to delete ALL domain names + * from the RDATA. Set to 0 otherwise. + */ +void knot_rdata_deep_free(knot_rdata_t **rdata, unsigned int type, + int free_all_dnames); + +/*! + * \brief Compares two RDATAs of the same type. + * + * \note Compares domain names normally (dname_compare()), i.e. + * case-insensitive. + * + * \param r1 First RDATA. + * \param r2 Second RDATA. + * \param format Descriptor of the RDATA format. + * + * \retval 0 if RDATAs are equal. + * \retval < 0 if \a r1 goes before \a r2 in canonical order. + * \retval > 0 if \a r1 goes after \a r2 in canonical order. + */ +int knot_rdata_compare(const knot_rdata_t *r1, const knot_rdata_t *r2, + const uint8_t *format); + +/*! + * \brief Retrieves the domain name from CNAME RDATA. + * + * \note This is only convenience function. It does not (and cannot) check if + * the given RDATA is of the right type, so it always returns the first + * RDATA item, even if it is not a domain name. + * + * \param rdata RDATA to get the CNAME domain name from. + * + * \return Canonical name stored in \a rdata or NULL if \a rdata has no items. + */ +const knot_dname_t *knot_rdata_cname_name(const knot_rdata_t *rdata); + +/*! + * \brief Retrieves the domain name from DNAME RDATA. + * + * \note This is only convenience function. It does not (and cannot) check if + * the given RDATA is of the right type, so it always returns the first + * RDATA item, even if it is not a domain name. + * + * \param rdata RDATA to get the DNAME domain name from. + * + * \return Target domain name stored in \a rdata or NULL if \a rdata has no + * items. + */ +const knot_dname_t *knot_rdata_dname_target(const knot_rdata_t *rdata); + +/*! + * \brief Retrieves the domain name from RDATA of given type. + * + * Supported types: + * - KNOT_RRTYPE_NS + * - KNOT_RRTYPE_MX + * - KNOT_RRTYPE_SRV + * - KNOT_RRTYPE_CNAME + * + * \note This is only convenience function. It does not (and cannot) check if + * the given RDATA is of the right type, so it always returns the RDATA + * item according to the given type, even if it is not a domain name. + * + * \param rdata RDATA to get the domain name from. + * \param type RR type of the RDATA. + * + * \return Domain name stored in \a rdata or NULL if \a rdata has not enough + * items. + */ +const knot_dname_t *knot_rdata_get_name(const knot_rdata_t *rdata, + uint16_t type); + +int64_t knot_rdata_soa_serial(const knot_rdata_t *rdata); + +uint32_t knot_rdata_soa_refresh(const knot_rdata_t *rdata); +uint32_t knot_rdata_soa_retry(const knot_rdata_t *rdata); +uint32_t knot_rdata_soa_expire(const knot_rdata_t *rdata); + +uint16_t knot_rdata_rrsig_type_covered(const knot_rdata_t *rdata); + +#endif /* _KNOT_RDATA_H */ + +/*! @} */ diff --git a/src/libknot/rrset.c b/src/libknot/rrset.c new file mode 100644 index 0000000..6083f77 --- /dev/null +++ b/src/libknot/rrset.c @@ -0,0 +1,719 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdint.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> + +#include "common.h" +#include "rrset.h" +#include "util/descriptor.h" +#include "util/error.h" +#include "util/utils.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ + +static void knot_rrset_disconnect_rdata(knot_rrset_t *rrset, + knot_rdata_t *prev, knot_rdata_t *rdata) +{ + if (prev == NULL) { + // find the previous RDATA in the series, as its pointer must + // be changed + prev = rdata->next; + while (prev->next != rdata) { + prev = prev->next; + } + } + + assert(prev); + assert(prev->next == rdata); + + prev->next = rdata->next; + + if (rrset->rdata == rdata) { + if (rdata->next == rdata) { + rrset->rdata = NULL; + } else { + rrset->rdata = rdata->next; + } + } +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_rrset_t *knot_rrset_new(knot_dname_t *owner, uint16_t type, + uint16_t rclass, uint32_t ttl) +{ + knot_rrset_t *ret = malloc(sizeof(knot_rrset_t)); + if (ret == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + ret->rdata = NULL; + + /* Retain reference to owner. */ + knot_dname_retain(owner); + + ret->owner = owner; + ret->type = type; + ret->rclass = rclass; + ret->ttl = ttl; + ret->rrsigs = NULL; + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_add_rdata(knot_rrset_t *rrset, knot_rdata_t *rdata) +{ + if (rrset == NULL || rdata == NULL) { + return KNOT_EBADARG; + } + + if (rrset->rdata == NULL) { + rrset->rdata = rdata; + rrset->rdata->next = rrset->rdata; + } else { + knot_rdata_t *tmp; + + tmp = rrset->rdata; + + while (tmp->next != rrset->rdata) { + tmp = tmp->next; + } + rdata->next = tmp->next; + tmp->next = rdata; + } + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +knot_rdata_t *knot_rrset_remove_rdata(knot_rrset_t *rrset, + const knot_rdata_t *rdata) +{ + if (rrset == NULL || rdata == NULL) { + return NULL; + } + + knot_rdata_t *prev = NULL; + knot_rdata_t *rr = rrset->rdata; + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrset->type); + + if (desc == NULL) { + return NULL; + } + + while (rr != NULL) { + /*! \todo maybe the dnames should be compared case-insensitive*/ + if (knot_rdata_compare(rr, rdata, desc->wireformat) == 0) { + knot_rrset_disconnect_rdata(rrset, prev, rr); + return rr; + } + prev = rr; + rr = knot_rrset_rdata_get_next(rrset, rr); + } + + return NULL; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_set_rrsigs(knot_rrset_t *rrset, knot_rrset_t *rrsigs) +{ + if (rrset == NULL) { + return KNOT_EBADARG; + } + + rrset->rrsigs = rrsigs; + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_add_rrsigs(knot_rrset_t *rrset, knot_rrset_t *rrsigs, + knot_rrset_dupl_handling_t dupl) +{ + if (rrset == NULL || rrsigs == NULL + || knot_dname_compare(rrset->owner, rrsigs->owner) != 0) { + return KNOT_EBADARG; + } + + int rc; + if (rrset->rrsigs != NULL) { + if (dupl == KNOT_RRSET_DUPL_MERGE) { + rc = knot_rrset_merge((void **)&rrset->rrsigs, + (void **)&rrsigs); + if (rc != KNOT_EOK) { + return rc; + } else { + return 1; + } + } else if (dupl == KNOT_RRSET_DUPL_SKIP) { + return 2; + } else if (dupl == KNOT_RRSET_DUPL_REPLACE) { + rrset->rrsigs = rrsigs; + } + } else { + rrset->rrsigs = rrsigs; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +const knot_dname_t *knot_rrset_owner(const knot_rrset_t *rrset) +{ + return rrset->owner; +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_rrset_get_owner(const knot_rrset_t *rrset) +{ + return rrset->owner; +} + +/*----------------------------------------------------------------------------*/ + +void knot_rrset_set_owner(knot_rrset_t *rrset, knot_dname_t* owner) +{ + if (rrset) { + /* Retain new owner and release old owner. */ + knot_dname_retain(owner); + knot_dname_release(rrset->owner); + rrset->owner = owner; + } +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_rrset_type(const knot_rrset_t *rrset) +{ + return rrset->type; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_rrset_class(const knot_rrset_t *rrset) +{ + return rrset->rclass; +} + +/*----------------------------------------------------------------------------*/ + +uint32_t knot_rrset_ttl(const knot_rrset_t *rrset) +{ + return rrset->ttl; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rdata_t *knot_rrset_rdata(const knot_rrset_t *rrset) +{ + return rrset->rdata; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rdata_t *knot_rrset_rdata_next(const knot_rrset_t *rrset, + const knot_rdata_t *rdata) +{ + if (rdata == NULL) { + return rrset->rdata; + } + if (rdata->next == rrset->rdata) { + return NULL; + } else { + return rdata->next; + } +} + +/*----------------------------------------------------------------------------*/ + +knot_rdata_t *knot_rrset_get_rdata(knot_rrset_t *rrset) +{ + if (rrset == NULL) { + return NULL; + } else { + return rrset->rdata; + } +} + +/*----------------------------------------------------------------------------*/ + +knot_rdata_t *knot_rrset_rdata_get_next(knot_rrset_t *rrset, + knot_rdata_t *rdata) +{ + if (rdata->next == rrset->rdata) { + return NULL; + } else { + return rdata->next; + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_rdata_rr_count(const knot_rrset_t *rrset) +{ + int count = 0; + const knot_rdata_t *rdata = rrset->rdata; + + while (rdata != NULL) { + ++count; + rdata = knot_rrset_rdata_next(rrset, rdata); + } + + return count; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rrset_t *knot_rrset_rrsigs(const knot_rrset_t *rrset) +{ + if (rrset == NULL) { + assert(0); + return NULL; + } else { + return rrset->rrsigs; + } +} + +/*----------------------------------------------------------------------------*/ + +knot_rrset_t *knot_rrset_get_rrsigs(knot_rrset_t *rrset) +{ + if (rrset == NULL) { + assert(0); + return NULL; + } else { + return rrset->rrsigs; + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_compare_rdata(const knot_rrset_t *r1, const knot_rrset_t *r2) +{ + if (r1 == NULL || r2 == NULL) { + return KNOT_EBADARG; + } + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(r1->type); + if (desc == NULL) { + return KNOT_EBADARG; + } + + // compare RDATA sets (order is not significant) + const knot_rdata_t *rdata1= knot_rrset_rdata(r1); + const knot_rdata_t *rdata2; + + // find all RDATA from r1 in r2 + while (rdata1 != NULL) { + rdata2 = knot_rrset_rdata(r2); + while (rdata2 != NULL && knot_rdata_compare(rdata1, rdata2, + desc->wireformat)) { + rdata2 = knot_rrset_rdata_next(r2, rdata2); + } + + if (rdata2 == NULL) { + // RDATA from r1 not found in r2 + return 0; + } + + // otherwise it was found, continue with next r1 RDATA + rdata1 = knot_rrset_rdata_next(r1, rdata1); + } + + // find all RDATA from r2 in r1 (this can be done in a better way) + rdata2 = knot_rrset_rdata(r2); + while (rdata2 != NULL) { + rdata1 = knot_rrset_rdata(r1); + while (rdata2 != NULL && knot_rdata_compare(rdata1, rdata2, + desc->wireformat)) { + rdata1 = knot_rrset_rdata_next(r1, rdata1); + } + + if (rdata1 == NULL) { + // RDATA from r1 not found in r2 + return 0; + } + + // otherwise it was found, continue with next r1 RDATA + rdata2 = knot_rrset_rdata_next(r2, rdata2); + } + + // all RDATA found + return 1; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_rrset_rr_to_wire(const knot_rrset_t *rrset, + const knot_rdata_t *rdata, uint8_t **pos, + size_t max_size) +{ + int size = 0; + + assert(rrset != NULL); + assert(rrset->owner != NULL); + assert(rdata != NULL); + assert(pos != NULL); + assert(*pos != NULL); + + fprintf(stderr, "Max size: %zu, owner: %p, owner size: %d\n", + max_size, rrset->owner, rrset->owner->size); + + // check if owner fits + if (size + knot_dname_size(rrset->owner) + 10 > max_size) { + return KNOT_ESPACE; + } + + memcpy(*pos, knot_dname_name(rrset->owner), + knot_dname_size(rrset->owner)); + *pos += knot_dname_size(rrset->owner); + size += knot_dname_size(rrset->owner); + + fprintf(stderr, "Max size: %zu, size: %d\n", max_size, size); + + fprintf(stderr, "Wire format:\n"); + + // put rest of RR 'header' + knot_wire_write_u16(*pos, rrset->type); + fprintf(stderr, " Type: %u\n", rrset->type); + *pos += 2; + + knot_wire_write_u16(*pos, rrset->rclass); + fprintf(stderr, " Class: %u\n", rrset->rclass); + *pos += 2; + + knot_wire_write_u32(*pos, rrset->ttl); + fprintf(stderr, " TTL: %u\n", rrset->ttl); + *pos += 4; + + // save space for RDLENGTH + uint8_t *rdlength_pos = *pos; + *pos += 2; + + size += 10; +// compr->wire_pos += size; + + fprintf(stderr, "Max size: %zu, size: %d\n", max_size, size); + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrset->type); + + uint16_t rdlength = 0; + + for (int i = 0; i < rdata->count; ++i) { + if (max_size < size + rdlength) { + return KNOT_ESPACE; + } + + switch (desc->wireformat[i]) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: { + knot_dname_t *dname = + knot_rdata_item(rdata, i)->dname; + if (size + rdlength + dname->size > max_size) { + return KNOT_ESPACE; + } + + // save whole domain name + memcpy(*pos, knot_dname_name(dname), + knot_dname_size(dname)); + fprintf(stderr, "Uncompressed dname size: %d\n", + knot_dname_size(dname)); + *pos += knot_dname_size(dname); + rdlength += knot_dname_size(dname); +// compr->wire_pos += dname->size; + break; + } + default: { + uint16_t *raw_data = + knot_rdata_item(rdata, i)->raw_data; + + if (size + rdlength + raw_data[0] > max_size) { + return KNOT_ESPACE; + } + + // copy just the rdata item data (without size) + memcpy(*pos, raw_data + 1, raw_data[0]); + fprintf(stderr, "Raw data size: %d\n", raw_data[0]); + *pos += raw_data[0]; + rdlength += raw_data[0]; +// compr->wire_pos += raw_data[0]; + break; + } + } + } + + fprintf(stderr, "Max size: %zu, size: %d\n", max_size, size); + + assert(size + rdlength <= max_size); + size += rdlength; + knot_wire_write_u16(rdlength_pos, rdlength); + + return size; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_to_wire(const knot_rrset_t *rrset, uint8_t *wire, size_t *size, + int *rr_count) +{ + // if no RDATA in RRSet, return + if (rrset->rdata == NULL) { + *size = 0; + *rr_count = 0; + return KNOT_EOK; + } + + + uint8_t *pos = wire; + int rrs = 0; + short rrset_size = 0; + + const knot_rdata_t *rdata = rrset->rdata; + do { + int ret = knot_rrset_rr_to_wire(rrset, rdata, &pos, + *size - rrset_size); + + assert(ret != 0); + + if (ret < 0) { + // some RR didn't fit in, so no RRs should be used + // TODO: remove last entries from compression table + fprintf(stderr, "Some RR didn't fit in.\n"); + return KNOT_ESPACE; + } + + fprintf(stderr, "RR of size %d added.\n", ret); + rrset_size += ret; + ++rrs; + } while ((rdata = knot_rrset_rdata_next(rrset, rdata)) != NULL); + + // the whole RRSet did fit in + assert(rrset_size <= *size); + assert(pos - wire == rrset_size); + *size = rrset_size; + + fprintf(stderr, " Size after: %zu\n", *size); + + *rr_count = rrs; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_compare(const knot_rrset_t *r1, + const knot_rrset_t *r2, + knot_rrset_compare_type_t cmp) +{ + if (cmp == KNOT_RRSET_COMPARE_PTR) { + return (r1 == r2); + } + + int res = ((r1->rclass == r2->rclass) + && (r1->type == r2->type) + && (r1->ttl == r2->ttl) + && knot_dname_compare(r1->owner, r2->owner) == 0); + + if (cmp == KNOT_RRSET_COMPARE_WHOLE && res) { + res = knot_rrset_compare_rdata(r1, r2); + if (res < 0) { + return 0; + } + } + + return res; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_deep_copy(const knot_rrset_t *from, knot_rrset_t **to) +{ + if (from == NULL || to == NULL) { + return KNOT_EBADARG; + } + + int ret; + + *to = (knot_rrset_t *)calloc(1, sizeof(knot_rrset_t)); + CHECK_ALLOC_LOG(*to, KNOT_ENOMEM); + + (*to)->owner = knot_dname_deep_copy(from->owner); + (*to)->rclass = from->rclass; + (*to)->ttl = from->ttl; + (*to)->type = from->type; + if (from->rrsigs != NULL) { + ret = knot_rrset_deep_copy(from->rrsigs, &(*to)->rrsigs); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(to, 1, 0, 0); + return ret; + } + } + assert((*to)->rrsigs == NULL || from->rrsigs != NULL); + + const knot_rdata_t *rdata = knot_rrset_rdata(from); + + /*! \note Order of RDATA will be reversed. */ + while (rdata != NULL) { + ret = knot_rrset_add_rdata(*to, knot_rdata_deep_copy(rdata, + knot_rrset_type(from))); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(to, 1, 1, 1); + return ret; + } + rdata = knot_rrset_rdata_next(from, rdata); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_shallow_copy(const knot_rrset_t *from, knot_rrset_t **to) +{ + *to = (knot_rrset_t *)malloc(sizeof(knot_rrset_t)); + CHECK_ALLOC_LOG(*to, KNOT_ENOMEM); + + memcpy(*to, from, sizeof(knot_rrset_t)); + + /* Retain owner. */ + knot_dname_retain((*to)->owner); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_rrset_free(knot_rrset_t **rrset) +{ + if (rrset == NULL || *rrset == NULL) { + return; + } + + /*! \todo Shouldn't we always release owner reference? */ + knot_dname_release((*rrset)->owner); + + free(*rrset); + *rrset = NULL; +} + +/*----------------------------------------------------------------------------*/ + +void knot_rrset_deep_free(knot_rrset_t **rrset, int free_owner, + int free_rdata, int free_rdata_dnames) +{ + if (rrset == NULL || *rrset == NULL) { + return; + } + + if (free_rdata) { + knot_rdata_t *tmp_rdata; + knot_rdata_t *next_rdata; + tmp_rdata = (*rrset)->rdata; + + while ((tmp_rdata != NULL) + && (tmp_rdata->next != (*rrset)->rdata) + && (tmp_rdata->next != NULL)) { + next_rdata = tmp_rdata->next; + knot_rdata_deep_free(&tmp_rdata, (*rrset)->type, + free_rdata_dnames); + tmp_rdata = next_rdata; + } + + assert(tmp_rdata == NULL + || tmp_rdata->next == (*rrset)->rdata); + + knot_rdata_deep_free(&tmp_rdata, (*rrset)->type, + free_rdata_dnames); + } + + // RRSIGs should have the same owner as this RRSet, so do not delete it + if ((*rrset)->rrsigs != NULL) { + knot_rrset_deep_free(&(*rrset)->rrsigs, 0, 1, + free_rdata_dnames); + } + + /*! \todo Release owner every time? */ + //if (free_owner) { + knot_dname_release((*rrset)->owner); + //} + + free(*rrset); + *rrset = NULL; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_merge(void **r1, void **r2) +{ + knot_rrset_t *rrset1 = (knot_rrset_t *)(*r1); + knot_rrset_t *rrset2 = (knot_rrset_t *)(*r2); + + if ((knot_dname_compare(rrset1->owner, rrset2->owner) != 0) + || rrset1->rclass != rrset2->rclass + || rrset1->type != rrset2->type + || rrset1->ttl != rrset2->ttl) { + return KNOT_EBADARG; + } + + // add all RDATAs from rrset2 to rrset1 (i.e. concatenate linked lists) + + // no RDATA in RRSet 1 + assert(rrset1 && rrset2); + if (rrset1->rdata == NULL) { + rrset1->rdata = rrset2->rdata; + return KNOT_EOK; + } + + knot_rdata_t *tmp_rdata = rrset1->rdata; + + if (!tmp_rdata) { + return KNOT_EOK; + } + + while (tmp_rdata->next != rrset1->rdata) { + tmp_rdata = tmp_rdata->next; + } + + tmp_rdata->next = rrset2->rdata; + + tmp_rdata = rrset2->rdata; //maybe unnecessary, but is clearer + + while (tmp_rdata->next != rrset2->rdata) { + tmp_rdata = tmp_rdata->next; + } + + tmp_rdata->next = rrset1->rdata; + + return KNOT_EOK; +} diff --git a/src/libknot/rrset.h b/src/libknot/rrset.h new file mode 100644 index 0000000..7754c7f --- /dev/null +++ b/src/libknot/rrset.h @@ -0,0 +1,306 @@ +/*! + * \file rrset.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief RRSet structure and API for manipulating it. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_RRSET_H_ +#define _KNOT_RRSET_H_ + +#include <stdint.h> + +#include "dname.h" +#include "rdata.h" + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure for representing an RRSet. + * + * For definition of a RRSet see RFC2181, Section 5. + * + * As all RRs within a RRSet share the same OWNER, TYPE, CLASS and TTL (see + * Section 5.2 of RFC2181), there is no need to duplicate these data in the + * program. Distinct Resource Records are thus represented only as distinct + * RDATA sections of corresponding RRs. + */ +struct knot_rrset { + /*! \brief Domain name being the owner of the RRSet. */ + knot_dname_t *owner; + uint16_t type; /*!< TYPE of the RRset. */ + uint16_t rclass; /*!< CLASS of the RRSet. */ + uint32_t ttl; /*!< TTL of the RRSet. */ + /*! + * \brief First item in an ordered cyclic list of RDATA items. + * + * \note The fact that the list is cyclic will easily allow for + * possible round-robin rotation of RRSets. + */ + knot_rdata_t *rdata; + struct knot_rrset *rrsigs; /*!< Set of RRSIGs covering this RRSet. */ +}; + +typedef struct knot_rrset knot_rrset_t; + +/*----------------------------------------------------------------------------*/ + +typedef enum { + KNOT_RRSET_COMPARE_PTR, + KNOT_RRSET_COMPARE_HEADER, + KNOT_RRSET_COMPARE_WHOLE +} knot_rrset_compare_type_t; + +typedef enum { + KNOT_RRSET_DUPL_MERGE, + KNOT_RRSET_DUPL_REPLACE, + KNOT_RRSET_DUPL_SKIP +} knot_rrset_dupl_handling_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a new RRSet with the given properties. + * + * The created RRSet contains no RDATAs (i.e. is actually empty). + * + * \param owner OWNER of the RRSet. + * \param type TYPE of the RRSet. + * \param rclass CLASS of the RRSet. + * \param ttl TTL of the RRset. + * + * \return New RRSet structure with the given OWNER, TYPE, CLASS and TTL or NULL + * if an error occured. + */ +knot_rrset_t *knot_rrset_new(knot_dname_t *owner, uint16_t type, + uint16_t rclass, uint32_t ttl); + +/*! + * \brief Adds the given RDATA to the RRSet. + * + * \param rrset RRSet to add the RDATA to. + * \param rdata RDATA to add to the RRSet. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * + * \todo Provide some function for comparing RDATAs. + */ +int knot_rrset_add_rdata(knot_rrset_t *rrset, knot_rdata_t *rdata); + +knot_rdata_t * knot_rrset_remove_rdata(knot_rrset_t *rrset, + const knot_rdata_t *rdata); + +/*! + * \brief Adds RRSIG signatures to this RRSet. + * + * \param rrset RRSet to add the signatures into. + * \param rrsigs Set of RRSIGs covering this RRSet. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_rrset_set_rrsigs(knot_rrset_t *rrset, knot_rrset_t *rrsigs); + +int knot_rrset_add_rrsigs(knot_rrset_t *rrset, knot_rrset_t *rrsigs, + knot_rrset_dupl_handling_t dupl); + +/*! + * \brief Returns the Owner of the RRSet. + * + * \param rrset RRSet to get the Owner of. + * + * \return Owner of the given RRSet. + */ +const knot_dname_t *knot_rrset_owner(const knot_rrset_t *rrset); + +/*! + * \todo Document me. + */ +knot_dname_t *knot_rrset_get_owner(const knot_rrset_t *rrset); + +/*! + * \brief Set rrset owner to specified dname. + * + * Previous owner will be replaced if exist. + * + * \param rrset Specified RRSet. + * \param owner New owner dname. + */ +void knot_rrset_set_owner(knot_rrset_t *rrset, knot_dname_t* owner); + +/*! + * \brief Returns the TYPE of the RRSet. + * + * \param rrset RRSet to get the TYPE of. + * + * \return TYPE of the given RRSet. + */ +uint16_t knot_rrset_type(const knot_rrset_t *rrset); + +/*! + * \brief Returns the CLASS of the RRSet. + * + * \param rrset RRSet to get the CLASS of. + * + * \return CLASS of the given RRSet. + */ +uint16_t knot_rrset_class(const knot_rrset_t *rrset); + +/*! + * \brief Returns the TTL of the RRSet. + * + * \param rrset RRSet to get the TTL of. + * + * \return TTL of the given RRSet. + */ +uint32_t knot_rrset_ttl(const knot_rrset_t *rrset); + +/*! + * \brief Returns the first RDATA in the RRSet. + * + * RDATAs in a RRSet are stored in a ordered cyclic list. + * + * \note If later a round-robin rotation of RRSets is employed, the RDATA + * returned by this function may not be the first RDATA in canonical + * order. + * + * \param rrset RRSet to get the RDATA from. + * + * \return First RDATA in the given RRSet. + */ +const knot_rdata_t *knot_rrset_rdata(const knot_rrset_t *rrset); + +const knot_rdata_t *knot_rrset_rdata_next(const knot_rrset_t *rrset, + const knot_rdata_t *rdata); + +/*! + * \brief Returns the first RDATA in the RRSet (non-const version). + * + * RDATAs in a RRSet are stored in a ordered cyclic list. + * + * \note If later a round-robin rotation of RRSets is employed, the RDATA + * returned by this function may not be the first RDATA in canonical + * order. + * + * \param rrset RRSet to get the RDATA from. + * + * \return First RDATA in the given RRSet or NULL if there is none or if no + * rrset was provided (\a rrset is NULL). + */ +knot_rdata_t *knot_rrset_get_rdata(knot_rrset_t *rrset); + +knot_rdata_t *knot_rrset_rdata_get_next(knot_rrset_t *rrset, + knot_rdata_t *rdata); + +int knot_rrset_rdata_rr_count(const knot_rrset_t *rrset); + +/*! + * \brief Returns the set of RRSIGs covering the given RRSet. + * + * \param rrset RRSet to get the signatures for. + * + * \return Set of RRSIGs which cover the given RRSet or NULL if there is none or + * if no rrset was provided (\a rrset is NULL). + */ +const knot_rrset_t *knot_rrset_rrsigs(const knot_rrset_t *rrset); + +knot_rrset_t *knot_rrset_get_rrsigs(knot_rrset_t *rrset); + +int knot_rrset_compare_rdata(const knot_rrset_t *r1, const knot_rrset_t *r2); + +int knot_rrset_to_wire(const knot_rrset_t *rrset, uint8_t *wire, size_t *size, + int *rr_count); + +/*! + * \brief Compares two RRSets. + * + * \note This function does not return 'standard' compare return values, because + * there is no way to define which RRSet is 'larger'. + * + * \param r1 First RRSet. + * \param r2 Second RRSet. + * \param cmp Type of comparison to perform. + * + * \retval <> 0 If RRSets are equal. + * \retval 0 if RRSets are not equal. + */ +int knot_rrset_compare(const knot_rrset_t *r1, + const knot_rrset_t *r2, + knot_rrset_compare_type_t cmp); + +/*! \todo Add unit test. */ +int knot_rrset_deep_copy(const knot_rrset_t *from, knot_rrset_t **to); + +/*! \todo Add unit test. */ +int knot_rrset_shallow_copy(const knot_rrset_t *from, knot_rrset_t **to); + +/*! + * \brief Destroys the RRSet structure. + * + * Does not destroy the OWNER domain name structure, nor the signatures, as + * these may be used elsewhere. + * + * Does not destroy RDATA structures neither, as they need special processing. + * + * Also sets the given pointer to NULL. + * + * \param rrset RRset to be destroyed. + */ +void knot_rrset_free(knot_rrset_t **rrset); + +/*! + * \brief Destroys the RRSet structure and all its substructures. + * + * Also sets the given pointer to NULL. + * + * \param rrset RRset to be destroyed. + * \param free_owner Set to 0 if you do not want the owner domain name to be + * destroyed also. Set to <> 0 otherwise. + * \param free_rdata ***\todo DOCUMENT ME*** + * \param free_rdata_dnames Set to <> 0 if you want to delete ALL domain names + * present in RDATA. Set to 0 otherwise. (See + * knot_rdata_deep_free().) + */ +void knot_rrset_deep_free(knot_rrset_t **rrset, int free_owner, + int free_rdata, int free_rdata_dnames); + +/*! + * \brief Merges two RRSets. + * + * Merges \a r1 into \a r2 by concatenating the list of RDATAs in \a r2 after + * the list of RDATAs in \a r1. \a r2 is unaffected by this, though you must not + * destroy the RDATAs in \a r2 as they are now also in \a r1. (You may use + * function knot_rrset_free() though, as it does not touch RDATAs). + * + * \note Member \a rrsigs is preserved from the first RRSet. + * + * \param r1 Pointer to RRSet to be merged into. + * \param r2 Poitner to RRSet to be merged. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG if the RRSets could not be merged, because their + * Owner, Type, Class or TTL does not match. + */ +int knot_rrset_merge(void **r1, void **r2); + +#endif /* _KNOT_RRSET_H_ */ + +/*! @} */ diff --git a/src/libknot/tsig-op.c b/src/libknot/tsig-op.c new file mode 100644 index 0000000..3178a23 --- /dev/null +++ b/src/libknot/tsig-op.c @@ -0,0 +1,1089 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdint.h> +#include <openssl/hmac.h> +#include <openssl/evp.h> +#include <time.h> +#include <ctype.h> + +#include "common.h" +#include "tsig.h" +#include "tsig-op.h" +#include "util/wire.h" +#include "util/error.h" +#include "util/debug.h" + + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + + +static int b64rmap_initialized = 0; +static uint8_t b64rmap[256]; + +static const uint8_t b64rmap_special = 0xf0; +static const uint8_t b64rmap_end = 0xfd; +static const uint8_t b64rmap_space = 0xfe; +static const uint8_t b64rmap_invalid = 0xff; + +/** + * Initializing the reverse map is not thread safe. + * Which is fine for NSD. For now... + **/ +void b64_initialize_rmap() +{ + int i; + char ch; + + /* Null: end of string, stop parsing */ + b64rmap[0] = b64rmap_end; + + for (i = 1; i < 256; ++i) { + ch = (char)i; + /* Whitespaces */ + if (isspace(ch)) { + b64rmap[i] = b64rmap_space; + } + /* Padding: stop parsing */ + else if (ch == Pad64) { + b64rmap[i] = b64rmap_end; + } + /* Non-base64 char */ + else { + b64rmap[i] = b64rmap_invalid; + } + } + + /* Fill reverse mapping for base64 chars */ + for (i = 0; Base64[i] != '\0'; ++i) { + b64rmap[(uint8_t)Base64[i]] = i; + } + + b64rmap_initialized = 1; +} + +int b64_pton_do(char const *src, uint8_t *target, size_t targsize) +{ + int tarindex, state, ch; + uint8_t ofs; + + state = 0; + tarindex = 0; + + while (1) { + ch = *src++; + ofs = b64rmap[ch]; + + if (ofs >= b64rmap_special) { + /* Ignore whitespaces */ + if (ofs == b64rmap_space) { + continue; + } + /* End of base64 characters */ + if (ofs == b64rmap_end) { + break; + } + /* A non-base64 character. */ + return (-1); + } + + switch (state) { + case 0: + if ((size_t)tarindex >= targsize) { + return (-1); + } + target[tarindex] = ofs << 2; + state = 1; + break; + case 1: + if ((size_t)tarindex + 1 >= targsize) { + return (-1); + } + target[tarindex] |= ofs >> 4; + target[tarindex+1] = (ofs & 0x0f) + << 4 ; + tarindex++; + state = 2; + break; + case 2: + if ((size_t)tarindex + 1 >= targsize) { + return (-1); + } + target[tarindex] |= ofs >> 2; + target[tarindex+1] = (ofs & 0x03) + << 6; + tarindex++; + state = 3; + break; + case 3: + if ((size_t)tarindex >= targsize) { + return (-1); + } + target[tarindex] |= ofs; + tarindex++; + state = 0; + break; + default: + abort(); + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (b64rmap[ch] != b64rmap_space) { + break; + } + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) { + return (-1); + } + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (b64rmap[ch] != b64rmap_space) { + return (-1); + } + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target[tarindex] != 0) { + return (-1); + } + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) { + return (-1); + } + } + + return (tarindex); +} + + +int b64_pton_len(char const *src) +{ + int tarindex, state, ch; + uint8_t ofs; + + state = 0; + tarindex = 0; + + while (1) { + ch = *src++; + ofs = b64rmap[ch]; + + if (ofs >= b64rmap_special) { + /* Ignore whitespaces */ + if (ofs == b64rmap_space) { + continue; + } + /* End of base64 characters */ + if (ofs == b64rmap_end) { + break; + } + /* A non-base64 character. */ + return (-1); + } + + switch (state) { + case 0: + state = 1; + break; + case 1: + tarindex++; + state = 2; + break; + case 2: + tarindex++; + state = 3; + break; + case 3: + tarindex++; + state = 0; + break; + default: + abort(); + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (b64rmap[ch] != b64rmap_space) { + break; + } + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) { + return (-1); + } + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (b64rmap[ch] != b64rmap_space) { + return (-1); + } + + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) { + return (-1); + } + } + + return (tarindex); +} + +int b64_pton(char const *src, uint8_t *target, size_t targsize) +{ + if (!b64rmap_initialized) { + b64_initialize_rmap(); + } + + if (target) { + return b64_pton_do(src, target, targsize); + } else { + return b64_pton_len(src); + } +} + +#define B64BUFSIZE 65535 /*!< Buffer size for b64 conversion. */ + + + + + + + + + + + + +const int KNOT_TSIG_MAX_DIGEST_SIZE = 64; // size of HMAC-SHA512 digest + + +static int knot_tsig_check_algorithm(const knot_rrset_t *tsig_rr) +{ + const knot_dname_t *alg_name = tsig_rdata_alg_name(tsig_rr); + if (!alg_name) { + return KNOT_EMALF; + } + + tsig_algorithm_t alg = tsig_alg_from_name(alg_name); + if (alg == 0) { + /*!< \todo is this error OK? */ + dbg_tsig("TSIG: unknown algorithm.\n"); + return KNOT_TSIG_EBADSIG; + } + + return KNOT_EOK; +} + +static int knot_tsig_check_key(const knot_rrset_t *tsig_rr, + const knot_key_t *tsig_key) +{ + const knot_dname_t *tsig_name = knot_rrset_owner(tsig_rr); + if (!tsig_name) { + return KNOT_EMALF; + } + + const char *name = knot_dname_to_str(tsig_name); + if (!name) { + return KNOT_EMALF; + } + + if (knot_dname_compare(tsig_name, tsig_key->name) != 0) { + /*!< \todo which error. */ + dbg_tsig("TSIG: unknown key: %s\n", name); + return KNOT_TSIG_EBADKEY; + } + + return KNOT_EOK; +} + +static int knot_tsig_compute_digest(const uint8_t *wire, size_t wire_len, + uint8_t *digest, size_t *digest_len, + const knot_key_t *key) +{ + if (!wire || !digest || !digest_len || !key) { + dbg_tsig("TSIG: digest: bad args.\n"); + return KNOT_EBADARG; + } + + if (!key->name) { + dbg_tsig("TSIG: digest: no algorithm\n"); + return KNOT_EMALF; + } + + tsig_algorithm_t tsig_alg = key->algorithm; + if (tsig_alg == 0) { + dbg_tsig("TSIG: digest: unknown algorithm\n"); + return KNOT_TSIG_EBADSIG; + } + + /* Create digest, using length of the algorithm. */ +// *digest = malloc(sizeof(uint8_t) * tsig_alg_digest_length(tsig_alg)); +// if (!digest) { +// ERR_ALLOC_FAILED; +// return KNOT_ENOMEM; +// } + + /* Decode key from Base64. */ + char decoded_key[B64BUFSIZE]; + + int decoded_key_size = b64_pton(key->secret, (uint8_t *)decoded_key, + B64BUFSIZE); + if (decoded_key_size < 0) { + dbg_tsig("TSIG: Could not decode Base64\n"); + return KNOT_EMALF; + } + + dbg_tsig("TSIG: decoded key size: %d\n", decoded_key_size); + dbg_tsig("TSIG: decoded key: '%*s'\n", decoded_key_size, decoded_key); + + dbg_tsig("TSIG: using this wire for digest calculation\n"); + + //dbg_tsig_hex(wire, wire_len); + + /* Compute digest. */ + HMAC_CTX ctx; + + switch (tsig_alg) { + case KNOT_TSIG_ALG_HMAC_MD5: + HMAC_Init(&ctx, decoded_key, + decoded_key_size, EVP_md5()); + break; + default: + return KNOT_ENOTSUP; + } /* switch */ + + unsigned tmp_dig_len = *digest_len; + HMAC_Update(&ctx, (const unsigned char *)wire, wire_len); + HMAC_Final(&ctx, digest, &tmp_dig_len); + *digest_len = tmp_dig_len; + + return KNOT_EOK; +} + +static int knot_tsig_check_time_signed(const knot_rrset_t *tsig_rr) +{ + if (!tsig_rr) { + return KNOT_EBADARG; + } + + /* Get the time signed and fudge values. */ + uint64_t time_signed = tsig_rdata_time_signed(tsig_rr); + if (time_signed == 0) { + return KNOT_TSIG_EBADTIME; + } + uint16_t fudge = tsig_rdata_fudge(tsig_rr); + if (fudge == 0) { + return KNOT_TSIG_EBADTIME; + } + + /* Get the current time. */ + time_t curr_time = time(NULL); + + /*!< \todo bleeding eyes. */ + if (difftime(curr_time, (time_t)time_signed) > fudge) { + return KNOT_TSIG_EBADTIME; + } + + return KNOT_EOK; +} + +static int knot_tsig_write_tsig_timers(uint8_t *wire, + const knot_rrset_t *tsig_rr) +{ + // put time signed + knot_wire_write_u48(wire, tsig_rdata_time_signed(tsig_rr)); + + // put fudge + knot_wire_write_u16(wire + 6, tsig_rdata_fudge(tsig_rr)); + + return KNOT_EOK; +} + +static int knot_tsig_write_tsig_variables(uint8_t *wire, + const knot_rrset_t *tsig_rr) +{ + /* Copy TSIG variables - starting with key name. */ + const knot_dname_t *tsig_owner = knot_rrset_owner(tsig_rr); + if (!tsig_owner) { + dbg_tsig("TSIG: write variables: no owner.\n"); + return KNOT_EBADARG; + } + + int offset = 0; + + memcpy(wire + offset, knot_dname_name(tsig_owner), + sizeof(uint8_t) * knot_dname_size(tsig_owner)); + dbg_tsig("TSIG: write variables: written owner (tsig alg): \n"); + /*knot_rrset_class(tsig_rr));*/ + dbg_tsig_hex_detail(wire + offset, knot_dname_size(tsig_owner)); + offset += knot_dname_size(tsig_owner); + + /*!< \todo which order? */ + + /* Copy class. */ + knot_wire_write_u16(wire + offset, knot_rrset_class(tsig_rr)); + dbg_tsig("TSIG: write variables: written CLASS: %u - ", + knot_rrset_class(tsig_rr)); + dbg_tsig_hex_detail(wire + offset, sizeof(uint16_t)); + offset += sizeof(uint16_t); + + /* Copy TTL - always 0. */ + knot_wire_write_u32(wire + offset, knot_rrset_ttl(tsig_rr)); + dbg_tsig("TSIG: write variables: written TTL: %u - ", + knot_rrset_ttl(tsig_rr)); + dbg_tsig_hex_detail(wire + offset, sizeof(uint32_t)); + offset += sizeof(uint32_t); + + /* Copy alg name. */ + const knot_dname_t *alg_name = tsig_rdata_alg_name(tsig_rr); + if (!alg_name) { + dbg_tsig("TSIG: write variables: no algorithm name.\n"); + return KNOT_EBADARG; + } +// alg_name = knot_dname_new_from_str("HMAC-MD5.SIG-ALG.REG.INT.", + //strlen("HMAC-MD5.SIG-ALG.REG.INT."), + //NULL); + + memcpy(wire + offset, knot_dname_name(alg_name), + sizeof(uint8_t) * knot_dname_size(alg_name)); + offset += knot_dname_size(alg_name); + dbg_tsig_detail("TSIG: write variables: written alg name: %s\n", + knot_dname_to_str(alg_name)); + + /* Following data are written in network order. */ + /* Time signed. */ + knot_wire_write_u48(wire + offset, tsig_rdata_time_signed(tsig_rr)); + offset += 6; + dbg_tsig_detail("TSIG: write variables: time signed: %llu - ", + tsig_rdata_time_signed(tsig_rr)); + dbg_tsig_hex_detail(wire + offset - 6, 6); + /* Fudge. */ + knot_wire_write_u16(wire + offset, tsig_rdata_fudge(tsig_rr)); + offset += sizeof(uint16_t); + dbg_tsig_detail("TSIG: write variables: fudge: %hu\n", + tsig_rdata_fudge(tsig_rr)); + /* TSIG error. */ + knot_wire_write_u16(wire + offset, tsig_rdata_error(tsig_rr)); + offset += sizeof(uint16_t); + /* Get other data length. */ + uint16_t other_data_length = tsig_rdata_other_data_length(tsig_rr); + /* Get other data. */ + const uint8_t *other_data = tsig_rdata_other_data(tsig_rr); + if (!other_data) { + dbg_tsig("TSIG: write variables: no other data.\n"); + return KNOT_EBADARG; + } + + /* + * We cannot write the whole other_data, as it contains its length in + * machine order. + */ + knot_wire_write_u16(wire + offset, other_data_length); + offset += sizeof(uint16_t); + + /* Skip the length. */ + dbg_tsig_detail("Copying other data.\n"); + memcpy(wire + offset, other_data, other_data_length); + + return KNOT_EOK; +} + +static int knot_tsig_wire_write_timers(uint8_t *wire, + const knot_rrset_t *tsig_rr) +{ + knot_wire_write_u48(wire, tsig_rdata_time_signed(tsig_rr)); + knot_wire_write_u16(wire + 6, tsig_rdata_fudge(tsig_rr)); + + return KNOT_EOK; +} + +int knot_tsig_create_sign_wire(const uint8_t *msg, size_t msg_len, + /*size_t msg_max_len, */const uint8_t *request_mac, + size_t request_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_rrset_t *tmp_tsig, + const knot_key_t *key) +{ + if (!msg || !key || digest_len == NULL) { + dbg_tsig("TSIG: create wire: bad args.\n"); + return KNOT_EBADARG; + } + + /* Create tmp TSIG. */ + int ret = KNOT_EOK; +// knot_rrset_t *tmp_tsig = +// knot_rrset_new(key->name, KNOT_RRTYPE_TSIG, KNOT_CLASS_ANY, 0); +// if (!tmp_tsig) { +// return KNOT_ENOMEM; +// } + +// tsig_rdata_store_current_time(tmp_tsig); + + /* + * Create tmp wire, it should contain message + * plus request mac plus tsig varibles. + */ + dbg_tsig("Counting wire size: %zu, %zu, %zu.\n", + msg_len, request_mac_len, + tsig_rdata_tsig_variables_length(tmp_tsig)); + size_t wire_len = sizeof(uint8_t) * + (msg_len + request_mac_len + ((request_mac_len > 0) + ? 2 : 0) + + tsig_rdata_tsig_variables_length(tmp_tsig)); + uint8_t *wire = malloc(wire_len); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + memset(wire, 0, wire_len); + + uint8_t *pos = wire; + + /* Copy the request MAC - should work even if NULL. */ + if (request_mac_len > 0) { + dbg_tsig_detail("Copying request MAC size\n"); + knot_wire_write_u16(pos, request_mac_len); + pos += 2; + } + dbg_tsig("Copying request mac.\n"); + memcpy(pos, request_mac, sizeof(uint8_t) * request_mac_len); + dbg_tsig_detail("TSIG: create wire: request mac: "); + dbg_tsig_hex_detail(pos, request_mac_len); + pos += request_mac_len; + /* Copy the original message. */ + dbg_tsig("Copying original message.\n"); + memcpy(pos, msg, msg_len); + dbg_tsig_detail("TSIG: create wire: original message: \n"); + //dbg_tsig_hex_detail(pos, msg_len); + pos += msg_len; + /* Copy TSIG variables. */ + dbg_tsig("Writing TSIG variables.\n"); + ret = knot_tsig_write_tsig_variables(pos, tmp_tsig); + if (ret != KNOT_EOK) { + dbg_tsig("TSIG: create wire: failed to write TSIG " + "variables: %s\n", knot_strerror(ret)); + return ret; + } + + /* Compute digest. */ + ret = knot_tsig_compute_digest(wire, wire_len, + digest, digest_len, key); + if (ret != KNOT_EOK) { + dbg_tsig("TSIG: create wire: failed to compute digest: %s\n", + knot_strerror(ret)); + *digest_len = 0; + return ret; + } + +// assert(digest_tmp_len > 0); + free(wire); + +// if (digest_tmp_len > *digest_len) { +// *digest_len = 0; +// return KNOT_ESPACE; +// } + +// knot_rrset_deep_free(&tmp_tsig, 1, 1, 1); + + // everything went ok, save the digest to the output parameter +// memcpy(digest, digest_tmp, digest_tmp_len); +// *digest_len = digest_tmp_len; + + return KNOT_EOK; +} + +static int knot_tsig_create_sign_wire_next(const uint8_t *msg, size_t msg_len, + const uint8_t *prev_mac, + size_t prev_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_rrset_t *tmp_tsig, + const knot_key_t *key) +{ + if (!msg || !key || digest_len == NULL) { + dbg_tsig("TSIG: create wire: bad args.\n"); + return KNOT_EBADARG; + } + + /* Create tmp TSIG. */ + int ret = KNOT_EOK; + + /* + * Create tmp wire, it should contain message + * plus request mac plus tsig varibles. + */ + dbg_tsig("Counting wire size: %zu, %zu, %zu.\n", + msg_len, prev_mac_len, + tsig_rdata_tsig_timers_length()); + size_t wire_len = sizeof(uint8_t) * + (msg_len + prev_mac_len + + tsig_rdata_tsig_timers_length()); + uint8_t *wire = malloc(wire_len); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + memset(wire, 0, wire_len); + + /* Copy the request MAC - should work even if NULL. */ + dbg_tsig("Copying request mac.\n"); + memcpy(wire, prev_mac, sizeof(uint8_t) * prev_mac_len); + dbg_tsig_detail("TSIG: create wire: request mac: "); + dbg_tsig_hex_detail(wire, prev_mac_len); + /* Copy the original message. */ + dbg_tsig("Copying original message.\n"); + memcpy(wire + prev_mac_len, msg, msg_len); + dbg_tsig_detail("TSIG: create wire: original message: \n"); + //dbg_tsig_hex_detail(wire + prev_mac_len, msg_len); + /* Copy TSIG variables. */ + + dbg_tsig("Writing TSIG timers.\n"); + ret = knot_tsig_write_tsig_timers(wire + prev_mac_len + msg_len, + tmp_tsig); +// ret = knot_tsig_write_tsig_variables(wire + prev_mac_len + msg_len, +// tmp_tsig); + if (ret != KNOT_EOK) { + dbg_tsig("TSIG: create wire: failed to write TSIG " + "timers: %s\n", knot_strerror(ret)); + return ret; + } + + /* Compute digest. */ + ret = knot_tsig_compute_digest(wire, wire_len, + digest, digest_len, key); + if (ret != KNOT_EOK) { + dbg_tsig("TSIG: create wire: failed to compute digest: %s\n", + knot_strerror(ret)); + *digest_len = 0; + return ret; + } + + free(wire); + + return KNOT_EOK; +} + +int knot_tsig_sign(uint8_t *msg, size_t *msg_len, + size_t msg_max_len, const uint8_t *request_mac, + size_t request_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_key_t *key) +{ + if (!msg || !msg_len || !key || digest == NULL || digest_len == NULL) { + return KNOT_EBADARG; + } + + knot_dname_t *key_name_copy = knot_dname_deep_copy(key->name); + if (!key_name_copy) { + dbg_tsig_detail("TSIG: key_name_copy = NULL\n"); + return KNOT_ENOMEM; + } + + knot_rrset_t *tmp_tsig = + knot_rrset_new(key_name_copy, + KNOT_RRTYPE_TSIG, KNOT_CLASS_ANY, 0); + if (!tmp_tsig) { + dbg_tsig_detail("TSIG: tmp_tsig = NULL\n"); + return KNOT_ENOMEM; + } + + /* Create rdata for TSIG RR. */ + knot_rdata_t *rdata = knot_rdata_new(); + if (!rdata) { + dbg_tsig_detail("TSIG: rdata = NULL\n"); + return KNOT_ENOMEM; + } + + knot_rrset_add_rdata(tmp_tsig, rdata); + + /* Create items for TSIG RR. */ + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(KNOT_RRTYPE_TSIG); + assert(desc); + + knot_rdata_item_t *items = + malloc(sizeof(knot_rdata_item_t) * desc->length); + if (!items) { + dbg_tsig_detail("TSIG: items = NULL\n"); + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + memset(items, 0, sizeof(knot_rdata_item_t) * desc->length); + + int ret = knot_rdata_set_items(rdata, items, desc->length); + if (ret != KNOT_EOK) { + dbg_tsig_detail("TSIG: rdata_set_items returned %s\n", knot_strerror(ret)); + return ret; + } + free(items); + + tsig_rdata_set_alg(tmp_tsig, key->algorithm); + tsig_rdata_store_current_time(tmp_tsig); + tsig_rdata_set_fudge(tmp_tsig, 300); + + /* Set original ID */ + tsig_rdata_set_orig_id(tmp_tsig, knot_wire_get_id(msg)); + + /* Set error */ + /*! \todo [TSIG] Set error and other data if appropriate. */ + tsig_rdata_set_tsig_error(tmp_tsig, 0); + + /* Set other len. */ + tsig_rdata_set_other_data(tmp_tsig, 0, 0); + + uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; + size_t digest_tmp_len = 0; + + dbg_tsig_detail("tmp_tsig before sign_wire():\n"); + knot_rrset_dump(tmp_tsig, 0); + + ret = knot_tsig_create_sign_wire(msg, *msg_len, /*msg_max_len,*/ + request_mac, request_mac_len, + digest_tmp, &digest_tmp_len, + tmp_tsig, key); + if (ret != KNOT_EOK) { + dbg_tsig("TSIG: could not create wire or sign wire: %s\n", + knot_strerror(ret)); + return ret; + } + + /* Set the digest. */ + size_t tsig_wire_len = msg_max_len - *msg_len; + int rr_count = 0; + tsig_rdata_set_mac(tmp_tsig, digest_tmp_len, digest_tmp); + + //knot_rrset_dump(tmp_tsig, 1); + + /* Write RRSet to wire */ + ret = knot_rrset_to_wire(tmp_tsig, msg + *msg_len, + &tsig_wire_len, &rr_count); + if (ret != KNOT_EOK) { + dbg_tsig_detail("TSIG: rrset_to_wire = %s\n", knot_strerror(ret)); + *digest_len = 0; + return ret; + } + + knot_rrset_deep_free(&tmp_tsig, 1, 1, 1); + + *msg_len += tsig_wire_len; + + uint16_t arcount = knot_wire_get_arcount(msg); + knot_wire_set_arcount(msg, ++arcount); + + // everything went ok, save the digest to the output parameter + memcpy(digest, digest_tmp, digest_tmp_len); + *digest_len = digest_tmp_len; + + return KNOT_EOK; +} + +int knot_tsig_sign_next(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const uint8_t *prev_digest, size_t prev_digest_len, + uint8_t *digest, size_t *digest_len, + const knot_key_t *key) +{ + if (!msg || !msg_len || !key || !key || !digest || !digest_len) { + return KNOT_EBADARG; + } + + uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; + size_t digest_tmp_len = 0; + + /* Create tmp TSIG. */ + knot_rrset_t *tmp_tsig = + knot_rrset_new(key->name, KNOT_RRTYPE_TSIG, KNOT_CLASS_ANY, 0); + if (!tmp_tsig) { + return KNOT_ENOMEM; + } + + tsig_rdata_store_current_time(tmp_tsig); + + /* Create wire to be signed. */ + size_t wire_len = prev_digest_len + *msg_len + KNOT_TSIG_TIMERS_LENGTH; + uint8_t *wire = malloc(wire_len); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + memset(wire, 0, wire_len); + + /* Write previous digest. */ + memcpy(wire, prev_digest, sizeof(uint8_t) * prev_digest_len); + /* Write original message. */ + memcpy(msg + prev_digest_len, msg, *msg_len); + /* Write timers. */ + knot_tsig_wire_write_timers(msg + prev_digest_len + *msg_len, tmp_tsig); + + int ret = 0; + ret = knot_tsig_compute_digest(wire, wire_len, + digest_tmp, &digest_tmp_len, key); + if (ret != KNOT_EOK) { + *digest_len = 0; + return ret; + } + + if (digest_tmp_len > *digest_len) { + *digest_len = 0; + return KNOT_ESPACE; + } + + free(wire); + + /* Set the MAC. */ + tsig_rdata_set_mac(tmp_tsig, *digest_len, digest); + + size_t tsig_wire_size = msg_max_len - *msg_len; + int rr_count = 0; + ret = knot_rrset_to_wire(tmp_tsig, msg + *msg_len, + &tsig_wire_size, &rr_count); + if (ret != KNOT_EOK) { + *digest_len = 0; + return ret; + } + + knot_rrset_deep_free(&tmp_tsig, 1, 1, 1); + + *msg_len += tsig_wire_size; + uint16_t arcount = knot_wire_get_arcount(msg); + knot_wire_set_arcount(msg, ++arcount); + + memcpy(digest, digest_tmp, digest_tmp_len); + *digest_len = digest_tmp_len; + + return KNOT_EOK; +} + +static int knot_tsig_check_digest(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *request_mac, + size_t request_mac_len, + const knot_key_t *tsig_key, + int use_times) +{ + if (!tsig_rr || !wire || !tsig_key) { + return KNOT_EBADARG; + } + + /* Check time signed. */ + int ret = knot_tsig_check_time_signed(tsig_rr); + if (ret != KNOT_EOK) { + return ret; + } + + dbg_tsig("TSIG: time checked.\n"); + + /* Check that libknot knows the algorithm. */ + ret = knot_tsig_check_algorithm(tsig_rr); + if (ret != KNOT_EOK) { + return ret; + } + + dbg_tsig("TSIG: algorithm checked.\n"); + + /* Check that key is valid, ie. the same as given in args. */ + ret = knot_tsig_check_key(tsig_rr, tsig_key); + if (ret != KNOT_EOK) { + return ret; + } + + dbg_tsig("TSIG: key validity checked.\n"); + + /* Time OK algorithm OK, key name OK - do digest. */ + /* Calculate the size of TSIG RR. */ + size_t tsig_len = tsig_wire_actsize(tsig_rr); + + dbg_tsig_detail("TSIG: check digest: wire before strip: \n"); + //dbg_tsig_hex_detail(wire, size); + + /* Strip the TSIG. */ + size -= tsig_len; + + dbg_tsig_detail("TSIG: check digest: wire after strip (stripped %zu):\n", + tsig_len); + //dbg_tsig_hex_detail(wire, size); + + uint8_t *wire_to_sign = malloc(sizeof(uint8_t) * size); + if (!wire_to_sign) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + memset(wire_to_sign, 0, sizeof(uint8_t) * size); + memcpy(wire_to_sign, wire, size); + + /* Decrease arcount. */ + knot_wire_set_arcount(wire_to_sign, + knot_wire_get_arcount(wire_to_sign) - 1); + + uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; + size_t digest_tmp_len = 0; + assert(tsig_rr->rdata); + + if (use_times) { + ret = knot_tsig_create_sign_wire_next(wire_to_sign, size, + request_mac, request_mac_len, + digest_tmp, &digest_tmp_len, + tsig_rr, tsig_key); + } else { + ret = knot_tsig_create_sign_wire(wire_to_sign, size, + request_mac, request_mac_len, + digest_tmp, &digest_tmp_len, + tsig_rr, tsig_key); + } + + assert(tsig_rr->rdata); + free(wire_to_sign); + + if (ret != KNOT_EOK) { + dbg_tsig("Failed to create wire format for checking: %s.\n", + knot_strerror(ret)); + return ret; + } + +// uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; +// size_t digest_tmp_len = 0; +// ret = knot_tsig_compute_digest(wire, size, digest_tmp, +// &digest_tmp_len, tsig_key); +// if (ret != KNOT_EOK) { +// dbg_tsig("TSIG: digest could not be calculated\n"); +// return ret; +// } + + dbg_tsig("TSIG: digest calculated\n"); + + /* Compare MAC from TSIG RR RDATA with just computed digest. */ + + /*!< \todo move to function. */ + const knot_dname_t *alg_name = tsig_rdata_alg_name(tsig_rr); + tsig_algorithm_t alg = tsig_alg_from_name(alg_name); + + /*! \todo [TSIG] TRUNCATION */ + uint16_t mac_length = tsig_rdata_mac_length(tsig_rr); + const uint8_t *tsig_mac = tsig_rdata_mac(tsig_rr); + + if (mac_length != tsig_alg_digest_length(alg)) { + dbg_tsig("TSIG: calculated digest length and given length do not match!\n"); + return KNOT_TSIG_EBADSIG; + } + +// assert(tsig_alg_digest_length(alg) == mac_length); + + dbg_tsig("TSIG: calc digest : "); + dbg_tsig_hex(digest_tmp, digest_tmp_len); + + dbg_tsig("TSIG: given digest: "); + dbg_tsig_hex(tsig_mac, mac_length); + + if (strncasecmp((char *)(tsig_mac), (char *)digest_tmp, + mac_length) != 0) { + return KNOT_TSIG_EBADSIG; + } + + return KNOT_EOK; +} + +int knot_tsig_server_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const knot_key_t *tsig_key) +{ + dbg_tsig_verb("tsig_server_check()\n"); + return knot_tsig_check_digest(tsig_rr, wire, size, NULL, 0, tsig_key, 0); +} + +int knot_tsig_client_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *request_mac, size_t request_mac_len, + const knot_key_t *tsig_key) +{ + dbg_tsig_verb("tsig_client_check()\n"); + return knot_tsig_check_digest(tsig_rr, wire, size, request_mac, + request_mac_len, tsig_key, 0); +} + +int knot_tsig_client_check_next(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *prev_digest, + size_t prev_digest_len, + const knot_key_t *tsig_key) +{ +// return knot_tsig_client_check(tsig_rr, wire, size, prev_digest, +// prev_digest_len, tsig_key); + dbg_tsig_verb("tsig_client_check_next()\n"); + return knot_tsig_check_digest(tsig_rr, wire, size, prev_digest, + prev_digest_len, tsig_key, 1); + return KNOT_ENOTSUP; +} diff --git a/src/libknot/tsig-op.h b/src/libknot/tsig-op.h new file mode 100644 index 0000000..b206dc7 --- /dev/null +++ b/src/libknot/tsig-op.h @@ -0,0 +1,161 @@ +/*! + * \file tsig-op.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief TSIG signing and validating. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_TSIG_OP_H_ +#define _KNOT_TSIG_OP_H_ + +#include <stdint.h> + +#include "tsig.h" +#include "rrset.h" + +/*! + * \brief Generate TSIG signature of a message. + * + * This function generates TSIG digest of the given message prepended with the + * given Request MAC (if any) and appended with TSIG Variables. It also appends + * the resulting TSIG RR to the message wire format and accordingly adjusts + * the message size. + * + * \note This function does not save the new digest to the 'digest' parameter + * unless everything went OK. This allows to sent the same buffer to + * the 'request_mac' and 'digest' parameters. + * + * \param msg Message to be signed. + * \param msg_len Size of the message in bytes. + * \param msg_max_len Maximum size of the message in bytes. + * \param request_mac Request MAC. (may be NULL). + * \param request_mac_len Size of the request MAC in bytes. + * \param digest Buffer to save the digest in. + * \param digest_len In: size of the buffer. Out: real size of the digest saved. + * \param tsig_rr RRSet containing the TSIG RR to be used. Data from the RR are + * appended to the signed message. + * + * \retval KNOT_EOK if everything went OK. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_sign(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const uint8_t *request_mac, size_t request_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_key_t *key); + +/*! + * \brief Generate TSIG signature of a 2nd or later message in a TCP session. + * + * This function generates TSIG digest of the given message prepended with the + * given Request MAC (if any) and appended with TSIG Variables. It also appends + * the resulting TSIG RR to the message wire format and accordingly adjusts + * the message size. + * + * \note This function does not save the new digest to the 'digest' parameter + * unless everything went OK. This allows to sent the same buffer to + * the 'request_mac' and 'digest' parameters. + * + * \param msg Message to be signed. + * \param msg_len Size of the message in bytes. + * \param msg_max_len Maximum size of the message in bytes. + * \param prev_digest Previous digest sent by the server in the session. + * \param prev_digest_len Size of the previous digest in bytes. + * \param digest Buffer to save the digest in. + * \param digest_len In: size of the buffer. Out: real size of the digest saved. + * \param tsig_rr RRSet containing the TSIG RR to be used. Data from the RR are + * appended to the signed message. + * + * \retval KNOT_EOK if successful. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_sign_next(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const uint8_t *prev_digest, size_t prev_digest_len, + uint8_t *digest, size_t *digest_len, + const knot_key_t *key); + +/*! + * \brief Checks incoming request. + * + * \param tsig_rr TSIG extracted from the packet. + * \param wire Wire format of the packet (including the TSIG RR). + * \param size Size of the wire format of packet in bytes. + * + * \retval KNOT_EOK If the signature is valid. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_server_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const knot_key_t *tsig_key); + +/*! + * \brief Checks incoming response. + * + * \param tsig_rr TSIG extracted from the packet. + * \param wire Wire format of the packet (including the TSIG RR). + * \param size Size of the wire format of packet in bytes. + * \param request_mac Request MAC. (may be NULL). + * \param request_mac_len Size of the request MAC in bytes. + * + * \retval KNOT_EOK If the signature is valid. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_client_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *request_mac, size_t request_mac_len, + const knot_key_t *key); + +/*! + * \brief Checks signature of 2nd or next packet in a TCP session. + * + * \param tsig_rr TSIG extracted from the packet. + * \param wire Wire format of the packet (including the TSIG RR). + * \param size Size of the wire format of packet in bytes. + * \param prev_digest Previous digest sent by the server in the session. + * \param prev_digest_len Size of the previous digest in bytes. + * + * \retval KNOT_EOK If the signature is valid. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_client_check_next(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *prev_digest, + size_t prev_digest_len, + const knot_key_t *key); + +#endif /* _KNOT_TSIG_H_ */ + +/*! @} */ diff --git a/src/libknot/tsig.c b/src/libknot/tsig.c new file mode 100644 index 0000000..432539f --- /dev/null +++ b/src/libknot/tsig.c @@ -0,0 +1,618 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <stdlib.h> +#include <stdint.h> +#include <assert.h> +#include <time.h> + +#include "tsig.h" +#include "util/error.h" +#include "util/debug.h" +#include "common.h" +#include "util/utils.h" +#include "rrset.h" +#include "rdata.h" +#include "dname.h" + +/*! \brief TSIG algorithms table. */ +#define TSIG_ALG_TABLE_SIZE 8 +static knot_lookup_table_t tsig_alg_table[TSIG_ALG_TABLE_SIZE] = { + { KNOT_TSIG_ALG_GSS_TSIG, "gss-tsig." }, + { KNOT_TSIG_ALG_HMAC_MD5, "hmac-md5.sig-alg.reg.int." }, + { KNOT_TSIG_ALG_HMAC_SHA1, "hmac-sha1." }, + { KNOT_TSIG_ALG_HMAC_SHA224, "hmac-sha224." }, + { KNOT_TSIG_ALG_HMAC_SHA256, "hmac-sha256." }, + { KNOT_TSIG_ALG_HMAC_SHA384, "hmac-sha384." }, + { KNOT_TSIG_ALG_HMAC_SHA512, "hmac-sha512." }, + { KNOT_TSIG_ALG_NULL, NULL } +}; + +int tsig_rdata_init(knot_rrset_t *tsig) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + /* Initializes rdata. */ + tsig->rdata = knot_rdata_new(); + if (!tsig->rdata) { + return KNOT_ENOMEM; + } + + tsig->rdata->items = + malloc(sizeof(knot_rdata_item_t) * KNOT_TSIG_ITEM_COUNT); + if (!tsig->rdata->items) { + return KNOT_ENOMEM; + } + + memset(tsig->rdata->items, 0, + sizeof(knot_rdata_item_t) * KNOT_TSIG_ITEM_COUNT); + + return KNOT_EOK; +} + +int tsig_rdata_set_alg_name(knot_rrset_t *tsig, knot_dname_t *alg_name) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 1); + + knot_dname_t *alg_name_copy = knot_dname_deep_copy(alg_name); + if (!alg_name_copy) { + return KNOT_ENOMEM; + } + + knot_rdata_item_set_dname(rdata, 0, alg_name_copy); + + return KNOT_EOK; +} + +int tsig_rdata_set_alg(knot_rrset_t *tsig, tsig_algorithm_t alg) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 1); + + const char *alg_str = tsig_alg_to_str(alg); + knot_dname_t *alg_name_copy = knot_dname_new_from_str(alg_str, + strlen(alg_str), + 0); + if (!alg_name_copy) { + return KNOT_ENOMEM; + } + + knot_rdata_item_set_dname(rdata, 0, alg_name_copy); + + return KNOT_EOK; +} + +int tsig_rdata_set_time_signed(knot_rrset_t *tsig, uint64_t time) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 2); + + /* Create the wire format. */ + uint16_t *wire = malloc(sizeof(uint8_t) * 6 + sizeof(uint16_t)); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + /* Write the length - 6. */ + wire[0] = 6; + knot_wire_write_u48((uint8_t *)(wire + 1), time); + + knot_rdata_item_set_raw_data(rdata, 1, wire); + + return KNOT_EOK; +} + +int tsig_rdata_set_fudge(knot_rrset_t *tsig, uint16_t fudge) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 3); + + /* Create the wire format. */ + uint16_t *wire = malloc(sizeof(uint8_t) * 2 + sizeof(uint16_t)); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + /* Write the length - 2. */ + wire[0] = sizeof(uint16_t); + knot_wire_write_u16((uint8_t *)(wire + 1), fudge); + + knot_rdata_item_set_raw_data(rdata, 2, wire); + + return KNOT_EOK; +} + +int tsig_rdata_set_mac(knot_rrset_t *tsig, uint16_t length, const uint8_t *mac) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 4); + + /* Create the wire format. */ + uint16_t *wire = malloc(sizeof(uint8_t) * length + 2 * sizeof(uint16_t)); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + /* Write the length. */ + wire[0] = length + sizeof(uint16_t); + knot_wire_write_u16((uint8_t *)(wire + 1), length); + /* Copy the actual MAC. */ + memcpy((uint8_t *)(wire + 2), mac, sizeof(uint8_t) * length); + knot_rdata_item_set_raw_data(rdata, 3, wire); + + return KNOT_EOK; +} + +int tsig_rdata_set_orig_id(knot_rrset_t *tsig, uint16_t id) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 5); + + /* Create the wire format. */ + uint16_t *wire = malloc(sizeof(uint8_t) * 2 + sizeof(uint16_t)); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + /* Write the length - 2. */ + wire[0] = sizeof(uint16_t); + knot_wire_write_u16((uint8_t *)(wire + 1), id); + + knot_rdata_item_set_raw_data(rdata, 4, wire); + + return KNOT_EOK; +} + +int tsig_rdata_set_tsig_error(knot_rrset_t *tsig, uint16_t tsig_error) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 6); + + /* Create the wire format. */ + uint16_t *wire = malloc(sizeof(uint8_t) * 2 + sizeof(uint16_t)); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + /* Write the length - 2. */ + wire[0] = sizeof(uint16_t); + knot_wire_write_u16((uint8_t *)(wire + 1), tsig_error); + + knot_rdata_item_set_raw_data(rdata, 5, wire); + + return KNOT_EOK; +} + +int tsig_rdata_set_other_data(knot_rrset_t *tsig, uint16_t length, + const uint8_t *other_data) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 6); + + /* Create the wire format. */ + uint16_t *wire = malloc(sizeof(uint8_t) * length + 2 * sizeof(uint16_t)); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + /* Write the length. */ + wire[0] = length + 2; + knot_wire_write_u16((uint8_t *)(wire + 1), length); + /* Copy the actual data. */ + memcpy(wire + 2, other_data, sizeof(uint8_t) * length); + knot_rdata_item_set_raw_data(rdata, 6, wire); + + return KNOT_EOK; +} + +const knot_dname_t *tsig_rdata_alg_name(const knot_rrset_t *tsig) +{ + if (!tsig) { + return NULL; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + dbg_tsig("TSIG: rdata: alg name: no rdata.\n"); + return NULL; + } + + if (knot_rdata_item_count(rdata) < 1) { + dbg_tsig("TSIG: rdata: alg name: not enough items.\n"); + return NULL; + } + + return knot_rdata_item(rdata, 0)->dname; +} + +tsig_algorithm_t tsig_rdata_alg(const knot_rrset_t *tsig) +{ + /*! \todo [TSIG] Implement me. */ + return KNOT_TSIG_ALG_HMAC_MD5; +} + +uint64_t tsig_rdata_time_signed(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 2) { + return 0; + } + + uint16_t *wire = knot_rdata_item(rdata, 1)->raw_data; + assert(wire[0] == 6); + /* Skip the size. */ + wire++; + + return knot_wire_read_u48((uint8_t *)wire); +} + +uint16_t tsig_rdata_fudge(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 3) { + return 0; + } + + uint16_t *wire = knot_rdata_item(rdata, 2)->raw_data; + assert(wire[0] == 2); + /* Skip the size. */ + wire++; + + return knot_wire_read_u16((uint8_t *)wire); +} + +const uint8_t *tsig_rdata_mac(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 4) { + return 0; + } + + return (uint8_t*)(knot_rdata_item(rdata, 3)->raw_data + 2); +} + +size_t tsig_rdata_mac_length(const knot_rrset_t *tsig) +{ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata || knot_rdata_item_count(rdata) < 4) { + return 0; + } + + return knot_wire_read_u16( + (uint8_t *)(knot_rdata_item(rdata, 3)->raw_data + 1)); +} + +uint16_t tsig_rdata_orig_id(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 5) { + return 0; + } + + uint16_t *wire = knot_rdata_item(rdata, 4)->raw_data; + assert(wire[0] == 2); + /* Skip the size. */ + wire++; + + return knot_wire_read_u16((uint8_t *)wire); +} + +uint16_t tsig_rdata_error(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 6) { + return 0; + } + + uint16_t *wire = knot_rdata_item(rdata, 5)->raw_data; + assert(wire[0] == 2); + /* Skip the size. */ + wire++; + + return knot_wire_read_u16((uint8_t *)wire); +} + +const uint8_t *tsig_rdata_other_data(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 7) { + return 0; + } + + return (uint8_t *)(knot_rdata_item(rdata, 6)->raw_data + 2); +} + +uint16_t tsig_rdata_other_data_length(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 7) { + return 0; + } + + return knot_wire_read_u16((uint8_t *) + (knot_rdata_item(rdata, 6)->raw_data + 1)); +} + +int tsig_alg_from_name(const knot_dname_t *alg_name) +{ + if (!alg_name) { + return 0; + } + + char *name = knot_dname_to_str(alg_name); + if (!name) { + return 0; + } + + knot_lookup_table_t *found = + knot_lookup_by_name(tsig_alg_table, name); + + if (!found) { + dbg_tsig("Unknown algorithm: %s \n", name); + free(name); + return 0; + } + + free(name); + + return found->id; +} + +uint16_t tsig_alg_digest_length(tsig_algorithm_t alg) +{ + switch (alg) { + case KNOT_TSIG_ALG_GSS_TSIG: + return KNOT_TSIG_ALG_DIG_LENGTH_GSS_TSIG; + case KNOT_TSIG_ALG_HMAC_MD5: + return KNOT_TSIG_ALG_DIG_LENGTH_HMAC_MD5; + case KNOT_TSIG_ALG_HMAC_SHA1: + return KNOT_TSIG_ALG_DIG_LENGTH_SHA1; + case KNOT_TSIG_ALG_HMAC_SHA224: + return KNOT_TSIG_ALG_DIG_LENGTH_SHA224; + case KNOT_TSIG_ALG_HMAC_SHA256: + return KNOT_TSIG_ALG_DIG_LENGTH_SHA384; + case KNOT_TSIG_ALG_HMAC_SHA512: + return KNOT_TSIG_ALG_DIG_LENGTH_SHA512; + default: + return 0; + } /* switch(alg) */ +} + +size_t tsig_rdata_tsig_variables_length(const knot_rrset_t *tsig) +{ + /* Key name, Algorithm name and Other data have variable lengths. */ + const knot_dname_t *key_name = knot_rrset_owner(tsig); + if (!key_name) { + return 0; + } + + const knot_dname_t *alg_name = tsig_rdata_alg_name(tsig); + if (!alg_name) { + return 0; + } + +// dbg_tsig_detail("key_name: %.*s (size: %u) alg_name: %.*s (size: %u)\n", knot_dname_size(key_name), +// key_name->name, alg_name->size, alg_name->name, +// key_name->size, alg_name->size); + +// dbg_tsig_hex_detail(key_name->name, key_name->size); +// dbg_tsig_hex_detail(alg_name->name, alg_name->size); + + uint16_t other_data_length = tsig_rdata_other_data_length(tsig); + + return knot_dname_size(key_name) + knot_dname_size(alg_name) + + other_data_length + KNOT_TSIG_VARIABLES_LENGTH; +} + +size_t tsig_rdata_tsig_timers_length() +{ + return KNOT_TSIG_TIMERS_LENGTH; +} + + +int tsig_rdata_store_current_time(knot_rrset_t *tsig) +{ + if (!tsig) { + return KNOT_EBADARG; + } + time_t curr_time = time(NULL); + /*!< \todo bleeding eyes. */ + tsig_rdata_set_time_signed(tsig, (uint64_t)curr_time); + return KNOT_EOK; +} + +const char* tsig_alg_to_str(tsig_algorithm_t alg) +{ + for (unsigned i = 0; i < TSIG_ALG_TABLE_SIZE; ++i) { + if (tsig_alg_table[i].id == alg) { + return tsig_alg_table[i].name; + } + } + + return ""; +} + +size_t tsig_wire_maxsize(const knot_key_t* key) +{ + size_t alg_name_size = strlen(tsig_alg_to_str(key->algorithm)) + 1; + + return knot_dname_size(key->name) + + sizeof(uint16_t) + /* TYPE */ + sizeof(uint16_t) + /* CLASS */ + sizeof(uint32_t) + /* TTL */ + sizeof(uint16_t) + /* RDLENGTH */ + alg_name_size + /* Alg. name */ + 6 * sizeof(uint8_t) + /* Time signed */ + sizeof(uint16_t) + /* Fudge */ + sizeof(uint16_t) + /* MAC size */ + tsig_alg_digest_length(key->algorithm) + /* MAC */ + sizeof(uint16_t) + /* Original ID */ + sizeof(uint16_t) + /* Error */ + sizeof(uint16_t) + /* Other len */ + 6* sizeof(uint8_t); /* uint48_t in case of BADTIME RCODE */ +} + +size_t tsig_wire_actsize(const knot_rrset_t *tsig) +{ + return knot_dname_size(knot_rrset_owner(tsig)) + + sizeof(uint16_t) + /* TYPE */ + sizeof(uint16_t) + /* CLASS */ + sizeof(uint32_t) + /* TTL */ + sizeof(uint16_t) + /* RDLENGTH */ + knot_dname_size(tsig_rdata_alg_name(tsig)) + + 6 * sizeof(uint8_t) + /* Time signed */ + sizeof(uint16_t) + /* Fudge */ + sizeof(uint16_t) + /* MAC size */ + tsig_rdata_mac_length(tsig) + + sizeof(uint16_t) + /* Original ID */ + sizeof(uint16_t) + /* Error */ + sizeof(uint16_t) + /* Other len */ + tsig_rdata_other_data_length(tsig); +} + diff --git a/src/libknot/tsig.h b/src/libknot/tsig.h new file mode 100644 index 0000000..eafcfab --- /dev/null +++ b/src/libknot/tsig.h @@ -0,0 +1,145 @@ +/*! + * \file tsig.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * \brief TSIG manipulation. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_TSIG_H_ +#define _KNOT_TSIG_H_ + +#include <stdint.h> + +#include "rrset.h" +#include "util/utils.h" + +/* The assigned numbers should not begin with 0 - reserved for error. */ +enum tsig_algorithm { + KNOT_TSIG_ALG_NULL = 0, + KNOT_TSIG_ALG_GSS_TSIG = 128, /*!< \brief gss-tsig. */ + KNOT_TSIG_ALG_HMAC_MD5, /*!< \brief HMAC-MD5.SIG-ALG.REG.INT. */ + KNOT_TSIG_ALG_HMAC_SHA1, /*!< \brief hmac-sha1. */ + KNOT_TSIG_ALG_HMAC_SHA224, /*!< \brief hmac-sha224. */ + KNOT_TSIG_ALG_HMAC_SHA256, /*!< \brief hmac-sha256. */ + KNOT_TSIG_ALG_HMAC_SHA384, /*!< \brief hmac-sha384. */ + KNOT_TSIG_ALG_HMAC_SHA512 /*!< \brief hmac-sha512. */ +}; + +typedef enum tsig_algorithm tsig_algorithm_t; + +struct knot_key { + knot_dname_t *name; /*!< Key name. */ + tsig_algorithm_t algorithm; /*!< Key algorithm. */ + char *secret; /*!< Key data. */ + size_t secret_size; /*!< Key length. */ +}; + +typedef struct knot_key knot_key_t; + +/*!< \todo FIND ALG LENGTHS */ +enum tsig_algorithm_digest_length { + KNOT_TSIG_ALG_DIG_LENGTH_GSS_TSIG = 0, + KNOT_TSIG_ALG_DIG_LENGTH_HMAC_MD5 = 16, + KNOT_TSIG_ALG_DIG_LENGTH_SHA1 = 0, + KNOT_TSIG_ALG_DIG_LENGTH_SHA224 = 0, + KNOT_TSIG_ALG_DIG_LENGTH_SHA256 = 0, + KNOT_TSIG_ALG_DIG_LENGTH_SHA384 = 0, + KNOT_TSIG_ALG_DIG_LENGTH_SHA512 = 0 +}; + +enum tsig_consts { + KNOT_TSIG_ITEM_COUNT = 7, + KNOT_TSIG_VARIABLES_LENGTH = sizeof(uint16_t) // class + + sizeof(uint32_t) // ttl + + 6 // time signed + + sizeof(uint16_t) // fudge + + sizeof(uint16_t) // error + + sizeof(uint16_t),// other data length + KNOT_TSIG_TIMERS_LENGTH = sizeof(uint16_t) //fugde + + 6 // time signed +}; + +/*! TSIG errors are defined in util/error.h + * and present negative value of the TSIG error to + * comply with other parts of the library. + * + * KNOT_TSIG_EBADSIG = -16 + * KNOT_TSIG_EBADKEY = -17 + * KNOT_TSIG_EBADTIME = -18 + */ + +/*! + * \note Uses the given domain name, do not deallocate it! + */ +int tsig_rdata_set_alg_name(knot_rrset_t *tsig, knot_dname_t *alg_name); +int tsig_rdata_set_alg(knot_rrset_t *tsig, tsig_algorithm_t alg); +int tsig_rdata_set_time_signed(knot_rrset_t *tsig, uint64_t time); +int tsig_rdata_store_current_time(knot_rrset_t *tsig); +int tsig_rdata_set_fudge(knot_rrset_t *tsig, uint16_t fudge); +int tsig_rdata_set_mac(knot_rrset_t *tsig, uint16_t length, + const uint8_t *mac); +int tsig_rdata_set_orig_id(knot_rrset_t *tsig, uint16_t id); +int tsig_rdata_set_tsig_error(knot_rrset_t *tsig, uint16_t tsig_error); +int tsig_rdata_set_other_data(knot_rrset_t *tsig, uint16_t length, + const uint8_t *other_data); + +const knot_dname_t *tsig_rdata_alg_name(const knot_rrset_t *tsig); +tsig_algorithm_t tsig_rdata_alg(const knot_rrset_t *tsig); +uint64_t tsig_rdata_time_signed(const knot_rrset_t *tsig); +uint16_t tsig_rdata_fudge(const knot_rrset_t *tsig); +const uint8_t *tsig_rdata_mac(const knot_rrset_t *tsig); +size_t tsig_rdata_mac_length(const knot_rrset_t *tsig); +uint16_t tsig_rdata_orig_id(const knot_rrset_t *tsig); +uint16_t tsig_rdata_error(const knot_rrset_t *tsig); +const uint8_t *tsig_rdata_other_data(const knot_rrset_t *tsig); +uint16_t tsig_rdata_other_data_length(const knot_rrset_t *tsig); +size_t tsig_rdata_tsig_variables_length(const knot_rrset_t *tsig); + +size_t tsig_rdata_tsig_timers_length(); + +int tsig_alg_from_name(const knot_dname_t *name); + +/*! + * \brief Convert TSIG algorithm identifier to name. + * + * \param alg TSIG algorithm identifier. + * + * \retval TSIG algorithm string name. + * \retval Empty string if undefined. + */ +const char* tsig_alg_to_str(tsig_algorithm_t alg); + +uint16_t tsig_alg_digest_length(tsig_algorithm_t alg); + +/*! + * \brief Return TSIG RRSET maximum wire size for given algorithm. + * + * \param key Signing key descriptor. + * + * \return RRSET wire size. + */ +size_t tsig_wire_maxsize(const knot_key_t *key); +size_t tsig_wire_actsize(const knot_rrset_t *tsig); + +#endif /* _KNOT_TSIG_H_ */ + +/*! @} */ diff --git a/src/libknot/updates/changesets.c b/src/libknot/updates/changesets.c new file mode 100644 index 0000000..cf9e6a0 --- /dev/null +++ b/src/libknot/updates/changesets.c @@ -0,0 +1,296 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +#include "updates/changesets.h" + +#include "rrset.h" +#include "util/error.h" + +static const size_t KNOT_CHANGESET_COUNT = 5; +static const size_t KNOT_CHANGESET_STEP = 5; +static const size_t KNOT_CHANGESET_RRSET_COUNT = 5; +static const size_t KNOT_CHANGESET_RRSET_STEP = 5; + +/*----------------------------------------------------------------------------*/ + +static int knot_changeset_check_count(knot_rrset_t ***rrsets, size_t count, + size_t *allocated) +{ + /* Check if allocated is sufficient. */ + if (count <= *allocated) { + return KNOT_EOK; + } + + /* How many steps is needed to content count? */ + size_t extra = (count - *allocated) % KNOT_CHANGESET_RRSET_STEP; + extra = (extra + 1) * KNOT_CHANGESET_RRSET_STEP; + + /* Allocate new memory block. */ + const size_t item_len = sizeof(knot_rrset_t *); + const size_t new_count = *allocated + extra; + knot_rrset_t **rrsets_new = malloc(new_count * item_len); + if (rrsets_new == NULL) { + return KNOT_ENOMEM; + } + + /* Clear old memory block and copy old data. */ + memset(rrsets_new, 0, new_count * item_len); + memcpy(rrsets_new, *rrsets, (*allocated) * item_len); + + /* Replace old rrsets. */ + free(*rrsets); + *rrsets = rrsets_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_changeset_rrsets_match(const knot_rrset_t *rrset1, + const knot_rrset_t *rrset2) +{ + return knot_rrset_compare(rrset1, rrset2, KNOT_RRSET_COMPARE_HEADER) + && (knot_rrset_type(rrset1) != KNOT_RRTYPE_RRSIG + || knot_rdata_rrsig_type_covered( + knot_rrset_rdata(rrset1)) + == knot_rdata_rrsig_type_covered( + knot_rrset_rdata(rrset2))); +} + +/*----------------------------------------------------------------------------*/ + +int knot_changeset_allocate(knot_changesets_t **changesets) +{ + // create new changesets + *changesets = (knot_changesets_t *)(malloc(sizeof(knot_changesets_t))); + if (*changesets == NULL) { + return KNOT_ENOMEM; + } + + memset(*changesets, 0, sizeof(knot_changesets_t)); + + return knot_changesets_check_size(*changesets); +} + +/*----------------------------------------------------------------------------*/ + +int knot_changeset_add_rrset(knot_rrset_t ***rrsets, + size_t *count, size_t *allocated, + knot_rrset_t *rrset) +{ + int ret = knot_changeset_check_count(rrsets, *count + 1, allocated); + if (ret != KNOT_EOK) { + return ret; + } + + (*rrsets)[*count] = rrset; + *count = *count + 1; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_changeset_add_rr(knot_rrset_t ***rrsets, size_t *count, + size_t *allocated, knot_rrset_t *rr) +{ + // try to find the RRSet in the list of RRSets, but search backwards + // as it is probable that the last RRSet is the one to which the RR + // belongs + int i = *count - 1; + + while (i >= 0 && !knot_changeset_rrsets_match((*rrsets)[i], rr)) { + --i; + } + + if (i >= 0) { + // found RRSet to merge the new one into + if (knot_rrset_merge((void **)&(*rrsets)[i], + (void **)&rr) != KNOT_EOK) { + return KNOT_ERROR; + } + + // remove the RR + /*! \todo does this make sense? */ + knot_rrset_free(&rr); // used to be deep free with all 1's + + return KNOT_EOK; + } else { + return knot_changeset_add_rrset(rrsets, count, allocated, rr); + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_changeset_add_new_rr(knot_changeset_t *changeset, + knot_rrset_t *rrset, + xfrin_changeset_part_t part) +{ + knot_rrset_t ***rrsets = NULL; + size_t *count = NULL; + size_t *allocated = NULL; + + switch (part) { + case XFRIN_CHANGESET_ADD: + rrsets = &changeset->add; + count = &changeset->add_count; + allocated = &changeset->add_allocated; + break; + case XFRIN_CHANGESET_REMOVE: + rrsets = &changeset->remove; + count = &changeset->remove_count; + allocated = &changeset->remove_allocated; + break; + default: + assert(0); + } + + assert(rrsets != NULL); + assert(count != NULL); + assert(allocated != NULL); + + int ret = knot_changeset_add_rr(rrsets, count, allocated, rrset); + if (ret != KNOT_EOK) { + return ret; + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +void knot_changeset_store_soa(knot_rrset_t **chg_soa, + uint32_t *chg_serial, knot_rrset_t *soa) +{ + *chg_soa = soa; + *chg_serial = knot_rdata_soa_serial(knot_rrset_rdata(soa)); +} + +/*----------------------------------------------------------------------------*/ + +int knot_changeset_add_soa(knot_changeset_t *changeset, knot_rrset_t *soa, + xfrin_changeset_part_t part) +{ + switch (part) { + case XFRIN_CHANGESET_ADD: + knot_changeset_store_soa(&changeset->soa_to, + &changeset->serial_to, soa); + break; + case XFRIN_CHANGESET_REMOVE: + knot_changeset_store_soa(&changeset->soa_from, + &changeset->serial_from, soa); + break; + default: + assert(0); + } + + /*! \todo Remove return value? */ + return KNOT_EOK; +} + +/*---------------------------------------------------------------------------*/ + +int knot_changesets_check_size(knot_changesets_t *changesets) +{ + /* Check if allocated is sufficient. */ + if (changesets->count <= changesets->allocated) { + return KNOT_EOK; + } + + /* How many steps is needed to content count? */ + size_t extra = (changesets->count - changesets->allocated) % KNOT_CHANGESET_STEP; + extra = (extra + 1) * KNOT_CHANGESET_STEP; + + /* Allocate new memory block. */ + const size_t item_len = sizeof(knot_changeset_t); + size_t new_count = (changesets->allocated + extra); + knot_changeset_t *sets = malloc(new_count * item_len); + if (sets == NULL) { + return KNOT_ENOMEM; + } + + /* Clear old memory block and copy old data. */ + memset(sets, 0, new_count * item_len); + memcpy(sets, changesets->sets, changesets->allocated * item_len); + + /* Replace old changesets. */ + free(changesets->sets); + changesets->sets = sets; + changesets->allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_free_changeset(knot_changeset_t **changeset) +{ + /* XXX XXX investigate wrong frees. */ + assert((*changeset)->add_allocated >= (*changeset)->add_count); + assert((*changeset)->remove_allocated >= (*changeset)->remove_count); + assert((*changeset)->allocated >= (*changeset)->size); + + int j; + for (j = 0; j < (*changeset)->add_count; ++j) { + knot_rrset_deep_free(&(*changeset)->add[j], 1, 1, 1); + } + free((*changeset)->add); + + for (j = 0; j < (*changeset)->remove_count; ++j) { + knot_rrset_deep_free(&(*changeset)->remove[j], 1, 1, 1); + } + free((*changeset)->remove); + + knot_rrset_deep_free(&(*changeset)->soa_from, 1, 1, 1); + knot_rrset_deep_free(&(*changeset)->soa_to, 1, 1, 1); + + free((*changeset)->data); + + + *changeset = NULL; +} + +/*----------------------------------------------------------------------------*/ + +void knot_free_changesets(knot_changesets_t **changesets) +{ + if (changesets == NULL || *changesets == NULL) { + return; + } + + assert((*changesets)->allocated >= (*changesets)->count); + + for (int i = 0; i < (*changesets)->count; ++i) { + knot_changeset_t *ch = &(*changesets)->sets[i]; + knot_free_changeset(&ch); + } + + free((*changesets)->sets); + + knot_rrset_deep_free(&(*changesets)->first_soa, 1, 1, 1); + + free(*changesets); + *changesets = NULL; +} + +/*---------------------------------------------------------------------------*/ + + diff --git a/src/libknot/updates/changesets.h b/src/libknot/updates/changesets.h new file mode 100644 index 0000000..e8d5e39 --- /dev/null +++ b/src/libknot/updates/changesets.h @@ -0,0 +1,102 @@ +/*! + * \file changesets.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Structure for representing IXFR/DDNS changeset and its API. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_CHANGESETS_H_ +#define _KNOT_CHANGESETS_H_ + +#include "rrset.h" + +/*! \todo Changeset must be serializable/deserializable, so + * all data and pointers have to be changeset-exclusive, + * or more advanced structure serialization scheme has to be + * implemented. + * + * \todo Preallocation of space for changeset. + */ +typedef struct { + knot_rrset_t *soa_from; + knot_rrset_t **remove; + size_t remove_count; + size_t remove_allocated; + + knot_rrset_t *soa_to; + knot_rrset_t **add; + size_t add_count; + size_t add_allocated; + + uint8_t *data; + size_t size; + size_t allocated; + uint32_t serial_from; + uint32_t serial_to; +} knot_changeset_t; + +/*----------------------------------------------------------------------------*/ + +typedef struct { + knot_changeset_t *sets; + size_t count; + size_t allocated; + knot_rrset_t *first_soa; +} knot_changesets_t; + +/*----------------------------------------------------------------------------*/ + +typedef enum { + XFRIN_CHANGESET_ADD, + XFRIN_CHANGESET_REMOVE +} xfrin_changeset_part_t; + +/*----------------------------------------------------------------------------*/ + +int knot_changeset_allocate(knot_changesets_t **changesets); + +int knot_changeset_add_rrset(knot_rrset_t ***rrsets, + size_t *count, size_t *allocated, + knot_rrset_t *rrset); + +int knot_changeset_add_rr(knot_rrset_t ***rrsets, size_t *count, + size_t *allocated, knot_rrset_t *rr); + +int knot_changeset_add_new_rr(knot_changeset_t *changeset, + knot_rrset_t *rrset, + xfrin_changeset_part_t part); + +void knot_changeset_store_soa(knot_rrset_t **chg_soa, + uint32_t *chg_serial, knot_rrset_t *soa); + +int knot_changeset_add_soa(knot_changeset_t *changeset, knot_rrset_t *soa, + xfrin_changeset_part_t part); + +int knot_changesets_check_size(knot_changesets_t *changesets); + +void knot_free_changeset(knot_changeset_t **changeset); + +void knot_free_changesets(knot_changesets_t **changesets); + +#endif /* _KNOT_CHANGESETS_H_ */ + +/*! @} */ diff --git a/src/libknot/updates/ddns.c b/src/libknot/updates/ddns.c new file mode 100644 index 0000000..4c6ab7b --- /dev/null +++ b/src/libknot/updates/ddns.c @@ -0,0 +1,638 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "updates/ddns.h" +#include "updates/changesets.h" +#include "util/debug.h" +#include "packet/packet.h" +#include "util/error.h" +#include "consts.h" + +/*----------------------------------------------------------------------------*/ +// Copied from XFR - maybe extract somewhere else +static int knot_ddns_prereq_check_rrsets(knot_rrset_t ***rrsets, + size_t *count, size_t *allocated) +{ + int new_count = 0; + if (*count == *allocated) { + new_count = *allocated * 2; + } + + knot_rrset_t **rrsets_new = + (knot_rrset_t **)calloc(new_count, sizeof(knot_rrset_t *)); + if (rrsets_new == NULL) { + return KNOT_ENOMEM; + } + + memcpy(rrsets_new, *rrsets, *count); + *rrsets = rrsets_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_prereq_check_dnames(knot_dname_t ***dnames, + size_t *count, size_t *allocated) +{ + int new_count = 0; + if (*count == *allocated) { + new_count = *allocated * 2; + } + + knot_dname_t **dnames_new = + (knot_dname_t **)calloc(new_count, sizeof(knot_dname_t *)); + if (dnames_new == NULL) { + return KNOT_ENOMEM; + } + + memcpy(dnames_new, *dnames, *count); + *dnames = dnames_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_add_prereq_rrset(const knot_rrset_t *rrset, + knot_rrset_t ***rrsets, + size_t *count, size_t *allocd) +{ + // check if such RRSet is not already there and merge if needed + int ret; + for (int i = 0; i < *count; ++i) { + if (knot_rrset_compare(rrset, (*rrsets)[i], + KNOT_RRSET_COMPARE_HEADER) == 0) { + ret = knot_rrset_merge((void **)&((*rrsets)[i]), + (void **)&rrset); + if (ret != KNOT_EOK) { + return ret; + } else { + return KNOT_EOK; + } + } + } + + // if we are here, the RRSet was not found + ret = knot_ddns_prereq_check_rrsets(rrsets, count, allocd); + if (ret != KNOT_EOK) { + return ret; + } + + knot_rrset_t *new_rrset = NULL; + ret = knot_rrset_deep_copy(rrset, &new_rrset); + if (ret != KNOT_EOK) { + return ret; + } + + (*rrsets)[(*count)++] = new_rrset; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_add_prereq_dname(const knot_dname_t *dname, + knot_dname_t ***dnames, + size_t *count, size_t *allocd) +{ + // we do not have to check if the name is not already there + // if it is, we will just check it twice in the zone + + int ret = knot_ddns_prereq_check_dnames(dnames, count, allocd); + if (ret != KNOT_EOK) { + return ret; + } + + knot_dname_t *dname_new = knot_dname_deep_copy(dname); + if (dname_new == NULL) { + return KNOT_ENOMEM; + } + + (*dnames)[(*count)++] = dname_new; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_add_prereq(knot_ddns_prereq_t *prereqs, + const knot_rrset_t *rrset, uint16_t qclass) +{ + assert(prereqs != NULL); + assert(rrset != NULL); + + if (knot_rrset_ttl(rrset) != 0) { + return KNOT_EMALF; + } + + int ret; + + if (knot_rrset_class(rrset) == KNOT_CLASS_ANY) { + if (knot_rrset_rdata(rrset) != NULL) { + return KNOT_EMALF; + } + if (knot_rrset_type(rrset) == KNOT_RRTYPE_ANY) { + ret = knot_ddns_add_prereq_dname( + knot_rrset_owner(rrset), &prereqs->in_use, + &prereqs->in_use_count, + &prereqs->in_use_allocd); + } else { + ret = knot_ddns_add_prereq_rrset(rrset, + &prereqs->exist, + &prereqs->exist_count, + &prereqs->exist_allocd); + } + } else if (knot_rrset_class(rrset) == KNOT_CLASS_NONE) { + if (knot_rrset_rdata(rrset) != NULL) { + return KNOT_EMALF; + } + if (knot_rrset_type(rrset) == KNOT_RRTYPE_ANY) { + ret = knot_ddns_add_prereq_dname( + knot_rrset_owner(rrset), &prereqs->not_in_use, + &prereqs->not_in_use_count, + &prereqs->not_in_use_allocd); + } else { + ret = knot_ddns_add_prereq_rrset(rrset, + &prereqs->not_exist, + &prereqs->not_exist_count, + &prereqs->not_exist_allocd); + } + } else if (knot_rrset_class(rrset) == qclass) { + ret = knot_ddns_add_prereq_rrset(rrset, + &prereqs->exist_full, + &prereqs->exist_full_count, + &prereqs->exist_full_allocd); + } else { + return KNOT_EMALF; + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_add_update(knot_changeset_t *changeset, + const knot_rrset_t *rrset, uint16_t qclass) +{ + assert(changeset != NULL); + assert(rrset != NULL); + + int ret; + + // create a copy of the RRSet + /*! \todo If the packet was not parsed all at once, we could save this + * copy. + */ + knot_rrset_t *rrset_copy; + ret = knot_rrset_deep_copy(rrset, &rrset_copy); + if (ret != KNOT_EOK) { + return ret; + } + + /*! \todo What about the SOAs? */ + + if (knot_rrset_class(rrset) == qclass) { + // this RRSet should be added to the zone + ret = knot_changeset_add_rr(&changeset->add, + &changeset->add_count, + &changeset->add_allocated, + rrset_copy); + } else { + // this RRSet marks removal of something from zone + // what should be removed is distinguished when applying + ret = knot_changeset_add_rr(&changeset->remove, + &changeset->remove_count, + &changeset->remove_allocated, + rrset_copy); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_exist(const knot_zone_contents_t *zone, + const knot_rrset_t *rrset, uint8_t *rcode) +{ + assert(zone != NULL); + assert(rrset != NULL); + assert(rcode != NULL); + assert(knot_rrset_rdata(rrset) == NULL); + assert(knot_rrset_type(rrset) != KNOT_RRTYPE_ANY); + assert(knot_rrset_ttl(rrset) == 0); + assert(knot_rrset_class(rrset) == KNOT_CLASS_ANY); + + if (!knot_dname_is_subdomain(knot_rrset_owner(rrset), + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset)); + if (node == NULL) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_ENONODE; + } else if (knot_node_rrset(node, knot_rrset_type(rrset)) == NULL) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_ENORRSET; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_exist_full(const knot_zone_contents_t *zone, + const knot_rrset_t *rrset, uint8_t *rcode) +{ + assert(zone != NULL); + assert(rrset != NULL); + assert(rcode != NULL); + assert(knot_rrset_rdata(rrset) == NULL); + assert(knot_rrset_type(rrset) != KNOT_RRTYPE_ANY); + assert(knot_rrset_ttl(rrset) == 0); + assert(knot_rrset_class(rrset) == KNOT_CLASS_ANY); + + if (!knot_dname_is_subdomain(knot_rrset_owner(rrset), + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + const knot_rrset_t *found; + + node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset)); + if (node == NULL) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_EPREREQ; + } else if ((found = knot_node_rrset(node, knot_rrset_type(rrset))) + == NULL) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_EPREREQ; + } else { + // do not have to compare the header, it is already done + assert(knot_rrset_type(found) == knot_rrset_type(rrset)); + assert(knot_dname_compare(knot_rrset_owner(found), + knot_rrset_owner(rrset)) == 0); + if (knot_rrset_compare_rdata(found, rrset) <= 0) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_EPREREQ; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_not_exist(const knot_zone_contents_t *zone, + const knot_rrset_t *rrset, uint8_t *rcode) +{ + assert(zone != NULL); + assert(rrset != NULL); + assert(rcode != NULL); + assert(knot_rrset_rdata(rrset) == NULL); + assert(knot_rrset_type(rrset) != KNOT_RRTYPE_ANY); + assert(knot_rrset_ttl(rrset) == 0); + assert(knot_rrset_class(rrset) == KNOT_CLASS_NONE); + + if (!knot_dname_is_subdomain(knot_rrset_owner(rrset), + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + const knot_rrset_t *found; + + node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset)); + if (node == NULL) { + return KNOT_EOK; + } else if ((found = knot_node_rrset(node, knot_rrset_type(rrset))) + == NULL) { + return KNOT_EOK; + } else { + // do not have to compare the header, it is already done + assert(knot_rrset_type(found) == knot_rrset_type(rrset)); + assert(knot_dname_compare(knot_rrset_owner(found), + knot_rrset_owner(rrset)) == 0); + if (knot_rrset_compare_rdata(found, rrset) <= 0) { + return KNOT_EOK; + } + } + + *rcode = KNOT_RCODE_YXRRSET; + return KNOT_EPREREQ; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_in_use(const knot_zone_contents_t *zone, + const knot_dname_t *dname, uint8_t *rcode) +{ + assert(zone != NULL); + assert(dname != NULL); + assert(rcode != NULL); + + if (!knot_dname_is_subdomain(dname, + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + + node = knot_zone_contents_find_node(zone, dname); + if (node == NULL) { + *rcode = KNOT_RCODE_NXDOMAIN; + return KNOT_EPREREQ; + } else if (knot_node_rrset_count(node) == 0) { + *rcode = KNOT_RCODE_NXDOMAIN; + return KNOT_EPREREQ; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_not_in_use(const knot_zone_contents_t *zone, + const knot_dname_t *dname, uint8_t *rcode) +{ + assert(zone != NULL); + assert(dname != NULL); + assert(rcode != NULL); + + if (!knot_dname_is_subdomain(dname, + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + + node = knot_zone_contents_find_node(zone, dname); + if (node == NULL) { + return KNOT_EOK; + } else if (knot_node_rrset_count(node) == 0) { + return KNOT_EOK; + } + + *rcode = KNOT_RCODE_YXDOMAIN; + return KNOT_EPREREQ; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +int knot_ddns_check_zone(const knot_zone_t *zone, knot_packet_t *query, + uint8_t *rcode) +{ + if (zone == NULL || query == NULL || rcode == NULL) { + return KNOT_EBADARG; + } + + if (knot_packet_qtype(query) != KNOT_RRTYPE_SOA) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + + if(!knot_zone_contents(zone)) { + *rcode = KNOT_RCODE_REFUSED; + return KNOT_ENOZONE; + } + + // 1) check if the zone is master or slave + if (!knot_zone_is_master(zone)) { + return KNOT_EBADZONE; + } + + // 2) check zone CLASS + if (knot_zone_contents_class(knot_zone_contents(zone)) != + knot_packet_qclass(query)) { + *rcode = KNOT_RCODE_NOTAUTH; + return KNOT_ENOZONE; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ddns_process_prereqs(knot_packet_t *query, + knot_ddns_prereq_t **prereqs, uint8_t *rcode) +{ + /*! \todo Consider not parsing the whole packet at once, but + * parsing one RR at a time - could save some memory and time. + */ + + if (query == NULL || prereqs == NULL || rcode == NULL) { + return KNOT_EBADARG; + } + + // allocate space for the prerequisities + *prereqs = (knot_ddns_prereq_t *)calloc(1, sizeof(knot_ddns_prereq_t)); + CHECK_ALLOC_LOG(*prereqs, KNOT_ENOMEM); + + int ret; + + for (int i = 0; i < knot_packet_answer_rrset_count(query); ++i) { + // we must copy the RRSets, because all those stored in the + // packet will be destroyed + ret = knot_ddns_add_prereq(*prereqs, + knot_packet_answer_rrset(query, i), + knot_packet_qclass(query)); + if (ret != KNOT_EOK) { + dbg_ddns("Failed to add prerequisity RRSet:%s\n", + knot_strerror(ret)); + *rcode = (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR + : KNOT_RCODE_SERVFAIL; + knot_ddns_prereqs_free(prereqs); + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ddns_check_prereqs(const knot_zone_contents_t *zone, + knot_ddns_prereq_t **prereqs, uint8_t *rcode) +{ + int i, ret; + + for (i = 0; i < (*prereqs)->exist_count; ++i) { + ret = knot_ddns_check_exist(zone, (*prereqs)->exist[i], rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + for (i = 0; i < (*prereqs)->exist_full_count; ++i) { + ret = knot_ddns_check_exist_full(zone, + (*prereqs)->exist_full[i], + rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + for (i = 0; i < (*prereqs)->not_exist_count; ++i) { + ret = knot_ddns_check_not_exist(zone, (*prereqs)->not_exist[i], + rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + for (i = 0; i < (*prereqs)->in_use_count; ++i) { + ret = knot_ddns_check_in_use(zone, (*prereqs)->in_use[i], + rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + for (i = 0; i < (*prereqs)->not_in_use_count; ++i) { + ret = knot_ddns_check_not_in_use(zone, + (*prereqs)->not_in_use[i], + rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_update(const knot_rrset_t *rrset, + const knot_packet_t *query, uint8_t *rcode) +{ + if (!knot_dname_is_subdomain(knot_rrset_owner(rrset), + knot_packet_qname(query))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + if (knot_rrset_class(rrset) == knot_packet_qclass(query)) { + if (knot_rrtype_is_metatype(knot_rrset_type(rrset))) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + } else if (knot_rrset_class(rrset) == KNOT_CLASS_ANY) { + if (knot_rrset_rdata(rrset) != NULL + || (knot_rrtype_is_metatype(knot_rrset_type(rrset)) + && knot_rrset_type(rrset) != KNOT_RRTYPE_ANY)) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + } else if (knot_rrset_class(rrset) == KNOT_CLASS_NONE) { + if (knot_rrset_ttl(rrset) != 0 + || knot_rrtype_is_metatype(knot_rrset_type(rrset))) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + } else { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ddns_process_update(knot_packet_t *query, + knot_changeset_t **changeset, uint8_t *rcode) +{ + // just put all RRSets from query's Authority section + // it will be distinguished when applying to the zone + + if (query == NULL || changeset == NULL || rcode == NULL) { + return KNOT_EBADARG; + } + + *changeset = (knot_changeset_t *)calloc(1, sizeof(knot_changeset_t)); + CHECK_ALLOC_LOG(*changeset, KNOT_ENOMEM); + + int ret; + + for (int i = 0; i < knot_packet_authority_rrset_count(query); ++i) { + + const knot_rrset_t *rrset = + knot_packet_authority_rrset(query, i); + + ret = knot_ddns_check_update(rrset, query, rcode); + if (ret != KNOT_EOK) { + return ret; + } + + ret = knot_ddns_add_update(*changeset, rrset, + knot_packet_qclass(query)); + + if (ret != KNOT_EOK) { + dbg_ddns("Failed to add update RRSet:%s\n", + knot_strerror(ret)); + *rcode = (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR + : KNOT_RCODE_SERVFAIL; + knot_free_changeset(changeset); + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_ddns_prereqs_free(knot_ddns_prereq_t **prereq) +{ + int i; + + for (i = 0; i < (*prereq)->exist_count; ++i) { + knot_rrset_deep_free(&(*prereq)->exist[i], 1, 1, 1); + } + + for (i = 0; i < (*prereq)->exist_full_count; ++i) { + knot_rrset_deep_free(&(*prereq)->exist_full[i], 1, 1, 1); + } + + for (i = 0; i < (*prereq)->not_exist_count; ++i) { + knot_rrset_deep_free(&(*prereq)->not_exist[i], 1, 1, 1); + } + + for (i = 0; i < (*prereq)->in_use_count; ++i) { + knot_dname_free(&(*prereq)->in_use[i]); + } + + for (i = 0; i < (*prereq)->not_in_use_count; ++i) { + knot_dname_free(&(*prereq)->not_in_use[i]); + } + + free(*prereq); + *prereq = NULL; +} diff --git a/src/libknot/updates/ddns.h b/src/libknot/updates/ddns.h new file mode 100644 index 0000000..dceebed --- /dev/null +++ b/src/libknot/updates/ddns.h @@ -0,0 +1,74 @@ +/*! + * \file ddns.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Dynamic updates processing. + * + * \addtogroup query_processing + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_DDNS_H_ +#define _KNOT_DDNS_H_ + +#include "updates/changesets.h" +#include "zone/zone.h" +#include "packet/packet.h" +#include "rrset.h" +#include "dname.h" + +typedef struct knot_ddns_prereq_t { + knot_rrset_t **exist; + size_t exist_count; + size_t exist_allocd; + + knot_rrset_t **exist_full; + size_t exist_full_count; + size_t exist_full_allocd; + + knot_rrset_t **not_exist; + size_t not_exist_count; + size_t not_exist_allocd; + + knot_dname_t **in_use; + size_t in_use_count; + size_t in_use_allocd; + + knot_dname_t **not_in_use; + size_t not_in_use_count; + size_t not_in_use_allocd; +} knot_ddns_prereq_t; + +int knot_ddns_check_zone(const knot_zone_t *zone, knot_packet_t *query, + uint8_t *rcode); + +int knot_ddns_process_prereqs(knot_packet_t *query, + knot_ddns_prereq_t **prereqs, uint8_t *rcode); + +int knot_ddns_check_prereqs(const knot_zone_contents_t *zone, + knot_ddns_prereq_t **prereqs, uint8_t *rcode); + +int knot_ddns_process_update(knot_packet_t *query, + knot_changeset_t **changeset, uint8_t *rcode); + +void knot_ddns_prereqs_free(knot_ddns_prereq_t **prereq); + +#endif /* _KNOT_DDNS_H_ */ + +/*! @} */ diff --git a/src/libknot/updates/xfr-in.c b/src/libknot/updates/xfr-in.c new file mode 100644 index 0000000..51be430 --- /dev/null +++ b/src/libknot/updates/xfr-in.c @@ -0,0 +1,3013 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <urcu.h> + +#include "updates/xfr-in.h" + +#include "nameserver/name-server.h" +#include "util/wire.h" +#include "util/debug.h" +// #include "knot/zone/zone-dump.h" +// #include "knot/zone/zone-load.h" +#include "packet/packet.h" +#include "dname.h" +#include "zone/zone.h" +#include "packet/query.h" +#include "packet/response.h" +#include "util/error.h" +#include "updates/changesets.h" +#include "tsig.h" +#include "tsig-op.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ + +static int xfrin_create_query(knot_dname_t *qname, uint16_t qtype, + uint16_t qclass, knot_ns_xfr_t *xfr, size_t *size, + const knot_rrset_t *soa, int use_tsig) +{ + knot_packet_t *pkt = knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + CHECK_ALLOC_LOG(pkt, KNOT_ENOMEM); + + /*! \todo Get rid of the numeric constant. */ + int rc = knot_packet_set_max_size(pkt, 512); + if (rc != KNOT_EOK) { + knot_packet_free(&pkt); + return KNOT_ERROR; + } + + rc = knot_query_init(pkt); + if (rc != KNOT_EOK) { + knot_packet_free(&pkt); + return KNOT_ERROR; + } + + knot_question_t question; + + /* Retain qname until the question is freed. */ + knot_dname_retain(qname); + + /* Set random query ID. */ + knot_packet_set_random_id(pkt); + knot_wire_set_id(pkt->wireformat, pkt->header.id); + + // this is ugly!! + question.qname = (knot_dname_t *)qname; + question.qtype = qtype; + question.qclass = qclass; + + rc = knot_query_set_question(pkt, &question); + if (rc != KNOT_EOK) { + knot_dname_release(question.qname); + knot_packet_free(&pkt); + return KNOT_ERROR; + } + + /* Reserve space for TSIG. */ + if (use_tsig && xfr->tsig_key) { + dbg_xfrin_detail("xfrin: setting packet TSIG size to %zu\n", + xfr->tsig_size); + knot_packet_set_tsig_size(pkt, xfr->tsig_size); + } + + /* Add SOA RR to authority section for IXFR. */ + if (qtype == KNOT_RRTYPE_IXFR && soa) { + knot_query_add_rrset_authority(pkt, soa); + } + + /*! \todo OPT RR ?? */ + + uint8_t *wire = NULL; + size_t wire_size = 0; + rc = knot_packet_to_wire(pkt, &wire, &wire_size); + if (rc != KNOT_EOK) { + dbg_xfrin("Failed to write packet to wire.\n"); + knot_dname_release(question.qname); + knot_packet_free(&pkt); + return KNOT_ERROR; + } + + if (wire_size > *size) { + dbg_xfrin("Not enough space provided for the wire " + "format of the query.\n"); + knot_packet_free(&pkt); + return KNOT_ESPACE; + } + + // wire format created, sign it with TSIG if required + if (use_tsig && xfr->tsig_key) { + char *name = knot_dname_to_str(xfr->tsig_key->name); + dbg_xfrin_detail("Signing XFR query with key (name %s): \n", + name); + free(name); + dbg_xfrin_hex_detail(xfr->tsig_key->secret, + xfr->tsig_key->secret_size); + + xfr->digest_size = xfr->digest_max_size; + rc = knot_tsig_sign(wire, &wire_size, *size, NULL, 0, + xfr->digest, &xfr->digest_size, xfr->tsig_key); + if (rc != KNOT_EOK) { + /*! \todo [TSIG] Handle TSIG errors. */ + knot_packet_free(&pkt); + return rc; + } + + dbg_xfrin_detail("Signed XFR query, new wire size: %zu, digest:" + "\n", wire_size); + dbg_xfrin_hex_detail((const char*)xfr->digest, xfr->digest_size); + } + + memcpy(xfr->wire, wire, wire_size); + *size = wire_size; + + dbg_xfrin("Created query of size %zu.\n", *size); + knot_packet_dump(pkt); + + knot_packet_free(&pkt); + + /* Release qname. */ + knot_dname_release(question.qname); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +int xfrin_create_soa_query(knot_dname_t *owner, knot_ns_xfr_t *xfr, + size_t *size) +{ + /*! \todo [TSIG] Should TSIG apply for SOA query too? */ + return xfrin_create_query(owner, KNOT_RRTYPE_SOA, + KNOT_CLASS_IN, xfr, size, 0, 0); +} + +/*----------------------------------------------------------------------------*/ + +int xfrin_transfer_needed(const knot_zone_contents_t *zone, + knot_packet_t *soa_response) +{ + // first, parse the rest of the packet + assert(!knot_packet_is_query(soa_response)); + dbg_xfrin("Response - parsed: %zu, total wire size: %zu\n", + soa_response->parsed, soa_response->size); + int ret; + + if (soa_response->parsed < soa_response->size) { + ret = knot_packet_parse_rest(soa_response); + if (ret != KNOT_EOK) { + return KNOT_EMALF; + } + } + + /* + * Retrieve the local Serial + */ + const knot_rrset_t *soa_rrset = + knot_node_rrset(knot_zone_contents_apex(zone), + KNOT_RRTYPE_SOA); + if (soa_rrset == NULL) { + char *name = knot_dname_to_str(knot_node_owner( + knot_zone_contents_apex(zone))); + dbg_xfrin("SOA RRSet missing in the zone %s!\n", name); + free(name); + return KNOT_ERROR; + } + + int64_t local_serial = knot_rdata_soa_serial( + knot_rrset_rdata(soa_rrset)); + if (local_serial < 0) { +dbg_xfrin_exec( + char *name = knot_dname_to_str(knot_rrset_owner(soa_rrset)); + dbg_xfrin("Malformed data in SOA of zone %s\n", name); + free(name); +); + return KNOT_EMALF; // maybe some other error + } + + /* + * Retrieve the remote Serial + */ + // the SOA should be the first (and only) RRSet in the response + soa_rrset = knot_packet_answer_rrset(soa_response, 0); + if (soa_rrset == NULL + || knot_rrset_type(soa_rrset) != KNOT_RRTYPE_SOA) { + return KNOT_EMALF; + } + + int64_t remote_serial = knot_rdata_soa_serial( + knot_rrset_rdata(soa_rrset)); + if (remote_serial < 0) { + return KNOT_EMALF; // maybe some other error + } + + return (ns_serial_compare(local_serial, remote_serial) < 0); +} + +/*----------------------------------------------------------------------------*/ + +int xfrin_create_axfr_query(knot_dname_t *owner, knot_ns_xfr_t *xfr, + size_t *size, int use_tsig) +{ + return xfrin_create_query(owner, KNOT_RRTYPE_AXFR, + KNOT_CLASS_IN, xfr, size, 0, use_tsig); +} + +/*----------------------------------------------------------------------------*/ + +int xfrin_create_ixfr_query(const knot_zone_contents_t *zone, + knot_ns_xfr_t *xfr, size_t *size, int use_tsig) +{ + /*! + * \todo Implement properly. + */ + knot_node_t *apex = knot_zone_contents_get_apex(zone); + const knot_rrset_t *soa = knot_node_rrset(apex, KNOT_RRTYPE_SOA); + + return xfrin_create_query(knot_node_get_owner(apex), KNOT_RRTYPE_IXFR, + KNOT_CLASS_IN, xfr, size, soa, use_tsig); +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_add_orphan_rrsig(xfrin_orphan_rrsig_t *rrsigs, + knot_rrset_t *rr) +{ + // try to find similar RRSIGs (check owner and type covered) in the list + assert(knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG); + + int ret = 0; + xfrin_orphan_rrsig_t **last = &rrsigs; + while (*last != NULL) { + // check if the RRSIG is not similar to the one we want to add + assert((*last)->rrsig != NULL); + if (knot_rrset_compare((*last)->rrsig, rr, + KNOT_RRSET_COMPARE_HEADER) == 1 + && knot_rdata_rrsig_type_covered(knot_rrset_rdata( + (*last)->rrsig)) + == knot_rdata_rrsig_type_covered(knot_rrset_rdata(rr))) { + ret = knot_rrset_merge((void **)&(*last)->rrsig, + (void **)&rr); + if (ret != KNOT_EOK) { + return ret; + } else { + return 1; + } + } + last = &((*last)->next); + } + + assert(*last == NULL); + // we did not find the right RRSIGs, add to the end + *last = (xfrin_orphan_rrsig_t *)malloc(sizeof(xfrin_orphan_rrsig_t)); + CHECK_ALLOC_LOG(*last, KNOT_ENOMEM); + + (*last)->rrsig = rr; + (*last)->next = NULL; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_process_orphan_rrsigs(knot_zone_contents_t *zone, + xfrin_orphan_rrsig_t *rrsigs) +{ + xfrin_orphan_rrsig_t **last = &rrsigs; + int ret = 0; + while (*last != NULL) { + knot_rrset_t *rrset = NULL; + knot_node_t *node = NULL; + ret = knot_zone_contents_add_rrsigs(zone, (*last)->rrsig, + &rrset, &node, + KNOT_RRSET_DUPL_MERGE, 1); + if (ret > 0) { + knot_rrset_free(&(*last)->rrsig); + } else if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add orphan RRSIG to zone.\n"); + return ret; + } else { + (*last)->rrsig = NULL; + } + + last = &((*last)->next); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_free_orphan_rrsigs(xfrin_orphan_rrsig_t **rrsigs) +{ + xfrin_orphan_rrsig_t *r = *rrsigs; + while (r != NULL) { + xfrin_orphan_rrsig_t *prev = r; + r = r->next; + free(prev); + } + + *rrsigs = NULL; +} + +/*----------------------------------------------------------------------------*/ +/*! \note [TSIG] */ +static int xfrin_check_tsig(knot_packet_t *packet, knot_ns_xfr_t *xfr, + int tsig_req) +{ + assert(packet != NULL); + assert(xfr != NULL); + + dbg_xfrin_verb("xfrin_check_tsig(): packet nr: %d, required: %d\n", + xfr->packet_nr, tsig_req); + + /* + * If we are expecting it (i.e. xfr->prev_digest_size > 0) + * a) it should be there (first, last or each 100th packet) and it + * is not + * Then we should discard the changes and close the connection. + * b) it should be there and it is or it may not be there (other + * packets) and it is + * We validate the TSIG and reset packet number counting and + * data aggregation. + * + * If we are not expecting it (i.e. xfr->prev_digest_size <= 0) and + * it is there => it should probably be considered an error + */ + knot_rrset_t *tsig = NULL; + int ret = knot_packet_parse_next_rr_additional(packet, &tsig); + if (ret != KNOT_EOK) { + return ret; + } + + if (xfr->tsig_key) { + if (tsig_req && tsig == NULL) { + // TSIG missing!! + return KNOT_EMALF; + } else if (tsig != NULL) { + // TSIG there, either required or not, process + if (xfr->packet_nr == 0) { + ret = knot_tsig_client_check(tsig, + xfr->wire, xfr->wire_size, + xfr->digest, xfr->digest_size, + xfr->tsig_key); + } else { + ret = knot_tsig_client_check_next(tsig, + xfr->wire, xfr->wire_size, + xfr->digest, xfr->digest_size, + xfr->tsig_key); + } + + if (ret != KNOT_EOK) { + /*! \note [TSIG] No need to check TSIG error + * here, propagate and check elsewhere.*/ + return ret; + } + + // and reset the data storage + //xfr->packet_nr = 1; + xfr->tsig_data_size = 0; + + // Extract the digest from the TSIG RDATA and store it. + if (xfr->digest_max_size < tsig_rdata_mac_length(tsig)) { + return KNOT_ESPACE; + } + memcpy(xfr->digest, tsig_rdata_mac(tsig), + tsig_rdata_mac_length(tsig)); + xfr->digest_size = tsig_rdata_mac_length(tsig); + + } else { // TSIG not required and not there + // just append the wireformat to the TSIG data + assert(KNOT_NS_TSIG_DATA_MAX_SIZE - xfr->tsig_data_size + >= xfr->wire_size); + memcpy(xfr->tsig_data + xfr->tsig_data_size, + xfr->wire, xfr->wire_size); + xfr->tsig_data_size += xfr->wire_size; + } + } else if (tsig != NULL) { + // TSIG where it should not be + return KNOT_EMALF; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int xfrin_process_axfr_packet(/*const uint8_t *pkt, size_t size, + xfrin_constructed_zone_t **constr*/ + knot_ns_xfr_t *xfr) +{ + const uint8_t *pkt = xfr->wire; + size_t size = xfr->wire_size; + xfrin_constructed_zone_t **constr = + (xfrin_constructed_zone_t **)(&xfr->data); + + if (pkt == NULL || constr == NULL) { + dbg_xfrin("Wrong parameters supported.\n"); + return KNOT_EBADARG; + } + + dbg_xfrin("Processing AXFR packet of size %zu.\n", size); + + // check if the response is OK + if (knot_wire_get_rcode(pkt) != KNOT_RCODE_NOERROR) { + return KNOT_EXFRREFUSED; + } + + /*! \todo Should TC bit be checked? */ + + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_NONE); + if (packet == NULL) { + dbg_xfrin("Could not create packet structure.\n"); + return KNOT_ENOMEM; + } + + int ret = knot_packet_parse_from_wire(packet, pkt, size, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Could not parse packet: %s.\n", + knot_strerror(ret)); + knot_packet_free(&packet); + /*! \todo Cleanup. */ + return KNOT_EMALF; + } + + /*! \todo [TSIG] If packet RCODE is NOTAUTH(9), process as TSIG error. */ + + knot_rrset_t *rr = NULL; + ret = knot_packet_parse_next_rr_answer(packet, &rr); + + if (ret != KNOT_EOK) { + dbg_xfrin("Could not parse first Answer RR: %s.\n", + knot_strerror(ret)); + knot_packet_free(&packet); + /*! \todo Cleanup. */ + return KNOT_EMALF; + } + + if (rr == NULL) { + dbg_xfrin("No RRs in the packet.\n"); + knot_packet_free(&packet); + /*! \todo Cleanup. */ + return KNOT_EMALF; + } + + /*! \todo We should probably test whether the Question of the first + * message corresponds to the SOA RR. + */ + + knot_node_t *node = NULL; + int in_zone = 0; + knot_zone_contents_t *zone = NULL; + + if (*constr == NULL) { + // this should be the first packet + /*! \note [TSIG] Packet number for checking TSIG validation. */ + xfr->packet_nr = 0; + /*! \note [TSIG] Storing total size of data for TSIG digest. */ + xfr->tsig_data_size = 0; + + // create new zone + /*! \todo Ensure that the packet is the first one. */ + if (knot_rrset_type(rr) != KNOT_RRTYPE_SOA) { + dbg_xfrin("No zone created, but the first RR in " + "Answer is not a SOA RR.\n"); + knot_packet_free(&packet); + knot_node_free(&node, 0, 0); + knot_rrset_deep_free(&rr, 1, 1, 1); + /*! \todo Cleanup. */ + return KNOT_EMALF; + } + + if (knot_dname_compare(knot_rrset_owner(rr), + knot_packet_qname(packet)) != 0) { +dbg_xfrin_exec( + char *rr_owner = + knot_dname_to_str(knot_rrset_owner(rr)); + char *qname = knot_dname_to_str( + knot_packet_qname(packet)); + + dbg_xfrin("Owner of the first SOA RR (%s) does not" + " match QNAME (%s).\n", rr_owner, qname); + + free(rr_owner); + free(qname); +); + /*! \todo Cleanup. */ + knot_packet_free(&packet); + knot_node_free(&node, 0, 0); + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_EMALF; + } + + node = knot_node_new(rr->owner, NULL, 0); + if (node == NULL) { + dbg_xfrin("Failed to create new node.\n"); + knot_packet_free(&packet); + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_ENOMEM; + } + + // the first RR is SOA and its owner and QNAME are the same + // create the zone + + *constr = (xfrin_constructed_zone_t *)malloc( + sizeof(xfrin_constructed_zone_t)); + if (*constr == NULL) { + dbg_xfrin("Failed to create new constr. zone.\n"); + knot_packet_free(&packet); + knot_node_free(&node, 0, 0); + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_ENOMEM; + } + + memset(*constr, 0, sizeof(xfrin_constructed_zone_t)); + + (*constr)->contents = knot_zone_contents_new(node, 0, 1, NULL); +// assert(0); + if ((*constr)->contents== NULL) { + dbg_xfrin("Failed to create new zone.\n"); + knot_packet_free(&packet); + knot_node_free(&node, 0, 0); + knot_rrset_deep_free(&rr, 1, 1, 1); + /*! \todo Cleanup. */ + return KNOT_ENOMEM; + } + + in_zone = 1; + assert(node->owner == rr->owner); + zone = (*constr)->contents; + assert(zone->apex == node); + assert(zone->apex->owner == rr->owner); + // add the RRSet to the node + //ret = knot_node_add_rrset(node, rr, 0); + ret = knot_zone_contents_add_rrset(zone, rr, &node, + KNOT_RRSET_DUPL_MERGE, 1); + if (ret < 0) { + dbg_xfrin("Failed to add RRSet to zone node: %s.\n", + knot_strerror(ret)); + knot_packet_free(&packet); + knot_node_free(&node, 0, 0); + knot_rrset_deep_free(&rr, 1, 1, 1); + /*! \todo Cleanup. */ + return KNOT_ERROR; + } else if (ret > 0) { + dbg_xfrin("Merged SOA RRSet.\n"); + // merged, free the RRSet + //knot_rrset_deep_free(&rr, 1, 0, 0); + knot_rrset_free(&rr); + } + + // take next RR + ret = knot_packet_parse_next_rr_answer(packet, &rr); + } else { + zone = (*constr)->contents; + ++xfr->packet_nr; + } + + /*! \note [TSIG] add the packet wire size to the data to be verified by + * TSIG + */ + if (xfr->tsig_key) { + dbg_xfrin("Adding packet wire to TSIG data (size till now: %zu," + " adding: %zu).\n", xfr->tsig_data_size, + xfr->wire_size); + assert(KNOT_NS_TSIG_DATA_MAX_SIZE - xfr->tsig_data_size + >= xfr->wire_size); + memcpy(xfr->tsig_data + xfr->tsig_data_size, xfr->wire, + xfr->wire_size); + xfr->tsig_data_size += xfr->wire_size; + } + + assert(zone != NULL); + + while (ret == KNOT_EOK && rr != NULL) { + // process the parsed RR + + dbg_xfrin("\nNext RR:\n\n"); + knot_rrset_dump(rr, 0); + + if (node != NULL + && knot_dname_compare(rr->owner, node->owner) != 0) { +dbg_xfrin_exec( + char *name = knot_dname_to_str(node->owner); + dbg_xfrin("Node owner: %s\n", name); + free(name); +); + if (!in_zone) { + // this should not happen + assert(0); + // the node is not in the zone and the RR has + // other owner, so a new node must be created + // insert the old node to the zone + } + + node = NULL; + } + + if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) { + // this must be the last SOA, do not do anything more + // discard the RR + assert(knot_zone_contents_apex((zone)) != NULL); + assert(knot_node_rrset(knot_zone_contents_apex((zone)), + KNOT_RRTYPE_SOA) != NULL); + dbg_xfrin("Found last SOA, transfer finished.\n"); + + dbg_xfrin("Verifying TSIG...\n"); + /*! \note [TSIG] Now check if there is not a TSIG record + * at the end of the packet. + */ + ret = xfrin_check_tsig(packet, xfr, 1); + + dbg_xfrin_detail("xfrin_check_tsig() returned %d\n", + ret); + + knot_packet_free(&packet); + knot_rrset_deep_free(&rr, 1, 1, 1); + + if (ret != KNOT_EOK) { + /*! \todo [TSIG] Handle TSIG errors. */ + return ret; + } + + // we must now find place for all orphan RRSIGs + ret = xfrin_process_orphan_rrsigs(zone, + (*constr)->rrsigs); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to process orphan " + "RRSIGs\n"); + /*! \todo Cleanup?? */ + return ret; + } + + xfrin_free_orphan_rrsigs(&(*constr)->rrsigs); + + return 1; + } + + if (knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG) { + // RRSIGs require special handling, as there are no + // nodes for them + knot_rrset_t *tmp_rrset = NULL; + ret = knot_zone_contents_add_rrsigs(zone, rr, + &tmp_rrset, &node, KNOT_RRSET_DUPL_MERGE, 1); + if (ret == KNOT_ENONODE || ret == KNOT_ENORRSET) { + dbg_xfrin("No node or RRSet for RRSIGs\n"); + dbg_xfrin("Saving for later insertion.\n"); + ret = xfrin_add_orphan_rrsig((*constr)->rrsigs, + rr); + if (ret > 0) { + dbg_xfrin("Merged RRSIGs.\n"); + knot_rrset_free(&rr); + } else if (ret != KNOT_EOK) { + dbg_xfrin("Failed to save orphan" + " RRSIGs.\n"); + knot_packet_free(&packet); + knot_node_free(&node, 1, 0); // ??? + knot_rrset_deep_free(&rr, 1, 1, 1); + return ret; + } + } else if (ret < 0) { + dbg_xfrin("Failed to add RRSIGs (%s).\n", + knot_strerror(ret)); + knot_packet_free(&packet); + knot_node_free(&node, 1, 0); // ??? + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_ERROR; /*! \todo Other error code. */ + } else if (ret == 1) { + assert(node != NULL); +dbg_xfrin_exec( + char *name = knot_dname_to_str(node->owner); + dbg_xfrin("Found node for the record in " + "zone: %s.\n", name); + free(name); +); + in_zone = 1; + knot_rrset_deep_free(&rr, 1, 0, 0); + } else if (ret == 2) { + // should not happen + assert(0); +// knot_rrset_deep_free(&rr, 1, 1, 1); + } else { + assert(node != NULL); +dbg_xfrin_exec( + char *name = knot_dname_to_str(node->owner); + dbg_xfrin("Found node for the record in " + "zone: %s.\n", name); + free(name); +); + in_zone = 1; + assert(tmp_rrset->rrsigs == rr); + } + + // parse next RR + ret = knot_packet_parse_next_rr_answer(packet, &rr); + + continue; + } + + /*! \note [TSIG] TSIG where it should not be - in Answer section.*/ + if (knot_rrset_type(rr) == KNOT_RRTYPE_TSIG) { + // not allowed here + dbg_xfrin(" in Answer section.\n"); + knot_packet_free(&packet); + knot_node_free(&node, 1, 0); // ??? + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_EMALF; + } + + knot_node_t *(*get_node)(const knot_zone_contents_t *, + const knot_dname_t *) = NULL; + int (*add_node)(knot_zone_contents_t *, knot_node_t *, int, + uint8_t, int) = NULL; + + if (knot_rrset_type(rr) == KNOT_RRTYPE_NSEC3) { + get_node = knot_zone_contents_get_nsec3_node; + add_node = knot_zone_contents_add_nsec3_node; + } else { + get_node = knot_zone_contents_get_node; + add_node = knot_zone_contents_add_node; + } + + if (node == NULL && (node = get_node(zone, + knot_rrset_owner(rr))) != NULL) { + // the node for this RR was found in the zone + dbg_xfrin("Found node for the record in zone.\n"); + in_zone = 1; + } + + if (node == NULL) { + // a new node for the RR is required but it is not + // in the zone + node = knot_node_new(rr->owner, NULL, 0); + if (node == NULL) { + dbg_xfrin("Failed to create new node.\n"); + knot_packet_free(&packet); + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_ENOMEM; + } + dbg_xfrin("Created new node for the record.\n"); + + // insert the RRSet to the node + ret = knot_node_add_rrset(node, rr, 1); + if (ret < 0) { + dbg_xfrin("Failed to add RRSet to node (%s" + ")\n", knot_strerror(ret)); + knot_packet_free(&packet); + knot_node_free(&node, 1, 0); // ??? + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_ERROR; + } else if (ret > 0) { + // should not happen, this is new node + assert(0); +// knot_rrset_deep_free(&rr, 1, 0, 0); + } + + // insert the node into the zone + ret = add_node(zone, node, 1, 0, 1); + assert(node != NULL); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add node to zone (%s)" + ".\n", knot_strerror(ret)); + knot_packet_free(&packet); + knot_node_free(&node, 1, 0); // ??? + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_ERROR; + } + + in_zone = 1; + } else { + assert(in_zone); + + ret = knot_zone_contents_add_rrset(zone, rr, &node, + KNOT_RRSET_DUPL_MERGE, 1); + if (ret < 0) { + dbg_xfrin("Failed to add RRSet to zone:" + "%s.\n", knot_strerror(ret)); + return KNOT_ERROR; + } else if (ret > 0) { + // merged, free the RRSet +// knot_rrset_deep_free(&rr, 1, 0, 0); + knot_rrset_free(&rr); + } + + } + + rr = NULL; + + // parse next RR + ret = knot_packet_parse_next_rr_answer(packet, &rr); + } + + assert(ret != KNOT_EOK || rr == NULL); + + if (ret < 0) { + // some error in parsing + dbg_xfrin("Could not parse next RR: %s.\n", + knot_strerror(ret)); + knot_packet_free(&packet); + knot_node_free(&node, 0, 0); + knot_rrset_deep_free(&rr, 1, 1, 1); + /*! \todo Cleanup. */ + return KNOT_EMALF; + } + + assert(ret == KNOT_EOK); + assert(rr == NULL); + + // if the last node is not yet in the zone, insert + if (!in_zone) { + assert(node != NULL); + ret = knot_zone_contents_add_node(zone, node, 1, 0, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add last node into zone (%s)" + ".\n", knot_strerror(ret)); + knot_packet_free(&packet); + knot_node_free(&node, 1, 0); + return KNOT_ERROR; /*! \todo Other error */ + } + } + + /*! \note [TSIG] Now check if there is not a TSIG record at the end of + * the packet. + */ + ret = xfrin_check_tsig(packet, xfr, + knot_ns_tsig_required(xfr->packet_nr)); + ++xfr->packet_nr; + + knot_packet_free(&packet); + dbg_xfrin("Processed one AXFR packet successfully.\n"); + + /*! \note [TSIG] TSIG errors are propagated and reported in a standard + * manner, as we're in response processing, no further error response + * should be sent. + */ + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_parse_first_rr(knot_packet_t **packet, const uint8_t *pkt, + size_t size, knot_rrset_t **rr) +{ + *packet = knot_packet_new(KNOT_PACKET_PREALLOC_NONE); + if (packet == NULL) { + dbg_xfrin("Could not create packet structure.\n"); + return KNOT_ENOMEM; + } + + int ret = knot_packet_parse_from_wire(*packet, pkt, size, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Could not parse packet: %s.\n", + knot_strerror(ret)); + knot_packet_free(packet); + return KNOT_EMALF; + } + + // check if the TC bit is set (it must not be) + if (knot_wire_get_tc(pkt)) { + dbg_xfrin("IXFR response has TC bit set.\n"); + knot_packet_free(packet); + return KNOT_EMALF; + } + + ret = knot_packet_parse_next_rr_answer(*packet, rr); + + if (ret != KNOT_EOK) { + dbg_xfrin("Could not parse first Answer RR: %s.\n", + knot_strerror(ret)); + knot_packet_free(packet); + return KNOT_EMALF; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int xfrin_process_ixfr_packet(/*const uint8_t *pkt, size_t size, + knot_changesets_t **chs*/knot_ns_xfr_t *xfr) +{ + size_t size = xfr->wire_size; + const uint8_t *pkt = xfr->wire; + knot_changesets_t **chs = (knot_changesets_t **)(&xfr->data); + + if (pkt == NULL || chs == NULL) { + dbg_xfrin("Wrong parameters supported.\n"); + return KNOT_EBADARG; + } + + // check if the response is OK + if (knot_wire_get_rcode(pkt) != KNOT_RCODE_NOERROR) { + return KNOT_EXFRREFUSED; + } + + knot_packet_t *packet = NULL; +// knot_rrset_t *soa1 = NULL; +// knot_rrset_t *soa2 = NULL; + knot_rrset_t *rr = NULL; + + int ret; + + if ((ret = xfrin_parse_first_rr(&packet, pkt, size, &rr)) != KNOT_EOK) { + return ret; + } + + assert(packet != NULL); + + // state of the transfer + // -1 .. a SOA is expected to create a new changeset + int state = 0; + + if (rr == NULL) { + dbg_xfrin("No RRs in the packet.\n"); + knot_packet_free(&packet); + /*! \todo Some other action??? */ + return KNOT_EMALF; + } + + if (*chs == NULL) { + dbg_xfrin("Changesets empty, creating new.\n"); + + ret = knot_changeset_allocate(chs); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(&rr, 1, 1, 1); + knot_packet_free(&packet); + return ret; + } + + // the first RR must be a SOA + if (knot_rrset_type(rr) != KNOT_RRTYPE_SOA) { + dbg_xfrin("First RR is not a SOA RR!\n"); + knot_rrset_deep_free(&rr, 1, 1, 1); + ret = KNOT_EMALF; + goto cleanup; + } + + // just store the first SOA for later use + (*chs)->first_soa = rr; + state = -1; + + dbg_xfrin("First SOA of IXFR saved, state set to -1.\n"); + + // parse the next one + ret = knot_packet_parse_next_rr_answer(packet, &rr); + if (ret != KNOT_EOK) { + return ret; + } + + /* + * If there is no other records in the response than the SOA, it + * means one of these two cases: + * + * 1) The server does not have newer zone than ours. + * This is indicated by serial equal to the one of our zone. + * 2) The server wants to send the transfer but is unable to fit + * it in the packet. This is indicated by serial different + * (newer) from the one of our zone. + * + * The serials must be compared in other parts of the server, so + * just indicate that the answer contains only one SOA. + */ + if (rr == NULL) { + dbg_xfrin("Response containing only SOA,\n"); + knot_packet_free(&packet); + return XFRIN_RES_SOA_ONLY; + } else if (knot_rrset_type(rr) != KNOT_RRTYPE_SOA) { + knot_rrset_deep_free(&rr, 1, 1, 1); + dbg_xfrin("Fallback to AXFR.\n"); + ret = XFRIN_RES_FALLBACK; + knot_free_changesets(chs); + xfr->data = 0; + return ret; + } + } else { + if ((*chs)->first_soa == NULL) { + dbg_xfrin("Changesets don't contain frist SOA!\n"); + ret = KNOT_EBADARG; + goto cleanup; + } + dbg_xfrin("Changesets present.\n"); + } + + /* + * Process the next RR. Different requirements are in place in + * different cases: + * + * 1) Last changeset has both soa_from and soa_to. + * a) The next RR is a SOA. + * i) The next RR is equal to the first_soa saved in changesets. + * This denotes the end of the transfer. It may be dropped and + * the end should be signalised by returning positive value. + * + * ii) The next RR is some other SOA. + * This means a start of new changeset - create it and add it + * to the list. + * + * b) The next RR is not a SOA. + * Put the RR into the ADD part of the last changeset as this is + * not finished yet. Continue while SOA is not encountered. Then + * jump to 1-a. + * + * 2) Last changeset has only the soa_from and does not have soa_to. + * a) The next RR is a SOA. + * This means start of the ADD section. Put the SOA to the + * changeset. Continue adding RRs to the ADD section while SOA + * is not encountered. This is identical to 1-b. + * + * b) The next RR is not a SOA. + * This means the REMOVE part is not finished yet. Add the RR to + * the REMOVE part. Continue adding next RRs until a SOA is + * encountered. Then jump to 2-a. + */ + + // first, find out in what state we are + /*! \todo It would be more elegant to store the state in the + * changesets structure, or in some place persistent between + * calls to this function. + */ + if (state != -1) { + dbg_xfrin("State is not -1, deciding...\n"); + // there should be at least one started changeset right now + if ((*chs)->count <= 0) { + knot_rrset_deep_free(&rr, 1, 1, 1); + ret = KNOT_EMALF; + goto cleanup; + } + + // a changeset should be created only when there is a SOA + assert((*chs)->sets[(*chs)->count - 1].soa_from != NULL); + + if ((*chs)->sets[(*chs)->count - 1].soa_to == NULL) { + state = XFRIN_CHANGESET_REMOVE; + } else { + state = XFRIN_CHANGESET_ADD; + } + } + + dbg_xfrin("State before the loop: %d\n", state); + + /*! \todo This may be implemented with much less IFs! */ + + while (ret == KNOT_EOK && rr != NULL) { +dbg_xfrin_exec( + dbg_xfrin("Next loop, state: %d\n", state); + char *name = knot_dname_to_str(knot_rrset_owner(rr)); + dbg_xfrin("Actual RR: %s, type %s.\n", name, + knot_rrtype_to_string(knot_rrset_type(rr))); + free(name); +); + switch (state) { + case -1: + // a SOA is expected + // this may be either a start of a changeset or the + // last SOA (in case the transfer was empty, but that + // is quite weird in fact + if (knot_rrset_type(rr) != KNOT_RRTYPE_SOA) { + dbg_xfrin("First RR is not a SOA RR!\n"); + dbg_xfrin("RR type: %s\n", + knot_rrtype_to_string(knot_rrset_type(rr))); + ret = KNOT_EMALF; + knot_rrset_deep_free(&rr, 1, 1, 1); + goto cleanup; + } + + if (knot_rdata_soa_serial(knot_rrset_rdata(rr)) + == knot_rdata_soa_serial( + knot_rrset_rdata((*chs)->first_soa))) { + + /*! \note [TSIG] Check TSIG, we're at the end of + * transfer. + */ + ret = xfrin_check_tsig(packet, xfr, 1); + + // last SOA, discard and end + knot_rrset_deep_free(&rr, 1, 1, 1); + knot_packet_free(&packet); + + /*! \note [TSIG] If TSIG validates, consider + * transfer complete. */ + if (ret == KNOT_EOK) { + ret = XFRIN_RES_COMPLETE; + } + + return ret; + } else { + // normal SOA, start new changeset + (*chs)->count++; + if ((ret = knot_changesets_check_size(*chs)) + != KNOT_EOK) { + (*chs)->count--; + knot_rrset_deep_free(&rr, 1, 1, 1); + goto cleanup; + } + + ret = knot_changeset_add_soa( + &(*chs)->sets[(*chs)->count - 1], rr, + XFRIN_CHANGESET_REMOVE); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(&rr, 1, 1, 1); + goto cleanup; + } + + // change state to REMOVE + state = XFRIN_CHANGESET_REMOVE; + } + break; + case XFRIN_CHANGESET_REMOVE: + // if the next RR is SOA, store it and change state to + // ADD + if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) { + // we should not be here if soa_from is not set + assert((*chs)->sets[(*chs)->count - 1].soa_from + != NULL); + + ret = knot_changeset_add_soa( + &(*chs)->sets[(*chs)->count - 1], rr, + XFRIN_CHANGESET_ADD); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(&rr, 1, 1, 1); + goto cleanup; + } + + state = XFRIN_CHANGESET_ADD; + } else { + // just add the RR to the REMOVE part and + // continue + if ((ret = knot_changeset_add_new_rr( + &(*chs)->sets[(*chs)->count - 1], rr, + XFRIN_CHANGESET_REMOVE)) != KNOT_EOK) { + knot_rrset_deep_free(&rr, 1, 1, 1); + goto cleanup; + } + } + break; + case XFRIN_CHANGESET_ADD: + // if the next RR is SOA change to state -1 and do not + // parse next RR + if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) { + state = -1; + continue; + } else { + + // just add the RR to the ADD part and continue + if ((ret = knot_changeset_add_new_rr( + &(*chs)->sets[(*chs)->count - 1], rr, + XFRIN_CHANGESET_ADD)) != KNOT_EOK) { + knot_rrset_deep_free(&rr, 1, 1, 1); + goto cleanup; + } + } + break; + default: + assert(0); + } + + // parse the next RR + dbg_xfrin("Parsing next RR..\n"); + ret = knot_packet_parse_next_rr_answer(packet, &rr); + dbg_xfrin("Returned %d, %p.\n", ret, rr); + } + + /*! \note Check TSIG, we're at the end of packet. It may not be + * required. + */ + ret = xfrin_check_tsig(packet, xfr, + knot_ns_tsig_required(xfr->packet_nr)); + dbg_xfrin_detail("xfrin_check_tsig() returned %d\n", ret); + ++xfr->packet_nr; + + /*! \note [TSIG] Cleanup and propagate error if TSIG validation fails.*/ + if (ret != KNOT_EOK) { + goto cleanup; + } + + // here no RRs remain in the packet but the transfer is not finished + // yet, return EOK + knot_packet_free(&packet); + return KNOT_EOK; + +cleanup: + /* We should go here only if some error occured. */ + assert(ret < 0); + + dbg_xfrin("Cleanup after processing IXFR/IN packet.\n"); + knot_free_changesets(chs); + knot_packet_free(&packet); + xfr->data = 0; + return ret; +} + +/*----------------------------------------------------------------------------*/ +/* Applying changesets to zone */ +/*----------------------------------------------------------------------------*/ + +typedef struct { + /*! + * Deleted (without owners and RDATA) after successful update. + */ + knot_rrset_t **old_rrsets; + int old_rrsets_count; + int old_rrsets_allocated; + + /*! + * Deleted after successful update. + */ + knot_rdata_t **old_rdata; + uint *old_rdata_types; + int old_rdata_count; + int old_rdata_allocated; + + /*! + * \brief Copied RRSets (i.e. modified by the update). + * + * Deleted (without owners and RDATA) after failed update. + */ + knot_rrset_t **new_rrsets; + int new_rrsets_count; + int new_rrsets_allocated; + + /*! + * Deleted (without contents) after successful update. + */ + knot_node_t **old_nodes; + int old_nodes_count; + int old_nodes_allocated; + + /*! + * Deleted (without contents) after failed update. + */ + knot_node_t **new_nodes; + int new_nodes_count; + int new_nodes_allocated; + + ck_hash_table_item_t **old_hash_items; + int old_hash_items_count; + int old_hash_items_allocated; +} xfrin_changes_t; + +/*----------------------------------------------------------------------------*/ + +static void xfrin_changes_free(xfrin_changes_t **changes) +{ + free((*changes)->old_nodes); + free((*changes)->old_rrsets); + free((*changes)->old_rdata); + free((*changes)->old_rdata_types); + free((*changes)->new_rrsets); + free((*changes)->new_nodes); + free((*changes)->old_hash_items); +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_changes_check_rrsets(knot_rrset_t ***rrsets, + int *count, int *allocated, int to_add) +{ + /* Ensure at least requested size is allocated. */ + int new_count = (*count + to_add); + assert(new_count >= 0); + if (new_count <= *allocated) { + return KNOT_EOK; + } + + /* Allocate new memory block. */ + knot_rrset_t **rrsets_new = malloc(new_count * sizeof(knot_rrset_t *)); + if (rrsets_new == NULL) { + return KNOT_ENOMEM; + } + + /* Initialize new memory and copy old data. */ + memset(rrsets_new, 0, new_count * sizeof(knot_rrset_t *)); + memcpy(rrsets_new, *rrsets, (*allocated) * sizeof(knot_rrset_t *)); + + /* Free old nodes and switch pointers. */ + free(*rrsets); + *rrsets = rrsets_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_changes_check_nodes(knot_node_t ***nodes, + int *count, int *allocated) +{ + assert(nodes != NULL); + assert(count != NULL); + assert(allocated != 0); + + /* Ensure at least count and some reserve is allocated. */ + int new_count = *count + 2; + if (new_count <= *allocated) { + return KNOT_EOK; + } + + /* Allocate new memory block. */ + const size_t node_len = sizeof(knot_node_t *); + knot_node_t **nodes_new = malloc(new_count * node_len); + if (nodes_new == NULL) { + return KNOT_ENOMEM; + } + + /* Clear memory block and copy old data. */ + memset(nodes_new, 0, new_count * node_len); + memcpy(nodes_new, *nodes, (*allocated) * node_len); + + /* Free old nodes and switch pointers. */ + free(*nodes); + *nodes = nodes_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_changes_check_rdata(knot_rdata_t ***rdatas, uint **types, + int count, int *allocated, int to_add) +{ + /* Ensure at least requested size is allocated. */ + int new_count = (count + to_add); + assert(new_count >= 0); + if (new_count <= *allocated) { + return KNOT_EOK; + } + + /* Allocate new memory block. */ + knot_rdata_t **rdatas_new = malloc(new_count * sizeof(knot_rdata_t *)); + if (rdatas_new == NULL) { + return KNOT_ENOMEM; + } + + uint *types_new = malloc(new_count * sizeof(uint)); + if (types_new == NULL) { + return KNOT_ENOMEM; + } + + /* Initialize new memory and copy old data. */ + memset(rdatas_new, 0, new_count * sizeof(knot_rdata_t *)); + memcpy(rdatas_new, *rdatas, (*allocated) * sizeof(knot_rdata_t *)); + + memset(types_new, 0, new_count * sizeof(uint)); + memcpy(types_new, *types, (*allocated) * sizeof(uint)); + + /* Free old rdatas and switch pointers. */ + free(*rdatas); + free(*types); + *rdatas = rdatas_new; + *types = types_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_changes_check_hash_items(ck_hash_table_item_t ***items, + int *count, int *allocated) +{ + /* Prevent infinite loop in case of allocated = 0. */ + int new_count = 0; + if (*allocated == 0) { + new_count = *count + 1; + } else { + if (*count == *allocated) { + new_count = *allocated * 2; + } + } + + const size_t item_len = sizeof(ck_hash_table_item_t *); + ck_hash_table_item_t **items_new = malloc(new_count * item_len); + if (items_new == NULL) { + return KNOT_ENOMEM; + } + + memset(items_new, 0, new_count * item_len); + memcpy(items_new, *items, (*count) * item_len); + free(*items); + *items = items_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_zone_contents_free(knot_zone_contents_t **contents) +{ + /*! \todo This should be all in some API!! */ + + if ((*contents)->table != NULL) { +// ck_destroy_table(&(*contents)->table, NULL, 0); + ck_table_free(&(*contents)->table); + } + + // free the zone tree, but only the structure + // (nodes are already destroyed) + dbg_zone("Destroying zone tree.\n"); + knot_zone_tree_free(&(*contents)->nodes); + dbg_zone("Destroying NSEC3 zone tree.\n"); + knot_zone_tree_free(&(*contents)->nsec3_nodes); + + knot_nsec3_params_free(&(*contents)->nsec3_params); + + knot_dname_table_deep_free(&(*contents)->dname_table); + + free(*contents); + *contents = NULL; +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_rollback_update(knot_zone_contents_t *contents, + xfrin_changes_t *changes) +{ + /* + * This function is called only when no references were actually set to + * the new nodes, just the new nodes reference other. + * We thus do not need to fix any references, just from the old nodes + * to the new ones. + */ + + // discard new nodes, but do not remove RRSets from them + for (int i = 0; i < changes->new_nodes_count; ++i) { + knot_node_free(&changes->new_nodes[i], 0, 0); + } + + // set references from old nodes to new nodes to NULL and remove the + // old flag + for (int i = 0; i < changes->old_nodes_count; ++i) { + knot_node_set_new_node(changes->old_nodes[i], NULL); + knot_node_clear_old(changes->old_nodes[i]); + } + + // discard new RRSets + for (int i = 0; i < changes->old_rrsets_count; ++i) { + knot_rrset_deep_free(&changes->new_rrsets[i], 0, 1, 0); + } + + // destroy the shallow copy of zone + xfrin_zone_contents_free(&contents); +} + +/*----------------------------------------------------------------------------*/ + +static knot_rdata_t *xfrin_remove_rdata(knot_rrset_t *from, + const knot_rrset_t *what) +{ + knot_rdata_t *old = NULL; + knot_rdata_t *old_actual = NULL; + + const knot_rdata_t *rdata = knot_rrset_rdata(what); + + while (rdata != NULL) { + old_actual = knot_rrset_remove_rdata(from, rdata); + if (old_actual != NULL) { + old_actual->next = old; + old = old_actual; + } + rdata = knot_rrset_rdata_next(what, rdata); + } + + return old; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_get_node_copy(knot_node_t **node, xfrin_changes_t *changes) +{ + knot_node_t *new_node = + knot_node_get_new_node(*node); + if (new_node == NULL) { + dbg_xfrin("Creating copy of node.\n"); + int ret = knot_node_shallow_copy(*node, &new_node); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to create node copy.\n"); + return KNOT_ENOMEM; + } + + dbg_xfrin_detail("Created copy of old node %p to new node %p\n", + *node, new_node); + + assert(changes); + +// changes->new_nodes_allocated = 0; + + // save the copy of the node + ret = xfrin_changes_check_nodes( + &changes->new_nodes, + &changes->new_nodes_count, + &changes->new_nodes_allocated); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add new node to list.\n"); + knot_node_free(&new_node, 0, 0); + return ret; + } + +// changes->old_nodes_allocated = 0; + + // save the old node to list of old nodes + ret = xfrin_changes_check_nodes( + &changes->old_nodes, + &changes->old_nodes_count, + &changes->old_nodes_allocated); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add old node to list.\n"); + knot_node_free(&new_node, 0, 0); + return ret; + } + + assert(changes->new_nodes); + assert(changes->old_nodes); + + changes->new_nodes[changes->new_nodes_count++] = new_node; + changes->old_nodes[changes->old_nodes_count++] = *node; + + // mark the old node as old + knot_node_set_old(*node); + + knot_node_set_new(new_node); + knot_node_set_new_node(*node, new_node); + } + + *node = new_node; + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy, + xfrin_changes_t *changes) +{ + // create new RRSet by copying the old one + int ret = knot_rrset_shallow_copy(old, copy); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to create RRSet copy.\n"); + return KNOT_ENOMEM; + } + + // add the RRSet to the list of new RRSets + ret = xfrin_changes_check_rrsets(&changes->new_rrsets, + &changes->new_rrsets_count, + &changes->new_rrsets_allocated, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add new RRSet to list.\n"); + knot_rrset_free(copy); + return ret; + } + + changes->new_rrsets[changes->new_rrsets_count++] = *copy; + + // add the old RRSet to the list of old RRSets + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add old RRSet to list.\n"); + return ret; + } + + changes->old_rrsets[changes->old_rrsets_count++] = old; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_copy_rrset(knot_node_t *node, knot_rr_type_t type, + knot_rrset_t **rrset, xfrin_changes_t *changes) +{ + knot_rrset_t *old = knot_node_remove_rrset(node, type); + + if (old == NULL) { + dbg_xfrin("RRSet not found for RR to be removed.\n"); + return 1; + } + + int ret = xfrin_copy_old_rrset(old, rrset, changes); + if (ret != KNOT_EOK) { + return ret; + } + + dbg_xfrin_detail("Copied old rrset %p to new %p.\n", + old, *rrset); + + // replace the RRSet in the node copy by the new one + ret = knot_node_add_rrset(node, *rrset, 0); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add RRSet copy to node\n"); + return KNOT_ERROR; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_remove_rrsigs(xfrin_changes_t *changes, + const knot_rrset_t *remove, + knot_node_t *node, + knot_rrset_t **rrset) +{ + assert(changes != NULL); + assert(remove != NULL); + assert(node != NULL); + assert(rrset != NULL); + assert(knot_rrset_type(remove) == KNOT_RRTYPE_RRSIG); + + /*! \todo These optimalizations may be useless as there may be only + * one RRSet of each type and owner in the changeset. + */ + + int ret; + + if (!*rrset + || knot_dname_compare(knot_rrset_owner(*rrset), + knot_node_owner(node)) != 0 + || knot_rrset_type(*rrset) != knot_rdata_rrsig_type_covered( + knot_rrset_rdata(remove))) { + // find RRSet based on the Type Covered + knot_rr_type_t type = knot_rdata_rrsig_type_covered( + knot_rrset_rdata(remove)); + + // copy the rrset + ret = xfrin_copy_rrset(node, type, rrset, changes); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to copy rrset from changeset.\n"); + return ret; + } + } else { + // we should have the right RRSIG RRSet in *rrset + assert(knot_rrset_type(*rrset) + == knot_rdata_rrsig_type_covered( + knot_rrset_rdata(remove))); + // this RRSet should be the already copied RRSet so we may + // update it right away + } + + // get the old rrsigs + knot_rrset_t *old = knot_rrset_get_rrsigs(*rrset); + if (old == NULL) { + return 1; + } + + // copy the RRSIGs + /*! \todo This may be done unnecessarily more times. */ + knot_rrset_t *rrsigs; + ret = xfrin_copy_old_rrset(old, &rrsigs, changes); + if (ret != KNOT_EOK) { + return ret; + } + + // set the RRSIGs to the new RRSet copy + if (knot_rrset_set_rrsigs(*rrset, rrsigs) != KNOT_EOK) { + dbg_xfrin("Failed to set rrsigs.\n"); + return KNOT_ERROR; + } + + + + // now in '*rrset' we have a copy of the RRSet which holds the RRSIGs + // and in 'rrsigs' we have the copy of the RRSIGs + + knot_rdata_t *rdata = xfrin_remove_rdata(rrsigs, remove); + if (rdata == NULL) { + dbg_xfrin("Failed to remove RDATA from RRSet: %s.\n", + knot_strerror(ret)); + return 1; + } + + // if the RRSet is empty, remove from node and add to old RRSets + // check if there is no RRSIGs; if there are, leave the RRSet + // there; it may be eventually removed when the RRSIGs are removed + if (knot_rrset_rdata(rrsigs) == NULL) { + // remove the RRSIGs from the RRSet + knot_rrset_set_rrsigs(*rrset, NULL); + + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, + 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add empty RRSet to the " + "list of old RRSets."); + // delete the RRSet right away + knot_rrset_free(&rrsigs); + return ret; + } + + changes->old_rrsets[changes->old_rrsets_count++] = rrsigs; + + // now check if the RRSet is not totally empty + if (knot_rrset_rdata(*rrset) == NULL) { + assert(knot_rrset_rrsigs(*rrset) == NULL); + + // remove the whole RRSet from the node + knot_rrset_t *tmp = knot_node_remove_rrset(node, + knot_rrset_type(*rrset)); + assert(tmp == *rrset); + + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, + 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add empty RRSet to " + "the list of old RRSets."); + // delete the RRSet right away + knot_rrset_free(rrset); + return ret; + } + + changes->old_rrsets[changes->old_rrsets_count++] = + *rrset; + } + } + + // connect the RDATA to the list of old RDATA + ret = xfrin_changes_check_rdata(&changes->old_rdata, + &changes->old_rdata_types, + changes->old_rdata_count, + &changes->old_rdata_allocated, 1); + if (ret != KNOT_EOK) { + return ret; + } + + changes->old_rdata[changes->old_rdata_count] = rdata; + changes->old_rdata_types[changes->old_rdata_count] = + knot_rrset_type(remove); + ++changes->old_rdata_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_remove_normal(xfrin_changes_t *changes, + const knot_rrset_t *remove, + knot_node_t *node, + knot_rrset_t **rrset) +{ + assert(changes != NULL); + assert(remove != NULL); + assert(node != NULL); + assert(rrset != NULL); + + int ret; + + dbg_xfrin_detail("Removing RRSet: \n"); + knot_rrset_dump(remove, 0); + + // now we have the copy of the node, so lets get the right RRSet + // check if we do not already have it + if (!*rrset + || knot_dname_compare(knot_rrset_owner(*rrset), + knot_node_owner(node)) != 0 + || knot_rrset_type(*rrset) + != knot_rrset_type(remove)) { + /*! + * \todo This may happen also with already + * copied RRSet. In that case it would be + * an unnecesary overhead but will + * probably not cause problems. TEST!! + */ + ret = xfrin_copy_rrset(node, + knot_rrset_type(remove), rrset, changes); + dbg_xfrin_detail("Copied RRSet:\n"); + knot_rrset_dump(*rrset, 0); + if (ret != KNOT_EOK) { + return ret; + } + } + + if (*rrset == NULL) { + dbg_xfrin("RRSet not found for RR to be removed.\n"); + return 1; + } + +dbg_xfrin_exec( + char *name = knot_dname_to_str(knot_rrset_owner(*rrset)); + dbg_xfrin("Updating RRSet with owner %s, type %s\n", name, + knot_rrtype_to_string(knot_rrset_type(*rrset))); + free(name); +); + + // remove the specified RRs from the RRSet (de facto difference of + // sets) + knot_rdata_t *rdata = xfrin_remove_rdata(*rrset, remove); + if (rdata == NULL) { + dbg_xfrin("Failed to remove RDATA from RRSet: %s.\n", + knot_strerror(ret)); + return 1; + } + +dbg_xfrin_exec_detail( + dbg_xfrin_detail("Removed rdata: \n"); + knot_rdata_t *r = rdata; + if (r != NULL) { + do { + dbg_xfrin_detail("pointer: %p\n", r); + knot_rdata_dump(r, knot_rrset_type(remove), 0); + r = r->next; + } while (r != NULL && r != rdata); + } +); + + // if the RRSet is empty, remove from node and add to old RRSets + // check if there is no RRSIGs; if there are, leave the RRSet + // there; it may be eventually removed when the RRSIGs are removed + if (knot_rrset_rdata(*rrset) == NULL + && knot_rrset_rrsigs(*rrset) == NULL) { + + knot_rrset_t *tmp = knot_node_remove_rrset(node, + knot_rrset_type(*rrset)); + dbg_xfrin_detail("Removed whole RRSet (%p).\n", tmp); + + // add the removed RRSet to list of old RRSets + + assert(tmp == *rrset); + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, + 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add empty RRSet to the " + "list of old RRSets."); + // delete the RRSet right away + knot_rrset_free(rrset); + return ret; + } + + changes->old_rrsets[changes->old_rrsets_count++] = *rrset; + } + + // connect the RDATA to the list of old RDATA + ret = xfrin_changes_check_rdata(&changes->old_rdata, + &changes->old_rdata_types, + changes->old_rdata_count, + &changes->old_rdata_allocated, 1); + if (ret != KNOT_EOK) { + return ret; + } + + changes->old_rdata[changes->old_rdata_count] = rdata; + changes->old_rdata_types[changes->old_rdata_count] = + knot_rrset_type(remove); + ++changes->old_rdata_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_remove_all_rrsets(xfrin_changes_t *changes, + knot_node_t *node, uint16_t type) +{ + /*! \todo Implement. */ + int ret; + + if (type == KNOT_RRTYPE_ANY) { + // put all the RRSets to the changes structure + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, + knot_node_rrset_count(node)); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to check changeset rrsets.\n"); + return ret; + } + + knot_rrset_t **rrsets = knot_node_get_rrsets(node); + knot_rrset_t **place = changes->old_rrsets + + changes->old_rrsets_count; + /*! \todo Test this!!! */ + memcpy(place, rrsets, knot_node_rrset_count(node) * sizeof(knot_rrset_t *)); + + // remove all RRSets from the node + knot_node_remove_all_rrsets(node); + } else { + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, + 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to check changeset rrsets.\n"); + return ret; + } + // remove only RRSet with the given type + knot_rrset_t *rrset = knot_node_remove_rrset(node, type); + changes->old_rrsets[changes->old_rrsets_count++] = rrset; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_remove(knot_zone_contents_t *contents, + knot_changeset_t *chset, + xfrin_changes_t *changes) +{ + /* + * Iterate over removed RRSets, copy appropriate nodes and remove + * the rrsets from them. By default, the RRSet should be copied so that + * RDATA may be removed from it. + */ + int ret = 0; + knot_node_t *node = NULL; + knot_rrset_t *rrset = NULL; + + for (int i = 0; i < chset->remove_count; ++i) { + // check if the old node is not the one we should use + if (!node || knot_rrset_owner(chset->remove[i]) + != knot_node_owner(node)) { + node = knot_zone_contents_get_node(contents, + knot_rrset_owner(chset->remove[i])); + if (node == NULL) { + dbg_xfrin("Node not found for RR to be removed" + "!\n"); + continue; + } + } + + // create a copy of the node if not already created + if (!knot_node_is_new(node)) { + ret = xfrin_get_node_copy(&node, changes); + if (ret != KNOT_EOK) { + return ret; + } + } + + assert(node != NULL); + assert(knot_node_is_new(node)); + + // first check if all RRSets should be removed + if (knot_rrset_class(chset->remove[i]) == KNOT_CLASS_ANY) { + ret = xfrin_apply_remove_all_rrsets( + changes, node, + knot_rrset_type(chset->remove[i])); + } else if (knot_rrset_type(chset->remove[i]) + == KNOT_RRTYPE_RRSIG) { + // this should work also for UPDATE + ret = xfrin_apply_remove_rrsigs(changes, + chset->remove[i], + node, &rrset); + } else { + // this should work also for UPDATE + ret = xfrin_apply_remove_normal(changes, + chset->remove[i], + node, &rrset); + } + + dbg_xfrin("xfrin_apply_remove() ret = %d\n", ret); + + if (ret > 0) { + continue; + } else if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static knot_node_t *xfrin_add_new_node(knot_zone_contents_t *contents, + knot_rrset_t *rrset) +{ + /*! \todo Why is the function disabled? */ + //return NULL; + + knot_node_t *node = knot_node_new(knot_rrset_get_owner(rrset), + NULL, KNOT_NODE_FLAGS_NEW); + if (node == NULL) { + dbg_xfrin("Failed to create a new node.\n"); + return NULL; + } + + int ret = 0; + + // insert the node into zone structures and create parents if + // necessary +// dbg_xfrin("Adding new node to zone. From owner: %s type %s\n", +// knot_dname_to_str(node->owner), +// knot_rrtype_to_string(rrset->type)); +// getchar(); + if (knot_rrset_type(rrset) == KNOT_RRTYPE_NSEC3) { + ret = knot_zone_contents_add_nsec3_node(contents, node, 1, 0, + 1); + } else { + ret = knot_zone_contents_add_node(contents, node, 1, + KNOT_NODE_FLAGS_NEW, 1); + } + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add new node to zone contents.\n"); + return NULL; + } + + // find previous node and connect the new one to it + knot_node_t *prev = NULL; + if (knot_rrset_type(rrset) == KNOT_RRTYPE_NSEC3) { + prev = knot_zone_contents_get_previous_nsec3(contents, + knot_rrset_owner(rrset)); + } else { + prev = knot_zone_contents_get_previous(contents, + knot_rrset_owner(rrset)); + } + + // fix prev and next pointers + if (prev != NULL) { + knot_node_set_previous(node, prev); + } + +// printf("contents owned by: %s (%p)\n", +// knot_dname_to_str(contents->apex->owner), +// contents); + assert(contents->zone != NULL); + knot_node_set_zone(node, contents->zone); + + return node; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_add_normal(xfrin_changes_t *changes, + knot_rrset_t *add, + knot_node_t *node, + knot_rrset_t **rrset) +{ + assert(changes != NULL); + assert(add != NULL); + assert(node != NULL); + assert(rrset != NULL); + + int ret; + + dbg_xfrin("applying rrset:\n"); + knot_rrset_dump(add, 0); +// getchar(); + + if (!*rrset + || knot_dname_compare(knot_rrset_owner(*rrset), + knot_node_owner(node)) != 0 + || knot_rrset_type(*rrset) + != knot_rrset_type(add)) { + dbg_xfrin("Removing rrset!\n"); + *rrset = knot_node_remove_rrset(node, knot_rrset_type(add)); + } + + dbg_xfrin("Removed RRSet: \n"); + knot_rrset_dump(*rrset, 1); + + if (*rrset == NULL) { +dbg_xfrin_exec_verb( + char *name = knot_dname_to_str(add->owner); + dbg_xfrin_verb("RRSet to be added not found in zone.\n"); + dbg_xfrin_verb("owner: %s type: %s\n", name, + knot_rrtype_to_string(add->type)); + free(name); +); +// getchar(); + // add the RRSet from the changeset to the node + /*! \todo What about domain names?? Shouldn't we use the + * zone-contents' version of this function?? + */ + ret = knot_node_add_rrset(node, add, 0); +// ret = knot_zone_contents_add_rrset(node->zone->contents, +// rrset, node, +// KNOT_RRSET_DUPL_MERGE, +// 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add RRSet to node.\n"); + return KNOT_ERROR; + } + return 1; // return 1 to indicate the add RRSet was used + } + + knot_rrset_t *old = *rrset; + +dbg_xfrin_exec( + char *name = knot_dname_to_str(knot_rrset_owner(*rrset)); + dbg_xfrin("Found RRSet with owner %s, type %s\n", name, + knot_rrtype_to_string(knot_rrset_type(*rrset))); + free(name); +); + knot_rrset_dump(*rrset, 1); + ret = xfrin_copy_old_rrset(old, rrset, changes); + if (ret != KNOT_EOK) { + assert(0); + return ret; + } + +// dbg_xfrin("After copy: Found RRSet with owner %s, type %s\n", +// knot_dname_to_str((*rrset)->owner), +// knot_rrtype_to_string(knot_rrset_type(*rrset))); + + // merge the changeset RRSet to the copy + /* What if the update fails? + * The changesets will be destroyed - that will destroy 'add', + * and the copied RRSet will be destroyed because it is in the new + * rrsets list. + * + * If the update is successfull, the old RRSet will be destroyed, + * but the one from the changeset will be not!! + * + * TODO: add the 'add' rrset to list of old RRSets? + */ + dbg_xfrin("Merging RRSets with owners: %s %s types: %d %d\n", + (*rrset)->owner->name, add->owner->name, (*rrset)->type, + add->type); + dbg_xfrin_detail("RDATA in RRSet1: %p, RDATA in RRSet2: %p\n", + (*rrset)->rdata, add->rdata); + ret = knot_rrset_merge((void **)rrset, (void **)&add); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to merge changeset RRSet to copy.\n"); + return KNOT_ERROR; + } + dbg_xfrin("Merge returned: %d\n", ret); + knot_rrset_dump(*rrset, 1); + ret = knot_node_add_rrset(node, *rrset, 0); + + // return 2 so that the add RRSet is removed from + // the changeset (and thus not deleted) + // and put to list of new RRSets (is this ok?) + // and deleted + return 2; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_add_rrsig(xfrin_changes_t *changes, + knot_rrset_t *add, + knot_node_t *node, + knot_rrset_t **rrset) +{ + assert(changes != NULL); + assert(add != NULL); + assert(node != NULL); + assert(rrset != NULL); + assert(knot_rrset_type(add) == KNOT_RRTYPE_RRSIG); + + int ret; + + knot_rr_type_t type = knot_rdata_rrsig_type_covered( + knot_rrset_rdata(add)); + + if (!*rrset + || knot_dname_compare(knot_rrset_owner(*rrset), + knot_node_owner(node)) != 0 + || knot_rrset_type(*rrset) != knot_rdata_rrsig_type_covered( + knot_rrset_rdata(add))) { + // copy the rrset + ret = xfrin_copy_rrset(node, type, rrset, changes); + if (ret < 0) { + return ret; + } + } else { + // we should have the right RRSIG RRSet in *rrset + assert(knot_rrset_type(*rrset) == type); + // this RRSet should be the already copied RRSet so we may + // update it right away + } + + if (*rrset == NULL) { + dbg_xfrin("RRSet to be added not found in zone.\n"); + + // create a new RRSet to add the RRSIGs into + *rrset = knot_rrset_new(knot_node_get_owner(node), type, + knot_rrset_class(add), + knot_rrset_ttl(add)); + if (*rrset == NULL) { + dbg_xfrin("Failed to create new RRSet for RRSIGs.\n"); + return KNOT_ENOMEM; + } + + // add the RRSet from the changeset to the node + ret = knot_node_add_rrset(node, *rrset, 0); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add RRSet to node.\n"); + return KNOT_ERROR; + } + } + +dbg_xfrin_exec( + char *name = knot_dname_to_str(knot_rrset_owner(*rrset)); + dbg_xfrin("Found RRSet with owner %s, type %s\n", name, + knot_rrtype_to_string(knot_rrset_type(*rrset))); + free(name); +); + + if (knot_rrset_rrsigs(*rrset) == NULL) { + ret = knot_rrset_set_rrsigs(*rrset, add); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add RRSIGs to the RRSet.\n"); + return KNOT_ERROR; + } + + return 1; + } else { + knot_rrset_t *old = knot_rrset_get_rrsigs(*rrset); + assert(old != NULL); + knot_rrset_t *rrsig; + + ret = xfrin_copy_old_rrset(old, &rrsig, changes); + if (ret != KNOT_EOK) { + return ret; + } + + // replace the old RRSIGs with the new ones + knot_rrset_set_rrsigs(*rrset, rrsig); + + // merge the changeset RRSet to the copy + /*! \todo What if the update fails? + * + */ + ret = knot_rrset_merge((void **)&rrsig, (void **)&add); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to merge changeset RRSet to copy.\n"); + return KNOT_ERROR; + } + + return 2; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_add(knot_zone_contents_t *contents, + knot_changeset_t *chset, + xfrin_changes_t *changes) +{ + // iterate over removed RRSets, copy appropriate nodes and remove + // the rrsets from them + int ret = 0; + knot_node_t *node = NULL; + knot_rrset_t *rrset = NULL; + + for (int i = 0; i < chset->add_count; ++i) { + dbg_xfrin_detail("Adding RRSet:\n"); + knot_rrset_dump(chset->add[i], 0); + // check if the old node is not the one we should use + if (!node || knot_rrset_owner(chset->add[i]) + != knot_node_owner(node)) { + node = knot_zone_contents_get_node(contents, + knot_rrset_owner(chset->add[i])); + if (node == NULL) { + // create new node, connect it properly to the + // zone nodes + dbg_xfrin("Creating new node from.\n"); + node = xfrin_add_new_node(contents, + chset->add[i]); + if (node == NULL) { + dbg_xfrin("Failed to create new node " + "in zone.\n"); + return KNOT_ERROR; + } +// continue; // continue with another RRSet + } + } + + // create a copy of the node if not already created + if (!knot_node_is_new(node)) { + xfrin_get_node_copy(&node, changes); + } + + assert(node != NULL); + assert(knot_node_is_new(node)); + + if (knot_rrset_type(chset->add[i]) == KNOT_RRTYPE_RRSIG) { + ret = xfrin_apply_add_rrsig(changes, chset->add[i], + node, &rrset); + } else { + ret = xfrin_apply_add_normal(changes, chset->add[i], + node, &rrset); + } + + dbg_xfrin("xfrin_apply_..() returned %d, rrset: %p\n", ret, + rrset); + + if (ret == 1) { + // the ADD RRSet was used, i.e. it should be removed + // from the changeset and saved in the list of new + // RRSets + ret = xfrin_changes_check_rrsets( + &changes->new_rrsets, + &changes->new_rrsets_count, + &changes->new_rrsets_allocated, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add old RRSet to list.\n"); + return ret; + } + + changes->new_rrsets[changes->new_rrsets_count++] = + chset->add[i]; + + chset->add[i] = NULL; + } else if (ret == 2) { + // the copy of the RRSet was used, but it was already + // stored in the new RRSets list + // just delete the add RRSet, but without RDATA + // as these were merged to the copied RRSet + knot_rrset_free(&chset->add[i]); + } else if (ret != KNOT_EOK) { + + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \todo This must be tested!! Simulate failure somehow. + */ +static void xfrin_clean_changes_after_fail(xfrin_changes_t *changes) +{ + /* 1) Delete copies of RRSets created because they were updated. + * Do not delete their RDATA or owners. + */ + for (int i = 0; i < changes->new_rrsets_count; ++i) { + knot_rrset_free(&changes->new_rrsets[i]); + } + + /* 2) Delete copies of nodes created because they were updated. + * Do not delete their RRSets. + */ + for (int i = 0; i < changes->new_nodes_count; ++i) { + knot_node_free(&changes->new_nodes[i], 0, 1); + } + + // changesets will be deleted elsewhere + // so just delete the changes structure + xfrin_changes_free(&changes); +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_replace_soa(knot_zone_contents_t *contents, + xfrin_changes_t *changes, + knot_changeset_t *chset) +{ + knot_node_t *node = knot_zone_contents_get_apex(contents); + assert(node != NULL); + + int ret = 0; + + // create a copy of the node if not already created + if (!knot_node_is_new(node)) { + ret = xfrin_get_node_copy(&node, changes); + if (ret != KNOT_EOK) { + return ret; + } + } + + assert(knot_node_is_new(node)); + + // set the node copy as the apex of the contents + contents->apex = node; + + // remove the SOA RRSet from the apex + knot_rrset_t *rrset = knot_node_remove_rrset(node, KNOT_RRTYPE_SOA); + assert(rrset != NULL); + + // add the old RRSet to the list of old RRSets + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add old RRSet to list.\n"); + return ret; + } + + // save also the SOA RDATA, because RDATA are not deleted with the + // RRSet + ret = xfrin_changes_check_rdata(&changes->old_rdata, + &changes->old_rdata_types, + changes->old_rdata_count, + &changes->old_rdata_allocated, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add old RDATA to list.\n"); + return ret; + } + + // save the SOA to the new RRSet, so that it is deleted if the + // apply fails + ret = xfrin_changes_check_rrsets(&changes->new_rrsets, + &changes->new_rrsets_count, + &changes->new_rrsets_allocated, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add old RRSet to list.\n"); + return ret; + } + + changes->old_rrsets[changes->old_rrsets_count++] = rrset; + + /*! \todo Maybe check if the SOA does not have more RDATA? */ + changes->old_rdata[changes->old_rdata_count] = rrset->rdata; + changes->old_rdata_types[changes->old_rdata_count] = KNOT_RRTYPE_SOA; + ++changes->old_rdata_count; + + // insert the new SOA RRSet to the node + dbg_xfrin_verb("Adding SOA.\n"); + ret = knot_node_add_rrset(node, chset->soa_to, 0); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add RRSet to node.\n"); + return KNOT_ERROR; + } + + changes->new_rrsets[changes->new_rrsets_count++] = chset->soa_to; + + // remove the SOA from the changeset, so it will not be deleted after + // successful apply + chset->soa_to = NULL; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_changeset(knot_zone_contents_t *contents, + xfrin_changes_t *changes, + knot_changeset_t *chset) +{ + /* + * Applies one changeset to the zone. Checks if the changeset may be + * applied (i.e. the origin SOA (soa_from) has the same serial as + * SOA in the zone apex. + */ + + // check if serial matches + /*! \todo Only if SOA is present? */ + const knot_rrset_t *soa = knot_node_rrset(contents->apex, + KNOT_RRTYPE_SOA); + if (soa == NULL || knot_rdata_soa_serial(knot_rrset_rdata(soa)) + != chset->serial_from) { + dbg_xfrin("SOA serials do not match!!\n"); + return KNOT_ERROR; + } + + int ret = xfrin_apply_remove(contents, chset, changes); + if (ret != KNOT_EOK) { + xfrin_clean_changes_after_fail(changes); + return ret; + } + + ret = xfrin_apply_add(contents, chset, changes); + if (ret != KNOT_EOK) { + xfrin_clean_changes_after_fail(changes); + return ret; + } + + /*! \todo Only if SOA is present? */ + return xfrin_apply_replace_soa(contents, changes, chset); +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_check_node_in_tree(knot_zone_tree_node_t *tnode, void *data) +{ + assert(tnode != NULL); + assert(data != NULL); + assert(tnode->node != NULL); + + xfrin_changes_t *changes = (xfrin_changes_t *)data; + + knot_node_t *node = knot_node_get_new_node(tnode->node); + + if (node == NULL) { + // no RRSets were removed from this node, thus it cannot be + // empty + return; + } + + dbg_xfrin("xfrin_check_node_in_tree: children of old node: %u, " + "children of new node: %u.\n", + knot_node_children(node), + knot_node_children(tnode->node)); + + + // check if the node is empty and has no children + // to be sure, check also the count of children of the old node + if (knot_node_rrset_count(node) == 0 + && knot_node_children(node) == 0 + && knot_node_children(tnode->node) == 0) { + // in this case the new node copy should be removed + // but it cannot be deleted because if a rollback happens, + // the node must be in the new nodes list + // just add it to the old nodes list so that it is deleted + // after successful update + + // set the new node of the old node to NULL + knot_node_set_new_node(tnode->node, NULL); + + // if the parent has a new copy, decrease the number of + // children of that copy + if (knot_node_new_node(knot_node_parent(node, 0))) { + /*! \todo Replace by some API. */ + --node->parent->new_node->children; + } + + // put the new node to te list of old nodes + if (xfrin_changes_check_nodes(&changes->old_nodes, + &changes->old_nodes_count, + &changes->old_nodes_allocated) + != KNOT_EOK) { + /*! \todo Notify about the error!!! */ + return; + } + + changes->old_nodes[changes->old_nodes_count++] = node; + + // leave the old node in the old node list, we will delete + // it later + } +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_finalize_remove_nodes(knot_zone_contents_t *contents, + xfrin_changes_t *changes) +{ + assert(contents != NULL); + assert(changes != NULL); + + knot_node_t *node; + knot_zone_tree_node_t *removed; + ck_hash_table_item_t *rem_hash; + int ret; + + for (int i = 0; i < changes->old_nodes_count; ++i) { + node = changes->old_nodes[i]; + + // if the node is marked as old and has no new node copy + // remove it from the zone structure but do not delete it + // that may be done only after the grace period + if (knot_node_is_old(node) + && knot_node_new_node(node) == NULL) { + + if (knot_node_rrset(node, KNOT_RRTYPE_NSEC3) + != NULL) { + ret = knot_zone_contents_remove_nsec3_node( + contents, node, &removed); + } else { + ret = knot_zone_contents_remove_node( + contents, node, &removed, &rem_hash); + } + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to remove node from zone" + "!\n"); + return KNOT_ENONODE; + } + + assert(removed != NULL); + assert(removed->node == node); + // delete the tree node (not needed) + free(removed); + + if (rem_hash != NULL) { + // save the removed hash table item + ret = xfrin_changes_check_hash_items( + &changes->old_hash_items, + &changes->old_hash_items_count, + &changes->old_hash_items_allocated); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to save the hash" + " table item to list of " + "old items.\n"); + return ret; + } + changes->old_hash_items[ + changes->old_hash_items_count++] + = rem_hash; + } + } + } + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_finalize_contents(knot_zone_contents_t *contents, + xfrin_changes_t *changes) +{ + // don't know what should have been done here, except for one thing: + // walk through the zone and remove empty nodes (save them in the + // old nodes list). But only those having no children!!! + + /* + * Walk through the zone and remove empty nodes. + * We must walk backwards, so that children are processed before + * their parents. This will allow to remove chain of parent-children + * nodes. + * We cannot remove the nodes right away as it would modify the very + * structure used for walking through the zone. Just put the nodes + * to the list of old nodes to be removed. + * We must also decrease the node's parent's children count now + * and not when deleting the node, so that the chain of parent-child + * nodes may be removed. + */ + knot_zone_tree_t *t = knot_zone_contents_get_nodes(contents); + assert(t != NULL); + + // walk through the zone and select nodes to be removed + knot_zone_tree_reverse_apply_postorder(t, xfrin_check_node_in_tree, + (void *)changes); + + // Do the same with NSEC3 nodes. + t = knot_zone_contents_get_nsec3_nodes(contents); + assert(t != NULL); + + knot_zone_tree_reverse_apply_postorder(t, xfrin_check_node_in_tree, + (void *)changes); + + // remove the nodes one by one + return xfrin_finalize_remove_nodes(contents, changes); +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_fix_refs_in_node(knot_zone_tree_node_t *tnode, void *data) +{ + /*! \todo Passed data is always seto to NULL. */ + assert(tnode != NULL); + //assert(data != NULL); + + //xfrin_changes_t *changes = (xfrin_changes_t *)data; + + // 1) Fix the reference to the node to the new one if there is some + knot_node_t *node = tnode->node; + + knot_node_t *new_node = knot_node_get_new_node(node); + if (new_node != NULL) { + //assert(knot_node_rrset_count(new_node) > 0); + node = new_node; + tnode->node = new_node; + } + + // 2) fix references from the node remaining in the zone + knot_node_update_refs(node); +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_fix_gen_in_node(knot_zone_tree_node_t *tnode, void *data) +{ + /*! \todo Passed data is always seto to NULL. */ + assert(tnode != NULL); + + knot_node_t *node = tnode->node; + + knot_node_set_old(node); +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_fix_hash_refs(ck_hash_table_item_t *item, void *data) +{ + if (item == NULL) { + return; + } + + knot_node_t *new_node = knot_node_get_new_node( + (knot_node_t *)item->value); + if (new_node != NULL) { + assert(item->key_length + == knot_dname_size(knot_node_owner(new_node))); + assert(strncmp(item->key, (const char *)knot_dname_name( + knot_node_owner(new_node)), item->key_length) == 0); + item->value = (void *)new_node; + item->key = (const char *)knot_dname_name( + knot_node_owner(new_node)); + } +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_fix_dname_refs(knot_dname_t *dname, void *data) +{ + UNUSED(data); + knot_dname_update_node(dname); +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_fix_references(knot_zone_contents_t *contents) +{ + /*! \todo This function must not fail!! */ + + /* + * Now the contents are already switched, and we should update all + * references not updated yet, so that the old contents may be removed. + * + * Walk through the zone tree, so that each node will be checked + * and updated. + */ + // fix references in normal nodes + knot_zone_tree_t *tree = knot_zone_contents_get_nodes(contents); + knot_zone_tree_forward_apply_inorder(tree, xfrin_fix_refs_in_node, + NULL); + + // fix refereces in NSEC3 nodes + tree = knot_zone_contents_get_nsec3_nodes(contents); + knot_zone_tree_forward_apply_inorder(tree, xfrin_fix_refs_in_node, + NULL); + + // fix references in hash table + ck_hash_table_t *table = knot_zone_contents_get_hash_table(contents); + ck_apply(table, xfrin_fix_hash_refs, NULL); + + // fix references dname table + int ret = knot_zone_contents_dname_table_apply(contents, + xfrin_fix_dname_refs, NULL); + assert(ret == KNOT_EOK); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_fix_generation(knot_zone_contents_t *contents) +{ + assert(contents != NULL); + + knot_zone_tree_t *tree = knot_zone_contents_get_nodes(contents); + knot_zone_tree_forward_apply_inorder(tree, xfrin_fix_gen_in_node, + NULL); + + tree = knot_zone_contents_get_nsec3_nodes(contents); + knot_zone_tree_forward_apply_inorder(tree, xfrin_fix_gen_in_node, + NULL); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_cleanup_update(xfrin_changes_t *changes) +{ + // free old nodes but do not destroy their RRSets + // remove owners also, because of reference counting + for (int i = 0; i < changes->old_nodes_count; ++i) { + dbg_xfrin_detail("Deleting old node: %p\n", changes->old_nodes[i]); + knot_node_dump(changes->old_nodes[i], 0); + knot_node_free(&changes->old_nodes[i], 1, 0); + } + + // free old RRSets, and destroy also domain names in them + // because of reference counting + + // check if there are not some duplicate RRSets +// for (int i = 0; i < changes->old_rrsets_count; ++i) { +// for (int j = i + 1; j < changes->old_rrsets_count; ++j) { +// if (changes->old_rrsets[i] == changes->old_rrsets[j]) { +// assert(0); +// } +// if (changes->old_rrsets[i]->rdata != NULL +// && changes->old_rrsets[i]->rdata +// == changes->old_rrsets[j]->rdata) { +// assert(0); +// } +// } +// } + + for (int i = 0; i < changes->old_rrsets_count; ++i) { +// knot_rrset_deep_free(&changes->old_rrsets[i], 1, 1, 1); + dbg_xfrin_detail("Deleting old RRSet: %p\n", changes->old_rrsets[i]); + knot_rrset_dump(changes->old_rrsets[i], 0); + knot_rrset_free(&changes->old_rrsets[i]); + } + + // delete old RDATA + for (int i = 0; i < changes->old_rdata_count; ++i) { + dbg_xfrin_detail("Deleting old RDATA: %p, type: %s\n", + changes->old_rdata[i], + knot_rrtype_to_string(changes->old_rdata_types[i])); + knot_rdata_dump(changes->old_rdata[i], changes->old_rdata_types[i], 0); + knot_rdata_t *rdata = changes->old_rdata[i]; + assert(rdata != NULL); + do { + knot_rdata_t *tmp = rdata->next; + knot_rdata_deep_free(&rdata, + changes->old_rdata_types[i], 1); + rdata = tmp; + } while (rdata != NULL && rdata != changes->old_rdata[i]); + changes->old_rdata[i] = NULL; + } + + // free old hash table items, but do not touch their contents + for (int i = 0; i < changes->old_hash_items_count; ++i) { + free(changes->old_hash_items[i]); + } + free(changes->old_hash_items); + + // free allocated arrays of nodes and rrsets + free(changes->new_nodes); + free(changes->old_nodes); + free(changes->new_rrsets); + free(changes->old_rrsets); + free(changes->old_rdata); + free(changes->old_rdata_types); +} + +/*----------------------------------------------------------------------------*/ + +int xfrin_apply_changesets_to_zone(knot_zone_t *zone, + knot_changesets_t *chsets) +{ + if (zone == NULL || chsets == NULL || chsets->count == 0) { + return KNOT_EBADARG; + } + + knot_zone_contents_t *old_contents = knot_zone_get_contents(zone); + if (!old_contents) { + return KNOT_EBADARG; + } + +// dbg_xfrin("\nOLD ZONE CONTENTS:\n\n"); +// knot_zone_contents_dump(old_contents, 1); + + /* + * Ensure that the zone generation is set to 0. + */ + if (!knot_zone_contents_gen_is_old(old_contents)) { + // this would mean that a previous update was not completed + // abort + dbg_zone("Trying to apply changesets to zone that is " + "being updated. Aborting.\n"); + return KNOT_EAGAIN; + } + + /* + * Create a shallow copy of the zone, so that the structures may be + * updated. + * + * This will create new zone contents structures (normal nodes' tree, + * NSEC3 tree, hash table, domain name table), but fill them with the + * data from the old contents. + */ + knot_zone_contents_t *contents_copy = NULL; + + int ret = knot_zone_contents_shallow_copy(old_contents, + &contents_copy); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to create shallow copy of zone: %s\n", + knot_strerror(ret)); + return ret; + } + + /* + * Now, apply one changeset after another until all are applied. + * Changesets may be either from IXFR or from a dynamic update. + * Dynamic updates use special TYPE and CLASS values to distinguish + * requests, such as "remove all RRSets from a node", "remove all RRs + * with the specified type from a node", etc. + * + * When updating anything within some node (removing RR, adding RR), + * the node structure is copied, but the RRSets within are not. + * + * 1) When removing RRs from node, The affected RRSet is copied. This + * it also a 'shallow copy', i.e. the RDATA remain the exact same. + * The specified RRs (i.e. RDATA) are then removed from the copied + * RRSet. + * 2) When adding RRs to node, there are two cases: + * a) If there is a RRSet that should contain these RRs + * this RRSet is copied (shallow copy) and the RRs are added to + * it (rrset_merge()). + * b) If there is not such a RRSet, the whole RRSet from the + * changeset is added to the new node (thus this RRSet must not + * be deleted afterwards). + * + * A special case are RRSIG RRs. These functions assume that they + * are grouped together in knot_rrset_t structures according to + * their header (owner, type, class) AND their 'type covered', i.e. + * there may be more RRSIG RRSets in one changeset (while there + * should not be more RRSets of any other type). + * 3) When removing RRSIG RRs from node, the appropriate RRSet holding + * them must be found (according to the 'type covered' field). This + * RRSet is then copied (shallow copy), Its RRSIGs are also copied + * and the RRSIG RRs are added to the RRSIG copy. + * 4) When adding RRSIG RRs to node, the same process is done - the + * proper RRSet holding them is found, copied, its RRSIGs are + * copied (if there are some) and the RRs are added to the copy. + * + * When a node is copied, reference to the copy is stored within the + * old node (node_t.old_node). This is important, because when the + * zone contents are switched to the new ones, references from old nodes + * that should point to new nodes are not yet set (it would influence + * replying from the old zone contents). While all these references + * (such as node_t.prev, node_t.next, node_t.parent, etc.) are properly + * modified, the search functions use old or new nodes accordingly + * (old nodes while old contents are used, new nodes when new contents + * are used). The 'check_version' parameter turns on this behaviour in + * search functions. + * + * In case of error, we must remove all data created by the update, i.e. + * - new nodes, + * - new RRSets, + * and remove the references to the new nodes from old nodes. + * + * In case of success, the RRSet structures from the changeset + * structures must not be deleted, as they are either already used by + * the server (stored within the new zone contents) or deleted when + * cleaning up the temporary 'changes' structure. + */ + xfrin_changes_t changes; + memset(&changes, 0, sizeof(xfrin_changes_t)); + + for (int i = 0; i < chsets->count; ++i) { + if ((ret = xfrin_apply_changeset(contents_copy, &changes, + &chsets->sets[i])) != KNOT_EOK) { + xfrin_rollback_update(contents_copy, &changes); + dbg_xfrin("Failed to apply changesets to zone: " + "%s\n", knot_strerror(ret)); + return ret; + } + } + + /* + * When all changesets are applied, set generation 1 to the copy of + * the zone so that new nodes are used instead of old ones. + */ +// knot_zone_contents_switch_generation(contents_copy); + //contents_copy->generation = 1; + knot_zone_contents_set_gen_new(contents_copy); + + /* + * Finalize the zone contents. + */ + ret = xfrin_finalize_contents(contents_copy, &changes); + if (ret != KNOT_EOK) { + xfrin_rollback_update(contents_copy, &changes); + dbg_xfrin("Failed to finalize new zone contents: %s\n", + knot_strerror(ret)); + return ret; + } + + /* + * Switch the zone contents + */ + knot_zone_contents_t *old = + knot_zone_switch_contents(zone, contents_copy); + assert(old == old_contents); + + /* + * From now on, the new contents of the zone are being used. + * References to nodes may be updated in the meantime. However, we must + * traverse the zone and fix all references that were not. + */ + /*! \todo This operation must not fail!!! .*/ + ret = xfrin_fix_references(contents_copy); + assert(ret == KNOT_EOK); + + // set generation to finished + knot_zone_contents_set_gen_new_finished(contents_copy); + + // set generation of all nodes to the old one + // now it is safe (no old nodes should be referenced) + ret = xfrin_fix_generation(contents_copy); + assert(ret == KNOT_EOK); + + /* + * Now we may also set the generation back to 0 so that another + * update is possible. + */ + knot_zone_contents_set_gen_old(contents_copy); + + /* + * Wait until all readers finish reading + */ + synchronize_rcu(); + + /* + * Delete all old and unused data. + */ + xfrin_zone_contents_free(&old_contents); + xfrin_cleanup_update(&changes); + + return KNOT_EOK; +} diff --git a/src/libknot/updates/xfr-in.h b/src/libknot/updates/xfr-in.h new file mode 100644 index 0000000..8a7c64b --- /dev/null +++ b/src/libknot/updates/xfr-in.h @@ -0,0 +1,184 @@ +/*! + * \file xfr-in.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief XFR client API. + * + * \addtogroup query_processing + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_XFR_IN_H_ +#define _KNOT_XFR_IN_H_ + +#include <stdint.h> +#include <string.h> + +#include "dname.h" +#include "zone/zone.h" +#include "packet/packet.h" +#include "nameserver/name-server.h" +#include "updates/changesets.h" + +/*----------------------------------------------------------------------------*/ + +typedef struct xfrin_orphan_rrsig { + knot_rrset_t *rrsig; + struct xfrin_orphan_rrsig *next; +} xfrin_orphan_rrsig_t; + +typedef struct xfrin_constructed_zone { + knot_zone_contents_t *contents; + xfrin_orphan_rrsig_t *rrsigs; +} xfrin_constructed_zone_t; + +typedef enum xfrin_transfer_result { + XFRIN_RES_COMPLETE = 1, + XFRIN_RES_SOA_ONLY = 2, + XFRIN_RES_FALLBACK = 3 +} xfrin_transfer_result_t; + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Creates normal query for the given zone name and the SOA type. + * + * \param owner Zone owner. + * \param buffer Buffer to fill the message in. + * \param size In: available space in the buffer. Out: actual size of the + * message in bytes. + * + * \retval KNOT_EOK + * \retval KNOT_ESPACE + * \retval KNOT_ERROR + */ +int xfrin_create_soa_query(knot_dname_t *owner, knot_ns_xfr_t *xfr, + size_t *size); + +/*! + * \brief Checks if a zone transfer is required by comparing the zone's SOA with + * the one received from master server. + * + * \param zone Zone to check. + * \param soa_response Response to SOA query received from master server. + * + * \retval < 0 if an error occured. + * \retval 1 if the transfer is needed. + * \retval 0 if the transfer is not needed. + */ +int xfrin_transfer_needed(const knot_zone_contents_t *zone, + knot_packet_t *soa_response); + +/*! + * \brief Creates normal query for the given zone name and the AXFR type. + * + * \param owner Zone owner. + * \param xfr Data structure holding important data for the query, namely + * pointer to the buffer for wireformat and TSIG data. + * \param size In: available space in the buffer. Out: actual size of the + * message in bytes. + * \param use_tsig If TSIG should be used. + * + * \todo Parameter use_tsig probably not needed. + * + * \retval KNOT_EOK + * \retval KNOT_ESPACE + * \retval KNOT_ERROR + */ +int xfrin_create_axfr_query(knot_dname_t *owner, knot_ns_xfr_t *xfr, + size_t *size, int use_tsig); + +/*! + * \brief Creates normal query for the given zone name and the IXFR type. + * + * \param zone Zone contents. + * \param buffer Buffer to fill the message in. + * \param size In: available space in the buffer. Out: actual size of the + * message in bytes. + * \param use_tsig If TSIG should be used. + * + * \todo Parameter use_tsig probably not needed. + * + * \retval KNOT_EOK + * \retval KNOT_ESPACE + * \retval KNOT_ERROR + */ +int xfrin_create_ixfr_query(const knot_zone_contents_t *zone, + knot_ns_xfr_t *xfr, size_t *size, int use_tsig); + +/*! + * \brief Processes the newly created transferred zone. + * + * \param nameserver Name server to update. + * \param zone Zone build from transfer. + * + * \retval KNOT_ENOTSUP + */ +int xfrin_zone_transferred(knot_nameserver_t *nameserver, + knot_zone_contents_t *zone); + +/*! + * \brief Processes one incoming packet of AXFR transfer by updating the given + * zone. + * + * \param pkt Incoming packet in wire format. + * \param size Size of the packet in bytes. + * \param zone Zone being built. If there is no such zone (i.e. this is the + * first packet, \a *zone may be set to NULL, in which case a new + * zone structure is created). + * + * \retval KNOT_EOK + * + * \todo Refactor!!! + */ +int xfrin_process_axfr_packet(/*const uint8_t *pkt, size_t size, + xfrin_constructed_zone_t **zone*/ + knot_ns_xfr_t *xfr); + +/*! + * \brief Destroys the whole changesets structure. + * + * Frees all RRSets present in the changesets and all their data. Also frees + * the changesets structure and sets the parameter to NULL. + * + * \param changesets Changesets to destroy. + */ +void xfrin_free_changesets(knot_changesets_t **changesets); + +/*! + * \brief Parses IXFR reply packet and fills in the changesets structure. + * + * \param pkt Packet containing the IXFR reply in wire format. + * \param size Size of the packet in bytes. + * \param changesets Changesets to be filled in. + * + * \retval KNOT_EOK + * \retval KNOT_EINVAL + * \retval KNOT_EMALF + * \retval KNOT_ENOMEM + */ +int xfrin_process_ixfr_packet(knot_ns_xfr_t *xfr/*const uint8_t *pkt, size_t size, + knot_changesets_t **changesets*/); + +int xfrin_apply_changesets_to_zone(knot_zone_t *zone, + knot_changesets_t *chsets); + +#endif /* _KNOTXFR_IN_H_ */ + +/*! @} */ diff --git a/src/libknot/util/debug.c b/src/libknot/util/debug.c new file mode 100644 index 0000000..0ca67c9 --- /dev/null +++ b/src/libknot/util/debug.c @@ -0,0 +1,233 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdint.h> +#include <assert.h> +#include <stdlib.h> + +#include "util/utils.h" +#include "util/debug.h" +#include "libknot.h" +#include "common/print.h" + +void knot_rdata_dump(knot_rdata_t *rdata, uint32_t type, char loaded_zone) +{ +#if defined(KNOT_ZONE_DEBUG) || defined(KNOT_RDATA_DEBUG) + fprintf(stderr, " ------- RDATA -------\n"); + if (rdata == NULL) { + fprintf(stderr, " There are no rdata in this RRset!\n"); + fprintf(stderr, " ------- RDATA -------\n"); + return; + } + knot_rrtype_descriptor_t *desc = knot_rrtype_descriptor_by_type(type); + assert(desc != NULL); + char *name; + + for (int i = 0; i < rdata->count; i++) { + if (rdata->items[i].raw_data == NULL) { + continue; + } + if (desc->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME ) { + assert(rdata->items[i].dname != NULL); + name = knot_dname_to_str(rdata->items[i].dname); + fprintf(stderr, " DNAME: %d: %s\n", + i, name); + free(name); + if (loaded_zone) { + if (rdata->items[i].dname->node) { + name = + knot_dname_to_str(rdata->items[i].dname->node->owner); + fprintf(stderr, " Has node owner: %s\n", name); + free(name); + } else { + fprintf(stderr, " No node set\n"); + } + } + fprintf(stderr, " labels: "); + hex_print((char *)rdata->items[i].dname->labels, + rdata->items[i].dname->label_count); + + } else { + assert(rdata->items[i].raw_data != NULL); + fprintf(stderr, " %d: raw_data: length: %d\n", i, + *(rdata->items[i].raw_data)); + fprintf(stderr, " "); + hex_print(((char *)( + rdata->items[i].raw_data + 1)), + rdata->items[i].raw_data[0]); + } + } + fprintf(stderr, " ------- RDATA -------\n"); +#endif +} + +void knot_rrset_dump(const knot_rrset_t *rrset, char loaded_zone) +{ +#if defined(KNOT_ZONE_DEBUG) || defined(KNOT_RRSET_DEBUG) + fprintf(stderr, " ------- RRSET -------\n"); + fprintf(stderr, " %p\n", rrset); + if (!rrset) { + return; + } + char *name = knot_dname_to_str(rrset->owner); + fprintf(stderr, " owner: %s\n", name); + free(name); + fprintf(stderr, " type: %s\n", knot_rrtype_to_string(rrset->type)); + fprintf(stderr, " class: %d\n", rrset->rclass); + fprintf(stderr, " ttl: %d\n", rrset->ttl); + + fprintf(stderr, " RRSIGs:\n"); + if (rrset->rrsigs != NULL) { + knot_rrset_dump(rrset->rrsigs, loaded_zone); + } else { + fprintf(stderr, " none\n"); + } + + if (rrset->rdata == NULL) { + fprintf(stderr, " NO RDATA!\n"); + fprintf(stderr, " ------- RRSET -------\n"); + return; + } + + fprintf(stderr, " rdata count: %d\n", rrset->rdata->count); + knot_rdata_t *tmp = rrset->rdata; + + while (tmp->next != rrset->rdata && tmp->next != NULL) { + knot_rdata_dump(tmp, rrset->type, loaded_zone); + tmp = tmp->next; + } + + knot_rdata_dump(tmp, rrset->type, loaded_zone); + + fprintf(stderr, " ------- RRSET -------\n"); +#endif +} + +void knot_node_dump(knot_node_t *node, void *loaded_zone) +{ +#if defined(KNOT_ZONE_DEBUG) || defined(KNOT_NODE_DEBUG) + //char loaded_zone = *((char*) data); + char *name; + + fprintf(stderr, "------- NODE --------\n"); + name = knot_dname_to_str(node->owner); + fprintf(stderr, "owner: %s\n", name); + free(name); + fprintf(stderr, "labels: "); + hex_print((char *)node->owner->labels, node->owner->label_count); + fprintf(stderr, "node: %p\n", node); + fprintf(stderr, "node (in node's owner): %p\n", node->owner->node); + if (loaded_zone && node->prev != NULL) { + name = knot_dname_to_str(node->prev->owner); + fprintf(stderr, "previous node: %s\n", name); + free(name); + } + + if (knot_node_is_deleg_point(node)) { + fprintf(stderr, "delegation point\n"); + } + + if (knot_node_is_non_auth(node)) { + fprintf(stderr, "non-authoritative node\n"); + } + + if (node->parent != NULL) { + /*! \todo This causes segfault when parent was free'd, + * e.g. when applying changesets. + */ + name = knot_dname_to_str(node->parent->owner); + fprintf(stderr, "parent: %s\n", name); + free(name); + } else { + fprintf(stderr, "no parent\n"); + } + + if (node->prev != NULL) { + fprintf(stderr, "previous node: %p\n", node->prev); + /*! \todo This causes segfault when prev was free'd, + * e.g. when applying changesets. + */ + name = knot_dname_to_str(node->prev->owner); + fprintf(stderr, "previous node: %s\n", name); + free(name); + } else { + fprintf(stderr, "previous node: none\n"); + } + + knot_rrset_t **rrsets = knot_node_get_rrsets(node); + + fprintf(stderr, "Wildcard child: "); + + if (node->wildcard_child != NULL) { + /*! \todo This causes segfault when wildcard child was free'd, + * e.g. when applying changesets. + */ + name = knot_dname_to_str(node->wildcard_child->owner); + fprintf(stderr, "%s\n", name); + free(name); + } else { + fprintf(stderr, "none\n"); + } + + fprintf(stderr, "NSEC3 node: "); + + if (node->nsec3_node != NULL) { + /*! \todo This causes segfault when nsec3_node was free'd, + * e.g. when applying changesets. + */ + name = knot_dname_to_str(node->nsec3_node->owner); + fprintf(stderr, "%s\n", name); + free(name); + } else { + fprintf(stderr, "none\n"); + } + + fprintf(stderr, "RRSet count: %d\n", node->rrset_count); + + for (int i = 0; i < node->rrset_count; i++) { + knot_rrset_dump(rrsets[i], (int) loaded_zone); + } + free(rrsets); + //assert(node->owner->node == node); + fprintf(stderr, "------- NODE --------\n"); +#endif +} + +void knot_zone_contents_dump(knot_zone_contents_t *zone, char loaded_zone) +{ +#if defined(KNOT_ZONE_DEBUG) + if (!zone) { + fprintf(stderr, "------- STUB ZONE --------\n"); + return; + } + + fprintf(stderr, "------- ZONE --------\n"); + + knot_zone_contents_tree_apply_inorder(zone, knot_node_dump, (void *)&loaded_zone); + + fprintf(stderr, "------- ZONE --------\n"); + + fprintf(stderr, "------- NSEC 3 tree -\n"); + + knot_zone_contents_nsec3_apply_inorder(zone, knot_node_dump, (void *)&loaded_zone); + + fprintf(stderr, "------- NSEC 3 tree -\n"); +#endif +} diff --git a/src/libknot/util/debug.h b/src/libknot/util/debug.h new file mode 100644 index 0000000..2f9f5fd --- /dev/null +++ b/src/libknot/util/debug.h @@ -0,0 +1,755 @@ +/*! + * \file debug.h + * + * \author Jan Kadlec <jan.kadlec.@nic.cz> + * \author Lubos Slovak <lubos.slovak@nic.cz> + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Functions for debug output of structures. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_DEBUG_H_ +#define _KNOT_DEBUG_H_ + +#include <stdint.h> +#include <stdio.h> + +#include "config.h" /* autoconf generated */ + +#include "rdata.h" +#include "rrset.h" +#include "zone/node.h" +#include "zone/zone.h" +#include "util/utils.h" +#include "common/print.h" + +/* + * Debug macros + */ +/*! \todo Set these during configure. */ +//#define KNOT_ZONE_DEBUG +//#define KNOT_RESPONSE_DEBUG +//#define KNOT_ZONEDB_DEBUG +//#define KNOT_DNAME_DEBUG +//#define KNOT_NODE_DEBUG +//#define KNOT_PACKET_DEBUG +//#define KNOT_EDNS_DEBUG +//#define KNOT_RRSET_DEBUG +//#define KNOT_RDATA_DEBUG +//#define KNOT_NSEC3_DEBUG +//#define CUCKOO_DEBUG +//#define CUCKOO_DEBUG_HASH +//#define KNOT_NS_DEBUG +//#define KNOT_XFRIN_DEBUG +//#define KNOT_DDNS_DEBUG +//#define KNOT_TSIG_DEBUG + +/*! + * \brief Dumps RDATA of the given type. + * + * This function is empty if neither KNOT_ZONE_DEBUG nor KNOT_RDATA_DEBUG + * is defined. + * + * \param rdata RDATA to dump. + * \param type Type of the RDATA (needed to properly parse the RDATA). + * \param loaded_zone Set to <> 0 if the RDATA is part of a zone loaded into + * the server. Set to 0 otherwise. + */ +void knot_rdata_dump(knot_rdata_t *rdata, uint32_t type, char loaded_zone); + +/*! + * \brief Dumps RRSet. + * + * This function is empty if neither KNOT_ZONE_DEBUG nor KNOT_RRSET_DEBUG + * is defined. + * + * \param rrset RRSet to dump. + * \param loaded_zone Set to <> 0 if the RRSet is part of a zone loaded into + * the server. Set to 0 otherwise. + */ +void knot_rrset_dump(const knot_rrset_t *rrset, char loaded_zone); + +/*! + * \brief Dumps zone node. + * + * This function is empty if neither KNOT_ZONE_DEBUG nor KNOT_NODE_DEBUG + * is defined. + * + * \param node Node to dump. + * \param loaded_zone Set to <> 0 if the node is part of a zone loaded into + * the server. Set to 0 otherwise. + */ +void knot_node_dump(knot_node_t *node, void *loaded_zone); + +/*! + * \brief Dumps the whole zone. + * + * This function is empty if KNOT_ZONE_DEBUG is not defined. + * + * \param zone Zone to dump. + * \param loaded_zone Set to <> 0 if the node is part of a zone loaded into + * the server. Set to 0 otherwise. + */ +void knot_zone_contents_dump(knot_zone_contents_t *zone, char loaded_zone); + +/******************************************************************************/ + +#ifdef KNOT_NS_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_ns(msg...) fprintf(stderr, msg) +#define dbg_ns_hex(data, len) hex_print((data), (len)) +#define dbg_ns_exec(cmds) do { cmds } while (0) +#else +#define dbg_ns(msg...) +#define dbg_ns_hex(data, len) +#define dbg_ns_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_ns_verb(msg...) fprintf(stderr, msg) +#define dbg_ns_hex_verb(data, len) hex_print((data), (len)) +#define dbg_ns_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_ns_verb(msg...) +#define dbg_ns_hex_verb(data, len) +#define dbg_ns_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_ns_detail(msg...) fprintf(stderr, msg) +#define dbg_ns_hex_detail(data, len) hex_print((data), (len)) +#define dbg_ns_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_ns_detail(msg...) +#define dbg_ns_hex_detail(data, len) +#define dbg_ns_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_ns(msg...) +#define dbg_ns_hex(data, len) +#define dbg_ns_exec(cmds) +#define dbg_ns_verb(msg...) +#define dbg_ns_hex_verb(data, len) +#define dbg_ns_exec_verb(cmds) +#define dbg_ns_detail(msg...) +#define dbg_ns_hex_detail(data, len) +#define dbg_ns_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_DNAME_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_dname(msg...) fprintf(stderr, msg) +#define dbg_dname_hex(data, len) hex_print((data), (len)) +#define dbg_dname_exec(cmds) do { cmds } while (0) +#else +#define dbg_dname(msg...) +#define dbg_dname_hex(data, len) +#define dbg_dname_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_dname_verb(msg...) fprintf(stderr, msg) +#define dbg_dname_hex_verb(data, len) hex_print((data), (len)) +#define dbg_dname_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_dname_verb(msg...) +#define dbg_dname_hex_verb(data, len) +#define dbg_dname_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_dname_detail(msg...) fprintf(stderr, msg) +#define dbg_dname_hex_detail(data, len) hex_print((data), (len)) +#define dbg_dname_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_dname_detail(msg...) +#define dbg_dname_hex_detail(data, len) +#define dbg_dname_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_dname(msg...) +#define dbg_dname_hex(data, len) +#define dbg_dname_exec(cmds) +#define dbg_dname_verb(msg...) +#define dbg_dname_hex_verb(data, len) +#define dbg_dname_exec_verb(cmds) +#define dbg_dname_detail(msg...) +#define dbg_dname_hex_detail(data, len) +#define dbg_dname_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_NODE_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_node(msg...) fprintf(stderr, msg) +#define dbg_node_hex(data, len) hex_print((data), (len)) +#else +#define dbg_node(msg...) +#define dbg_node_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_node_verb(msg...) fprintf(stderr, msg) +#define dbg_node_hex_verb(data, len) hex_print((data), (len)) +#else +#define dbg_node_verb(msg...) +#define dbg_node_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_node_detail(msg...) fprintf(stderr, msg) +#define dbg_node_hex_detail(data, len) hex_print((data), (len)) +#else +#define dbg_node_detail(msg...) +#define dbg_node_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_node(msg...) +#define dbg_node_hex(data, len) +#define dbg_node_verb(msg...) +#define dbg_node_hex_verb(data, len) +#define dbg_node_detail(msg...) +#define dbg_node_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOT_ZONE_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_zone(msg...) fprintf(stderr, msg) +#define dbg_zone_hex(data, len) hex_print((data), (len)) +#define dbg_zone_exec(cmds) do { cmds } while (0) +#else +#define dbg_zone(msg...) +#define dbg_zone_hex(data, len) +#define dbg_zone_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_zone_verb(msg...) fprintf(stderr, msg) +#define dbg_zone_hex_verb(data, len) hex_print((data), (len)) +#define dbg_zone_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_zone_verb(msg...) +#define dbg_zone_hex_verb(data, len) +#define dbg_zone_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_zone_detail(msg...) fprintf(stderr, msg) +#define dbg_zone_hex_detail(data, len) hex_print((data), (len)) +#define dbg_zone_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_zone_detail(msg...) +#define dbg_zone_hex_detail(data, len) +#define dbg_zone_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_zone(msg...) +#define dbg_zone_hex(data, len) +#define dbg_zone_exec(cmds) +#define dbg_zone_verb(msg...) +#define dbg_zone_hex_verb(data, len) +#define dbg_zone_exec_verb(cmds) +#define dbg_zone_detail(msg...) +#define dbg_zone_hex_detail(data, len) +#define dbg_zone_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_ZONEDB_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_zonedb(msg...) fprintf(stderr, msg) +#define dbg_zonedb_hex(data, len) hex_print((data), (len)) +#define dbg_zonedb_exec(cmds) do { cmds } while (0) +#else +#define dbg_zonedb(msg...) +#define dbg_zonedb_hex(data, len) +#define dbg_zonedb_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_zonedb_verb(msg...) fprintf(stderr, msg) +#define dbg_zonedb_hex_verb(data, len) hex_print((data), (len)) +#define dbg_zonedb_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_zonedb_verb(msg...) +#define dbg_zonedb_hex_verb(data, len) +#define dbg_zonedb_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_zonedb_detail(msg...) fprintf(stderr, msg) +#define dbg_zonedb_hex_detail(data, len) hex_print((data), (len)) +#define dbg_zonedb_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_zonedb_detail(msg...) +#define dbg_zonedb_hex_detail(data, len) +#define dbg_zonedb_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_zonedb(msg...) +#define dbg_zonedb_hex(data, len) +#define dbg_zonedb_exec(cmds) +#define dbg_zonedb_verb(msg...) +#define dbg_zonedb_hex_verb(data, len) +#define dbg_zonedb_exec_verb(cmds) +#define dbg_zonedb_detail(msg...) +#define dbg_zonedb_hex_detail(data, len) +#define dbg_zonedb_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_RESPONSE_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_response(msg...) fprintf(stderr, msg) +#define dbg_response_hex(data, len) hex_print((data), (len)) +#define dbg_response_exec(cmds) do { cmds } while (0) +#else +#define dbg_response(msg...) +#define dbg_response_hex(data, len) +#define dbg_response_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_response_verb(msg...) fprintf(stderr, msg) +#define dbg_response_hex_verb(data, len) hex_print((data), (len)) +#define dbg_response_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_response_verb(msg...) +#define dbg_response_hex_verb(data, len) +#define dbg_response_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_response_detail(msg...) fprintf(stderr, msg) +#define dbg_response_hex_detail(data, len) hex_print((data), (len)) +#define dbg_response_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_response_detail(msg...) +#define dbg_response_hex_detail(data, len) +#define dbg_response_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_response(msg...) +#define dbg_response_hex(data, len) +#define dbg_response_exec(cmds) +#define dbg_response_verb(msg...) +#define dbg_response_hex_verb(data, len) +#define dbg_response_exec_verb(cmds) +#define dbg_response_detail(msg...) +#define dbg_response_hex_detail(data, len) +#define dbg_response_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_PACKET_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_packet(msg...) fprintf(stderr, msg) +#define dbg_packet_hex(data, len) hex_print((data), (len)) +#define dbg_packet_exec(cmds) do { cmds } while (0) +#else +#define dbg_packet(msg...) +#define dbg_packet_hex(data, len) +#define dbg_packet_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_packet_verb(msg...) fprintf(stderr, msg) +#define dbg_packet_hex_verb(data, len) hex_print((data), (len)) +#define dbg_packet_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_packet_verb(msg...) +#define dbg_packet_hex_verb(data, len) +#define dbg_packet_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_packet_detail(msg...) fprintf(stderr, msg) +#define dbg_packet_hex_detail(data, len) hex_print((data), (len)) +#define dbg_packet_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_packet_detail(msg...) +#define dbg_packet_hex_detail(data, len) +#define dbg_packet_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_packet(msg...) +#define dbg_packet_hex(data, len) +#define dbg_packet_exec(cmds) +#define dbg_packet_verb(msg...) +#define dbg_packet_hex_verb(data, len) +#define dbg_packet_exec_verb(cmds) +#define dbg_packet_detail(msg...) +#define dbg_packet_hex_detail(data, len) +#define dbg_packet_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_EDNS_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_edns(msg...) fprintf(stderr, msg) +#define dbg_edns_hex(data, len) hex_print((data), (len)) +#else +#define dbg_edns(msg...) +#define dbg_edns_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_edns_verb(msg...) fprintf(stderr, msg) +#define dbg_edns_hex_verb(data, len) hex_print((data), (len)) +#else +#define dbg_edns_verb(msg...) +#define dbg_edns_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_edns_detail(msg...) fprintf(stderr, msg) +#define dbg_edns_hex_detail(data, len) hex_print((data), (len)) +#else +#define dbg_edns_detail(msg...) +#define dbg_edns_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_edns(msg...) +#define dbg_edns_hex(data, len) +#define dbg_edns_verb(msg...) +#define dbg_edns_hex_verb(data, len) +#define dbg_edns_detail(msg...) +#define dbg_edns_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOT_NSEC3_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_nsec3(msg...) fprintf(stderr, msg) +#define dbg_nsec3_hex(data, len) hex_print((data), (len)) +#else +#define dbg_nsec3(msg...) +#define dbg_nsec3_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_nsec3_verb(msg...) fprintf(stderr, msg) +#define dbg_nsec3_hex_verb(data, len) hex_print((data), (len)) +#else +#define dbg_nsec3_verb(msg...) +#define dbg_nsec3_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_nsec3_detail(msg...) fprintf(stderr, msg) +#define dbg_nsec3_hex_detail(data, len) hex_print((data), (len)) +#else +#define dbg_nsec3_detail(msg...) +#define dbg_nsec3_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_nsec3(msg...) +#define dbg_nsec3_hex(data, len) +#define dbg_nsec3_verb(msg...) +#define dbg_nsec3_hex_verb(data, len) +#define dbg_nsec3_detail(msg...) +#define dbg_nsec3_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef CUCKOO_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_ck(msg...) fprintf(stderr, msg) +#define dbg_ck_hex(data, len) hex_print((data), (len)) +#else +#define dbg_ck(msg...) +#define dbg_ck_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_ck_verb(msg...) fprintf(stderr, msg) +#define dbg_ck_hex_verb(data, len) hex_print((data), (len)) +#else +#define dbg_ck_verb(msg...) +#define dbg_ck_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_ck_detail(msg...) fprintf(stderr, msg) +#define dbg_ck_hex_detail(data, len) hex_print((data), (len)) +#else +#define dbg_ck_detail(msg...) +#define dbg_ck_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_ck(msg...) +#define dbg_ck_hex(data, len) +#define dbg_ck_verb(msg...) +#define dbg_ck_hex_verb(data, len) +#define dbg_ck_detail(msg...) +#define dbg_ck_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef CUCKOO_DEBUG_HASH + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_ck_hash(msg...) fprintf(stderr, msg) +#define dbg_ck_rehash(msg...) fprintf(stderr, msg) +#define dbg_ck_hash_hex(data, len) hex_print((data), (len)) +#else +#define dbg_ck_hash(msg...) +#define dbg_ck_rehash(msg...) +#define dbg_ck_hash_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_ck_hash_verb(msg...) fprintf(stderr, msg) +#define dbg_ck_hash_hex_verb(data, len) hex_print((data), (len)) +#else +#define dbg_ck_hash_verb(msg...) +#define dbg_ck_hash_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_ck_hash_detail(msg...) fprintf(stderr, msg) +#define dbg_ck_hash_hex_detail(data, len) hex_print((data), (len)) +#else +#define dbg_ck_hash_detail(msg...) +#define dbg_ck_hash_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_ck_hash(msg...) +#define dbg_ck_rehash(msg...) +#define dbg_ck_hash_hex(data, len) +#define dbg_ck_hash_verb(msg...) +#define dbg_ck_hash_hex_verb(data, len) +#define dbg_ck_hash_detail(msg...) +#define dbg_ck_hash_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOT_XFRIN_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_xfrin(msg...) fprintf(stderr, msg) +#define dbg_xfrin_hex(data, len) hex_print((data), (len)) +#define dbg_xfrin_exec(cmds) do { cmds } while (0) +#else +#define dbg_xfrin(msg...) +#define dbg_xfrin_hex(data, len) +#define dbg_xfrin_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_xfrin_verb(msg...) fprintf(stderr, msg) +#define dbg_xfrin_hex_verb(data, len) hex_print((data), (len)) +#define dbg_xfrin_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_xfrin_verb(msg...) +#define dbg_xfrin_hex_verb(data, len) +#define dbg_xfrin_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_xfrin_detail(msg...) fprintf(stderr, msg) +#define dbg_xfrin_hex_detail(data, len) hex_print((data), (len)) +#define dbg_xfrin_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_xfrin_detail(msg...) +#define dbg_xfrin_hex_detail(data, len) +#define dbg_xfrin_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_xfrin(msg...) +#define dbg_xfrin_hex(data, len) +#define dbg_xfrin_exec(cmds) +#define dbg_xfrin_verb(msg...) +#define dbg_xfrin_hex_verb(data, len) +#define dbg_xfrin_exec_verb(cmds) +#define dbg_xfrin_detail(msg...) +#define dbg_xfrin_hex_detail(data, len) +#define dbg_xfrin_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_DDNS_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_ddns(msg...) fprintf(stderr, msg) +#define dbg_ddns_hex(data, len) hex_print((data), (len)) +#else +#define dbg_ddns(msg...) +#define dbg_ddns_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_ddns_verb(msg...) fprintf(stderr, msg) +#define dbg_ddns_hex_verb(data, len) hex_print((data), (len)) +#else +#define dbg_ddns_verb(msg...) +#define dbg_ddns_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_ddns_detail(msg...) fprintf(stderr, msg) +#define dbg_ddns_hex_detail(data, len) hex_print((data), (len)) +#else +#define dbg_ddns_detail(msg...) +#define dbg_ddns_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_ddns(msg...) +#define dbg_ddns_hex(data, len) +#define dbg_ddns_verb(msg...) +#define dbg_ddns_hex_verb(data, len) +#define dbg_ddns_detail(msg...) +#define dbg_ddns_hex_detail(data, len) +#endif + +#ifdef KNOT_TSIG_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_tsig(msg...) fprintf(stderr, msg) +#define dbg_tsig_hex(data, len) hex_print((const char*)(data), (len)) +#else +#define dbg_tsig(msg...) +#define dbg_tsig_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_tsig_verb(msg...) fprintf(stderr, msg) +#define dbg_tsig_hex_verb(data, len) hex_print((const char*)(data), (len)) +#else +#define dbg_tsig_verb(msg...) +#define dbg_tsig_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_tsig_detail(msg...) fprintf(stderr, msg) +#define dbg_tsig_hex_detail(data, len) hex_print((const char*)(data), (len)) +#else +#define dbg_tsig_detail(msg...) +#define dbg_tsig_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_tsig(msg...) +#define dbg_tsig_hex(data, len) +#define dbg_tsig_verb(msg...) +#define dbg_tsig_hex_verb(data, len) +#define dbg_tsig_detail(msg...) +#define dbg_tsig_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#endif /* _KNOT_DEBUG_H_ */ + +/*! @} */ diff --git a/src/libknot/util/descriptor.c b/src/libknot/util/descriptor.c new file mode 100644 index 0000000..fa94c24 --- /dev/null +++ b/src/libknot/util/descriptor.c @@ -0,0 +1,501 @@ +/*! + * \file descriptor.c + * + * \author Modifications by Jan Kadlec <jan.kadlec@nic.cz>, + * most of the work by NLnet labs. + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * \note Most of the constants and functions were taken from NSD's dns.c. + * + * \addtogroup libknot + * @{ + */ + +/* + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <config.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <assert.h> +#include <string.h> +#include <sys/types.h> + +#include "libknot.h" + +enum desclen { KNOT_RRTYPE_DESCRIPTORS_LENGTH = 32770 }; // used to be 101 + +/*! + * \brief Table for linking RR class constants to their textual representation. + */ +static knot_lookup_table_t dns_rrclasses[] = { + { KNOT_CLASS_IN, "IN" }, /* the Internet */ + { KNOT_CLASS_CS, "CS" }, /* the CSNET class (Obsolete) */ + { KNOT_CLASS_CH, "CH" }, /* the CHAOS class */ + { KNOT_CLASS_HS, "HS" }, /* Hesiod */ + { 0, NULL } +}; + +/*! \brief RR type descriptors. */ +static knot_rrtype_descriptor_t + knot_rrtype_descriptors[KNOT_RRTYPE_DESCRIPTORS_LENGTH] = { + /* 0 */ + { 0, NULL, 1, { KNOT_RDATA_WF_BINARY }, { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 1 */ + { KNOT_RRTYPE_A, "A", 1, { KNOT_RDATA_WF_A }, { KNOT_RDATA_ZF_A }, true }, + /* 2 */ + { KNOT_RRTYPE_NS, "NS", 1, + { KNOT_RDATA_WF_COMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 3 */ + { KNOT_RRTYPE_MD, "MD", 1, + { KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 4 */ + { KNOT_RRTYPE_MF, "MF", 1, + { KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 5 */ + { KNOT_RRTYPE_CNAME, "CNAME", 1, + { KNOT_RDATA_WF_COMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 6 */ + { KNOT_RRTYPE_SOA, "SOA", 7, + { KNOT_RDATA_WF_COMPRESSED_DNAME, KNOT_RDATA_WF_COMPRESSED_DNAME, + KNOT_RDATA_WF_LONG, KNOT_RDATA_WF_LONG, KNOT_RDATA_WF_LONG, + KNOT_RDATA_WF_LONG, KNOT_RDATA_WF_LONG }, + { KNOT_RDATA_ZF_DNAME, KNOT_RDATA_ZF_DNAME, KNOT_RDATA_ZF_PERIOD, KNOT_RDATA_ZF_PERIOD, + KNOT_RDATA_ZF_PERIOD, KNOT_RDATA_ZF_PERIOD, KNOT_RDATA_ZF_PERIOD }, + true }, + /* 7 */ + { KNOT_RRTYPE_MB, "MB", 1, + { KNOT_RDATA_WF_COMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 8 */ + { KNOT_RRTYPE_MG, "MG", 1, + { KNOT_RDATA_WF_COMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 9 */ + { KNOT_RRTYPE_MR, "MR", 1, + { KNOT_RDATA_WF_COMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 10 */ + { KNOT_RRTYPE_NULL, NULL, 1, + { KNOT_RDATA_WF_BINARY }, { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 11 */ + { KNOT_RRTYPE_WKS, "WKS", 2, + { KNOT_RDATA_WF_A, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_A, KNOT_RDATA_ZF_SERVICES }, true }, + /* 12 */ + { KNOT_RRTYPE_PTR, "PTR", 1, + { KNOT_RDATA_WF_COMPRESSED_DNAME }, + { KNOT_RDATA_ZF_DNAME }, true }, + /* 13 */ + { KNOT_RRTYPE_HINFO, "HINFO", 2, + { KNOT_RDATA_WF_TEXT_SINGLE, KNOT_RDATA_WF_TEXT_SINGLE }, + { KNOT_RDATA_ZF_TEXT, KNOT_RDATA_ZF_TEXT }, true }, + /* 14 */ + { KNOT_RRTYPE_MINFO, "MINFO", 2, + { KNOT_RDATA_WF_COMPRESSED_DNAME, + KNOT_RDATA_WF_COMPRESSED_DNAME }, + { KNOT_RDATA_ZF_DNAME, KNOT_RDATA_ZF_DNAME }, true }, + /* 15 */ + { KNOT_RRTYPE_MX, "MX", 2, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_COMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME }, true }, + /* 16 */ /* This is obscure, but I guess there's no other way */ + { KNOT_RRTYPE_TXT, "TXT", 1, + { KNOT_RDATA_WF_TEXT }, + { KNOT_RDATA_ZF_TEXT }, + false }, + /* 17 */ + { KNOT_RRTYPE_RP, "RP", 2, + { KNOT_RDATA_WF_COMPRESSED_DNAME, + KNOT_RDATA_WF_COMPRESSED_DNAME }, + { KNOT_RDATA_ZF_DNAME, KNOT_RDATA_ZF_DNAME }, true }, + /* 18 */ + { KNOT_RRTYPE_AFSDB, "AFSDB", 2, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_COMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME }, true }, + /* 19 */ + { KNOT_RRTYPE_X25, "X25", 1, + { KNOT_RDATA_WF_TEXT_SINGLE }, + { KNOT_RDATA_ZF_TEXT }, true }, + /* 20 */ + { KNOT_RRTYPE_ISDN, "ISDN", 2, + { KNOT_RDATA_WF_TEXT_SINGLE, KNOT_RDATA_WF_TEXT_SINGLE }, + { KNOT_RDATA_ZF_TEXT, KNOT_RDATA_ZF_TEXT }, false }, + /* 21 */ + { KNOT_RRTYPE_RT, "RT", 2, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_COMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME }, true }, + /* 22 */ + { KNOT_RRTYPE_NSAP, "NSAP", 1, + { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_NSAP }, true }, + /* 23 */ + { 23, NULL, 1, { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 24 */ + { KNOT_RRTYPE_SIG, "SIG", 9, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_LONG, KNOT_RDATA_WF_LONG, KNOT_RDATA_WF_LONG, + KNOT_RDATA_WF_SHORT,KNOT_RDATA_WF_UNCOMPRESSED_DNAME, + KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_RRTYPE, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_PERIOD, + KNOT_RDATA_ZF_TIME, KNOT_RDATA_ZF_TIME, KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME, + KNOT_RDATA_ZF_BASE64 }, + true }, + /* 25 */ + { KNOT_RRTYPE_KEY, "KEY", 4, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_ALGORITHM, + KNOT_RDATA_ZF_BASE64 }, true }, + /* 26 */ + { KNOT_RRTYPE_PX, "PX", 3, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_UNCOMPRESSED_DNAME, + KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME, KNOT_RDATA_ZF_DNAME }, true }, + /* 27 */ + { 27, NULL, 1, { KNOT_RDATA_WF_BINARY }, { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 28 */ + { KNOT_RRTYPE_AAAA, "AAAA", 1, + { KNOT_RDATA_WF_AAAA }, + { KNOT_RDATA_ZF_AAAA }, true }, + /* 29 */ + { KNOT_RRTYPE_LOC, "LOC", 1, + { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_LOC }, true }, + /* 30 */ + { KNOT_RRTYPE_NXT, "NXT", 2, + { KNOT_RDATA_WF_UNCOMPRESSED_DNAME, + KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_DNAME, KNOT_RDATA_ZF_NXT }, true }, + /* 31 */ + { 31, NULL, 1, { KNOT_RDATA_WF_BINARY }, { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 32 */ + { 32, NULL, 1, { KNOT_RDATA_WF_BINARY }, { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 33 */ + { KNOT_RRTYPE_SRV, "SRV", 4, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_SHORT, + KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME }, + true }, + /* 34 */ + { 34, NULL, 1, { KNOT_RDATA_WF_BINARY }, { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 35 */ + { KNOT_RRTYPE_NAPTR, "NAPTR", 6, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_TEXT_SINGLE, + KNOT_RDATA_WF_TEXT_SINGLE, KNOT_RDATA_WF_TEXT_SINGLE, + KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_TEXT, KNOT_RDATA_ZF_TEXT, + KNOT_RDATA_ZF_TEXT, KNOT_RDATA_ZF_DNAME }, true }, + /* 36 */ + { KNOT_RRTYPE_KX, "KX", 2, + { KNOT_RDATA_WF_SHORT, + KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME }, true }, + /* 37 */ + { KNOT_RRTYPE_CERT, "CERT", 4, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_SHORT, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_CERTIFICATE_TYPE, KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_ALGORITHM, + KNOT_RDATA_ZF_BASE64 }, true }, + /* 38 */ + { KNOT_RRTYPE_A6, NULL, 1, { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 39 */ + { KNOT_RRTYPE_DNAME, "DNAME", 1, + { KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, + { KNOT_RDATA_ZF_DNAME }, true }, + /* 40 */ + { 40, NULL, 1, { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 41 */ + /* OPT has its parser token, but should never be in zone file... */ + { KNOT_RRTYPE_OPT, "OPT", 1, + { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 42 */ + { KNOT_RRTYPE_APL, "APL", 1, + { KNOT_RDATA_WF_APL }, + { KNOT_RDATA_ZF_APL }, false }, + /* 43 */ + { KNOT_RRTYPE_DS, "DS", 4, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_ALGORITHM, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_HEX }, true }, + /* 44 */ + { KNOT_RRTYPE_SSHFP, "SSHFP", 3, + { KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_HEX }, + true }, + /* 45 */ + { KNOT_RRTYPE_IPSECKEY, "IPSECKEY", 5, + { KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_IPSECGATEWAY, + KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_IPSECGATEWAY, + KNOT_RDATA_ZF_BASE64 }, false }, + /* 46 */ + { KNOT_RRTYPE_RRSIG, "RRSIG", 9, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_LONG, + KNOT_RDATA_WF_LONG, KNOT_RDATA_WF_LONG, + KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_LITERAL_DNAME, + KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_RRTYPE, KNOT_RDATA_ZF_ALGORITHM, + KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_PERIOD, + KNOT_RDATA_ZF_TIME, KNOT_RDATA_ZF_TIME, + KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_LITERAL_DNAME, + KNOT_RDATA_ZF_BASE64 }, true }, + /* 47 */ + { KNOT_RRTYPE_NSEC, "NSEC", 2, + { KNOT_RDATA_WF_LITERAL_DNAME, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_LITERAL_DNAME, KNOT_RDATA_ZF_NSEC }, + true }, + /* 48 */ + { KNOT_RRTYPE_DNSKEY, "DNSKEY", 4, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_BYTE, + KNOT_RDATA_ZF_ALGORITHM, KNOT_RDATA_ZF_BASE64 }, true }, + /* 49 */ + { KNOT_RRTYPE_DHCID, "DHCID", 1, { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_BASE64 }, true }, + /* 50 */ + { KNOT_RRTYPE_NSEC3, "NSEC3", 6, + { KNOT_RDATA_WF_BYTE, /* hash type */ + KNOT_RDATA_WF_BYTE, /* flags */ + KNOT_RDATA_WF_SHORT, /* iterations */ + KNOT_RDATA_WF_BINARYWITHLENGTH, /* salt */ + KNOT_RDATA_WF_BINARYWITHLENGTH, /* next hashed name */ + KNOT_RDATA_WF_BINARY /* type bitmap */ }, + { KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_HEX_LEN, + KNOT_RDATA_ZF_BASE32, KNOT_RDATA_ZF_NSEC }, + true }, + /* 51 */ + { KNOT_RRTYPE_NSEC3PARAM, "NSEC3PARAM", 4, + { KNOT_RDATA_WF_BYTE, /* hash type */ + KNOT_RDATA_WF_BYTE, /* flags */ + KNOT_RDATA_WF_SHORT, /* iterations */ + KNOT_RDATA_WF_BINARYWITHLENGTH /* salt */ }, + { KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_BYTE, + KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_HEX_LEN }, true }, + /* 52 */ + + + /* In NSD they have indices between 52 and 99 filled with + unknown types. TODO add here if it's really needed? */ + /* it is indeed needed, in rrtype_from_string */ + + /* There's a GNU extension that works like this: [first ... last] = value */ + + /* 99 */ + [99] = { KNOT_RRTYPE_SPF, "SPF", 1, + { KNOT_RDATA_WF_TEXT }, + { KNOT_RDATA_ZF_TEXT }, false }, + /* TSIG pseudo RR. */ + [250] = { KNOT_RRTYPE_TSIG, "TSIG", 7, + { KNOT_RDATA_WF_UNCOMPRESSED_DNAME, KNOT_RDATA_WF_UINT48, + KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BINARYWITHSHORT, + KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_SHORT, + KNOT_RDATA_WF_BINARYWITHSHORT }, + /* Zoneformat not needed. */ + {0, 0, 0, 0, 0}, true }, + /* 32769 */ + [32769] = { KNOT_RRTYPE_DLV, "DLV", 4, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_ALGORITHM, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_HEX }, + true }, +}; + +knot_rrtype_descriptor_t *knot_rrtype_descriptor_by_type(uint16_t type) +{ + if (type < KNOT_RRTYPE_LAST + 1) { + return &knot_rrtype_descriptors[type]; + } else if (type == KNOT_RRTYPE_DLV) { + return &knot_rrtype_descriptors[KNOT_RRTYPE_DLV]; + } + return &knot_rrtype_descriptors[0]; +} + +/* I see a lot of potential here to speed up zone parsing - this is O(n) * + * could be better */ +knot_rrtype_descriptor_t *knot_rrtype_descriptor_by_name(const char *name) +{ + int i; + + for (i = 0; i < KNOT_RRTYPE_DLV + 1; ++i) { + if (knot_rrtype_descriptors[i].name && + strcasecmp(knot_rrtype_descriptors[i].name, name) == 0) { + return &knot_rrtype_descriptors[i]; + } + } + + if (knot_rrtype_descriptors[KNOT_RRTYPE_DLV].name && + strcasecmp(knot_rrtype_descriptors[KNOT_RRTYPE_DLV].name, + name) == 0) { + return &knot_rrtype_descriptors[KNOT_RRTYPE_DLV]; + } + + return NULL; +} + +const char *knot_rrtype_to_string(uint16_t rrtype) +{ + static char buf[20]; + knot_rrtype_descriptor_t *descriptor = + knot_rrtype_descriptor_by_type(rrtype); + if (descriptor->name) { + return descriptor->name; + } else { + snprintf(buf, sizeof(buf), "TYPE%d", (int) rrtype); + return buf; + } +} + +uint16_t knot_rrtype_from_string(const char *name) +{ + char *end; + long rrtype; + knot_rrtype_descriptor_t *entry; + + entry = knot_rrtype_descriptor_by_name(name); + if (entry) { + return entry->type; + } + + if (strlen(name) < 5) { + return 0; + } + + if (strncasecmp(name, "TYPE", 4) != 0) { + return 0; + } + + if (!isdigit((int)name[4])) { + return 0; + } + + /* The rest from the string must be a number. */ + rrtype = strtol(name + 4, &end, 10); + if (*end != '\0') { + return 0; + } + if (rrtype < 0 || rrtype > 65535L) { + return 0; + } + + return (uint16_t) rrtype; +} + +const char *knot_rrclass_to_string(uint16_t rrclass) +{ + static char buf[20]; + knot_lookup_table_t *entry = knot_lookup_by_id(dns_rrclasses, + rrclass); + if (entry) { + assert(strlen(entry->name) < sizeof(buf)); + knot_strlcpy(buf, entry->name, sizeof(buf)); + } else { + snprintf(buf, sizeof(buf), "CLASS%d", (int) rrclass); + } + return buf; +} + +uint16_t knot_rrclass_from_string(const char *name) +{ + char *end; + long rrclass; + knot_lookup_table_t *entry; + + entry = knot_lookup_by_name(dns_rrclasses, name); + if (entry) { + return (uint16_t) entry->id; + } + + if (strlen(name) < 6) { + return 0; + } + + if (strncasecmp(name, "CLASS", 5) != 0) { + return 0; + } + + if (!isdigit((int)name[5])) { + return 0; + } + + // The rest from the string must be a number. + rrclass = strtol(name + 5, &end, 10); + if (*end != '\0') { + return 0; + } + if (rrclass < 0 || rrclass > 65535L) { + return 0; + } + + return (uint16_t) rrclass; +} + +size_t knot_wireformat_size(unsigned int wire_type) +{ + switch(wire_type) { + case KNOT_RDATA_WF_BYTE: + return 1; + break; + case KNOT_RDATA_WF_SHORT: + return 2; + break; + case KNOT_RDATA_WF_LONG: + return 4; + break; + case KNOT_RDATA_WF_A: + return 4; + break; + default: /* unknown size */ + return 0; + break; + } /* switch */ +} + +int knot_rrtype_is_metatype(uint16_t type) +{ + /*! \todo Check if there are some other metatypes. */ + return (type == KNOT_RRTYPE_ANY + || type == KNOT_RRTYPE_AXFR + || type == KNOT_RRTYPE_IXFR + || type == KNOT_RRTYPE_MAILA + || type == KNOT_RRTYPE_MAILB + || type == KNOT_RRTYPE_OPT); +} + diff --git a/src/libknot/util/descriptor.h b/src/libknot/util/descriptor.h new file mode 100644 index 0000000..10d3d20 --- /dev/null +++ b/src/libknot/util/descriptor.h @@ -0,0 +1,332 @@ +/*! + * \file descriptor.h + * + * \author Modifications by Jan Kadlec <jan.kadlec@nic.cz>, + * most of the work by NLnet Labs. + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * \note Most of the constants and functions were taken from NSD's dns.h. + * + * \addtogroup libknot + * @{ + */ + +/* + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _KNOT_DESCRIPTOR_H_ +#define _KNOT_DESCRIPTOR_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> + +enum knot_mxrdtln { + /*! \brief Maximum items in RDATA wireformat. */ + KNOT_MAX_RDATA_ITEMS = 64, + /*! \brief Maximum size of one item in RDATA wireformat. */ + KNOT_MAX_RDATA_ITEM_SIZE = 65534, + /*! \brief Maximum wire size of one RDATA. */ + KNOT_MAX_RDATA_WIRE_SIZE = + KNOT_MAX_RDATA_ITEMS * KNOT_MAX_RDATA_ITEM_SIZE +}; + +typedef enum knot_mxrdtln knot_mxrdtln_t; +//#define MAXRDATALEN 64 + +/* 64 is in NSD. Seems a little too much, but I'd say it's not a real issue. */ + +/*! + * \brief Resource record class codes. + */ +enum knot_rr_class { + KNOT_CLASS_IN = 1, + KNOT_CLASS_CS, + KNOT_CLASS_CH, + KNOT_CLASS_HS, + KNOT_CLASS_NONE = 254, + KNOT_CLASS_ANY = 255 +}; + +typedef enum knot_rr_class knot_rr_class_t; + +/*! + * \brief Resource record type constants. + * \todo Not all indices can be used for indexing. + */ +enum knot_rr_type { + KNOT_RRTYPE_UNKNOWN, /*!< 0 - an unknown type */ + KNOT_RRTYPE_A, /*!< 1 - a host address */ + KNOT_RRTYPE_NS, /*!< 2 - an authoritative name server */ + KNOT_RRTYPE_MD, /*!< 3 - a mail destination (Obsolete - use MX) */ + KNOT_RRTYPE_MF, /*!< 4 - a mail forwarder (Obsolete - use MX) */ + KNOT_RRTYPE_CNAME, /*!< 5 - the canonical name for an alias */ + KNOT_RRTYPE_SOA, /*!< 6 - marks the start of a zone of authority */ + KNOT_RRTYPE_MB, /*!< 7 - a mailbox domain name (EXPERIMENTAL) */ + KNOT_RRTYPE_MG, /*!< 8 - a mail group member (EXPERIMENTAL) */ + KNOT_RRTYPE_MR, /*!< 9 - a mail rename domain name (EXPERIMENTAL) */ + KNOT_RRTYPE_NULL, /*!< 10 - a null RR (EXPERIMENTAL) */ + KNOT_RRTYPE_WKS, /*!< 11 - a well known service description */ + KNOT_RRTYPE_PTR, /*!< 12 - a domain name pointer */ + KNOT_RRTYPE_HINFO, /*!< 13 - host information */ + KNOT_RRTYPE_MINFO, /*!< 14 - mailbox or mail list information */ + KNOT_RRTYPE_MX, /*!< 15 - mail exchange */ + KNOT_RRTYPE_TXT, /*!< 16 - text strings */ + KNOT_RRTYPE_RP, /*!< 17 - RFC1183 */ + KNOT_RRTYPE_AFSDB, /*!< 18 - RFC1183 */ + KNOT_RRTYPE_X25, /*!< 19 - RFC1183 */ + KNOT_RRTYPE_ISDN, /*!< 20 - RFC1183 */ + KNOT_RRTYPE_RT, /*!< 21 - RFC1183 */ + KNOT_RRTYPE_NSAP, /*!< 22 - RFC1706 */ + + KNOT_RRTYPE_SIG = 24, /*!< 24 - 2535typecode */ + KNOT_RRTYPE_KEY, /*!< 25 - 2535typecode */ + KNOT_RRTYPE_PX, /*!< 26 - RFC2163 */ + + KNOT_RRTYPE_AAAA = 28, /*!< 28 - ipv6 address */ + KNOT_RRTYPE_LOC, /*!< 29 - LOC record RFC1876 */ + KNOT_RRTYPE_NXT, /*!< 30 - 2535typecode */ + + KNOT_RRTYPE_SRV = 33, /*!< 33 - SRV record RFC2782 */ + + KNOT_RRTYPE_NAPTR = 35, /*!< 35 - RFC2915 */ + KNOT_RRTYPE_KX, /*!< 36 - RFC2230 Key Exchange Delegation Record */ + KNOT_RRTYPE_CERT, /*!< 37 - RFC2538 */ + KNOT_RRTYPE_A6, /*!< 38 - RFC2874 */ + KNOT_RRTYPE_DNAME, /*!< 39 - RFC2672 */ + + KNOT_RRTYPE_OPT = 41, /*!< 41 - Pseudo OPT record... */ + KNOT_RRTYPE_APL, /*!< 42 - RFC3123 */ + KNOT_RRTYPE_DS, /*!< 43 - RFC 4033, 4034, and 4035 */ + KNOT_RRTYPE_SSHFP, /*!< 44 - SSH Key Fingerprint */ + KNOT_RRTYPE_IPSECKEY, /*!< 45 - public key for ipsec use. RFC 4025 */ + KNOT_RRTYPE_RRSIG, /*!< 46 - RFC 4033, 4034, and 4035 */ + KNOT_RRTYPE_NSEC, /*!< 47 - RFC 4033, 4034, and 4035 */ + KNOT_RRTYPE_DNSKEY, /*!< 48 - RFC 4033, 4034, and 4035 */ + KNOT_RRTYPE_DHCID, /*!< 49 - RFC4701 DHCP information */ + /*! + * \brief 50 - NSEC3, secure denial, prevents zonewalking + */ + KNOT_RRTYPE_NSEC3, + /*! + * \brief 51 - NSEC3PARAM at zone apex nsec3 parameters + */ + KNOT_RRTYPE_NSEC3PARAM, + + /* TODO consider some better way of doing this, indices too high */ + + KNOT_RRTYPE_SPF = 99, /*!< RFC 4408 */ + + // not designating any RRs + KNOT_RRTYPE_TSIG = 250, /*!< TSIG - RFC2845. */ + KNOT_RRTYPE_IXFR = 251, /*!< IXFR (not an actual RR). */ + KNOT_RRTYPE_AXFR = 252, /*!< AXFR (not an actual RR). */ + /*! + * \brief A request for mailbox-related records (MB, MG or MR) + */ + KNOT_RRTYPE_MAILB = 253, + /*! + * \brief A request for mail agent RRs (Obsolete - see MX) + */ + KNOT_RRTYPE_MAILA = 254, + KNOT_RRTYPE_ANY = 255, /*!< any type (wildcard) */ + + // totally weird numbers (cannot use for indexing) + KNOT_RRTYPE_TA = 32768, /*!< DNSSEC Trust Authorities */ + KNOT_RRTYPE_DLV = 32769, /*!< RFC 4431 */ + + /*! \brief Last normal RR type. */ + KNOT_RRTYPE_LAST = KNOT_RRTYPE_TSIG + /*! \todo [TSIG] Is it allright to include all <= RR TSIG? + * Because TSIG is normal RR type. */ +}; + +typedef enum knot_rr_type knot_rr_type_t; + +/*! \brief Constants characterising the wire format of RDATA items. */ +enum knot_rdata_wireformat { + /*! + * \brief Possibly compressed domain name. + */ + KNOT_RDATA_WF_COMPRESSED_DNAME = 50, + KNOT_RDATA_WF_UNCOMPRESSED_DNAME = 51, /*!< Uncompressed domain name. */ + KNOT_RDATA_WF_LITERAL_DNAME = 52, /*!< Literal (not downcased) dname. */ + KNOT_RDATA_WF_BYTE = 1, /*!< 8-bit integer. */ + KNOT_RDATA_WF_SHORT = 2, /*!< 16-bit integer. */ + KNOT_RDATA_WF_LONG = 4, /*!< 32-bit integer. */ + KNOT_RDATA_WF_UINT48 = 8, /*!< 48-bit integer. */ + KNOT_RDATA_WF_TEXT = 53, /*!< Text string. */ + KNOT_RDATA_WF_A = 58, /*!< 32-bit IPv4 address. */ + KNOT_RDATA_WF_AAAA = 16, /*!< 128-bit IPv6 address. */ + KNOT_RDATA_WF_BINARY = 54, /*!< Binary data (unknown length). */ + /*! + * \brief Binary data preceded by 1 byte length + */ + KNOT_RDATA_WF_BINARYWITHLENGTH = 55, + KNOT_RDATA_WF_APL = 56, /*!< APL data. */ + KNOT_RDATA_WF_IPSECGATEWAY = 57, /*!< IPSECKEY gateway ip4, ip6 or dname. */ + KNOT_RDATA_WF_BINARYWITHSHORT = 59, + KNOT_RDATA_WF_TEXT_SINGLE = 60 /*!< Text string. */ +}; + +/*! \brief Constants characterising the format of RDATA items in zone file. */ +enum knot_rdata_zoneformat +{ + KNOT_RDATA_ZF_DNAME, /* Domain name. */ + KNOT_RDATA_ZF_LITERAL_DNAME, /* DNS name (not lowercased domain name). */ + KNOT_RDATA_ZF_TEXT, /* Text string. */ + KNOT_RDATA_ZF_BYTE, /* 8-bit integer. */ + KNOT_RDATA_ZF_SHORT, /* 16-bit integer. */ + KNOT_RDATA_ZF_LONG, /* 32-bit integer. */ + KNOT_RDATA_ZF_A, /* 32-bit IPv4 address. */ + KNOT_RDATA_ZF_AAAA, /* 128-bit IPv6 address. */ + KNOT_RDATA_ZF_RRTYPE, /* RR type. */ + KNOT_RDATA_ZF_ALGORITHM, /* Cryptographic algorithm. */ + KNOT_RDATA_ZF_CERTIFICATE_TYPE, + KNOT_RDATA_ZF_PERIOD, /* Time period. */ + KNOT_RDATA_ZF_TIME, + KNOT_RDATA_ZF_BASE64, /* Base-64 binary data. */ + KNOT_RDATA_ZF_BASE32, /* Base-32 binary data. */ + KNOT_RDATA_ZF_HEX, /* Hexadecimal binary data. */ + KNOT_RDATA_ZF_HEX_LEN, /* Hexadecimal binary data. Skip initial length byte. */ + KNOT_RDATA_ZF_NSAP, /* NSAP. */ + KNOT_RDATA_ZF_APL, /* APL. */ + KNOT_RDATA_ZF_IPSECGATEWAY, /* IPSECKEY gateway ip4, ip6 or dname. */ + KNOT_RDATA_ZF_SERVICES, /* Protocol and port number bitmap. */ + KNOT_RDATA_ZF_NXT, /* NXT type bitmap. */ + KNOT_RDATA_ZF_NSEC, /* NSEC type bitmap. */ + KNOT_RDATA_ZF_LOC, /* Location data. */ + KNOT_RDATA_ZF_UNKNOWN /* Unknown data. */ +}; + +/*! \brief Constants characterising the wire format of RDATA items. */ +typedef enum knot_rdata_zoneformat knot_rdata_zoneformat_t; + +/*! \brief Enum containing wireformat codes. */ +typedef enum knot_rdatawireformat knot_rdata_wireformat_t; + +/*! \brief Structure holding RR descriptor. */ +struct knot_rrtype_descriptor { + uint16_t type; /*!< RR type */ + const char *name; /*!< Textual name. */ + uint8_t length; /*!< Maximum number of RDATA items. */ + + /*! \brief Wire format specification for the RDATA. */ + uint8_t wireformat[KNOT_MAX_RDATA_ITEMS]; + + /*! \brief Zone file format specification for the RDATA. */ + uint8_t zoneformat[KNOT_MAX_RDATA_ITEMS]; + + bool fixed_items; /*!< Has fixed number of RDATA items? */ +}; + +/*! \brief Structure holding RR descriptor. */ +typedef struct knot_rrtype_descriptor knot_rrtype_descriptor_t; + +/*! + * \brief Gets RR descriptor for given RR type. + * + * \param type Code of RR type whose descriptor should be returned. + * + * \return RR descriptor for given type code, NULL descriptor if + * unknown type. + * + * \todo Change return value to const. + */ +knot_rrtype_descriptor_t *knot_rrtype_descriptor_by_type(uint16_t type); + +/*! + * \brief Gets RR descriptor for given RR name. + * + * \param name Mnemonic of RR type whose descriptor should be returned. + * + * \return RR descriptor for given name, NULL descriptor if + * unknown type. + * + * \todo Change return value to const. + */ +knot_rrtype_descriptor_t *knot_rrtype_descriptor_by_name(const char *name); + +/*! + * \brief Converts numeric type representation to mnemonic string. + * + * \param rrtype Type RR type code to be converted. + * + * \return Mnemonic string if found, str(TYPE[rrtype]) otherwise. + */ +const char *knot_rrtype_to_string(uint16_t rrtype); + +/*! + * \brief Converts mnemonic string representation of a type to numeric one. + * + * \param name Mnemonic string to be converted. + * + * \return Correct code if found, 0 otherwise. + */ +uint16_t knot_rrtype_from_string(const char *name); + +/*! + * \brief Converts numeric class representation to string one. + * + * \param rrclass Class code to be converted. + * + * \return String represenation of class if found, + * str(CLASS[rrclass]) otherwise. + */ +const char *knot_rrclass_to_string(uint16_t rrclass); + +/*! + * \brief Converts string representation of a class to numeric one. + * + * \param name Class string to be converted. + * + * \return Correct code if found, 0 otherwise. + */ +uint16_t knot_rrclass_from_string(const char *name); + +/*! + * \brief Returns size of wireformat type in bytes. + * + * \param wire_type Wireformat type. + * + * \retval Size of given type on success. + * \retval 0 on unknown type or type that has no length. + */ +size_t knot_wireformat_size(unsigned int wire_type); + +int knot_rrtype_is_metatype(uint16_t type); + +#endif /* _KNOT_DESCRIPTOR_H_ */ + +/*! @} */ + diff --git a/src/libknot/util/error.h b/src/libknot/util/error.h new file mode 100644 index 0000000..da45151 --- /dev/null +++ b/src/libknot/util/error.h @@ -0,0 +1,87 @@ +/*! + * \file error.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Error codes and function for getting error message. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_ERROR_H_ +#define _KNOT_ERROR_H_ + +#include "common/errors.h" + +/*! \brief Error codes used in the library. */ +enum knot_error { + KNOT_EOK = 0, /*!< OK */ + + /* TSIG errors. */ + KNOT_TSIG_EBADSIG = -16, /*!< Failed to verify TSIG MAC. */ + KNOT_TSIG_EBADKEY = -17, /*!< TSIG key not recognized or invalid. */ + KNOT_TSIG_EBADTIME = -18,/*!< TSIG signing time out of range. */ + + /* General errors. */ + KNOT_ERROR = -10000, /*!< General error. */ + KNOT_ENOMEM, /*!< Not enough memory. */ + KNOT_ENOTSUP, /*!< Operation not supported. */ + KNOT_EAGAIN, /*!< OS lacked necessary resources. */ + KNOT_ERANGE, /*!< Value is out of range. */ + KNOT_EBADARG, /*!< Wrong argument supported. */ + KNOT_EFEWDATA, /*!< Not enough data to parse. */ + KNOT_ESPACE, /*!< Not enough space provided. */ + KNOT_EMALF, /*!< Malformed data. */ + KNOT_ECRYPTO, /*!< Error in crypto library. */ + KNOT_ENSEC3PAR, /*!< Missing or wrong NSEC3PARAM record. */ + KNOT_EBADZONE, /*!< Domain name does not belong to the zone. */ + KNOT_EHASH, /*!< Error in hash table. */ + KNOT_EZONEIN, /*!< Error inserting zone. */ + KNOT_ENOZONE, /*!< No such zone found. */ + KNOT_ENONODE, /*!< No such node in zone found. */ + KNOT_ENORRSET, /*!< No such RRSet found. */ + KNOT_EDNAMEPTR, /*!< Domain name pointer larger than allowed. */ + KNOT_EPAYLOAD, /*!< Payload in OPT RR larger than max wire size. */ + KNOT_ECRC, /*!< Wrong dump CRC. */ + KNOT_EPREREQ, /*!< UPDATE prerequisity not met. */ + KNOT_ENOXFR, /*!< Transfer was not sent. */ + KNOT_ENOIXFR, /*!< Transfer is not IXFR (is in AXFR format). */ + KNOT_EXFRREFUSED, /*!< Zone transfer refused by the server. */ + KNOT_ECONN, /*!< Connection reset. */ + KNOT_ERROR_COUNT = 30 +}; + +/*! \brief Table linking error messages to error codes. */ +extern const error_table_t knot_error_msgs[KNOT_ERROR_COUNT]; + +/*! + * \brief Returns error message for the given error code. + * + * \param code Error code. + * + * \return String containing the error message. + */ +static inline const char *knot_strerror(int code) +{ + return error_to_str((const error_table_t*)knot_error_msgs, code); +} + +#endif /* _KNOT_ERROR_H_ */ + +/*! @} */ diff --git a/src/libknot/util/libknot_error.c b/src/libknot/util/libknot_error.c new file mode 100644 index 0000000..bc2bed2 --- /dev/null +++ b/src/libknot/util/libknot_error.c @@ -0,0 +1,53 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include "util/error.h" +#include "util/utils.h" + +#include "common/errors.h" + +const error_table_t knot_error_msgs[KNOT_ERROR_COUNT] = { + {KNOT_EOK, "OK"}, + {KNOT_ERROR, "General error."}, + {KNOT_ENOMEM, "Not enough memory."}, + {KNOT_ENOTSUP, "Operation not supported."}, + {KNOT_EAGAIN, "OS lacked necessary resources."}, + {KNOT_ERANGE, "Value is out of range."}, + {KNOT_EBADARG, "Wrong argument supported."}, + {KNOT_EFEWDATA, "Not enough data to parse."}, + {KNOT_ESPACE, "Not enough space provided."}, + {KNOT_EMALF, "Malformed data."}, + {KNOT_ECRYPTO, "Error in crypto library."}, + {KNOT_ENSEC3PAR, "Missing or wrong NSEC3PARAM record."}, + {KNOT_EBADZONE, "Domain name does not belong to the given zone."}, + {KNOT_EHASH, "Error in hash table."}, + {KNOT_EZONEIN, "Error inserting zone."}, + {KNOT_ENOZONE, "No such zone found."}, + {KNOT_ENONODE, "No such node in zone found."}, + {KNOT_ENORRSET, "No such RRSet found."}, + {KNOT_EDNAMEPTR, "Domain name pointer larger than allowed."}, + {KNOT_EPAYLOAD, "Payload in OPT RR larger than max wire size."}, + {KNOT_ECRC, "CRC check failed."}, + {KNOT_EPREREQ, "UPDATE prerequisity not met."}, + {KNOT_ENOXFR, "Transfer was not sent."}, + {KNOT_ENOIXFR, "Transfer is not IXFR (is in AXFR format)."}, + {KNOT_EXFRREFUSED, "Zone transfer refused by the server."}, + {KNOT_TSIG_EBADSIG, "Failed to verify TSIG MAC." }, + {KNOT_TSIG_EBADKEY, "TSIG key not recognized or invalid." }, + {KNOT_TSIG_EBADTIME, "TSIG signing time out of range." }, + {KNOT_ECONN, "Connection reset."}, + {KNOT_ERROR, 0} +}; diff --git a/src/libknot/util/tolower.c b/src/libknot/util/tolower.c new file mode 100644 index 0000000..d71c467 --- /dev/null +++ b/src/libknot/util/tolower.c @@ -0,0 +1,276 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include "util/tolower.h" + +const uint8_t char_table[CHAR_TABLE_SIZE] = { + '\x00', + '\x01', + '\x02', + '\x03', + '\x04', + '\x05', + '\x06', + '\x07', + '\x08', + '\x09', + '\x0A', + '\x0B', + '\x0C', + '\x0D', + '\x0E', + '\x0F', + '\x10', + '\x11', + '\x12', + '\x13', + '\x14', + '\x15', + '\x16', + '\x17', + '\x18', + '\x19', + '\x1A', + '\x1B', + '\x1C', + '\x1D', + '\x1E', + '\x1F', + '\x20', + '\x21', /* ! */ + '\x22', /* " */ + '\x23', /* # */ + '\x24', /* $ */ + '\x25', /* % */ + '\x26', /* & */ + '\x27', /* ' */ + '\x28', /* ( */ + '\x29', /* ) */ + '\x2A', /* * */ + '\x2B', /* + */ + '\x2C', /* , */ + '\x2D', /* - */ + '\x2E', /* . */ + '\x2F', /* / */ + '\x30', /* 0 */ + '\x31', /* 1 */ + '\x32', /* 2 */ + '\x33', /* 3 */ + '\x34', /* 4 */ + '\x35', /* 5 */ + '\x36', /* 6 */ + '\x37', /* 7 */ + '\x38', /* 8 */ + '\x39', /* 9 */ + '\x3A', /* : */ + '\x3B', /* ; */ + '\x3C', /* < */ + '\x3D', /* = */ + '\x3E', /* > */ + '\x3F', /* ? */ + '\x40', /* @ */ + '\x61', /* A */ + '\x62', /* B */ + '\x63', /* C */ + '\x64', /* D */ + '\x65', /* E */ + '\x66', /* F */ + '\x67', /* G */ + '\x68', /* H */ + '\x69', /* I */ + '\x6A', /* J */ + '\x6B', /* K */ + '\x6C', /* L */ + '\x6D', /* M */ + '\x6E', /* N */ + '\x6F', /* O */ + '\x70', /* P */ + '\x71', /* Q */ + '\x72', /* R */ + '\x73', /* S */ + '\x74', /* T */ + '\x75', /* U */ + '\x76', /* V */ + '\x77', /* W */ + '\x78', /* X */ + '\x79', /* Y */ + '\x7A', /* Z */ + '\x5B', /* [ */ + '\x5C', /* \ */ + '\x5D', /* ] */ + '\x5E', /* ^ */ + '\x5F', /* _ */ + '\x60', /* ` */ + '\x61', /* a */ + '\x62', /* b */ + '\x63', /* c */ + '\x64', /* d */ + '\x65', /* e */ + '\x66', /* f */ + '\x67', /* g */ + '\x68', /* h */ + '\x69', /* i */ + '\x6A', /* j */ + '\x6B', /* k */ + '\x6C', /* l */ + '\x6D', /* m */ + '\x6E', /* n */ + '\x6F', /* o */ + '\x70', /* p */ + '\x71', /* q */ + '\x72', /* r */ + '\x73', /* s */ + '\x74', /* t */ + '\x75', /* u */ + '\x76', /* v */ + '\x77', /* w */ + '\x78', /* x */ + '\x79', /* y */ + '\x7A', /* z */ + '\x7B', /* { */ + '\x7C', /* | */ + '\x7D', /* } */ + '\x7E', /* ~ */ + '\x7F', + '\x80', + '\x81', + '\x82', + '\x83', + '\x84', + '\x85', + '\x86', + '\x87', + '\x88', + '\x89', + '\x8A', + '\x8B', + '\x8C', + '\x8D', + '\x8E', + '\x8F', + '\x90', + '\x91', + '\x92', + '\x93', + '\x94', + '\x95', + '\x96', + '\x97', + '\x98', + '\x99', + '\x9A', + '\x9B', + '\x9C', + '\x9D', + '\x9E', + '\x9F', + '\xA0', + '\xA1', + '\xA2', + '\xA3', + '\xA4', + '\xA5', + '\xA6', + '\xA7', + '\xA8', + '\xA9', + '\xAA', + '\xAB', + '\xAC', + '\xAD', + '\xAE', + '\xAF', + '\xB0', + '\xB1', + '\xB2', + '\xB3', + '\xB4', + '\xB5', + '\xB6', + '\xB7', + '\xB8', + '\xB9', + '\xBA', + '\xBB', + '\xBC', + '\xBD', + '\xBE', + '\xBF', + '\xC0', + '\xC1', + '\xC2', + '\xC3', + '\xC4', + '\xC5', + '\xC6', + '\xC7', + '\xC8', + '\xC9', + '\xCA', + '\xCB', + '\xCC', + '\xCD', + '\xCE', + '\xCF', + '\xD0', + '\xD1', + '\xD2', + '\xD3', + '\xD4', + '\xD5', + '\xD6', + '\xD7', + '\xD8', + '\xD9', + '\xDA', + '\xDB', + '\xDC', + '\xDD', + '\xDE', + '\xDF', + '\xE0', + '\xE1', + '\xE2', + '\xE3', + '\xE4', + '\xE5', + '\xE6', + '\xE7', + '\xE8', + '\xE9', + '\xEA', + '\xEB', + '\xEC', + '\xED', + '\xEE', + '\xEF', + '\xF0', + '\xF1', + '\xF2', + '\xF3', + '\xF4', + '\xF5', + '\xF6', + '\xF7', + '\xF8', + '\xF9', + '\xFA', + '\xFB', + '\xFC', + '\xFD', + '\xFE', + '\xFF', +}; diff --git a/src/libknot/util/tolower.h b/src/libknot/util/tolower.h new file mode 100644 index 0000000..6b9e98c --- /dev/null +++ b/src/libknot/util/tolower.h @@ -0,0 +1,57 @@ +/*! + * \file tolower.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Table for converting ASCII characters to lowercase. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_TOLOWER_H_ +#define _KNOT_TOLOWER_H_ + +#include <stdint.h> + +/*! \brief Size of the character conversion table. */ +#define KNOT_CHAR_TABLE_SIZE 256 + +enum { + /*! \brief Size of the character conversion table. */ + CHAR_TABLE_SIZE = KNOT_CHAR_TABLE_SIZE +}; + +/*! \brief Character table mapping uppercase letters to lowercase. */ +extern const uint8_t char_table[CHAR_TABLE_SIZE]; + +/*! + * \brief Converts ASCII character to lowercase. + * + * \param c ASCII character code. + * + * \return \a c converted to lowercase (or \a c if not applicable). + */ +static inline uint8_t knot_tolower(uint8_t c) { +#if KNOT_CHAR_TABLE_SIZE < 256 + assert(c < CHAR_TABLE_SIZE); +#endif + return char_table[c]; +} + +#endif /* _KNOT_TOLOWER_H_ */ diff --git a/src/libknot/util/utils.c b/src/libknot/util/utils.c new file mode 100644 index 0000000..17b33a7 --- /dev/null +++ b/src/libknot/util/utils.c @@ -0,0 +1,127 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <string.h> +#include <pthread.h> +#include <fcntl.h> +#include <unistd.h> + +#include "common.h" +#include "util/utils.h" +#include "common/WELL1024a.h" + +/*----------------------------------------------------------------------------*/ + +knot_lookup_table_t *knot_lookup_by_name(knot_lookup_table_t *table, + const char *name) +{ + while (table->name != NULL) { + if (strcasecmp(name, table->name) == 0) { + return table; + } + table++; + } + + return NULL; +} + +/*----------------------------------------------------------------------------*/ + +knot_lookup_table_t *knot_lookup_by_id(knot_lookup_table_t *table, + int id) +{ + while (table->name != NULL) { + if (table->id == id) { + return table; + } + table++; + } + + return NULL; +} + +/*----------------------------------------------------------------------------*/ + +size_t knot_strlcpy(char *dst, const char *src, size_t size) +{ + char *d = dst; + const char *s = src; + size_t n = size; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) { + break; + } + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (size != 0) { + *d = '\0'; /* NUL-terminate dst */ + } + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} + +/*! \brief TLS key for rand seed. */ +static pthread_key_t _qr_key; +static pthread_once_t _qr_once = PTHREAD_ONCE_INIT; + +/*! \brief TLS key initializer. */ +static void _qr_init() +{ + (void) pthread_key_create(&_qr_key, NULL); + (void) pthread_setspecific(_qr_key, (void*)time(0)); +} + +size_t knot_quick_rand() +{ + (void) pthread_once(&_qr_once, _qr_init); + size_t x = (size_t)pthread_getspecific(_qr_key); + + /* Numerical Recipes in C. + * The Art of Scientific Computing, 2nd Edition, + * 1992, ISBN 0-521-43108-5. + * Page 284. + */ + x = 1664525L * x + 1013904223L; + (void) pthread_setspecific(_qr_key, (void*)x); + return x; +} + +uint16_t knot_random_id() +{ + return (uint16_t)(tls_rand() * ((uint16_t)~0)); +} + +struct flock* knot_file_lock(short type, short whence) +{ + static struct flock ret; + ret.l_type = type; + ret.l_start = 0; + ret.l_whence = whence; + ret.l_len = 0; + ret.l_pid = getpid(); + return &ret; +} + diff --git a/src/libknot/util/utils.h b/src/libknot/util/utils.h new file mode 100644 index 0000000..f43b8f0 --- /dev/null +++ b/src/libknot/util/utils.h @@ -0,0 +1,196 @@ +/*! + * \file utils.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Various utilities. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_UTILS_H_ +#define _KNOT_UTILS_H_ + +#include <string.h> +#include <stdint.h> +#include <stdio.h> + +/*! + * \brief A general purpose lookup table. + * + * \note Taken from NSD. + */ +struct knot_lookup_table { + int id; + const char *name; +}; + +typedef struct knot_lookup_table knot_lookup_table_t; + +/*! + * \brief Looks up the given name in the lookup table. + * + * \param table Lookup table. + * \param name Name to look up. + * + * \return Item in the lookup table with the given name or NULL if no such is + * present. + */ +knot_lookup_table_t *knot_lookup_by_name(knot_lookup_table_t *table, + const char *name); + +/*! + * \brief Looks up the given id in the lookup table. + * + * \param table Lookup table. + * \param id ID to look up. + * + * \return Item in the lookup table with the given id or NULL if no such is + * present. + */ +knot_lookup_table_t *knot_lookup_by_id(knot_lookup_table_t *table, + int id); + +/*! + * \brief Strlcpy - safe string copy function, based on FreeBSD implementation. + * + * http://www.openbsd.org/cgi-bin/cvsweb/src/lib/libc/string/ + * + * \param dst Destination string. + * \param src Source string. + * \param size How many characters to copy - 1. + * + * \return strlen(src), if retval >= siz, truncation occurred. + */ +size_t knot_strlcpy(char *dst, const char *src, size_t size); + +/* + * Writing / reading arbitrary data to / from wireformat. + */ + +/*! + * \brief Reads 2 bytes from the wireformat data. + * + * \param pos Data to read the 2 bytes from. + * + * \return The 2 bytes read, in inverse endian. + */ +static inline uint16_t knot_wire_read_u16(const uint8_t *pos) +{ + return (pos[0] << 8) | pos[1]; +} + +/*! + * \brief Reads 4 bytes from the wireformat data. + * + * \param pos Data to read the 4 bytes from. + * + * \return The 4 bytes read, in inverse endian. + */ +static inline uint32_t knot_wire_read_u32(const uint8_t *pos) +{ + return (pos[0] << 24) | (pos[1] << 16) | (pos[2] << 8) | pos[3]; +} + +/*! + * \brief Reads 6 bytes from the wireformat data. + * + * \param pos Data to read the 6 bytes from. + * + * \return The 6 bytes read, in inverse endian. + */ +static inline uint64_t knot_wire_read_u48(const uint8_t *pos) +{ + return ((uint64_t)(pos[0]) << 40) | ((uint64_t)(pos[1]) << 32) | (pos[2] << 24) | + (pos[3] << 16) | (pos[4] << 8) | pos[5]; +} + +/*! + * \brief Writes 2 bytes in wireformat. + * + * The endian of the data is inverted. + * + * \param pos Position where to put the 2 bytes. + * \param data Data to put. + */ +static inline void knot_wire_write_u16(uint8_t *pos, uint16_t data) +{ + pos[0] = (uint8_t)((data >> 8) & 0xff); + pos[1] = (uint8_t)(data & 0xff); +} + +/*! + * \brief Writes 4 bytes in wireformat. + * + * The endian of the data is inverted. + * + * \param pos Position where to put the 4 bytes. + * \param data Data to put. + */ +static inline void knot_wire_write_u32(uint8_t *pos, uint32_t data) +{ + pos[0] = (uint8_t)((data >> 24) & 0xff); + pos[1] = (uint8_t)((data >> 16) & 0xff); + pos[2] = (uint8_t)((data >> 8) & 0xff); + pos[3] = (uint8_t)(data & 0xff); +} + +/*! + * \brief Writes 6 bytes in wireformat. + * + * The endian of the data is inverted. + * + * \param pos Position where to put the 4 bytes. + * \param data Data to put. + */ +static inline void knot_wire_write_u48(uint8_t *pos, uint64_t data) +{ + pos[0] = (uint8_t)((data >> 40) & 0xff); + pos[1] = (uint8_t)((data >> 32) & 0xff); + pos[2] = (uint8_t)((data >> 24) & 0xff); + pos[3] = (uint8_t)((data >> 16) & 0xff); + pos[4] = (uint8_t)((data >> 8) & 0xff); + pos[5] = (uint8_t)(data & 0xff); +} + +/*! + * \brief Linear congruential generator. + * + * Simple pseudorandom generator for general purpose. + * \warning Do not use for cryptography. + * \return Random number <0, (size_t)~0> + */ +size_t knot_quick_rand(); + +uint16_t knot_random_id(); + +/*! + * \brief Helper function for simple locking. + * + * \param type Type of lock. + * \param type Starting position of lock. + * + * \return Locking structure. + */ +struct flock* knot_file_lock(short type, short whence); + +#endif /* _KNOT_UTILS_H_ */ + +/*! @} */ + diff --git a/src/libknot/util/wire.h b/src/libknot/util/wire.h new file mode 100644 index 0000000..0a24ff1 --- /dev/null +++ b/src/libknot/util/wire.h @@ -0,0 +1,926 @@ +/*! + * \file wire.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Functions for manipulating and parsing raw data in DNS packets. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_WIRE_H_ +#define _KNOT_WIRE_H_ + +#include <stdint.h> +#include <assert.h> + +#include "util/utils.h" + +/*! \brief Offset of DNS header fields in wireformat. */ +enum knot_wire_offsets { + KNOT_WIRE_OFFSET_ID = 0, + KNOT_WIRE_OFFSET_FLAGS1 = 2, + KNOT_WIRE_OFFSET_FLAGS2 = 3, + KNOT_WIRE_OFFSET_QDCOUNT = 4, + KNOT_WIRE_OFFSET_ANCOUNT = 6, + KNOT_WIRE_OFFSET_NSCOUNT = 8, + KNOT_WIRE_OFFSET_ARCOUNT = 10 +}; + +/*! \brief Minimum size for some parts of the DNS packet. */ +enum knot_wire_sizes { + KNOT_WIRE_HEADER_SIZE = 12, + KNOT_WIRE_QUESTION_MIN_SIZE = 5, + KNOT_WIRE_RR_MIN_SIZE = 11 +}; + +/* + * Packet header manipulation functions. + */ + +/*! + * \brief Returns the ID from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return DNS packet ID. + */ +static inline uint16_t knot_wire_get_id(const uint8_t *packet) +{ + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_ID); +} + +/*! + * \brief Sets the ID to the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param id DNS packet ID. + */ +static inline void knot_wire_set_id(uint8_t *packet, uint16_t id) +{ + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ID, id); +} + +/*! + * \brief Returns the first byte of flags from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return First byte of DNS flags. + */ +static inline uint8_t knot_wire_get_flags1(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS1); +} + +/*! + * \brief Sets the first byte of flags to the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param flags1 First byte of the DNS flags. + */ +static inline uint8_t knot_wire_set_flags1(uint8_t *packet, uint8_t flags1) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) = flags1; +} + +/*! + * \brief Returns the second byte of flags from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Second byte of DNS flags. + */ +static inline uint8_t knot_wire_get_flags2(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2); +} + +/*! + * \brief Sets the second byte of flags to the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param flags2 Second byte of the DNS flags. + */ +static inline uint8_t knot_wire_set_flags2(uint8_t *packet, uint8_t flags2) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) = flags2; +} + +/*! + * \brief Returns the QDCOUNT (count of Question entries) from wire format of + * the packet. + * + * \param packet Wire format of the packet. + * + * \return QDCOUNT (count of Question entries in the packet). + */ +static inline uint16_t knot_wire_get_qdcount(const uint8_t *packet) +{ + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_QDCOUNT); +} + +/*! + * \brief Sets the QDCOUNT (count of Question entries) to wire format of the + * packet. + * + * \param packet Wire format of the packet. + * \param qdcount QDCOUNT (count of Question entries in the packet). + */ +static inline void knot_wire_set_qdcount(uint8_t *packet, uint16_t qdcount) +{ + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_QDCOUNT, qdcount); +} + +/*! + * \brief Returns the ANCOUNT (count of Answer entries) from wire format of + * the packet. + * + * \param packet Wire format of the packet. + * + * \return ANCOUNT (count of Answer entries in the packet). + */ +static inline uint16_t knot_wire_get_ancount(const uint8_t *packet) +{ + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_ANCOUNT); +} + +/*! + * \brief Sets the ANCOUNT (count of Answer entries) to wire format of the + * packet. + * + * \param packet Wire format of the packet. + * \param ancount ANCOUNT (count of Answer entries in the packet). + */ +static inline void knot_wire_set_ancount(uint8_t *packet, uint16_t ancount) +{ + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ANCOUNT, ancount); +} + +/*! + * \brief Returns the NSCOUNT (count of Authority entries) from wire format of + * the packet. + * + * \param packet Wire format of the packet. + * + * \return NSCOUNT (count of Authority entries in the packet). + */ +static inline uint16_t knot_wire_get_nscount(const uint8_t *packet) +{ + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_NSCOUNT); +} + +/*! + * \brief Sets the NSCOUNT (count of Authority entries) to wire format of the + * packet. + * + * \param packet Wire format of the packet. + * \param nscount NSCOUNT (count of Authority entries in the packet). + */ +static inline void knot_wire_set_nscount(uint8_t *packet, uint16_t nscount) +{ + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_NSCOUNT, nscount); +} + +/*! + * \brief Returns the ARCOUNT (count of Additional entries) from wire format of + * the packet. + * + * \param packet Wire format of the packet. + * + * \return ARCOUNT (count of Additional entries in the packet). + */ +static inline uint16_t knot_wire_get_arcount(const uint8_t *packet) +{ + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_ARCOUNT); +} + +/*! + * \brief Sets the ARCOUNT (count of Additional entries) to wire format of the + * packet. + * + * \param packet Wire format of the packet. + * \param arcount ARCOUNT (count of Additional entries in the packet). + */ +static inline void knot_wire_set_arcount(uint8_t *packet, uint16_t arcount) +{ + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ARCOUNT, arcount); +} + +/* + * Packet header flags manipulation functions. + */ +/*! \brief Constants for DNS header flags in the first flags byte. */ +enum knot_wire_flags1_consts { + KNOT_WIRE_RD_MASK = (uint8_t)0x01U, /*!< RD bit mask. */ + KNOT_WIRE_RD_SHIFT = 0, /*!< RD bit shift. */ + KNOT_WIRE_TC_MASK = (uint8_t)0x02U, /*!< TC bit mask. */ + KNOT_WIRE_TC_SHIFT = 1, /*!< TC bit shift. */ + KNOT_WIRE_AA_MASK = (uint8_t)0x04U, /*!< AA bit mask. */ + KNOT_WIRE_AA_SHIFT = 2, /*!< AA bit shift. */ + KNOT_WIRE_OPCODE_MASK = (uint8_t)0x78U, /*!< OPCODE mask. */ + KNOT_WIRE_OPCODE_SHIFT = 3, /*!< OPCODE shift. */ + KNOT_WIRE_QR_MASK = (uint8_t)0x80U, /*!< QR bit mask. */ + KNOT_WIRE_QR_SHIFT = 7 /*!< QR bit shift. */ +}; + +/*! \brief Constants for DNS header flags in the second flags byte. */ +enum knot_wire_flags2_consts { + KNOT_WIRE_RCODE_MASK = (uint8_t)0x0fU, /*!< RCODE mask. */ + KNOT_WIRE_RCODE_SHIFT = 0, /*!< RCODE shift. */ + KNOT_WIRE_CD_MASK = (uint8_t)0x10U, /*!< CD bit mask. */ + KNOT_WIRE_CD_SHIFT = 4, /*!< CD bit shift. */ + KNOT_WIRE_AD_MASK = (uint8_t)0x20U, /*!< AD bit mask. */ + KNOT_WIRE_AD_SHIFT = 5, /*!< AD bit shift. */ + KNOT_WIRE_Z_MASK = (uint8_t)0x40U, /*!< Zero bit mask. */ + KNOT_WIRE_Z_SHIFT = 6, /*!< Zero bit shift. */ + KNOT_WIRE_RA_MASK = (uint8_t)0x80U, /*!< RA bit mask. */ + KNOT_WIRE_RA_SHIFT = 7 /*!< RA bit shift. */ +}; + +/* + * Functions for getting / setting / clearing flags and codes directly in packet + */ + +/*! + * \brief Returns the RD bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the RD bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_rd(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Sets the RD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_rd(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Clears the RD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_flags_clear_rd(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Returns the TC bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the TC bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_tc(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Sets the TC bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_tc(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Clears the TC bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_tc(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Returns the AA bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the AA bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_aa(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Sets the AA bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_aa(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Clears the AA bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_aa(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Returns the OPCODE from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return OPCODE of the packet. + */ +static inline uint8_t knot_wire_get_opcode(const uint8_t *packet) +{ + return (*(packet + KNOT_WIRE_OFFSET_FLAGS1) + & KNOT_WIRE_OPCODE_MASK) >> KNOT_WIRE_OPCODE_SHIFT; +} + +/*! + * \brief Sets the OPCODE in the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param opcode OPCODE to set. + */ +static inline void knot_wire_set_opcode(uint8_t *packet, short opcode) +{ + uint8_t *flags1 = packet + KNOT_WIRE_OFFSET_FLAGS1; + *flags1 = (*flags1 & ~KNOT_WIRE_OPCODE_MASK) + | ((opcode) << KNOT_WIRE_OPCODE_SHIFT); +} + +/*! + * \brief Returns the QR bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the QR bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_qr(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Sets the QR bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_qr(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Clears the QR bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_qr(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Returns the RCODE from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return RCODE of the packet. + */ +static inline uint8_t knot_wire_get_rcode(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) + & KNOT_WIRE_RCODE_MASK; +} + +/*! + * \brief Sets the RCODE in the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param rcode RCODE to set. + */ +static inline void knot_wire_set_rcode(uint8_t *packet, short rcode) +{ + uint8_t *flags2 = packet + KNOT_WIRE_OFFSET_FLAGS2; + *flags2 = (*flags2 & ~KNOT_WIRE_RCODE_MASK) | (rcode); +} + +/*! + * \brief Returns the CD bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the CD bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_cd(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Sets the CD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_cd(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Clears the CD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_cd(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Returns the AD bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the AD bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_ad(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Sets the AD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_ad(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Clears the AD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_ad(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Returns the Zero bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the Zero bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_z(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Sets the Zero bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_z(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Clears the Zero bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_z(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Returns the RA bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the RA bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_ra(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_RA_MASK; +} + +/*! + * \brief Sets the RA bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_ra(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_RA_MASK; +} + +/*! + * \brief Clears the RA bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_ra(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_RA_MASK; +} + +/* + * Functions for getting / setting / clearing flags in flags variable + */ + +/*! + * \brief Returns the RD bit from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return Flags byte with only the RD bit according to its setting in + * \a flags1. + */ +static inline uint8_t knot_wire_flags_get_rd(uint8_t flags1) +{ + return flags1 & KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Sets the RD bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_set_rd(uint8_t *flags1) +{ + *flags1 |= KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Clears the RD bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_flags_clear_rd(uint8_t *flags1) +{ + *flags1 &= ~KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Returns the TC bit from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return Flags byte with only the TC bit according to its setting in + * \a flags1. + */ +static inline uint8_t knot_wire_flags_get_tc(uint8_t flags1) +{ + return flags1 & KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Sets the TC bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_set_tc(uint8_t *flags1) +{ + *flags1 |= KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Clears the TC bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_tc(uint8_t *flags1) +{ + *flags1 &= ~KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Returns the AA bit from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return Flags byte with only the AA bit according to its setting in + * \a flags1. + */ +static inline uint8_t knot_wire_flags_get_aa(uint8_t flags1) +{ + return flags1 & KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Sets the AA bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_set_aa(uint8_t *flags1) +{ + *flags1 |= KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Clears the AA bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_aa(uint8_t *flags1) +{ + *flags1 &= ~KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Returns the OPCODE from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return OPCODE + */ +static inline uint8_t knot_wire_flags_get_opcode(uint8_t flags1) +{ + return (flags1 & KNOT_WIRE_OPCODE_MASK) + >> KNOT_WIRE_OPCODE_SHIFT; +} + +/*! + * \brief Sets the OPCODE in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * \param opcode OPCODE to set. + */ +static inline void knot_wire_flags_set_opcode(uint8_t *flags1, short opcode) +{ + *flags1 = (*flags1 & ~KNOT_WIRE_OPCODE_MASK) + | ((opcode) << KNOT_WIRE_OPCODE_SHIFT); +} + +/*! + * \brief Returns the QR bit from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return Flags byte with only the QR bit according to its setting in + * \a flags1. + */ +static inline uint8_t knot_wire_flags_get_qr(uint8_t flags1) +{ + return flags1 & KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Sets the QR bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_set_qr(uint8_t *flags1) +{ + *flags1 |= KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Clears the QR bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_qr(uint8_t *flags1) +{ + *flags1 &= ~KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Returns the RCODE from the second byte of flags. + * + * \param flags2 First byte of DNS header flags. + * + * \return RCODE + */ +static inline uint8_t knot_wire_flags_get_rcode(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_RCODE_MASK; +} + +/*! + * \brief Sets the RCODE in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * \param rcode RCODE to set. + */ +static inline void knot_wire_flags_set_rcode(uint8_t *flags2, short rcode) +{ + *flags2 = (*flags2 & ~KNOT_WIRE_RCODE_MASK) | (rcode); +} + +/*! + * \brief Returns the CD bit from the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * + * \return Flags byte with only the CD bit according to its setting in + * \a flags2. + */ +static inline uint8_t knot_wire_flags_get_cd(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Sets the CD bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_set_cd(uint8_t *flags2) +{ + *flags2 |= KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Clears the CD bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_cd(uint8_t *flags2) +{ + *flags2 &= ~KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Returns the AD bit from the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * + * \return Flags byte with only the AD bit according to its setting in + * \a flags2. + */ +static inline uint8_t knot_wire_flags_get_ad(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Sets the AD bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_set_ad(uint8_t *flags2) +{ + *flags2 |= KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Clears the AD bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_ad(uint8_t *flags2) +{ + *flags2 &= ~KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Returns the Zero bit from the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * + * \return Flags byte with only the Zero bit according to its setting in + * \a flags2. + */ +static inline uint8_t knot_wire_flags_get_z(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Sets the Zero bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_set_z(uint8_t *flags2) +{ + *flags2 |= KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Clears the Zero bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_z(uint8_t *flags2) +{ + *flags2 &= ~KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Returns the RA bit from the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * + * \return Flags byte with only the RA bit according to its setting in + * \a flags2. + */ +static inline uint8_t knot_wire_flags_get_ra(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_RA_MASK; +} + +/*! + * \brief Sets the RA bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_set_ra(uint8_t *flags2) +{ + *flags2 |= KNOT_WIRE_RA_MASK; +} + +/*! + * \brief Clears the RA bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_ra(uint8_t *flags2) +{ + *flags2 &= ~KNOT_WIRE_RA_MASK; +} + +/* + * Pointer manipulation + */ + +enum knot_wire_pointer_consts { + /*! \brief DNS packet pointer designation (first two bits set to 1). */ + KNOT_WIRE_PTR = (uint8_t)0xc0U +}; + +/*! + * \brief Creates a DNS packet pointer and stores it in wire format. + * + * \param pos Position where tu put the pointer. + * \param ptr Relative position of the item to which the pointer should point in + * the wire format of the packet. + */ +static inline void knot_wire_put_pointer(uint8_t *pos, size_t ptr) +{ + uint16_t p = ptr; + knot_wire_write_u16(pos, p); + assert((pos[0] & KNOT_WIRE_PTR) == 0); + pos[0] |= KNOT_WIRE_PTR; +} + +static inline int knot_wire_is_pointer(const uint8_t *pos) +{ + return ((pos[0] & KNOT_WIRE_PTR) != 0); +} + +static inline size_t knot_wire_get_pointer(const uint8_t *pos) +{ + /*! \todo memcpy() is not needed, may be directly assigned. */ + uint16_t p = 0; + memcpy(&p, pos, 2); + p &= ~KNOT_WIRE_PTR; + + uint16_t p2 = knot_wire_read_u16((uint8_t *)&p); + return p2; +} + +#endif /* _KNOT_WIRE_H_ */ + +/*! @} */ diff --git a/src/libknot/zone/dname-table.c b/src/libknot/zone/dname-table.c new file mode 100644 index 0000000..c41b4bd --- /dev/null +++ b/src/libknot/zone/dname-table.c @@ -0,0 +1,310 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <stddef.h> + +#include "zone/dname-table.h" +#include "util/error.h" + +/*!< Tree functions. */ +TREE_DEFINE(dname_table_node, avl); + +struct knot_dname_table_fnc_data { + void (*func)(knot_dname_t *dname, void *data); + void *data; +}; + +static void knot_dname_table_apply(struct dname_table_node *node, void *data) +{ + assert(data != NULL); + assert(node != NULL); + struct knot_dname_table_fnc_data *d = + (struct knot_dname_table_fnc_data *)data; + d->func(node->dname, d->data); +} + +/*! + * \brief Comparison function to be used with tree. + * + * \param n1 First dname to be compared. + * \param n2 Second dname to be compared. + * + * \return strncmp of dname's wireformats. + */ +static int compare_dname_table_nodes(struct dname_table_node *n1, + struct dname_table_node *n2) +{ + assert(n1 && n2); + return (strncmp((char *)n1->dname->name, (char *)n2->dname->name, + (n1->dname->size < n2->dname->size) ? + (n1->dname->size):(n2->dname->size))); +} + +/*! + * \brief Deletes tree node along with its domain name. + * + * \param node Node to be deleted. + * \param data If <> 0, dname in the node will be freed as well. + */ +static void delete_dname_table_node(struct dname_table_node *node, void *data) +{ + if ((ssize_t)data == 1) { + knot_dname_release(node->dname); + } else if ((ssize_t)data == 2) { + knot_dname_free(&node->dname); + } + + /*!< \todo it would be nice to set pointers to NULL. */ + free(node); +} + +static void knot_dname_table_delete_subtree(struct dname_table_node *root) +{ + if (root == NULL) { + return; + } + + knot_dname_table_delete_subtree(root->avl.avl_left); + knot_dname_table_delete_subtree(root->avl.avl_right); + free(root); +} + +static int knot_dname_table_copy_node(const struct dname_table_node *from, + struct dname_table_node **to) +{ + if (from == NULL) { + return KNOT_EOK; + } + + *to = (struct dname_table_node *) + malloc(sizeof(struct dname_table_node)); + if (*to == NULL) { + return KNOT_ENOMEM; + } + memset(*to, 0, sizeof(struct dname_table_node)); + + (*to)->dname = from->dname; + knot_dname_retain((*to)->dname); + (*to)->avl.avl_height = from->avl.avl_height; + + int ret = knot_dname_table_copy_node(from->avl.avl_left, + &(*to)->avl.avl_left); + if (ret != KNOT_EOK) { + return ret; + } + + ret = knot_dname_table_copy_node(from->avl.avl_right, + &(*to)->avl.avl_right); + if (ret != KNOT_EOK) { + knot_dname_table_delete_subtree((*to)->avl.avl_left); + (*to)->avl.avl_left = NULL; + return ret; + } + + return KNOT_EOK; +} + +knot_dname_table_t *knot_dname_table_new() +{ + knot_dname_table_t *ret = malloc(sizeof(knot_dname_table_t)); + CHECK_ALLOC_LOG(ret, NULL); + + ret->tree = malloc(sizeof(table_tree_t)); + if (ret->tree == NULL) { + ERR_ALLOC_FAILED; + free(ret); + return NULL; + } + + TREE_INIT(ret->tree, compare_dname_table_nodes); + + ret->id_counter = 1; + + return ret; +} + +knot_dname_t *knot_dname_table_find_dname(const knot_dname_table_t *table, + knot_dname_t *dname) +{ + if (table == NULL || dname == NULL) { + return NULL; + } + + struct dname_table_node *node = NULL; + struct dname_table_node sought; + sought.dname = dname; + + node = TREE_FIND(table->tree, dname_table_node, avl, &sought); + + if (node == NULL) { + return NULL; + } else { + /* Increase reference counter. */ + knot_dname_retain(node->dname); + + return node->dname; + } +} + +int knot_dname_table_add_dname(knot_dname_table_t *table, + knot_dname_t *dname) +{ + if (dname == NULL || table == NULL) { + return KNOT_EBADARG; + } + + /* Node for insertion has to be created */ + struct dname_table_node *node = + malloc(sizeof(struct dname_table_node)); + CHECK_ALLOC_LOG(node, KNOT_ENOMEM); + + // convert the dname to lowercase + knot_dname_to_lower(dname); + + node->dname = dname; + node->avl.avl_height = 0; + node->avl.avl_left = NULL; + node->avl.avl_right = NULL; + + node->dname->id = table->id_counter++; + assert(node->dname->id != 0); + + /* Increase reference counter. */ + knot_dname_retain(dname); + + TREE_INSERT(table->tree, dname_table_node, avl, node); + return KNOT_EOK; +} + +int knot_dname_table_add_dname_check(knot_dname_table_t *table, + knot_dname_t **dname) +{ + knot_dname_t *found_dname = NULL; + + if (table == NULL || dname == NULL || *dname == NULL) { + return KNOT_EBADARG; + } + + /* Fetch dname, need to release it later. */ + found_dname = knot_dname_table_find_dname(table ,*dname); + + if (!found_dname) { + /* Store reference in table. */ + return knot_dname_table_add_dname(table, *dname); + } else { + /*! \todo Remove the check for equality. */ + if (found_dname != *dname) { + /* Replace dname with found. */ + knot_dname_release(*dname); + *dname = found_dname; + return 1; /*! \todo Error code? */ + + } else { + + /* If the dname is already in the table, there is already + * a reference to it. + */ + knot_dname_release(found_dname); + } + } + + return KNOT_EOK; +} + +int knot_dname_table_shallow_copy(knot_dname_table_t *from, + knot_dname_table_t *to) +{ + to->id_counter = from->id_counter; + + if (to->tree == NULL) { + to->tree = malloc(sizeof(table_tree_t)); + if (to->tree == NULL) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + TREE_INIT(to->tree, compare_dname_table_nodes); + } + + return knot_dname_table_copy_node(from->tree->th_root, + &to->tree->th_root); +} + +void knot_dname_table_free(knot_dname_table_t **table) +{ + if (table == NULL || *table == NULL) { + return; + } + + /* Walk the tree and free each node, but not the dnames. */ + TREE_POST_ORDER_APPLY((*table)->tree, dname_table_node, avl, + delete_dname_table_node, 0); + + free((*table)->tree); + + free(*table); + *table = NULL; +} + +void knot_dname_table_deep_free(knot_dname_table_t **table) +{ + if (table == NULL || *table == NULL) { + return; + } + + /* Walk the tree and free each node, but free the dnames. */ + TREE_POST_ORDER_APPLY((*table)->tree, dname_table_node, avl, + delete_dname_table_node, (void *) 1); + + free((*table)->tree); + + free(*table); + *table = NULL; +} + +void knot_dname_table_destroy(knot_dname_table_t **table) +{ + if (table == NULL || *table == NULL) { + return; + } + + /* Walk the tree and free each node, but free the dnames. */ + TREE_POST_ORDER_APPLY((*table)->tree, dname_table_node, avl, + delete_dname_table_node, (void *) 2); + + free((*table)->tree); + + free(*table); + *table = NULL; +} + +void knot_dname_table_tree_inorder_apply(const knot_dname_table_t *table, + void (*applied_function)(knot_dname_t *node, + void *data), + void *data) +{ + struct knot_dname_table_fnc_data d; + d.data = data; + d.func = applied_function; + + TREE_FORWARD_APPLY(table->tree, dname_table_node, avl, + knot_dname_table_apply, &d); +} + diff --git a/src/libknot/zone/dname-table.h b/src/libknot/zone/dname-table.h new file mode 100644 index 0000000..dd86eaf --- /dev/null +++ b/src/libknot/zone/dname-table.h @@ -0,0 +1,168 @@ +/*! + * \file dname-table.h + * + * \author Jan Kadlec <jan.kadlec.@nic.cz> + * + * \brief Structures representing dname table and functions for + * manipulating these structures. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_DNAME_TABLE_H_ +#define _KNOT_DNAME_TABLE_H_ + +#include <config.h> + +#include "common/tree.h" + +#include "dname.h" +#include "common.h" + + +/*! + * \brief Structure encapsulating + */ +struct dname_table_node { + knot_dname_t *dname; /*!< Dname stored in node. */ + TREE_ENTRY(dname_table_node) avl; /*!< Tree variables. */ +}; + +/*! + * \brief Tree structure. + */ +typedef TREE_HEAD(avl, dname_table_node) table_tree_t; + +/*! + * \brief Structure holding tree together with dname ID counter. + */ +struct knot_dname_table { + unsigned int id_counter; /*!< ID counter (starts from 1) */ + table_tree_t *tree; /*!< AVL tree */ +}; + +typedef struct knot_dname_table knot_dname_table_t; + +/*! + * \brief Creates new empty domain name table. + * + * \retval Created table on success. + * \retval NULL on memory error. + */ +knot_dname_table_t *knot_dname_table_new(); + +/*! + * \brief Finds name in the domain name table. + * + * \note Reference count to dname will be incremented, caller is responsible + * for releasing it. + * + * \param table Domain name table to be searched. + * \param dname Dname to be searched. + * + * \retval Pointer to found dname when dname is present in the table. + * \retval NULL when dname is not present. + */ +knot_dname_t *knot_dname_table_find_dname(const knot_dname_table_t *table, + knot_dname_t *dname); + +/*! + * \brief Adds domain name to domain name table. + * + * \param table Domain name table to be added to. + * \param dname Domain name to be added. + * + * \warning Function does not check for duplicates! + * + * \note This function encapsulates dname in a structure and saves it to a tree. + * + * \retval KNOT_EOK on success. + * \retval KNOT_ENOMEM when memory runs out. + */ +int knot_dname_table_add_dname(knot_dname_table_t *table, + knot_dname_t *dname); + +/*! + * \brief Adds domain name to domain name table and checks for duplicates. + * + * \param table Domain name table to be added to. + * \param dname Domain name to be added. + * + * \note This function encapsulates dname in a structure and saves it to a tree. + * \note If a duplicate is found, \a dname is replaced by the name from table. + * + * \retval KNOT_EOK on success. + * \retval KNOT_ENOMEM when memory runs out. + */ +int knot_dname_table_add_dname_check(knot_dname_table_t *table, + knot_dname_t **dname); + +/*! + * \brief Creates a shallow copy of the domain name table. + * + * Expects an existing knot_dname_table_t structure to be passed via \a to, + * and fills it with the same data (domain names) as the original. Actual + * tree nodes are created, but domain names are not copied (just referenced). + * + * \param from Original domain name table. + * \param to Copy of the domain name table. + */ +int knot_dname_table_shallow_copy(knot_dname_table_t *from, + knot_dname_table_t *to); + +/*! + * \brief Frees dname table without its nodes. Sets pointer to NULL. + * + * \param table Table to be freed. + */ +void knot_dname_table_free(knot_dname_table_t **table); + +/*! + * \brief Frees dname table and all its nodes (and release dnames in the nodes) + * Sets pointer to NULL. + * + * \param table Table to be freed. + */ +void knot_dname_table_deep_free(knot_dname_table_t **table); + +/*! + * \brief Frees dname table and all its nodes (including dnames in the nodes) + * Sets pointer to NULL. + * + * \param table Table to be freed. + */ +void knot_dname_table_destroy(knot_dname_table_t **table); + +/*! + * \brief Encapsulation of domain name table tree traversal function. + * + * \param table Table containing tree to be traversed. + * \param applied_function Function to be used to process nodes. + * \param data Data to be passed to processing function. + */ +void knot_dname_table_tree_inorder_apply(const knot_dname_table_t *table, + void (*applied_function)(knot_dname_t *dname, + void *data), + void *data); + + +#endif // _KNOT_DNAME_TABLE_H_ + +/*! @} */ + diff --git a/src/libknot/zone/node.c b/src/libknot/zone/node.c new file mode 100644 index 0000000..1d2f938 --- /dev/null +++ b/src/libknot/zone/node.c @@ -0,0 +1,906 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> + +#include <urcu.h> + +#include "common.h" +#include "zone/node.h" +#include "rrset.h" +#include "util/error.h" +#include "common/skip-list.h" +#include "common/tree.h" +#include "util/debug.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Returns the delegation point flag + * + * \param flags Flags to retrieve the flag from. + * + * \return A byte with only the delegation point flag set if it was set in + * \a flags. + */ +static inline uint8_t knot_node_flags_get_deleg(uint8_t flags) +{ + return flags & KNOT_NODE_FLAGS_DELEG; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets the delegation point flag. + * + * \param flags Flags to set the flag in. + */ +static inline void knot_node_flags_set_deleg(uint8_t *flags) +{ + *flags |= KNOT_NODE_FLAGS_DELEG; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Returns the non-authoritative node flag + * + * \param flags Flags to retrieve the flag from. + * + * \return A byte with only the non-authoritative node flag set if it was set in + * \a flags. + */ +static inline uint8_t knot_node_flags_get_nonauth(uint8_t flags) +{ + return flags & KNOT_NODE_FLAGS_NONAUTH; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets the non-authoritative node flag. + * + * \param flags Flags to set the flag in. + */ +static inline void knot_node_flags_set_nonauth(uint8_t *flags) +{ + *flags |= KNOT_NODE_FLAGS_NONAUTH; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Returns the old node flag + * + * \param flags Flags to retrieve the flag from. + * + * \return A byte with only the old node flag set if it was set in \a flags. + */ +static inline uint8_t knot_node_flags_get_old(uint8_t flags) +{ + return flags & KNOT_NODE_FLAGS_OLD; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets the old node flag. + * + * \param flags Flags to set the flag in. + */ +static inline void knot_node_flags_set_new(uint8_t *flags) +{ + *flags |= KNOT_NODE_FLAGS_NEW; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Returns the new node flag + * + * \param flags Flags to retrieve the flag from. + * + * \return A byte with only the new node flag set if it was set in \a flags. + */ +static inline uint8_t knot_node_flags_get_new(uint8_t flags) +{ + return flags & KNOT_NODE_FLAGS_NEW; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets the new node flag. + * + * \param flags Flags to set the flag in. + */ +static inline void knot_node_flags_set_old(uint8_t *flags) +{ + *flags |= KNOT_NODE_FLAGS_OLD; +} + +/*----------------------------------------------------------------------------*/ + +static inline void knot_node_flags_clear_new(uint8_t *flags) +{ + *flags &= ~KNOT_NODE_FLAGS_NEW; +} + +/*----------------------------------------------------------------------------*/ + +static inline void knot_node_flags_clear_old(uint8_t *flags) +{ + *flags &= ~KNOT_NODE_FLAGS_OLD; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Compares the two keys as RR types. + * + * \note This function may be used in data structures requiring generic + * comparation function. + * + * \param key1 First RR type. + * \param key2 Second RR type. + * + * \retval 0 if \a key1 is equal to \a key2. + * \retval < 0 if \a key1 is lower than \a key2. + * \retval > 0 if \a key1 is higher than \a key2. + */ +static int compare_rrset_types(void *rr1, void *rr2) +{ + knot_rrset_t *rrset1 = (knot_rrset_t *)rr1; + knot_rrset_t *rrset2 = (knot_rrset_t *)rr2; + return ((rrset1->type > rrset2->type) ? 1 : + (rrset1->type == rrset2->type) ? 0 : -1); +} + +/*----------------------------------------------------------------------------*/ + +static int knot_node_zone_gen_is_new(const knot_node_t *node) +{ + assert(node->zone != NULL); + knot_zone_contents_t *cont = rcu_dereference(node->zone->contents); + assert(cont != NULL); + return knot_zone_contents_gen_is_new(cont); +} + +/*----------------------------------------------------------------------------*/ + +static int knot_node_zone_gen_is_old(const knot_node_t *node) +{ + assert(node->zone != NULL); + knot_zone_contents_t *cont = rcu_dereference(node->zone->contents); + assert(cont != NULL); + return knot_zone_contents_gen_is_old(cont); +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_node_new(knot_dname_t *owner, knot_node_t *parent, + uint8_t flags) +{ + knot_node_t *ret = (knot_node_t *)calloc(1, sizeof(knot_node_t)); + if (ret == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + /* Store reference to owner. */ + knot_dname_retain(owner); + ret->owner = owner; + knot_node_set_parent(ret, parent); + ret->rrset_tree = gen_tree_new(compare_rrset_types); + ret->flags = flags; + + assert(ret->children == 0); + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_add_rrset(knot_node_t *node, knot_rrset_t *rrset, + int merge) +{ + int ret = 0; + + if ((ret = (gen_tree_add(node->rrset_tree, rrset, + (merge) ? knot_rrset_merge : NULL))) < 0) { + dbg_node("Failed to add rrset to node->rrset_tree.\n"); + return KNOT_ERROR; + } + + if (ret >= 0) { + node->rrset_count += (ret > 0 ? 0 : 1); + return ret; + } else { + return KNOT_ERROR; + } +} + +/*----------------------------------------------------------------------------*/ + +const knot_rrset_t *knot_node_rrset(const knot_node_t *node, + uint16_t type) +{ + assert(node != NULL); + assert(node->rrset_tree != NULL); + knot_rrset_t rrset; + rrset.type = type; + return (const knot_rrset_t *)gen_tree_find(node->rrset_tree, &rrset); +} + +/*----------------------------------------------------------------------------*/ + +knot_rrset_t *knot_node_get_rrset(knot_node_t *node, uint16_t type) +{ + knot_rrset_t rrset; + rrset.type = type; + return (knot_rrset_t *)gen_tree_find(node->rrset_tree, &rrset); +} + +/*----------------------------------------------------------------------------*/ + +knot_rrset_t *knot_node_remove_rrset(knot_node_t *node, uint16_t type) +{ + knot_rrset_t dummy_rrset; + dummy_rrset.type = type; + knot_rrset_t *rrset = + (knot_rrset_t *)gen_tree_find(node->rrset_tree, &dummy_rrset); + if (rrset != NULL) { + gen_tree_remove(node->rrset_tree, rrset); + node->rrset_count--; + } + return rrset; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_remove_all_rrsets(knot_node_t *node) +{ + // remove RRSets but do not delete them + gen_tree_clear(node->rrset_tree); + node->rrset_count = 0; + +} + +/*----------------------------------------------------------------------------*/ + +short knot_node_rrset_count(const knot_node_t *node) +{ + return node->rrset_count; +} + +/*----------------------------------------------------------------------------*/ + +struct knot_node_save_rrset_arg { + knot_rrset_t **array; + size_t count; +}; + +static void save_rrset_to_array(void *node, void *data) +{ + knot_rrset_t *rrset = (knot_rrset_t *)node; + struct knot_node_save_rrset_arg *args = + (struct knot_node_save_rrset_arg *)data; + args->array[args->count++] = rrset; +} + +knot_rrset_t **knot_node_get_rrsets(const knot_node_t *node) +{ +// knot_node_dump(node, 1); + if (node->rrset_count == 0) { + return NULL; + } + knot_rrset_t **rrsets = (knot_rrset_t **)malloc( + node->rrset_count * sizeof(knot_rrset_t *)); + CHECK_ALLOC_LOG(rrsets, NULL); + struct knot_node_save_rrset_arg args; + args.array = rrsets; + args.count = 0; + + gen_tree_apply_inorder(node->rrset_tree, save_rrset_to_array, + &args); + + assert(args.count == node->rrset_count); + + return rrsets; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rrset_t **knot_node_rrsets(const knot_node_t *node) +{ + //knot_node_dump((knot_node_t *)node, (void*)1); + if (node->rrset_count == 0) { + return NULL; + } + + knot_rrset_t **rrsets = (knot_rrset_t **)malloc( + node->rrset_count * sizeof(knot_rrset_t *)); + CHECK_ALLOC_LOG(rrsets, NULL); + struct knot_node_save_rrset_arg args; + args.array = rrsets; + args.count = 0; + + gen_tree_apply_inorder(node->rrset_tree, save_rrset_to_array, + &args); + + assert(args.count == node->rrset_count); + assert(args.count); + + return (const knot_rrset_t **)rrsets; + +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_node_parent(const knot_node_t *node, + int check_version) +{ +// assert(!check_version +// || (node->zone != NULL && node->zone->contents != NULL)); + + knot_node_t *parent = node->parent; + + if (check_version && node->zone != NULL) { + int new_gen = knot_node_zone_gen_is_new(node); +// short ver = knot_node_zone_generation(node); + + /*! \todo Remove, this will not be true during the reference + * fixing. + */ +// assert(new_gen || parent == NULL +// || !knot_node_is_new(parent)); + + if (new_gen && parent != NULL) { + // we want the new node + assert(node->parent->new_node != NULL); + parent = parent->new_node; + } + } + + return parent; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_parent(knot_node_t *node, knot_node_t *parent) +{ + // decrease number of children of previous parent + if (node->parent != NULL) { + --parent->children; + } + // set the parent + node->parent = parent; + + // increase the count of children of the new parent + if (parent != NULL) { + ++parent->children; + } +} + +/*----------------------------------------------------------------------------*/ + +unsigned int knot_node_children(const knot_node_t *node) +{ + return node->children; +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_node_previous(const knot_node_t *node, + int check_version) +{ + return knot_node_get_previous(node, check_version); +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_node_get_previous(const knot_node_t *node, + int check_version) +{ + assert(!check_version + || (node->zone != NULL && node->zone->contents != NULL)); + + knot_node_t *prev = node->prev; + + if (check_version && prev != NULL) { + int new_gen = knot_node_zone_gen_is_new(node); + int old_gen = knot_node_zone_gen_is_old(node); +// short ver = knot_node_zone_generation(node); + + if (old_gen) { // we want old node + while (knot_node_is_new(prev)) { + prev = prev->prev; + } + assert(!knot_node_is_new(prev)); + } else if (new_gen) { // we want new node + while (knot_node_is_old(prev)) { + if (prev->new_node) { + prev = prev->new_node; + } else { + prev = prev; + } + } + assert(knot_node_is_new(prev)); + } + } + + return prev; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_previous(knot_node_t *node, knot_node_t *prev) +{ + node->prev = prev; + if (prev != NULL) { + // set the prev pointer of the next node to the given node + if (prev->next != NULL) { + assert(prev->next->prev == prev); + prev->next->prev = node; + } + node->next = prev->next; + prev->next = node; + } +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_node_nsec3_node(const knot_node_t *node, + int check_version) +{ + knot_node_t *nsec3_node = node->nsec3_node; + if (nsec3_node == NULL) { + return NULL; + } + + if (check_version) { + int new_gen = knot_node_zone_gen_is_new(node); + int old_gen = knot_node_zone_gen_is_old(node); +// short ver = knot_node_zone_generation(node); + assert(new_gen || !knot_node_is_new(nsec3_node)); + if (old_gen && knot_node_is_new(nsec3_node)) { + return NULL; + } else if (new_gen && knot_node_is_old(nsec3_node)) { + nsec3_node = nsec3_node->new_node; + } + } + + return nsec3_node; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_nsec3_node(knot_node_t *node, knot_node_t *nsec3_node) +{ + node->nsec3_node = nsec3_node; + if (nsec3_node != NULL) { + nsec3_node->nsec3_referer = node; + } +} + +/*----------------------------------------------------------------------------*/ + +const knot_dname_t *knot_node_owner(const knot_node_t *node) +{ + return node->owner; +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_node_get_owner(const knot_node_t *node) +{ + return node->owner; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_owner(knot_node_t *node, knot_dname_t* owner) +{ + if (node) { + /* Retain new owner and release old owner. */ + knot_dname_retain(owner); + knot_dname_release(node->owner); + node->owner = owner; + } +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_node_wildcard_child(const knot_node_t *node, + int check_version) +{ + knot_node_t *w = node->wildcard_child; + + if (check_version && w != 0) { + int new_gen = knot_node_zone_gen_is_new(node); + int old_gen = knot_node_zone_gen_is_old(node); +// short ver = knot_node_zone_generation(node); + + if (old_gen && knot_node_is_new(w)) { + return NULL; + } else if (new_gen && knot_node_is_old(w)) { + assert(w->new_node != NULL); + w = w->new_node; + } + } + + return w; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_wildcard_child(knot_node_t *node, + knot_node_t *wildcard_child) +{ + node->wildcard_child = wildcard_child; +// assert(wildcard_child->parent == node); +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_node_current(const knot_node_t *node) +{ + if (node == NULL || node->zone == NULL + || knot_zone_contents(node->zone) == NULL) { + return node; + } + + int new_gen = knot_node_zone_gen_is_new(node); + int old_gen = knot_node_zone_gen_is_old(node); +// short ver = knot_node_zone_generation(node); + + if (old_gen && knot_node_is_new(node)) { + return NULL; + } else if (new_gen && knot_node_is_old(node)) { + assert(node->new_node != NULL); + return node->new_node; + } + return node; +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_node_get_current(knot_node_t *node) +{ + if (node == NULL || node->zone == NULL + || knot_zone_contents(node->zone) == NULL) { + return node; + } + + int new_gen = knot_node_zone_gen_is_new(node); + int old_gen = knot_node_zone_gen_is_old(node); +// short ver = knot_node_zone_generation(node); + + if (old_gen && knot_node_is_new(node)) { + return NULL; + } else if (new_gen && knot_node_is_old(node)) { + assert(node->new_node != NULL); + return node->new_node; + } + + assert((old_gen && knot_node_is_old(node)) + || (new_gen && knot_node_is_new(node)) + || (!old_gen && !new_gen)); + + return node; +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_node_new_node(const knot_node_t *node) +{ + return node->new_node; +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_node_get_new_node(const knot_node_t *node) +{ + return node->new_node; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_new_node(knot_node_t *node, + knot_node_t *new_node) +{ + node->new_node = new_node; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_zone(knot_node_t *node, knot_zone_t *zone) +{ + node->zone = zone; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_update_ref(knot_node_t **ref) +{ + if (*ref != NULL && knot_node_is_old(*ref)) { + *ref = (*ref)->new_node; + } +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_update_refs(knot_node_t *node) +{ + /* CLEANUP */ + /* OMG! */ + // reference to previous node + knot_node_update_ref(&node->prev); +// if (node->prev && knot_node_is_old(node->prev)) { +// assert(node->prev->new_node != NULL); +// node->prev = node->prev->new_node; +// } + + // reference to next node + knot_node_update_ref(&node->next); +// if (node->next && knot_node_is_old(node->next)) { +// assert(node->next->new_node != NULL); +// node->next = node->next->new_node; +// } + + // reference to parent +// if (node->parent && knot_node_is_old(node->parent)) { +// assert(node->parent->new_node != NULL); +// // do not use the API function to set parent, so that children count +// // is not changed +// //knot_node_set_parent(node, node->parent->new_node); +// node->parent = node->parent->new_node; +// } + knot_node_update_ref(&node->parent); + + // reference to wildcard child + knot_node_update_ref(&node->wildcard_child); +// if (node->wildcard_child && knot_node_is_old(node->wildcard_child)) { +// assert(node->wildcard_child->new_node != NULL); +// node->wildcard_child = node->wildcard_child->new_node; +// } + + // reference to NSEC3 node + knot_node_update_ref(&node->nsec3_node); +// if (node->nsec3_node && knot_node_is_old(node->nsec3_node)) { +// assert(node->nsec3_node->new_node != NULL); +// node->nsec3_node = node->nsec3_node->new_node; +// } + + // reference to NSEC3 referrer + knot_node_update_ref(&node->nsec3_referer); +// if (node->nsec3_referer && knot_node_is_old(node->nsec3_referer)) { +// assert(node->nsec3_referer->new_node != NULL); +// node->nsec3_referer = node->nsec3_referer->new_node; +// } +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_deleg_point(knot_node_t *node) +{ + knot_node_flags_set_deleg(&node->flags); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_is_deleg_point(const knot_node_t *node) +{ + return knot_node_flags_get_deleg(node->flags); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_non_auth(knot_node_t *node) +{ + knot_node_flags_set_nonauth(&node->flags); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_is_non_auth(const knot_node_t *node) +{ + return knot_node_flags_get_nonauth(node->flags); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_is_auth(const knot_node_t *node) +{ + return (node->flags == 0); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_is_new(const knot_node_t *node) +{ + return knot_node_flags_get_new(node->flags); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_is_old(const knot_node_t *node) +{ + return knot_node_flags_get_old(node->flags); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_new(knot_node_t *node) +{ + knot_node_flags_set_new(&node->flags); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_old(knot_node_t *node) +{ + knot_node_flags_set_old(&node->flags); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_clear_new(knot_node_t *node) +{ + knot_node_flags_clear_new(&node->flags); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_clear_old(knot_node_t *node) +{ + knot_node_flags_clear_old(&node->flags); +} + +/*----------------------------------------------------------------------------*/ + +static void knot_node_free_rrsets_from_tree(void *item, void *data) +{ + if (item == NULL) { + return; + } + + knot_rrset_t *rrset = (knot_rrset_t *)(item); + knot_rrset_deep_free(&rrset, 0, 1, *((int *)data)); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_free_rrsets(knot_node_t *node, int free_rdata_dnames) +{ + /* CLEANUP */ +// knot_rrset_t **rrsets = knot_node_get_rrsets(node); +// for (int i = 0; i < node->rrset_count; i++) { +// knot_rrset_deep_free(&(rrsets[i]), 0, 1, free_rdata_dnames); +// } + +// free(rrsets); + + char *name = knot_dname_to_str(node->owner); + free(name); + + gen_tree_destroy(&node->rrset_tree, knot_node_free_rrsets_from_tree, + (void *)&free_rdata_dnames); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_free(knot_node_t **node, int free_owner, int fix_refs) +{ + if (node == NULL || *node == NULL) { + return; + } + + dbg_node("Freeing node.\n"); + if ((*node)->rrset_tree != NULL) { + dbg_node("Freeing RRSets.\n"); + gen_tree_destroy(&(*node)->rrset_tree, NULL, NULL); + } + + /*! \todo Always release owner? */ + //if (free_owner) { + dbg_node("Releasing owner.\n"); + knot_dname_release((*node)->owner); + //} + + // check nodes referencing this node and fix the references + + if (fix_refs) { + // previous node + dbg_node("Checking previous.\n"); + if ((*node)->prev && (*node)->prev->next == (*node)) { + (*node)->prev->next = (*node)->next; + } + + dbg_node("Checking next.\n"); + if ((*node)->next && (*node)->next->prev == (*node)) { + (*node)->next->prev = (*node)->prev; + } + + // NSEC3 node + dbg_node("Checking NSEC3.\n"); + if ((*node)->nsec3_node + && (*node)->nsec3_node->nsec3_referer == (*node)) { + (*node)->nsec3_node->nsec3_referer = NULL; + } + + dbg_node("Checking NSEC3 ref.\n"); + if ((*node)->nsec3_referer + && (*node)->nsec3_referer->nsec3_node == (*node)) { + (*node)->nsec3_referer->nsec3_node = NULL; + } + + // wildcard child node + dbg_node("Checking parent's wildcard child.\n"); + if ((*node)->parent + && (*node)->parent->wildcard_child == (*node)) { + (*node)->parent->wildcard_child = NULL; + } + + // fix parent's children count + if ((*node)->parent) { + --(*node)->parent->children; + } + } + + free(*node); + *node = NULL; + + dbg_node("Done.\n"); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_compare(knot_node_t *node1, knot_node_t *node2) +{ + return knot_dname_compare(node1->owner, node2->owner); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_shallow_copy(const knot_node_t *from, knot_node_t **to) +{ + // create new node + *to = knot_node_new(from->owner, from->parent, from->flags); + if (*to == NULL) { + return KNOT_ENOMEM; + } + + /* Free old rrset_tree, as it will be replaced by shallow copy. */ + gen_tree_destroy(&(*to)->rrset_tree, 0, 0); + + // copy references + // do not use the API function to set parent, so that children count + // is not changed + memcpy(*to, from, sizeof(knot_node_t)); + + // copy RRSets + // copy the skip list with the old references + /* CLEANUP */ + (*to)->rrset_tree = gen_tree_shallow_copy(from->rrset_tree); +// assert((*to)->rrset_tree != from->rrset_tree); +// (*to)->rrsets = skip_copy_list(from->rrsets); + if ((*to)->rrset_tree == NULL) { + free(*to); + *to = NULL; + return KNOT_ENOMEM; + } + + return KNOT_EOK; +} diff --git a/src/libknot/zone/node.h b/src/libknot/zone/node.h new file mode 100644 index 0000000..fcb612d --- /dev/null +++ b/src/libknot/zone/node.h @@ -0,0 +1,436 @@ +/*! + * \file node.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Structure representing one node in domain name tree and API for + * manipulating it. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_NODE_H_ +#define _KNOT_NODE_H_ + +#include "dname.h" +#include "common/skip-list.h" +#include "rrset.h" +#include "common/tree.h" +#include "common/general-tree.h" + +struct knot_zone; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure representing one node in a domain name tree, i.e. one domain + * name in a zone. + * + * RRSets are ordered by type and stored in a skip-list to allow fast lookup. + */ +struct knot_node { + knot_dname_t *owner; /*!< Domain name being the owner of this node. */ + struct knot_node *parent; /*!< Parent node in the name hierarchy. */ + + /*! \brief Type-ordered list of RRSets belonging to this node. */ + general_tree_t *rrset_tree; + + unsigned short rrset_count; /*!< Number of RRSets stored in the node. */ + + /*! \brief Wildcard node being the direct descendant of this node. */ + struct knot_node *wildcard_child; + + /*! + * \brief Previous node in canonical order. + * + * Only authoritative nodes or delegation points are referenced by this, + * as only they may contain NSEC records needed for authenticating + * negative answers. + */ + struct knot_node *prev; + + struct knot_node *next; + + /*! + * \brief NSEC3 node corresponding to this node. + * + * Such NSEC3 node has owner in form of the hashed domain name of this + * node prepended as a single label to the zone name. + */ + struct knot_node *nsec3_node; + + struct knot_node *nsec3_referer; + + /*! + * \brief Various flags. + * + * Currently only two: + * 0x01 - node is a delegation point + * 0x02 - node is non-authoritative (under a delegation point) + * 0x80 - node is old and will be removed (during update) + * 0x40 - node is new, should not be used while zone is old + */ + uint8_t flags; + + struct knot_node *new_node; + + unsigned int children; + + /*! + * \brief Generation of node to be used. + * + * If set to 0, the old node will be used. Otherwise new nodes will + * be used. This applies when getting some referenced node. + + */ +// short **generation; + + struct knot_zone *zone; +}; + +typedef struct knot_node knot_node_t; + +/*----------------------------------------------------------------------------*/ +/*! \brief Flags used to mark nodes with some property. */ +typedef enum { + /*! \brief Node is a delegation point (i.e. marking a zone cut). */ + KNOT_NODE_FLAGS_DELEG = (uint8_t)0x01, + /*! \brief Node is not authoritative (i.e. below a zone cut). */ + KNOT_NODE_FLAGS_NONAUTH = (uint8_t)0x02, + /*! \brief Node is old and will be removed (during update). */ + KNOT_NODE_FLAGS_OLD = (uint8_t)0x80, + /*! \brief Node is new and should not be used while zoen is old. */ + KNOT_NODE_FLAGS_NEW = (uint8_t)0x40 +} knot_node_flags_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates and initializes new node structure. + * + * \todo Owner reference counter will be increased. + * + * \param owner Owner of the created node. + * \param parent Parent of the created node. + * \param flags Document me. + * + * \todo Document missing parameters. + * + * \return Newly created node or NULL if an error occured. + */ +knot_node_t *knot_node_new(knot_dname_t *owner, knot_node_t *parent, + uint8_t flags); + +/*! + * \brief Adds an RRSet to the node. + * + * \param node Node to add the RRSet to. + * \param rrset RRSet to add. + * + * \retval KNOT_EOK on success. + * \retval KNOT_ERROR if the RRSet could not be inserted. + */ +int knot_node_add_rrset(knot_node_t *node, knot_rrset_t *rrset, + int merge); + +/*! + * \brief Returns the RRSet of the given type from the node. + * + * \param node Node to get the RRSet from. + * \param type Type of the RRSet to retrieve. + * + * \return RRSet from node \a node having type \a type, or NULL if no such + * RRSet exists in this node. + */ +const knot_rrset_t *knot_node_rrset(const knot_node_t *node, + uint16_t type); + +/*! + * \brief Returns the RRSet of the given type from the node (non-const version). + * + * \param node Node to get the RRSet from. + * \param type Type of the RRSet to retrieve. + * + * \return RRSet from node \a node having type \a type, or NULL if no such + * RRSet exists in this node. + */ +knot_rrset_t *knot_node_get_rrset(knot_node_t *node, uint16_t type); + +knot_rrset_t *knot_node_remove_rrset(knot_node_t *node, uint16_t type); + +void knot_node_remove_all_rrsets(knot_node_t *node); + +/*! + * \brief Returns number of RRSets in the node. + * + * \param node Node to get the RRSet count from. + * + * \return Number of RRSets in \a node. + */ +short knot_node_rrset_count(const knot_node_t *node); + +/*! + * \brief Returns all RRSets from the node. + * + * \param node Node to get the RRSets from. + * + * \return Newly allocated array of RRSets or NULL if an error occured. + */ +knot_rrset_t **knot_node_get_rrsets(const knot_node_t *node); + +/*! + * \brief Returns all RRSets from the node. + * + * \note This function is identical to knot_node_get_rrsets(), only it returns + * non-modifiable data. + * + * \param node Node to get the RRSets from. + * + * \return Newly allocated array of RRSets or NULL if an error occured. + */ +const knot_rrset_t **knot_node_rrsets(const knot_node_t *node); + +/*! + * \brief Returns the parent of the node. + * + * \param node Node to get the parent of. + * + * \return Parent node of the given node or NULL if no parent has been set (e.g. + * node in a zone apex has no parent). + */ +const knot_node_t *knot_node_parent(const knot_node_t *node, + int check_version); + +/*! + * \brief Sets the parent of the node. + * + * \param node Node to set the parent of. + * \param parent Parent to set to the node. + */ +void knot_node_set_parent(knot_node_t *node, knot_node_t *parent); + +unsigned int knot_node_children(const knot_node_t *node); + +/*! + * \brief Returns the previous authoritative node or delegation point in + * canonical order or the first node in zone. + * + * \param node Node to get the previous node of. + * + * \return Previous authoritative node or delegation point in canonical order or + * the first node in zone if \a node is the last node in zone. + * \retval NULL if previous node is not set. + */ +const knot_node_t *knot_node_previous(const knot_node_t *node, + int check_version); + +/*! + * \brief Returns the previous authoritative node or delegation point in + * canonical order or the first node in zone. + * + * \note This function is identical to knot_node_previous() except that it + * returns non-const node. + * + * \param node Node to get the previous node of. + * + * \return Previous authoritative node or delegation point in canonical order or + * the first node in zone if \a node is the last node in zone. + * \retval NULL if previous node is not set. + */ +knot_node_t *knot_node_get_previous(const knot_node_t *node, + int check_version); + +/*! + * \brief Sets the previous node of the given node. + * + * \param node Node to set the previous node to. + * \param prev Previous node to set. + */ +void knot_node_set_previous(knot_node_t *node, knot_node_t *prev); + +/*! + * \brief Returns the NSEC3 node corresponding to the given node. + * + * \param node Node to get the NSEC3 node for. + * + * \return NSEC3 node corresponding to \a node (i.e. node with owner name + * created by concatenating the hash of owner domain name of \a node + * and the name of the zone \a node belongs to). + * \retval NULL if the NSEC3 node is not set. + */ +const knot_node_t *knot_node_nsec3_node(const knot_node_t *node, + int check_version); + +/*! + * \brief Sets the corresponding NSEC3 node of the given node. + * + * \param node Node to set the NSEC3 node to. + * \param nsec3_node NSEC3 node to set. + */ +void knot_node_set_nsec3_node(knot_node_t *node, knot_node_t *nsec3_node); + +/*! + * \brief Returns the owner of the node. + * + * \param node Node to get the owner of. + * + * \return Owner of the given node. + */ +const knot_dname_t *knot_node_owner(const knot_node_t *node); + +/*! + * \todo Document me. + */ +knot_dname_t *knot_node_get_owner(const knot_node_t *node); + +/*! + * \brief Set node owner to specified dname. + * + * Previous owner will be replaced if exist. + * + * \param node Specified node. + * \param owner New owner dname. + */ +void knot_node_set_owner(knot_node_t *node, knot_dname_t* owner); + +/*! + * \brief Returns the wildcard child of the node. + * + * \param node Node to get the owner of. + * + * \return Wildcard child of the given node or NULL if it has none. + */ +const knot_node_t *knot_node_wildcard_child(const knot_node_t *node, + int check_version); + +/*! + * \brief Sets the wildcard child of the node. + * + * \param node Node to set the wildcard child of. + * \param wildcard_child Wildcard child of the node. + */ +void knot_node_set_wildcard_child(knot_node_t *node, + knot_node_t *wildcard_child); + +const knot_node_t *knot_node_current(const knot_node_t *node); + +knot_node_t *knot_node_get_current(knot_node_t *node); + +const knot_node_t *knot_node_new_node(const knot_node_t *node); + +knot_node_t *knot_node_get_new_node(const knot_node_t *node); + +void knot_node_set_new_node(knot_node_t *node, + knot_node_t *new_node); + +void knot_node_set_zone(knot_node_t *node, struct knot_zone *zone); + +void knot_node_update_ref(knot_node_t **ref); + +void knot_node_update_refs(knot_node_t *node); + +/*! + * \brief Mark the node as a delegation point. + * + * \param node Node to mark as a delegation point. + */ +void knot_node_set_deleg_point(knot_node_t *node); + +/*! + * \brief Checks if the node is a delegation point. + * + * \param node Node to check. + * + * \retval <> 0 if \a node is marked as delegation point. + * \retval 0 otherwise. + */ +int knot_node_is_deleg_point(const knot_node_t *node); + +/*! + * \brief Mark the node as non-authoritative. + * + * \param node Node to mark as non-authoritative. + */ +void knot_node_set_non_auth(knot_node_t *node); + +/*! + * \brief Checks if the node is non-authoritative. + * + * \param node Node to check. + * + * \retval <> 0 if \a node is marked as non-authoritative. + * \retval 0 otherwise. + */ +int knot_node_is_non_auth(const knot_node_t *node); + +int knot_node_is_auth(const knot_node_t *node); + +int knot_node_is_new(const knot_node_t *node); + +int knot_node_is_old(const knot_node_t *node); + +void knot_node_set_new(knot_node_t *node); + +void knot_node_set_old(knot_node_t *node); + +void knot_node_clear_new(knot_node_t *node); + +void knot_node_clear_old(knot_node_t *node); + +/*! + * \brief Destroys the RRSets within the node structure. + * + * \param node Node to be destroyed. + * \param free_rdata_dnames Set to <> 0 if you want to delete ALL domain names + * present in RDATA. Set to 0 otherwise. (See + * knot_rdata_deep_free().) + */ +void knot_node_free_rrsets(knot_node_t *node, int free_rdata_dnames); + +/*! + * \brief Destroys the node structure. + * + * Does not destroy the RRSets within the node. + * Also sets the given pointer to NULL. + * + * \param node Node to be destroyed. + * \param free_owner Set to 0 if you do not want the owner domain name to be + * destroyed also. Set to <> 0 otherwise. + * \param fix_refs + * + * \todo Document missing parameters. + */ +void knot_node_free(knot_node_t **node, int free_owner, int fix_refs); + +/*! + * \brief Compares two nodes according to their owner. + * + * \param node1 First node. + * \param node2 Second node. + * + * \retval < 0 if \a node1 goes before \a node2 according to canonical order + * of their owner names. + * \retval 0 if they are equal. + * \retval > 0 if \a node1 goes after \a node2. + */ +int knot_node_compare(knot_node_t *node1, knot_node_t *node2); + +int knot_node_shallow_copy(const knot_node_t *from, knot_node_t **to); + +#endif /* _KNOT_NODE_H_ */ + +/*! @} */ diff --git a/src/libknot/zone/zone-contents.c b/src/libknot/zone/zone-contents.c new file mode 100644 index 0000000..d550728 --- /dev/null +++ b/src/libknot/zone/zone-contents.c @@ -0,0 +1,2396 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "zone/zone-contents.h" +#include "util/error.h" +#include "util/debug.h" +#include "common/base32hex.h" +#include "consts.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ + +typedef struct { + void (*func)(knot_node_t *, void *); + void *data; +} knot_zone_tree_func_t; + +typedef struct { + knot_node_t *first_node; + knot_zone_contents_t *zone; + knot_node_t *previous_node; + int check_ver; +} knot_zone_adjust_arg_t; + +/*----------------------------------------------------------------------------*/ + +static void knot_zone_tree_apply(knot_zone_tree_node_t *node, + void *data) +{ + if (node == NULL || data == NULL) { + return; + } + + knot_zone_tree_func_t *f = (knot_zone_tree_func_t *)data; + f->func(node->node, f->data); +} + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Checks if the given node can be inserted into the given zone. + * + * Checks if both the arguments are non-NULL and if the owner of the node + * belongs to the zone (i.e. is a subdomain of the zone apex). + * + * \param zone Zone to which the node is going to be inserted. + * \param node Node to check. + * + * \retval KNOT_EOK if both arguments are non-NULL and the node belongs to the + * zone. + * \retval KNOT_EBADARG if either of the arguments is NULL. + * \retval KNOT_EBADZONE if the node does not belong to the zone. + */ +static int knot_zone_contents_check_node( + const knot_zone_contents_t *contents, const knot_node_t *node) +{ + if (contents == NULL || node == NULL) { + return KNOT_EBADARG; + } + + // assert or just check?? + assert(contents->apex != NULL); + + if (!knot_dname_is_subdomain(node->owner, + knot_node_owner(contents->apex))) { +dbg_zone_exec( + char *node_owner = knot_dname_to_str(knot_node_owner(node)); + char *apex_owner = knot_dname_to_str(contents->apex->owner); + dbg_zone("zone: Trying to insert foreign node to a " + "zone. Node owner: %s, zone apex: %s\n", + node_owner, apex_owner); + free(node_owner); + free(apex_owner); +); + return KNOT_EBADZONE; + } + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Destroys all RRSets in a node. + * + * This function is designed to be used in the tree-iterating functions. + * + * \param node Node to destroy RRSets from. + * \param data Unused parameter. + */ +static void knot_zone_contents_destroy_node_rrsets_from_tree( + knot_zone_tree_node_t *tnode, void *data) +{ + assert(tnode != NULL); + assert(tnode->node != NULL); + + int free_rdata_dnames = (int)((intptr_t)data); + knot_node_free_rrsets(tnode->node, free_rdata_dnames); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Destroys node owner. + * + * This function is designed to be used in the tree-iterating functions. + * + * \param node Node to destroy the owner of. + * \param data Unused parameter. + */ +static void knot_zone_contents_destroy_node_owner_from_tree( + knot_zone_tree_node_t *tnode, void *data) +{ + assert(tnode != NULL); + assert(tnode->node != NULL); + + UNUSED(data); + /*!< \todo change completely! */ + knot_node_free(&tnode->node, 0, 0); +} + +/*! + * \brief Finds and sets wildcard child for given node's owner. + * + * \param zone Current zone. + * \param node Node to be used. + */ +static void find_and_set_wildcard_child(knot_zone_contents_t *zone, + knot_node_t *node) +{ + knot_dname_t *chopped = knot_dname_left_chop(node->owner); + assert(chopped); + knot_node_t *wildcard_parent; + wildcard_parent = + knot_zone_contents_get_node(zone, chopped); + + knot_dname_free(&chopped); + + assert(wildcard_parent); /* it *has* to be there */ + + knot_node_set_wildcard_child(wildcard_parent, node); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adjusts one RDATA item by replacing domain name by one present in the + * zone. + * + * This function tries to find the domain name in the zone. If the name is not + * in the zone, it does nothing. If it is there, it destroys the domain name + * stored in the RDATA item and replaces it by pointer to the domain name from + * the zone. + * + * \warning Call this function only with RDATA items which store domain names, + * otherwise the behaviour is undefined. + * + * \param rdata RDATA where the item is located. + * \param zone Zone to which the RDATA belongs. + * \param pos Position of the RDATA item in the RDATA. + */ +static void knot_zone_contents_adjust_rdata_item(knot_rdata_t *rdata, + knot_zone_contents_t *zone, + knot_node_t *node, + int pos) +{ + return; + const knot_rdata_item_t *dname_item + = knot_rdata_item(rdata, pos); + + assert(dname_item); + + if (dname_item != NULL) { + knot_dname_t *dname = dname_item->dname; + const knot_node_t *n = NULL; + const knot_node_t *closest_encloser = NULL; + const knot_node_t *prev = NULL; + + if (knot_dname_is_wildcard(dname)) { + find_and_set_wildcard_child(zone, node); + } + + int ret = knot_zone_contents_find_dname(zone, dname, &n, + &closest_encloser, &prev); + + // n = knot_zone_find_node(zone, dname); + + if (ret == KNOT_EBADARG || ret == KNOT_EBADZONE) { + // TODO: do some cleanup if needed + return; + } + + assert(ret != KNOT_ZONE_NAME_FOUND + || n == closest_encloser); + + if (ret != KNOT_ZONE_NAME_FOUND + && (closest_encloser != NULL)) { + dbg_zone("Saving closest encloser to RDATA.\n"); + // save pointer to the closest encloser + knot_rdata_item_t *item = + knot_rdata_get_item(rdata, pos); + assert(item->dname != NULL); + assert(item->dname->node == NULL); + //skip_insert(list, (void *)item->dname, + // (void *)closest_encloser->owner, NULL); + item->dname->node = closest_encloser->owner->node; + } + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adjusts all RDATA in the given RRSet by replacing domain names by ones + * present in the zone. + * + * This function selects the RDATA items containing a domain name (according to + * RR type descriptor of the RRSet's type and adjusts the item using + * knot_zone_adjust_rdata_item(). + * + * \param rrset RRSet to adjust RDATA in. + * \param zone Zone to which the RRSet belongs. + */ +static void knot_zone_contents_adjust_rdata_in_rrset(knot_rrset_t *rrset, + knot_zone_contents_t *zone, + knot_node_t *node) +{ + uint16_t type = knot_rrset_type(rrset); + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(type); + assert(desc); + + knot_rdata_t *rdata_first = knot_rrset_get_rdata(rrset); + knot_rdata_t *rdata = rdata_first; + + if (rdata == NULL) { + return; + } + + while (rdata->next != rdata_first) { + for (int i = 0; i < rdata->count; ++i) { + if (desc->wireformat[i] + == KNOT_RDATA_WF_COMPRESSED_DNAME + || desc->wireformat[i] + == KNOT_RDATA_WF_UNCOMPRESSED_DNAME + || desc->wireformat[i] + == KNOT_RDATA_WF_LITERAL_DNAME) { + dbg_zone("Adjusting domain name at " + "position %d of RDATA of record with owner " + "%s and type %s.\n", + i, rrset->owner->name, + knot_rrtype_to_string(type)); + + knot_zone_contents_adjust_rdata_item(rdata, + zone, + node, + i); + } + } + rdata = rdata->next; + } + + for (int i = 0; i < rdata->count; ++i) { + if (desc->wireformat[i] + == KNOT_RDATA_WF_COMPRESSED_DNAME + || desc->wireformat[i] + == KNOT_RDATA_WF_UNCOMPRESSED_DNAME + || desc->wireformat[i] + == KNOT_RDATA_WF_LITERAL_DNAME) { + dbg_zone("Adjusting domain name at " + "position %d of RDATA of record with owner " + "%s and type %s.\n", + i, rrset->owner->name, + knot_rrtype_to_string(type)); + + knot_zone_contents_adjust_rdata_item(rdata, zone, + node, i); + } + } + +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adjusts all RRSets in the given node by replacing domain names in + * RDATA by ones present in the zone. + * + * This function just calls knot_zone_adjust_rdata_in_rrset() for all RRSets + * in the node (including all RRSIG RRSets). + * + * \param node Zone node to adjust the RRSets in. + * \param zone Zone to which the node belongs. + */ +static void knot_zone_contents_adjust_rrsets(knot_node_t *node, + knot_zone_contents_t *zone) +{ + //return; + knot_rrset_t **rrsets = knot_node_get_rrsets(node); + short count = knot_node_rrset_count(node); + + assert(count == 0 || rrsets != NULL); + + for (int r = 0; r < count; ++r) { + assert(rrsets[r] != NULL); + dbg_zone("Adjusting next RRSet.\n"); + knot_zone_contents_adjust_rdata_in_rrset(rrsets[r], zone, + node); + knot_rrset_t *rrsigs = rrsets[r]->rrsigs; + if (rrsigs != NULL) { + dbg_zone("Adjusting next RRSIGs.\n"); + knot_zone_contents_adjust_rdata_in_rrset(rrsigs, + zone, + node); + } + } + + free(rrsets); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adjusts zone node for faster query processing. + * + * - Adjusts RRSets in the node (see knot_zone_adjust_rrsets()). + * - Marks the node as delegation point or non-authoritative (below a zone cut) + * if applicable. + * - Stores reference to corresponding NSEC3 node if applicable. + * + * \param node Zone node to adjust. + * \param zone Zone the node belongs to. + */ +static void knot_zone_contents_adjust_node(knot_node_t *node, + knot_zone_contents_t *zone, + int check_ver) +{ + +dbg_zone_exec( + char *name = knot_dname_to_str(node->owner); + dbg_zone("----- Adjusting node %s -----\n", name); + free(name); +); + + // adjust domain names in RDATA + knot_zone_contents_adjust_rrsets(node, zone); + +dbg_zone_exec( + if (knot_node_parent(node, 1)) { + char *name = knot_dname_to_str(knot_node_owner( + knot_node_parent(node, check_ver))); + dbg_zone("Parent: %s\n", name); + dbg_zone("Parent is delegation point: %s\n", + knot_node_is_deleg_point(knot_node_parent(node, check_ver)) + ? "yes" : "no"); + dbg_zone("Parent is non-authoritative: %s\n", + knot_node_is_non_auth(knot_node_parent(node, check_ver)) + ? "yes" : "no"); + free(name); + } else { + dbg_zone("No parent!\n"); + } +); + // delegation point / non-authoritative node + if (knot_node_parent(node, check_ver) + && (knot_node_is_deleg_point(knot_node_parent(node, check_ver)) + || knot_node_is_non_auth(knot_node_parent(node, check_ver)))) { + knot_node_set_non_auth(node); + } else if (knot_node_rrset(node, KNOT_RRTYPE_NS) != NULL + && node != zone->apex) { + knot_node_set_deleg_point(node); + } + + // authorative node? +// if (!knot_node_is_non_auth(node)) { + zone->node_count++; +// } + + // assure that owner has proper node + if (knot_dname_node(knot_node_owner(node), 0) == NULL) { + knot_dname_set_node(knot_node_get_owner(node), node); + knot_dname_set_node(knot_node_get_owner(node), node); + } + + // NSEC3 node (only if NSEC3 tree is not empty) + const knot_node_t *prev; + const knot_node_t *nsec3; + int match = knot_zone_contents_find_nsec3_for_name(zone, + knot_node_owner(node), + &nsec3, &prev, check_ver); + if (match != KNOT_ZONE_NAME_FOUND) { + nsec3 = NULL; + } + + knot_node_set_nsec3_node(node, (knot_node_t *)nsec3); + + dbg_zone("Set flags to the node: \n"); + dbg_zone("Delegation point: %s\n", + knot_node_is_deleg_point(node) ? "yes" : "no"); + dbg_zone("Non-authoritative: %s\n", + knot_node_is_non_auth(node) ? "yes" : "no"); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adjusts zone node for faster query processing. + * + * This function is just a wrapper over knot_zone_adjust_node() to be used + * in tree-traversing functions. + * + * \param node Zone node to adjust. + * \param data Zone the node belongs to. + */ +static void knot_zone_contents_adjust_node_in_tree( + knot_zone_tree_node_t *tnode, void *data) +{ + assert(data != NULL); + assert(tnode != NULL); + assert(tnode->node != NULL); + + knot_zone_adjust_arg_t *args = (knot_zone_adjust_arg_t *)data; + knot_node_t *node = tnode->node; + knot_node_set_previous(node, args->previous_node); + args->previous_node = node; + if (args->first_node == NULL) { + args->first_node = node; + } + knot_zone_contents_t *zone = args->zone; + + knot_zone_contents_adjust_node(node, zone, args->check_ver); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adjusts NSEC3 node for faster query processing. + * + * This function is just a wrapper over knot_zone_adjust_nsec3_node() to be + * used in tree-traversing functions. + * + * \param node Zone node to adjust. + * \param data Zone the node belongs to. + */ +static void knot_zone_contents_adjust_nsec3_node_in_tree( + knot_zone_tree_node_t *tnode, void *data) +{ + assert(data != NULL); + assert(tnode != NULL); + assert(tnode->node != NULL); + + knot_zone_adjust_arg_t *args = (knot_zone_adjust_arg_t *)data; + knot_node_t *node = tnode->node; + knot_node_set_previous(node, args->previous_node); + args->previous_node = node; + if (args->first_node == NULL) { + args->first_node = node; + } + + /* Not needed anymore. */ +// knot_zone_contents_t *zone = args->zone; +// knot_zone_contents_adjust_nsec3_node(node, zone, 1); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a NSEC3 hashed name for the given domain name. + * + * \note The zone's NSEC3PARAM record must be parsed prior to calling this + * function (see knot_zone_load_nsec3param()). + * + * \param zone Zone from which to take the NSEC3 parameters. + * \param name Domain name to hash. + * \param nsec3_name Hashed name. + * + * \retval KNOT_EOK + * \retval KNOT_ENSEC3PAR + * \retval KNOT_ECRYPTO + * \retval KNOT_ERROR if an error occured while creating a new domain name + * from the hash or concatenating it with the zone name. + */ +static int knot_zone_contents_nsec3_name(const knot_zone_contents_t *zone, + const knot_dname_t *name, + knot_dname_t **nsec3_name) +{ + assert(nsec3_name != NULL); + + *nsec3_name = NULL; + + const knot_nsec3_params_t *nsec3_params = + knot_zone_contents_nsec3params(zone); + + if (nsec3_params == NULL) { +dbg_zone_exec( + char *n = knot_dname_to_str(zone->apex->owner); + dbg_zone("No NSEC3PARAM for zone %s.\n", n); + free(n); +); + return KNOT_ENSEC3PAR; + } + + uint8_t *hashed_name = NULL; + size_t hash_size = 0; + +dbg_zone_exec( + char *n = knot_dname_to_str(name); + dbg_zone("Hashing name %s.\n", n); + free(n); +); + + int res = knot_nsec3_sha1(nsec3_params, knot_dname_name(name), + knot_dname_size(name), &hashed_name, + &hash_size); + + if (res != 0) { + char *n = knot_dname_to_str(name); + dbg_zone("Error while hashing name %s.\n", n); + free(n); + return KNOT_ECRYPTO; + } + + dbg_zone("Hash: "); + dbg_zone_hex((char *)hashed_name, hash_size); + dbg_zone("\n"); + + char *name_b32 = NULL; + size_t size = base32hex_encode_alloc((char *)hashed_name, hash_size, + &name_b32); + + if (size == 0) { + char *n = knot_dname_to_str(name); + dbg_zone("Error while encoding hashed name %s to " + "base32.\n", n); + free(n); + if (name_b32 != NULL) { + free(name_b32); + } + return KNOT_ECRYPTO; + } + + assert(name_b32 != NULL); + free(hashed_name); + + dbg_zone("Base32-encoded hash: %s\n", name_b32); + + /* Will be returned to caller, make sure it is released after use. */ + *nsec3_name = knot_dname_new_from_str(name_b32, size, NULL); + + free(name_b32); + + if (*nsec3_name == NULL) { + dbg_zone("Error while creating domain name for hashed" + " name.\n"); + return KNOT_ERROR; + } + + assert(zone->apex->owner != NULL); + knot_dname_t *ret = knot_dname_cat(*nsec3_name, zone->apex->owner); + + if (ret == NULL) { + dbg_zone("Error while creating NSEC3 domain name for " + "hashed name.\n"); + knot_dname_release(*nsec3_name); + return KNOT_ERROR; + } + + assert(ret == *nsec3_name); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tries to find the given domain name in the zone tree. + * + * \param zone Zone to search in. + * \param name Domain name to find. + * \param node Found node. + * \param previous Previous node in canonical order (i.e. the one directly + * preceding \a name in canonical order, regardless if the name + * is in the zone or not). + * + * \retval <> 0 if the domain name was found. In such case \a node holds the + * zone node with \a name as its owner. \a previous is set + * properly. + * \retval 0 if the domain name was not found. \a node may hold any (or none) + * node. \a previous is set properly. + */ +static int knot_zone_contents_find_in_tree(knot_zone_tree_t *tree, + const knot_dname_t *name, + knot_node_t **node, + knot_node_t **previous) +{ + assert(tree != NULL); + assert(name != NULL); + assert(node != NULL); + assert(previous != NULL); + + knot_node_t *found = NULL, *prev = NULL; +// knot_node_t *found2 = NULL, *prev2 = NULL; + + int exact_match = knot_zone_tree_get_less_or_equal( + tree, name, &found, &prev, 1); + +// assert(prev != NULL); + assert(exact_match >= 0); + *node = found; + *previous = prev; + +// if (prev == NULL) { +// // either the returned node is the root of the tree, or it is +// // the leftmost node in the tree; in both cases node was found +// // set the previous node of the found node +// assert(exact_match); +// assert(found != NULL); +// *previous = knot_node_get_previous(found, 1); +// } else { +// // otherwise check if the previous node is not an empty +// // non-terminal +// *previous = (knot_node_rrset_count(prev) == 0) +// ? knot_node_get_previous(prev, 1) +// : prev; +// } + + return exact_match; +} + +/*----------------------------------------------------------------------------*/ + +static void knot_zone_contents_node_to_hash(knot_zone_tree_node_t *tnode, + void *data) +{ + assert(tnode != NULL && tnode->node != NULL + && tnode->node->owner != NULL && data != NULL); + + knot_node_t *node = tnode->node; + + knot_zone_contents_t *zone = (knot_zone_contents_t *)data; + /* + * By the original approach, only authoritative nodes and delegation + * points should be added to the hash table, but currently, all nodes + * are being added when the zone is created (don't know why actually:), + * so we will do no distinction here neither. + */ + +#ifdef USE_HASH_TABLE +//dbg_zone_exec( +// char *name = knot_dname_to_str(node->owner); +// dbg_zone("Adding node with owner %s to hash table.\n", name); +// free(name); +//); + //assert(zone->table != NULL); + // add the node also to the hash table if authoritative, or deleg. point + if (zone->table != NULL + && ck_insert_item(zone->table, + (const char *)node->owner->name, + node->owner->size, (void *)node) != 0) { + dbg_zone("Error inserting node into hash table!\n"); + } +#endif +} + +/*----------------------------------------------------------------------------*/ + +static int knot_zone_contents_dnames_from_rdata_to_table( + knot_dname_table_t *table, knot_rdata_t *rdata, + knot_rrtype_descriptor_t *d) +{ + unsigned int count = knot_rdata_item_count(rdata); + int rc = 0; + assert(count <= d->length); + // for each RDATA item + for (unsigned int j = 0; j < count; ++j) { + if (d->wireformat[j] + == KNOT_RDATA_WF_COMPRESSED_DNAME + || d->wireformat[j] + == KNOT_RDATA_WF_UNCOMPRESSED_DNAME + || d->wireformat[j] + == KNOT_RDATA_WF_LITERAL_DNAME) { + dbg_zone("Saving dname from " + "rdata to dname table" + ".\n"); + rc = knot_dname_table_add_dname_check(table, + &knot_rdata_get_item(rdata, j)->dname); + if (rc < 0) { + dbg_zone("Error: %s\n", + knot_strerror(rc)); + return rc; + } + } + } + + dbg_zone("RDATA OK.\n"); + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_zone_contents_dnames_from_rrset_to_table( + knot_dname_table_t *table, knot_rrset_t *rrset, int replace_owner, + knot_dname_t *owner) +{ + assert(table != NULL && rrset != NULL && owner != NULL); + + if (replace_owner) { + // discard the old owner and replace it with the new + knot_rrset_set_owner(rrset, owner); + } + dbg_zone("RRSet owner: %p\n", rrset->owner); + + knot_rrtype_descriptor_t *desc = knot_rrtype_descriptor_by_type( + knot_rrset_type(rrset)); + if (desc == NULL) { + // not recognized RR type, ignore + dbg_zone("RRSet type not recognized.\n"); + return KNOT_EOK; + } + // for each RDATA in RRSet + knot_rdata_t *rdata = knot_rrset_get_rdata(rrset); + while (rdata != NULL) { + int rc = knot_zone_contents_dnames_from_rdata_to_table(table, + rdata, desc); + if (rc != KNOT_EOK) { + return rc; + } + + rdata = knot_rrset_rdata_get_next(rrset, rdata); + } + + dbg_zone("RRSet OK.\n"); + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_zone_contents_dnames_from_node_to_table( + knot_dname_table_t *table, knot_node_t *node) +{ + /* + * Assuming that all the RRSets have the same owner as the node. + */ + + // insert owner + char *name = knot_dname_to_str(node->owner); + dbg_zone("Node owner before inserting to dname table: %p.\n", + node->owner); + dbg_zone("Node owner before inserting to dname table: %s.\n", + name); + free(name); + //knot_dname_t *old_owner = node->owner; + int rc = knot_dname_table_add_dname_check(table, &node->owner); + if (rc < 0) { + dbg_zone("Failed to add dname to dname table.\n"); + return rc; + } + int replace_owner = (rc > 0); + dbg_zone("Node owner after inserting to dname table: %p.\n", + node->owner); + name = knot_dname_to_str(node->owner); + dbg_zone("Node owner after inserting to dname table: %s.\n", + name); + free(name); + + knot_rrset_t **rrsets = knot_node_get_rrsets(node); + // for each RRSet + for (int i = 0; i < knot_node_rrset_count(node); ++i) { + dbg_zone("Inserting RRSets from node to table.\n"); + rc = knot_zone_contents_dnames_from_rrset_to_table(table, + rrsets[i], replace_owner, node->owner); + if (rc != KNOT_EOK) { + return rc; + } + } + + free(rrsets); + + dbg_zone("Node OK\n"); + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_zone_contents_t *knot_zone_contents_new(knot_node_t *apex, + uint node_count, + int use_domain_table, + struct knot_zone *zone) +{ + knot_zone_contents_t *contents = (knot_zone_contents_t *) + calloc(1, sizeof(knot_zone_contents_t)); + if (contents == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + +// printf("created cont: %p (%s)\n", +// contents, knot_dname_to_str(apex->owner)); + + contents->apex = apex; + contents->zone = zone; + knot_node_set_zone(apex, zone); + + dbg_zone("Creating tree for normal nodes.\n"); + contents->nodes = malloc(sizeof(knot_zone_tree_t)); + if (contents->nodes == NULL) { + ERR_ALLOC_FAILED; + goto cleanup; + } + + dbg_zone("Creating tree for NSEC3 nodes.\n"); + contents->nsec3_nodes = malloc(sizeof(knot_zone_tree_t)); + if (contents->nsec3_nodes == NULL) { + ERR_ALLOC_FAILED; + goto cleanup; + } + + if (use_domain_table) { + dbg_zone("Creating domain name table.\n"); + contents->dname_table = knot_dname_table_new(); + if (contents->dname_table == NULL) { + ERR_ALLOC_FAILED; + goto cleanup; + } + } else { + contents->dname_table = NULL; + } + + contents->node_count = node_count; + + /* Initialize NSEC3 params */ + dbg_zone("Initializing NSEC3 parameters.\n"); + contents->nsec3_params.algorithm = 0; + contents->nsec3_params.flags = 0; + contents->nsec3_params.iterations = 0; + contents->nsec3_params.salt_length = 0; + contents->nsec3_params.salt = NULL; + + dbg_zone("Initializing zone trees.\n"); + if (knot_zone_tree_init(contents->nodes) != KNOT_EOK + || knot_zone_tree_init(contents->nsec3_nodes) != KNOT_EOK) { + goto cleanup; + } + + dbg_zone("Inserting apex into the zone tree.\n"); + if (knot_zone_tree_insert(contents->nodes, apex) != KNOT_EOK) { + dbg_zone("Failed to insert apex to the zone tree.\n"); + goto cleanup; + } + +#ifdef USE_HASH_TABLE + if (contents->node_count > 0) { + dbg_zone("Creating hash table.\n"); + contents->table = ck_create_table(contents->node_count); + if (contents->table == NULL) { + goto cleanup; + } + + // insert the apex into the hash table + dbg_zone("Inserting apex into the hash table.\n"); + if (ck_insert_item(contents->table, + (const char *)knot_dname_name( + knot_node_owner(apex)), + knot_dname_size(knot_node_owner(apex)), + (void *)apex) != 0) { + ck_destroy_table(&contents->table, NULL, 0); + goto cleanup; + } + } else { + contents->table = NULL; + } +#endif + + // insert names from the apex to the domain table + if (use_domain_table) { + dbg_zone("Inserting names from apex to table.\n"); + int rc = knot_zone_contents_dnames_from_node_to_table( + contents->dname_table, apex); + if (rc != KNOT_EOK) { + ck_destroy_table(&contents->table, NULL, 0); + goto cleanup; + } + } + + return contents; + +cleanup: + dbg_zone("Cleaning up.\n"); + free(contents->dname_table); + free(contents->nodes); + free(contents->nsec3_nodes); + free(contents); + return NULL; +} + +/*----------------------------------------------------------------------------*/ + +//short knot_zone_contents_generation(const knot_zone_contents_t *zone) +//{ +// return zone->generation; +//} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_gen_is_old(const knot_zone_contents_t *contents) +{ + return (contents->generation == 0); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_gen_is_new(const knot_zone_contents_t *contents) +{ + return (contents->generation == 1); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_gen_is_finished(const knot_zone_contents_t *contents) +{ + return (contents->generation == -1); +} + +/*----------------------------------------------------------------------------*/ + +//void knot_zone_contents_switch_generation(knot_zone_contents_t *zone) +//{ +// zone->generation = 1 - zone->generation; +//} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_contents_set_gen_old(knot_zone_contents_t *contents) +{ + contents->generation = 0; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_contents_set_gen_new(knot_zone_contents_t *contents) +{ + contents->generation = 1; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_contents_set_gen_new_finished(knot_zone_contents_t *contents) +{ + contents->generation = -1; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_zone_contents_class(const knot_zone_contents_t *contents) +{ + if (contents == NULL || contents->apex == NULL + || knot_node_rrset(contents->apex, KNOT_RRTYPE_SOA) == NULL) { + return KNOT_EBADARG; + } + + return knot_rrset_class(knot_node_rrset(contents->apex, + KNOT_RRTYPE_SOA)); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_add_node(knot_zone_contents_t *zone, + knot_node_t *node, int create_parents, + uint8_t flags, int use_domain_table) +{ + if (zone == NULL || node == NULL) { + return KNOT_EBADARG; + } + + int ret = 0; + if ((ret = knot_zone_contents_check_node(zone, node)) != 0) { + return ret; + } + + ret = knot_zone_tree_insert(zone->nodes, node); + if (ret != KNOT_EOK) { + dbg_zone("Failed to insert node into zone tree.\n"); + return ret; + } + +#ifdef USE_HASH_TABLE + char *name = knot_dname_to_str(node->owner); +// dbg_zone("Adding node with owner %s to hash table.\n", name); + free(name); + //assert(zone->table != NULL); + // add the node also to the hash table if authoritative, or deleg. point + if (zone->table != NULL + && ck_insert_item(zone->table, + (const char *)node->owner->name, + node->owner->size, (void *)node) != 0) { + dbg_zone("Error inserting node into hash table!\n"); + /*! \todo Remove the node from the tree. */ + return KNOT_EHASH; + } +#endif + assert(knot_zone_contents_find_node(zone, node->owner)); + + if (use_domain_table) { + ret = knot_zone_contents_dnames_from_node_to_table( + zone->dname_table, node); + if (ret != KNOT_EOK) { + /*! \todo Remove node from the tree and hash table.*/ + dbg_zone("Failed to add dnames into table.\n"); + return ret; + } + } + + knot_node_set_zone(node, zone->zone); + + if (!create_parents) { + return KNOT_EOK; + } + + dbg_zone("Creating parents of the node.\n"); + + knot_dname_t *chopped = + knot_dname_left_chop(knot_node_owner(node)); + if (knot_dname_compare(knot_node_owner(zone->apex), chopped) == 0) { + dbg_zone("Zone apex is the parent.\n"); + knot_node_set_parent(node, zone->apex); + } else { + knot_node_t *next_node; + while ((next_node + = knot_zone_contents_get_node(zone, chopped)) == NULL) { + /* Adding new dname to zone + add to table. */ + dbg_zone("Creating new node.\n"); + next_node = knot_node_new(chopped, NULL, flags); + if (next_node == NULL) { + /* Directly discard. */ + knot_dname_free(&chopped); + return KNOT_ENOMEM; + } + if (use_domain_table) { + ret = + knot_zone_contents_dnames_from_node_to_table( + zone->dname_table, next_node); + if (ret != KNOT_EOK) { + /*! \todo Will next_node leak? */ + knot_dname_release(chopped); + return ret; + } + } + + if (next_node->owner != chopped) { + /* Node owner was in RDATA */ + chopped = next_node->owner; + } + + assert(knot_zone_contents_find_node(zone, chopped) + == NULL); + assert(knot_node_owner(next_node) == chopped); + + dbg_zone("Inserting new node to zone tree.\n"); +// TREE_INSERT(zone->tree, knot_node, avl, next_node); + + ret = knot_zone_tree_insert(zone->nodes, + next_node); + if (ret != KNOT_EOK) { + dbg_zone("Failed to insert new node " + "to zone tree.\n"); + /*! \todo Delete the node?? */ + /* Directly discard. */ + knot_dname_release(chopped); + return ret; + } + +#ifdef USE_HASH_TABLE +dbg_zone_exec( + char *name = knot_dname_to_str( + knot_node_owner(next_node)); + dbg_zone("Adding new node with owner %s to " + "hash table.\n", name); + free(name); +); + + if (zone->table != NULL + && ck_insert_item(zone->table, + (const char *)knot_dname_name( + knot_node_owner(next_node)), + knot_dname_size(knot_node_owner(next_node)), + (void *)next_node) != 0) { + dbg_zone("Error inserting node into " + "hash table!\n"); + /*! \todo Delete the node?? */ + /* Directly discard. */ + knot_dname_release(chopped); + return KNOT_EHASH; + } + + // set parent + knot_node_set_parent(node, next_node); + + // set zone + knot_node_set_zone(next_node, zone->zone); + + // check if the node is not wildcard child of the parent + if (knot_dname_is_wildcard( + knot_node_owner(node))) { + knot_node_set_wildcard_child(next_node, node); + } +#endif + dbg_zone("Next parent.\n"); + node = next_node; + knot_dname_t *chopped_last = chopped; + chopped = knot_dname_left_chop(chopped); + + /* Release last chop, reference is already stored + * in next_node. + */ + knot_dname_release(chopped_last); + + } + // set the found parent (in the zone) as the parent of the last + // inserted node + assert(knot_node_parent(node, 0) == NULL); + knot_node_set_parent(node, next_node); + + dbg_zone("Created all parents.\n"); + } + + /* Directly discard. */ + /*! \todo This may be double-release. */ + knot_dname_release(chopped); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_add_rrset(knot_zone_contents_t *zone, + knot_rrset_t *rrset, knot_node_t **node, + knot_rrset_dupl_handling_t dupl, + int use_domain_table) +{ + if (zone == NULL || rrset == NULL || zone->apex == NULL + || zone->apex->owner == NULL || node == NULL) { + return KNOT_EBADARG; + } + + // check if the RRSet belongs to the zone + if (knot_dname_compare(knot_rrset_owner(rrset), + zone->apex->owner) != 0 + && !knot_dname_is_subdomain(knot_rrset_owner(rrset), + zone->apex->owner)) { + return KNOT_EBADZONE; + } + + if ((*node) == NULL + && (*node = knot_zone_contents_get_node(zone, + knot_rrset_owner(rrset))) == NULL) { + return KNOT_ENONODE; + } + + assert(*node != NULL); + + // add all domain names from the RRSet to domain name table + int rc; + + /*! \todo REMOVE RRSET */ + rc = knot_node_add_rrset(*node, rrset, + dupl == KNOT_RRSET_DUPL_MERGE); + if (rc < 0) { + dbg_zone("Failed to add RRSet to node.\n"); + return rc; + } + + int ret = rc; + + if (use_domain_table) { + dbg_zone("Saving RRSet to table.\n"); + rc = knot_zone_contents_dnames_from_rrset_to_table( + zone->dname_table, rrset, 0, (*node)->owner); + if (rc != KNOT_EOK) { + dbg_zone("Error saving domain names from " + "RRSIGs to the domain name table.\n " + "The zone may be in an inconsistent " + "state.\n"); + // WARNING: the zone is not in consistent state now - + // there may be domain names in it that are not inserted + // into the domain table + return rc; + } + } + + // replace RRSet's owner with the node's owner (that is already in the + // table) + /*! \todo Do even if domain table is not used?? */ + if (ret == KNOT_EOK && rrset->owner != (*node)->owner) { + knot_rrset_set_owner(rrset, (*node)->owner); + } + + dbg_zone("RRSet OK.\n"); + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_add_rrsigs(knot_zone_contents_t *zone, + knot_rrset_t *rrsigs, + knot_rrset_t **rrset, + knot_node_t **node, + knot_rrset_dupl_handling_t dupl, + int use_domain_table) +{ + if (zone == NULL || rrsigs == NULL || rrset == NULL || node == NULL + || zone->apex == NULL || zone->apex->owner == NULL) { +dbg_zone_exec( + dbg_zone("Parameters: zone=%p, rrsigs=%p, rrset=%p, " + "node=%p\n", zone, rrsigs, rrset, node); + if (zone != NULL) { + dbg_zone("zone->apex=%p\n", zone->apex); + if (zone->apex != NULL) { + dbg_zone("zone->apex->owner=%p\n", + zone->apex->owner); + } + } +); + return KNOT_EBADARG; + } + + // check if the RRSet belongs to the zone + if (*rrset != NULL + && knot_dname_compare(knot_rrset_owner(*rrset), + zone->apex->owner) != 0 + && !knot_dname_is_subdomain(knot_rrset_owner(*rrset), + zone->apex->owner)) { + return KNOT_EBADZONE; + } + + // check if the RRSIGs belong to the RRSet + if (*rrset != NULL + && (knot_dname_compare(knot_rrset_owner(rrsigs), + knot_rrset_owner(*rrset)) != 0)) { + dbg_zone("RRSIGs does not belong to the given RRSet.\n"); + return KNOT_EBADARG; + } + + // if no RRSet given, try to find the right RRSet + if (*rrset == NULL) { + // even no node given + // find proper node + knot_node_t *(*get_node)(const knot_zone_contents_t *, + const knot_dname_t *) + = (knot_rdata_rrsig_type_covered( + knot_rrset_rdata(rrsigs)) == KNOT_RRTYPE_NSEC3) + ? knot_zone_contents_get_nsec3_node + : knot_zone_contents_get_node; + + if (*node == NULL + && (*node = get_node( + zone, knot_rrset_owner(rrsigs))) == NULL) { + dbg_zone("Failed to find node for RRSIGs.\n"); + return KNOT_ENONODE; + } + + assert(*node != NULL); + + // find the RRSet in the node + // take only the first RDATA from the RRSIGs + dbg_zone("Finding RRSet for type %s\n", + knot_rrtype_to_string( + knot_rdata_rrsig_type_covered( + knot_rrset_rdata(rrsigs)))); + *rrset = knot_node_get_rrset( + *node, knot_rdata_rrsig_type_covered( + knot_rrset_rdata(rrsigs))); + if (*rrset == NULL) { + dbg_zone("Failed to find RRSet for RRSIGs.\n"); + return KNOT_ENORRSET; + } + } + + assert(*rrset != NULL); + + // add all domain names from the RRSet to domain name table + int rc; + int ret = KNOT_EOK; + + rc = knot_rrset_add_rrsigs(*rrset, rrsigs, dupl); + if (rc < 0) { + dbg_dname("Failed to add RRSIGs to RRSet.\n"); + return rc; + } else if (rc > 0) { + assert(dupl == KNOT_RRSET_DUPL_MERGE); + ret = 1; + } + + if (use_domain_table) { + dbg_zone("Saving RRSIG RRSet to table.\n"); + rc = knot_zone_contents_dnames_from_rrset_to_table( + zone->dname_table, rrsigs, 0, (*rrset)->owner); + if (rc != KNOT_EOK) { + dbg_zone("Error saving domain names from " + "RRSIGs to the domain name table.\n " + "The zone may be in an inconsistent " + "state.\n"); + // WARNING: the zone is not in consistent state now - + // there may be domain names in it that are not inserted + // into the domain table + return rc; + } + } + + // replace RRSet's owner with the node's owner (that is already in the + // table) + if ((*rrset)->owner != (*rrset)->rrsigs->owner) { + knot_rrset_set_owner((*rrset)->rrsigs, (*rrset)->owner); + } + + dbg_zone("RRSIGs OK\n"); + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_add_nsec3_node(knot_zone_contents_t *zone, + knot_node_t *node, int create_parents, + uint8_t flags, int use_domain_table) +{ + UNUSED(create_parents); + UNUSED(flags); + + if (zone == NULL || node == NULL) { + return KNOT_EBADARG; + } + + int ret = 0; + if ((ret = knot_zone_contents_check_node(zone, node)) != 0) { + return ret; + } + + // how to know if this is successfull?? +// TREE_INSERT(zone->nsec3_nodes, knot_node, avl, node); + knot_zone_tree_insert(zone->nsec3_nodes, node); + + if (use_domain_table) { + ret = knot_zone_contents_dnames_from_node_to_table( + zone->dname_table, node); + if (ret != KNOT_EOK) { + /*! \todo Remove the node from the tree. */ + dbg_zone("Failed to add dnames into table.\n"); + return ret; + } + } + + // no parents to be created, the only parent is the zone apex + // set the apex as the parent of the node + knot_node_set_parent(node, zone->apex); + + // set the zone to the node + knot_node_set_zone(node, zone->zone); + + // cannot be wildcard child, so nothing to be done + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_add_nsec3_rrset(knot_zone_contents_t *zone, + knot_rrset_t *rrset, + knot_node_t **node, + knot_rrset_dupl_handling_t dupl, + int use_domain_table) +{ + if (zone == NULL || rrset == NULL || zone->apex == NULL + || zone->apex->owner == NULL || node == NULL) { + return KNOT_EBADARG; + } + + // check if the RRSet belongs to the zone + if (knot_dname_compare(knot_rrset_owner(rrset), + zone->apex->owner) != 0 + && !knot_dname_is_subdomain(knot_rrset_owner(rrset), + zone->apex->owner)) { + return KNOT_EBADZONE; + } + + if ((*node) == NULL + && (*node = knot_zone_contents_get_nsec3_node( + zone, knot_rrset_owner(rrset))) == NULL) { + return KNOT_ENONODE; + } + + assert(*node != NULL); + + // add all domain names from the RRSet to domain name table + int rc; + + /*! \todo REMOVE RRSET */ + rc = knot_node_add_rrset(*node, rrset, + dupl == KNOT_RRSET_DUPL_MERGE); + if (rc < 0) { + return rc; + } + + int ret = rc; + + if (use_domain_table) { + dbg_zone("Saving NSEC3 RRSet to table.\n"); + rc = knot_zone_contents_dnames_from_rrset_to_table( + zone->dname_table, rrset, 0, (*node)->owner); + if (rc != KNOT_EOK) { + dbg_zone("Error saving domain names from " + "RRSIGs to the domain name table.\n " + "The zone may be in an inconsistent " + "state.\n"); + // WARNING: the zone is not in consistent state now - + // there may be domain names in it that are not inserted + // into the domain table + return rc; + } + } + + // replace RRSet's owner with the node's owner (that is already in the + // table) + /*! \todo Do even if domain table is not used? */ + if (rrset->owner != (*node)->owner) { + knot_rrset_set_owner(rrset, (*node)->owner); + } + + dbg_zone("NSEC3 OK\n"); + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_remove_node(knot_zone_contents_t *contents, + const knot_node_t *node, knot_zone_tree_node_t **removed_tree, + ck_hash_table_item_t **removed_hash) +{ + if (contents == NULL || node == NULL) { + return KNOT_EBADARG; + } + + const knot_dname_t *owner = knot_node_owner(node); + + // 1) remove the node from hash table + *removed_hash = NULL; + *removed_hash = ck_remove_item(contents->table, + (const char *)knot_dname_name(owner), + knot_dname_size(owner)); +// int ret = ck_detete_item(contents->table, +// (const char *)knot_dname_name(owner), +// knot_dname_size(owner), NULL, 0); + if (*removed_hash == NULL) { + return KNOT_ENONODE; + } + + // 2) remove the node from the zone tree + *removed_tree = NULL; + int ret = knot_zone_tree_remove(contents->nodes, owner, removed_tree); + if (ret != KNOT_EOK) { + return KNOT_ENONODE; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_remove_nsec3_node(knot_zone_contents_t *contents, + const knot_node_t *node, knot_zone_tree_node_t **removed) +{ + if (contents == NULL || node == NULL) { + return KNOT_EBADARG; + } + + const knot_dname_t *owner = knot_node_owner(node); + + // remove the node from the zone tree + *removed = NULL; + int ret = knot_zone_tree_remove(contents->nsec3_nodes, owner, removed); + if (ret != KNOT_EOK) { + return KNOT_ENONODE; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_create_and_fill_hash_table( + knot_zone_contents_t *zone) +{ + if (zone == NULL || zone->apex == NULL || zone->apex->owner == NULL) { + return KNOT_EBADARG; + } + /* + * 1) Create hash table. + */ +#ifdef USE_HASH_TABLE + if (zone->node_count > 0) { + zone->table = ck_create_table(zone->node_count); + if (zone->table == NULL) { + return KNOT_ENOMEM; + } + + // insert the apex into the hash table + if (ck_insert_item(zone->table, + (const char *)zone->apex->owner->name, + zone->apex->owner->size, + (void *)zone->apex) != 0) { + return KNOT_EHASH; + } + } else { + zone->table = NULL; + return KNOT_EOK; // OK? + } + + /* + * 2) Fill in the hash table. + * + * In this point, the nodes in the zone must be adjusted, so that only + * relevant nodes (authoritative and delegation points are inserted. + * + * TODO: how to know if this was successful?? + */ + /*! \todo Replace by zone tree. */ + int ret = knot_zone_tree_forward_apply_inorder(zone->nodes, + knot_zone_contents_node_to_hash, zone); + if (ret != KNOT_EOK) { + dbg_zone("Failed to insert nodes to hash table.\n"); + return ret; + } + +#endif + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_zone_contents_get_node(const knot_zone_contents_t *zone, + const knot_dname_t *name) +{ + if (zone == NULL || name == NULL) { + return NULL; + } + + // create dummy node to use for lookup +// knot_node_t *tmp = knot_node_new((knot_dname_t *)name, NULL); +// knot_node_t *n = TREE_FIND(zone->tree, knot_node, avl, tmp); +// knot_node_free(&tmp, 0); + + knot_node_t *n; + int ret = knot_zone_tree_get(zone->nodes, name, &n); + if (ret != KNOT_EOK) { + dbg_zone("Failed to find name in the zone tree.\n"); + return NULL; + } + + return n; +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_zone_contents_get_nsec3_node( + const knot_zone_contents_t *zone, const knot_dname_t *name) +{ + if (zone == NULL || name == NULL) { + return NULL; + } + + // create dummy node to use for lookup +// knot_node_t *tmp = knot_node_new((knot_dname_t *)name, NULL); +// knot_node_t *n = TREE_FIND(zone->nsec3_nodes, knot_node, avl, tmp); +// knot_node_free(&tmp, 0); + knot_node_t *n; + int ret = knot_zone_tree_get(zone->nsec3_nodes, name, &n); + + if (ret != KNOT_EOK) { + dbg_zone("Failed to find NSEC3 name in the zone tree." + "\n"); + return NULL; + } + + return n; +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_zone_contents_find_node( + const knot_zone_contents_t *zone,const knot_dname_t *name) +{ + return knot_zone_contents_get_node(zone, name); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_find_dname(const knot_zone_contents_t *zone, + const knot_dname_t *name, + const knot_node_t **node, + const knot_node_t **closest_encloser, + const knot_node_t **previous) +{ + if (zone == NULL || name == NULL || node == NULL + || closest_encloser == NULL || previous == NULL + || zone->apex == NULL || zone->apex->owner == NULL) { + return KNOT_EBADARG; + } + +dbg_zone_exec( + char *name_str = knot_dname_to_str(name); + char *zone_str = knot_dname_to_str(zone->apex->owner); + dbg_zone("Searching for name %s in zone %s...\n", + name_str, zone_str); + free(name_str); + free(zone_str); +); + + if (knot_dname_compare(name, zone->apex->owner) == 0) { + *node = zone->apex; + *closest_encloser = *node; + return KNOT_ZONE_NAME_FOUND; + } + + if (!knot_dname_is_subdomain(name, zone->apex->owner)) { + *node = NULL; + *closest_encloser = NULL; + return KNOT_EBADZONE; + } + + knot_node_t *found = NULL, *prev = NULL; + + int exact_match = knot_zone_contents_find_in_tree(zone->nodes, name, + &found, &prev); + assert(exact_match >= 0); + *node = found; + *previous = prev; + +dbg_zone_exec( + char *name_str = (*node) ? knot_dname_to_str((*node)->owner) + : "(nil)"; + char *name_str2 = (*previous != NULL) + ? knot_dname_to_str((*previous)->owner) + : "(nil)"; + dbg_zone("Search function returned %d, node %s and prev: %s\n", + exact_match, name_str, name_str2); + + if (*node) { + free(name_str); + } + if (*previous != NULL) { + free(name_str2); + } +); + + *closest_encloser = *node; + + // there must be at least one node with domain name less or equal to + // the searched name if the name belongs to the zone (the root) + if (*node == NULL) { + return KNOT_EBADZONE; + } + + // TODO: this could be replaced by saving pointer to closest encloser + // in node + + if (!exact_match) { + int matched_labels = knot_dname_matched_labels( + knot_node_owner((*closest_encloser)), name); + while (matched_labels < knot_dname_label_count( + knot_node_owner((*closest_encloser)))) { + (*closest_encloser) = + knot_node_parent((*closest_encloser), 1); + assert(*closest_encloser); + } + } +dbg_zone_exec( + char *n = knot_dname_to_str(knot_node_owner((*closest_encloser))); + dbg_zone("Closest encloser: %s\n", n); + free(n); +); + + dbg_zone("find_dname() returning %d\n", exact_match); + + return (exact_match) + ? KNOT_ZONE_NAME_FOUND + : KNOT_ZONE_NAME_NOT_FOUND; +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_zone_contents_get_previous( + const knot_zone_contents_t *zone, const knot_dname_t *name) +{ + if (zone == NULL || name == NULL) { + return NULL; + } + + knot_node_t *found = NULL, *prev = NULL; + + int exact_match = knot_zone_contents_find_in_tree(zone->nodes, name, + &found, &prev); + assert(exact_match >= 0); + assert(prev != NULL); + + return prev; +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_zone_contents_find_previous( + const knot_zone_contents_t *zone, const knot_dname_t *name) +{ + return knot_zone_contents_get_previous(zone, name); +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_zone_contents_get_previous_nsec3( + const knot_zone_contents_t *zone, const knot_dname_t *name) +{ + if (zone == NULL || name == NULL) { + return NULL; + } + + knot_node_t *found = NULL, *prev = NULL; + + int exact_match = knot_zone_contents_find_in_tree(zone->nsec3_nodes, + name, &found, &prev); + assert(exact_match >= 0); + assert(prev != NULL); + + return prev; +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_zone_contents_find_previous_nsec3( + const knot_zone_contents_t *zone, const knot_dname_t *name) +{ + return knot_zone_contents_get_previous(zone, name); +} + +/*----------------------------------------------------------------------------*/ + +static void knot_zone_contents_left_chop(char *name, size_t *size) +{ + short label_size = name[0]; + + memmove(name, name + label_size + 1, *size -label_size - 1); + *size = *size - label_size - 1; +} + +/*----------------------------------------------------------------------------*/ +#ifdef USE_HASH_TABLE +int knot_zone_contents_find_dname_hash(const knot_zone_contents_t *zone, + const knot_dname_t *name, + const knot_node_t **node, + const knot_node_t **closest_encloser) +{ + if (zone == NULL || name == NULL || node == NULL + || closest_encloser == NULL) { + return KNOT_EBADARG; + } + +dbg_zone_exec( + char *name_str = knot_dname_to_str(name); + char *zone_str = knot_dname_to_str(zone->apex->owner); + dbg_zone("Searching for name %s in zone %s...\n", + name_str, zone_str); + free(name_str); + free(zone_str); +); + + if (knot_dname_compare(name, zone->apex->owner) == 0) { + *node = zone->apex; + *closest_encloser = *node; + return KNOT_ZONE_NAME_FOUND; + } + + if (!knot_dname_is_subdomain(name, zone->apex->owner)) { + *node = NULL; + *closest_encloser = NULL; + return KNOT_EBADZONE; + } + + // temporary name used for hashing + char name_tmp[KNOT_MAX_DNAME_LENGTH]; + size_t name_size = name->size; + if (knot_dname_to_lower_copy(name, name_tmp, KNOT_MAX_DNAME_LENGTH) + != KNOT_EOK) { + return KNOT_ERROR; + } + + const ck_hash_table_item_t *item = ck_find_item(zone->table, + name_tmp, name_size); + + if (item != NULL) { + *node = (const knot_node_t *)item->value; + *closest_encloser = *node; + + dbg_zone("Found node in hash table: %p (owner %p, " + "labels: %d)\n", *node, (*node)->owner, + knot_dname_label_count((*node)->owner)); + assert(*node != NULL); + assert(*closest_encloser != NULL); + return KNOT_ZONE_NAME_FOUND; + } + + *node = NULL; + + // chop leftmost labels until some node is found + // copy the name for chopping + /* Local allocation, will be discarded. */ + //knot_dname_t *name_copy = knot_dname_deep_copy(name); +dbg_zone_exec( + //char *n = knot_dname_to_str(name_copy); + dbg_zone("Finding closest encloser..\nStarting with: %.*s\n", + (int)name_size, name_tmp); + //free(n); +); + + while (item == NULL) { + //knot_dname_left_chop_no_copy(name_copy); + knot_zone_contents_left_chop(name_tmp, &name_size); +dbg_zone_exec( + //char *n = knot_dname_to_str(name_copy); + dbg_zone("Chopped leftmost label: %.*s\n", + (int)name_size, name_tmp); + //free(n); +); + // not satisfied in root zone!! + //assert(name_copy->label_count > 0); + assert(name_size > 0); + + item = ck_find_item(zone->table, name_tmp, name_size); + } + + /* Directly discard. */ + //knot_dname_free(&name_copy); + + assert(item != NULL); + *closest_encloser = (const knot_node_t *)item->value; + + return KNOT_ZONE_NAME_NOT_FOUND; +} +#endif +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_zone_contents_find_nsec3_node( + const knot_zone_contents_t *zone, const knot_dname_t *name) +{ + return knot_zone_contents_get_nsec3_node(zone, name); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_find_nsec3_for_name(const knot_zone_contents_t *zone, + const knot_dname_t *name, + const knot_node_t **nsec3_node, + const knot_node_t **nsec3_previous, + int check_ver) +{ + if (zone == NULL || name == NULL + || nsec3_node == NULL || nsec3_previous == NULL) { + return KNOT_EBADARG; + } + + knot_dname_t *nsec3_name = NULL; + int ret = knot_zone_contents_nsec3_name(zone, name, &nsec3_name); + + if (ret != KNOT_EOK) { + return ret; + } + +dbg_zone_exec( + char *n = knot_dname_to_str(nsec3_name); + dbg_zone("NSEC3 node name: %s.\n", n); + free(n); +); + + const knot_node_t *found = NULL, *prev = NULL; + + // create dummy node to use for lookup + int exact_match = knot_zone_tree_find_less_or_equal( + zone->nsec3_nodes, nsec3_name, &found, &prev, check_ver); + assert(exact_match >= 0); + + knot_dname_release(nsec3_name); + +dbg_zone_exec( + if (found) { + char *n = knot_dname_to_str(found->owner); + dbg_zone("Found NSEC3 node: %s.\n", n); + free(n); + } else { + dbg_zone("Found no NSEC3 node.\n"); + } + + if (prev) { + assert(prev->owner); + char *n = knot_dname_to_str(prev->owner); + dbg_zone("Found previous NSEC3 node: %s.\n", n); + free(n); + } else { + dbg_zone("Found no previous NSEC3 node.\n"); + } +); + *nsec3_node = found; + + if (prev == NULL) { + // either the returned node is the root of the tree, or it is + // the leftmost node in the tree; in both cases node was found + // set the previous node of the found node + assert(exact_match); + assert(*nsec3_node != NULL); + *nsec3_previous = knot_node_previous(*nsec3_node, check_ver); + } else { + *nsec3_previous = prev; + } + + dbg_zone("find_nsec3_for_name() returning %d\n", exact_match); + + return (exact_match) + ? KNOT_ZONE_NAME_FOUND + : KNOT_ZONE_NAME_NOT_FOUND; +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_zone_contents_apex( + const knot_zone_contents_t *zone) +{ + if (zone == NULL) { + return NULL; + } + + return zone->apex; +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_zone_contents_get_apex(const knot_zone_contents_t *zone) +{ + if (zone == NULL) { + return NULL; + } + + return zone->apex; +} + +/*----------------------------------------------------------------------------*/ + +//knot_dname_t *knot_zone_contents_name(const knot_zone_contents_t *zone) +//{ +// if (zone == NULL) { +// return NULL; +// } + +// return zone->name; +//} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_adjust(knot_zone_contents_t *zone, int check_ver) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + // load NSEC3PARAM (needed on adjusting function) + knot_zone_contents_load_nsec3param(zone); + + knot_zone_adjust_arg_t adjust_arg; + adjust_arg.zone = zone; + adjust_arg.first_node = NULL; + adjust_arg.previous_node = NULL; + adjust_arg.check_ver = check_ver; + + dbg_zone("Adjusting normal nodes.\n"); + int ret = knot_zone_tree_forward_apply_inorder(zone->nodes, + knot_zone_contents_adjust_node_in_tree, + &adjust_arg); + if (ret != KNOT_EOK) { + return ret; + } + dbg_zone("Done.\n"); + + assert(zone->apex == adjust_arg.first_node); + knot_node_set_previous(zone->apex, adjust_arg.previous_node); + + adjust_arg.first_node = NULL; + adjust_arg.previous_node = NULL; + + dbg_zone("Adjusting NSEC3 nodes.\n"); + ret = knot_zone_tree_forward_apply_inorder( + zone->nsec3_nodes, + knot_zone_contents_adjust_nsec3_node_in_tree, + &adjust_arg); + + dbg_zone("Done.\n"); + if (adjust_arg.first_node) { + knot_node_set_previous(adjust_arg.first_node, + adjust_arg.previous_node); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_load_nsec3param(knot_zone_contents_t *zone) +{ + if (zone == NULL || zone->apex == NULL) { + return KNOT_EBADARG; + } + + const knot_rrset_t *rrset = knot_node_rrset(zone->apex, + KNOT_RRTYPE_NSEC3PARAM); + + if (rrset != NULL) { + knot_nsec3_params_from_wire(&zone->nsec3_params, rrset); + } else { + memset(&zone->nsec3_params, 0, sizeof(knot_nsec3_params_t)); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_nsec3_enabled(const knot_zone_contents_t *zone) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + //return (zone->nsec3_params.algorithm != 0); + return (zone->nsec3_nodes->th_root != NULL); +} + +/*----------------------------------------------------------------------------*/ + +const knot_nsec3_params_t *knot_zone_contents_nsec3params( + const knot_zone_contents_t *zone) +{ + if (zone == NULL) { + return NULL; + } + + if (knot_zone_contents_nsec3_enabled(zone)) { + return &zone->nsec3_params; + } else { + return NULL; + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_tree_apply_postorder(knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, void *data), + void *data) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_func_t f; + f.func = function; + f.data = data; + + return knot_zone_tree_forward_apply_postorder(zone->nodes, + knot_zone_tree_apply, &f); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_tree_apply_inorder(knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, void *data), + void *data) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_func_t f; + f.func = function; + f.data = data; + + return knot_zone_tree_forward_apply_inorder(zone->nodes, + knot_zone_tree_apply, &f); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_tree_apply_inorder_reverse( + knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, void *data), void *data) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_func_t f; + f.func = function; + f.data = data; + + return knot_zone_tree_reverse_apply_inorder(zone->nodes, + knot_zone_tree_apply, &f); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_nsec3_apply_postorder(knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, void *data), + void *data) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_func_t f; + f.func = function; + f.data = data; + + return knot_zone_tree_forward_apply_postorder( + zone->nsec3_nodes, knot_zone_tree_apply, &f); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_nsec3_apply_inorder(knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, void *data), + void *data) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_func_t f; + f.func = function; + f.data = data; + + return knot_zone_tree_forward_apply_inorder( + zone->nsec3_nodes, knot_zone_tree_apply, &f); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_nsec3_apply_inorder_reverse( + knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, void *data), void *data) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_func_t f; + f.func = function; + f.data = data; + + return knot_zone_tree_reverse_apply_inorder( + zone->nsec3_nodes, knot_zone_tree_apply, &f); +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_tree_t *knot_zone_contents_get_nodes( + knot_zone_contents_t *contents) +{ + return contents->nodes; +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_tree_t *knot_zone_contents_get_nsec3_nodes( + knot_zone_contents_t *contents) +{ + return contents->nsec3_nodes; +} + +/*----------------------------------------------------------------------------*/ + +ck_hash_table_t *knot_zone_contents_get_hash_table( + knot_zone_contents_t *contents) +{ + return contents->table; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_dname_table_apply(knot_zone_contents_t *contents, + void (*function)(knot_dname_t *, + void *), + void *data) +{ + if (contents == NULL || function == NULL) { + return KNOT_EBADARG; + } + + knot_dname_table_tree_inorder_apply(contents->dname_table, + function, data); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_shallow_copy(const knot_zone_contents_t *from, + knot_zone_contents_t **to) +{ + if (from == NULL || to == NULL) { + return KNOT_EBADARG; + } + + /* Copy to same destination as source. */ + if (from == *to) { + return KNOT_EBADARG; + } + + int ret = KNOT_EOK; + + knot_zone_contents_t *contents = (knot_zone_contents_t *)calloc( + 1, sizeof(knot_zone_contents_t)); + if (contents == NULL) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + contents->apex = from->apex; + + contents->nodes = malloc(sizeof(knot_zone_tree_t)); + if (contents->nodes == NULL) { + ERR_ALLOC_FAILED; + ret = KNOT_ENOMEM; + goto cleanup; + } + + contents->nsec3_nodes = malloc(sizeof(knot_zone_tree_t)); + if (contents->nsec3_nodes == NULL) { + ERR_ALLOC_FAILED; + ret = KNOT_ENOMEM; + goto cleanup; + } + + if (from->dname_table != NULL) { + contents->dname_table = knot_dname_table_new(); + if (contents->dname_table == NULL) { + ERR_ALLOC_FAILED; + ret = KNOT_ENOMEM; + goto cleanup; + } + if ((ret = knot_dname_table_shallow_copy(from->dname_table, + contents->dname_table)) != KNOT_EOK) { + goto cleanup; + } + } else { + contents->dname_table = NULL; + } + + contents->node_count = from->node_count; + contents->generation = from->generation; + + contents->zone = from->zone; + + /* Initialize NSEC3 params */ + memcpy(&contents->nsec3_params, &from->nsec3_params, + sizeof(knot_nsec3_params_t)); + + if ((ret = knot_zone_tree_shallow_copy(from->nodes, + contents->nodes)) != KNOT_EOK + || (ret = knot_zone_tree_shallow_copy(from->nsec3_nodes, + contents->nsec3_nodes)) != KNOT_EOK) { + goto cleanup; + } + +#ifdef USE_HASH_TABLE + if (from->table != NULL) { +// ret = ck_copy_table(from->table, &contents->table); + ret = ck_shallow_copy(from->table, &contents->table); + if (ret != 0) { + dbg_zone("knot_zone_contents_shallow_copy: " + "hash table copied\n"); + ret = KNOT_ERROR; + goto cleanup; + } + } +#endif + + dbg_zone("knot_zone_contents_shallow_copy: " + "finished OK\n"); + + *to = contents; + return KNOT_EOK; + +cleanup: + knot_zone_tree_free(&contents->nodes); + knot_zone_tree_free(&contents->nsec3_nodes); + free(contents->dname_table); + free(contents); + return ret; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_contents_free(knot_zone_contents_t **contents) +{ + if (contents == NULL || *contents == NULL) { + return; + } + + // free the zone tree, but only the structure + knot_zone_tree_free(&(*contents)->nodes); + knot_zone_tree_free(&(*contents)->nsec3_nodes); + +#ifdef USE_HASH_TABLE + if ((*contents)->table != NULL) { + ck_destroy_table(&(*contents)->table, NULL, 0); + } +#endif + knot_nsec3_params_free(&(*contents)->nsec3_params); + + knot_dname_table_free(&(*contents)->dname_table); + + free(*contents); + *contents = NULL; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_contents_deep_free(knot_zone_contents_t **contents, + int destroy_dname_table) +{ + if (contents == NULL || *contents == NULL) { + return; + } + + if ((*contents) != NULL) { + +#ifdef USE_HASH_TABLE + if ((*contents)->table != NULL) { + ck_destroy_table(&(*contents)->table, NULL, 0); + } +#endif + /* has to go through zone twice, rdata may contain references to + node owners earlier in the zone which may be already freed */ + /* NSEC3 tree is deleted first as it may contain references to + the normal tree. */ + + knot_zone_tree_reverse_apply_postorder( + (*contents)->nsec3_nodes, + knot_zone_contents_destroy_node_rrsets_from_tree, + (void*)1); + + knot_zone_tree_reverse_apply_postorder( + (*contents)->nsec3_nodes, + knot_zone_contents_destroy_node_owner_from_tree, 0); + + knot_zone_tree_reverse_apply_postorder( + (*contents)->nodes, + knot_zone_contents_destroy_node_rrsets_from_tree, + (void*)1); + + knot_zone_tree_reverse_apply_postorder( + (*contents)->nodes, + knot_zone_contents_destroy_node_owner_from_tree, 0); + + // free the zone tree, but only the structure + // (nodes are already destroyed) + dbg_zone("Destroying zone tree.\n"); + knot_zone_tree_free(&(*contents)->nodes); + dbg_zone("Destroying NSEC3 zone tree.\n"); + knot_zone_tree_free(&(*contents)->nsec3_nodes); + + knot_nsec3_params_free(&(*contents)->nsec3_params); + + if (destroy_dname_table) { + /* + * Hack, used in zcompile - destroys the table using + * dname_free() instead of dname_retain(). + */ + knot_dname_table_destroy(&(*contents)->dname_table); + } else { + knot_dname_table_deep_free(&(*contents)->dname_table); + } + } + + free((*contents)); + *contents = NULL; +} + diff --git a/src/libknot/zone/zone-contents.h b/src/libknot/zone/zone-contents.h new file mode 100644 index 0000000..2856f76 --- /dev/null +++ b/src/libknot/zone/zone-contents.h @@ -0,0 +1,556 @@ +/*! + * \file zone-contents.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Zone contents structure and API for manipulating it. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_ZONE_CONTENTS_H_ +#define _KNOT_ZONE_CONTENTS_H_ + +//#include <time.h> + +#include "zone/node.h" +#include "dname.h" +#include "nsec3.h" +#include "zone/dname-table.h" +#include "common/tree.h" +#include "hash/cuckoo-hash-table.h" + +#include "zone-tree.h" + +struct knot_zone; + +/*----------------------------------------------------------------------------*/ + +typedef struct knot_zone_contents_t { + knot_node_t *apex; /*!< Apex node of the zone (holding SOA) */ + + ck_hash_table_t *table; /*!< Hash table for holding zone nodes. */ + knot_zone_tree_t *nodes; + knot_zone_tree_t *nsec3_nodes; + + /*! + * \todo Unify the use of this field - authoritative nodes vs. all. + */ + uint node_count; + + knot_dname_table_t *dname_table; + + knot_nsec3_params_t nsec3_params; + + /*! \brief Generation of the zone during update. + * + * Possible values: + * - 0 - Original version of the zone. Old nodes should be used. + * - 1 - New (updated) zone. New nodes should be used. + * - -1 - New (updated) zone, but exactly the stored nodes should be + * used, no matter their generation. + */ + short generation; + + struct knot_zone *zone; +} knot_zone_contents_t; + +/*----------------------------------------------------------------------------*/ + +knot_zone_contents_t *knot_zone_contents_new(knot_node_t *apex, + uint node_count, + int use_domain_table, + struct knot_zone *zone); + +time_t knot_zone_contents_version(const knot_zone_contents_t *contents); + +void knot_zone_contents_set_version(knot_zone_contents_t *contents, + time_t version); + +//short knot_zone_contents_generation(const knot_zone_contents_t *contents); + +int knot_zone_contents_gen_is_old(const knot_zone_contents_t *contents); +int knot_zone_contents_gen_is_new(const knot_zone_contents_t *contents); +int knot_zone_contents_gen_is_finished(const knot_zone_contents_t *contents); + +//void knot_zone_contents_switch_generation(knot_zone_contents_t *contents); + +void knot_zone_contents_set_gen_old(knot_zone_contents_t *contents); +void knot_zone_contents_set_gen_new(knot_zone_contents_t *contents); +void knot_zone_contents_set_gen_new_finished(knot_zone_contents_t *contents); + +uint16_t knot_zone_contents_class(const knot_zone_contents_t *contents); + +/*! + * \brief Adds a node to the given zone. + * + * Checks if the node belongs to the zone, i.e. if its owner is a subdomain of + * the zone's apex. It thus also forbids adding node with the same name as the + * zone apex. + * + * \warning This function may destroy domain names saved in the node, that + * are already present in the zone. + * + * \param zone Zone to add the node into. + * \param node Node to add into the zone. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_EBADZONE + * \retval KNOT_EHASH + */ +int knot_zone_contents_add_node(knot_zone_contents_t *contents, + knot_node_t *node, int create_parents, + uint8_t flags, int use_domain_table); + +/*! + * \brief Adds a RRSet to the given zone. + * + * Checks if the RRSet belongs to the zone, i.e. if its owner is a subdomain of + * the zone's apex. The RRSet is inserted only if the node is given, or if + * a node where the RRSet should belong is found in the zone. + * + * \warning The function does not check if the node is already inserted in the + * zone, just assumes that it is. + * \warning This function may destroy domain names saved in the RRSet, that + * are already present in the zone. + * + * \param zone Zone to add the node into. + * \param rrset RRSet to add into the zone. + * \param node Node the RRSet should be inserted into. (Should be a node of the + * given zone.) If set to NULL, the function will find proper node + * and set it to this parameter. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_EBADZONE + */ +int knot_zone_contents_add_rrset(knot_zone_contents_t *contents, + knot_rrset_t *rrset, + knot_node_t **node, + knot_rrset_dupl_handling_t dupl, + int use_domain_table); + +int knot_zone_contents_add_rrsigs(knot_zone_contents_t *contents, + knot_rrset_t *rrsigs, + knot_rrset_t **rrset, knot_node_t **node, + knot_rrset_dupl_handling_t dupl, + int use_domain_table); + +/*! + * \brief Adds a node holding NSEC3 records to the given zone. + * + * Checks if the node belongs to the zone, i.e. if its owner is a subdomain of + * the zone's apex. It does not check if the node really contains any NSEC3 + * records, nor if the name is a hash (as there is actually no way of + * determining this). + * + * \param zone Zone to add the node into. + * \param node Node to add into the zone. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_EBADZONE + */ +int knot_zone_contents_add_nsec3_node(knot_zone_contents_t *contents, + knot_node_t *node, int create_parents, + uint8_t flags, int use_domain_table); + +int knot_zone_contents_add_nsec3_rrset(knot_zone_contents_t *contents, + knot_rrset_t *rrset, + knot_node_t **node, + knot_rrset_dupl_handling_t dupl, + int use_domain_table); + +int knot_zone_contents_remove_node(knot_zone_contents_t *contents, + const knot_node_t *node, knot_zone_tree_node_t **removed_tree, + ck_hash_table_item_t **removed_hash); + +//knot_zone_tree_node_t *knot_zone_contents_remove_node( +// knot_zone_contents_t *contents, const knot_node_t *node); + +int knot_zone_contents_remove_nsec3_node(knot_zone_contents_t *contents, + const knot_node_t *node, knot_zone_tree_node_t **removed); + +/*! + * \warning Always call knot_zone_adjust_dnames() prior to calling this + * function. Otherwise the node count would not be set. + * + * \note Currently, all nodes (even non-authoritative) are inserted into the + * hash table. + */ +int knot_zone_contents_create_and_fill_hash_table( + knot_zone_contents_t *contents); + +/*! + * \brief Tries to find a node with the specified name in the zone. + * + * \param zone Zone where the name should be searched for. + * \param name Name to find. + * + * \return Corresponding node if found, NULL otherwise. + */ +knot_node_t *knot_zone_contents_get_node( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +/*! + * \brief Tries to find a node with the specified name among the NSEC3 nodes + * of the zone. + * + * \param zone Zone where the name should be searched for. + * \param name Name to find. + * + * \return Corresponding node if found, NULL otherwise. + */ +knot_node_t *knot_zone_contents_get_nsec3_node( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +/*! + * \brief Tries to find a node with the specified name in the zone. + * + * \note This function is identical to knot_zone_contents_get_node(), only it returns + * constant reference. + * + * \param zone Zone where the name should be searched for. + * \param name Name to find. + * + * \return Corresponding node if found, NULL otherwise. + */ +const knot_node_t *knot_zone_contents_find_node( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +/*! + * \brief Tries to find domain name in the given zone using AVL tree. + * + * \param[in] zone Zone to search for the name. + * \param[in] name Domain name to search for. + * \param[out] node The found node (if it was found, otherwise it may contain + * arbitrary node). + * \param[out] closest_encloser Closest encloser of the given name in the zone. + * \param[out] previous Previous domain name in canonical order. + * + * \retval KNOT_ZONE_NAME_FOUND if node with owner \a name was found. + * \retval KNOT_ZONE_NAME_NOT_FOUND if it was not found. + * \retval KNOT_EBADARG + * \retval KNOT_EBADZONE + */ +int knot_zone_contents_find_dname(const knot_zone_contents_t *contents, + const knot_dname_t *name, + const knot_node_t **node, + const knot_node_t **closest_encloser, + const knot_node_t **previous); + +/*! + * \brief Finds previous name in canonical order to the given name in the zone. + * + * \param zone Zone to search for the name. + * \param name Domain name to find the previous domain name of. + * + * \return Previous node in canonical order, or NULL if some parameter is wrong. + */ +const knot_node_t *knot_zone_contents_find_previous( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +knot_node_t *knot_zone_contents_get_previous( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +const knot_node_t *knot_zone_contents_find_previous_nsec3( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +knot_node_t *knot_zone_contents_get_previous_nsec3( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +#ifdef USE_HASH_TABLE +/*! + * \brief Tries to find domain name in the given zone using the hash table. + * + * \param[in] zone Zone to search for the name. + * \param[in] name Domain name to search for. + * \param[out] node The found node (if it was found, otherwise it may contain + * arbitrary node). + * \param[out] closest_encloser Closest encloser of the given name in the zone. + * \param[out] previous Previous domain name in canonical order. + * + * \retval KNOT_ZONE_NAME_FOUND if node with owner \a name was found. + * \retval KNOT_ZONE_NAME_NOT_FOUND if it was not found. + * \retval KNOT_EBADARG + * \retval KNOT_EBADZONE + */ +int knot_zone_contents_find_dname_hash(const knot_zone_contents_t *contents, + const knot_dname_t *name, + const knot_node_t **node, + const knot_node_t **closest_encloser); +#endif + +/*! + * \brief Tries to find a node with the specified name among the NSEC3 nodes + * of the zone. + * + * \note This function is identical to knot_zone_contents_get_nsec3_node(), only it + * returns constant reference. + * + * \param zone Zone where the name should be searched for. + * \param name Name to find. + * + * \return Corresponding node if found, NULL otherwise. + */ +const knot_node_t *knot_zone_contents_find_nsec3_node( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +/*! + * \brief Finds NSEC3 node and previous NSEC3 node in canonical order, + * corresponding to the given domain name. + * + * This functions creates a NSEC3 hash of \a name and tries to find NSEC3 node + * with the hashed domain name as owner. + * + * \param[in] zone Zone to search in. + * \param[in] name Domain name to get the corresponding NSEC3 nodes for. + * \param[out] nsec3_node NSEC3 node corresponding to \a name (if found, + * otherwise this may be an arbitrary NSEC3 node). + * \param[out] nsec3_previous The NSEC3 node immediately preceding hashed domain + * name corresponding to \a name in canonical order. + * + * \retval KNOT_ZONE_NAME_FOUND if the corresponding NSEC3 node was found. + * \retval KNOT_ZONE_NAME_NOT_FOUND if it was not found. + * \retval KNOT_EBADARG + * \retval KNOT_ENSEC3PAR + * \retval KNOT_ECRYPTO + * \retval KNOT_ERROR + */ +int knot_zone_contents_find_nsec3_for_name( + const knot_zone_contents_t *contents, + const knot_dname_t *name, + const knot_node_t **nsec3_node, + const knot_node_t **nsec3_previous, + int check_ver); +/*! + * \brief Returns the apex node of the zone. + * + * \param zone Zone to get the apex of. + * + * \return Zone apex node. + */ +const knot_node_t *knot_zone_contents_apex( + const knot_zone_contents_t *contents); + +knot_node_t *knot_zone_contents_get_apex( + const knot_zone_contents_t *contents); + +//knot_dname_t *knot_zone_contents_name( +// const knot_zone_contents_t *contents); + +/*! + * \brief Optimizes zone by replacing domain names in RDATA with references to + * domain names present in zone (as node owners). + * + * \param zone Zone to adjust domain names in. + */ +int knot_zone_contents_adjust(knot_zone_contents_t *contents, int check_ver); + +/*! + * \brief Parses the NSEC3PARAM record stored in the zone. + * + * This function properly fills in the nsec3_params field of the zone structure + * according to data stored in the NSEC3PARAM record. This is necessary to do + * before any NSEC3 operations on the zone are requested, otherwise they will + * fail (error KNOT_ENSEC3PAR). + * + * \note If there is no NSEC3PARAM record in the zone, this function clears + * the nsec3_params field of the zone structure (fills it with zeros). + * + * \param zone Zone to get the NSEC3PARAM record from. + */ +int knot_zone_contents_load_nsec3param(knot_zone_contents_t *contents); + +/*! + * \brief Checks if the zone uses NSEC3. + * + * This function will return 0 if the NSEC3PARAM record was not parse prior to + * calling it. + * + * \param zone Zone to check. + * + * \retval <> 0 if the zone uses NSEC3. + * \retval 0 if it does not. + * + * \see knot_zone_contents_load_nsec3param() + */ +int knot_zone_contents_nsec3_enabled(const knot_zone_contents_t *contents); + +/*! + * \brief Returns the parsed NSEC3PARAM record of the zone. + * + * \note You must parse the NSEC3PARAM record prior to calling this function + * (knot_zone_contents_load_nsec3param()). + * + * \param zone Zone to get the NSEC3PARAM record from. + * + * \return Parsed NSEC3PARAM from the zone or NULL if the zone does not use + * NSEC3 or the record was not parsed before. + * + * \see knot_zone_contents_load_nsec3param() + */ +const knot_nsec3_params_t *knot_zone_contents_nsec3params( + const knot_zone_contents_t *contents); + +/*! + * \brief Applies the given function to each regular node in the zone. + * + * This function uses post-order depth-first forward traversal, i.e. the + * function is first recursively applied to subtrees and then to the root. + * + * \param zone Nodes of this zone will be used as parameters for the function. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + */ +int knot_zone_contents_tree_apply_postorder(knot_zone_contents_t *contents, + void (*function)(knot_node_t *node, void *data), + void *data); + +/*! + * \brief Applies the given function to each regular node in the zone. + * + * This function uses in-order depth-first forward traversal, i.e. the function + * is first recursively applied to left subtree, then to the root and then to + * the right subtree. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param zone Nodes of this zone will be used as parameters for the function. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + */ +int knot_zone_contents_tree_apply_inorder(knot_zone_contents_t *contents, + void (*function)(knot_node_t *node, void *data), + void *data); + +/*! + * \brief Applies the given function to each regular node in the zone. + * + * This function uses in-order depth-first reverse traversal, i.e. the function + * is first recursively applied to right subtree, then to the root and then to + * the left subtree. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param zone Nodes of this zone will be used as parameters for the function. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + */ +int knot_zone_contents_tree_apply_inorder_reverse( + knot_zone_contents_t *contents, + void (*function)(knot_node_t *node, void *data), void *data); + +/*! + * \brief Applies the given function to each NSEC3 node in the zone. + * + * This function uses post-order depth-first forward traversal, i.e. the + * function is first recursively applied to subtrees and then to the root. + * + * \param zone NSEC3 nodes of this zone will be used as parameters for the + * function. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + */ +int knot_zone_contents_nsec3_apply_postorder(knot_zone_contents_t *contents, + void (*function)(knot_node_t *node, void *data), + void *data); + +/*! + * \brief Applies the given function to each NSEC3 node in the zone. + * + * This function uses in-order depth-first forward traversal, i.e. the function + * is first recursively applied to left subtree, then to the root and then to + * the right subtree. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param zone NSEC3 nodes of this zone will be used as parameters for the + * function. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + */ +int knot_zone_contents_nsec3_apply_inorder(knot_zone_contents_t *contents, + void (*function)(knot_node_t *node, void *data), + void *data); + +/*! + * \brief Applies the given function to each NSEC3 node in the zone. + * + * This function uses in-order depth-first reverse traversal, i.e. the function + * is first recursively applied to right subtree, then to the root and then to + * the left subtree. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param zone NSEC3 nodes of this zone will be used as parameters for the + * function. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + */ +int knot_zone_contents_nsec3_apply_inorder_reverse( + knot_zone_contents_t *contents, + void (*function)(knot_node_t *node, void *data), void *data); + +knot_zone_tree_t *knot_zone_contents_get_nodes( + knot_zone_contents_t *contents); + +knot_zone_tree_t *knot_zone_contents_get_nsec3_nodes( + knot_zone_contents_t *contents); + +ck_hash_table_t *knot_zone_contents_get_hash_table( + knot_zone_contents_t *contents); + +int knot_zone_contents_dname_table_apply(knot_zone_contents_t *contents, + void (*function)(knot_dname_t *, + void *), + void *data); + +/*! + * \brief Creates a shallow copy of the zone (no stored data are copied). + * + * This function creates a new zone structure in \a to, creates new trees for + * regular nodes and for NSEC3 nodes, creates new hash table and a new domain + * table. It also fills these structures with the exact same data as the + * original zone is - no copying of stored data is done, just pointers are + * copied. + * + * \param from Original zone. + * \param to Copy of the zone. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_zone_contents_shallow_copy(const knot_zone_contents_t *from, + knot_zone_contents_t **to); + +void knot_zone_contents_free(knot_zone_contents_t **contents); + +void knot_zone_contents_deep_free(knot_zone_contents_t **contents, + int destroy_dname_table); + +#endif + +/*! @} */ diff --git a/src/libknot/zone/zone-tree.c b/src/libknot/zone/zone-tree.c new file mode 100644 index 0000000..cdf128e --- /dev/null +++ b/src/libknot/zone/zone-tree.c @@ -0,0 +1,470 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> + +#include "zone-tree.h" +#include "zone/node.h" +#include "util/error.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ + +// AVL tree functions +TREE_DEFINE(knot_zone_tree_node, avl); + +/*----------------------------------------------------------------------------*/ + +static int knot_zone_tree_node_compare(knot_zone_tree_node_t *node1, + knot_zone_tree_node_t *node2) +{ + assert(node1 != NULL); + assert(node2 != NULL); + assert(node1->node != NULL); + assert(node2->node != NULL); + assert(knot_node_owner(node1->node) != NULL); + assert(knot_node_owner(node2->node) != NULL); + + return knot_node_compare(node1->node, node2->node); +} + +/*----------------------------------------------------------------------------*/ + +static void knot_zone_tree_delete_subtree(knot_zone_tree_node_t *root) +{ + if (root == NULL) { + return; + } + + knot_zone_tree_delete_subtree(root->avl.avl_left); + knot_zone_tree_delete_subtree(root->avl.avl_right); + free(root); +} + +/*----------------------------------------------------------------------------*/ + +static int knot_zone_tree_copy_node(knot_zone_tree_node_t *from, + knot_zone_tree_node_t **to) +{ + if (from == NULL) { + *to = NULL; + return KNOT_EOK; + } + + *to = (knot_zone_tree_node_t *) + malloc(sizeof(knot_zone_tree_node_t)); + if (*to == NULL) { + return KNOT_ENOMEM; + } + + (*to)->node = from->node; + (*to)->avl.avl_height = from->avl.avl_height; + + int ret = knot_zone_tree_copy_node(from->avl.avl_left, + &(*to)->avl.avl_left); + if (ret != KNOT_EOK) { + return ret; + } + + ret = knot_zone_tree_copy_node(from->avl.avl_right, + &(*to)->avl.avl_right); + if (ret != KNOT_EOK) { + knot_zone_tree_delete_subtree((*to)->avl.avl_left); + (*to)->avl.avl_left = NULL; + return ret; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static void knot_zone_tree_free_node(knot_zone_tree_node_t *node, + int free_data, int free_owner) +{ + if (node == NULL) { + return; + } + + knot_zone_tree_free_node(node->avl.avl_left, free_data, free_owner); + + knot_zone_tree_free_node(node->avl.avl_right, free_data, free_owner); + + if (free_data) { + knot_node_free(&node->node, free_owner, 0); + } + + free(node); +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_init(knot_zone_tree_t *tree) +{ + if (tree == NULL) { + return KNOT_EBADARG; + } + + TREE_INIT(tree, knot_zone_tree_node_compare); + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_insert(knot_zone_tree_t *tree, knot_node_t *node) +{ + if (tree == NULL || node == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_node_t *znode = (knot_zone_tree_node_t *)malloc( + sizeof(knot_zone_tree_node_t)); + if (znode == NULL) { + return KNOT_ENOMEM; + } + + znode->node = node; + znode->avl.avl_left = NULL; + znode->avl.avl_right = NULL; + znode->avl.avl_height = 0; + + /*! \todo How to know if this was successful? */ + TREE_INSERT(tree, knot_zone_tree_node, avl, znode); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_find(knot_zone_tree_t *tree, const knot_dname_t *owner, + const knot_node_t **found) +{ + if (tree == NULL || owner == NULL || found == NULL) { + return KNOT_EBADARG; + } + + knot_node_t *f = NULL; + int ret = knot_zone_tree_get(tree, owner, &f); + *found = f; + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_get(knot_zone_tree_t *tree, const knot_dname_t *owner, + knot_node_t **found) +{ + if (tree == NULL || owner == NULL) { + return KNOT_EBADARG; + } + + *found = NULL; + + // create dummy node to use for lookup + knot_zone_tree_node_t *tmp = (knot_zone_tree_node_t *)malloc( + sizeof(knot_zone_tree_node_t)); + if (tmp == NULL) { + return KNOT_ENOMEM; + } + + // create dummy data node to use for lookup + knot_node_t *tmp_data = knot_node_new( + (knot_dname_t *)owner, NULL, 0); + if (tmp_data == NULL) { + free(tmp); + return KNOT_ENOMEM; + } + tmp->node = tmp_data; + + knot_zone_tree_node_t *n = TREE_FIND(tree, knot_zone_tree_node, avl, + tmp); + + knot_node_free(&tmp_data, 0, 0); + free(tmp); + + if (n != NULL) { + *found = n->node; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_find_less_or_equal(knot_zone_tree_t *tree, + const knot_dname_t *owner, + const knot_node_t **found, + const knot_node_t **previous, + int check_version) +{ + if (tree == NULL || owner == NULL || found == NULL || previous == NULL) { + return KNOT_EBADARG; + } + + knot_node_t *f, *p; + int ret = knot_zone_tree_get_less_or_equal(tree, owner, &f, &p, check_version); + + *found = f; + *previous = p; + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_get_less_or_equal(knot_zone_tree_t *tree, + const knot_dname_t *owner, + knot_node_t **found, + knot_node_t **previous, + int check_version) +{ + if (tree == NULL || owner == NULL || found == NULL + || previous == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_node_t *f = NULL, *prev = NULL; + + // create dummy node to use for lookup + knot_zone_tree_node_t *tmp = (knot_zone_tree_node_t *)malloc( + sizeof(knot_zone_tree_node_t)); + if (tmp == NULL) { + return KNOT_ENOMEM; + } + + // create dummy data node to use for lookup + knot_node_t *tmp_data = knot_node_new( + (knot_dname_t *)owner, NULL, 0); + if (tmp_data == NULL) { + free(tmp); + return KNOT_ENOMEM; + } + tmp->node = tmp_data; + + int exact_match = TREE_FIND_LESS_EQUAL( + tree, knot_zone_tree_node, avl, tmp, &f, &prev); + + knot_node_free(&tmp_data, 0, 0); + free(tmp); + + *found = (exact_match > 0) ? f->node : NULL; + + if (exact_match < 0) { + // previous is not really previous but should be the leftmost + // node in the tree; take it's previous + assert(prev != NULL); + *previous = knot_node_get_previous(prev->node, check_version); + exact_match = 0; + } else if (prev == NULL) { + if (!exact_match) { + printf("Searched for owner %s in zone tree.\n", + knot_dname_to_str(owner)); + printf("Exact match: %d\n", exact_match); + printf("Found node: %p: %s.\n", f, (f) + ? knot_dname_to_str(knot_node_owner(f->node)) + : "none"); + printf("Previous node: %p: %s.\n", prev, (prev) + ? knot_dname_to_str(knot_node_owner(prev->node)) + : "none"); + } + + // either the returned node is the root of the tree, or + // it is the leftmost node in the tree; in both cases + // node was found set the previous node of the found + // node + assert(exact_match > 0); + assert(f != NULL); + *previous = knot_node_get_previous(f->node, check_version); + } else { + // otherwise check if the previous node is not an empty + // non-terminal + /*! \todo Here we assume that the 'prev' pointer always points + * to an empty non-terminal. + */ + *previous = (knot_node_rrset_count(prev->node) == 0) + ? knot_node_get_previous(prev->node, check_version) + : prev->node; + } + + assert(exact_match >= 0); + + return exact_match; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_remove(knot_zone_tree_t *tree, + const knot_dname_t *owner, + knot_zone_tree_node_t **removed) +{ + if (tree == NULL || owner == NULL || removed == NULL) { + return KNOT_EBADARG; + } + + // create dummy node to use for lookup + knot_zone_tree_node_t *tmp = (knot_zone_tree_node_t *)malloc( + sizeof(knot_zone_tree_node_t)); + if (tmp == NULL) { + return KNOT_ENOMEM; + } + + // create dummy data node to use for lookup + knot_node_t *tmp_data = knot_node_new( + (knot_dname_t *)owner, NULL, 0); + if (tmp_data == NULL) { + free(tmp); + return KNOT_ENOMEM; + } + tmp->node = tmp_data; + + // we must first find the node, so that it may be destroyed + knot_zone_tree_node_t *n = TREE_FIND(tree, knot_zone_tree_node, avl, + tmp); + + /*! \todo How to know if this was successful? */ + TREE_REMOVE(tree, knot_zone_tree_node, avl, tmp); + + knot_node_free(&tmp_data, 0, 0); + free(tmp); + +// *removed = (n) ? n->node : NULL; +// free(n); + *removed = n; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_forward_apply_inorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data) +{ + if (tree == NULL || function == NULL) { + return KNOT_EBADARG; + } + + TREE_FORWARD_APPLY(tree, knot_zone_tree_node, avl, + function, data); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_forward_apply_postorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data) +{ + if (tree == NULL || function == NULL) { + return KNOT_EBADARG; + } + + TREE_POST_ORDER_APPLY(tree, knot_zone_tree_node, avl, + function, data); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_reverse_apply_inorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data) +{ + if (tree == NULL || function == NULL) { + return KNOT_EBADARG; + } + + TREE_REVERSE_APPLY(tree, knot_zone_tree_node, avl, + function, data); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_reverse_apply_postorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data) +{ + if (tree == NULL || function == NULL) { + return KNOT_EBADARG; + } + + TREE_REVERSE_APPLY_POST(tree, knot_zone_tree_node, avl, + function, data); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_shallow_copy(knot_zone_tree_t *from, + knot_zone_tree_t *to) +{ + if (to == NULL || from == NULL) { + return KNOT_EBADARG; + } + /* + * This function will copy the tree by hand, so that the nodes + * do not have to be inserted the normal way. It should be substantially + * faster. + */ + + to->th_cmp = from->th_cmp; + + return knot_zone_tree_copy_node(from->th_root, &to->th_root); +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_tree_free(knot_zone_tree_t **tree) +{ + if (tree == NULL || *tree == NULL) { + return; + } + knot_zone_tree_free_node((*tree)->th_root, 0, 0); + free(*tree); + *tree = NULL; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_tree_deep_free(knot_zone_tree_t **tree, int free_owners) +{ + if (tree == NULL || *tree == NULL) { + return; + } + knot_zone_tree_free_node((*tree)->th_root, 1, free_owners); + free(*tree); + *tree = NULL; +} + +/*----------------------------------------------------------------------------*/ diff --git a/src/libknot/zone/zone-tree.h b/src/libknot/zone/zone-tree.h new file mode 100644 index 0000000..0971749 --- /dev/null +++ b/src/libknot/zone/zone-tree.h @@ -0,0 +1,300 @@ +/*! + * \file zone-tree.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Zone tree structure and API for manipulating it. + * + * Implemented as AVL tree. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_ZONE_TREE_H_ +#define _KNOT_ZONE_TREE_H_ + +#include "common/tree.h" +#include "zone/node.h" + +/*----------------------------------------------------------------------------*/ + +typedef struct knot_zone_tree_node { + /*! \brief Structure for connecting this node to an AVL tree. */ + TREE_ENTRY(knot_zone_tree_node) avl; + /*! \brief Zone tree data. */ + knot_node_t *node; + /*! \brief Owner of the node. */ +// knot_dname_t *owner; +} knot_zone_tree_node_t; + +/*----------------------------------------------------------------------------*/ + +typedef TREE_HEAD(knot_zone_tree, knot_zone_tree_node) knot_zone_tree_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Initializes the zone tree. + * + * Does not allocate the structure. Must be called before any use of the tree. + * + * \param tree Zone tree structure to initialize. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_zone_tree_init(knot_zone_tree_t *tree); + +/*! + * \brief Inserts the given node into the zone tree. + * + * \param tree Zone tree to insert the node into. + * \param node Node to insert. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_zone_tree_insert(knot_zone_tree_t *tree, knot_node_t *node); + +/*! + * \brief Finds node with the given owner in the zone tree. + * + * \param tree Zone tree to search in. + * \param owner Owner of the node to find. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_zone_tree_find(knot_zone_tree_t *tree, + const knot_dname_t *owner, + const knot_node_t **found); + +/*! + * \brief Finds node with the given owner in the zone tree. + * + * \note This function is identical to knot_zone_tree_find() except that it + * returns non-const node. + * + * \param tree Zone tree to search in. + * \param owner Owner of the node to find. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_zone_tree_get(knot_zone_tree_t *tree, + const knot_dname_t *owner, + knot_node_t **found); + +/*! + * \brief Tries to find the given domain name in the zone tree and returns the + * associated node and previous node in canonical order. + * + * \param zone Zone to search in. + * \param owner Owner of the node to find. + * \param found Found node. + * \param previous Previous node in canonical order (i.e. the one directly + * preceding \a owner in canonical order, regardless if the name + * is in the zone or not). + * + * \retval > 0 if the domain name was found. In such case \a found holds the + * zone node with \a owner as its owner. + * \a previous is set properly. + * \retval 0 if the domain name was not found. \a found may hold any (or none) + * node. \a previous is set properly. + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_zone_tree_find_less_or_equal(knot_zone_tree_t *tree, + const knot_dname_t *owner, + const knot_node_t **found, + const knot_node_t **previous, + int check_version); + +/*! + * \brief Tries to find the given domain name in the zone tree and returns the + * associated node and previous node in canonical order. + * + * \note This function is identical to knot_zone_tree_find_less_or_equal() + * except that it returns non-const nodes. + * + * \param zone Zone to search in. + * \param owner Owner of the node to find. + * \param found Found node. + * \param previous Previous node in canonical order (i.e. the one directly + * preceding \a owner in canonical order, regardless if the name + * is in the zone or not). + * + * \retval > 0 if the domain name was found. In such case \a found holds the + * zone node with \a owner as its owner. + * \a previous is set properly. + * \retval 0 if the domain name was not found. \a found may hold any (or none) + * node. \a previous is set properly. + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_zone_tree_get_less_or_equal(knot_zone_tree_t *tree, + const knot_dname_t *owner, + knot_node_t **found, + knot_node_t **previous, + int check_version); + +/*! + * \brief Removes node with the given owner from the zone tree and returns it. + * + * \param tree Zone tree to remove the node from. + * \param owner Owner of the node to find. + * \param removed The removed node. + * + * \retval The removed node. + */ +int knot_zone_tree_remove(knot_zone_tree_t *tree, + const knot_dname_t *owner, + knot_zone_tree_node_t **removed); + +/*! + * \brief Applies the given function to each node in the zone. + * + * This function uses in-order depth-first forward traversal, i.e. the function + * is first recursively applied to left subtree, then to the root and then to + * the right subtree. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param tree Zone tree to apply the function to. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_zone_tree_forward_apply_inorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data); + +/*! + * \brief Applies the given function to each node in the zone. + * + * This function uses post-order depth-first forward traversal, i.e. the + * function is first recursively applied to subtrees and then to the root. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param tree Zone tree to apply the function to. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_zone_tree_forward_apply_postorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data); + +/*! + * \brief Applies the given function to each node in the zone. + * + * This function uses in-order depth-first reverse traversal, i.e. the function + * is first recursively applied to right subtree, then to the root and then to + * the left subtree. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param tree Zone tree to apply the function to. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_zone_tree_reverse_apply_inorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data); + +/*! + * \brief Applies the given function to each node in the zone. + * + * This function uses post-order depth-first reverse traversal, i.e. the + * function is first recursively applied to right subtree, then to the + * left subtree and then to the root. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param tree Zone tree to apply the function to. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_zone_tree_reverse_apply_postorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data); + +/*! + * \brief Copies the whole zone tree structure (but not the data contained + * within). + * + * \warning This function does not check if the target zone tree is empty, + * it just replaces the root pointer. + * + * \param from Original zone tree. + * \param to Zone tree to copy the original one into. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +int knot_zone_tree_shallow_copy(knot_zone_tree_t *from, + knot_zone_tree_t *to); + +/*! + * \brief Destroys the zone tree, not touching the saved data. + * + * \param tree Zone tree to be destroyed. + */ +void knot_zone_tree_free(knot_zone_tree_t **tree); + +/*! + * \brief Destroys the zone tree, together with the saved data. + * + * \param tree Zone tree to be destroyed. + * \param free_owners Set to <> 0 if owners of the nodes should be destroyed + * as well. Set to 0 otherwise. + */ +void knot_zone_tree_deep_free(knot_zone_tree_t **tree, int free_owners); + +/*----------------------------------------------------------------------------*/ + +#endif // _KNOT_ZONE_TREE_H_ + +/*! @} */ + diff --git a/src/libknot/zone/zone.c b/src/libknot/zone/zone.c new file mode 100644 index 0000000..9de1a53 --- /dev/null +++ b/src/libknot/zone/zone.c @@ -0,0 +1,246 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +#include <urcu.h> + +#include "common.h" +#include "zone/zone.h" +#include "zone/node.h" +#include "dname.h" +#include "consts.h" +#include "util/descriptor.h" +#include "nsec3.h" +#include "util/error.h" +#include "util/debug.h" +#include "util/utils.h" +#include "common/tree.h" +#include "common/base32hex.h" +#include "hash/cuckoo-hash-table.h" +#include "zone/zone-contents.h" + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_zone_t *knot_zone_new_empty(knot_dname_t *name) +{ + if (!name) { + return 0; + } + + dbg_zone("Creating new zone!\n"); + + knot_zone_t *zone = malloc(sizeof(knot_zone_t)); + if (zone == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + memset(zone, 0, sizeof(knot_zone_t)); + + // save the zone name + dbg_zone("Setting zone name.\n"); + zone->name = name; + return zone; +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_t *knot_zone_new(knot_node_t *apex, uint node_count, + int use_domain_table) +{ + knot_zone_t *zone = knot_zone_new_empty( + knot_dname_deep_copy(knot_node_owner(apex))); + if (zone == NULL) { + return NULL; + } + + dbg_zone("Creating zone contents.\n"); + zone->contents = knot_zone_contents_new(apex, node_count, + use_domain_table, zone); + if (zone->contents == NULL) { + knot_dname_release(zone->name); + free(zone); + return NULL; + } + + zone->contents->zone = zone; + + return zone; +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_contents_t *knot_zone_get_contents( + const knot_zone_t *zone) +{ + if (zone == NULL) { + return NULL; + } + + return rcu_dereference(zone->contents); +} + +/*----------------------------------------------------------------------------*/ + +const knot_zone_contents_t *knot_zone_contents( + const knot_zone_t *zone) +{ + if (zone == NULL) { + return NULL; + } + + return rcu_dereference(zone->contents); +} + +/*----------------------------------------------------------------------------*/ + +time_t knot_zone_version(const knot_zone_t *zone) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + return zone->version; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_set_version(knot_zone_t *zone, time_t version) +{ + if (zone == NULL) { + return; + } + + zone->version = version; +} + +/*----------------------------------------------------------------------------*/ + +short knot_zone_is_master(const knot_zone_t *zone) +{ + return zone->master; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_set_master(knot_zone_t *zone, short master) +{ + zone->master = master; +} + +/*----------------------------------------------------------------------------*/ + +const void *knot_zone_data(const knot_zone_t *zone) +{ + return zone->data; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_set_data(knot_zone_t *zone, void *data) +{ + zone->data = data; +} + +/*----------------------------------------------------------------------------*/ + +const knot_dname_t *knot_zone_name(const knot_zone_t *zone) +{ + return zone->name; +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_contents_t *knot_zone_switch_contents(knot_zone_t *zone, + knot_zone_contents_t *new_contents) +{ + if (zone == NULL) { + return NULL; + } + + knot_zone_contents_t *old_contents = + rcu_xchg_pointer(&zone->contents, new_contents); + return old_contents; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_free(knot_zone_t **zone) +{ + if (zone == NULL || *zone == NULL) { + return; + } + + dbg_zone("zone_free().\n"); + + if ((*zone)->contents + && !knot_zone_contents_gen_is_old((*zone)->contents)) { + // zone is in the middle of an update, report + dbg_zone("Destroying zone that is in the middle of an " + "update.\n"); + } + + knot_dname_release((*zone)->name); + + /* Call zone data destructor if exists. */ + if ((*zone)->dtor) { + (*zone)->dtor(*zone); + } + + knot_zone_contents_free(&(*zone)->contents); + free(*zone); + *zone = NULL; + + dbg_zone("Done.\n"); +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_deep_free(knot_zone_t **zone, int destroy_dname_table) +{ + if (zone == NULL || *zone == NULL) { + return; + } + + if ((*zone)->contents + && !knot_zone_contents_gen_is_old((*zone)->contents)) { + // zone is in the middle of an update, report + dbg_zone("Destroying zone that is in the middle of an " + "update.\n"); + } + +dbg_zone_exec( + char *name = knot_dname_to_str((*zone)->name); + dbg_zone("Destroying zone %p, name: %s.\n", *zone, name); + free(name); +); + + knot_dname_release((*zone)->name); + + /* Call zone data destructor if exists. */ + if ((*zone)->dtor) { + (*zone)->dtor(*zone); + } + + knot_zone_contents_deep_free(&(*zone)->contents, destroy_dname_table); + free(*zone); + *zone = NULL; +} diff --git a/src/libknot/zone/zone.h b/src/libknot/zone/zone.h new file mode 100644 index 0000000..331ef1f --- /dev/null +++ b/src/libknot/zone/zone.h @@ -0,0 +1,157 @@ +/*! + * \file zone.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Zone structure and API for manipulating it. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_ZONE_H_ +#define _KNOT_ZONE_H_ + +#include <time.h> + +#include "zone/node.h" +#include "dname.h" +#include "nsec3.h" +#include "zone/dname-table.h" +#include "common/tree.h" +#include "hash/cuckoo-hash-table.h" + +#include "zone-tree.h" + +#include "zone/zone-contents.h" + +/*----------------------------------------------------------------------------*/ + +//typedef TREE_HEAD(avl_tree, knot_node) avl_tree_t; +//struct event_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Return values for search functions. + * + * Used in knot_zone_find_dname() and knot_zone_find_dname_hash(). + */ +enum knot_zone_retvals { + KNOT_ZONE_NAME_FOUND = 1, + KNOT_ZONE_NAME_NOT_FOUND = 0 +}; + +typedef enum knot_zone_retvals knot_zone_retvals_t; + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Structure for holding DNS zone. + * + * \warning Make sure not to insert the same nodes using both the normal and + * NSEC3 functions. Although this will be successfull, it will produce + * double-free errors when destroying the zone. + */ +struct knot_zone { + knot_dname_t *name; + + knot_zone_contents_t *contents; + + time_t version; + + /*! \todo Set when loading zone. */ + short master; + + void *data; /*!< Pointer to generic zone-related data. */ + int (*dtor)(struct knot_zone *); /*!< Data destructor. */ +}; + +typedef struct knot_zone knot_zone_t; + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Creates new empty DNS zone. + * + * \notice Zone will be created without contents. + * + * \param name Zone owner. + * + * \return The initialized zone structure or NULL if an error occured. + */ +knot_zone_t *knot_zone_new_empty(knot_dname_t *name); + +/*! + * \brief Creates new DNS zone. + * + * \param apex Node representing the zone apex. + * \param node_count Number of authorative nodes in the zone. + * + * \return The initialized zone structure or NULL if an error occured. + */ +knot_zone_t *knot_zone_new(knot_node_t *apex, uint node_count, + int use_domain_table); + +knot_zone_contents_t *knot_zone_get_contents( + const knot_zone_t *zone); + +const knot_zone_contents_t *knot_zone_contents( + const knot_zone_t *zone); + + +time_t knot_zone_version(const knot_zone_t *zone); + +void knot_zone_set_version(knot_zone_t *zone, time_t version); + +short knot_zone_is_master(const knot_zone_t *zone); + +void knot_zone_set_master(knot_zone_t *zone, short master); + +const void *knot_zone_data(const knot_zone_t *zone); + +void knot_zone_set_data(knot_zone_t *zone, void *data); + +const knot_dname_t *knot_zone_name(const knot_zone_t *zone); + +knot_zone_contents_t *knot_zone_switch_contents(knot_zone_t *zone, + knot_zone_contents_t *new_contents); + +/*! + * \brief Correctly deallocates the zone structure, without deleting its nodes. + * + * Also sets the given pointer to NULL. + * + * \param zone Zone to be freed. + */ +void knot_zone_free(knot_zone_t **zone); + +/*! + * \brief Correctly deallocates the zone structure and all nodes within. + * + * Also sets the given pointer to NULL. + * + * \param zone Zone to be freed. + * \param free_rdata_dnames Set to <> 0 if you want to delete ALL domain names + * present in RDATA. Set to 0 otherwise. (See + * knot_rdata_deep_free().) + */ +void knot_zone_deep_free(knot_zone_t **zone, int destroy_dname_table); + +#endif + +/*! @} */ diff --git a/src/libknot/zone/zonedb.c b/src/libknot/zone/zonedb.c new file mode 100644 index 0000000..8f07d45 --- /dev/null +++ b/src/libknot/zone/zonedb.c @@ -0,0 +1,389 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdlib.h> +#include <assert.h> + +#include <urcu.h> + +#include "common.h" +#include "zone/zone.h" +#include "zone/zonedb.h" +#include "dname.h" +#include "zone/node.h" +#include "util/error.h" +#include "util/debug.h" +#include "common/general-tree.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Compares the two arguments interpreted as zone names (domain names). + * + * Use this function with generic data structures (such as the skip list). + * + * \param d1 First zone name. + * \param d2 Second zone name. + * + * \retval 0 if the two zone names are equal. + * \retval < 0 if \a d1 is before \a d2 in canonical order. + * \retval > 0 if \a d1 is after \a d2 in canonical order. + */ +static int knot_zonedb_compare_zone_names(void *p1, void *p2) +{ + const knot_zone_t *zone1 = (const knot_zone_t *)p1; + const knot_zone_t *zone2 = (const knot_zone_t *)p2; + + int ret = knot_dname_compare(zone1->name, zone2->name); + +dbg_zonedb_exec( + char *name1 = knot_dname_to_str(zone1->name); + char *name2 = knot_dname_to_str(zone2->name); + dbg_zonedb("Compared names %s and %s, result: %d.\n", + name1, name2, ret); + free(name1); + free(name2); +); + + return (ret); +} + +/*----------------------------------------------------------------------------*/ + +//static int knot_zonedb_replace_zone_in_list(void **list_item, void **new_zone) +//{ +// assert(list_item != NULL); +// assert(*list_item != NULL); +// assert(new_zone != NULL); +// assert(*new_zone != NULL); + +// dbg_zonedb("Replacing list item %p with new zone %p\n", +// *list_item, *new_zone); + +// *list_item = *new_zone; + +// return 0; +//} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_zonedb_t *knot_zonedb_new() +{ + knot_zonedb_t *db = + (knot_zonedb_t *)malloc(sizeof(knot_zonedb_t)); + CHECK_ALLOC_LOG(db, NULL); + + db->zone_tree = gen_tree_new(knot_zonedb_compare_zone_names); + if (db->zone_tree == NULL) { + free(db); + return NULL; + } + + db->zone_count = 0; + + return db; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zonedb_add_zone(knot_zonedb_t *db, knot_zone_t *zone) +{ + if (db == NULL || zone == NULL) { + return KNOT_EBADARG; + } +dbg_zonedb_exec( + char *name = knot_dname_to_str(zone->name); + dbg_zonedb("Inserting zone %s into zone db.\n", name); + free(name); +); + + int ret = KNOT_EOK; + if (knot_zone_contents(zone)) { + ret = knot_zone_contents_load_nsec3param( + knot_zone_get_contents(zone)); + if (ret != KNOT_EOK) { + return ret; + } + } + + ret = gen_tree_add(db->zone_tree, zone, NULL); + + if (ret == 0) { + db->zone_count++; + } + + return (ret != 0) ? KNOT_EZONEIN : KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_t *knot_zonedb_remove_zone(knot_zonedb_t *db, + const knot_dname_t *zone_name) +{ + knot_zone_t dummy_zone; + memset(&dummy_zone, 0, sizeof(knot_zone_t)); + dummy_zone.name = (knot_dname_t *)zone_name; + + // add some lock to avoid multiple removals + knot_zone_t *z = (knot_zone_t *)gen_tree_find(db->zone_tree, + &dummy_zone); + + if (z == NULL) { + return NULL; + } + + // remove the zone from the skip list, but do not destroy it + gen_tree_remove(db->zone_tree, &dummy_zone); + +// if (destroy_zone) { +// // properly destroy the zone and all its contents +// knot_zone_deep_free(&z, 0); +// } + + db->zone_count--; + + //return KNOT_EOK; + return z; +} + +/*----------------------------------------------------------------------------*/ + +//knot_zone_t *knot_zonedb_replace_zone(knot_zonedb_t *db, +// knot_zone_t *zone) +//{ +// knot_zone_t *z = knot_zonedb_find_zone(db, +// knot_node_owner(knot_zone_apex(zone))); +// if (z == NULL) { +// return NULL; +// } + +// /*! \todo The replace should be atomic!!! */ + +// dbg_zonedb("Found zone: %p\n", z); + +// int ret = skip_remove(db->zones, +// (void *)knot_node_owner(knot_zone_apex(zone)), +// NULL, NULL); +// if (ret != 0) { +// return NULL; +// } + +// dbg_zonedb("Removed zone, return value: %d\n", ret); +// dbg_zonedb("Old zone: %p\n", z); + +// ret = skip_insert(db->zones, +// (void *)knot_node_owner(knot_zone_apex(zone)), +// (void *)zone, NULL); + +// dbg_zonedb("Inserted zone, return value: %d\n", ret); + +// if (ret != 0) { +// // return the removed zone back +// skip_insert(db->zones, +// (void *)knot_node_owner(knot_zone_apex(z)), +// (void *)z, NULL); +// /*! \todo There may be problems and the zone may remain +// removed. */ +// return NULL; +// } + +// return z; +//} + +/*----------------------------------------------------------------------------*/ + +knot_zone_t *knot_zonedb_find_zone(const knot_zonedb_t *db, + const knot_dname_t *zone_name) +{ + knot_zone_t dummy_zone; + dummy_zone.name = (knot_dname_t *)zone_name; + return (knot_zone_t *)gen_tree_find(db->zone_tree, &dummy_zone); +} + +/*----------------------------------------------------------------------------*/ + +const knot_zone_t *knot_zonedb_find_zone_for_name(knot_zonedb_t *db, + const knot_dname_t *dname) +{ + if (db == NULL || dname == NULL) { + return NULL; + } + + knot_zone_t dummy_zone; + dummy_zone.name = (knot_dname_t *)dname; + void *found = NULL; + int exact_match = gen_tree_find_less_or_equal(db->zone_tree, + &dummy_zone, + &found); + UNUSED(exact_match); + + knot_zone_t *zone = (found) ? (knot_zone_t *)found : NULL; + +dbg_zonedb_exec( + char *name = knot_dname_to_str(dname); + dbg_zonedb("Found zone for name %s: %p\n", name, zone); + free(name); +); + if (zone != NULL && zone->contents != NULL + && knot_dname_compare(zone->contents->apex->owner, dname) != 0 + && !knot_dname_is_subdomain(dname, zone->contents->apex->owner)) { + zone = NULL; + } + + return zone; +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_contents_t *knot_zonedb_expire_zone(knot_zonedb_t *db, + const knot_dname_t *zone_name) +{ + if (db == NULL || zone_name == NULL) { + return NULL; + } + + // Remove the contents from the zone, but keep the zone in the zonedb. + + knot_zone_t *zone = knot_zonedb_find_zone(db, zone_name); + if (zone == NULL) { + return NULL; + } + + return knot_zone_switch_contents(zone, NULL); +} + +/*----------------------------------------------------------------------------*/ + +knot_zonedb_t *knot_zonedb_copy(const knot_zonedb_t *db) +{ + knot_zonedb_t *db_new = + (knot_zonedb_t *)malloc(sizeof(knot_zonedb_t)); + CHECK_ALLOC_LOG(db_new, NULL); + + db_new->zone_tree = gen_tree_shallow_copy(db->zone_tree); + if (db_new->zone_tree == NULL) { + free(db_new); + return NULL; + } + + return db_new; +} + +size_t knot_zonedb_zone_count(const knot_zonedb_t *db) +{ + return db->zone_count; +} + +struct knot_zone_db_tree_arg { + const knot_zone_t **zones; + size_t count; +}; + +static void save_zone_to_array(void *node, void *data) +{ + knot_zone_t *zone = (knot_zone_t *)node; + struct knot_zone_db_tree_arg *args = + (struct knot_zone_db_tree_arg *)data; + assert(data); + args->zones[args->count++] = zone; +} + +const knot_zone_t **knot_zonedb_zones(const knot_zonedb_t *db) +{ + struct knot_zone_db_tree_arg args; + args.zones = malloc(sizeof(knot_zone_t) * db->zone_count); + args.count = 0; + CHECK_ALLOC_LOG(args.zones, NULL); + + gen_tree_apply_inorder(db->zone_tree, save_zone_to_array, + &args); + assert(db->zone_count == args.count); + + return args.zones; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zonedb_free(knot_zonedb_t **db) +{ + gen_tree_destroy(&((*db)->zone_tree), NULL ,NULL); + free(*db); + *db = NULL; +} + +/*----------------------------------------------------------------------------*/ + +static void delete_zone_from_db(void *node, void *data) +{ + UNUSED(data); + knot_zone_t *zone = (knot_zone_t *)node; + assert(zone); + synchronize_rcu(); + knot_zone_deep_free(&zone, 0); +} + +void knot_zonedb_deep_free(knot_zonedb_t **db) +{ + dbg_zonedb("Deleting zone db (%p).\n", *db); +// dbg_zonedb("Is it empty (%p)? %s\n", +// (*db)->zones, skip_is_empty((*db)->zones) ? "yes" : "no"); + +//dbg_zonedb_exec( +// int i = 1; +// char *name = NULL; +// while (zn != NULL) { +// dbg_zonedb("%d. zone: %p, key: %p\n", i, zn->value, +// zn->key); +// assert(zn->key == ((knot_zone_t *)zn->value)->apex->owner); +// name = knot_dname_to_str((knot_dname_t *)zn->key); +// dbg_zonedb(" zone name: %s\n", name); +// free(name); + +// zn = skip_next(zn); +// } + +// zn = skip_first((*db)->zones); +//); + +// while (zn != NULL) { +// zone = (knot_zone_t *)zn->value; +// assert(zone != NULL); + +// // remove the zone from the database +// skip_remove((*db)->zones, zn->key, NULL, NULL); +// // wait for all readers to finish +// synchronize_rcu; +// // destroy the zone +// knot_zone_deep_free(&zone, 0); + +// zn = skip_first((*db)->zones); +// } + +// assert(skip_is_empty((*db)->zones)); + +// skip_destroy_list(&(*db)->zones, NULL, NULL); + gen_tree_destroy(&((*db)->zone_tree), delete_zone_from_db, NULL); + assert((*db)->zone_tree == NULL); + free(*db); + *db = NULL; +} + +/*----------------------------------------------------------------------------*/ + diff --git a/src/libknot/zone/zonedb.h b/src/libknot/zone/zonedb.h new file mode 100644 index 0000000..d5a4992 --- /dev/null +++ b/src/libknot/zone/zonedb.h @@ -0,0 +1,145 @@ +/*! + * \file zonedb.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Zone database structure and API for manipulating it. + * + * Zone database groups several zones and provides functions for finding + * suitable zone for a domain name, for searching in a particular zone, etc. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_ZONEDB_H_ +#define _KNOT_ZONEDB_H_ + +#include "common/general-tree.h" +#include "zone/zone.h" +#include "zone/node.h" +#include "dname.h" + +/*! + * \brief Zone database structure. Contains all zones managed by the server. + */ +struct knot_zonedb { + general_tree_t *zone_tree; /*!< AVL tree of zones. */ + size_t zone_count; +}; + +typedef struct knot_zonedb knot_zonedb_t; + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Allocates and initializes the zone database structure. + * + * \return Pointer to the created zone database structure or NULL if an error + * occured. + */ +knot_zonedb_t *knot_zonedb_new(); + +/*! + * \brief Adds new zone to the database. + * + * \param db Zone database to store the zone. + * \param zone Parsed zone. + * + * \retval KNOT_EOK + * \retval KNOT_EZONEIN + */ +int knot_zonedb_add_zone(knot_zonedb_t *db, knot_zone_t *zone); + +/*! + * \brief Removes the given zone from the database if it exists. + * + * \note Assumes that the zone was adjusted using knot_zone_adjust_dnames(). + * If it was not, it may leak some memory due to checks used in + * knot_rdata_deep_free(). + * + * \param db Zone database to remove from. + * \param zone_name Name of the zone to be removed. + * \param destroy_zone Set to <> 0 if you do want the function to destroy the + * zone after removing from zone database. Set to 0 + * otherwise. + * + * \retval KNOT_EOK + * \retval KNOT_ENOZONE + */ +knot_zone_t * knot_zonedb_remove_zone(knot_zonedb_t *db, + const knot_dname_t *zone_name); + +//knot_zone_t *knot_zonedb_replace_zone(knot_zonedb_t *db, +// knot_zone_t *zone); + +/*! + * \brief Finds zone exactly matching the given zone name. + * + * \param db Zone database to search in. + * \param zone_name Domain name representing the zone name. + * + * \return Zone with \a zone_name being the owner of the zone apex or NULL if + * not found. + */ +knot_zone_t *knot_zonedb_find_zone(const knot_zonedb_t *db, + const knot_dname_t *zone_name); + + +/*! + * \brief Finds zone the given domain name should belong to. + * + * \param db Zone database to search in. + * \param dname Domain name to find zone for. + * + * \retval Zone in which the domain name should be present or NULL if no such + * zone is found. + */ +const knot_zone_t *knot_zonedb_find_zone_for_name(knot_zonedb_t *db, + const knot_dname_t *dname); + +knot_zone_contents_t *knot_zonedb_expire_zone(knot_zonedb_t *db, + const knot_dname_t *zone_name); + +size_t knot_zonedb_zone_count(const knot_zonedb_t *db); +const knot_zone_t **knot_zonedb_zones(const knot_zonedb_t *db); + +/*! + * \brief Destroys and deallocates the zone database structure (but not the + * zones within). + * + * \param db Zone database to be destroyed. + */ +void knot_zonedb_free(knot_zonedb_t **db); + +/*! + * \brief Destroys and deallocates the whole zone database including the zones. + * + * \note Assumes that the zone was adjusted using knot_zone_adjust_dnames(). + * If it was not, it may leak some memory due to checks used in + * knot_rdata_deep_free(). + * + * \param db Zone database to be destroyed. + */ +void knot_zonedb_deep_free(knot_zonedb_t **db); + +/*----------------------------------------------------------------------------*/ + +#endif /* _KNOT_ZONEDB_H_ */ + +/*! @} */ diff --git a/src/tests/README b/src/tests/README new file mode 100644 index 0000000..2f299ad --- /dev/null +++ b/src/tests/README @@ -0,0 +1,10 @@ +Unit testing +------------ + +Make assembles "unittest" binary with all of the planned tests included. +So far it is accepting the same parameters as the "cutedns", +but an own parameter format is being developed. + +Example: +bin/unittest samples/example.com.zone + diff --git a/src/tests/common/acl_tests.c b/src/tests/common/acl_tests.c new file mode 100644 index 0000000..b4232ac --- /dev/null +++ b/src/tests/common/acl_tests.c @@ -0,0 +1,111 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <sys/socket.h> + +#include "tests/common/acl_tests.h" +#include "common/sockaddr.h" +#include "common/acl.h" + +static int acl_tests_count(int argc, char *argv[]); +static int acl_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api acl_tests_api = { + "ACL", //! Unit name + &acl_tests_count, //! Count scheduled tests + &acl_tests_run //! Run scheduled tests +}; + +static int acl_tests_count(int argc, char *argv[]) +{ + return 13; +} + +static int acl_tests_run(int argc, char *argv[]) +{ + // 1. Create an ACL + acl_t *acl = acl_new(ACL_DENY, "simple ACL"); + ok(acl != 0, "acl: new"); + + // 2. Create IPv4 address + sockaddr_t test_v4; + int ret = sockaddr_set(&test_v4, AF_INET, "127.0.0.1", 12345); + ok(ret > 0, "acl: new IPv4 address"); + + // 3. Create IPv6 address + sockaddr_t test_v6; + ret = sockaddr_set(&test_v6, AF_INET6, "::1", 54321); + ok(ret > 0, "acl: new IPv6 address"); + + // 4. Create simple IPv4 rule + ret = acl_create(acl, &test_v4, ACL_ACCEPT); + ok(ret == ACL_ACCEPT, "acl: inserted IPv4 rule"); + + // 5. Create simple IPv6 rule + ret = acl_create(acl, &test_v6, ACL_ACCEPT); + ok(ret == ACL_ACCEPT, "acl: inserted IPv6 rule"); + + // 6. Create simple IPv4 'any port' rule + sockaddr_t test_v4a; + sockaddr_set(&test_v4a, AF_INET, "20.20.20.20", 0); + ret = acl_create(acl, &test_v4a, ACL_ACCEPT); + ok(ret == ACL_ACCEPT, "acl: inserted IPv4 'any port' rule"); + + // 7. Attempt to match unmatching address + sockaddr_t unmatch_v4; + sockaddr_set(&unmatch_v4, AF_INET, "10.10.10.10", 24424); + ret = acl_match(acl, &unmatch_v4); + ok(ret == ACL_DENY, "acl: matching non-existing address"); + + // 8. Attempt to match unmatching IPv6 address + sockaddr_t unmatch_v6; + sockaddr_set(&unmatch_v6, AF_INET6, "2001:db8::1428:57ab", 24424); + ret = acl_match(acl, &unmatch_v6); + ok(ret == ACL_DENY, "acl: matching non-existing IPv6 address"); + + // 9. Attempt to match matching address + ret = acl_match(acl, &test_v4); + ok(ret == ACL_ACCEPT, "acl: matching existing address"); + + // 10. Attempt to match matching address + ret = acl_match(acl, &test_v6); + ok(ret == ACL_ACCEPT, "acl: matching existing IPv6 address"); + + // 11. Attempt to match matching 'any port' address + sockaddr_t match_v4a; + sockaddr_set(&match_v4a, AF_INET, "20.20.20.20", 24424); + ret = acl_match(acl, &match_v4a); + ok(ret == ACL_ACCEPT, "acl: matching existing IPv4 'any port' address"); + + // 12. Attempt to match matching address without matching port + sockaddr_set(&unmatch_v4, AF_INET, "127.0.0.1", 54321); + ret = acl_match(acl, &unmatch_v4); + ok(ret == ACL_DENY, "acl: matching address without matching port"); + + // 13. Invalid parameters + lives_ok({ + acl_delete(0); + acl_create(0, 0, ACL_ERROR); + acl_match(0, 0); + acl_truncate(0); + acl_name(0); + }, "acl: won't crash with NULL parameters"); + + // Return + return 0; +} diff --git a/src/tests/common/acl_tests.h b/src/tests/common/acl_tests.h new file mode 100644 index 0000000..a928e2d --- /dev/null +++ b/src/tests/common/acl_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_ACL_TESTS_H_ +#define _KNOTD_ACL_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api acl_tests_api; + +#endif /* _KNOTD_ACL_TESTS_H_ */ diff --git a/src/tests/common/da_tests.c b/src/tests/common/da_tests.c new file mode 100644 index 0000000..627e8aa --- /dev/null +++ b/src/tests/common/da_tests.c @@ -0,0 +1,330 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include "tests/common/da_tests.h" +#include "common/dynamic-array.h" +#include <unistd.h> +#include <urcu.h> + +static int da_tests_count(int argc, char *argv[]); +static int da_tests_run(int argc, char *argv[]); + +/* + * Unit API. + */ +unit_api da_tests_api = { + "Dynamic array", + &da_tests_count, + &da_tests_run +}; + +/* + * Unit implementation. + */ + +static const int DA_TEST_COUNT = 5; +static const int RCU_THREADS = 3; +static const int DA_FRAGMENT = 10; +static const int DA_DEF_SIZE = 1000; +static const int DA_OPERATIONS = 1000; +enum Operations { + DA_RESERVE = 0, + DA_OCCUPY = 1, + DA_RELEASE = 2, + DA_OPCOUNT = 3 +}; + +static int da_tests_count(int argc, char *argv[]) +{ + return DA_TEST_COUNT; +} + +static void do_something(int loops) +{ + int i; + int res = 1; + + static const int LOOPS = 10000; + + for (int j = 1; j <= LOOPS; ++j) { + for (i = 1; i <= loops; ++i) { + res *= i; + } + } +} + +static void *test_rcu_routine(void *obj) +{ + rcu_register_thread(); + rcu_read_lock(); + + do_something(1000); + + rcu_read_unlock(); + rcu_unregister_thread(); + + return NULL; +} + +static int test_rcu_threads() +{ + // Create threads + pthread_t *threads = malloc(RCU_THREADS * sizeof(pthread_t)); + for (int i = 0; i < RCU_THREADS; ++i) { + if (pthread_create(&threads[i], NULL, test_rcu_routine, NULL)) { + diag("rcu: failed to create thread %d", i); + free(threads); + return 0; + } + } + + // Join threads + void *pret = NULL; + for (int i = 0; i < RCU_THREADS; ++i) { + if (pthread_join(threads[i], &pret)) { + diag("rcu: failed to join thread %d", i); + free(threads); + return 0; + } + } + + synchronize_rcu(); + free(threads); + + return 1; +} + +static int test_da_init(da_array_t *arr) +{ + return da_initialize(arr, DA_DEF_SIZE, sizeof(uint)) == 0; +} + +static int test_da_random_op(da_array_t *arr) +{ + unsigned seed = (unsigned)time(0); + uint allocated = DA_DEF_SIZE; + uint size = 0; + + for (int i = 0; i < DA_OPERATIONS; ++i) { + int r = rand_r(&seed) % DA_OPCOUNT; + int count = rand_r(&seed) % DA_FRAGMENT + 1; + + switch (r) { + + // Perform reserve operation + case DA_RESERVE: + if (da_reserve(arr, count) >= 0 && + size <= allocated) { + if ((allocated - size) < count) { + allocated *= 2; + } + } else { + diag("dynamic-array: da_reserve(%p, %d) failed" + " (size %d, alloc'd %d)", + arr, count, size, allocated); + return 0; + } + break; + + // Perform occupy operation + case DA_OCCUPY: + if (da_occupy(arr, count) == 0) { + uint *items = (uint *) da_get_items(arr); + for (int j = 0; j < da_get_count(arr); ++j) { + items[j] = rand_r(&seed); + } + if (size <= allocated && + (allocated - size) >= count) { + size += count; + } else { + return 0; + } + } else { + diag("dynamic-array: da_occupy(%p, %d) failed" + " (size %d, alloc'd %d)", + arr, count, size, allocated); + return 0; + } + break; + + // Perform release operation + case DA_RELEASE: + if (arr->count > 0) { + count = (rand_r(&seed) % DA_FRAGMENT) % arr->count; + da_release(arr, count); + + if (size <= allocated && size >= count) { + size -= count; + } else { + return 0; + } + } + break; + + default: + break; + } + + // Check allocated / size + if (allocated != arr->allocated || size != arr->count) { + diag("dynamic-array: allocated memory %d (expected %d)" + " size %d (expected %d) mismatch", + arr->allocated, allocated, arr->count, size); + return 0; + } + } + + return 1; +} + +void *test_da_read(void *obj) +{ + rcu_register_thread(); + rcu_read_lock(); + + unsigned seed = (unsigned)time(0); + da_array_t *arr = (da_array_t *) obj; + int index = rand_r(&seed) % da_get_count(arr); + + note(" dynamic-array: read thread"); + note(" read thread: saving pointer to %d. item", index); + uint *item = &((uint *) da_get_items(arr))[index]; + note(" read thread: before: pointer: %p item: %u", item, *item); + + do_something(100000); + + note(" read thread after: pointer: %p item: %u", item, *item); + rcu_read_unlock(); + note(" read thread unlocked: pointer: %p item: %u", item, *item); + + do_something(10000); + + note(" read thread: now the item should be deallocated"); + //note(" read thread: pointer: %p item: %u", item, *item); + + rcu_unregister_thread(); + + return NULL; +} + +static int test_da_resize_holding(da_array_t *arr) +{ + int ret = 1; + rcu_register_thread(); + pthread_t reader; + + // Create thread for reading + note("dynamic-array: creating read threads"); + if (pthread_create(&reader, NULL, test_da_read, (void *)arr)) { + diag("dynamic-array: failed to create reading thread", + __func__); + rcu_unregister_thread(); + return 0; + } + + // Wait some time, so the other thread gets the item for reading + do_something(5000); + + // Force resize + note(" dynamic-array: array resized"); + if (da_reserve(arr, arr->allocated - arr->count + 1) == -1) { + diag("dynamic-array: da_reserve(%p, %d) failed", arr, + arr->allocated - arr->count + 1); + ret = 0; + } + + //Wait for the thread to finish + void *pret = NULL; + if (pthread_join(reader, &pret)) { + diag("dynamic-array: failed to join reading thread", + __func__); + ret = 0; + } + + rcu_unregister_thread(); + return ret; +} + +static int test_da_resize(da_array_t *arr) +{ + unsigned seed = (unsigned)time(0); + int orig_count = da_get_count(arr); + note("dynamic-array: allocated: %d, items: %d", arr->allocated, + orig_count); + // store the items currently in the array + int *items = (int *)malloc(orig_count * sizeof(int)); + for (int i = 0; i < orig_count; ++i) { + items[i] = ((int *)da_get_items(arr))[i]; + } + + // force resize + int res = 0; + while ((res = da_reserve(arr, 10)) == 0) { + int i = da_get_count(arr); + da_occupy(arr, 10); + for (; i < da_get_count(arr); ++i) { + ((int *)da_get_items(arr))[i] = rand_r(&seed); + } + } + + if (res < 0) { + diag("dynamic-array: failed to reserve space"); + return 0; + } + + int errors = 0; + for (int i = 0; i < orig_count; ++i) { + if (items[i] != ((int *)da_get_items(arr))[i]) { + diag("dynamic-array: Wrong item on position %d." + "Should be: %d, " + "present value: %d", i, items[i], + ((int *)da_get_items(arr))[i]); + ++errors; + } + } + + free(items); + + return errors == 0; +} + +static int da_tests_run(int argc, char *argv[]) +{ + // Init + rcu_init(); + da_array_t array; + + // Test 1: test rcu + ok(test_rcu_threads(), "dynamic-array: rcu tests"); + + // Test 2: init + ok(test_da_init(&array), "dynamic-array: init"); + + // Test 3: reserve/occupy random operations + ok(test_da_random_op(&array), + "dynamic-array: randomized reserve/occupy/release"); + + // Test 4: resizing array while holding an item + ok(test_da_resize_holding(&array), + "dynamic-array: resize array while holding an item"); + + // Test 5: resize + ok(test_da_resize(&array), "dynamic-array: resize array"); + + // Cleanup + da_destroy(&array); + return 0; +} diff --git a/src/tests/common/da_tests.h b/src/tests/common/da_tests.h new file mode 100644 index 0000000..d51b7be --- /dev/null +++ b/src/tests/common/da_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_DA_TESTS_H_ +#define _KNOTD_DA_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api da_tests_api; + +#endif /* _KNOTD_DA_TESTS_H_ */ diff --git a/src/tests/common/events_tests.c b/src/tests/common/events_tests.c new file mode 100644 index 0000000..d7702fe --- /dev/null +++ b/src/tests/common/events_tests.c @@ -0,0 +1,192 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdint.h> +#include <sys/time.h> + +#include "tests/common/events_tests.h" +#include "common/evqueue.h" +#include "common/evsched.h" + +static int events_tests_count(int argc, char *argv[]); +static int events_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api events_tests_api = { + "Event queue and scheduler", //! Unit name + &events_tests_count, //! Count scheduled tests + &events_tests_run //! Run scheduled tests +}; + +void* term_thr(void *arg) +{ + evsched_t *s = (evsched_t *)arg; + + /* Sleep for 100ms. */ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100 * 1000; // 100ms + select(0, 0, 0, 0, &tv); + + /* Issue termination event. */ + evsched_schedule_term(s, 0); + return 0; +} + +static int events_tests_count(int argc, char *argv[]) +{ + return 9 + 11; +} + +static int events_tests_run(int argc, char *argv[]) +{ + /* + * Event queue tests. + */ + + // 1. Construct an event queue + evqueue_t *q = evqueue_new(); + ok(q != 0, "evqueue: new"); + + // 2. Send integer through event queue + int ret = 0; + uint8_t sent = 0xaf, rcvd = 0; + ret = evqueue_write(q, &sent, sizeof(uint8_t)); + ok(ret == sizeof(uint8_t), "evqueue: send byte through"); + + // 3. Receive byte from event queue + ret = evqueue_read(q, &rcvd, sizeof(uint8_t)); + ok(ret == sizeof(uint8_t), "evqueue: received byte"); + + // 4. Received match + ok(sent == rcvd, "evqueue: received byte match"); + + // 5. Sending event + event_t ev, rev; + memset(&ev, 0, sizeof(event_t)); + memset(&rev, 0, sizeof(event_t)); + ev.type = 0xfa11; + ev.data = (void*)0xceed; + ret = evqueue_add(q, &ev); + ok(ret == 0, "evqueue: sent event to queue"); + + // 6. Poll for new events + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 100 * 1000 * 1000; // 100ms + ret = evqueue_poll(q, &ts, 0); + ok(ret > 0, "evqueue: polling queue for events"); + + // 7. Compare received event + ret = evqueue_get(q, &rev); + /* Compare useful data, as event owner was changed in evqueue_get(). */ + if (ev.type == rev.type && ev.data == rev.data) { + ret = 0; + } + ok(ret == 0, "evqueue: received event matches sent"); + + // 8. Invalid parameters + lives_ok({ + evqueue_free(0); + evqueue_poll(0,0,0); + evqueue_read(0, 0, 0); + evqueue_write(0, 0, 0); + evqueue_read(0, 0, 0); + evqueue_get(0, 0); + evqueue_add(0, 0); + }, "evqueue: won't crash with NULL parameters"); + + // 9. Free event queue + lives_ok({evqueue_free(&q);}, "evqueue: delete"); + + /* + * Event scheduler tests. + */ + + // 1. Construct event scheduler + event_t *e = 0; + evsched_t *s = evsched_new(); + ok(s != 0, "evsched: new"); + + // 2. Schedule event to happen after N ms + int msecs = 50; + struct timeval st, rt; + gettimeofday(&st, 0); + e = evsched_schedule_cb(s, 0, (void*)0xcafe, msecs); + ok(e != 0, "evsched: scheduled empty event after %dms", msecs); + + // 3. Wait for next event + e = evsched_next(s); + evsched_event_finished(s); + gettimeofday(&rt, 0); + ok(e != 0, "evsched: received valid event"); + + // 4. Check receive time + double passed = (rt.tv_sec - st.tv_sec) * 1000; + passed += (rt.tv_usec - st.tv_usec) / 1000; + double margin = msecs * 0.2; + double lb = msecs - margin, ub = msecs + margin; + int in_bounds = (passed >= lb) && (passed <= ub); + ok(in_bounds, "evsched: receive time %.1lfms is in <%.1lf,%.1lf>", + passed, lb, ub); + + // 5. Check data + ok(e->data == (void*)0xcafe, "evsched: received data is valid"); + + // 6. Delete event + lives_ok({evsched_event_free(s, e);}, "evsched: deleted event"); + + // 7. Insert and immediately cancel an event + e = evsched_schedule_cb(s, 0, (void*)0xdead, 1000); + ret = evsched_cancel(s, e); + ok(ret == 0, "evsched: inserted and cancelled an event"); + if (e) { + evsched_event_free(s, e); + } + + // 8. Start listener thread and block + pthread_t t; + pthread_create(&t, 0, term_thr, s); + e = evsched_next(s); + evsched_event_finished(s); + ok(e != 0, "evsched: received termination event"); + + // 9. Termination event is valid + ok(e->type == EVSCHED_TERM, "evsched: termination event is valid"); + evsched_event_free(s, e); + pthread_join(t, 0); + + // 10. Invalid parameters + lives_ok({ + evsched_delete(0); + evsched_event_new(0, 0); + evsched_event_free(0, 0); + evsched_next(0); + evsched_schedule(0, 0, 0); + evsched_schedule_cb(0, 0, 0, 0); + evsched_schedule_term(0, 0); + evsched_cancel(0, 0); + + }, "evsched: won't crash with NULL parameters"); + + // 11. Delete event scheduler + lives_ok({evsched_delete(&s);}, "evsched: delete"); + + + return 0; +} diff --git a/src/tests/common/events_tests.h b/src/tests/common/events_tests.h new file mode 100644 index 0000000..b54b6da --- /dev/null +++ b/src/tests/common/events_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD__EVENTS_TESTS_H_ +#define _KNOTD__EVENTS_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api events_tests_api; + +#endif /* _KNOTD__EVENTS_TESTS_H_ */ diff --git a/src/tests/common/fdset_tests.c b/src/tests/common/fdset_tests.c new file mode 100644 index 0000000..7dd95a1 --- /dev/null +++ b/src/tests/common/fdset_tests.c @@ -0,0 +1,177 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdint.h> +#include <sys/time.h> +#include <pthread.h> + +#include "tests/common/fdset_tests.h" +#include "common/fdset.h" + +#define WRITE_PATTERN ((char) 0xde) +#define WRITE_PATTERN_LEN sizeof(char) + + +/* Subtract the `struct timeval' values X and Y, + storing the result in RESULT. + Return 1 if the difference is negative, otherwise 0. + Copyright http://www.delorie.com/gnu/docs/glibc/libc_428.html +*/ +static int timeval_subtract (struct timeval *result, struct timeval *x, struct timeval* y) +{ + /* Perform the carry for the later subtraction by updating y. */ + if (x->tv_usec < y->tv_usec) { + int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; + y->tv_usec -= 1000000 * nsec; + y->tv_sec += nsec; + } + if (x->tv_usec - y->tv_usec > 1000000) { + int nsec = (x->tv_usec - y->tv_usec) / 1000000; + y->tv_usec += 1000000 * nsec; + y->tv_sec -= nsec; + } + + /* Compute the time remaining to wait. + tv_usec is certainly positive. */ + result->tv_sec = x->tv_sec - y->tv_sec; + result->tv_usec = x->tv_usec - y->tv_usec; + + /* Return 1 if result is negative. */ + return x->tv_sec < y->tv_sec; +} + +static size_t timeval_diff(struct timeval *from, struct timeval *to) { + struct timeval res; + timeval_subtract(&res, to, from); + return res.tv_sec*1000 + res.tv_usec/1000; +} + +static int fdset_tests_count(int argc, char *argv[]); +static int fdset_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api fdset_tests_api = { + "Native fdset poll wrapper", //! Unit name + &fdset_tests_count, //! Count scheduled tests + &fdset_tests_run //! Run scheduled tests +}; + +void* thr_action(void *arg) +{ + int *fd = (int *)arg; + + /* Sleep for 100ms. */ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100 * 1000; // 100ms + select(0, 0, 0, 0, &tv); + + /* Write pattern. */ + char pattern = WRITE_PATTERN; + int ret = write(*fd, &pattern, WRITE_PATTERN_LEN); + ret = ret; /* Use variable. */ + + return 0; +} + +static int fdset_tests_count(int argc, char *argv[]) +{ + return 11; +} + +static int fdset_tests_run(int argc, char *argv[]) +{ + diag("fdset: implements '%s'", fdset_method()); + + /* 1. Create fdset. */ + fdset_t *set = fdset_new(); + ok(set != 0, "fdset: new"); + + /* 2. Create pipe. */ + int fds[2], tmpfds[2]; + int ret = pipe(fds); + ok(ret >= 0, "fdset: pipe() works"); + ret = pipe(tmpfds); + + /* 3. Add fd to set. */ + ret = fdset_add(set, fds[0], OS_EV_READ); + ok(ret == 0, "fdset: add to set works"); + fdset_add(set, tmpfds[0], OS_EV_READ); + + /* Schedule write. */ + struct timeval ts, te; + gettimeofday(&ts, 0); + pthread_t t; + pthread_create(&t, 0, thr_action, &fds[1]); + + /* 4. Watch fdset. */ + ret = fdset_wait(set); + gettimeofday(&te, 0); + size_t diff = timeval_diff(&ts, &te); + + ok(ret > 0 && diff > 99 && diff < 10000, + "fdset: poll returned events in %zu ms", diff); + + /* 5. Prepare event set. */ + fdset_it_t it; + ret = fdset_begin(set, &it); + ok(ret == 0 && it.fd == fds[0], "fdset: begin is valid, ret=%d", ret); + + /* 6. Receive data. */ + char buf = 0x00; + ret = read(it.fd, &buf, WRITE_PATTERN_LEN); + ok(ret >= 0 && buf == WRITE_PATTERN, "fdset: contains valid data, fd=%d", it.fd); + + /* 7. Iterate event set. */ + ret = fdset_next(set, &it); + ok(ret < 0, "fdset: boundary check works"); + + /* 8. Remove from event set. */ + ret = fdset_remove(set, fds[0]); + ok(ret == 0, "fdset: remove from fdset works"); + close(fds[0]); + close(fds[1]); + ret = fdset_remove(set, tmpfds[0]); + close(tmpfds[1]); + close(tmpfds[1]); + + /* 9. Poll empty fdset. */ + ret = fdset_wait(set); + ok(ret <= 0, "fdset: polling empty fdset returns -1 (ret=%d)", ret); + + /* 10. Crash test. */ + lives_ok({ + fdset_destroy(0); + fdset_add(0, -1, 0); + fdset_remove(0, -1); + fdset_wait(0); + fdset_begin(0, 0); + fdset_end(0, 0); + fdset_next(0, 0); + fdset_method(); + }, "fdset: crash test successful"); + + /* 11. Destroy fdset. */ + ret = fdset_destroy(set); + ok(ret == 0, "fdset: destroyed"); + + /* Cleanup. */ + pthread_join(t, 0); + + return 0; +} diff --git a/src/tests/common/fdset_tests.h b/src/tests/common/fdset_tests.h new file mode 100644 index 0000000..d29e1a9 --- /dev/null +++ b/src/tests/common/fdset_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_FDSET_TESTS_H_ +#define _KNOTD_FDSET_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api fdset_tests_api; + +#endif /* _KNOTD_FDSET_TESTS_H_ */ diff --git a/src/tests/common/skiplist_tests.c b/src/tests/common/skiplist_tests.c new file mode 100644 index 0000000..4fe99ec --- /dev/null +++ b/src/tests/common/skiplist_tests.c @@ -0,0 +1,198 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <time.h> + +#include "tests/common/skiplist_tests.h" +#include "common/skip-list.h" + +static int skiplist_tests_count(int argc, char *argv[]); +static int skiplist_tests_run(int argc, char *argv[]); + +/* + * Unit API. + */ +unit_api skiplist_tests_api = { + "Skip list", + &skiplist_tests_count, + &skiplist_tests_run +}; + +/* + * Unit implementation. + */ + +static const int SKIPLIST_TEST_COUNT = 5; + +static int skiplist_tests_count(int argc, char *argv[]) +{ + return SKIPLIST_TEST_COUNT; +} + +/* Comparing and merging limited to int keys used in test. + */ +int test_skip_compare_keys(void *key1, void *key2) +{ + return ((long)key1 < (long)key2) ? + -1 : (((long)key1 > (long)key2) ? 1 : 0); +} + +int test_skip_merge_values(void **lvalue, void **rvalue) +{ + (*lvalue) = (void *)((long)(*lvalue) + (long)(*rvalue)); + return 0; +} + +int test_skiplist_create(skip_list_t **list) +{ + *list = skip_create_list(test_skip_compare_keys); + return *list != NULL; +} + +int test_skiplist_fill(skip_list_t *list, long *uitems, int loops) +{ + int uitem_count = 0; + for (int i = 0; i < loops; ++i) { + long key = rand() % 100 + 1; + long value = rand() % 100 + 1; + int res = skip_insert(list, (void *)key, (void *)value, + test_skip_merge_values); + switch (res) { + case -2: + diag("skiplist: merging failed"); + return 0; + break; + case -1: + diag("skiplist: insert failed"); + return 0; + break; + case 0: + uitems[uitem_count++] = key; + break; + default: + break; + } + } + + return uitem_count; +} + +int test_skiplist_lookup_seq(skip_list_t *list, long *uitems, int uitems_count) +{ + int errors = 0; + + // Sequential lookup + for (int i = 0; i < uitems_count; ++i) { + void *found = skip_find(list, (void *) uitems[i]); + if (found == NULL) { + diag("skiplist: sequential " + "lookup failed, key: %d", uitems[i]); + ++errors; + } + } + + if (errors) { + diag("skiplist: sequential lookup: %d found %d missed," + " %.2f%% success rate", + uitems_count - errors, errors, + (uitems_count - errors) / (float) uitems_count * 100.0); + } + + return errors == 0; +} + +int test_skiplist_lookup_rand(skip_list_t *list, long *uitems, int uitems_count) +{ + int errors = 0; + srand((unsigned)time(NULL)); + + // Random lookup + for (int i = 0; i < uitems_count; ++i) { + long key = rand() % uitems_count + 1; + void *found = skip_find(list, (void *) key); + if (found == NULL) { + diag("skiplist: random lookup" + "failed, key: %d", uitems[i]); + ++errors; + } + } + + if (errors) { + diag("skiplist: sequential lookup: " + "%d found %d missed, %.2f%% success rate", + uitems_count - errors, errors, + (uitems_count - errors) / (float) uitems_count * 100.0); + } + return errors == 0; +} + + +int test_skiplist_remove(skip_list_t *list, long *uitems, int uitems_count) +{ + int errors = 0; + + // delete items + for (int i = 0; i < uitems_count; ++i) { + int res = skip_remove(list, (void *) uitems[i], NULL, NULL); + switch (res) { + case 0: + break; + default: + ++errors; + break; + } + } + + if (errors) { + diag("skiplist: sequential lookup: %d found %d missed, " + "%.2f%% success rate", + uitems_count - errors, errors, + (uitems_count - errors) / (float) uitems_count * 100.0); + } + return errors == 0; +} + +static int skiplist_tests_run(int argc, char *argv[]) +{ + const int loops = 100; + int uitems_count = 0; + long *uitems = malloc(loops * sizeof(long)); + skip_list_t *list = 0; + + // Test 1: create + ok(test_skiplist_create(&list), "skiplist: create"); + + // Test 2: fill + ok(uitems_count = test_skiplist_fill(list, uitems, loops), + "skiplist: fill"); + + // Test 3: sequential lookup + ok(test_skiplist_lookup_seq(list, uitems, uitems_count), + "skiplist: sequential lookup"); + + // Test 4: sequential lookup + ok(test_skiplist_lookup_seq(list, uitems, uitems_count), + "skiplist: random lookup lookup"); + + // Test 5: remove items + ok(test_skiplist_remove(list, uitems, uitems_count), + "skiplist: random lookup lookup"); + + // Cleanup + skip_destroy_list(&list, NULL, NULL); + free(uitems); + return 0; +} diff --git a/src/tests/common/skiplist_tests.h b/src/tests/common/skiplist_tests.h new file mode 100644 index 0000000..ff91706 --- /dev/null +++ b/src/tests/common/skiplist_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_SKIPLIST_TESTS_H_ +#define _KNOTD_SKIPLIST_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api skiplist_tests_api; + +#endif /* _KNOTD_SKIPLIST_TESTS_H_ */ diff --git a/src/tests/common/slab_tests.c b/src/tests/common/slab_tests.c new file mode 100644 index 0000000..f362ca0 --- /dev/null +++ b/src/tests/common/slab_tests.c @@ -0,0 +1,141 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <unistd.h> +#include <stdlib.h> +#include <time.h> +#include <stdbool.h> + +#include "tests/common/slab_tests.h" +#include "common/slab/slab.h" +#include "knot/common.h" + +/* Explicitly ask for symbols, + * as the constructor and destructor + * aren't created for test modules. + */ +extern void slab_init(); +extern void slab_deinit(); + +static int slab_tests_count(int argc, char *argv[]); +static int slab_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api slab_tests_api = { + "SLAB allocator", //! Unit name + &slab_tests_count, //! Count scheduled tests + &slab_tests_run //! Run scheduled tests +}; + +static int slab_tests_count(int argc, char *argv[]) +{ + return 7; +} + +static int slab_tests_run(int argc, char *argv[]) +{ + // 1. Create slab cache + srand(time(0)); + const unsigned pattern = 0xdeadbeef; + slab_cache_t cache; + int ret = slab_cache_init(&cache, sizeof(int)); + ok(ret == 0, "slab: created empty cache"); + + // 2. Couple alloc/free + bool valid_free = true; + lives_ok({ + for(int i = 0; i < 100; ++i) { + int* data = (int*)slab_cache_alloc(&cache); + *data = pattern; + slab_free(data); + if (*data == pattern) + valid_free = false; + } + }, "slab: couple alloc/free"); + + // 5. Verify freed block + ok(valid_free, "slab: freed memory is correctly invalidated"); + + // 4. Reap memory + slab_t* slab = cache.slabs_free; + int free_count = 0; + while (slab) { + slab_t* next = slab->next; + if (slab_isempty(slab)) { + ++free_count; + } + slab = next; + } + + int reaped = slab_cache_reap(&cache); + cmp_ok(reaped, "==", free_count, "slab: cache reaping works"); + + // Stress cache + int alloc_count = 73521; + void** ptrs = alloca(alloc_count * sizeof(void*)); + int ptrs_i = 0; + for(int i = 0; i < alloc_count; ++i) { + double roll = rand() / (double) RAND_MAX; + if ((ptrs_i == 0) || (roll < 0.6)) { + int id = ptrs_i++; + ptrs[id] = slab_cache_alloc(&cache); + if (ptrs[id] == 0) { + ptrs_i--; + } else { + int* data = (int*)ptrs[id]; + *data = pattern; + } + } else { + slab_free(ptrs[--ptrs_i]); + } + } + + // 5. Delete cache + slab_cache_destroy(&cache); + ok(cache.bufsize == 0, "slab: freed cache"); + + // 6. Greate GP allocator + slab_alloc_t alloc; + ret = slab_alloc_init(&alloc); + ok(ret == 0, "slab: created GP allocator"); + + // 7. Stress allocator + unsigned ncount = 0; + ptrs_i = 0; + for(int i = 0; i < alloc_count; ++i) { + double roll = rand() / (double) RAND_MAX; + size_t bsize = roll * 2048; + bsize = MAX(bsize, 8); + if ((ptrs_i == 0) || (roll < 0.6)) { + void* m = slab_alloc_alloc(&alloc, bsize); + if (m == 0) { + ++ncount; + } else { + ptrs[ptrs_i++] = m; + } + } else { + slab_free(ptrs[--ptrs_i]); + } + } + + cmp_ok(ncount, "==", 0, "slab: GP allocator alloc/free working"); + + // 7. Destroy allocator + slab_alloc_destroy(&alloc); + + return 0; +} diff --git a/src/tests/common/slab_tests.h b/src/tests/common/slab_tests.h new file mode 100644 index 0000000..4d45fb8 --- /dev/null +++ b/src/tests/common/slab_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_SLAB_TESTS_H_ +#define _KNOTD_SLAB_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api slab_tests_api; + +#endif /* _KNOTD_SLAB_TESTS_H_ */ diff --git a/src/tests/files/sample_conf b/src/tests/files/sample_conf new file mode 100644 index 0000000..6cd9e50 --- /dev/null +++ b/src/tests/files/sample_conf @@ -0,0 +1,59 @@ +# configuration file will follow bird (and juniper) type of configuration file +# i.e. curly brackets will be used; + +# what to do with }; +# a) ignore ; if it follows } + +system { + + identity "I have no mouth and must scream"; + version "Infinitesimal"; + storage "/var/run/knot/"; +} + +keys { + key0.example.net hmac-md5 "Wg=="; # key special for one remote + key1.example.net hmac-md5 "==gW"; # implicit key for whole zone +} + +remotes { + remote0 { address 1.2.3.4; } +} + +zones { + example.net { + file "/var/lib/knot/example.net"; + xfr-out remote0; + } +} + +interfaces { + interface0 { + address 10.10.1.1; + port 53531; + } + + interface1 { + address ::0; + # port 53; + } +} + +log { + syslog { + any notice, warning, error; + zone all; + } + + file "/var/log/knot/server.err" { + server error; + } + + stderr { + any warning, error; + } + + stdout { + any info; + } +} diff --git a/src/tests/knot/conf_tests.c b/src/tests/knot/conf_tests.c new file mode 100644 index 0000000..61520ea --- /dev/null +++ b/src/tests/knot/conf_tests.c @@ -0,0 +1,141 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> + +#include "tests/knot/conf_tests.h" +#include "knot/conf/conf.h" + +/* Resources. */ +#include "tests/sample_conf.rc" + +static int conf_tests_count(int argc, char *argv[]); +static int conf_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api conf_tests_api = { + "Configuration parser", //! Unit name + &conf_tests_count, //! Count scheduled tests + &conf_tests_run //! Run scheduled tests +}; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int conf_tests_count(int argc, char *argv[]) +{ + return 21; +} + +/*! Run all scheduled tests for given parameters. + */ +static int conf_tests_run(int argc, char *argv[]) +{ + + // Test 1: Allocate new config + const char *config_fn = "rc:/sample_conf"; + conf_t *conf = conf_new(config_fn); + ok(conf != 0, "config_new()"); + + // Test 2: Parse config + int ret = conf_parse_str(conf, sample_conf_rc); + ok(ret == 0, "parsing configuration file %s", config_fn); + skip(ret != 0, conf_tests_count(argc, argv) - 2); + { + + // Test 3: Test server version (0-level depth) + is(conf->version, "Infinitesimal", "server version loaded ok"); + + // Test 4: Test interfaces (1-level depth) + ok(!EMPTY_LIST(conf->ifaces), "configured interfaces exist"); + + // Test 5,6,7,8: Interfaces content (2-level depth) + struct node *n = HEAD(conf->ifaces); + conf_iface_t *iface = (conf_iface_t*)n; + is(iface->address, "10.10.1.1", "interface0 address check"); + cmp_ok(iface->port, "==", 53531, "interface0 port check"); + n = n->next; + iface = (conf_iface_t*)n; + is(iface->address, "::0", "interface1 address check"); + cmp_ok(iface->port, "==", 53, "interface1 default port check"); + + // Test 9,10: Check server key + if(conf->key_count <= 0) { + ok(0, "TSIG key algorithm check - NO KEY FOUND"); + ok(0, "TSIG key secret check - NO KEY FOUND"); + } else { + knot_key_t *k = &((conf_key_t *)HEAD(conf->keys))->k; + cmp_ok(k->algorithm, "==", KNOT_TSIG_ALG_HMAC_MD5, + "TSIG key algorithm check"); + is(k->secret, "Wg==", "TSIG key secret check"); + } + + // Test 11,12,13,14,15,16,17,18: Check logging facilities + cmp_ok(conf->logs_count, "==", 4, "log facilites count check"); + n = HEAD(conf->logs); + ok(!EMPTY_LIST(conf->logs), "log facilities not empty"); + + conf_log_t *log = (conf_log_t*)n; + node *nm = HEAD(log->map); + conf_log_map_t *m = (conf_log_map_t*)nm; + cmp_ok(log->type, "==", LOGT_SYSLOG, "log0 is syslog"); + + skip(EMPTY_LIST(log->map), 5); + { + cmp_ok(m->source, "==", LOG_ANY, "syslog first rule is ANY"); + int mask = LOG_MASK(LOG_NOTICE)|LOG_MASK(LOG_WARNING)|LOG_MASK(LOG_ERR); + cmp_ok(m->prios, "==", mask, "syslog mask is equal"); + nm = nm->next; + m = (conf_log_map_t*)nm; + ok(m != 0, "syslog has more than 1 rule"); + skip(!m, 2); + { + cmp_ok(m->source, "==", LOG_ZONE, "syslog next rule is for zone"); + cmp_ok(m->prios, "==", 0xff, "rule for zone is: any level"); + } + endskip; + } endskip; + + // Test 19,20: File facility checks + n = n->next; + log = (conf_log_t*)n; + ok(n != 0, "log has next facility"); + skip(!n, 1); + { + is(log->file, "/var/log/knot/server.err", "log file matches"); + } endskip; + + // Test 21: Load key dname + const char *sample_str = "key0.example.net"; + knot_dname_t *sample = knot_dname_new_from_str(sample_str, + strlen(sample_str), 0); + if (conf->key_count > 0) { + knot_key_t *k = &((conf_key_t *)HEAD(conf->keys))->k; + ok(knot_dname_compare(sample, k->name) == 0, + "TSIG key dname check"); + } else { + ok(0, "TSIG key dname check - NO KEY FOUND"); + } + knot_dname_free(&sample); + + } endskip; + + // Deallocating config + conf_free(conf); + + return 0; +} diff --git a/src/tests/knot/conf_tests.h b/src/tests/knot/conf_tests.h new file mode 100644 index 0000000..dfd2fd7 --- /dev/null +++ b/src/tests/knot/conf_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_CONF_TESTS_H_ +#define _KNOTD_CONF_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api conf_tests_api; + +#endif /* _KNOTD_CONF_TESTS_H_ */ diff --git a/src/tests/knot/dthreads_tests.c b/src/tests/knot/dthreads_tests.c new file mode 100644 index 0000000..d95fbed --- /dev/null +++ b/src/tests/knot/dthreads_tests.c @@ -0,0 +1,392 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +#include <pthread.h> +#include <sched.h> +#include <sys/select.h> +#include <signal.h> + +#include "tests/knot/dthreads_tests.h" +#include "knot/server/dthreads.h" + +static int dt_tests_count(int argc, char *argv[]); +static int dt_tests_run(int argc, char *argv[]); + +/* + * Unit API. + */ +unit_api dthreads_tests_api = { + "DThreads", + &dt_tests_count, + &dt_tests_run +}; + +/* + * Unit implementation. + */ +static const int DT_TEST_COUNT = 23; + +/* Unit runnable data. */ +static pthread_mutex_t _runnable_mx; +static volatile int _runnable_i = 0; +static const int _runnable_cycles = 10000; + +/*! \brief Unit runnable. */ +int runnable(struct dthread_t *thread) +{ + for (int i = 0; i < _runnable_cycles; ++i) { + + // Increase counter + pthread_mutex_lock(&_runnable_mx); + ++_runnable_i; + pthread_mutex_unlock(&_runnable_mx); + + // Cancellation point + if (dt_is_cancelled(thread)) { + break; + } + + // Yield + sched_yield(); + } + + return 0; +} + +/*! \brief Unit blocking runnable. */ +int runnable_simio(struct dthread_t *thread) +{ + // Infinite blocking, must be interrupted + select(0, 0, 0, 0, 0); + return 0; +} + +/*! \brief Create unit. */ +static inline dt_unit_t *dt_test_create(int size) +{ + return dt_create(size); +} + +/*! \brief Assign a task. */ +static inline int dt_test_single(dt_unit_t *unit) +{ + return dt_repurpose(unit->threads[0], &runnable, NULL) == 0; +} + +/*! \brief Assign task to all unit threads. */ +static inline int dt_test_coherent(dt_unit_t *unit) +{ + int ret = 0; + for (int i = 0; i < unit->size; ++i) { + ret += dt_repurpose(unit->threads[i], &runnable, NULL); + } + + return ret == 0; +} + +/*! \brief Repurpose single thread. */ +static inline int dt_test_repurpose(dt_unit_t *unit, int id) +{ + return dt_repurpose(unit->threads[id], &runnable_simio, NULL) == 0; +} + +/*! \brief Cancel single thread. */ +static inline int dt_test_cancel(dt_unit_t *unit, int id) +{ + return dt_cancel(unit->threads[id]) == 0; +} + +/*! \brief Reanimate dead threads. */ +static inline int dt_test_reanimate(dt_unit_t *unit) +{ + // Compact all threads + int ret = 0; + ret += dt_compact(unit); + + // Remove purpose from all + for (int i = 0; i < unit->size; ++i) { + ret += dt_repurpose(unit->threads[i], 0, 0); + } + + // Set single thread to purpose + ret += dt_repurpose(unit->threads[0], &runnable, 0); + + // Restart + _runnable_i = 0; + ret += dt_start(unit); + + // Wait for finish + ret += dt_join(unit); + + // Verify + int expected = 1 * _runnable_cycles; + if (_runnable_i != expected) { + return 0; + } + + // Check return codes + return ret == 0; +} + +/*! \brief Resize unit. */ +static inline int dt_test_resize(dt_unit_t *unit, int size) +{ + // Resize + int ret = 0; + ret = dt_resize(unit, size); + if (ret < 0) { + return 0; + } + + // Check outcome + if (unit->size != size) { + return 0; + } + + // Repurpose all + _runnable_i = 0; + for (int i = 0; i < size; ++i) { + ret += dt_repurpose(unit->threads[i], &runnable, 0); + ret += dt_start_id(unit->threads[i]); + } + + // Wait for finish + ret += dt_join(unit); + + // Verify + int expected = size * _runnable_cycles; + note("resize test: %d threads, %d ticks, %d expected", + size, _runnable_i, expected); + if (_runnable_i != expected) { + return 0; + } + + // Check return codes + return ret == 0; +} + +/*! \brief Resize unit while threads are active. */ +static inline int dt_test_liveresize(dt_unit_t *unit) +{ + // Size + int size = unit->size; + int size_hi = size + 2; + int size_lo = size - 1; + + // Expand + int ret = 0; + ret = dt_resize(unit, size_hi); + if (ret < 0) { + return 0; + } + + // Repurpose all + for (int i = 0; i < unit->size; ++i) { + ret += dt_repurpose(unit->threads[i], &runnable, 0); + } + + // Restart + _runnable_i = 0; + ret += dt_start(unit); + + // Shrink + ret += dt_resize(unit, size_lo); + + // Wait for finish + ret += dt_join(unit); + + // Verify + int expected_hi = size_hi * _runnable_cycles; + int expected_lo = size_lo * _runnable_cycles; + note("resize test: %d->%d->%d threads, %d ticks, <%d,%d> expected", + size, size_hi, size_lo, _runnable_i, expected_lo, expected_hi); + + if (_runnable_i > expected_hi || _runnable_i < expected_lo) { + return 0; + } + + // Check return codes + return ret == 0; +} + +/*! \brief Start unit. */ +static inline int dt_test_start(dt_unit_t *unit) +{ + return dt_start(unit) == 0; +} + +/*! \brief Stop unit. */ +static inline int dt_test_stop(dt_unit_t *unit) +{ + return dt_stop(unit); +} + +/*! \brief Join unit. */ +static inline int dt_test_join(dt_unit_t *unit) +{ + return dt_join(unit) == 0; +} + +/*! API: return number of tests. */ +static int dt_tests_count(int argc, char *argv[]) +{ + return DT_TEST_COUNT; +} + +// Signal handler +static void interrupt_handle(int s) +{ +} + +/*! API: run tests. */ +static int dt_tests_run(int argc, char *argv[]) +{ + // Register service and signal handler + struct sigaction sa; + sa.sa_handler = interrupt_handle; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, NULL); // Interrupt + + /* Initialize */ + srand(time(NULL)); + struct timeval tv; + pthread_mutex_init(&_runnable_mx, NULL); + + /* Test 1: Create unit */ + dt_unit_t *unit = dt_test_create(dt_optimal_size()); + ok(unit != 0, "dthreads: create unit (optimal size %d)", unit->size); + skip(unit == 0, DT_TEST_COUNT - 1); + + /* Test 2: Assign a single task. */ + ok(dt_test_single(unit), "dthreads: assign single task"); + + /* Test 3: Start tasks. */ + _runnable_i = 0; + ok(dt_test_start(unit), "dthreads: start single task"); + + /* Test 4: Wait for tasks. */ + ok(dt_test_join(unit), "dthreads: join threads"); + + /* Test 5: Compare counter. */ + int expected = _runnable_cycles * 1; + cmp_ok(_runnable_i, "==", expected, "dthreads: result ok"); + + /* Test 6: Repurpose threads. */ + _runnable_i = 0; + ok(dt_test_coherent(unit), "dthreads: repurpose to coherent"); + + /* Test 7: Restart threads. */ + ok(dt_test_start(unit), "dthreads: start coherent unit"); + + /* Test 8: Repurpose single thread. */ + tv.tv_sec = 0; + tv.tv_usec = 4000 + rand() % 1000; // 4-5ms + note("waiting for %dus to let thread do some work ...", + tv.tv_usec); + select(0, 0, 0, 0, &tv); + ok(dt_test_repurpose(unit, 0), "dthreads: repurpose on-the-fly"); + + /* Test 9: Cancel blocking thread. */ + tv.tv_sec = 0; + tv.tv_usec = (250 + rand() % 500) * 1000; // 250-750ms + note("waiting for %dms to let thread pretend blocking I/O ...", + tv.tv_usec / 1000); + select(0, 0, 0, 0, &tv); + ok(dt_test_cancel(unit, 0), "dthreads: cancel blocking thread"); + + /* Test 10: Wait for tasks. */ + ok(dt_test_join(unit), "dthreads: join threads"); + + /* Test 11: Compare counter. */ + int expected_lo = _runnable_cycles * (unit->size - 1); + cmp_ok(_runnable_i, ">=", expected_lo, + "dthreads: result %d is => %d", _runnable_i, expected_lo); + + /* Test 12: Compare counter #2. */ + int expected_hi = _runnable_cycles * unit->size; + cmp_ok(_runnable_i, "<=", expected_hi, + "dthreads: result %d is <= %d", _runnable_i, expected_hi); + + /* Test 13: Reanimate dead threads. */ + ok(dt_test_reanimate(unit), "dthreads: reanimate dead threads"); + + /* Test 14: Expand unit by 100%. */ + int size = unit->size * 2; + ok(dt_test_resize(unit, size), + "dthreads: expanding unit to size * 2 (%d threads)", size); + + /* Test 15: Shrink unit to half. */ + size = unit->size / 2; + ok(dt_test_resize(unit, size), + "dthreads: shrinking unit to size / 2 (%d threads)", size); + + /* Test 16: Resize while threads are active. */ + ok(dt_test_liveresize(unit), "dthreads: resizing unit while active"); + + /* Test 17: Deinitialize */ + dt_delete(&unit); + ok(unit == 0, "dthreads: delete unit"); + endskip; + + /* Test 18: Wrong values. */ + unit = dt_create(-1); + ok(unit == 0, "dthreads: create with negative count"); + unit = dt_create_coherent(dt_optimal_size(), 0, 0); + + /* Test 19: NULL runnable. */ + cmp_ok(dt_start(unit), "==", 0, "dthreads: start with NULL runnable"); + + /* Test 20: resize to negative value. */ + cmp_ok(dt_resize(unit, -19), + "<", 0, "dthreads: resize to negative size"); + + /* Test 21: resize to zero value. */ + cmp_ok(dt_resize(unit, 0), "<", 0, "dthreads: resize to NULL size"); + dt_join(unit); + dt_delete(&unit); + + /* Test 22: NULL operations crashing. */ + int op_count = 14; + int expected_min = op_count * -1; + // All functions must return -1 at least + int ret = 0; + lives_ok( { + ret += dt_activate(0); // -1 + ret += dt_cancel(0); // -1 + ret += dt_compact(0); // -1 + dt_delete(0); // + ret += dt_is_cancelled(0); // 0 + ret += dt_join(0); // -1 + ret += dt_repurpose(0, 0, 0); // -1 + ret += dt_resize(0, 0); // -1 + ret += dt_setprio(0, 0); // -1 + ret += dt_signalize(0, SIGALRM); // -1 + ret += dt_start(0); // -1 + ret += dt_start_id(0); // -1 + ret += dt_stop(0); // -1 + ret += dt_stop_id(0); // -1 + ret += dt_unit_lock(0); // -1 + ret += dt_unit_unlock(0); // -1 + }, "dthreads: not crashed while executing functions on NULL context"); + + /* Test 23: expected results. */ + cmp_ok(ret, "<=", expected_min, + "dthreads: correct values when passed NULL context " + "(%d, min: %d)", ret, expected_min); + + pthread_mutex_destroy(&_runnable_mx); + return 0; +} diff --git a/src/tests/knot/dthreads_tests.h b/src/tests/knot/dthreads_tests.h new file mode 100644 index 0000000..e41bdc5 --- /dev/null +++ b/src/tests/knot/dthreads_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_DTHREADS_TESTS_H_ +#define _KNOTD_DTHREADS_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api dthreads_tests_api; + +#endif /* _KNOTD_DTHREADS_TESTS_H_ */ diff --git a/src/tests/knot/journal_tests.c b/src/tests/knot/journal_tests.c new file mode 100644 index 0000000..21c92fe --- /dev/null +++ b/src/tests/knot/journal_tests.c @@ -0,0 +1,184 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +#include <string.h> + +#include "tests/knot/journal_tests.h" +#include "knot/server/journal.h" +#include "knot/other/error.h" + +static int journal_tests_count(int argc, char *argv[]); +static int journal_tests_run(int argc, char *argv[]); + +/* + * Unit API. + */ +unit_api journal_tests_api = { + "Journal", + &journal_tests_count, + &journal_tests_run +}; + +/* + * Unit implementation. + */ +static const int JOURNAL_TEST_COUNT = 11; + +/*! \brief Generate random string with given length. */ +static int randstr(char* dst, size_t len) +{ + for (int i = 0; i < len - 1; ++i) { + dst[i] = '0' + (int) (('Z'-'0') * (rand() / (RAND_MAX + 1.0))); + } + dst[len - 1] = '\0'; + + return 0; +} + +/*! \brief Walk journal of chars into buffer. */ +static int _wbi = 0; +static char _walkbuf[7]; +static int walkchars_cmp(uint64_t k1, uint64_t k2) { + return k1 - k2; +} + +static int walkchars(journal_t *j, journal_node_t *n) { + journal_read(j, n->id, walkchars_cmp, _walkbuf + _wbi); + ++_wbi; + return 0; +} + +/*! API: return number of tests. */ +static int journal_tests_count(int argc, char *argv[]) +{ + return JOURNAL_TEST_COUNT; +} + +/*! API: run tests. */ +static int journal_tests_run(int argc, char *argv[]) +{ + /* Test 1: Create tmpfile. */ + int fsize = 8092; + int jsize = 6; + char jfn_buf[] = "/tmp/journal.XXXXXX"; + int tmp_fd = mkstemp(jfn_buf); + ok(tmp_fd >= 0, "journal: create temporary file"); + skip(tmp_fd < 0, JOURNAL_TEST_COUNT - 1); + + /* Test 2: Create journal. */ + const char *jfilename = jfn_buf; + int ret = journal_create(jfilename, jsize); + ok(ret == KNOTD_EOK, "journal: create journal '%s'", jfilename); + + /* Test 3: Open journal. */ + journal_t *j = journal_open(jfilename, fsize, 0); + ok(j != 0, "journal: open"); + + /* Test 4: Write entry to log. */ + const char *sample = "deadbeef"; + ret = journal_write(j, 0x0a, sample, strlen(sample)); + ok(ret == KNOTD_EOK, "journal: write"); + + /* Test 5: Read entry from log. */ + char tmpbuf[64] = {'\0'}; + ret = journal_read(j, 0x0a, 0, tmpbuf); + ok(ret == KNOTD_EOK, "journal: read entry"); + + /* Test 6: Compare read data. */ + ret = strncmp(sample, tmpbuf, strlen(sample)); + ok(ret == 0, "journal: read data integrity check"); + + /* Append several characters. */ + journal_write(j, 0, "X", 1); /* Dummy */ + char word[7] = { 'w', 'o', 'r', 'd', '0', '\0', '\0' }; + for (int i = 0; i < strlen(word); ++i) { + journal_write(j, i, word+i, 1); + } + + /* Test 7: Compare journal_walk() result. */ + _wbi = 0; + journal_walk(j, walkchars); + _walkbuf[_wbi] = '\0'; + ret = strcmp(word, _walkbuf); + ok(ret == 0, "journal: read data integrity check 2 '%s'", _walkbuf); + _wbi = 0; + + /* Test 8: Change single letter and compare. */ + word[5] = 'X'; + journal_write(j, 5, word+5, 1); /* append 'X', shifts out 'w' */ + journal_walk(j, walkchars); + _walkbuf[_wbi] = '\0'; + ret = strcmp(word + 1, _walkbuf); + ok(ret == 0, "journal: read data integrity check 3 '%s'", _walkbuf); + _wbi = 0; + + /* Close journal. */ + journal_close(j); + + /* Recreate journal. */ + remove(jfilename); + fsize = 8092; + jsize = 512; + ret = journal_create(jfilename, jsize); + j = journal_open(jfilename, fsize, 0); + + /* Test 9: Write random data. */ + int chk_key = 0; + char chk_buf[64] = {'\0'}; + ret = 0; + const int itcount = 1;//jsize * 5 + 5; + for (int i = 0; i < itcount; ++i) { + int key = rand() % 65535; + randstr(tmpbuf, sizeof(tmpbuf)); + if (journal_write(j, key, tmpbuf, sizeof(tmpbuf)) != KNOTD_EOK) { + ret = -1; + break; + } + + /* Store some key on the end. */ + if (i == itcount - 2) { + chk_key = key; + memcpy(chk_buf, tmpbuf, sizeof(chk_buf)); + } + } + ok(ret == 0, "journal: sustained looped writes"); + + /* Test 10: Check data integrity. */ + memset(tmpbuf, 0, sizeof(tmpbuf)); + journal_read(j, chk_key, 0, tmpbuf); + ret = strncmp(chk_buf, tmpbuf, sizeof(chk_buf)); + ok(ret == 0, "journal: read data integrity check"); + + /* Test 11: Reopen log and re-read value. */ + memset(tmpbuf, 0, sizeof(tmpbuf)); + journal_close(j); + j = journal_open(jfilename, fsize, 0); + journal_read(j, chk_key, 0, tmpbuf); + ret = strncmp(chk_buf, tmpbuf, sizeof(chk_buf)); + ok(ret == 0, "journal: read data integrity check after close/open"); + + /* Close journal. */ + journal_close(j); + + /* Close temporary file fd. */ + close(tmp_fd); + + /* Delete journal. */ + remove(jfilename); + + endskip; + + return 0; +} diff --git a/src/tests/knot/journal_tests.h b/src/tests/knot/journal_tests.h new file mode 100644 index 0000000..beec8ca --- /dev/null +++ b/src/tests/knot/journal_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_JOURNAL_TESTS_H_ +#define _KNOTD_JOURNAL_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api journal_tests_api; + +#endif /* _KNOTD_JOURNAL_TESTS_H_ */ diff --git a/src/tests/knot/server_tests.c b/src/tests/knot/server_tests.c new file mode 100644 index 0000000..5ae04d8 --- /dev/null +++ b/src/tests/knot/server_tests.c @@ -0,0 +1,113 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +#include "tests/knot/server_tests.h" +#include "knot/server/server.h" + +static int server_tests_count(int argc, char *argv[]); +static int server_tests_run(int argc, char *argv[]); + +/* + * Unit API. + */ +unit_api server_tests_api = { + "Server", + &server_tests_count, + &server_tests_run +}; + +/* + * Unit implementation. + */ + +static const int SERVER_TEST_COUNT = 4; + +/*! Test: create server. */ +server_t *test_server_create() +{ + return server_create(); +} + +/*! Test: start server. */ +int test_server_start(server_t *s) +{ + return server_start(s) == 0; +} + +/*! Test: finish server. */ +int test_server_finish(server_t *s) +{ + return server_wait(s) == 0; +} + +/*! Test: stop server. */ +int test_server_destroy(server_t *s) +{ + server_destroy(&s); + return s == 0; +} + +/*! API: return number of tests. */ +static int server_tests_count(int argc, char *argv[]) +{ + return SERVER_TEST_COUNT + 1; +} + +// Signal handler +static void interrupt_handle(int s) +{ +} + +/*! API: run tests. */ +static int server_tests_run(int argc, char *argv[]) +{ + server_t *server = 0; + int ret = 0; + + // Register service and signal handler + struct sigaction sa; + sa.sa_handler = interrupt_handle; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, NULL); // Interrupt + + //! Test server for correct initialization + server = test_server_create(); + ok(server != 0, "server: initialized"); + + //! Test server startup + ret = 0; + lives_ok( { + ret = test_server_start(server); + }, "server: not crashing on runtime"); + + //! Test server exit code + ok(ret, "server: started ok"); + if (ret) { + server_stop(server); + } else { + diag("server crashed, skipping deinit and destroy tests"); + } + + //! Test server waiting for finish + skip(!ret, 2); + ok(test_server_finish(server), "server: waiting for finish"); + + //! Test server for correct deinitialization + ok(test_server_destroy(server), "server: deinit"); + endskip; + + return 0; +} diff --git a/src/tests/knot/server_tests.h b/src/tests/knot/server_tests.h new file mode 100644 index 0000000..43ad0c1 --- /dev/null +++ b/src/tests/knot/server_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_SERVER_TESTS_H_ +#define _KNOTD_SERVER_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api server_tests_api; + +#endif /* _KNOTD_SERVER_TESTS_H_ */ diff --git a/src/tests/libknot/files/parsed_data b/src/tests/libknot/files/parsed_data Binary files differnew file mode 100644 index 0000000..4027c92 --- /dev/null +++ b/src/tests/libknot/files/parsed_data diff --git a/src/tests/libknot/files/parsed_data_queries b/src/tests/libknot/files/parsed_data_queries Binary files differnew file mode 100644 index 0000000..5857c87 --- /dev/null +++ b/src/tests/libknot/files/parsed_data_queries diff --git a/src/tests/libknot/files/raw_data b/src/tests/libknot/files/raw_data Binary files differnew file mode 100644 index 0000000..f94236b --- /dev/null +++ b/src/tests/libknot/files/raw_data diff --git a/src/tests/libknot/files/raw_data_queries b/src/tests/libknot/files/raw_data_queries Binary files differnew file mode 100644 index 0000000..9062d5a --- /dev/null +++ b/src/tests/libknot/files/raw_data_queries diff --git a/src/tests/libknot/libknot/cuckoo_tests.c b/src/tests/libknot/libknot/cuckoo_tests.c new file mode 100644 index 0000000..c1306a3 --- /dev/null +++ b/src/tests/libknot/libknot/cuckoo_tests.c @@ -0,0 +1,382 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +#include <time.h> +#include <assert.h> + +#include "tests/libknot/libknot/cuckoo_tests.h" + +#include "libknot/hash/cuckoo-hash-table.h" + +//#define CK_TEST_DEBUG +//#define CK_TEST_LOOKUP +//#define CK_TEST_OUTPUT +//#define CK_TEST_REMOVE +//#define CK_TEST_COMPARE + +#ifdef CK_TEST_DEBUG +#define CK_TEST_LOOKUP +#define CK_TEST_OUTPUT +#define CK_TEST_REMOVE +#define CK_TEST_COMPARE +#endif + +/*----------------------------------------------------------------------------*/ + +static int cuckoo_tests_count(int argc, char *argv[]); +static int cuckoo_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api cuckoo_tests_api = { + "Cuckoo hashing", //! Unit name + &cuckoo_tests_count, //! Count scheduled tests + &cuckoo_tests_run //! Run scheduled tests +}; + +/*----------------------------------------------------------------------------*/ + +/* + * Unit implementation + */ +static const int CUCKOO_TESTS_COUNT = 13; +static const int CUCKOO_MAX_ITEMS = 1000; +static const int CUCKOO_TEST_MAX_KEY_SIZE = 10; + +typedef struct test_cuckoo_items { + char **keys; + size_t *key_sizes; + size_t *values; + size_t *deleted; + int count; + int total_count; +} test_cuckoo_items; + +/*----------------------------------------------------------------------------*/ + +static inline char rand_char() +{ + return (char)((rand() % 26) + 97); +} + +/*----------------------------------------------------------------------------*/ + +static inline void rand_str(char *str, int size) +{ + for (int i = 0; i < size; ++i) { + str[i] = rand_char(); + } +} + +/*----------------------------------------------------------------------------*/ + +static int cuckoo_tests_count(int argc, char *argv[]) +{ + return CUCKOO_TESTS_COUNT; +} + +/*----------------------------------------------------------------------------*/ + +static int test_cuckoo_create(ck_hash_table_t **table, uint items) +{ + *table = ck_create_table(items); + return (*table != NULL); +} + +/*----------------------------------------------------------------------------*/ + +static int test_cuckoo_insert(ck_hash_table_t *table, + const test_cuckoo_items *items) +{ + assert(table != NULL); + int errors = 0; + for (int i = 0; i < items->count; ++i) { + assert(items->values[i] != 0); + if (ck_insert_item(table, items->keys[i], items->key_sizes[i], + (void *)items->values[i]) != 0) { + ++errors; + } + } + return errors == 0; +} + +/*----------------------------------------------------------------------------*/ + +static int test_cuckoo_lookup(ck_hash_table_t *table, + const test_cuckoo_items *items) +{ + int errors = 0; + for (int i = 0; i < items->count; ++i) { + const ck_hash_table_item_t *found = ck_find_item( + table, items->keys[i], items->key_sizes[i]); + if (!found) { + if (items->deleted[i] == 0) { + diag("Not found item with key %.*s\n", + items->key_sizes[i], items->keys[i]); + ++errors; + } + } else { + if (items->deleted[i] != 0 + || found->key != items->keys[i] + || (size_t)(found->value) != items->values[i]) { + diag("Found item with key %.*s (size %u) " + "(should be %.*s (size %u)) and value %zu " + "(should be %d).\n", + found->key_length, found->key, + found->key_length, items->key_sizes[i], + items->keys[i], items->key_sizes[i], + (size_t)found->value, items->values[i]); + ++errors; + } + } + } + + if (errors > 0) { + diag("Not found %d of %d items.\n", errors, items->count); + } else { + note("Found %d items.\n", items->count); + } + + return errors == 0; +} + +/*----------------------------------------------------------------------------*/ + +static int test_cuckoo_delete(ck_hash_table_t *table, test_cuckoo_items *items) +{ + int errors = 0; + // delete approx. 1/10 items from the table + int count = rand() % (CUCKOO_MAX_ITEMS / 10) + 1; + + for (int i = 0; i < count; ++i) { + int item = rand() % items->count; + if (items->deleted[item] == 0 + && ck_delete_item(table, items->keys[item], + items->key_sizes[item], NULL, 0) != 0) { + ++errors; + } else { + items->deleted[item] = 1; + } + } + + return errors == 0; +} + +/*----------------------------------------------------------------------------*/ + +static int test_cuckoo_modify(ck_hash_table_t *table, test_cuckoo_items *items) +{ + int errors = 0; + // modify approx. 1/10 items from the table + int count = rand() % (CUCKOO_MAX_ITEMS / 10) + 1; + + for (int i = 0; i < count; ++i) { + int item = rand() % items->count; + int old_value = items->values[item]; + items->values[item] = rand() + 1; + if (ck_update_item(table, items->keys[item], + items->key_sizes[item], + (void *)items->values[item], NULL) != 0 + && items->deleted[item] == 1) { + ++errors; + items->values[item] = old_value; + } + } + + return 1; +} + +/*----------------------------------------------------------------------------*/ + +static int test_cuckoo_rehash(ck_hash_table_t *table) +{ + return (ck_rehash(table) == 0); +} + +/*----------------------------------------------------------------------------*/ + +static int test_cuckoo_resize(ck_hash_table_t *table) +{ + // test the resize explicitly + return (ck_resize_table(table) == 0); +} + +/*----------------------------------------------------------------------------*/ + +static int test_cuckoo_full(ck_hash_table_t *table, test_cuckoo_items *items) +{ + // invoke the resize by inserting so much items that thay cannot + // fit into the table + int new_count = table->items; + + while (new_count < hashsize(table->table_size_exp) * table->table_count) { + new_count += table->items; + } + + note("Old item count: %d, new count: %d, capacity of the table: %d\n", + table->items, new_count, + hashsize(table->table_size_exp) * table->table_count); + + assert(new_count <= items->total_count); + + int errors = 0; + + for (int i = items->count; i < new_count; ++i) { + assert(items->values[i] != 0); + if (ck_insert_item(table, items->keys[i], items->key_sizes[i], + (void *)items->values[i]) != 0) { + ++errors; + } + } + + items->count = new_count; + + return (errors == 0); +} + +/*----------------------------------------------------------------------------*/ + +static void create_random_items(test_cuckoo_items *items, int item_count) +{ + assert(items != NULL); + + items->count = item_count; + items->total_count = item_count * 10; + items->values = (size_t *)malloc(items->total_count * sizeof(size_t)); + items->key_sizes = (size_t *)malloc(items->total_count * sizeof(size_t)); + items->deleted = (size_t *)malloc(items->total_count * sizeof(size_t)); + items->keys = (char **)malloc(items->total_count * sizeof(char *)); + + for (int i = 0; i < items->total_count; ++i) { + int value = rand() + 1; + int key_size = rand() % CUCKOO_TEST_MAX_KEY_SIZE + 1; + char *key = malloc(key_size * sizeof(char)); + assert(key != NULL); + rand_str(key, key_size); + + // check if the key is not already in the table + int found = 0; + for (int j = 0; j < i; ++j) { + if (items->key_sizes[j] == key_size + && strncmp(items->keys[j], key, key_size) == 0) { + found = 1; + break; + } + } + + if (!found) { + assert(value != 0); + items->values[i] = value; + items->key_sizes[i] = key_size; + items->keys[i] = key; + items->deleted[i] = 0; + } else { + free(key); + --i; + } + } +} + +/*----------------------------------------------------------------------------*/ + +static void delete_items(test_cuckoo_items *items) +{ + free(items->deleted); + free(items->key_sizes); + free(items->values); + for (int i = 0; i < items->total_count; ++i) { + free(items->keys[i]); + } + free(items->keys); +} + +/*----------------------------------------------------------------------------*/ + +/*! Run all scheduled tests for given parameters. + */ +static int cuckoo_tests_run(int argc, char *argv[]) +{ + srand(time(NULL)); + int res; + + const int item_count = rand() % CUCKOO_MAX_ITEMS + 1; + test_cuckoo_items *items = (test_cuckoo_items *) + malloc(sizeof(test_cuckoo_items)); + + ck_hash_table_t *table = NULL; + + // Test 1: create + ok(res = test_cuckoo_create(&table, item_count), + "cuckoo hashing: create"); + + create_random_items(items, item_count); + + skip(!res, 10); + // Test 2: insert + ok(test_cuckoo_insert(table, items), "cuckoo hashing: insert"); + + // Test 3: lookup + ok(test_cuckoo_lookup(table, items), "cuckoo hashing: lookup"); + + // Test 4: delete + ok(test_cuckoo_delete(table, items), "cuckoo hashing: delete"); + + // Test 5: lookup 2 + ok(test_cuckoo_lookup(table, items), + "cuckoo hashing: lookup after delete"); + + // Test 6: modify + ok(test_cuckoo_modify(table, items), "cuckoo hashing: modify"); + + // Test 7: lookup 3 + ok(test_cuckoo_lookup(table, items), + "cuckoo hashing: lookup after modify"); + + // Test 8: rehash + ok(test_cuckoo_rehash(table), "cuckoo hashing: rehash"); + + // Test 9: lookup 4 + ok(test_cuckoo_lookup(table, items), + "cuckoo hashing: lookup after rehash"); + + // Test 10: resize + ok(test_cuckoo_resize(table), "cuckoo hashing: resize"); + + // Test 11: lookup 5 + ok(test_cuckoo_lookup(table, items), + "cuckoo hashing: lookup after resize"); + + // Test 12: owerflow the table + ok(test_cuckoo_full(table, items), "cuckoo hashing: overflow"); + + // Test 13: lookup 5 + ok(test_cuckoo_lookup(table, items), + "cuckoo hashing: lookup after overflow"); + + endskip; + + /** + * \note These last 2 tests found some major bug in the cuckoo hash + * table, so running them results in abort upon assertion. + * Disabled for now. + */ + + // Cleanup + ck_destroy_table(&table, NULL, 0); + delete_items(items); + free(items); + + return 0; +} diff --git a/src/tests/libknot/libknot/cuckoo_tests.h b/src/tests/libknot/libknot/cuckoo_tests.h new file mode 100644 index 0000000..b6b0db8 --- /dev/null +++ b/src/tests/libknot/libknot/cuckoo_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_CUCKOO_TESTS_H_ +#define _KNOTD_CUCKOO_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api cuckoo_tests_api; + +#endif /* _KNOTD_CUCKOO_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/dname_table_tests.c b/src/tests/libknot/libknot/dname_table_tests.c new file mode 100644 index 0000000..0d00a44 --- /dev/null +++ b/src/tests/libknot/libknot/dname_table_tests.c @@ -0,0 +1,393 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/* blame: jan.kadlec@nic.cz */ + +#include <assert.h> + +#include "dname_table_tests.h" +#include "libknot/util/error.h" +#include "libknot/zone/dname-table.h" +/* *test_t structures */ +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" + +static int knot_dname_table_tests_count(int argc, char *argv[]); +static int knot_dname_table_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api dname_table_tests_api = { + "Dname table", //! Unit name + &knot_dname_table_tests_count, //! Count scheduled tests + &knot_dname_table_tests_run //! Run scheduled tests +}; + +/* Helper functions. */ +static knot_dname_t *dname_from_test_dname_str(const test_dname_t *test_dname) +{ + assert(test_dname != NULL); + knot_dname_t *ret = knot_dname_new_from_str (test_dname->str, + strlen(test_dname->str), + NULL); + CHECK_ALLOC(ret, NULL); + + return ret; +} + +static int dname_compare_sort_wrapper(const void *ptr1, const void *ptr2) +{ + const knot_dname_t *dname1 = + dname_from_test_dname_str((const test_dname_t *)ptr1); + const knot_dname_t *dname2 = + dname_from_test_dname_str((const test_dname_t *)ptr2); + assert(dname1 && dname2); + return knot_dname_compare(dname1, dname2); +} + +/* Unit implementation. */ +enum {DNAME_TABLE_DNAME_COUNT = 3}; + +/* Strings are enough, we're not testing dname here ... */ +static test_dname_t DNAME_TABLE_DNAMES[DNAME_TABLE_DNAME_COUNT] = { + /* list ptr, string, wire, length, labels, label_count */ + {NULL, NULL, ".", NULL, 1, NULL, 0}, + {NULL, NULL, "a.ns.nic.cz.", NULL, 13, NULL, 0}, + {NULL, NULL, "b.ns.nic.cz.", NULL, 13, NULL, 0} +}; + +static int test_dname_table_new() +{ + knot_dname_table_t *table = knot_dname_table_new(); + if (table == NULL) { + return 0; + } + + knot_dname_table_free(&table); + return 1; +} + +struct test_dname_table_arg { + /* Times two - safety measure. */ + knot_dname_t *array[DNAME_TABLE_DNAME_COUNT * 2]; + uint count; +}; + +static void save_dname_to_array(knot_dname_t *node, void *data) +{ + assert(data); + struct test_dname_table_arg *arg = (struct test_dname_table_arg *)data; + arg->array[arg->count] = node; + arg->count++; +} + +static int test_dname_table_adding() +{ + int errors = 0; + knot_dname_table_t *table = knot_dname_table_new(); + CHECK_ALLOC(table, 0); + + /* Add NULL */ + if (knot_dname_table_add_dname(table, NULL) != KNOT_EBADARG) { + diag("Adding NULL dname did not result in an error!"); + errors++; + } + + /* Add to NULL table*/ + if (knot_dname_table_add_dname(NULL, NULL) != KNOT_EBADARG) { + diag("Adding to NULL table did not result in an error!"); + errors++; + } + + /* Add NULL */ + if (knot_dname_table_add_dname_check(table, NULL) != KNOT_EBADARG) { + diag("Adding NULL dname did not result in an error!"); + errors++; + } + + /* Add to NULL table*/ + if (knot_dname_table_add_dname_check(NULL, NULL) != KNOT_EBADARG) { + diag("Adding to NULL table did not result in an error!"); + errors++; + } + + + /* Add valid dnames. */ + for (int i = 0; i < DNAME_TABLE_DNAME_COUNT; i++) { + knot_dname_t *dname = + dname_from_test_dname_str(&DNAME_TABLE_DNAMES[i]); + if (!dname) { + diag("Could not create dname from test dname!"); + errors++; + continue; + } + if (knot_dname_table_add_dname(table, dname) != KNOT_EOK) { + diag("Could not add dname! (%s)", + DNAME_TABLE_DNAMES[i].str); + errors++; + } + } + + /* + * Using inorder traversal of the table, + * create array containing dnames. + */ + + struct test_dname_table_arg arg; + arg.count = 0; + + knot_dname_table_tree_inorder_apply(table, save_dname_to_array, &arg); + + if (arg.count != DNAME_TABLE_DNAME_COUNT) { + diag("Table contains too many dnames!"); + /* No sense in continuing. */ + knot_dname_table_deep_free(&table); + return 0; + } + + /* + * Check that inordered array is really sorted + * and contains valid dnames. + */ + for (int i = 0; i < DNAME_TABLE_DNAME_COUNT; i++) { + assert(arg.array[i]); + const char *str = knot_dname_to_str(arg.array[i]); + if (str == NULL) { + diag("Wrong dname in table!"); + errors++; + continue; + } + + if (arg.array[i]->size != + DNAME_TABLE_DNAMES[i].size) { + diag("Wrong dname size in table!"); + diag("Is: %u should be %u.", + arg.array[i]->size, + DNAME_TABLE_DNAMES[i].size); + errors++; + continue; + } + + if (strncmp(str, DNAME_TABLE_DNAMES[i].str, + DNAME_TABLE_DNAMES[i].size) != 0) { + diag("Wrong dname wire in table!"); + errors++; + } + } + + /* Now add one dname once again. It has to be the first item! */ + + if (knot_dname_table_add_dname(table, + dname_from_test_dname_str(&DNAME_TABLE_DNAMES[0])) != + KNOT_EOK) { + diag("Could not add dname to table once it's already there!"); + /* Next test would not make sense. */ + knot_dname_table_deep_free(&table); + return 0; + } + + /* + * After walking the table, there should now be + * DNAME_TABLE_DNAME_COUNT + 1 items, with 2 identical + * items at the beginning. + */ + + memset(arg.array, 0, + sizeof(knot_dname_t *) * DNAME_TABLE_DNAME_COUNT * 2); + arg.count = 0; + knot_dname_table_tree_inorder_apply(table, save_dname_to_array, &arg); + + if (arg.count != DNAME_TABLE_DNAME_COUNT + 1) { + diag("Identical dname was not added!"); + /* Again, next test would not make any sense. */ + knot_dname_table_deep_free(&table); + return 0; + } + + if (knot_dname_compare(arg.array[0], arg.array[1]) != 0) { + diag("First two dnames in table are not identical!"); + errors++; + } + + /* Delete table, wipe out array. */ + knot_dname_table_deep_free(&table); + memset(arg.array, 0, + sizeof(knot_dname_t *) * DNAME_TABLE_DNAME_COUNT * 2); + arg.count = 0; + + table = knot_dname_table_new(); + assert(table); + + /* + * Add dname with same content twice using knot_dname_table_add2 - + * table should now only contain one item. + */ + + knot_dname_t *tmp_dname = + dname_from_test_dname_str(&DNAME_TABLE_DNAMES[0]); + assert(tmp_dname); + + if (knot_dname_table_add_dname_check(table, &tmp_dname) != KNOT_EOK) { + diag("Could not add dname using dname_table_add_dname2!"); + knot_dname_table_deep_free(&table); + knot_dname_free(&tmp_dname); + return 0; + } + + tmp_dname = dname_from_test_dname_str(&DNAME_TABLE_DNAMES[0]); + assert(tmp_dname); + + knot_dname_t *dname_before_add = tmp_dname; + + if (knot_dname_table_add_dname_check(table, &tmp_dname) != 1) { + diag("Could not add dname again using dname_table_add_dname2!"); + knot_dname_table_deep_free(&table); + return 0; + } + + if (tmp_dname == dname_before_add) { + diag("Dname was not freed after insertion!"); + errors++; + } + + knot_dname_table_tree_inorder_apply(table, save_dname_to_array, &arg); + + if (arg.count != 1) { + diag("Add_dname2 has added dname when it shouldn't!"); + errors++; + } + + if (knot_dname_compare(tmp_dname, arg.array[0]) != 0) { + diag("Add_dname2 has added wrong dname!"); + errors++; + } + + knot_dname_table_deep_free(&table); + return (errors == 0); +} + +static int test_dname_table_find() +{ + int errors = 0; + knot_dname_table_t *table = knot_dname_table_new(); + assert(table); + + if (knot_dname_table_find_dname(table, NULL) != NULL) { + diag("Dname table did not return NULL when searching NULL!"); + errors++; + } + + if (knot_dname_table_find_dname(NULL, NULL) != NULL) { + diag("Passing NULL instead of dname table did not " + "return NULL!"); + errors++; + } + + /* Add all dnames but the last one. */ + for (int i = 0; i < DNAME_TABLE_DNAME_COUNT - 1; i++) { + knot_dname_t *dname = + dname_from_test_dname_str(&DNAME_TABLE_DNAMES[i]); + if (!dname) { + diag("Could not create dname from test dname!"); + errors++; + continue; + } + if (knot_dname_table_add_dname(table, dname) != KNOT_EOK) { + diag("Could not add dname! (%s)", + DNAME_TABLE_DNAMES[i].str); + errors++; + } + } + + /* Search for added dnames. */ + for (int i = 0; i < DNAME_TABLE_DNAME_COUNT - 1; i++) { + knot_dname_t *dname = + dname_from_test_dname_str(&DNAME_TABLE_DNAMES[i]); + if (!dname) { + diag("Could not create dname from test dname!"); + errors++; + continue; + } + + knot_dname_t *found_dname = + knot_dname_table_find_dname(table, dname); + + if (found_dname == NULL) { + diag("Dname table did not return " + "dname when it should!"); + errors++; + continue; + } + + if (knot_dname_compare(found_dname, dname) != 0) { + diag("Returned dname did not match!"); + errors++; + continue; + } + } + + /* Search for last dname, it should return NULL. */ + knot_dname_t *dname = + dname_from_test_dname_str( + &DNAME_TABLE_DNAMES[DNAME_TABLE_DNAME_COUNT]); + assert(dname); + + if (knot_dname_table_find_dname(table, dname) != NULL) { + diag("Dname table returned dname when it " + "should not be there!"); + errors++; + } + + knot_dname_free(&dname); + knot_dname_table_deep_free(&table); + + return (errors == 0); +} + +static const int KNOT_DNAME_TABLE_TEST_COUNT = 3; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_dname_table_tests_count(int argc, char *argv[]) +{ + return KNOT_DNAME_TABLE_TEST_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_dname_table_tests_run(int argc, char *argv[]) +{ + int final_res = 1; + int res = 0; + + /* Sort array containing test dnames. */ + qsort(DNAME_TABLE_DNAMES, DNAME_TABLE_DNAME_COUNT, + sizeof(test_dname_t), dname_compare_sort_wrapper); + + ok((res = test_dname_table_new()), "dname table: new"); + final_res *= res; + + skip(!res, 2); + + ok((res = test_dname_table_adding()), "dname table: adding"); + final_res *= res; + + ok((res = test_dname_table_find()), "dname table: searching"); + final_res *= res; + + endskip; + + return final_res; +} diff --git a/src/tests/libknot/libknot/dname_table_tests.h b/src/tests/libknot/libknot/dname_table_tests.h new file mode 100644 index 0000000..f3088e9 --- /dev/null +++ b/src/tests/libknot/libknot/dname_table_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_DNAME_TABLE_TESTS_H_ +#define _KNOTD_DNAME_TABLE_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api dname_table_tests_api; + +#endif /* _KNOTD_DNAME_TABLE_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/dname_tests.c b/src/tests/libknot/libknot/dname_tests.c new file mode 100644 index 0000000..9730756 --- /dev/null +++ b/src/tests/libknot/libknot/dname_tests.c @@ -0,0 +1,877 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <assert.h> + +#include "tests/libknot/libknot/dname_tests.h" +#include "libknot/dname.h" +#include "libknot/zone/node.h" + +static int knot_dname_tests_count(int argc, char *argv[]); +static int knot_dname_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api dname_tests_api = { + "DNS library - dname", //! Unit name + &knot_dname_tests_count, //! Count scheduled tests + &knot_dname_tests_run //! Run scheduled tests +}; + +/* + * Unit implementation. + */ + +// C will not accept const int in other const definition +enum { TEST_DOMAINS_OK = 8 }; + +enum { TEST_DOMAINS_BAD = 4 }; + +enum { TEST_DOMAINS_NON_FQDN = 6 }; + +static knot_node_t *NODE_ADDRESS = (knot_node_t *)0xDEADBEEF; + +struct test_domain { + char *str; + char *wire; + uint size; + char *labels; + short label_count; +}; + +/*! \warning Do not change the order in those, if you want to test some other + * feature with new dname, add it at the end of these arrays. + */ +static const struct test_domain + test_domains_ok[TEST_DOMAINS_OK] = { + { "abc.test.domain.com.", "\3abc\4test\6domain\3com", 21, + "\x0\x4\x9\x10", 4 }, + { "some.test.domain.com.", "\4some\4test\6domain\3com", 22, + "\x0\x5\xA\x11", 4 }, + { "xyz.test.domain.com.", "\3xyz\4test\6domain\3com", 21, + "\x0\x4\x9\x10", 4 }, + { "some.test.domain.com.", "\4some\4test\6domain\3com", 22, + "\x0\x5\xA\x11", 4 }, + { "test.domain.com.", "\4test\6domain\3com", 17, + "\x0\x5\xC", 3 }, + { ".", "\0", 1, + "", 0 }, + { "foo.bar.net.", "\3foo\3bar\3net", 13, + "\x0\x4\x8", 3}, + { "bar.net.", "\3bar\3net", 9, + "\x0\x4", 2} +}; + +static const struct test_domain // sizes are strlen()s here + test_domains_non_fqdn[TEST_DOMAINS_NON_FQDN] = { + { "www", "\3www", 4, "\x0", 1 }, + { "example", "\7example", 8, "\x0", 1 }, + { "com", "\3com", 4, "\x0", 1 }, + { "www.example.com", "\3www\7example\3com", 16, "\x0\x4\xC", + 3 }, + { "some", "\4some", 5, "\x0", 1 }, + { "example.com", "\7example\3com", 12, "\x0\x8", 2 } + }; + +static const struct test_domain + test_domains_bad[TEST_DOMAINS_BAD] = { + { NULL, "\2ex\3com", 0, "", 0 }, + { "ex.com.", NULL, 0, "", 0 }, + { "ex.com.\5", "\3ex\3com\0\5", 10, "", 0 }, + { "example.com", "\3example\3com", 12, "\x0\x8", 2 } +}; + +static int test_dname_create() +{ + knot_dname_t *dname = knot_dname_new(); + if (dname == NULL + || knot_dname_name(dname) != NULL + || knot_dname_size(dname) != 0 + || knot_dname_node(dname, 0) != NULL) { + diag("New domain name not initialized properly!"); + return 0; + } + knot_dname_free(&dname); + if (dname != NULL) { + diag("Pointer to the structure not set to" + "NULL when deallocating!"); + return 0; + } + return 1; +} + +static int test_dname_delete() +{ + // how to test this?? + return 0; +} + +static int check_domain_name(const knot_dname_t *dname, + const struct test_domain *test_domains, int i, + int check_node) +{ + int errors = 0; + + if (dname == NULL) { + diag("Domain name #%d not created!", i); + return 1; + } + + // check size + if (knot_dname_size(dname) != test_domains[i].size) { + diag("Bad size of the created domain name: %u (should be %u).", + knot_dname_size(dname), test_domains[i].size); + ++errors; + } + // check wire format + uint size = knot_dname_size(dname); + if (strncmp((char *)knot_dname_name(dname), + test_domains[i].wire, size) != 0) { + diag("The wire format of the created domain name is wrong:" + " '%.*s' (should be '%.*s').", + size, knot_dname_name(dname), + size, test_domains[i].wire); + ++errors; + } + // check labels + if (test_domains[i].label_count != dname->label_count) { + diag("Label count of the created domain name is wrong:" + " %d (should be %d)\n", dname->label_count, + test_domains[i].label_count); + ++errors; + } + if (strncmp((char *)dname->labels, test_domains[i].labels, + test_domains[i].label_count) != 0) { + diag("Label offsets of the created domain name are wrong.\n"); + ++errors; + } + + if (check_node) { + if (knot_dname_node(dname, 0) != NODE_ADDRESS) { + diag("Node pointer in the created domain name is wrong:" + "%p (should be %p)", + knot_dname_node(dname, 0), NODE_ADDRESS); + ++errors; + } + } + + return errors; +} + +static int test_dname_create_from_str() +{ + int errors = 0; + knot_dname_t *dname = NULL; + + for (int i = 0; i < TEST_DOMAINS_OK && errors == 0; ++i) { + //note("testing domain: %s", test_domains_ok[i].str); + dname = knot_dname_new_from_str(test_domains_ok[i].str, + strlen(test_domains_ok[i].str), NODE_ADDRESS); + errors += check_domain_name(dname, test_domains_ok, i, 1); + knot_dname_free(&dname); + } + + return (errors == 0); +} + +static int test_dname_create_from_str_non_fqdn() +{ + int errors = 0; + knot_dname_t *dname = NULL; + + for (int i = 0; i < TEST_DOMAINS_NON_FQDN; ++i) { + //note("testing domain: %s", test_domains_non_fqdn[i].str); + dname = knot_dname_new_from_str(test_domains_non_fqdn[i].str, + strlen(test_domains_non_fqdn[i].str), NULL); + errors += check_domain_name(dname, test_domains_non_fqdn, i, 0); + knot_dname_free(&dname); + } + + return (errors == 0); +} + +static int test_dname_cat() +{ + int errors = 0; + + /* + * This uses three particular dnames from test_domains structure + * where the third dname is a concatenation of the first two dnames. + */ + + knot_dname_t *d1, *d2, *d3; + + d1 = knot_dname_new_from_str(test_domains_non_fqdn[0].str, + strlen(test_domains_non_fqdn[0].str), NULL); + d2 = knot_dname_new_from_str(test_domains_non_fqdn[1].str, + strlen(test_domains_non_fqdn[1].str), NULL); + d3 = knot_dname_new_from_str(test_domains_non_fqdn[2].str, + strlen(test_domains_non_fqdn[2].str), NULL); + + knot_dname_cat(d1, d2); + knot_dname_cat(d1, d3); + + errors += check_domain_name(d1, test_domains_non_fqdn, 3, 0); + + knot_dname_free(&d1); + knot_dname_free(&d2); + knot_dname_free(&d3); + + /* + * Same thing as above, only different case. + */ + + d1 = knot_dname_new_from_str(test_domains_non_fqdn[4].str, + strlen(test_domains_non_fqdn[4].str), + NODE_ADDRESS); + + d2 = knot_dname_new_from_str(test_domains_ok[4].str, + strlen(test_domains_ok[4].str), + NODE_ADDRESS); + + knot_dname_cat(d1, d2); + + errors += check_domain_name(d1, test_domains_ok, 1, 1); + + knot_dname_free(&d1); + knot_dname_free(&d2); + + return (errors == 0); +} + +static int test_dname_left_chop() +{ + int errors = 0; + + /* Uses same principle as test_dname_cat(), only reversed */ + + /* TODO this would maybe deserver separate structure */ + + knot_dname_t *d1; + + d1 = knot_dname_new_from_str(test_domains_ok[1].str, + strlen(test_domains_ok[1].str), + NODE_ADDRESS); + + knot_dname_t *chopped; + + chopped = knot_dname_left_chop(d1); + + errors += check_domain_name(chopped, test_domains_ok, 4, 0); + + knot_dname_free(&d1); + knot_dname_free(&chopped); + + d1 = knot_dname_new_from_str(test_domains_non_fqdn[3].str, + strlen(test_domains_non_fqdn[3].str), + NODE_ADDRESS); + + chopped = knot_dname_left_chop(d1); + + errors += check_domain_name(chopped, test_domains_non_fqdn, 5, 0); + + knot_dname_free(&d1); + knot_dname_free(&chopped); + + return (errors == 0); +} + +static int test_dname_create_from_wire() +{ + int errors = 0; + knot_dname_t *dname = NULL; + + for (int i = 0; i < TEST_DOMAINS_OK && errors == 0; ++i) { + assert(strlen(test_domains_ok[i].wire) + 1 == + test_domains_ok[i].size); + dname = knot_dname_new_from_wire( + (uint8_t *)test_domains_ok[i].wire, + test_domains_ok[i].size, NODE_ADDRESS); + errors += check_domain_name(dname, test_domains_ok, i, 1); + knot_dname_free(&dname); + } + + return (errors == 0); +} + +static int test_dname_to_str() +{ + int errors = 0; + + /* + * Converts dname wireformat to string represenation, which is compared + * with entries in test_domains structure. + */ + + knot_dname_t *dname = NULL; + + for (int i = 0; i < TEST_DOMAINS_OK && errors == 0; ++i) { + dname = knot_dname_new_from_wire( + (uint8_t *)test_domains_ok[i].wire, + test_domains_ok[i].size, NODE_ADDRESS); + char *name_str = knot_dname_to_str(dname); + if (strcmp(name_str, test_domains_ok[i].str) != 0) { + diag("Presentation format of domain name wrong:" + " %s (should be %s)", + name_str, test_domains_ok[i].str); + ++errors; + } + free(name_str); + knot_dname_free(&dname); + } + + return (errors == 0); +} + +/* called by lives_ok */ +static int test_faulty_data() +{ + knot_dname_t *dname = NULL; + + /* + * This takes dnames from test_domains_bad array, which contains + * malformed dnames. TODO add something like: 2www3foo - it's gonna fail + */ + + for (int i = 0; i < TEST_DOMAINS_BAD; i++) { + + if (test_domains_bad[i].str != NULL) { + dname = knot_dname_new_from_str( + test_domains_bad[i].str, + strlen(test_domains_bad[i].str), + NODE_ADDRESS); + } else { + dname = knot_dname_new_from_str( + test_domains_bad[i].str, 0, NODE_ADDRESS); + } + + knot_dname_free(&dname); + + dname = knot_dname_new_from_wire( + (uint8_t *)test_domains_bad[i].wire, + test_domains_bad[i].size, NODE_ADDRESS); + + knot_dname_free(&dname); + } + + return 1; //did it get here? success +} + +static int test_dname_compare() +{ + knot_dname_t *dnames[TEST_DOMAINS_OK]; + + /* This uses particular dnames from TEST_DOMAINS_OK array */ + + for (int i = 0; i < TEST_DOMAINS_OK; ++i) { + dnames[i] = knot_dname_new_from_wire( + (uint8_t *)test_domains_ok[i].wire, + test_domains_ok[i].size, NODE_ADDRESS); + } + + int errors = 0; + /* abc < some */ + if (knot_dname_compare(dnames[0], dnames[1]) >= 0) { + diag("Dname comparison error"); + errors++; + } + + /* abc.test.domain.com. < foo.bar.net. */ + if (knot_dname_compare(dnames[0], dnames[6]) >= 0) { + diag("Dname comparison error"); + errors++; + } + + /* foo.bar.net. < . */ + if (knot_dname_compare(dnames[5], dnames[0]) >= 0) { + diag("Dname comparison error"); + errors++; + } + + /* bar.net. < foo.bar.net. */ + if (knot_dname_compare(dnames[7], dnames[6]) >= 0) { + diag("Dname comparison error"); + errors++; + } + + /* some == some */ + if (knot_dname_compare(dnames[1], dnames[3]) != 0) { + diag("Dname comparison error"); + errors++; + } + + /*xyz > some */ + if (knot_dname_compare(dnames[2], dnames[1]) <= 0) { + diag("Dname comparison error"); + errors++; + } + + /*foo.bar.net. > xyz.test.domain.com. */ + if (knot_dname_compare(dnames[6], dnames[3]) <= 0) { + diag("Dname comparison error"); + errors++; + } + +// /* xyz.test.domain.com. > . */ +// if (knot_dname_compare(dnames[3], dnames[5]) <= 0) { +// diag("Dname comparison error"); +// errors++; +// } + + /* bar.net. < foo.bar.net. */ + if (knot_dname_compare(dnames[6], dnames[7]) <= 0) { + diag("Dname comparison error"); + errors++; + } + + for (int i = 0; i < TEST_DOMAINS_OK; i++) { + knot_dname_free(&dnames[i]); + } + + return (errors == 0); +} + +static int test_dname_is_fqdn() +{ + int errors = 0; + + knot_dname_t *dname; + + /* All dnames in TEST_DOMAINS_OK are fqdn */ + + for (int i = 0; i < TEST_DOMAINS_OK && !errors; ++i) { + dname = knot_dname_new_from_wire( + (uint8_t *)test_domains_ok[i].wire, + test_domains_ok[i].size, NODE_ADDRESS); + errors += !knot_dname_is_fqdn(dname); + knot_dname_free(&dname); + } + + /* None of the following dnames should be fqdn */ + + for (int i = 0; i < TEST_DOMAINS_NON_FQDN && !errors; ++i) { + dname = knot_dname_new_from_str(test_domains_non_fqdn[i].str, + strlen(test_domains_non_fqdn[i].str), NULL); + errors += knot_dname_is_fqdn(dname); + knot_dname_free(&dname); + } + + return (errors == 0); +} + +static int test_dname_is_subdomain() +{ + int errors = 0; + + knot_dname_t *dnames_fqdn[TEST_DOMAINS_OK]; + knot_dname_t *dnames_non_fqdn[TEST_DOMAINS_NON_FQDN]; + + for (int i = 0; i < TEST_DOMAINS_OK; ++i) { + dnames_fqdn[i] = knot_dname_new_from_wire( + (const uint8_t *)test_domains_ok[i].wire, + test_domains_ok[i].size, NULL); + assert(dnames_fqdn[i] != NULL); + } + + for (int i = 0; i < TEST_DOMAINS_NON_FQDN; ++i) { + dnames_non_fqdn[i] = knot_dname_new_from_str( + test_domains_non_fqdn[i].str, + test_domains_non_fqdn[i].size, NULL); + assert(dnames_non_fqdn[i] != NULL); + } + + // fqdn names 0 - 3 should be subdomains of name 4 + knot_dname_t *parent = dnames_fqdn[4]; + for (int i = 0; i < 3; ++i) { + if (!knot_dname_is_subdomain(dnames_fqdn[i], parent)) { + diag("(fqdn 1-%d) " + "Name %s was not considered subdomain of %s", i, + knot_dname_name(dnames_fqdn[i]), + knot_dname_name(parent)); + ++errors; + } + } + + // fqdn names 0 - 4 should be subdomains of name 5 (root) + parent = dnames_fqdn[5]; + for (int i = 0; i < 4; ++i) { + if (!knot_dname_is_subdomain(dnames_fqdn[i], parent)) { + diag("(fqdn 2-%d) " + "Name %s was not considered subdomain of %s", i, + knot_dname_name(dnames_fqdn[i]), + knot_dname_name(parent)); + ++errors; + } + } + + // non-fqdn names 3 and 5 should be subdomains of non-fqdn name 2 + parent = dnames_non_fqdn[2]; + if (!knot_dname_is_subdomain(dnames_non_fqdn[3], parent)) { + diag("(non-fqdn 1) " + "Name %.*s was not considered subdomain of %.*s", + knot_dname_size(dnames_non_fqdn[3]), + knot_dname_name(dnames_non_fqdn[3]), + knot_dname_size(parent), + knot_dname_name(parent)); + ++errors; + } + if (!knot_dname_is_subdomain(dnames_non_fqdn[5], parent)) { + diag("(non-fqdn 2) " + "Name %.*s was not considered subdomain of %.*s", + knot_dname_size(dnames_non_fqdn[5]), + knot_dname_name(dnames_non_fqdn[5]), + knot_dname_size(parent), + knot_dname_name(parent)); + ++errors; + } + + // non-fqdn name 3 should be subdomain of non-fqdn name 5 + parent = dnames_non_fqdn[5]; + if (!knot_dname_is_subdomain(dnames_non_fqdn[3], parent)) { + diag("(non-fqdn 3) " + "Name %.*s was not considered subdomain of %.*s", + knot_dname_size(dnames_non_fqdn[3]), + knot_dname_name(dnames_non_fqdn[3]), + knot_dname_size(parent), + knot_dname_name(parent)); + ++errors; + } + + // identical names should not be considered subdomains + if (knot_dname_is_subdomain(dnames_fqdn[0], dnames_fqdn[0])) { + diag("(identical names) " + "Name %s was considered subdomain of itself", + knot_dname_name(dnames_fqdn[0])); + ++errors; + } + if (knot_dname_is_subdomain(dnames_fqdn[1], dnames_fqdn[3])) { + diag("(identical names) " + "Name %s was considered subdomain of %s", + knot_dname_name(dnames_fqdn[1]), + knot_dname_name(dnames_fqdn[3])); + ++errors; + } + + // fqdn name should not be considered subdomain of non-fqdn name + if (knot_dname_is_subdomain(dnames_fqdn[1], dnames_non_fqdn[2])) { + diag("(fqdn subdomain of non-fqdn) " + "Name %s was considered subdomain of %.*s", + knot_dname_name(dnames_fqdn[1]), + knot_dname_size(dnames_non_fqdn[2]), + knot_dname_name(dnames_non_fqdn[2])); + ++errors; + } + + // non-fqdn name should not be considered subdomain of fqdn name + if (knot_dname_is_subdomain(dnames_fqdn[1], dnames_non_fqdn[2])) { + diag("(non-fqdn subdomain of fqdn) " + "Name %s was considered subdomain of %.*s", + knot_dname_name(dnames_fqdn[1]), + knot_dname_size(dnames_non_fqdn[2]), + knot_dname_name(dnames_non_fqdn[2])); + ++errors; + } + + // parent name should not be considered subdomain of its subdomain + if (knot_dname_is_subdomain(dnames_fqdn[4], dnames_fqdn[0])) { + diag("(ancestor subdomain of name) " + "Name %s was considered subdomain of %s", + knot_dname_name(dnames_fqdn[4]), + knot_dname_name(dnames_fqdn[0])); + ++errors; + } + + for (int i = 0; i < TEST_DOMAINS_OK; ++i) { + knot_dname_free(&dnames_fqdn[i]); + } + + for (int i = 0; i < TEST_DOMAINS_NON_FQDN; ++i) { + knot_dname_free(&dnames_non_fqdn[i]); + } + + return (errors == 0); +} + +static int check_wires(const uint8_t *wire1, uint size1, + uint8_t *wire2, uint size2) +{ + if (size1 != size2) { + return 0; + } + + int i; + + for (i = 0; (i < size1); i++) { + if (wire1[i] != wire2[i]) { + return 0; + } + } + + return 1; +} + +/*!< \note not to be run separately */ +static int test_dname_name(knot_dname_t **dnames_fqdn, + knot_dname_t **dnames_non_fqdn) +{ + assert(dnames_fqdn); + assert(dnames_non_fqdn); + + int errors = 0; + + for (int i = 0; i < TEST_DOMAINS_OK; i++) { + const uint8_t *tmp_name; + + tmp_name = knot_dname_name(dnames_fqdn[i]); + if (!check_wires(tmp_name, dnames_fqdn[i]->size, + (uint8_t *)test_domains_ok[i].wire, + test_domains_ok[i].size)) { + diag("Got bad name value from structure: " + "%s, should be: %s. Sizes: %d and: %d", + tmp_name, test_domains_ok[i].wire, + dnames_fqdn[i]->size, + test_domains_ok[i].size); + errors++; + } + } + + for (int i = 0; i < TEST_DOMAINS_NON_FQDN; i++) { + const uint8_t *tmp_name; + tmp_name = knot_dname_name(dnames_non_fqdn[i]); + if (!check_wires(tmp_name, dnames_non_fqdn[i]->size - 1, + (uint8_t *)test_domains_non_fqdn[i].wire, + test_domains_non_fqdn[i].size)) { + diag("Got bad name value from structure: " + "%s, should be: %s. Sizes: %d and %d\n", + tmp_name, test_domains_non_fqdn[i].wire, + dnames_non_fqdn[i]->size, + test_domains_non_fqdn[i].size); +// hex_print(dnames_non_fqdn[i]->name, +// dnames_non_fqdn[i]->size); +// hex_print(test_domains_non_fqdn[i].wire, +// test_domains_non_fqdn[i].size); +// diag("%s and %s\n", +// knot_dname_to_str(dnames_non_fqdn[i]), +// test_domains_non_fqdn[i]); + errors++; + } + } + + return errors; +} + +/* \note not to be run separately */ +static int test_dname_size(knot_dname_t **dnames_fqdn, + knot_dname_t **dnames_non_fqdn) +{ + assert(dnames_fqdn); + assert(dnames_non_fqdn); + + int errors = 0; + + for (int i = 0; i < TEST_DOMAINS_OK; i++) { + uint8_t tmp_size; + if ((tmp_size = knot_dname_size(dnames_fqdn[i])) != + test_domains_ok[i].size) { + diag("Got bad size value from structure: " + "%u, should be: %u", + tmp_size, test_domains_ok[i].size); + errors++; + } + } + + for (int i = 0; i < TEST_DOMAINS_NON_FQDN; i++) { + uint8_t tmp_size; + if ((tmp_size = knot_dname_size(dnames_non_fqdn[i])) != + test_domains_non_fqdn[i].size) { + diag("Got bad size value from structure: " + "%u, should be: %u", + tmp_size, test_domains_non_fqdn[i].size); + errors++; + } + } + + return errors; +} + +/* \note not to be run separately */ +static int test_dname_node(knot_dname_t **dnames_fqdn, + knot_dname_t **dnames_non_fqdn) +{ + assert(dnames_fqdn); + assert(dnames_non_fqdn); + + int errors = 0; + + for (int i = 0; i < TEST_DOMAINS_OK; i++) { + const knot_node_t *tmp_node; + if ((tmp_node = knot_dname_node(dnames_fqdn[i], 0)) != + NODE_ADDRESS) { + diag("Got bad node value from structure: " + "%p, should be: %p", + tmp_node, NODE_ADDRESS); + errors++; + } + } + + for (int i = 0; i < TEST_DOMAINS_NON_FQDN; i++) { + const knot_node_t *tmp_node; + if ((tmp_node = knot_dname_node(dnames_non_fqdn[i], 0)) != + NODE_ADDRESS) { + diag("Got bad node value from structure: " + "%s, should be: %s", + tmp_node, NODE_ADDRESS); + errors++; + } + } + + return errors; +} + +static int test_dname_getters(uint type) +{ + int errors = 0; + + knot_dname_t *dnames_fqdn[TEST_DOMAINS_OK]; + knot_dname_t *dnames_non_fqdn[TEST_DOMAINS_NON_FQDN]; + + for (int i = 0; i < TEST_DOMAINS_OK; i++) { + dnames_fqdn[i] = knot_dname_new_from_wire( + (uint8_t *)test_domains_ok[i].wire, + test_domains_ok[i].size, NODE_ADDRESS); + assert(dnames_fqdn[i] != NULL); + } + + for (int i = 0; i < TEST_DOMAINS_NON_FQDN; i++) { + printf("Creating dname: %s size: %d\n", test_domains_non_fqdn[i].wire, test_domains_non_fqdn[i].size); + dnames_non_fqdn[i] = knot_dname_new_from_str( + test_domains_non_fqdn[i].str, + test_domains_non_fqdn[i].size, NODE_ADDRESS); + assert(dnames_non_fqdn[i] != NULL); + } + + switch (type) { + case 0: { + errors += test_dname_name(dnames_fqdn, dnames_non_fqdn); + break; + } + + case 1: { + errors += test_dname_size(dnames_fqdn, dnames_non_fqdn); + break; + } + + case 2: { + errors += test_dname_node(dnames_fqdn, dnames_non_fqdn); + break; + } + } /* switch */ + + for (int i = 0; i < TEST_DOMAINS_OK; i++) { + knot_dname_free(&dnames_fqdn[i]); + } + + for (int i = 0; i < TEST_DOMAINS_NON_FQDN; i++) { + knot_dname_free(&dnames_non_fqdn[i]); + } + + return (errors == 0); +} + +static const int KNOT_DNAME_TEST_COUNT = 15; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_dname_tests_count(int argc, char *argv[]) +{ + return KNOT_DNAME_TEST_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_dname_tests_run(int argc, char *argv[]) +{ + int res = 0, + res_str = 0, + res_wire = 0, + res_str_non_fqdn = 0, + res_final = 1; + + res = test_dname_create(); + ok(res, "dname: create empty"); + res_final *= res; + + skip(!res, 12); + + todo(); + + ok((res = test_dname_delete()), "dname: delete"); + //res_final *= res; + + endtodo; + + ok((res_str = test_dname_create_from_str()), "dname: create from str"); + ok((res_wire = test_dname_create_from_wire()), + "dname: create from wire"); + ok((res_str_non_fqdn = test_dname_create_from_str_non_fqdn()), + "dname: create from str non fqdn"); + res_final *= res_str; + res_final *= res_wire; + res_final *= res_str_non_fqdn; + + todo(); + res = test_dname_getters(0); + ok(res, "dname: name"); + endtodo; + + todo(); + res = test_dname_getters(1); + ok(res, "dname: size"); + endtodo; + + res = test_dname_getters(2); + ok(res, "dname: node"); + + skip(!res_str || !res_wire || !res_str_non_fqdn, 2); + + ok((res = test_dname_to_str()), "dname: convert to str"); + res_final *= res; + + lives_ok(test_faulty_data(); , "dname: faulty data test"); + + endskip; /* !res_str || !res_wire */ + + ok((res = test_dname_compare()), "dname: compare"); + res_final *= res; + + ok((res = test_dname_cat()), "dname: cat"); + res_final *= res; + + ok((res = test_dname_is_fqdn()), "dname: fqdn"); + res_final *= res; + + ok((res = test_dname_left_chop()), "dname: left chop"); + res_final *= res; + + ok((res = test_dname_is_subdomain()), "dname: is subdomain"); + res_final *= res; + + endskip; /* create failed */ + + return res_final; +} diff --git a/src/tests/libknot/libknot/dname_tests.h b/src/tests/libknot/libknot/dname_tests.h new file mode 100644 index 0000000..a7d75aa --- /dev/null +++ b/src/tests/libknot/libknot/dname_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_DNAME_TESTS_H_ +#define _KNOTD_DNAME_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api dname_tests_api; + +#endif /* _KNOTD_DNAME_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/edns_tests.c b/src/tests/libknot/libknot/edns_tests.c new file mode 100644 index 0000000..ac5d130 --- /dev/null +++ b/src/tests/libknot/libknot/edns_tests.c @@ -0,0 +1,596 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "tests/libknot/libknot/edns_tests.h" +#include "libknot/common.h" +#include "libknot/edns.h" + +static int knot_edns_tests_count(int argc, char *argv[]); +static int knot_edns_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api edns_tests_api = { + "DNS library - EDNS", //! Unit name + &knot_edns_tests_count, //! Count scheduled tests + &knot_edns_tests_run //! Run scheduled tests +}; + +/* + * Unit implementation. + */ + +enum { TEST_EDNS = 1, OPTION_COUNT = 3 }; + +struct test_edns_options { + uint16_t code; + uint16_t length; + uint8_t *data; +}; + +struct test_edns { + struct test_edns_options *options; + uint16_t payload; + uint8_t ext_rcode; + uint8_t version; + uint16_t flags; + short option_count; + short options_max; + short size; +}; + +typedef struct test_edns test_edns_t; + +struct test_edns_options test_options_data[OPTION_COUNT] = { + {5, 7, (uint8_t *)"123456"}, + {4, 3, (uint8_t *)"12"}, + {1, 5, (uint8_t *)"13333"} +}; + +test_edns_t test_edns_data[TEST_EDNS] = { +{ NULL, 4096, 2, 0, 0, 0, 10, 11} +}; + +enum edns_mask { + KNOT_EDNS_DO_MASK = (uint16_t)0x8000 +}; + +/* Creates actual knot_opt_rr_t variable from test_edns_t variable */ +static knot_opt_rr_t *opt_rr_from_test_edns(test_edns_t *test_edns) +{ + knot_opt_rr_t *ret = knot_edns_new(); + + CHECK_ALLOC_LOG(ret, NULL); + + ret->flags = test_edns->flags; + ret->ext_rcode = test_edns->ext_rcode; + ret->payload = test_edns->payload; + ret->version = test_edns->version; + + for (int i = 0; i < test_edns->option_count; i++) { + if (knot_edns_add_option(ret, test_edns->options[i].code, + test_edns->options[i].length, + test_edns->options[i].data) != 0) { + knot_edns_free(&ret); + return NULL; + } + } + + return ret; +} + +/* simple wire compare - 0 if same, 1 otherwise */ +static int edns_compare_wires(uint8_t *wire1, + uint8_t *wire2, + uint16_t length) +{ + for (uint i = 0; i < length; i++) { + if (wire1[i] != wire2[i]) { + return 1; + } + } + + return 0; +} + +static int check_edns(const knot_opt_rr_t *edns, + const test_edns_t *test_edns) +{ + if (edns->option_count != test_edns->option_count) { + diag("Option count is wrong"); + return -1; + } + + for (int i = 0; i < edns->option_count; i++) { + /* check options */ + if (edns->options[i].code != test_edns->options[i].code) { + diag("Code in options is wrong"); + return -1; + } + + if (edns->options[i].length != test_edns->options[i].length) { + diag("Length in options is wrong"); + return -1; + } + + if (edns_compare_wires(edns->options[i].data, + test_edns->options[i].data, + edns->options[i].length) != 0) { + diag("Data in options are wrong"); + return -1; + } + } + + if (edns->version != test_edns->version) { + diag("Version is wrong"); + return -1; + } + + if (edns->flags != test_edns->flags) { + diag("Flags are wrong"); + return -1; + } + + if (edns->size != test_edns->size) { + diag("Size is wrong"); + return -1; + } + + return 0; +} + +static int test_edns_get_payload(const knot_opt_rr_t *edns, + test_edns_t *test_edns) +{ + if (knot_edns_get_payload(edns) != + test_edns->payload) { + return 0; + } else { + return 1; + } +} + +static int test_edns_get_ext_rcode(const knot_opt_rr_t *edns, + test_edns_t *test_edns) +{ + if (knot_edns_get_ext_rcode(edns) != + test_edns->ext_rcode) { + return 0; + } else { + return 1; + } +} + +static int test_edns_get_flags(const knot_opt_rr_t *edns, + test_edns_t *test_edns) +{ + if (knot_edns_get_flags(edns) != + test_edns->flags) { + return 0; + } else { + return 1; + } +} + +static int test_edns_get_version(const knot_opt_rr_t *edns, + test_edns_t *test_edns) +{ + if (knot_edns_get_version(edns) != + test_edns->version) { + return 0; + } else { + return 1; + } +} + +static int test_edns_do(const knot_opt_rr_t *edns, + test_edns_t *test_edns) +{ + if (knot_edns_do(edns) != + (test_edns->flags & KNOT_EDNS_DO_MASK)) { + return 0; + } else { + return 1; + } +} + +static int test_edns_size(knot_opt_rr_t *edns, test_edns_t *test_edns) +{ + if (knot_edns_size(edns) != + test_edns->size) { + return 0; + } else { + return 1; + } +} + +static int test_edns_set_payload(knot_opt_rr_t *edns, + test_edns_t *test_edns) +{ + knot_edns_set_payload(edns, test_edns->payload); + + if (edns->payload != + test_edns->payload) { + return 0; + } else { + return 1; + } +} + +static int test_edns_set_ext_rcode(knot_opt_rr_t *edns, + test_edns_t *test_edns) +{ + knot_edns_set_ext_rcode(edns, test_edns->ext_rcode); + if (edns->ext_rcode != + test_edns->ext_rcode) { + return 0; + } else { + return 1; + } +} + +static int test_edns_set_version(knot_opt_rr_t *edns, + test_edns_t *test_edns) +{ + knot_edns_set_version(edns, + test_edns->version); + + if (edns->version != + test_edns->version) { + return 0; + } else { + return 1; + } +} + +static int test_edns_set_do(knot_opt_rr_t *edns) +{ + knot_edns_set_do(edns); + + if (!knot_edns_do(edns)) { + return 0; + } else { + return 1; + } +} + +static int test_edns_getters(uint type) +{ + int errors = 0; + for (int i = 0; i < TEST_EDNS; i++) { + knot_opt_rr_t *edns = + opt_rr_from_test_edns(&(test_edns_data[i])); + if (edns == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + + switch(type) { + case 0: + if (test_edns_get_payload(edns, + &test_edns_data[i]) != 1) { + diag("Got wrong payload!"); + errors++; + } + break; + case 1: + if (test_edns_get_ext_rcode(edns, + &test_edns_data[i]) != 1) { + diag("Got wrong extended RCODE!"); + errors++; + } + break; + case 2: + if (test_edns_get_flags(edns, + &test_edns_data[i]) != 1) { + diag("Got wrong flags!"); + + errors++; + } + break; + case 3: + if (test_edns_get_version(edns, + &test_edns_data[i]) != 1) { + diag("Got wrong version!"); + errors++; + } + break; + case 4: + if (test_edns_do(edns, + &test_edns_data[i]) != 1) { + diag("Got wrong DO bit!"); + errors++; + } + break; + case 5: + if (test_edns_size(edns, + &test_edns_data[i]) != 1) { + diag("Got wrong size!"); + errors++; + } + break; + default: + diag("Unknown option"); + errors++; + } /* switch */ + + knot_edns_free(&edns); + } + + return (errors == 0); +} + +static int test_edns_setters(uint type) +{ + int errors = 0; + for (int i = 0; i < TEST_EDNS; i++) { + knot_opt_rr_t *edns = + opt_rr_from_test_edns(&(test_edns_data[i])); + if (edns == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + + switch(type) { + case 0: + if (test_edns_set_payload(edns, + &test_edns_data[i]) != 1) { + diag("Set wrong payload!"); + errors++; + } + break; + case 1: + if (test_edns_set_ext_rcode(edns, + &test_edns_data[i]) != 1) { + diag("Set wrong ext_rcode"); + errors++; + } + break; + case 2: + if (test_edns_set_version(edns, + &test_edns_data[i]) != 1) { + diag("Set wrong version!"); + errors++; + } + break; + case 3: + if (test_edns_set_do(edns) != 1) { + diag("Set wrong DO bit!"); + errors++; + } + break; + default: + diag("Unknown option"); + errors++; + } /* switch */ + + knot_edns_free(&edns); + } + + return (errors == 0); +} + +static int test_edns_wire() +{ + /* + * Tests to_wire and from_wire in one test. + */ + for (int i = 0; i < TEST_EDNS; i++) { + /* Creates instance from test_edns_t. */ + knot_opt_rr_t *edns = + opt_rr_from_test_edns(&(test_edns_data[i])); + if (edns == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + + uint8_t *wire = NULL; + wire = malloc(sizeof(uint8_t) * edns->size); + CHECK_ALLOC_LOG(wire, 0); + + /* Converts EDNS to wire. */ + short wire_size = knot_edns_to_wire(edns, wire, 100); + + if (wire_size == -1) { + diag("Could not create EDNS wire"); + return 0; + } + + knot_opt_rr_t *edns_from_wire = knot_edns_new(); + if (edns == NULL) { + return 0; + } + + /* TODO use some constant */ + /* Creates new EDNS from wire */ + if (knot_edns_new_from_wire(edns_from_wire, + wire, + 100) <= 0) { + diag("Could not create from wire"); + return 0; + } + + /* Checks whether EDNS created from wire is the same */ + if (check_edns(edns_from_wire, + &(test_edns_data[i])) != 0) { + diag("EDNS created from wire is different from the " + "original one"); + } + + free(wire); + knot_edns_free(&edns_from_wire); + knot_edns_free(&edns); + } + return 1; +} + +static int test_edns_add_option() +{ + /* + * Create empty EDNS and add options one by one, testing their presence. + */ + for (int i = 0; i < TEST_EDNS; i++) { + knot_opt_rr_t *edns = knot_edns_new(); + assert(edns->option_count == 0); + + if (edns == NULL) { + ERR_ALLOC_FAILED; + return 0; + } + + for (int j = 0; j < test_edns_data[i].option_count; j++) { + if (knot_edns_add_option(edns, + test_edns_data[i].options[j].code, + test_edns_data[i].options[j].length, + test_edns_data[i].options[j]. + data) != 0) { + diag("Could not add option"); + return 0; + } + + if (edns->options[j].code != + test_edns_data[i].options[j].code) { + diag("Option code wrongly added!"); + return 0; + } + + if (edns->options[j].length != + test_edns_data[i].options[j].length) { + diag("Option length wrongly added!"); + return 0; + } + + if (edns_compare_wires(edns->options[j].data, + test_edns_data[i]. + options[j].data, + edns->options[j].length) != 0) { + diag("Option wire wrongly added!"); + return 0; + } + } + knot_edns_free(&edns); + } + return 1; +} + +static int test_edns_has_option() +{ + /* + * Create empty EDNS and add options one by one, testing their presence + */ + for (int i = 0; i < TEST_EDNS; i++) { + knot_opt_rr_t *edns = knot_edns_new(); + assert(edns->option_count == 0); + + if (edns == NULL) { + ERR_ALLOC_FAILED; + return 0; + } + + for (int j = 0; j < test_edns_data[i].option_count; j++) { + if (knot_edns_add_option(edns, + test_edns_data[i].options[j].code, + test_edns_data[i].options[j].length, + test_edns_data[i].options[j]. + data) != 0) { + diag("Could not add option"); + return 0; + } + + if (knot_edns_has_option(edns, + test_edns_data[i].options[j].code) != 1) { + diag("Option not found!"); + return 0; + } + } + knot_edns_free(&edns); + } + return 1; +} + +static const int KNOT_EDNS_TESTS_COUNT = 12; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_edns_tests_count(int argc, char *argv[]) +{ + return KNOT_EDNS_TESTS_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_edns_tests_run(int argc, char *argv[]) +{ + int res = 0; + int res_final = 1; + + res = test_edns_getters(0); + ok(res, "ends: get payload"); + res_final *= res; + + res = test_edns_getters(1); + ok(res, "ends: get extenden RCODE"); + res_final *= res; + + res = test_edns_getters(2); + ok(res, "ends: get flags"); + res_final *= res; + + res = test_edns_getters(3); + ok(res, "ends: get version"); + res_final *= res; + + res = test_edns_getters(4); + ok(res, "ends: do"); + res_final *= res; + + res = test_edns_getters(5); + ok(res, "ends: size"); + res_final *= res; + + res = test_edns_setters(0); + ok(res, "ends: set payload"); + res_final *= res; + + res = test_edns_setters(1); + ok(res, "ends: set extended RCODE"); + res_final *= res; + + res = test_edns_setters(2); + ok(res, "ends: set version"); + res_final *= res; + + res = test_edns_setters(3); + ok(res, "ends: set DO"); + res_final *= res; + + res = test_edns_add_option(); + ok(res, "ends: add option"); + res_final *= res; + + res = test_edns_has_option(); + ok(res, "ends: has option"); + res_final *= res; + + res = test_edns_wire(); + ok(res, "ends: to_wire and from_wire"); + res_final *= res; + + return res_final; +} diff --git a/src/tests/libknot/libknot/edns_tests.h b/src/tests/libknot/libknot/edns_tests.h new file mode 100644 index 0000000..4553234 --- /dev/null +++ b/src/tests/libknot/libknot/edns_tests.h @@ -0,0 +1,34 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file edns_tests.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * Contains unit tests for ENDS API + * + * Contains tests for: + * - ENDS API + */ +#ifndef _KNOTD__EDNS_TESTS_H_ +#define _KNOTD__EDNS_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api edns_tests_api; + +#endif /* _KNOTD__EDNS_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/node_tests.c b/src/tests/libknot/libknot/node_tests.c new file mode 100644 index 0000000..f04a202 --- /dev/null +++ b/src/tests/libknot/libknot/node_tests.c @@ -0,0 +1,344 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include "tests/libknot/libknot/node_tests.h" +#include "libknot/dname.h" +#include "libknot/zone/node.h" +#include "libknot/util/descriptor.h" + +static int knot_node_tests_count(int argc, char *argv[]); +static int knot_node_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api node_tests_api = { + "DNS library - node", //! Unit name + &knot_node_tests_count, //! Count scheduled tests + &knot_node_tests_run //! Run scheduled tests +}; + +/* + * Unit implementation. + */ + +// C will not accept const int in other const definition +enum { TEST_NODES = 2, RRSETS = 5}; + +struct test_node { + knot_dname_t owner; + knot_node_t *parent; + uint size; +}; + +static knot_dname_t test_dnames[TEST_NODES] = { + {{}, (uint8_t *)"\3www\7example\3com", 17}, + {{}, (uint8_t *)"\3www\7example\3com", 17} +}; + +static struct test_node test_nodes[TEST_NODES] = { + {{{}, (uint8_t *)"\3com", 4}, (knot_node_t *)NULL}, + {{{}, (uint8_t *)"\3www\7example\3com", 17}, (knot_node_t *)NULL} +}; + +static knot_rrset_t rrsets[RRSETS] = { + {&test_dnames[0], 1, 1, 3600, NULL, NULL}, + {&test_dnames[1], 2, 1, 3600, NULL, NULL}, + {&test_dnames[1], 7, 1, 3600, NULL, NULL}, + {&test_dnames[1], 3, 1, 3600, NULL, NULL}, + {&test_dnames[1], 9, 1, 3600, NULL, NULL} +}; + +static int test_node_create() +{ + /* Tests creation of node by comparing with test_node struct */ + knot_node_t *tmp; + int errors = 0; + for (int i = 0; i < TEST_NODES && !errors; i++) { + tmp = knot_node_new(&test_nodes[i].owner, + test_nodes[i].parent, 0); + if (tmp == NULL || + tmp->owner != &test_nodes[i].owner || + tmp->parent != test_nodes[i].parent || + tmp->rrset_tree == NULL) { + errors++; + diag("Failed to create node structure"); + } + knot_node_free(&tmp, 0, 0); + } + return (errors == 0); +} + +static int test_node_add_rrset() +{ + knot_node_t *tmp; + knot_rrset_t *rrset; + int errors = 0; + for (int i = 0; i < TEST_NODES && !errors; i++) { + /* create node from test_node structure */ + tmp = knot_node_new(&test_nodes[i].owner, + test_nodes[i].parent, 0); + rrset = &rrsets[i]; + if (knot_node_add_rrset(tmp, rrset, 0) < 0) { + errors++; + diag("Failed to insert rrset into node"); + } + + /* check if rrset is really there */ + + const knot_rrset_t *rrset_from_node = NULL; + if ((rrset_from_node = + knot_node_rrset(tmp, rrset->type)) == NULL) { + errors++; + diag("Inserted rrset could not be found"); + continue; + } + + /* compare rrset from node with original rrset */ + + const knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrset->type); + + int cmp = 0; + + if ((rrset_from_node->rdata == NULL) && + (rrset->rdata == NULL)) { + cmp = 0; + } else if ((rrset_from_node->rdata != NULL) && + (rrset->rdata != NULL)) { + cmp = knot_rdata_compare(rrset_from_node->rdata, + rrset->rdata, + desc->wireformat); + } else { /* one is not NULL and other is -> error */ + cmp = 1; + } + + if (!((rrset_from_node->type == rrset->type) && + (rrset_from_node->rclass == rrset->rclass) && + (rrset_from_node->ttl == rrset->ttl) && + (rrset_from_node->rrsigs == rrset->rrsigs) && + (cmp == 0))) { + errors++; + diag("Values in found rrset are wrong"); + } + + knot_node_free(&tmp, 0, 0); + } + + return (errors == 0); +} + +static int test_node_get_rrset() +{ + knot_node_t *tmp; + knot_rrset_t *rrset; + int errors = 0; + + knot_node_t *nodes[TEST_NODES]; + + for (int i = 0; i < TEST_NODES && !errors; i++) { + tmp = knot_node_new(&test_nodes[i].owner, + test_nodes[i].parent, 0); + nodes[i] = tmp; + for (int j = 0; j < RRSETS; j++) { + knot_node_add_rrset(tmp, &rrsets[j], 0); + } + } + + for (int i = 0; i < TEST_NODES && !errors; i++) { + for (int j = 0; j < RRSETS; j++) { + rrset = &rrsets[j]; + if (knot_node_rrset(nodes[i], rrset->type) + != rrset) { + errors++; + diag("Failed to get proper rrset from node"); + } + } + knot_node_free(&nodes[i], 0, 0); + } + + return (errors == 0); +} + +static int test_node_get_parent() +{ + knot_node_t *tmp; + knot_rrset_t *rrset; + int errors = 0; + + knot_node_t *nodes[TEST_NODES]; + + for (int i = 0; i < TEST_NODES && !errors; i++) { + tmp = knot_node_new(&test_nodes[i].owner, + test_nodes[i].parent, 0); + nodes[i] = tmp; + rrset = &rrsets[i]; + knot_node_add_rrset(tmp, rrset, 0); + } + + for (int i = 0; i < TEST_NODES && !errors; i++) { + rrset = &rrsets[i]; + if (knot_node_parent(nodes[i], 0) != test_nodes[i].parent) { + errors++; + diag("Failed to get proper parent from node"); + } + knot_node_free(&nodes[i], 0, 0); + } + return (errors == 0); +} + +static int test_node_sorting() +{ + knot_node_t *tmp; + knot_rrset_t *rrset; + int errors = 0; + + tmp = knot_node_new(&test_nodes[0].owner, test_nodes[0].parent, 0); + + /* Will add rrsets to node. */ + + for (int i = 0; i < RRSETS && !errors; i++) { + rrset = &rrsets[i]; + knot_node_add_rrset(tmp, rrset, 0); + } + +// const skip_node_t *node = skip_first(tmp->rrsets); + +// int last = *((uint16_t *)node->key); + +// /* TODO there is now an API function knot_node_rrsets ... */ + +// /* Iterates through skip list and checks, whether it is sorted. */ + +// while ((node = skip_next(node)) != NULL) { +// if (last > *((uint16_t *)node->key)) { +// errors++; +// diag("RRset sorting error"); +// } +// last = *((uint16_t *)node->key); +// } + + knot_node_free(&tmp, 0, 0); + return (errors == 0); +} + +static int test_node_delete() +{ + int errors = 0; + + knot_node_t *tmp_node; + + for (int i = 0; i < TEST_NODES; i++) { + tmp_node = knot_node_new(&test_nodes[i].owner, + test_nodes[i].parent, 0); + + knot_node_free(&tmp_node, 0, 0); + + errors += (tmp_node != NULL); + } + + return (errors == 0); +} + +static int test_node_set_parent() +{ + knot_node_t *tmp_parent = knot_node_new(NULL, NULL, 0); + int errors = 0; + + knot_node_t *tmp_node; + + for (int i = 0; i < TEST_NODES; i++) { + tmp_node = knot_node_new(&test_nodes[i].owner, + test_nodes[i].parent, 0); + + knot_node_set_parent(tmp_node, tmp_parent); + + if (tmp_node->parent != tmp_node->parent) { + diag("Parent node is wrongly set."); + errors++; + } + knot_node_free(&tmp_node, 0, 0); + } + knot_node_free(&tmp_parent, 0, 0); + return (errors == 0); +} + +static int test_node_free_rrsets() +{ + int errors = 0; + + knot_node_t *tmp_node; + + for (int i = 0; i < TEST_NODES; i++) { + tmp_node = knot_node_new(&test_nodes[i].owner, + test_nodes[i].parent, 0); + + knot_node_free_rrsets(tmp_node, 0); + +// errors += (tmp_node->rrsets != NULL); + + knot_node_free(&tmp_node, 0, 0); + } + return (errors == 0); +} + +static const int KNOT_NODE_TEST_COUNT = 8; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_node_tests_count(int argc, char *argv[]) +{ + return KNOT_NODE_TEST_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_node_tests_run(int argc, char *argv[]) +{ + int res = 0, + res_final = 1; + + res = test_node_create(); + ok(res, "node: create"); + res_final *= res; + + skip(!res, 6) + + ok((res = test_node_add_rrset()), "node: add"); + res_final *= res; + + ok((res = test_node_get_rrset()), "node: get"); + res_final *= res; + + ok((res = test_node_get_parent()), "node: get parent"); + res_final *= res; + + ok((res = test_node_set_parent()), "node: set parent"); + res_final *= res; + + ok((res = test_node_sorting()), "node: sort"); + res_final *= res; + + ok((res = test_node_free_rrsets()), "node: free rrsets"); + res_final *= res; + + endskip; + + ok((res = test_node_delete()), "node: delete"); + //res_final *= res; + + return res_final; +} diff --git a/src/tests/libknot/libknot/node_tests.h b/src/tests/libknot/libknot/node_tests.h new file mode 100644 index 0000000..a90179f --- /dev/null +++ b/src/tests/libknot/libknot/node_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_NODE_TESTS_H_ +#define _KNOTD_NODE_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api node_tests_api; + +#endif /* _KNOTD_NODE_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/nsec3_tests.c b/src/tests/libknot/libknot/nsec3_tests.c new file mode 100644 index 0000000..7b95549 --- /dev/null +++ b/src/tests/libknot/libknot/nsec3_tests.c @@ -0,0 +1,252 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/* blame: jan.kadlec@nic.cz */ + +#include <assert.h> + +#include "libknot/common.h" +#include "libknot/util/error.h" +#include "libknot/nsec3.h" +#include "libknot/util/utils.h" +#include "common/base32hex.h" +#include "nsec3_tests.h" + +#ifdef TEST_WITH_LDNS +#include "ldns/ldns.h" +#endif + +static int knot_nsec3_tests_count(int argc, char *argv[]); +static int knot_nsec3_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api nsec3_tests_api = { + "NSEC3", //! Unit name + &knot_nsec3_tests_count, //! Count scheduled tests + &knot_nsec3_tests_run //! Run scheduled tests +}; + +extern int compare_wires_simple(uint8_t *w1, uint8_t *w2, uint count); + +static int test_nsec3_params_from_wire() +{ + /* Create sample NSEC3PARAM rdata */ + knot_rdata_item_t items[4]; + knot_rdata_t *rdata = knot_rdata_new(); + rdata->items = items; + rdata->count = 4; + knot_rdata_item_set_raw_data(rdata, 0, (uint16_t *)"\x1\x0\x1"); + knot_rdata_item_set_raw_data(rdata, 1, (uint16_t *)"\x1\x0\x0"); + knot_rdata_item_set_raw_data(rdata, 2, (uint16_t *)"\x2\x0\x0\x64"); + knot_rdata_item_set_raw_data(rdata, 3, + (uint16_t *)"\xF\x0\xE\xF\xF\xF\xF\xF\xF\xF\xF\xF\xF\xF\xF\xF\xF"); + + knot_rrset_t *rrset = + knot_rrset_new(knot_dname_new_from_str("cz.", + strlen("cz."), NULL), + KNOT_RRTYPE_NSEC3PARAM, + KNOT_CLASS_IN, + 3600); + assert(rrset); + int ret = knot_rrset_add_rdata(rrset, rdata); + assert(ret == KNOT_EOK); + + knot_nsec3_params_t nsec3_test_params; + + int errors = 0; + int lived = 0; + lives_ok({ + /* Create special variable for this block. */ + if (knot_nsec3_params_from_wire(NULL, NULL) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + + lived = 0; + if (knot_nsec3_params_from_wire(&nsec3_test_params, NULL) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + + lived = 0; + if (knot_nsec3_params_from_wire(NULL, rrset) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + + }, "nsec3 params from wire NULL tests"); + errors += lived != 1; + + if (knot_nsec3_params_from_wire(&nsec3_test_params, + rrset) != KNOT_EOK) { + diag("Could not convert nsec3 params to wire!"); + return 0; + } + + if (nsec3_test_params.algorithm != 1) { + diag("Algorithm error"); + errors++; + } + + if (nsec3_test_params.flags != 0) { + diag("Flags error %d", nsec3_test_params.flags); + errors++; + } + + if (nsec3_test_params.iterations != 100) { + diag("Iterations error %d", nsec3_test_params.iterations); + errors++; + } + printf("salt length: %d\n", nsec3_test_params.salt_length); + + if (nsec3_test_params.salt_length != 14) { + diag("Salt length error %d", nsec3_test_params.salt_length); + return 0; + } + + if (compare_wires_simple((uint8_t *)nsec3_test_params.salt, + (uint8_t *)"\xF\xF\xF\xF\xF\xF\xF\xF\xF\xF\xF\xF\xF\xF", + 14) != 0) { + diag("Salt wire error"); + errors++; + } + + knot_rrset_free(&rrset); + return (errors == 0); +} + +static int test_nsec3_sha1() +{ + int errors = 0; + int lived = 0; + + knot_nsec3_params_t nsec3_test_params; + + lives_ok({ + if (knot_nsec3_sha1(NULL, NULL, 1, NULL, NULL) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_nsec3_sha1(&nsec3_test_params, + NULL, 1, NULL, NULL) != + KNOT_EBADARG) { + errors++; + } + uint8_t data[20]; + lived = 1; + lived = 0; + if (knot_nsec3_sha1(&nsec3_test_params, + data, 20, NULL, NULL) != + KNOT_EBADARG) { + errors++; + } + uint8_t *digest = NULL; + lived = 1; + lived = 0; + if (knot_nsec3_sha1(&nsec3_test_params, + data, 20, &digest, NULL) != + KNOT_EBADARG) { + errors++; + } +// size_t size = 0; +// lived = 1; +// lived = 0; +// if (knot_nsec3_sha1(&nsec3_test_params, +// data, 20, &digest, &size) != +// KNOT_EBADARG) { +// errors++; +// } + lived = 1; + }, "NSEC3: nsec3 sha1 NULL tests"); + if (errors) { + diag("Does not return KNOT_EBADARG after " + "execution with wrong arguments!"); + } + + errors += lived != 1; + + uint8_t *digest = NULL; + size_t digest_size = 0; + if (knot_nsec3_sha1(&nsec3_test_params, + (uint8_t *)"\2ns\3nic\2cz", + strlen("\2ns\3nic\2cz"), &digest, + &digest_size) != KNOT_EOK) { + diag("Could not hash name!"); + return 0; + } + +#ifdef TEST_WITH_LDNS + ldns_rdf *name = ldns_dname_new_frm_str("ns.nic.cz."); + assert(name); + ldns_rdf *hashed_name = ldns_nsec3_hash_name(name, + nsec3_test_params.algorithm, + nsec3_test_params.iterations, + nsec3_test_params.salt_length, + nsec3_test_params.salt); + assert(hashed_name); +// knot_dname_t *dname_from_ldns = +// knot_dname_new_from_wire(ldns_rdf_data(hashed_name), +// ldns_rdf_size(hashed_name), +// NULL); + + char *name_b32 = NULL; + size_t size_b32 = base32hex_encode_alloc((char *)digest, digest_size, + &name_b32); + +// hex_print(name_b32, size_b32); +// hex_print(ldns_rdf_data(hashed_name), ldns_rdf_size(hashed_name)); + if (ldns_rdf_size(hashed_name) != size_b32) { + diag("Wrong hashed name length! Should be: %d is: %d", + ldns_rdf_size(hashed_name), size_b32); + return 0; + } + + if (compare_wires_simple(ldns_rdf_data(hashed_name), + (uint8_t *)name_b32, size_b32) != 0) { + diag("Wrong hashed name wire!"); + errors++; + } +#endif + +#ifndef TEST_WITH_LDNS + diag("Warning: without ldns this test is only partial!"); +#endif + return (errors == 0); +} + +static const int KNOT_NSEC3_TESTS_COUNT = 2; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_nsec3_tests_count(int argc, char *argv[]) +{ + return KNOT_NSEC3_TESTS_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_nsec3_tests_run(int argc, char *argv[]) +{ + ok(test_nsec3_params_from_wire(), "nsec3: params from wire"); + ok(test_nsec3_sha1(), "nsec3: sha1"); + return 1; +} diff --git a/src/tests/libknot/libknot/nsec3_tests.h b/src/tests/libknot/libknot/nsec3_tests.h new file mode 100644 index 0000000..10e7ed9 --- /dev/null +++ b/src/tests/libknot/libknot/nsec3_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_NSEC3_TESTS_H_ +#define _KNOTD_NSEC3_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api nsec3_tests_api; + +#endif /* _KNOTD_NSEC3_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/packet_tests.c b/src/tests/libknot/libknot/packet_tests.c new file mode 100644 index 0000000..185504f --- /dev/null +++ b/src/tests/libknot/libknot/packet_tests.c @@ -0,0 +1,430 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/* blame: jan.kadlec@nic.cz */ + +#include <assert.h> +#include <stdint.h> + +#include "packet_tests.h" +#include "libknot/util/error.h" +#include "libknot/packet/packet.h" +#include "libknot/util/wire.h" +/* *test_t structures */ +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" + +static int packet_tests_count(int argc, char *argv[]); +static int packet_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api packet_tests_api = { + "packet", //! Unit name + &packet_tests_count, //! Count scheduled tests + &packet_tests_run //! Run scheduled tests +}; + +static int test_packet_new() +{ + int errors = 0; + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_NONE); + if (packet == NULL) { + diag("Could not create packet using prealloc_node constant!"); + errors++; + } + knot_packet_free(&packet); + + packet = knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + if (packet == NULL) { + diag("Could not create packet using prealloc_query constant!"); + errors++; + } + knot_packet_free(&packet); + + packet = knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + if (packet == NULL) { + diag("Could not create packet using prealloc_resp constant!"); + errors++; + } + knot_packet_free(&packet); + + /*!< \todo Should it create packet using any size? */ + + return (errors == 0); +} + +static int test_packet_parse_from_wire() +{ + int errors = 0; + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + + int tmp = 0; + lives_ok({ + if (knot_packet_parse_from_wire(NULL, NULL, 0, 0) != + KNOT_EBADARG) { + diag("Trying to parse NULL packet with NULL wire " + "did not return KNOT_EBADARG!"); + errors++; + } + tmp = 1; + tmp = 0; + if (knot_packet_parse_from_wire(packet, NULL, 0, 0) != + KNOT_EBADARG) { + diag("Trying to parse with NULL wire " + "did not return KNOT_EBADARG!"); + errors++; + } + tmp = 1; + tmp = 0; + if (knot_packet_parse_from_wire(packet, (uint8_t *)0xbeef, + 0, 0) != + KNOT_EFEWDATA) { + diag("Trying to parse 0 lengt" + "did not return KNOT_EOK!"); + errors++; + } + tmp = 1; + }, "packet: parse from wire NULL tests."); + errors += tmp != 1; + + knot_packet_free(&packet); + + return (errors == 0); +} + +static int test_packet_parse_next_rr_answer() +{ + int errors = 0; + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(packet); + + int tmp = 0; + lives_ok({ + int ret = 0; + if (knot_packet_parse_next_rr_answer(NULL, NULL) != + KNOT_EBADARG) { + diag("Trying to parse next RR answer with " + "NULL packet with and NULL RRSet " + "did not return KNOT_EBADARG!"); + errors++; + } + tmp = 1; + tmp = 0; + if ((ret = knot_packet_parse_next_rr_answer(packet, + NULL)) != + KNOT_EBADARG) { + diag("Trying to parse next RR with NULL RRSet pointer " + "did not return KNOT_EBADARG! Got %d.", + ret); + errors++; + } + tmp = 1; +// knot_rrset_t *rrset = (knot_rrset_t *)0xaaaa; +// tmp = 0; +// if (knot_packet_parse_next_rr_answer(packet, +// &rrset) != +// KNOT_EBADARG) { +// diag("Trying to parse next RR answer with rrset pointer" +// " not pointing to NULL did not " +// "return KNOT_EBADARG!"); +// errors++; +// } +// tmp = 1; + }, "packet: parse next rr answer NULL tests."); + errors += tmp != 1; + + knot_packet_free(&packet); + + return (errors == 0); +} + +static int test_packet_parse_rest() +{ + int res = 0; + lives_ok({res = knot_packet_parse_rest(NULL);}, + "packet: parse rest NULL test"); + + if (res != KNOT_EBADARG) { + diag("parse rest NULL did not return KNOT_EBADARG.\n"); + return 1; + } + + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_NONE); + assert(packet); + + todo(); + lives_ok({res = knot_packet_parse_rest(packet);}, + "packet: parser rest empty packet"); + endtodo; + + knot_packet_free(&packet); + + return 1; +} + + +static int test_packet_set_max_size() +{ + int errors = 0; + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_NONE); + assert(packet); + + int lived = 0; + + lives_ok({ + lived = 0; + if (knot_packet_set_max_size(NULL, 1) != KNOT_EBADARG) { + diag("Calling packet_set_max() with NULL packet " + "did not return KNOT_EBADARG"); + errors++; + } + lived = 1; + }, "packet: set max size NULL test"); + + errors += lived != 1; + + if (knot_packet_set_max_size(packet, 0) != KNOT_EBADARG) { + diag("Calling packet_set_max() with size eqeal to 0 did not " + "return KNOT_EBADARG"); + errors++; + } + + if (knot_packet_set_max_size(packet, 10) != KNOT_EOK) { + diag("Calling packet_set_max() with valid arguments did not " + "return KNOT_EOK"); + errors++; + } + + knot_packet_free(&packet); + + return (errors == 0); +} + +static int test_packet_add_tmp_rrset() +{ + int errors = 0; + int lived = 0; + + /* knot_packet_add_tmp_rrset only works with pointers. */ + knot_rrset_t *rrset = (knot_rrset_t *)0xabcdef; + + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(packet); + + lives_ok({ + if (knot_packet_add_tmp_rrset(NULL, rrset) != + KNOT_EBADARG) { + diag("Trying to add to NULL packet did not return " + "KNOT_EBADARG!"); + errors++; + } + lived = 1; + + lived = 0; + if (knot_packet_add_tmp_rrset(packet, NULL) != + KNOT_EBADARG) { + diag("Trying to add NULL rrset did not return " + "KNOT_EBADARG!"); + errors++; + } + lived = 1; + + lived = 0; + if (knot_packet_add_tmp_rrset(NULL, NULL) != + KNOT_EBADARG) { + diag("Trying to add NULL rrset to NULL packet " + "did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + }, "packet: add tmp rrset NULL test"); + errors += lived != 1; + + if (knot_packet_add_tmp_rrset(packet, rrset) != KNOT_EOK) { + diag("Could not add valid RRSet to packet!"); + errors++; + } + + /* Not freeing because RRSet is fake. */ +// knot_packet_free(&packet); + + free(packet->wireformat); + free(packet); + + return (errors == 0); +} + +//static int test_packet_contains() +//{ +// int errors = 0; +// int lives = 0; + +// knot_packet_t *packet = +// knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); +// assert(packet); + +// lives_ok({ +// if (knot_packet_contains(packet, NULL, +// KNOT_RRSET_COMPARE_PTR) != +// KNOT_EBADARG{ +// diag(); +// } +// }, "packet: contains NULL tests); + +// knot_packet_contains() + +//} + +static int test_packet_header_to_wire() +{ + int errors = 0; + int lived = 0; + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(packet); + size_t size; + + lives_ok({ + knot_packet_header_to_wire(NULL, NULL, NULL); + lived = 1; + lived = 0; + knot_packet_header_to_wire(&packet->header, NULL, &size); + lived = 1; + }, "packet: header to wire NULL tests"); + errors += lived != 1; + + knot_packet_free(&packet); + return (errors == 0); +} + +static int test_packet_question_to_wire() +{ + int errors = 0 ; + int lived = 0; + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(packet); + + lives_ok({ + if (knot_packet_question_to_wire(NULL) != KNOT_EBADARG) { + diag("Calling packet_question_to_wire with " + "NULL pointer did not result to KNOT_EBADARG!"); + errors++; + } + lived = 1; + }, "packet: question to wire NULL tests"); + errors += lived != 1; + + packet->size = KNOT_WIRE_HEADER_SIZE + 1; + if (knot_packet_question_to_wire(packet) != KNOT_ERROR) { + diag("Calling packet_question_to_wire with oversized packet " + "did not return KNOT_ERROR!"); + errors++; + } + + knot_packet_free(&packet); + return (errors == 0); +} + +static int test_packet_edns_to_wire() +{ + int errors = 0 ; + int lived = 0; + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(packet); + + lives_ok({ + knot_packet_edns_to_wire(NULL); + lived = 1; + }, "packet: question to wire NULL tests"); + errors += lived != 1; + + knot_packet_free(&packet); + return (errors == 0); +} + +static int test_packet_to_wire() +{ + int errors = 0 ; + int lived = 0; + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(packet); + + lives_ok({ + if (knot_packet_to_wire(NULL, NULL, NULL) != KNOT_EBADARG) { + diag("Calling packet_to_wire with " + "NULL pointers did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + size_t size; + lived = 0; + if (knot_packet_to_wire(packet, NULL, &size) != + KNOT_EBADARG) { + diag("Calling packet_to_wire with " + "NULL wire did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + uint8_t *wire = (uint8_t *)0xabcdef; + lived = 0; + if (knot_packet_to_wire(packet, &wire, &size) != + KNOT_EBADARG) { + diag("Calling packet_to_wire with " + "wire not pointing to NULL did not return" + " KNOT_EBADARG!"); + errors++; + } + lived = 1; + }, "packet: to wire NULL tests"); + errors += lived != 1; + + knot_packet_free(&packet); + return (errors == 0); +} + +static const uint KNOT_PACKET_TEST_COUNT = 21; + +static int packet_tests_count(int argc, char *argv[]) +{ + return KNOT_PACKET_TEST_COUNT; +} + +static int packet_tests_run(int argc, char *argv[]) +{ + int res = 0; + ok(res = test_packet_new(), "packet: new"); + skip(!res, 20); + ok(test_packet_parse_rest(), "packet: parse rest"); + ok(test_packet_parse_from_wire(), "packet: parse from wire"); + ok(test_packet_parse_next_rr_answer(), "packet: parse next rr answer"); + ok(test_packet_set_max_size(), "packet: set max size"); + ok(test_packet_add_tmp_rrset(), "packet: add tmp rrset"); + ok(test_packet_header_to_wire(), "packet: header to wire"); + ok(test_packet_question_to_wire(), "packet: header to wire"); + ok(test_packet_edns_to_wire(), "packet: header to wire"); + ok(test_packet_to_wire(), "packet: to wire"); +// ok(res = test_packet_contains(), "Packet: contains"); + endskip; + return 1; +} diff --git a/src/tests/libknot/libknot/packet_tests.h b/src/tests/libknot/libknot/packet_tests.h new file mode 100644 index 0000000..5a8ce03 --- /dev/null +++ b/src/tests/libknot/libknot/packet_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_PACKET_TESTS_H_ +#define _KNOTD_PACKET_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api packet_tests_api; + +#endif /* _KNOTD_PACKET_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/query_tests.c b/src/tests/libknot/libknot/query_tests.c new file mode 100644 index 0000000..1e4e081 --- /dev/null +++ b/src/tests/libknot/libknot/query_tests.c @@ -0,0 +1,160 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/* blame: jan.kadlec@nic.cz */ + +#include <assert.h> +#include <stdint.h> + +#include "packet_tests.h" +#include "libknot/util/error.h" +#include "libknot/packet/packet.h" +#include "libknot/util/wire.h" +#include "libknot/packet/query.h" +/* *test_t structures */ +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" + +static int query_tests_count(int argc, char *argv[]); +static int query_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api query_tests_api = { + "query", //! Unit name + &query_tests_count, //! Count scheduled tests + &query_tests_run //! Run scheduled tests +}; + +static const uint KNOT_QUERY_TEST_COUNT = 1; + +static int query_tests_count(int argc, char *argv[]) +{ + return KNOT_QUERY_TEST_COUNT; +} + +static int test_query_init() +{ + int errors = 0; + int lived = 0; + knot_packet_t *query = + knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + assert(query); + lives_ok({ + if (knot_query_init(NULL) != KNOT_EBADARG) { + diag("Calling query_init with NULL query did " + "not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + }, "query: init NULL tests"); + errors += lived != 1; + + assert(knot_packet_set_max_size(query, 1024 * 10) == KNOT_EOK); + if (knot_query_init(query) != KNOT_EOK) { + diag("Calling query_init with valid query did not return " + "KNOT_EOK!"); + errors++; + } + + if (!knot_packet_is_query(query)) { + diag("QR flag was not set!"); + errors++; + } + + return (errors == 0); +} + +static int test_query_set_question() +{ + int errors = 0; + int lived = 0; + + knot_packet_t *query = + knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + assert(query); + assert(knot_packet_set_max_size(query, 1024 * 10) == KNOT_EOK); + knot_query_init(query); + + knot_rrset_t *rrset = + knot_rrset_new(knot_dname_new_from_str("a.ns.cz.", + strlen("a.ns.cz."), + NULL), + KNOT_RRTYPE_A, KNOT_CLASS_IN, 3600); + assert(rrset); + + knot_question_t *question = malloc(sizeof(knot_question_t)); + assert(question); + question->qname = rrset->owner; + question->qtype = rrset->type; + question->qclass = rrset->rclass; + + lives_ok({ + if (knot_query_set_question(NULL, NULL) != KNOT_EBADARG) { + diag("Calling query_set_question with NULL"); + errors++; + } + lived = 1; + lived = 0; + if (knot_query_set_question(query, NULL) != KNOT_EBADARG) { + diag("Calling query_set_question with NULL"); + errors++; + } + lived = 1; + lived = 0; + if (knot_query_set_question(NULL, question) != KNOT_EBADARG) { + diag("Calling query_set_question with NULL"); + errors++; + } + lived = 1; + }, "query: set question NULL tests"); + errors += lived != 1; + + if (knot_query_set_question(query, question) != KNOT_EOK) { + diag("Calling query_set_question with valid arguments "); + errors++; + } + + if (query->question.qname != rrset->owner) { + diag("Qname was not set right!"); + errors++; + } + + if (query->question.qtype != rrset->type) { + diag("Qtype was not set right!"); + errors++; + } + + if (query->question.qclass != rrset->rclass) { + diag("Qclass was not set right!"); + errors++; + } + + if (query->header.qdcount != 1) { + diag("Qdcount was not set right!"); + errors++; + } + + knot_packet_free(&query); + knot_rrset_deep_free(&rrset, 1, 0, 0); + + return (errors == 0); +} + +static int query_tests_run(int argc, char *argv[]) +{ + ok(test_query_init(), "query: init"); + ok(test_query_set_question(), "query: set question"); + return 1; +} diff --git a/src/tests/libknot/libknot/query_tests.h b/src/tests/libknot/libknot/query_tests.h new file mode 100644 index 0000000..037ecab --- /dev/null +++ b/src/tests/libknot/libknot/query_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_QUERY_TESTS_H_ +#define _KNOTD_QUERY_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api query_tests_api; + +#endif /* _KNOTD_QUERY_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/rdata_tests.c b/src/tests/libknot/libknot/rdata_tests.c new file mode 100644 index 0000000..561686a --- /dev/null +++ b/src/tests/libknot/libknot/rdata_tests.c @@ -0,0 +1,954 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <assert.h> + +#include "tests/libknot/libknot/rdata_tests.h" +#include "libknot/common.h" +#include "libknot/rdata.h" +#include "libknot/dname.h" +#include "libknot/util/descriptor.h" +#include "libknot/util/utils.h" +#include "libknot/util/error.h" + +enum { TEST_DOMAINS_OK = 8 }; + +struct test_domain { + char *str; + char *wire; + uint size; + char *labels; + short label_count; +}; + +/*! \warning Do not change the order in those, if you want to test some other + * feature with new dname, add it at the end of these arrays. + */ +static const struct test_domain + test_domains_ok[TEST_DOMAINS_OK] = { + { "abc.test.domain.com.", "\3abc\4test\6domain\3com", 21, + "\x0\x4\x9\x10", 4 }, + { "some.test.domain.com.", "\4some\4test\6domain\3com", 22, + "\x0\x5\xA\x11", 4 }, + { "xyz.test.domain.com.", "\3xyz\4test\6domain\3com", 21, + "\x0\x4\x9\x10", 4 }, + { "some.test.domain.com.", "\4some\4test\6domain\3com", 22, + "\x0\x5\xA\x11", 4 }, + { "test.domain.com.", "\4test\6domain\3com", 17, + "\x0\x5\xC", 3 }, + { ".", "\0", 1, + "", 0 }, + { "foo.bar.net.", "\3foo\3bar\3net", 13, + "\x0\x4\x8", 3}, + { "bar.net.", "\3bar\3net", 9, + "\x0\x4", 2} +}; + + +static int knot_rdata_tests_count(int argc, char *argv[]); +static int knot_rdata_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api rdata_tests_api = { + "DNS library - rdata", //! Unit name + &knot_rdata_tests_count, //! Count scheduled tests + &knot_rdata_tests_run //! Run scheduled tests +}; + +/*----------------------------------------------------------------------------*/ +/* + * Unit implementation. + */ + +static uint16_t *RDATA_ITEM_PTR = (uint16_t *)0xDEADBEEF; + +enum { RDATA_ITEMS_COUNT = 7, TEST_RDATA_COUNT = 4 , RDATA_DNAMES_COUNT = 2 }; + +static knot_dname_t RDATA_DNAMES[RDATA_DNAMES_COUNT] = { + {{}, (uint8_t *)"\6abcdef\7example\3com", 20, + (uint8_t *)"\x0\x7\xF", 3}, + {{}, (uint8_t *)"\6abcdef\3foo\3com", 16, + (uint8_t *)"\x0\x7\xB", 3} +}; + +static knot_rdata_item_t TEST_RDATA_ITEMS[RDATA_ITEMS_COUNT] = { + {.dname = (knot_dname_t *)0xF00}, + {.raw_data = (uint16_t *)"some data"}, + {.raw_data = (uint16_t *)"other data"}, + {.raw_data = (uint16_t *)"123456"}, + {.raw_data = (uint16_t *)"654321"}, + {.dname = &RDATA_DNAMES[0]}, + {.dname = &RDATA_DNAMES[1]} +}; + +/* \note indices 0 to 3 should not be changed - used in (and only in) + * test_rdata_compare() - better than creating new struct just for this + */ +static knot_rdata_t test_rdata[TEST_RDATA_COUNT] = { + {&TEST_RDATA_ITEMS[3], 1, &test_rdata[1]}, + {&TEST_RDATA_ITEMS[4], 1, &test_rdata[2]}, + {&TEST_RDATA_ITEMS[5], 1, &test_rdata[3]}, + {&TEST_RDATA_ITEMS[6], 1, &test_rdata[4]}, +}; + +static knot_rdata_t TEST_RDATA = { + &TEST_RDATA_ITEMS[0], + 3, + &TEST_RDATA +}; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tests knot_rdata_new(). + * + * Creates new RDATA structure with no items and tests if there really are no + * items in it. + * + * \retval > 0 on success. + * \retval 0 otherwise. + */ +static int test_rdata_create() +{ + knot_rdata_t *rdata = knot_rdata_new(); + if (rdata == NULL) { + diag("RDATA structure not created!"); + return 0; + } + + if (knot_rdata_item(rdata, 0) != NULL) { + diag("Get item returned something else than NULL!"); + knot_rdata_free(&rdata); + return 0; + } + + knot_rdata_free(&rdata); + return 1; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tests knot_rdata_free(). + * + * \retval > 0 on success. + * \retval 0 otherwise. + */ +static int test_rdata_delete() +{ + // how to test this?? + return 0; +} + +/*----------------------------------------------------------------------------*/ + +static void generate_rdata(uint8_t *data, int size) +{ + for (int i = 0; i < size; ++i) { + data[i] = rand() % 256; + } +} + +/*----------------------------------------------------------------------------*/ + +static int fill_rdata(uint8_t *data, int max_size, uint16_t rrtype, + knot_rdata_t *rdata) +{ + assert(rdata != NULL); + assert(data != NULL); + assert(max_size > 0); + + uint8_t *pos = data; + int used = 0; + int wire_size = 0; + + //note("Filling RRType %u", rrtype); + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrtype); + + uint item_count = desc->length; + knot_rdata_item_t *items = + (knot_rdata_item_t *)malloc(item_count + * sizeof(knot_rdata_item_t)); + + for (int i = 0; i < item_count; ++i) { + uint size = 0; + int domain = 0; + knot_dname_t *dname = NULL; + int binary = 0; + int stored_size = 0; + + switch (desc->wireformat[i]) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: + dname = knot_dname_new_from_wire( + (uint8_t *)test_domains_ok[0].wire, + test_domains_ok[0].size, NULL); + assert(dname != NULL); + /* note("Created domain name: %s", + knot_dname_name(dname)); */ + //note("Domain name ptr: %p", dname); + domain = 1; + size = knot_dname_size(dname); + //note("Size of created domain name: %u", size); + assert(size < KNOT_MAX_RDATA_ITEM_SIZE); + // store size of the domain name + *(pos++) = size; + // copy the domain name + memcpy(pos, knot_dname_name(dname), size); + pos += size; + break; + default: + binary = 1; + size = rand() % KNOT_MAX_RDATA_ITEM_SIZE; + } + + if (binary) { + // Rewrite the actual 2 bytes in the data array + // with length. + // (this is a bit ugly, but does the work ;-) + knot_wire_write_u16(pos, size); + //*pos = size; + } + + //note("Filling %u bytes", size); + used += size; + assert(used < max_size); + + if (domain) { + items[i].dname = dname; + wire_size += knot_dname_size(dname); +/* note("Saved domain name ptr on index %d: %p", + i, items[i].dname); */ + } else { + free(dname); +// note("Saved raw data ptr on index %d: %p",i, pos); + items[i].raw_data = (uint16_t *)pos; + pos += size; + wire_size += size; + if (binary && !stored_size) { + wire_size -= 2; + } + } + } + + int res = knot_rdata_set_items(rdata, items, item_count); + if (res != 0) { + diag("knot_rdata_set_items() returned %d.", res); + free(items); + return -1; + } else { + free(items); + return wire_size; + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Checks if all RDATA items in the given RDATA structure are correct. + * + * \return Number of errors encountered. Error is either if some RDATA item + * is not set (i.e. NULL) or if it has other than the expected value. + */ +static int check_rdata(const uint8_t *data, int max_size, uint16_t rrtype, + const knot_rdata_t *rdata) +{ + assert(rdata != NULL); + assert(data != NULL); + assert(max_size > 0); + + int errors = 0; + + const uint8_t *pos = data; + int used = 0; + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrtype); + uint item_count = desc->length; + //note("check_rdata(), RRType: %u", rrtype); + //note(" item count: %u", item_count); + + for (int i = 0; i < item_count; ++i) { + uint size = 0; + int domain = 0; + int binary = 0; + + //note(" item: %d", i); + + switch (desc->wireformat[i]) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: + //note(" domain name"); + domain = 1; + size = knot_dname_size(knot_rdata_item( + rdata, i)->dname); + break; + default: + size = + knot_wire_read_u16((uint8_t *) + (knot_rdata_item( + rdata, i)->raw_data)); + } + + assert(size > 0); + //note("Size: %u", size); + used += size; + assert(used < max_size); + + //note(" item size: %u", size); + + if (domain) { + /*note("Domain name ptr: %p", + knot_rdata_get_item(rdata, i)->dname);*/ + // check dname size + if (*pos != size) { + diag("Domain name stored in %d-th" + "RDATA has wrong size: %d" + " (should be %d)", size, *pos); + ++errors; + } else if (strncmp((char *)knot_dname_name( + knot_rdata_item(rdata, i)->dname), + (char *)(pos + 1), *pos) != 0) { + diag("Domain name stored in %d-th" + "RDATA item is wrong: %s (" + "should be %.*s)", i, + knot_dname_name(knot_rdata_item( + rdata, i)->dname), + *pos, (char *)(pos + 1)); + ++errors; + } + + pos += *pos + 1; + + continue; + } + + if (binary && + size != + knot_wire_read_u16( + (uint8_t *)(knot_rdata_item(rdata, i)->raw_data))) { + diag("Size of stored binary data is wrong:" + " %u (should be %u)", + knot_rdata_item(rdata, i)->raw_data[0] + 1, + size); + ++errors; + } + + if (strncmp((char *) + (&knot_rdata_item(rdata, i)->raw_data[0]), + (char *)pos, size) != 0) { +/* knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrtype); */ + + diag("Data stored in %d-th RDATA item are wrong.", i); + ++errors; + } + + pos += size; + } + + return errors; +} + +/*----------------------------------------------------------------------------*/ + +//static int convert_to_wire(const uint8_t *data, int max_size, uint16_t rrtype, +// uint8_t *data_wire) +//{ +// //note("Converting type %u", rrtype); + +// int wire_size = 0; +// const uint8_t *pos = data; +// uint8_t *pos_wire = data_wire; + +// knot_rrtype_descriptor_t *desc = +// knot_rrtype_descriptor_by_type(rrtype); +// uint item_count = desc->length; + +// for (int i = 0; i < item_count; ++i) { +// const uint8_t *from = NULL; +// uint to_copy = 0; + +// switch (desc->wireformat[i]) { +// case KNOT_RDATA_WF_COMPRESSED_DNAME: +// case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: +// case KNOT_RDATA_WF_LITERAL_DNAME: +// // copy the domain name without its length +// from = pos + 1; +// to_copy = *pos; +// pos += *pos + 1; +///* note("Domain name in wire format (size %u): %s", +// to_copy, (char *)from); */ +// break; +// case KNOT_RDATA_WF_BYTE: +// //note(" 1byte int"); +// from = pos; +// to_copy = 1; +// pos += 1; +// break; +// case KNOT_RDATA_WF_SHORT: +// //note(" 2byte int"); +// from = pos; +// to_copy = 2; +// pos += 2; +// break; +// case KNOT_RDATA_WF_LONG: +// //note(" 4byte int"); +// from = pos; +// to_copy = 4; +// pos += 4; +// break; +// case KNOT_RDATA_WF_A: +// //note(" A"); +// from = pos; +// to_copy = 4; +// pos += 4; +// break; +// case KNOT_RDATA_WF_AAAA: +// //note(" AAAA"); +// from = pos; +// to_copy = 16; +// pos += 16; +// break; +// case KNOT_RDATA_WF_BINARY: +// case KNOT_RDATA_WF_APL: // saved as binary +// case KNOT_RDATA_WF_IPSECGATEWAY: // saved as binary +// //note(" binary"); +// from = pos + 1; +// to_copy = *pos; +// pos += *pos + 1; +// break; +// case KNOT_RDATA_WF_TEXT: +// case KNOT_RDATA_WF_BINARYWITHLENGTH: +// //note(" text or binary with length (%u)", *pos); +// to_copy = *pos + 1; +// from = pos; +// pos += *pos + 1; +// break; +// default: +// assert(0); +// } + +// //note("Copying %u bytes from %p", to_copy, from); + +// assert(from != NULL); +// assert(to_copy != 0); + +// memcpy(pos_wire, from, to_copy); +// pos_wire += to_copy; +// wire_size += to_copy; + +// assert(wire_size < max_size); +// } + +// return wire_size; +//} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tests knot_rdata_set_item(). + * + * \retval > 0 on success. + * \retval 0 otherwise. + */ +static int test_rdata_set_item() +{ + knot_rdata_t *rdata = knot_rdata_new(); + knot_rdata_item_t item; + item.raw_data = RDATA_ITEM_PTR; + + int ret = knot_rdata_set_item(rdata, 0, item); + if (ret == 0) { + diag("knot_rdata_set_item() called on empty RDATA" + "returned %d instead of error (-1).", ret); + knot_rdata_free(&rdata); + return 0; + } + +// uint8_t *data = malloc(sizeof(uint8_t) * KNOT_MAX_RDATA_WIRE_SIZE); +// assert(data); + uint8_t data[KNOT_MAX_RDATA_WIRE_SIZE]; + generate_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE); + + // set items through set_items() and then call set_item() + uint16_t rrtype = rand() % KNOT_RRTYPE_LAST + 1; + if (fill_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE, rrtype, rdata) < 0) { + knot_rdata_free(&rdata); + diag("Error filling RDATA"); + return 0; + } + + uint8_t pos = rand() % knot_rrtype_descriptor_by_type(rrtype)->length; + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrtype); + + // if the rdata on this position is domain name, free it to avoid leaks + if (desc->wireformat[pos] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME + || desc->wireformat[pos] == KNOT_RDATA_WF_COMPRESSED_DNAME + || desc->wireformat[pos] == KNOT_RDATA_WF_LITERAL_DNAME) { + knot_dname_free(&(rdata->items[pos].dname)); + } + + ret = knot_rdata_set_item(rdata, pos, item); + if (ret != 0) { + diag("knot_rdata_set_item() called on filled" + " RDATA returned %d instead of 0.", ret); + knot_rdata_free(&rdata); + return 0; + } + + if (knot_rdata_item(rdata, pos)->raw_data != RDATA_ITEM_PTR) { + diag("RDATA item on position %d is wrong: %p (should be %p).", + pos, knot_rdata_item(rdata, pos)->raw_data, + RDATA_ITEM_PTR); + knot_rdata_free(&rdata); + return 0; + } + + for (int x = 0; x < desc->length; x++) { + if (x != pos && ( + desc->wireformat[x] == + KNOT_RDATA_WF_UNCOMPRESSED_DNAME || + desc->wireformat[x] == + KNOT_RDATA_WF_COMPRESSED_DNAME || + desc->wireformat[x] == + KNOT_RDATA_WF_LITERAL_DNAME)) { + knot_dname_free(&(rdata->items[x].dname)); + } + } + +// knot_rdata_free(&rdata); + return 1; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tests knot_rdata_set_items(). + * + * Iterates over the test_rdatas array and for each testing RDATA it creates + * the RDATA structure, sets its items (\see set_rdata_all()) and checks if the + * items are set properly (\see check_rdata()). + * + * \retval > 0 on success. + * \retval 0 otherwise. + */ +static int test_rdata_set_items() +{ + knot_rdata_t *rdata = NULL; + knot_rdata_item_t *item = (knot_rdata_item_t *)0xDEADBEEF; + int errors = 0; + + // check error return values + if (knot_rdata_set_items(rdata, NULL, 0) != KNOT_EBADARG) { + diag("Return value of knot_rdata_set_items() " + "when rdata == NULL is wrong"); + return 0; + } else { + rdata = knot_rdata_new(); + assert(rdata != NULL); + + if (knot_rdata_set_items(rdata, NULL, 0) != KNOT_EBADARG) { + diag("Return value of knot_rdata_set_items()" + " when items == NULL is wrong"); +// knot_rdata_free(&rdata); + return 0; + } else if (knot_rdata_set_items(rdata, item, 0) != + KNOT_EBADARG) { + diag("Return value of knot_rdata_set_items()" + " when count == 0" + "is wrong"); +// knot_rdata_free(&rdata); + return 0; + } +// knot_rdata_free(&rdata); + } + + // generate some random data +// uint8_t *data = malloc(sizeof(uint8_t) * KNOT_MAX_RDATA_WIRE_SIZE); + uint8_t data [KNOT_MAX_RDATA_WIRE_SIZE]; + generate_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE); + + for (int i = 0; i <= KNOT_RRTYPE_LAST; ++i) { + rdata = knot_rdata_new(); + + if (fill_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE, i, rdata) + < 0) { + ++errors; + } + errors += check_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE, i, + rdata); + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(i); + + for (int x = 0; x < desc->length; x++) { + if (desc->wireformat[x] == + KNOT_RDATA_WF_UNCOMPRESSED_DNAME || + desc->wireformat[x] == + KNOT_RDATA_WF_COMPRESSED_DNAME || + desc->wireformat[x] == + KNOT_RDATA_WF_LITERAL_DNAME) { +// printf("freeing %p\n", rdata->items[x].dname); + knot_dname_free(&(rdata->items[x].dname)); + } + } + +// knot_rdata_free(&rdata); + } + + return (errors == 0); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tests knot_rdata_get_item(). + * + * \retval > 0 on success. + * \retval 0 otherwise. + */ +static int test_rdata_get_item() +{ + const knot_rdata_t *rdata = &TEST_RDATA; + + if (knot_rdata_item(rdata, TEST_RDATA.count) != NULL) { + diag("knot_rdata_get_item() called with" + "invalid position did not return NULL"); + return 0; + } + + int errors = 0; + if ((knot_rdata_item(rdata, 0)->dname) + != TEST_RDATA.items[0].dname) { + diag("RDATA item on position 0 is wrong: %p (should be %p)", + knot_rdata_item(rdata, 0), TEST_RDATA.items[0]); + ++errors; + } + if ((knot_rdata_item(rdata, 1)->raw_data) + != TEST_RDATA.items[1].raw_data) { + diag("RDATA item on position 0 is wrong: %p (should be %p)", + knot_rdata_item(rdata, 1), TEST_RDATA.items[1]); + ++errors; + } + if ((knot_rdata_item(rdata, 2)->raw_data) + != TEST_RDATA.items[2].raw_data) { + diag("RDATA item on position 0 is wrong: %p (should be %p)", + knot_rdata_item(rdata, 2), TEST_RDATA.items[2]); + ++errors; + } + + return (errors == 0); +} + +/*----------------------------------------------------------------------------*/ + +static int test_rdata_compare() +{ + int errors = 0; + + uint8_t format_rawdata = KNOT_RDATA_WF_BINARY; + + uint8_t format_dname = KNOT_RDATA_WF_LITERAL_DNAME; + + /* 123456 \w 654321 -> result -1 */ + if (knot_rdata_compare(&test_rdata[0], + &test_rdata[1], + &format_rawdata) != -1) { + diag("RDATA raw data comparison failed 0"); + errors++; + } + + /* 123456 \w 123456 -> result 0 */ + if (knot_rdata_compare(&test_rdata[0], + &test_rdata[0], + &format_rawdata) != 0) { + diag("RDATA raw data comparison failed 1 "); + errors++; + } + + /* 123456 \w 654321 -> result 1 */ + if (knot_rdata_compare(&test_rdata[1], + &test_rdata[0], + &format_rawdata) != 1) { + diag("RDATA raw data comparison failed 2"); + errors++; + } + + /* abcdef.example.com. \w abcdef.foo.com. -> result -1 */ + int ret = 0; + if ((ret = knot_rdata_compare(&test_rdata[2], + &test_rdata[3], + &format_dname)) >= 0) { + diag("RDATA dname comparison failed 3"); + errors++; + } + + /* abcdef.example.com. \w abcdef.example.com. -> result 0 */ + if (knot_rdata_compare(&test_rdata[2], + &test_rdata[2], + &format_dname) != 0) { + diag("RDATA dname comparison failed 4"); + errors++; + } + + /* abcdef.example.com. \w abcdef.foo.com -> result 1 */ + if (knot_rdata_compare(&test_rdata[3], + &test_rdata[2], + &format_dname) != 1) { + diag("RDATA dname comparison failed 5"); + errors++; + } + + + + + return (errors == 0); +} + +/*----------------------------------------------------------------------------*/ + +//static int test_rdata_wire_size() +//{ +// knot_rdata_t *rdata; +// int errors = 0; + +// // generate some random data +// uint8_t data[KNOT_MAX_RDATA_WIRE_SIZE]; +// generate_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE); + +// for (int i = 0; i <= KNOT_RRTYPE_LAST; ++i) { +// rdata = knot_rdata_new(); + +// int size = +// fill_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE, i, rdata); + +// if (size < 0) { +// ++errors; +// } else { +// int counted_size = knot_rdata_wire_size(rdata, +// knot_rrtype_descriptor_by_type(i)->wireformat); +// if (size != counted_size) { +// diag("Wrong wire size computed (type %d):" +// " %d (should be %d)", +// i, counted_size, size); +// ++errors; +// } +// } + +// knot_rrtype_descriptor_t *desc = +// knot_rrtype_descriptor_by_type(i); + +// for (int x = 0; x < desc->length; x++) { +// if (desc->wireformat[x] == +// KNOT_RDATA_WF_UNCOMPRESSED_DNAME || +// desc->wireformat[x] == +// KNOT_RDATA_WF_COMPRESSED_DNAME || +// desc->wireformat[x] == +// KNOT_RDATA_WF_LITERAL_DNAME) { +// knot_dname_free(&(rdata->items[x].dname)); +// } +// } +// knot_rdata_free(&rdata); +// } + +// return (errors == 0); +//} + +/*----------------------------------------------------------------------------*/ + +//static int test_rdata_to_wire() +//{ +// knot_rdata_t *rdata; +// int errors = 0; + +// // generate some random data +// uint8_t data[KNOT_MAX_RDATA_WIRE_SIZE]; +// uint8_t data_wire[KNOT_MAX_RDATA_WIRE_SIZE]; +// uint8_t rdata_wire[KNOT_MAX_RDATA_WIRE_SIZE]; +// generate_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE); + +// for (int i = 0; i <= KNOT_RRTYPE_LAST; ++i) { +// rdata = knot_rdata_new(); + +// int size = +// fill_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE, i, rdata); + +// int size_expected = +// convert_to_wire(data, KNOT_MAX_RDATA_WIRE_SIZE, i, +// data_wire); + +// if (size < 0) { +// ++errors; +// } else { +// if (size != size_expected) { +// diag("Wire format size (%u) not" +// " as expected (%u)", +// size, size_expected); +// ++errors; +// } else { +// if (knot_rdata_to_wire(rdata, +// knot_rrtype_descriptor_by_type(i)-> +// wireformat, rdata_wire, +// KNOT_MAX_RDATA_WIRE_SIZE) != 0) { +// diag("Error while converting RDATA" +// " to wire format."); +// ++errors; +// } else { +// if (strncmp((char *)data_wire, +// (char *)rdata_wire, size) +// != 0) { +// diag("RDATA converted to wire" +// "format does not match" +// " the expected value"); +// ++errors; +// } +// } +// } +// } + +// knot_rrtype_descriptor_t *desc = +// knot_rrtype_descriptor_by_type(i); + +// for (int x = 0; x < desc->length; x++) { +// if (desc->wireformat[x] == +// KNOT_RDATA_WF_UNCOMPRESSED_DNAME || +// desc->wireformat[x] == +// KNOT_RDATA_WF_COMPRESSED_DNAME || +// desc->wireformat[x] == +// KNOT_RDATA_WF_LITERAL_DNAME) { +// knot_dname_free(&(rdata->items[x].dname)); +// } +// } +// knot_rdata_free(&rdata); +// } + +// return (errors == 0); +//} + +static int test_rdata_free() +{ + return 0; +// knot_rdata_t *tmp_rdata; + +// tmp_rdata = knot_rdata_new(); + +// knot_rdata_free(&tmp_rdata); + +// return (tmp_rdata == NULL); +} +/* Can't test this with current implementation + * would be trying to free pointers on stack */ +static int test_rdata_deep_free() +{ + return 0; + +/* int errors = 0; + + knot_rdata_t *tmp_rdata; + + uint8_t data[KNOT_MAX_RDATA_WIRE_SIZE]; + + for (int i = 0; i <= KNOT_RRTYPE_LAST; i++) { + tmp_rdata = knot_rdata_new(); + + fill_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE, i, tmp_rdata); + + knot_rdata_deep_free(&tmp_rdata, i, 0); + errors += (tmp_rdata != NULL); + } + + return (errors == 0); */ +} + +/*----------------------------------------------------------------------------*/ + +static const int KNOT_RDATA_TEST_COUNT = 8; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_rdata_tests_count(int argc, char *argv[]) +{ + return KNOT_RDATA_TEST_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_rdata_tests_run(int argc, char *argv[]) +{ + int res = 0, + res_final = 1; + + res = test_rdata_create(); + ok(res, "rdata: create empty"); + res_final *= res; + + skip(!res, 6); + + todo(); + + ok(res = test_rdata_delete(), "rdata: delete"); + //res_final *= res; + + endtodo; + + ok(res = test_rdata_get_item(), "rdata: get item"); + res_final *= res; + + skip(!res, 4) + + ok(res = test_rdata_set_items(), "rdata: set items all at once"); + res_final *= res; + + skip(!res, 3); + + ok(res = test_rdata_set_item(), "rdata: set items one-by-one"); + res_final *= res; + + ok(res = test_rdata_compare(), "rdata: compare"); + res_final *= res; + +// ok(res = test_rdata_wire_size(), "rdata: wire size"); +// res_final *= res; + +// skip(!res, 1); + +// ok(res = test_rdata_to_wire(), "rdata: to wire"); +// res_final *= res; + +// endskip; /* test_rdata_wire_size() failed */ + + endskip; /* test_rdata_set_items() failed */ + + endskip; /* test_rdata_get_item() failed */ + + endskip; /* test_rdata_create() failed */ + + todo(); + + ok(res = test_rdata_deep_free(), "rdata: deep free"); + res_final *= res; + + ok(res = test_rdata_free(), "rdata: free"); + res_final *= res; + + endtodo; + + return res_final; +} diff --git a/src/tests/libknot/libknot/rdata_tests.h b/src/tests/libknot/libknot/rdata_tests.h new file mode 100644 index 0000000..1f43c91 --- /dev/null +++ b/src/tests/libknot/libknot/rdata_tests.h @@ -0,0 +1,52 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file rdata_tests.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * Contains unit tests for RDATA (knot_rdata_t) and RDATA item + * (knot_rdata_item_t) structures. + * + * Contains tests for: + * - creating empty RDATA structure with or without reserved space. + * - setting RDATA items one-by-one + * - setting RDATA items all at once + * + * As for now, the tests use several (TEST_RDATAS) RDATA structures, each + * with different number of RDATA items (given by test_rdatas). These are all + * initialized to pointers derived from RDATA_ITEM_PTR (first is RDATA_ITEM_PTR, + * second RDATA_ITEM_PTR + 1, etc.). The functions only test if the pointer + * is set properly. + * + * \todo It may be better to test also some RDATAs with predefined contents, + * such as some numbers, some domain name, etc. For this purpose, we'd + * need RDATA descriptors (telling the types of each RDATA item within an + * RDATA). + * + * \todo It will be fine to test all possible output values of all functions, + * e.g. test whether knot_rdata_get_item() returns NULL when passed an + * illegal position, etc. + */ +#ifndef _KNOTD_RDATA_TESTS_H_ +#define _KNOTD_RDATA_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api rdata_tests_api; + +#endif /* _KNOTD_RDATA_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/response_tests.c b/src/tests/libknot/libknot/response_tests.c new file mode 100644 index 0000000..93cf2df --- /dev/null +++ b/src/tests/libknot/libknot/response_tests.c @@ -0,0 +1,450 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <inttypes.h> + +//#define RESP_TEST_DEBUG +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" +#include "tests/libknot/libknot/response_tests.h" +#include "common/lists.h" +#include "libknot/common.h" +#include "libknot/util/error.h" +#include "libknot/packet/response.h" +#include "libknot/rdata.h" +#include "libknot/rrset.h" +#include "libknot/dname.h" +#include "libknot/util/wire.h" +#include "libknot/util/descriptor.h" +#include "libknot/edns.h" + +#ifdef TEST_WITH_LDNS +#include "ldns/ldns.h" +#endif + +static int knot_response_tests_count(int argc, char *argv[]); +static int knot_response_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api response_tests_api = { + "DNS library - response", //! Unit name + &knot_response_tests_count, //! Count scheduled tests + &knot_response_tests_run //! Run scheduled tests +}; + +static int test_response_init() +{ + int errors = 0; + int lived = 0; + lives_ok({ + if (knot_response_init(NULL) != KNOT_EBADARG) { + diag("Calling response_init with NULL packet did " + "not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + }, "response: init NULL tests"); + errors += lived != 1; + + knot_packet_t *response = + knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + assert(response); + response->max_size = KNOT_WIRE_HEADER_SIZE - 1; + if (knot_response_init(response) != KNOT_ESPACE) { + diag("Calling response_init too small packet did " + "not return KNOT_ESPACE!"); + errors++; + } + + return (errors == 0); +} + +static int test_response_init_query() +{ + int errors = 0; + int lived = 0; + lives_ok({ + if (knot_response_init_from_query(NULL, NULL) != + KNOT_EBADARG) { + diag("Calling response_init_query with NULL packet and " + "NULL query did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + knot_packet_t *response = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(response); + knot_packet_set_max_size(response, + KNOT_PACKET_PREALLOC_RESPONSE); + knot_response_init(response); + lived = 0; + if (knot_response_init_from_query(response, NULL) != + KNOT_EBADARG) { + diag("Calling response_init_query with NULL query " + "did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + knot_packet_t *query = + knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + if (knot_response_init_from_query(NULL, query) != + KNOT_EBADARG) { + diag("Calling response_init_query with NULL response " + "did not return KNOT_EBADARG!"); + errors++; + } + }, "response: init from query NULL tests"); + errors += lived != 1; + + /* Cannot test the rest of return values, since there is now constant + * controlling value that could return KNOT_EDNAMEPTR */ + + return (errors == 0); +} + +int compare_wires_simple(uint8_t *wire1, uint8_t *wire2, uint count) +{ + int i = 0; + while (i < count && + wire1[i] == wire2[i]) { + i++; + } + return (!(count == i)); +} + + +//static int test_response_clear() +//{ +// int errors = 0; +// int lived = 0; +// lives_ok({ +// knot_response_clear(NULL, 1); +// lived = 1; +// }, "response: clear NULL tests"); +// errors += lived != 1; + +// /* +// * Create new response, convert to wire, then add something, clear +// * the response, convert to wire again and compare wires. +// */ + +// knot_packet_t *response = +// knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); +// knot_packet_set_max_size(response, KNOT_WIRE_HEADER_SIZE * 100); +// assert(knot_response_init(response) == KNOT_EOK); + +// uint8_t *original_wire = NULL; +// size_t original_size = 0; +// assert(knot_packet_to_wire(response, &original_wire, +// &original_size) == +// KNOT_EOK); +// /* Do something in question section. */ +//// test_dname_t test_dname; +//// test_dname.str = "ns8.nic.cz."; +//// knot_dname_t *dname = dname_from_test_dname_str(&test_dname); +//// assert(dname); + +// response->question.qtype = KNOT_RRTYPE_HINFO; +// response->question.qclass = KNOT_CLASS_CH; + +// uint8_t *question_changed_wire = NULL; +// size_t question_changed_size = 0; +// assert(knot_packet_to_wire(response, +// &question_changed_wire, +// &question_changed_size) == +// KNOT_EOK); + +// knot_response_set_aa(response); +// knot_response_set_tc(response); +// knot_response_set_rcode(response, knot_quick_rand()); + +// knot_response_clear(response, 0); +// uint8_t *new_wire = NULL; +// size_t new_size = 0; +// assert(knot_packet_to_wire(response, &new_wire, &new_size) == +// KNOT_EOK); +// if (question_changed_size != new_size) { +// diag("Wrong wire size after calling response_clear! " +// "got %d should be %d", new_size, question_changed_size); +// errors++; +// } else { +// if (compare_wires_simple(question_changed_wire, +// new_wire, new_size)) { +// diag("Wrong wire after calling response_clear! "); +// errors++; +// } +// } +// free(new_wire); + +// new_wire = NULL; +// new_size = 0; + +// /*!< \todo figure out this segfault! */ + +//// knot_response_clear(response, 1); +//// assert(knot_packet_to_wire(response, &new_wire, &new_size) == +//// KNOT_EOK); + +//// if (original_size != new_size) { +//// diag("Wrong wire size after calling response_clear!"); +//// errors++; +//// } else { +//// if (compare_wires_simple(original_wire, +//// new_wire, new_size)) { +//// diag("Wrong wire after calling response_clear!"); +//// errors++; +//// } +//// } + +//// free(new_wire); +//// free(original_wire); +//// free(question_changed_wire); +//// knot_packet_free(&response); + +// return (errors == 0); +//} + +static int test_response_add_opt() +{ + int errors = 0; + int lived = 0; + + knot_opt_rr_t opt; + opt.payload = 512; + opt.ext_rcode = 0; + opt.version = EDNS_VERSION_0; + opt.flags = 0; + opt.options = NULL; + opt.option_count = 0; + opt.options_max = 0; + opt.size = 25; // does it matter? + + lives_ok({ + if (knot_response_add_opt(NULL, NULL, 0) != KNOT_EBADARG) { + diag("Calling response add opt with NULL arguments " + "did not result to KNOT_EBADARG"); + errors++; + } + lived = 1; + knot_packet_t *response = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(response); + lived = 0; + if (knot_response_add_opt(response, + NULL, 0) != KNOT_EBADARG) { + diag("Calling response add opt with NULL OPT RR " + "did not result to KNOT_EBADARG"); + errors++; + } + lived = 1; + + lived = 0; + if (knot_response_add_opt(NULL, + &opt, 0) != KNOT_EBADARG) { + diag("Calling response add opt with NULL response " + "did not result to KNOT_EBADARG"); + errors++; + } + lived = 1; + knot_packet_free(&response); + }, "response: add opt NULL tests"); + errors += lived != 1; + + knot_packet_t *response = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(response); + knot_packet_set_max_size(response, KNOT_PACKET_PREALLOC_RESPONSE * 100); + assert(knot_response_init(response) == KNOT_EOK);; + + if (knot_response_add_opt(response, &opt, 0) != KNOT_EOK) { + diag("Adding valid OPT RR to response " + "did not return KNOT_EOK"); + errors++; + } + + opt.payload = response->max_size + 1; + if (knot_response_add_opt(response, &opt, 1) != KNOT_EPAYLOAD) { + diag("If OPT RR payload is bigger than response max size " + "response_add_opt does not return KNOT_EPAYLOAD!"); + errors++; + } + + opt.payload = 0; + if (knot_response_add_opt(response, &opt, 1) != KNOT_EBADARG) { + diag("Calling response_add_opt with OPT RR payload set to 0 " + "did not return KNOT_EBADARG"); + } + + knot_packet_free(&response); + return (errors == 0); +} + +static int test_response_add_generic(int (*func)(knot_packet_t *, + const knot_rrset_t *, + int, int, int)) +{ + int errors = 0; + int lived = 0; + + lives_ok({ + if (func(NULL, NULL, 0, 0, 0) != KNOT_EBADARG) { + diag("Calling response add rrset with NULL " + "arguments did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + knot_packet_t *response = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(response); + lived = 0; + if (func(response, NULL, 0, 0, 0) != KNOT_EBADARG) { + diag("Calling response add rrset with NULL rrset " + "did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + knot_dname_t *owner = + knot_dname_new_from_str("ns.nic.cz.", + strlen("ns.nic.cz."), + NULL); + assert(owner); + knot_rrset_t *rrset = + knot_rrset_new(owner, KNOT_RRTYPE_A, + KNOT_CLASS_IN, 3600); + assert(rrset); + lived = 0; + if (func(NULL, rrset, 0, 0, 0) != KNOT_EBADARG) { + diag("Calling response add rrset with NULL response " + "did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + knot_rrset_deep_free(&rrset, 1, 0, 0); + knot_packet_free(&response); + }, "response: rrset adding NULL tests"); + errors += lived != 1; + + /*!< \todo Test case when KNOT_ESPACE should be returned. */ + /*!< \todo Compression and so on - should it be tested here? */ + + knot_packet_t *response = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(response); + + knot_dname_t *owner = + knot_dname_new_from_str("ns12.nic.cz.", + strlen("ns12.nic.cz."), + NULL); + assert(owner); + knot_rrset_t *rrset = + knot_rrset_new(owner, KNOT_RRTYPE_NS, + KNOT_CLASS_IN, 3600); + assert(rrset); + if (func(response, rrset, 0, 0, 0) != KNOT_EOK) { + diag("Adding valid RRSet to response did not result to " + "KNOT_EOK"); + errors++; + } + + knot_rrset_deep_free(&rrset, 1, 0, 0); + knot_packet_free(&response); + + return (errors == 0); +} + +static void test_response_add_rrset() +{ + ok(test_response_add_generic(knot_response_add_rrset_answer), + "response: add answer rrset"); + ok(test_response_add_generic(knot_response_add_rrset_authority), + "response: add answer authority"); + ok(test_response_add_generic(knot_response_add_rrset_additional), + "response: add answer additional"); +} + +static int test_response_add_nsid() +{ + int errors = 0; + int lived = 0; + + knot_packet_t *response = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(response); + + uint8_t *nsid = (uint8_t *)"knotDNS"; + uint16_t nsid_size = strlen((char *)nsid); + lives_ok({ + if (knot_response_add_nsid(NULL, + NULL, 1) != KNOT_EBADARG) { + diag("Calling response add nsid with NULL arguments " + "did not return KNOT_EBADARG"); + errors++; + } + lived = 1; + + lived = 0; + if (knot_response_add_nsid(NULL, nsid, + nsid_size) != KNOT_EBADARG) { + diag("Calling response add nsid with NULL response " + "did not return KNOT_EBADARG"); + errors++; + } + lived = 1; +// lived = 0; +// if (knot_response_add_nsid(response, nsid, +// 0) != KNOT_EBADARG) { +// diag("Calling response add nsid with zero size " +// "did not return KNOT_EBADARG"); +// errors++; +// } +// lived = 1; + }, "response: add nsid NULL tests"); + errors += lived != 1; + + if (knot_response_add_nsid(response, nsid, + nsid_size) != KNOT_EOK) { + diag("Adding valid nsid to response did not return KNOT_EOK"); + errors++; + } + + knot_packet_free(&response); + return (errors == 0); +} + +static const int KNOT_response_TEST_COUNT = 14; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_response_tests_count(int argc, char *argv[]) +{ + return KNOT_response_TEST_COUNT; +} + + +/*! Run all scheduled tests for given parameters. + */ +static int knot_response_tests_run(int argc, char *argv[]) +{ + ok(test_response_init(), "response: init"); + ok(test_response_init_query(), "response: init from query"); +// ok(test_response_clear(), "response: clear"); + ok(test_response_add_opt(), "response: add opt"); + test_response_add_rrset(); + ok(test_response_add_nsid(), "response: add nsid"); + return 1; +} diff --git a/src/tests/libknot/libknot/response_tests.h b/src/tests/libknot/libknot/response_tests.h new file mode 100644 index 0000000..c9a117b --- /dev/null +++ b/src/tests/libknot/libknot/response_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_response_TESTS_H_ +#define _KNOTD_response_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api response_tests_api; + +#endif /* _KNOTD_response_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/rrset_tests.c b/src/tests/libknot/libknot/rrset_tests.c new file mode 100644 index 0000000..fa75195 --- /dev/null +++ b/src/tests/libknot/libknot/rrset_tests.c @@ -0,0 +1,888 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "tests/libknot/libknot/rrset_tests.h" +#include "libknot/common.h" +#include "libknot/util/descriptor.h" +#include "libknot/rrset.h" +#include "libknot/dname.h" +#include "libknot/rdata.h" +#include "libknot/util/utils.h" +#include "libknot/zone/node.h" +#include "libknot/util/debug.h" + +static int knot_rrset_tests_count(int argc, char *argv[]); +static int knot_rrset_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api rrset_tests_api = { + "DNS library - rrset", //! Unit name + &knot_rrset_tests_count, //! Count scheduled tests + &knot_rrset_tests_run //! Run scheduled tests +}; + +/*----------------------------------------------------------------------------*/ +/* + * Unit implementation. + */ + +static knot_node_t *NODE_ADDRESS = (knot_node_t *)0xDEADBEEF; + +enum { TEST_RRSETS = 6 , TEST_RRSIGS = 6}; + +//void *RRSIG_ADDRESS = (void *)0xDEADBEEF; +//void *RRSIG_FIRST = RRSIG_ADDRESS + 10; + +struct test_domain { + char *str; + char *wire; + uint size; + char *labels; + short label_count; +}; + +struct test_rrset { + char *owner; + uint16_t type; + uint16_t rclass; + uint32_t ttl; + knot_rdata_t *rdata; + const knot_rrset_t *rrsigs; +}; + +/* this has to changed */ +static const char *signature_strings[TEST_RRSIGS] = +{"signature 1", "signature 2", "signature 3", + "signature 4", "signature 5", "signature 6"}; + +enum { + RR_DNAMES_COUNT = 3, + RR_ITEMS_COUNT = 3, + RR_RDATA_COUNT = 4, +}; + +enum { TEST_DOMAINS_OK = 8 }; + +static knot_dname_t RR_DNAMES[RR_DNAMES_COUNT] = + { {{}, (uint8_t *)"\7example\3com", 13, NULL}, //0's at the end are added + {{}, (uint8_t *)"\3ns1\7example\3com", 17, NULL}, + {{}, (uint8_t *)"\3ns2\7example\3com", 17, NULL} }; + +/* 192.168.1.1 */ +static uint8_t address[4] = {0xc0, 0xa8, 0x01, 0x01}; + +static knot_rdata_item_t RR_ITEMS[RR_ITEMS_COUNT] = + { {.dname = &RR_DNAMES[1]}, + {.dname = &RR_DNAMES[2]}, + {.raw_data = (uint16_t *)address} }; + +/*! \warning Do not change the order. */ +/* TODO this does not work as expected */ +static knot_rdata_t RR_RDATA[RR_RDATA_COUNT] = + { {&RR_ITEMS[0], 1, &RR_RDATA[0]}, /* first ns */ + {&RR_ITEMS[1], 1, &RR_RDATA[1]}, /* second ns */ + {&RR_ITEMS[0], 1, &RR_RDATA[3]}, /* both in cyclic list */ + {&RR_ITEMS[1], 1, &RR_RDATA[2]} }; + +/*! \warning Do not change the order in those, if you want to test some other + * feature with new dname, add it at the end of these arrays. + */ +static const struct test_domain + test_domains_ok[TEST_DOMAINS_OK] = { + { "abc.test.domain.com.", "\3abc\4test\6domain\3com", 21, + "\x0\x4\x9\x10", 4 }, + { "some.test.domain.com.", "\4some\4test\6domain\3com", 22, + "\x0\x5\xA\x11", 4 }, + { "xyz.test.domain.com.", "\3xyz\4test\6domain\3com", 21, + "\x0\x4\x9\x10", 4 }, + { "some.test.domain.com.", "\4some\4test\6domain\3com", 22, + "\x0\x5\xA\x11", 4 }, + { "test.domain.com.", "\4test\6domain\3com", 17, + "\x0\x5\xC", 3 }, + { ".", "\0", 1, + "", 0 }, + { "foo.bar.net.", "\3foo\3bar\3net", 13, + "\x0\x4\x8", 3}, + { "bar.net.", "\3bar\3net", 9, + "\x0\x4", 2} +}; + +static struct test_rrset test_rrsets[TEST_RRSETS] = { + { "example.com.", KNOT_RRTYPE_NS, KNOT_CLASS_IN, + 3600, NULL, NULL }, + { "example2.com.", KNOT_RRTYPE_NS, KNOT_CLASS_IN, + 3600, NULL, NULL }, + { "example3.com.", KNOT_RRTYPE_NS, KNOT_CLASS_IN, + 3600, NULL, NULL }, + { "example.com.", KNOT_RRTYPE_NS, KNOT_CLASS_IN, + 3600, NULL, NULL }, + { "example.com.", KNOT_RRTYPE_NS, KNOT_CLASS_IN, + 3600, NULL, NULL }, + { "example.com.", KNOT_RRTYPE_NS, KNOT_CLASS_IN, + 3600, NULL, NULL } +}; + +static const struct test_rrset test_rrsigs[TEST_RRSIGS] = { + { "example.com.", 46, 1, 3600, NULL }, + { "example2.com.", 46, 1, 3600, NULL }, + { "example3.com.", 46, 1, 3600, NULL }, + { "example4.com.", 46, 1, 3600, NULL }, + { "example5.com.", 46, 1, 3600, NULL }, + { "example6.com.", 46, 1, 3600, NULL } +}; + +static void generate_rdata(uint8_t *data, int size) +{ + for (int i = 0; i < size; ++i) { + data[i] = rand() % 256; + } +} + +static int fill_rdata_r(uint8_t *data, int max_size, uint16_t rrtype, + knot_rdata_t *rdata) +{ + assert(rdata != NULL); + assert(data != NULL); + assert(max_size > 0); + + uint8_t *pos = data; + int used = 0; + int wire_size = 0; + +// note("Filling RRType %u", rrtype); + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrtype); + + uint item_count = desc->length; + knot_rdata_item_t *items = + (knot_rdata_item_t *)malloc(item_count + * sizeof(knot_rdata_item_t)); + + for (int i = 0; i < item_count; ++i) { + uint size = 0; + int domain = 0; + knot_dname_t *dname = NULL; + int binary = 0; + int stored_size = 0; + + switch (desc->wireformat[i]) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: + dname = knot_dname_new_from_wire( + (uint8_t *)test_domains_ok[0].wire, + test_domains_ok[0].size, NULL); + assert(dname != NULL); +// note("Created domain name: %s", +// knot_dname_name(dname)); +// note("Domain name ptr: %p", dname); + domain = 1; + size = knot_dname_size(dname); +// note("Size of created domain name: %u", size); + assert(size < KNOT_MAX_RDATA_ITEM_SIZE); + // store size of the domain name + *(pos++) = size; + // copy the domain name + memcpy(pos, knot_dname_name(dname), size); + pos += size; + break; + default: + binary = 1; + size = rand() % KNOT_MAX_RDATA_ITEM_SIZE; + } + + if (binary) { + // Rewrite the actual 2 bytes in the data array + // with length. + // (this is a bit ugly, but does the work ;-) + knot_wire_write_u16(pos, size); + //*pos = size; + } + + //note("Filling %u bytes", size); + used += size; + assert(used < max_size); + + if (domain) { + items[i].dname = dname; + wire_size += knot_dname_size(dname); +/* note("Saved domain name ptr on index %d: %p", + i, items[i].dname); */ + } else { + free(dname); +// note("Saved raw data ptr on index %d: %p",i, pos); + items[i].raw_data = (uint16_t *)pos; + pos += size; + wire_size += size; + if (binary && !stored_size) { + wire_size -= 2; + } + } + } + + int res = knot_rdata_set_items(rdata, items, item_count); + if (res != 0) { + diag("knot_rdata_set_items() returned %d.", res); + free(items); + return -1; + } else { + free(items); + return wire_size; + } +} + +/* fills test_rrsets with random rdata when empty */ +static void create_rdata() +{ + knot_rdata_t *r; + + uint8_t *data = + malloc(sizeof(uint8_t) * KNOT_MAX_RDATA_WIRE_SIZE); + + assert(data); + + for (int i = 0; i < TEST_RRSETS; i++) { + if (test_rrsets[i].rdata == NULL) { + r = knot_rdata_new(); + + /* from rdata tests */ + generate_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE); + if (fill_rdata_r(data, KNOT_MAX_RDATA_WIRE_SIZE, + test_rrsets[i].type, r) <= 0) { + diag("Error creating rdata!"); + + } + + test_rrsets[i].rdata = r; + } + } + + free(data); +} + +static int check_rrset(const knot_rrset_t *rrset, int i, + int check_rdata, int check_items, + int check_rrsigs) +{ + /* following implementation should be self-explanatory */ + int errors = 0; + + if (rrset == NULL) { + diag("RRSet not created!"); + return 1; + } + + char *owner = knot_dname_to_str(rrset->owner); + if (strcmp(owner, test_rrsets[i].owner) != 0) { + diag("OWNER domain name wrong: '%s' (should be '%s')", + owner, test_rrsets[i].owner); + ++errors; + } + free(owner); + + if (rrset->type != test_rrsets[i].type) { + diag("TYPE wrong: %u (should be: %u)", rrset->type, + test_rrsets[i].type); + ++errors; + } + + if (rrset->rclass != test_rrsets[i].rclass) { + diag("CLASS wrong: %u (should be: %u)", rrset->rclass, + test_rrsets[i].rclass); + ++errors; + } + + if (rrset->ttl != test_rrsets[i].ttl) { + diag("TTL wrong: %u (should be: %u)", rrset->ttl, + test_rrsets[i].ttl); + ++errors; + } + + if (check_rdata) { + /* TODO use rdata_compare */ + knot_rdata_t *rdata = rrset->rdata; + + if (rdata == NULL) { + diag("There are no RDATAs in the RRSet"); + ++errors; + } + + if (rdata != NULL) { + while (rdata->next != NULL && + rdata->next != rrset->rdata) { + rdata = rdata->next; + } + if (rdata->next == NULL) { + diag("The list of RDATAs is not cyclic!"); + ++errors; + } else { + assert(rdata->next == rrset->rdata); + } + } + } + + if (check_items) { + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrset->type); + if (knot_rdata_compare(rrset->rdata, + test_rrsets[i].rdata, + desc->wireformat) != 0) { + diag("Rdata items do not match."); + errors++; + } + } + + /* TODO this deserves a major improvement!!! */ + + /* + * Will work only with null terminated strings, + * consider changing to more versatile implementation + */ + + /* How about, once it's tested, using rdata_compare */ + + if (check_rrsigs) { + + const knot_rrset_t *rrsigs; + + rrsigs = knot_rrset_rrsigs(rrset); + if (strcmp((const char *)rrsigs->rdata->items[0].raw_data, + signature_strings[i])) { + diag("Signatures are not equal" + "to those set when creating." + "Comparing %s with %s", + rrsigs->rdata->items[0].raw_data, + signature_strings[i]); + errors++; + } + } + return errors; +} + +static int test_rrset_create() +{ + int errors = 0; + + for (int i = 0; i < TEST_RRSETS; ++i) { + knot_dname_t *owner = knot_dname_new_from_str( + test_rrsets[i].owner, + strlen(test_rrsets[i].owner), + NODE_ADDRESS); + if (owner == NULL) { + diag("Error creating owner domain name!"); + return 0; + } + knot_rrset_t *rrset = knot_rrset_new(owner, + test_rrsets[i].type, + test_rrsets[i].rclass, + test_rrsets[i].ttl); + + errors += check_rrset(rrset, i, 0, 0, 0); + + knot_rrset_free(&rrset); + knot_dname_free(&owner); + } + + //diag("Total errors: %d", errors); + + return (errors == 0); +} + +/* Not implemented - no way how to test unfreed memory from here (yet) */ +static int test_rrset_delete() +{ + return 0; +} + +static int test_rrset_add_rdata() +{ + /* rdata add */ + int errors = 0; + for (int i = 0; i < TEST_RRSETS; i++) { + knot_dname_t *owner = knot_dname_new_from_str( + test_rrsets[i].owner, + strlen(test_rrsets[i].owner), + NODE_ADDRESS); + if (owner == NULL) { + diag("Error creating owner domain name!"); + return 0; + } + + knot_rrset_t *rrset = knot_rrset_new(owner, + test_rrsets[i].type, + test_rrsets[i].rclass, + test_rrsets[i].ttl); + + knot_rrset_add_rdata(rrset, test_rrsets[i].rdata); + + errors += check_rrset(rrset, i, 1, 0, 0); + + knot_rrset_free(&rrset); + knot_dname_free(&owner); + } + + /* test whether adding works properly = keeps order of added elements */ + + /* + * Beware, this is dependent on the internal structure of rrset and + * may change. + */ + + knot_rrset_t *rrset = knot_rrset_new(NULL, 0, 0, 0); + + knot_rdata_t *r; + + knot_rdata_item_t *item; + + static const char *test_strings[10] = + { "-2", "9", "2", "10", "1", "5", "8", "4", "6", "7" }; + + /* add items */ + + for (int i = 0; i < 10; i++) { + r = knot_rdata_new(); + item = malloc(sizeof(knot_rdata_item_t)); + item->raw_data = (uint16_t *)test_strings[i]; + //following statement creates a copy + knot_rdata_set_items(r, item, 1); + knot_rrset_add_rdata(rrset, r); + free(item); + } + + knot_rdata_t *tmp = rrset->rdata; + + /* check if order has been kept */ + + int i = 0; + while (tmp->next != rrset->rdata && !errors) { + if (strcmp(test_strings[i], (char *)tmp->items[0].raw_data)) { + diag("Adding RDATA error!, is %s should be %s", + tmp->items[0].raw_data, test_strings[i]); + errors++; + } + i++; + tmp = tmp->next; + + } + + tmp = rrset->rdata; + + knot_rdata_t *next; + + while (tmp->next != rrset->rdata) { + next = tmp->next; + knot_rdata_free(&tmp); + tmp = next; + } + + knot_rdata_free(&tmp); + + knot_rrset_free(&rrset); + + return (errors == 0); +} + +static int test_rrset_rrsigs() +{ + int errors = 0; + + knot_rdata_item_t *item; + + knot_rdata_t *tmp; + + knot_dname_t *owner; + + knot_rrset_t *rrset; + + /* Gets rrsigs and checks, if signatures are the same */ + + for (int i = 0; i < TEST_RRSETS; i++) { + owner = knot_dname_new_from_str(test_rrsets[i].owner, + strlen(test_rrsets[i].owner), NODE_ADDRESS); + if (owner == NULL) { + diag("Error creating owner domain name!"); + return 0; + } + + rrset = knot_rrset_new(owner, test_rrsets[i].type, + test_rrsets[i].rclass, test_rrsets[i].ttl); + + knot_rrset_add_rdata(rrset, test_rrsets[i].rdata); + + //owners are the same + + assert(TEST_RRSETS == TEST_RRSIGS); + + knot_rrset_t *rrsig = knot_rrset_new(owner, + test_rrsigs[i].type, + test_rrsigs[i].rclass, + test_rrsigs[i].ttl); + + tmp = knot_rdata_new(); + item = malloc(sizeof(knot_rdata_item_t)); + /* signature is just a string, + * should be sufficient for testing */ + item->raw_data = (uint16_t *)signature_strings[i]; + knot_rdata_set_items(tmp, item, 1); + knot_rrset_add_rdata(rrsig, tmp); + + if (knot_rrset_set_rrsigs(rrset, rrsig) + != 0) { + diag("Could not set rrsig"); + errors++; + } + errors += check_rrset(rrset, i, 0, 0, 1); + knot_rrset_free(&rrset); + free(item); + knot_rdata_free(&tmp); + knot_rrset_free(&rrsig); + } + return (errors == 0); +} + +static int test_rrset_merge() +{ + knot_rrset_t *merger1; + knot_rrset_t *merger2; + knot_dname_t *owner1; + knot_dname_t *owner2; + + int r; + + owner1 = knot_dname_new_from_str(test_rrsets[3].owner, + strlen(test_rrsets[3].owner), NULL); + merger1 = knot_rrset_new(owner1, test_rrsets[3].type, + test_rrsets[3].rclass, + test_rrsets[3].ttl); + + knot_rrset_add_rdata(merger1, test_rrsets[3].rdata); + + owner2 = knot_dname_new_from_str(test_rrsets[4].owner, + strlen(test_rrsets[4].owner), NULL); + merger2 = knot_rrset_new(owner2, test_rrsets[4].type, + test_rrsets[4].rclass, + test_rrsets[4].ttl); + + knot_rrset_add_rdata(merger2, test_rrsets[4].rdata); + +// knot_rrset_dump(merger1, 1); + + int ret = 0; + if ((ret = knot_rrset_merge((void **)&merger1, + (void **)&merger2)) != 0) { + diag("Could not merge rrsets. (reason %d)", ret); + return 0; + } + +// knot_rrset_dump(merger1, 1); + + r = check_rrset(merger1, 5, 1, 1, 0); + + knot_rrset_free(&merger1); + knot_rrset_free(&merger2); + + if (r) { + diag("Merged rdata are wrongly set."); + return 0; + } + + return 1; +} + +static int test_rrset_owner(knot_rrset_t **rrsets) +{ + int errors = 0; + for (int i = 0; i < TEST_RRSETS; i++) { + char *dname_str = + knot_dname_to_str(knot_rrset_owner(rrsets[i])); + if (strcmp(dname_str, test_rrsets[i].owner)) { + diag("Got wrong value for owner from rrset."); + errors++; + } + free(dname_str); + } + return errors; +} + +static int test_rrset_type(knot_rrset_t **rrsets) +{ + int errors = 0; + for (int i = 0; i < TEST_RRSETS; i++) { + if (knot_rrset_type(rrsets[i]) != test_rrsets[i].type) { + errors++; + diag("Got wrong value for type from rrset."); + } + } + return errors; +} + +static int test_rrset_class(knot_rrset_t **rrsets) +{ + int errors = 0; + for (int i = 0; i < TEST_RRSETS; i++) { + if (knot_rrset_class(rrsets[i]) != test_rrsets[i].rclass) { + errors++; + diag("Got wrong value for class from rrset."); + } + } + + return errors; +} + +static int test_rrset_ttl(knot_rrset_t **rrsets) +{ + int errors = 0; + for (int i = 0; i < TEST_RRSETS; i++) { + if (knot_rrset_ttl(rrsets[i]) != test_rrsets[i].ttl) { + errors++; + diag("Got wrong value for ttl from rrset."); + } + } + return errors; +} + +static int test_rrset_ret_rdata(knot_rrset_t **rrsets) +{ + int errors = 0; + + knot_rrtype_descriptor_t *desc; + + for (int i = 0; i < TEST_RRSETS; i++) { + + desc = knot_rrtype_descriptor_by_type(rrsets[i]->type); + assert(desc); + +// knot_rdata_dump(test_rrsets[i].rdata, 1); + // knot_rdata_dump(rrsets[i]->rdata, 1); + + if (knot_rdata_compare(knot_rrset_rdata(rrsets[i]), + test_rrsets[i].rdata, + desc->wireformat)) { + errors++; + diag("Got wrong value for rdata from rrset."); + } + } + return errors; +} + +static int test_rrset_get_rdata(knot_rrset_t **rrsets) +{ + int errors = 0; + + knot_rrtype_descriptor_t *desc; + + for (int i = 0; i < TEST_RRSETS; i++) { + desc = knot_rrtype_descriptor_by_type(rrsets[i]->type); + assert(desc); + if (knot_rdata_compare(knot_rrset_get_rdata(rrsets[i]), + test_rrsets[i].rdata, + desc->wireformat)) { + errors++; + diag("Got wrong value for rdata from rrset. (Get)"); + } + } + return errors; +} + +static int test_rrset_ret_rrsigs(knot_rrset_t **rrsets) +{ + int errors = 0; + + for (int i = 0; i < TEST_RRSETS; i++) { + /* TODO should I test the insides of structure as well? */ + if (knot_rrset_rrsigs(rrsets[i]) != test_rrsets[i].rrsigs) { + errors++; + diag("Got wrong value for rrsigs from rrset."); + } + } + return errors; +} + +static int test_rrset_getters(uint type) +{ + int errors = 0; + + knot_rrset_t *rrsets[TEST_RRSETS]; + + for (int i = 0; i < TEST_RRSETS; i++) { + knot_dname_t *owner = knot_dname_new_from_str( + test_rrsets[i].owner, + strlen(test_rrsets[i].owner), + NODE_ADDRESS); + if (owner == NULL) { + diag("Error creating owner domain name!"); + return 0; + } + rrsets[i] = knot_rrset_new(owner, + test_rrsets[i].type, + test_rrsets[i].rclass, + test_rrsets[i].ttl); + + knot_rrset_add_rdata(rrsets[i], test_rrsets[i].rdata); + } + + switch (type) { + case 0: { + errors += test_rrset_owner(rrsets); + break; + } + case 1: { + errors += test_rrset_type(rrsets); + break; + } + case 2: { + errors += test_rrset_class(rrsets); + break; + } + case 3: { + errors += test_rrset_ttl(rrsets); + break; + } + case 4: { + errors += test_rrset_ret_rdata(rrsets); + break; + } + case 5: { + errors += test_rrset_get_rdata(rrsets); + break; + } + case 6: { + errors += test_rrset_ret_rrsigs(rrsets); + break; + } + } /* switch */ + + for (int i = 0; i < TEST_RRSETS; i++) { + knot_dname_free(&rrsets[i]->owner); + knot_rrset_free(&rrsets[i]); + } + + + return (errors == 0); +} + +static int test_rrset_deep_free() +{ + /*!< \warning Cannot be run when some rdata are on stack! */ + int errors = 0; + + knot_rrset_t *tmp_rrset; + knot_dname_t *owner; + for (int i = 0; i < TEST_RRSETS; i++) { + owner = knot_dname_new_from_str( + test_rrsets[i].owner, + strlen(test_rrsets[i].owner), + NODE_ADDRESS); + if (owner == NULL) { + diag("Error creating owner domain name!"); + return 0; + } + + tmp_rrset = knot_rrset_new(owner, + test_rrsets[i].type, + test_rrsets[i].rclass, + test_rrsets[i].ttl); + + knot_rrset_add_rdata(tmp_rrset, test_rrsets[i].rdata); + + knot_rrset_deep_free(&tmp_rrset, 1, 1, 0); + + errors += (tmp_rrset != NULL); + } + + return (errors == 0); +} + +/*----------------------------------------------------------------------------*/ + +static const int KNOT_RRSET_TEST_COUNT = 13; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_rrset_tests_count(int argc, char *argv[]) +{ + return KNOT_RRSET_TEST_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_rrset_tests_run(int argc, char *argv[]) +{ + int res = 0, + res_final = 1; + +/* for (int i = 0; i < 4; i++) { + knot_rdata_dump(&RR_RDATA[i], 2, 1); + printf("%p %p\n", &RR_RDATA[i], (&RR_RDATA)[i]->next); + } */ + + create_rdata(); + + res = test_rrset_create(); + ok(res, "rrset: create"); + res_final *= res; + + skip(!res, 11); + + todo(); + + ok(res = test_rrset_delete(), "rrset: delete"); + //res_final *= res; + + endtodo; + + ok(res = test_rrset_getters(0), "rrset: owner"); + res_final *= res; + + ok(res = test_rrset_getters(1), "rrset: type"); + res_final *= res; + + ok(res = test_rrset_getters(2), "rrset: class"); + res_final *= res; + + ok(res = test_rrset_getters(3), "rrset: ttl"); + res_final *= res; + + ok(res = test_rrset_getters(4), "rrset: rdata"); + res_final *= res; + + ok(res = test_rrset_getters(5), "rrset: get rdata"); + res_final *= res; + + ok(res = test_rrset_getters(6), "rrset: rrsigs"); + res_final *= res; + + ok(res = test_rrset_add_rdata(), "rrset: add_rdata"); + res_final *= res; + + ok(res = test_rrset_rrsigs(), "rrset: rrsigs manipulation"); + res_final *= res; + + ok(res = test_rrset_merge(), "rrset: rdata merging"); + res_final *= res; + + ok(res = test_rrset_deep_free(), "rrset: deep free"); + res_final *= res; + + endskip; /* !res_create */ + + return res_final; +} diff --git a/src/tests/libknot/libknot/rrset_tests.h b/src/tests/libknot/libknot/rrset_tests.h new file mode 100644 index 0000000..b0787d6 --- /dev/null +++ b/src/tests/libknot/libknot/rrset_tests.h @@ -0,0 +1,34 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/*! + * \file rrset_tests.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * Contains unit tests for RRSet (knot_rrset_t) and its API. + * + * Contains tests for: + * - + */ +#ifndef _KNOTD_RRSET_TESTS_H_ +#define _KNOTD_RRSET_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api rrset_tests_api; + +#endif /* _KNOTD_RRSET_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/zone_tests.c b/src/tests/libknot/libknot/zone_tests.c new file mode 100644 index 0000000..2fdd61a --- /dev/null +++ b/src/tests/libknot/libknot/zone_tests.c @@ -0,0 +1,853 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "tests/libknot/libknot/zone_tests.h" +#include "libknot/common.h" +#include "libknot/zone/dname-table.h" +#include "libknot/zone/zone.h" +#include "libknot/util/error.h" +#include "libknot/zone/node.h" + +static int knot_zone_tests_count(int argc, char *argv[]); +static int knot_zone_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api zone_tests_api = { + "DNS library - zone", //! Unit name + &knot_zone_tests_count, //! Count scheduled tests + &knot_zone_tests_run //! Run scheduled tests +}; + +/* + * Unit implementation. + */ + +enum { TEST_NODES_GOOD = 7, TEST_NODES_BAD = 1, TRAVERSAL_TYPES = 3}; + +struct zone_test_node { + knot_dname_t owner; + knot_node_t *parent; +}; + +static struct zone_test_node test_apex = +{{{}, (uint8_t *)"\3com\0", 5, (uint8_t *)"\x0", 1}, (knot_node_t *)NULL}; + +static struct zone_test_node test_nodes_bad[TEST_NODES_BAD] = { + {{{},(uint8_t *)"\5other\6domain\0", 14, (uint8_t *)"\x0\x6", 2}, + (knot_node_t *)NULL} +}; + +static struct zone_test_node test_nodes_good[TEST_NODES_GOOD] = { + {{{}, (uint8_t *)"\7example\3com\0", 13, (uint8_t *)"\x0\x8", 2}, + (knot_node_t *)NULL}, + {{{}, (uint8_t *)"\3www\7example\3com\0", 17, (uint8_t *)"\x0\x4\xC", 3}, + (knot_node_t *)NULL}, + {{{}, (uint8_t *)"\7another\6domain\3com\0", 20, (uint8_t *)"\x0\x8\xF", 3}, + (knot_node_t *)NULL}, + {{{}, (uint8_t *)"\5mail1\7example\3com\0", 19, (uint8_t *)"\x0\x6\xE", 3}, + (knot_node_t *)NULL}, + {{{}, (uint8_t *)"\5mail2\7example\3com\0", 19, (uint8_t *)"\x0\x6\xE", 3}, + (knot_node_t *)NULL}, + {{{}, (uint8_t *)"\3smb\7example\3com\0", 17, (uint8_t *)"\x0\x4\xC", 3}, + (knot_node_t *)NULL}, + {{{}, (uint8_t *)"\4smtp\7example\3com\0", 18, (uint8_t *)"\x0\x5\xD", 3}, + (knot_node_t *)NULL}, +}; + +static int test_zone_check_node(const knot_node_t *node, + const struct zone_test_node *test_node, + int test_parent) +{ + return (node->owner == &test_node->owner) && + ((test_parent) ? node->parent == test_node->parent : 1); +} + +static int test_zone_create(knot_zone_contents_t **zone) +{ +// knot_dname_t *dname = knot_dname_new_from_wire( +// test_apex.owner.name, test_apex.owner.size, NULL); +// assert(dname); + + knot_node_t *node = knot_node_new(&test_apex.owner, + test_apex.parent, 0); + if (node == NULL) { + diag("zone: Could not create zone apex."); + return 0; + } + + *zone = knot_zone_contents_new(node, 0, 0, NULL); + + if ((*zone) == NULL) { + diag("zone: Failed to create zone."); + knot_node_free(&node, 1, 0); + return 0; + } + + if ((*zone)->apex != node) { + diag("zone: Zone apex not set right."); + knot_node_free(&node, 1, 0); + return 0; + } + + return 1; +} + +static int test_zone_add_node(knot_zone_contents_t *zone, int nsec3) +{ + /* + * NSEC3 nodes are de facto identical to normal nodes, so there is no + * need for separate tests. The only difference is where are they stored + * in the zone structure. + */ + + int errors = 0; + int res = 0; + + //note("Good nodes"); + + for (int i = 0; i < TEST_NODES_GOOD; ++i) { + knot_node_t *node = knot_node_new(&test_nodes_good[i].owner, + test_nodes_good[i].parent, 0); + if (node == NULL) { + diag("zone: Could not create node."); + return 0; + } + + if ((res = ((nsec3) ? knot_zone_contents_add_nsec3_node(zone, node, 0, 0, 0) + : knot_zone_contents_add_node(zone, node, 0, 0, 0))) != 0) { + diag("zone: Failed to insert node into zone (returned" + " %d).", res); + knot_node_free(&node, 0, 0); + ++errors; + } + /* TODO check values in the node as well */ + } + + //note("Bad nodes"); + + for (int i = 0; i < TEST_NODES_BAD; ++i) { + knot_node_t *node = knot_node_new(&test_nodes_bad[i].owner, + test_nodes_bad[i].parent, 0); + if (node == NULL) { + diag("zone: Could not create node."); + return 0; + } + + if ((res = ((nsec3) ? knot_zone_contents_add_nsec3_node(zone, node, 0, 0, 0) + : knot_zone_contents_add_node(zone, node, 0, 0, 0))) != + KNOT_EBADZONE) { + diag("zone: Inserting wrong node did not result in" + "proper return value (%d instead of %d).", res, + KNOT_EBADZONE); + ++errors; + } + knot_node_free(&node, 0, 0); + } + + //note("NULL zone"); + + note("Inserting into NULL zone...\n"); + + knot_node_t *node = knot_node_new(&test_nodes_good[0].owner, + test_nodes_good[0].parent, 0); + if (node == NULL) { + diag("zone: Could not create node."); + return 0; + } + + if ((res = ((nsec3) ? knot_zone_contents_add_nsec3_node(NULL, node, 0, 0, 0) + : knot_zone_contents_add_node(NULL, node, 0, 0, 0))) != KNOT_EBADARG) { + diag("zone: Inserting node to NULL zone did not result in" + "proper return value (%d instead of %d)", res, + KNOT_EBADARG); + ++errors; + } + + knot_node_free(&node, 0, 0); + + //note("NULL node"); + note("Inserting NULL node...\n"); + + if ((res = ((nsec3) ? knot_zone_contents_add_nsec3_node(zone, NULL, 0, 0, 0) + : knot_zone_contents_add_node(zone, NULL, 0, 0, 0))) != KNOT_EBADARG) { + diag("zone: Inserting NULL node to zone did not result in" + "proper return value (%d instead of %d)", res, + KNOT_EBADARG); + ++errors; + } + + if (!nsec3) { + //note("Inserting Apex again...\n"); + + node = knot_node_new(&test_apex.owner, test_apex.parent, 0); + if (node == NULL) { + diag("zone: Could not create node."); + return 0; + } + + //note("Apex again"); + + if ((res = knot_zone_contents_add_node(zone, node, 0, 0, 0)) != + KNOT_EBADZONE) { + diag("zone: Inserting zone apex again did not result in" + "proper return value (%d instead of -2)", + KNOT_EBADZONE); + ++errors; + } + + knot_node_free(&node, 0, 0); + } + + // check if all nodes are inserted + //int nodes = 0; + if (!nsec3 + && !test_zone_check_node(knot_zone_contents_apex(zone), &test_apex, !nsec3)) { + diag("zone: Apex of zone not right."); +// diag("Apex owner: %s (%p), apex parent: %p\n", +// knot_dname_to_str(knot_zone_apex(zone)->owner), +// knot_zone_apex(zone)->owner, +// knot_zone_apex(zone)->parent); +// diag("Should be: owner: %s (%p), parent: %p\n", +// knot_dname_to_str(&test_apex.owner), +// &test_apex.owner, +// test_apex.parent); + ++errors; + } + //++nodes; + for (int i = 0; i < TEST_NODES_GOOD; ++i) { + const knot_node_t *n = ((nsec3) ? knot_zone_contents_find_nsec3_node( + zone, &test_nodes_good[i].owner) : + knot_zone_contents_find_node(zone, &test_nodes_good[i].owner)); + if (n == NULL) { + diag("zone: Missing node with owner %s", + test_nodes_good[i].owner.name); + ++errors; + continue; + } + + if (!test_zone_check_node(n, &test_nodes_good[i], !nsec3)) { + diag("zone: Node does not match: owner: %s (should be " + "%s), parent: %p (should be %p)", + n->owner->name, test_nodes_good[i].owner.name, + n->parent, test_nodes_good[i].parent); + ++errors; + } + //++nodes; + } + + //note("zone: %d nodes in the zone (including apex)", nodes); + + return (errors == 0); +} + +static int test_zone_get_node(knot_zone_contents_t *zone, int nsec3) +{ + int errors = 0; + + for (int i = 0; i < TEST_NODES_GOOD; ++i) { + if (((nsec3) ? knot_zone_contents_get_nsec3_node( + zone, &test_nodes_good[i].owner) + : knot_zone_contents_get_node(zone, &test_nodes_good[i].owner)) + == NULL) { + diag("zone: Node (%s) not found in zone.", + (char *)test_nodes_good[i].owner.name); + ++errors; + } + } + + for (int i = 0; i < TEST_NODES_BAD; ++i) { + if (((nsec3) ? knot_zone_contents_get_nsec3_node( + zone, &test_nodes_bad[i].owner) + : knot_zone_contents_get_node(zone, &test_nodes_bad[i].owner)) + != NULL) { + diag("zone: Node (%s) found in zone even if it should" + "not be there.", + (char *)test_nodes_bad[i].owner.name); + ++errors; + } + } + + if (((nsec3) + ? knot_zone_contents_get_nsec3_node(NULL, &test_nodes_good[0].owner) + : knot_zone_contents_get_node(NULL, &test_nodes_good[0].owner)) != NULL) { + diag("zone: Getting node from NULL zone did not result in" + "proper return value (NULL)"); + ++errors; + } + + if (((nsec3) ? knot_zone_contents_get_nsec3_node(zone, NULL) + : knot_zone_contents_get_node(zone, NULL)) != NULL) { + diag("zone: Getting node with NULL owner from zone did not " + "result in proper return value (NULL)"); + ++errors; + } + + if (!nsec3 && knot_zone_contents_get_node(zone, &test_apex.owner) == NULL) { + diag("zone: Getting zone apex from the zone failed"); + ++errors; + } + + return (errors == 0); +} + +static int test_zone_find_node(knot_zone_contents_t *zone, int nsec3) +{ + int errors = 0; + + for (int i = 0; i < TEST_NODES_GOOD; ++i) { + if (((nsec3) ? knot_zone_contents_find_nsec3_node( + zone, &test_nodes_good[i].owner) + : knot_zone_contents_find_node(zone, &test_nodes_good[i].owner)) + == NULL) { + diag("zone: Node (%s) not found in zone.", + (char *)test_nodes_good[i].owner.name); + ++errors; + } + } + + for (int i = 0; i < TEST_NODES_BAD; ++i) { + if (((nsec3) ? knot_zone_contents_find_nsec3_node( + zone, &test_nodes_bad[i].owner) + : knot_zone_contents_find_node(zone, &test_nodes_bad[i].owner)) + != NULL) { + diag("zone: Node (%s) found in zone even if it should" + "not be there.", + (char *)test_nodes_bad[i].owner.name); + ++errors; + } + } + + if (((nsec3) + ? knot_zone_contents_find_nsec3_node(NULL, &test_nodes_good[0].owner) + : knot_zone_contents_find_node(NULL, &test_nodes_good[0].owner)) != NULL) { + diag("zone: Finding node from NULL zone did not result in" + "proper return value (NULL)"); + ++errors; + } + + if (((nsec3) ? knot_zone_contents_find_nsec3_node(zone, NULL) + : knot_zone_contents_find_node(zone, NULL)) != NULL) { + diag("zone: Finding node with NULL owner from zone did not " + "result in proper return value (NULL)"); + ++errors; + } + + if (!nsec3 && knot_zone_contents_find_node(zone, &test_apex.owner) == NULL) { + diag("zone: Finding zone apex from the zone failed"); + ++errors; + } + + return (errors == 0); +} + +//static void test_zone_destroy_node_from_tree(knot_node_t *node, +// void *data) +//{ +// UNUSED(data); +// knot_node_free(&node, 0); +//} + +/* explained below */ +static size_t node_index = 0; + +/*! \brief + * This function will overwrite parent field in node structure - + * we don't (and can't, with current structures) use it in these tests anyway. + * Since zone structure itself has no count field, only option known to me + * is (sadly) to use a global variable. + */ +static void tmp_apply_function(knot_node_t *node, void *data) +{ + node->parent = (knot_node_t *)node_index; + node_index++; +} + +/* \note Since I am unaware of a way how to get a return value from traversal + * functions, I will use (hopefully for the last time here) global variable + */ + +static int compare_ok = 1; + +static void tmp_compare_function(knot_node_t *node, void *data) +{ + /* node_index will start set to zero */ + if (node->parent != (knot_node_t *)node_index) { + compare_ok = 0; + return; + } else if (!compare_ok) { + diag("Traversal function has partially set values right"); + } + node->parent = NULL; + node_index++; +} + +static int test_zone_tree_apply(knot_zone_contents_t *zone, + int type, int nsec3) +{ + + assert(node_index == 0); + assert(compare_ok == 1); + + int (*traversal_func)(knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, + void *data), + void *data); + + switch (type) { + case 0: { + if (nsec3) { + traversal_func = + &knot_zone_contents_nsec3_apply_postorder; + diag("Testing postorder traversal"); + } else { + traversal_func = + &knot_zone_contents_tree_apply_postorder; + diag("Testing postorder traversal - NSEC3"); + } + break; + } + case 1: { + if (nsec3) { + traversal_func = + &knot_zone_contents_nsec3_apply_inorder; + diag("Testing inorder traversal"); + } else { + traversal_func = + &knot_zone_contents_tree_apply_inorder; + diag("Testing inorder traversal - NSEC3"); + } + break; + } + case 2: { + if (nsec3) { + traversal_func = + &knot_zone_contents_nsec3_apply_inorder_reverse; + diag("Testing inorder reverse traversal"); + } else { + traversal_func = + &knot_zone_contents_tree_apply_inorder_reverse; + diag("Testing inorder reverse " + "traversal - NSEC3"); + } + break; + } + default: { + diag("Unknown traversal function type"); + return 0; + } + } + + /* + * This will iterate through tree and set node->parent field values + * from 0 to number of nodes. + */ + + traversal_func(zone, &tmp_apply_function, NULL); + + node_index = 0; + + /* + * This will check whether the values were set accordingly. + */ + + traversal_func(zone, &tmp_compare_function, NULL); + + int ret = compare_ok; + + compare_ok = 1; + node_index = 0; + + return (ret); +} + +/* Tests all kinds of zone traversals, explainded above */ +static int test_zone_traversals(knot_zone_contents_t *zone) +{ + for (int i = 0; i < TRAVERSAL_TYPES; i++) { + for (int j = 0; j < 2; j++) { + if (!test_zone_tree_apply(zone, i, j)) { + return 0; + } + } + } + return 1; +} + +struct zone_test_param { + /* Times 2 so that we don't have to mess with mallocs. */ + knot_node_t *knot_node_array[TEST_NODES_GOOD * 5]; + knot_dname_t *table_node_array[TEST_NODES_GOOD * 5]; + size_t count; +}; + +static void tree_node_to_array(knot_node_t *node, void *data) +{ + struct zone_test_param *param = (struct zone_test_param *)data; + param->knot_node_array[param->count++] = node; +} + +static void tree_dname_node_to_array(knot_dname_t *node, + void *data) +{ + struct zone_test_param *param = (struct zone_test_param *)data; + param->table_node_array[param->count++] = node; +} + +extern int compare_wires_simple(uint8_t *w1, uint8_t *w2, uint count); +static int test_zone_shallow_copy() +{ + int errors = 0; + int lived = 0; + knot_dname_t *apex_dname = + knot_dname_new_from_str("a.ns.nic.cz.", + strlen("a.ns.nic.cz"), NULL); + assert(apex_dname); + knot_node_t *apex_node = + knot_node_new(apex_dname, NULL, 0); + assert(apex_node); + lives_ok({ + if (knot_zone_contents_shallow_copy(NULL, NULL) != KNOT_EBADARG) { + diag("Calling zone_shallow_copy with NULL " + "arguments did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + lived = 0; + knot_zone_contents_t *zone = knot_zone_contents_new(apex_node, + 0, 1, 0); + if (knot_zone_contents_shallow_copy(zone, NULL) != KNOT_EBADARG) { + diag("Calling zone_shallow_copy with NULL destination " + "zone argument did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_contents_shallow_copy(NULL, &zone) != KNOT_EBADARG) { + diag("Calling zone_shallow_copy with NULL source " + "zone argument did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_contents_shallow_copy(zone, &zone) != KNOT_EBADARG) { + diag("Calling zone_shallow_copy with identical source " + "and destination zone did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + knot_zone_contents_free(&zone); + }, "zone: shallow copy NULL tests"); + errors += lived != 1; + + knot_dname_t *d = knot_dname_deep_copy(&test_nodes_good[0].owner); + if (d == NULL) { + return 0; + } + knot_node_t *n = knot_node_new(d, NULL, 0); + + /* example.com. */ +// knot_zone_t *from_zone = +// knot_zone_new(knot_node_new(&test_nodes_good[0].owner, +// test_nodes_good[0].parent, 0), 10, 1); + knot_zone_t *from_zone = knot_zone_new(n, 10, 1); + knot_zone_contents_t *from = knot_zone_get_contents(from_zone); + + /* Add nodes to zone. */ + for (int i = 1; i < TEST_NODES_GOOD; ++i) { + knot_dname_t *d = knot_dname_deep_copy(&test_nodes_good[i].owner); + if (d == NULL) { + return 0; + } + knot_node_t *node = knot_node_new(d, test_nodes_good[i].parent, + 0); + if (node == NULL) { + diag("zone: Could not create node."); + return 0; + } + + if (knot_zone_contents_add_node(from, node, 1, 1, 1) != KNOT_EOK) { + diag("zone: Could not add node. %s", + knot_dname_to_str(node->owner)); +// return 0; + } + } + + /* Make a copy of zone */ + knot_zone_contents_t *to = NULL; + int ret = 0; + if ((ret = knot_zone_contents_shallow_copy(from, &to) != KNOT_EOK)) { + diag("Could not copy zone! %s", knot_strerror(ret)); + return 0; + } + + assert(to); + + /* Compare non-tree parts of the zone. */ +// if (from->data != to->data) { +// diag("Zone data field wrong after shallow copy!"); +// errors++; +// } + +// if (from->dtor != to->dtor) { +// diag("Zone data destructor field wrong after shallow copy!"); +// errors++; +// } + + if (from->node_count != to->node_count) { + diag("Zone node count data field wrong after shallow copy!"); + errors++; + } + +// if (from->version != to->version) { +// diag("Zone version data field wrong after shallow copy!"); +// errors++; +// } + + if (from->apex != to->apex) { + diag("Zone apex differ after shallow copy!"); + } + + if (compare_wires_simple((uint8_t *)(&from->nsec3_params), + (uint8_t *)(&to->nsec3_params), + sizeof(from->nsec3_params)) != 0) { + diag("Nsec3_params data field wrong after shallow copy!"); + errors++; + } + + if (from->nodes == to->nodes) { + diag("Copied zones have identical trees!"); + errors++; + } + + if (from->nsec3_nodes == to->nsec3_nodes) { + diag("Copied zones have identical trees!"); + errors++; + } + + /* Compare nodes, convert tree to array then compare those arrays. */ + struct zone_test_param param1; + memset(¶m1, 0, sizeof(struct zone_test_param)); + + knot_zone_contents_tree_apply_inorder(from, tree_node_to_array, + (void *)¶m1); + + struct zone_test_param param2; + memset(¶m2, 0, sizeof(struct zone_test_param)); + + knot_zone_contents_tree_apply_inorder(to, tree_node_to_array, + (void *)¶m2); + + if (param1.count != param2.count) { + diag("wrong tree"); + return 0; + } + + for (int i = 0; i < param1.count; i++) { + if (param1.knot_node_array[i] != + param2.knot_node_array[i]) { + diag("wrong tree"); + return 0; + } + } + + param1.count = 0; + knot_dname_table_tree_inorder_apply(from->dname_table, + tree_dname_node_to_array, + (void *)¶m1); + + param2.count = 0; + knot_dname_table_tree_inorder_apply(to->dname_table, + tree_dname_node_to_array, + (void *)¶m2); + + if (param1.count != param2.count) { + diag("wrong table count"); + return 0; + } + + for (int i = 0; i < param1.count; i++) { + if (param1.table_node_array[i] != param2.table_node_array[i]) { + diag("wrong table nodes"); + errors++; + } + } + +#ifdef USE_HASH_TABLE + if (from->table) { + if (from->table == to->table) { + diag("hash tables after shallow copy are identical!"); + return 0; + } + uint i; + if (hashsize(from->table->table_size_exp) != + hashsize(to->table->table_size_exp)) { + diag("hash tables after shallow copy error!"); + return 0; + } + + if (from->table->table_count != to->table->table_count) { + diag("hash tables after shallow copy error!"); + return 0; + } + + for (uint t = 0; t < from->table->table_count; ++t) { + for (i = 0; i < + hashsize(from->table->table_size_exp); i++) { + if (from->table->tables[t][i] == NULL) { + if (to->table->tables[t][i] != NULL) { + diag("hash table item error"); + } + continue; + } + if ((from->table->tables[t])[i]->key_length != + (to->table->tables[t])[i]->key_length) { + diag("hash table key lengths error!"); + return 0; + } + if ((from->table->tables[t])[i]->key != + (to->table->tables[t])[i]->key) { + diag("hash table key error!"); + return 0; + } + if ((from->table->tables[t])[i]->value != + (to->table->tables[t])[i]->value) { + diag("hash table value error!"); + return 0; + } + } + } + + ck_stash_item_t *item1 = from->table->stash; + ck_stash_item_t *item2 = to->table->stash; + while (item1 != NULL && item2 != NULL) { + if (item1->item->key_length != + item2->item->key_length) { + diag("hash stash key length error!"); + return 0; + } + if (item1->item->key != item2->item->key) { + diag("hash stash key error!"); + return 0; + } + if (item1->item->value != item2->item->value) { + diag("hash stash value error!"); + return 0; + } + + item1 = item1->next; + item2 = item2->next; + } + } else { + if (to->table) { + diag("Hash table is not set to NULL " + "after shallow copy!"); + errors++; + } + } +#endif + +// knot_zone_deep_free(&from_zone, 0); +// knot_zone_contents_free(&to); + return (errors == 0); + +} + +//static int test_zone_free(knot_zone_t **zone) +//{ +// knot_zone_tree_apply_postorder(*zone, +// test_zone_destroy_node_from_tree, +// NULL); +// knot_zone_nsec3_apply_postorder(*zone, +// test_zone_destroy_node_from_tree, +// NULL); +// knot_zone_free(zone); +// return (*zone == NULL); +//} + +static const int KNOT_ZONE_TEST_COUNT = 10; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_zone_tests_count(int argc, char *argv[]) +{ + return KNOT_ZONE_TEST_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_zone_tests_run(int argc, char *argv[]) +{ + int res = 0, + res_final = 0; + + knot_zone_contents_t *zone = NULL; + + ok((res = test_zone_create(&zone)), "zone: create"); + res_final *= res; + + skip(!res, 6); + + ok((res = test_zone_add_node(zone, 0)), "zone: add node"); + res_final *= res; + + skip(!res, 2); + + ok((res = test_zone_get_node(zone, 0)), "zone: get node"); + res_final *= res; + + skip(!res, 1); + + ok((res = test_zone_find_node(zone, 0)), "zone: find node"); + res_final *= res; + + endskip; // get node failed + + endskip; // add node failed + + ok((res = test_zone_add_node(zone, 1)), "zone: add nsec3 node"); + res_final *= res; + + skip(!res, 2); + + ok((res = test_zone_get_node(zone, 1)), "zone: get nsec3 node"); + res_final *= res; + + skip(!res, 1); + + ok((res = test_zone_find_node(zone, 1)), "zone: find nsec3 node"); + res_final *= res; + + endskip; // get nsec3 node failed + + endskip; // add nsec3 node failed + + ok(res = test_zone_traversals(zone), "zone: traversals"); + res_final *= res; + + ok((res = test_zone_shallow_copy()), "zone: shallow copy"); + res_final *= res; + +// ok((res = test_zone_free(&zone)), "zone: free"); +// res_final *= res; + + endskip; // create failed + + return res_final; +} diff --git a/src/tests/libknot/libknot/zone_tests.h b/src/tests/libknot/libknot/zone_tests.h new file mode 100644 index 0000000..5539709 --- /dev/null +++ b/src/tests/libknot/libknot/zone_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_ZONE_TESTS_H_ +#define _KNOTD_ZONE_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api zone_tests_api; + +#endif /* _KNOTD_ZONE_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/zone_tree_tests.c b/src/tests/libknot/libknot/zone_tree_tests.c new file mode 100644 index 0000000..c26746e --- /dev/null +++ b/src/tests/libknot/libknot/zone_tree_tests.c @@ -0,0 +1,758 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <assert.h> + +#include "tests/libknot/libknot/zone_tree_tests.h" +#include "libknot/zone/zone-tree.h" +#include "libknot/util/error.h" + +static int knot_zone_tree_tests_count(int argc, char *argv[]); +static int knot_zone_tree_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api zone_tree_tests_api = { + "DNS library - zone tree", //! Unit name + &knot_zone_tree_tests_count, //! Count scheduled tests + &knot_zone_tree_tests_run //! Run scheduled tests +}; + +/* + * Unit implementation. + */ + +static int test_tree_init() +{ + int errors = 0; + int lived = 0; + + lives_ok({ + if (knot_zone_tree_init(NULL) != KNOT_EBADARG) { + diag("Calling knot_zone_tree_init with NULL " + "tree did not return KNOT_EBADARG!"); + errors++; + } + lived = 1; + }, "zone tree: init NULL tests"); + errors += lived != 1; + + return (errors == 0); +} + +static int test_tree_insert() +{ + int errors = 0; + int lived = 0; + + knot_zone_tree_t *tree = malloc(sizeof(knot_zone_tree_t)); + assert(tree); + knot_zone_tree_init(tree); + knot_node_t *node = + knot_node_new(knot_dname_new_from_str("a.ns.nic.cz.", + strlen("a.ns.nic.cz."), + NULL), + NULL, 0); + assert(node); + + lives_ok({ + if (knot_zone_tree_insert(NULL, NULL) != KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_insert(tree, NULL) != KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_insert(NULL, node) != KNOT_EBADARG) { + errors++; + } + lived = 1; + }, "zone tree: insert NULL tests"); + if (errors) { + diag("Zone tree insert did not return KNOT_EBADARG " + "when given wrong arguments"); + } + errors += lived != 1; + + if (knot_zone_tree_insert(tree, node) != KNOT_EOK) { + diag("Calling zone tree insert with valid arguments " + "did not return KNOT_EOK"); + errors++; + } + + /* Sorting will be tested in traversal functions. */ + return (errors == 0); +} + +static int test_tree_finding() +{ + int errors = 0; + int lived = 0; + + knot_zone_tree_t *tree = malloc(sizeof(knot_zone_tree_t)); + assert(tree); + knot_zone_tree_init(tree); + const knot_node_t *node = + knot_node_new(knot_dname_new_from_str("a.ns.nic.cz.", + strlen("a.ns.nic.cz."), + NULL), + NULL, 0); + assert(node); + + lives_ok({ + if (knot_zone_tree_find(NULL, NULL, NULL) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_find(tree, NULL, NULL) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_find(tree, node->owner, + NULL) != KNOT_EBADARG) { + errors++; + } + lived = 1; + const knot_node_t *found_node = NULL; + lived = 0; + if (knot_zone_tree_find(NULL, node->owner, + &found_node) != KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_find(tree, NULL, + &found_node) != KNOT_EBADARG) { + errors++; + } + lived = 1; + }, "zone tree: find NULL tests"); + if (errors) { + diag("Zone tree find did not return KNOT_EBADARG " + "when given wrong arguments"); + } + + errors += lived != 1; + + /* Insert node */ + assert(knot_zone_tree_insert(tree, (knot_node_t *)node) == KNOT_EOK); + + knot_node_t *found_node = NULL; + if (knot_zone_tree_find(tree, node->owner, + (const knot_node_t **)&found_node) != + KNOT_EOK) { + diag("Calling zone tree find with valid arguments did " + "not return KNOT_EOK"); + errors++; + } + + if (found_node != node) { + diag("Zone tree find did not return right node"); + errors++; + } + + if (knot_zone_tree_get(tree, node->owner, &found_node) != + KNOT_EOK) { + diag("Calling zone tree get with valid arguments did " + "not return KNOT_EOK"); + errors++; + } + + if (found_node != node) { + diag("Zone tree get did not return right node"); + errors++; + } + + /* Try to search for node not in tree. */ + knot_dname_t *alien_dname = + knot_dname_new_from_str("this.name.is.not.in.the.tree.", + strlen("this.name.is.not.in.the.tree."), + NULL); + + if (knot_zone_tree_find(tree, alien_dname, + (const knot_node_t **)&found_node) != + KNOT_EOK) { + diag("Calling zone tree find with valid arguments did " + "not return KNOT_EOK"); + errors++; + } + + if (found_node != NULL) { + diag("Zone tree find returned node that was not in the tree!"); + errors++; + } + + if (knot_zone_tree_get(tree, alien_dname, &found_node) != + KNOT_EOK) { + diag("Calling zone tree get with valid arguments did " + "not return KNOT_EOK"); + errors++; + } + + if (found_node != NULL) { + diag("Zone tree get returned node that was not in the tree!"); + errors++; + } + + return (errors == 0); +} + +static int test_tree_finding_less_or_equal() +{ + diag("Issue nr.: 1145"); + int errors = 0; + int lived = 0; + + knot_zone_tree_t *tree = malloc(sizeof(knot_zone_tree_t)); + assert(tree); + knot_zone_tree_init(tree); + const knot_node_t *node = + knot_node_new(knot_dname_new_from_str("a.ns.nic.cz.", + strlen("a.ns.nic.cz"), + NULL), + NULL, 0); + assert(node); + + lives_ok({ + if (knot_zone_tree_find_less_or_equal(NULL, + NULL, + NULL, + NULL, 0) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_find_less_or_equal(tree, NULL, + NULL, NULL, 0) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_find_less_or_equal(tree, + node->owner, + NULL, + NULL, 0) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + const knot_node_t *found_node = NULL; + lived = 0; + if (knot_zone_tree_find_less_or_equal(NULL, node->owner, + &found_node, NULL, 0) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + const knot_node_t *previous_node = NULL; + lived = 0; + if (knot_zone_tree_find_less_or_equal(tree, NULL, + &found_node, + &previous_node, 0) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + }, "zone tree: tree find less or equal NULL tests"); + if (errors) { + diag("Zone tree find did not return KNOT_EBADARG " + "when given wrong arguments"); + } + + if (!lived) { + errors++; + } + + const knot_node_t *previous_node = NULL; + + /* Insert node - exact match. */ + assert(knot_zone_tree_insert(tree, (knot_node_t *)node) == KNOT_EOK); + + + const knot_node_t *found_node = NULL; + if (knot_zone_tree_find_less_or_equal(tree, + node->owner, + &found_node, + &previous_node, 0) <= 0) { + diag("Calling zone tree find less with valid arguments did " + "not return KNOT_EOK"); + errors++; + } + + if (found_node != node) { + diag("Zone tree find did not return right node"); + errors++; + } + + if (knot_zone_tree_get_less_or_equal(tree, node->owner, + (knot_node_t **)&found_node, + (knot_node_t **)&previous_node, 0) <= + 0) { + diag("Calling zone tree get less with valid arguments did " + "not return KNOT_EOK"); + errors++; + } + + if (found_node != node) { + diag("Zone tree get less did not return right node"); + errors++; + } + + knot_dname_t *less_dname = + knot_dname_new_from_str("ns.nic.cz.", + strlen("ns.nic.cz."), + NULL); + + assert(knot_dname_compare(less_dname, node->owner) < 0); + + if (knot_zone_tree_find_less_or_equal(tree, + less_dname, + &found_node, + &previous_node, 0) <= 0) { + diag("Calling zone tree find less or equal " + "with valid arguments did " + "not return > 0"); + errors++; + } + + if (found_node != node) { + diag("Zone tree find less or equal did not return right node"); + errors++; + } + + if (knot_zone_tree_get_less_or_equal(tree, less_dname, + (knot_node_t **)&found_node, + (knot_node_t **)&previous_node, 0) <= + 0) { + diag("Calling zone tree less or equal with valid arguments did " + "not return > 0"); + errors++; + } + + if (found_node != node) { + diag("Zone tree get less or equal did not return right node"); + errors++; + } + + /* Try to search for node not in tree. */ + knot_dname_t *alien_dname = + knot_dname_new_from_str("this.name.is.not.in.the.tree.", + strlen("this.name.is.not.in.the.tree."), + NULL); + + if (knot_zone_tree_find_less_or_equal(tree, alien_dname, + &found_node, + &previous_node, 0) != + 0) { + diag("Calling zone tree find less with valid arguments did " + "not return 0"); + errors++; + } + + if (knot_zone_tree_get_less_or_equal(tree, + alien_dname, + (knot_node_t **)&found_node, + (knot_node_t **)&previous_node, 0) != + 0) { + diag("Calling zone tree get with valid arguments did " + "not return 0"); + errors++; + } + + /* Set node previous label. */ + knot_node_t *tmp_node = + knot_node_new(knot_dname_new_from_str("ns.nic.cz.", + strlen("ns.nic.cz"), + NULL), NULL, 0); + assert(tmp_node); + knot_node_set_parent((knot_node_t *)node, tmp_node); + + if (knot_zone_tree_find_less_or_equal(tree, node->owner, + &found_node, + &previous_node, 0) <= + 0) { + diag("Calling zone tree find with valid arguments did " + "not return > 0"); + errors++; + } + + if (found_node != node || previous_node != tmp_node) { + diag("Zone tree find did not return valid nodes!"); + errors++; + } + + + if (knot_zone_tree_get_less_or_equal(tree, node->owner, + (knot_node_t **)&found_node, + (knot_node_t **)&previous_node, 0) <= + 0) { + diag("Calling zone tree get with valid arguments did " + "not return > 0"); + errors++; + } + + if (found_node != node || previous_node != tmp_node) { + diag("Zone get find did not return valid nodes!"); + errors++; + } + + return (errors == 0); +} + +static int test_tree_remove() +{ + int errors = 0; + int lived = 0; + + knot_zone_tree_t *tree = malloc(sizeof(knot_zone_tree_t)); + assert(tree); + knot_zone_tree_init(tree); + knot_node_t *node = + knot_node_new(knot_dname_new_from_str("a.ns.nic.cz.", + strlen("a.ns.nic.cz"), + NULL), + NULL, 0); + assert(node); + + /* Add node. */ + int ret = knot_zone_tree_insert(tree, node); + assert(ret == 0); + assert(ret == 0); + + lives_ok({ + if (knot_zone_tree_remove(NULL, NULL, NULL) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_remove(tree, NULL, NULL) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_remove(tree, node->owner, NULL) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_remove(NULL, node->owner, NULL) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + knot_zone_tree_node_t *deleted_node = NULL; + lived = 0; + if (knot_zone_tree_remove(NULL, node->owner, &deleted_node) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_remove(tree, NULL, &deleted_node) != + KNOT_EBADARG) { + errors++; + } + lived = 1; + }, "zone tree: remove NULL tests"); + if (errors) { + diag("Zone tree remove did not return KNOT_EBADARG " + "when given wrong arguments"); + } + + errors += lived != 1; + + knot_zone_tree_node_t *removed_node = NULL; + + /* Remove previously inserted node. */ + if (knot_zone_tree_remove(tree, node->owner, &removed_node) != + KNOT_EOK) { + diag("Could not remove previously inserted node!"); + errors++; + } + + if (removed_node == NULL || removed_node->node != node) { + diag("Wrong node was removed!"); + errors++; + } + + /* + * Try remove the node again - it should not be there and + * removed_node should be NULL. + */ + + if (knot_zone_tree_remove(tree, node->owner, &removed_node) != + KNOT_EOK) { + diag("Could not remove previously inserted node!"); + errors++; + } + + if (removed_node != NULL) { + diag("Zone tree remove returned previously removed node!"); + errors++; + } + + return (errors == 0); + +} + +struct test_zone_tree_args { + knot_node_t *array[10 * 1024]; + size_t count; +}; + +static void add_to_array(knot_zone_tree_node_t *node, void *data) +{ + struct test_zone_tree_args *args = + (struct test_zone_tree_args *)data; + args->array[args->count++] = node->node; +} + +static int test_traversal(knot_node_t **nodes, + size_t node_count, + uint code) +{ + int errors = 0; + int lived = 0; + + int (*trav_func)(knot_zone_tree_t *, + void (*)(knot_zone_tree_node_t *, void *), + void *); + + trav_func = (code) ? knot_zone_tree_reverse_apply_inorder : + knot_zone_tree_forward_apply_inorder; + + knot_zone_tree_t *tree = malloc(sizeof(knot_zone_tree_t)); + assert(tree); + knot_zone_tree_init(tree); + + lives_ok({ + if (trav_func(NULL, NULL, NULL) != KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (trav_func(tree, NULL, NULL) != KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (trav_func(NULL, add_to_array, NULL) != KNOT_EBADARG) { + errors++; + } + lived = 1; + }, "zone tree: traversal NULL tests"); + + if (errors) { + diag("Traversal function did not return KNOT_EBADARG " + "when given NULL parameters"); + } + + errors += lived != 1; + + /* Add nodes to tree. */ + for (int i = 0; i < node_count; i++) { + assert(knot_zone_tree_insert(tree, nodes[i]) == KNOT_EOK); + } + + struct test_zone_tree_args args; + args.count = 0; + + trav_func(tree, add_to_array, &args); + + if (args.count != node_count) { + diag("Traversal function traversed more nodes than it " + "should have!"); + return ++errors; + } + + for (int i = 0; i < node_count; i++) { + int match = nodes[i] == args.array[i]; + if (!match) { + diag("Traversal function returned nodes in wrong " + "order!"); + errors++; + } + } + + return errors; +} + +static int test_tree_traversals() +{ + /*!< \todo I can test inorder and reverse inorder, but I don't know + * how to test others. It is somehow tested in zone tests. */ + int errors = 0; + + /* Create few nodes. (5 should be enough) */ + knot_node_t *nodes[5]; + for (int i = 0; i < 5; i++) { + char owner_string[20]; + owner_string[0] = i + '0'; + memcpy(owner_string + 1, ".ns.test.cz.", + strlen(".ns.test.cz.") + 1); + nodes[i] = + knot_node_new(knot_dname_new_from_str(owner_string, + strlen(owner_string), + NULL), NULL, 0); + } + + if (test_traversal(nodes, 5, 0)) { + diag("Inorder traversal failed"); + errors++; + } + + for (int i = 0; i < 5; i++) { + char owner_string[20]; + owner_string[0] = (5 - i) + '0'; + memcpy(owner_string + 1, ".ns.test.cz.", + strlen(".ns.test.cz.") + 1); + nodes[i] = + knot_node_new(knot_dname_new_from_str(owner_string, + strlen(owner_string), + NULL), NULL, 0); + } + + if (test_traversal(nodes, 5, 1)) { + diag("Reverse inorder traversal failed"); + errors++; + } + + return (errors == 0); +} + +static int test_tree_shallow_copy() +{ + int errors = 0; + int lived = 0; + + knot_zone_tree_t *tree = malloc(sizeof(knot_zone_tree_t)); + assert(tree); + knot_zone_tree_init(tree); + + lives_ok({ + if (knot_zone_tree_shallow_copy(NULL, NULL) != KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_shallow_copy(tree, NULL) != KNOT_EBADARG) { + errors++; + } + lived = 1; + lived = 0; + if (knot_zone_tree_shallow_copy(NULL, tree) != KNOT_EBADARG) { + errors++; + } + lived = 1; + }, "zone tree: shallow copy NULL tests"); + if (errors) { + diag("Zone tree shallow copy did not return KNOT_EBADARG when " + "given NULL arguments"); + } + errors += lived != 1; + + /* Create few nodes. (5 should be enough) */ + knot_node_t *nodes[5]; + for (int i = 0; i < 5; i++) { + char owner_string[20]; + owner_string[0] = i + '0'; + memcpy(owner_string + 1, ".ns.test.cz.", + strlen(".ns.test.cz.") + 1); + nodes[i] = + knot_node_new(knot_dname_new_from_str(owner_string, + strlen(owner_string), + NULL), NULL, 0); + /* Insert node to tree. */ + assert(knot_zone_tree_insert(tree, nodes[i]) == KNOT_EOK); + } + + /* Create shallow copy. */ + knot_zone_tree_t *new_tree = malloc(sizeof(knot_zone_tree_t)); + assert(new_tree); + knot_zone_tree_init(new_tree); + + if (knot_zone_tree_shallow_copy(tree, new_tree) != KNOT_EOK) { + diag("Zone tree shallow copy did not return KNOT_EOK " + "when executed with valid parameters"); + return 0; + } + + /* Traverse the tree twice and check that arrays are the same. */ + struct test_zone_tree_args args1; + args1.count = 0; + + knot_zone_tree_forward_apply_inorder(tree, add_to_array, + &args1); + + + struct test_zone_tree_args args2; + args2.count = 0; + knot_zone_tree_forward_apply_inorder(new_tree, add_to_array, + &args2); + + if (args1.count != args2.count) { + diag("Zone tree created by shallow copy has wrong count" + "of nodes"); + return 0; + } + + for (int i = 0; i < args1.count; i++) { + if (args1.array[i] != args2.array[i]) { + diag("Zone tree created by shallow copy has wrong " + "nodes"); + errors++; + } + } + + return (errors == 0); + +} + + +static const int KNOT_ZONE_TREE_TEST_COUNT = 14; + +static int knot_zone_tree_tests_count(int argc, char *argv[]) +{ + return KNOT_ZONE_TREE_TEST_COUNT; +} + +static int knot_zone_tree_tests_run(int argc, char *argv[]) +{ + ok(test_tree_init(), "zone tree: init"); + ok(test_tree_insert(), "zone tree: insertion"); + ok(test_tree_finding(), "zone tree: finding"); + todo(); + ok(test_tree_finding_less_or_equal(), "zone tree: find less or equal"); + endtodo; + ok(test_tree_remove(), "zone tree: removal"); + ok(test_tree_traversals(), "zone tree: traversals"); + ok(test_tree_shallow_copy(), "zone tree: shallow copy"); + + return 1; +} diff --git a/src/tests/libknot/libknot/zone_tree_tests.h b/src/tests/libknot/libknot/zone_tree_tests.h new file mode 100644 index 0000000..4cea88c --- /dev/null +++ b/src/tests/libknot/libknot/zone_tree_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTDZONE_TREE_TESTS_H_ +#define _KNOTDZONE_TREE_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api zone_tree_tests_api; + +#endif /* _KNOTDZONE_TREE_TESTS_H_ */ diff --git a/src/tests/libknot/libknot/zonedb_tests.c b/src/tests/libknot/libknot/zonedb_tests.c new file mode 100644 index 0000000..7b45587 --- /dev/null +++ b/src/tests/libknot/libknot/zonedb_tests.c @@ -0,0 +1,44 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include "tests/libknot/libknot/zonedb_tests.h" + + +static int zonedb_tests_count(int argc, char *argv[]); +static int zonedb_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api zonedb_tests_api = { + "Zone database", //! Unit name + &zonedb_tests_count, //! Count scheduled tests + &zonedb_tests_run //! Run scheduled tests +}; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int zonedb_tests_count(int argc, char *argv[]) +{ + return 0; +} + +/*! Run all scheduled tests for given parameters. + */ +static int zonedb_tests_run(int argc, char *argv[]) +{ + return 0; +} diff --git a/src/tests/libknot/libknot/zonedb_tests.h b/src/tests/libknot/libknot/zonedb_tests.h new file mode 100644 index 0000000..0c4f8ef --- /dev/null +++ b/src/tests/libknot/libknot/zonedb_tests.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_ZONEDB_TESTS_H_ +#define _KNOTD_ZONEDB_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api zonedb_tests_api; + +#endif /* _KNOTD_ZONEDB_TESTS_H_ */ diff --git a/src/tests/libknot/realdata/files/parsed_data b/src/tests/libknot/realdata/files/parsed_data Binary files differnew file mode 100644 index 0000000..fe22b90 --- /dev/null +++ b/src/tests/libknot/realdata/files/parsed_data diff --git a/src/tests/libknot/realdata/files/parsed_data_queries b/src/tests/libknot/realdata/files/parsed_data_queries Binary files differnew file mode 100644 index 0000000..5857c87 --- /dev/null +++ b/src/tests/libknot/realdata/files/parsed_data_queries diff --git a/src/tests/libknot/realdata/files/raw_data b/src/tests/libknot/realdata/files/raw_data Binary files differnew file mode 100644 index 0000000..502005e --- /dev/null +++ b/src/tests/libknot/realdata/files/raw_data diff --git a/src/tests/libknot/realdata/files/raw_data_queries b/src/tests/libknot/realdata/files/raw_data_queries Binary files differnew file mode 100644 index 0000000..9062d5a --- /dev/null +++ b/src/tests/libknot/realdata/files/raw_data_queries diff --git a/src/tests/libknot/realdata/libknot/dname_tests_realdata.c b/src/tests/libknot/realdata/libknot/dname_tests_realdata.c new file mode 100644 index 0000000..d0216c7 --- /dev/null +++ b/src/tests/libknot/realdata/libknot/dname_tests_realdata.c @@ -0,0 +1,411 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <assert.h> +#include <stdarg.h> + +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" +#include "tests/libknot/realdata/libknot/dname_tests_realdata.h" +#include "libknot/dname.h" +#include "libknot/common.h" + +#include "common/print.h" +#include "common/lists.h" + +static int knot_dname_tests_count(int argc, char *argv[]); +static int knot_dname_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api dname_tests_api = { + "DNS library - dname", //! Unit name + &knot_dname_tests_count, //! Count scheduled tests + &knot_dname_tests_run //! Run scheduled tests +}; + +/* + * Unit implementation. + */ + +int check_domain_name(const knot_dname_t *dname, + const test_dname_t *test_dname) +{ + int errors = 0; + + if (dname == NULL) { + diag("Domain name not created!"); + return 1; + } + +// diag("test_dname: %p, dname: %p", test_dname, dname); + // check size + if (knot_dname_size(dname) != test_dname->size) { + diag("Bad size of the created domain name: %u (should be %u).", + knot_dname_size(dname), test_dname->size); + ++errors; + } else { + // check wire format + uint size = knot_dname_size(dname); + if (strncmp((char *)knot_dname_name(dname), + (char *)test_dname->wire, size) != 0) { + diag("The wire format of the created " + "domain name is wrong:" + " '%.*s' (should be '%.*s').", + size, knot_dname_name(dname), + size, test_dname->wire); + ++errors; + } + } + // check labels + if (test_dname->label_count != dname->label_count) { + diag("Label count of the created domain name is wrong:" + " %d (should be %d)\n", dname->label_count, + test_dname->label_count); + ++errors; + } + if (strncmp((char *)dname->labels, (char *)test_dname->labels, + test_dname->label_count) != 0) { + diag("Label offsets of the created domain name are wrong.\n"); + hex_print((char *)dname->labels, test_dname->label_count); + hex_print((char *)test_dname->labels, test_dname->label_count); + ++errors; + } + + return errors; +} + +static int test_dname_create_from_str(const list *dname_list) +{ + int errors = 0; + knot_dname_t *dname = NULL; + + /* Test with real data. */ + node *n = NULL; + WALK_LIST(n, *dname_list) { + //note("testing domain: %s", test_domains_ok[i].str); + test_dname_t *test_dname = (test_dname_t *)n; + dname = knot_dname_new_from_str(test_dname->str, + strlen(test_dname->str), NULL); + errors += check_domain_name(dname, test_dname); + knot_dname_free(&dname); + } + + return (errors == 0); +} + +static int test_dname_create_from_wire(const list *dname_list) +{ + int errors = 0; + knot_dname_t *dname = NULL; + + node *n = NULL; + WALK_LIST(n, *dname_list) { + test_dname_t *test_dname = (test_dname_t *)n; + dname = knot_dname_new_from_wire(test_dname->wire, + test_dname->size, NULL); + errors += check_domain_name(dname, test_dname); + knot_dname_free(&dname); + } + + return (errors == 0); +} + +static int test_dname_to_str(const list *dname_list) +{ + int errors = 0; + + /* + * Converts dname wireformat to string represenation, which is compared + * with entries in test_domains structure. + */ + + knot_dname_t *dname = NULL; + + /* Test with real data. */ + node *n = NULL; + WALK_LIST(n, *dname_list) { + //note("testing domain: %s", test_domains_ok[i].str); + test_dname_t *test_dname = (test_dname_t *)n; + dname = knot_dname_new_from_wire( + test_dname->wire, + test_dname->size, + NULL); + if (dname == NULL) { + ERR_ALLOC_FAILED; + return 0; + } + + char *name_str = knot_dname_to_str(dname); + if (strcmp(name_str, test_dname->str) != 0) { + diag("Presentation format of domain name wrong:" + " %s (should be %s)", + name_str, test_dname->str); + ++errors; + } + free(name_str); + knot_dname_free(&dname); + } + + return (errors == 0); +} + +static int test_dname_is_fqdn(const list *dname_list) +{ + int errors = 0; + + knot_dname_t *dname; + + /* All dnames from real data are fqdn */ + + node *n = NULL; + WALK_LIST(n, *dname_list) { + test_dname_t *test_dname = (test_dname_t *)n; + dname = knot_dname_new_from_wire(test_dname->wire, + test_dname->size, NULL); + errors += !knot_dname_is_fqdn(dname); + knot_dname_free(&dname); + } + + return (errors == 0); +} + +//static int check_wires(const uint8_t *wire1, uint size1, +// uint8_t *wire2, uint size2) +//{ +// if (size1 != size2) { +// return 0; +// } + +// int i; + +// for (i = 0; (i < size1); i++) { +// if (wire1[i] != wire2[i]) { +// return 0; +// } +// } + +// return 1; +//} + +///* \note not to be run separately */ +//static int test_dname_name(knot_dname_t **dnames_fqdn, +// knot_dname_t **dnames_non_fqdn) +//{ +// assert(dnames_fqdn); +// assert(dnames_non_fqdn); + +// int errors = 0; + +// for (int i = 0; i < TEST_DOMAINS_OK; i++) { +// const uint8_t *tmp_name; +// tmp_name = knot_dname_name(dnames_fqdn[i]); +// if (!check_wires(tmp_name, dnames_fqdn[i]->size, +// (uint8_t *)test_domains_ok[i].wire, +// test_domains_ok[i].size)) { +// diag("Got bad name value from structure: " +// "%s, should be: %s", +// tmp_name, test_domains_ok[i].wire); +// errors++; +// } +// } + +// for (int i = 0; i < TEST_DOMAINS_NON_FQDN; i++) { +// const uint8_t *tmp_name; +// tmp_name = knot_dname_name(dnames_non_fqdn[i]); +// if (!check_wires(tmp_name, dnames_non_fqdn[i]->size, +// (uint8_t *)test_domains_non_fqdn[i].wire, +// test_domains_non_fqdn[i].size)) { +// diag("Got bad name value from structure: " +// "%s, should be: %s", +// tmp_name, test_domains_non_fqdn[i].wire); +// errors++; +// } +// } + +// return errors; +//} + +///* \note not to be run separately */ +//static int test_dname_size(knot_dname_t **dnames_fqdn, +// knot_dname_t **dnames_non_fqdn) +//{ +// assert(dnames_fqdn); +// assert(dnames_non_fqdn); + +// int errors = 0; + +// for (int i = 0; i < TEST_DOMAINS_OK; i++) { +// uint8_t tmp_size; +// if ((tmp_size = knot_dname_size(dnames_fqdn[i])) != +// test_domains_ok[i].size) { +// diag("Got bad size value from structure: " +// "%u, should be: %u", +// tmp_size, test_domains_ok[i].size); +// errors++; +// } +// } + +// for (int i = 0; i < TEST_DOMAINS_NON_FQDN; i++) { +// uint8_t tmp_size; +// if ((tmp_size = knot_dname_size(dnames_non_fqdn[i])) != +// test_domains_non_fqdn[i].size) { +// diag("Got bad size value from structure: " +// "%u, should be: %u", +// tmp_size, test_domains_non_fqdn[i].size); +// errors++; +// } +// } + +// return errors; +//} + +///* \note not to be run separately */ +//static int test_dname_node(knot_dname_t **dnames_fqdn, +// knot_dname_t **dnames_non_fqdn) +//{ +// assert(dnames_fqdn); +// assert(dnames_non_fqdn); + +// int errors = 0; + +// for (int i = 0; i < TEST_DOMAINS_OK; i++) { +// const knot_node_t *tmp_node; +// if ((tmp_node = knot_dname_node(dnames_fqdn[i])) != +// NODE_ADDRESS) { +// diag("Got bad node value from structure: " +// "%p, should be: %p", +// tmp_node, NODE_ADDRESS); +// errors++; +// } +// } + +// for (int i = 0; i < TEST_DOMAINS_NON_FQDN; i++) { +// const knot_node_t *tmp_node; +// if ((tmp_node = knot_dname_node(dnames_non_fqdn[i])) != +// NODE_ADDRESS) { +// diag("Got bad node value from structure: " +// "%s, should be: %s", +// tmp_node, NODE_ADDRESS); +// errors++; +// } +// } + +// return errors; +//} + +//static int test_dname_getters(uint type) +//{ +// int errors = 0; + +// knot_dname_t *dnames_fqdn[TEST_DOMAINS_OK]; +// knot_dname_t *dnames_non_fqdn[TEST_DOMAINS_NON_FQDN]; + +// for (int i = 0; i < TEST_DOMAINS_OK; i++) { +// dnames_fqdn[i] = knot_dname_new_from_wire( +// (uint8_t *)test_domains_ok[i].wire, +// test_domains_ok[i].size, NODE_ADDRESS); +// assert(dnames_fqdn[i] != NULL); +// } + +// for (int i = 0; i < TEST_DOMAINS_NON_FQDN; i++) { +// dnames_non_fqdn[i] = knot_dname_new_from_wire( +// (uint8_t *)test_domains_non_fqdn[i].wire, +// test_domains_non_fqdn[i].size, NODE_ADDRESS); +// assert(dnames_non_fqdn[i] != NULL); +// } + +// switch (type) { +// case 0: { +// errors += test_dname_name(dnames_fqdn, dnames_non_fqdn); +// break; +// } + +// case 1: { +// errors += test_dname_size(dnames_fqdn, dnames_non_fqdn); +// break; +// } + +// case 2: { +// errors += test_dname_node(dnames_fqdn, dnames_non_fqdn); +// break; +// } +// } /* switch */ + +// for (int i = 0; i < TEST_DOMAINS_OK; i++) { +// knot_dname_free(&dnames_fqdn[i]); +// } + +// for (int i = 0; i < TEST_DOMAINS_NON_FQDN; i++) { +// knot_dname_free(&dnames_non_fqdn[i]); +// } + +// return (errors == 0); +//} + +static const int KNOT_DNAME_TEST_COUNT = 4; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_dname_tests_count(int argc, char *argv[]) +{ + return KNOT_DNAME_TEST_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_dname_tests_run(int argc, char *argv[]) +{ + const test_data_t *data = data_for_knot_tests; + + int res = 0, + res_str = 0, + res_wire = 0, + res_str_non_fqdn = 0, + res_final = 1; + + ok((res_str = test_dname_create_from_str(&data->dname_list)), + "dname: create from string"); + ok((res_wire = test_dname_create_from_wire(&data->dname_list)), + "dname: create from wire"); + + res_final *= res_str; + res_final *= res_wire; + res_final *= res_str_non_fqdn; + +// res = test_dname_getters(0); +// ok(res, "dname: name"); + +// res = test_dname_getters(1); +// ok(res, "dname: size"); + +// res = test_dname_getters(2); +// ok(res, "dname: node"); + +// skip(!res_str || !res_wire || !res_str_non_fqdn, 2); + + ok((res = test_dname_to_str(&data->dname_list)), + "dname: convert to str"); + res_final *= res; + +// endskip; /* !res_str || !res_wire */ + + ok((res = test_dname_is_fqdn(&data->dname_list)), "dname: fqdn"); + res_final *= res; + + return res_final; +} diff --git a/src/tests/libknot/realdata/libknot/dname_tests_realdata.h b/src/tests/libknot/realdata/libknot/dname_tests_realdata.h new file mode 100644 index 0000000..a7d75aa --- /dev/null +++ b/src/tests/libknot/realdata/libknot/dname_tests_realdata.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_DNAME_TESTS_H_ +#define _KNOTD_DNAME_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api dname_tests_api; + +#endif /* _KNOTD_DNAME_TESTS_H_ */ diff --git a/src/tests/libknot/realdata/libknot/edns_tests_realdata.c b/src/tests/libknot/realdata/libknot/edns_tests_realdata.c new file mode 100644 index 0000000..257d480 --- /dev/null +++ b/src/tests/libknot/realdata/libknot/edns_tests_realdata.c @@ -0,0 +1,563 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "tests/libknot/realdata/libknot/edns_tests_realdata.h" +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" +#include "libknot/common.h" +#include "libknot/edns.h" + +static int knot_edns_tests_count(int argc, char *argv[]); +static int knot_edns_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api edns_tests_api = { + "DNS library - EDNS", //! Unit name + &knot_edns_tests_count, //! Count scheduled tests + &knot_edns_tests_run //! Run scheduled tests +}; + +/* + * Unit implementation. + */ + +///* Creates actual knot_opt_rr_t variable from test_edns_t variable */ +//static knot_opt_rr_t *opt_rr_from_test_edns(test_edns_t *test_edns) +//{ +// knot_opt_rr_t *ret = knot_edns_new(); + +// CHECK_ALLOC_LOG(ret, NULL); + +// ret->flags = test_edns->flags; +// ret->ext_rcode = test_edns->ext_rcode; +// ret->payload = test_edns->payload; +// ret->version = test_edns->version; + +// for (int i = 0; i < test_edns->option_count; i++) { +// if (knot_edns_add_option(ret, test_edns->options[i].code, +// test_edns->options[i].length, +// test_edns->options[i].data) != 0) { +// knot_edns_free(&ret); +// return NULL; +// } +// } + +// return ret; +//} + +///* simple wire compare - 0 if same, 1 otherwise */ +//static int edns_compare_wires(uint8_t *wire1, +// uint8_t *wire2, +// uint16_t length) +//{ +// for (uint i = 0; i < length; i++) { +// if (wire1[i] != wire2[i]) { +// return 1; +// } +// } + +// return 0; +//} + +//static int check_edns(const knot_opt_rr_t *edns, +// const test_edns_t *test_edns) +//{ +// if (edns->option_count != test_edns->option_count) { +// diag("Option count is wrong"); +// return -1; +// } + +// for (int i = 0; i < edns->option_count; i++) { +// /* check options */ +// if (edns->options[i].code != test_edns->options[i].code) { +// diag("Code in options is wrong"); +// return -1; +// } + +// if (edns->options[i].length != test_edns->options[i].length) { +// diag("Length in options is wrong"); +// return -1; +// } + +// if (edns_compare_wires(edns->options[i].data, +// test_edns->options[i].data, +// edns->options[i].length) != 0) { +// diag("Data in options are wrong"); +// return -1; +// } +// } + +// if (edns->version != test_edns->version) { +// diag("Version is wrong"); +// return -1; +// } + +// if (edns->flags != test_edns->flags) { +// diag("Flags are wrong"); +// return -1; +// } + +// if (edns->size != test_edns->size) { +// diag("Size is wrong"); +// return -1; +// } + +// return 0; +//} + +//static int test_edns_get_payload(const knot_opt_rr_t *edns, +// test_edns_t *test_edns) +//{ +// if (knot_edns_get_payload(edns) != +// test_edns->payload) { +// return 0; +// } else { +// return 1; +// } +//} + +//static int test_edns_get_ext_rcode(const knot_opt_rr_t *edns, +// test_edns_t *test_edns) +//{ +// if (knot_edns_get_ext_rcode(edns) != +// test_edns->ext_rcode) { +// return 0; +// } else { +// return 1; +// } +//} + +//static int test_edns_get_flags(const knot_opt_rr_t *edns, +// test_edns_t *test_edns) +//{ +// if (knot_edns_get_flags(edns) != +// test_edns->flags) { +// return 0; +// } else { +// return 1; +// } +//} + +//static int test_edns_get_version(const knot_opt_rr_t *edns, +// test_edns_t *test_edns) +//{ +// if (knot_edns_get_version(edns) != +// test_edns->version) { +// return 0; +// } else { +// return 1; +// } +//} + +//static int test_edns_do(const knot_opt_rr_t *edns, +// test_edns_t *test_edns) +//{ +// if (knot_edns_do(edns) != +// (test_edns->flags & KNOT_EDNS_DO_MASK)) { +// return 0; +// } else { +// return 1; +// } +//} + +//static int test_edns_size(knot_opt_rr_t *edns, test_edns_t *test_edns) +//{ +// diag("%d %d\n", edns->size, test_edns->size); +// if (knot_edns_size(edns) != +// test_edns->size) { +// return 0; +// } else { +// return 1; +// } +//} + +//static int test_edns_set_payload(knot_opt_rr_t *edns, +// test_edns_t *test_edns) +//{ +// knot_edns_set_payload(edns, test_edns->payload); + +// if (edns->payload != +// test_edns->payload) { +// return 0; +// } else { +// return 1; +// } +//} + +//static int test_edns_set_ext_rcode(knot_opt_rr_t *edns, +// test_edns_t *test_edns) +//{ +// knot_edns_set_ext_rcode(edns, test_edns->ext_rcode); +// if (edns->ext_rcode != +// test_edns->ext_rcode) { +// return 0; +// } else { +// return 1; +// } +//} + +//static int test_edns_set_version(knot_opt_rr_t *edns, +// test_edns_t *test_edns) +//{ +// knot_edns_set_version(edns, +// test_edns->version); + +// if (edns->version != +// test_edns->version) { +// return 0; +// } else { +// return 1; +// } +//} + +//static int test_edns_set_do(knot_opt_rr_t *edns) +//{ +// knot_edns_set_do(edns); + +// if (!knot_edns_do(edns)) { +// return 0; +// } else { +// return 1; +// } +//} + +//static int test_edns_getters(uint type) +//{ +// int errors = 0; +// for (int i = 0; i < TEST_EDNS; i++) { +// knot_opt_rr_t *edns = +// opt_rr_from_test_edns(&(test_edns_data[i])); +// if (edns == NULL) { +// ERR_ALLOC_FAILED; +// return -1; +// } + +// switch(type) { +// case 0: +// if (test_edns_get_payload(edns, +// &test_edns_data[i]) != 1) { +// diag("Got wrong payload!"); +// errors++; +// } +// break; +// case 1: +// if (test_edns_get_ext_rcode(edns, +// &test_edns_data[i]) != 1) { +// diag("Got wrong extended RCODE!"); +// errors++; +// } +// break; +// case 2: +// if (test_edns_get_flags(edns, +// &test_edns_data[i]) != 1) { +// diag("Got wrong flags!"); + +// errors++; +// } +// break; +// case 3: +// if (test_edns_get_version(edns, +// &test_edns_data[i]) != 1) { +// diag("Got wrong version!"); +// errors++; +// } +// break; +// case 4: +// if (test_edns_do(edns, +// &test_edns_data[i]) != 1) { +// diag("Got wrong DO bit!"); +// errors++; +// } +// break; +// case 5: +// if (test_edns_size(edns, +// &test_edns_data[i]) != 1) { +// diag("Got wrong size!"); +// errors++; +// } +// break; +// default: +// diag("Unknown option"); +// errors++; +// } /* switch */ + +// knot_edns_free(&edns); +// } + +// return (errors == 0); +//} + +//static int test_edns_setters(uint type) +//{ +// int errors = 0; +// for (int i = 0; i < TEST_EDNS; i++) { +// knot_opt_rr_t *edns = +// opt_rr_from_test_edns(&(test_edns_data[i])); +// if (edns == NULL) { +// ERR_ALLOC_FAILED; +// return -1; +// } + +// switch(type) { +// case 0: +// if (test_edns_set_payload(edns, +// &test_edns_data[i]) != 1) { +// diag("Set wrong payload!"); +// errors++; +// } +// break; +// case 1: +// if (test_edns_set_ext_rcode(edns, +// &test_edns_data[i]) != 1) { +// diag("Set wrong ext_rcode"); +// errors++; +// } +// break; +// case 2: +// if (test_edns_set_version(edns, +// &test_edns_data[i]) != 1) { +// diag("Set wrong version!"); +// errors++; +// } +// break; +// case 3: +// if (test_edns_set_do(edns) != 1) { +// diag("Set wrong DO bit!"); +// errors++; +// } +// break; +// default: +// diag("Unknown option"); +// errors++; +// } /* switch */ + +// knot_edns_free(&edns); +// } + +// return (errors == 0); +//} + +//static int test_edns_wire() +//{ +// /* +// * Tests to_wire and from_wire in one test. +// */ +// for (int i = 0; i < TEST_EDNS; i++) { +// /* Creates instance from test_edns_t. */ +// knot_opt_rr_t *edns = +// opt_rr_from_test_edns(&(test_edns_data[i])); +// if (edns == NULL) { +// ERR_ALLOC_FAILED; +// return -1; +// } + +// uint8_t *wire = NULL; +// wire = malloc(sizeof(uint8_t) * edns->size); +// CHECK_ALLOC_LOG(wire, 0); + +// /* Converts EDNS to wire. */ +// short wire_size = knot_edns_to_wire(edns, wire, 100); + +// if (wire_size == -1) { +// diag("Could not create EDNS wire"); +// return 0; +// } + +// knot_opt_rr_t *edns_from_wire = knot_edns_new(); +// if (edns == NULL) { +// return 0; +// } + +// /* TODO use some constant */ +// /* Creates new EDNS from wire */ +// if (knot_edns_new_from_wire(edns_from_wire, +// wire, +// 100) <= 0) { +// diag("Could not create from wire"); +// return 0; +// } + +// /* Checks whether EDNS created from wire is the same */ +// if (check_edns(edns_from_wire, +// &(test_edns_data[i])) != 0) { +// diag("EDNS created from wire is different from the " +// "original one"); +// } + +// free(wire); +// knot_edns_free(&edns_from_wire); +// knot_edns_free(&edns); +// } +// return 1; +//} + +//static int test_edns_add_option() +//{ +// /* +// * Create empty EDNS and add options one by one, testing their presence. +// */ +// for (int i = 0; i < TEST_EDNS; i++) { +// knot_opt_rr_t *edns = knot_edns_new(); +// assert(edns->option_count == 0); + +// if (edns == NULL) { +// ERR_ALLOC_FAILED; +// return 0; +// } + +// for (int j = 0; j < test_edns_data[i].option_count; j++) { +// if (knot_edns_add_option(edns, +// test_edns_data[i].options[j].code, +// test_edns_data[i].options[j].length, +// test_edns_data[i].options[j]. +// data) != 0) { +// diag("Could not add option"); +// return 0; +// } + +// if (edns->options[j].code != +// test_edns_data[i].options[j].code) { +// diag("Option code wrongly added!"); +// return 0; +// } + +// if (edns->options[j].length != +// test_edns_data[i].options[j].length) { +// diag("Option length wrongly added!"); +// return 0; +// } + +// if (edns_compare_wires(edns->options[j].data, +// test_edns_data[i]. +// options[j].data, +// edns->options[j].length) != 0) { +// diag("Option wire wrongly added!"); +// return 0; +// } +// } +// knot_edns_free(&edns); +// } +// return 1; +//} + +//static int test_edns_has_option() +//{ +// /* +// * Create empty EDNS and add options one by one, testing their presence +// */ +// for (int i = 0; i < TEST_EDNS; i++) { +// knot_opt_rr_t *edns = knot_edns_new(); +// assert(edns->option_count == 0); + +// if (edns == NULL) { +// ERR_ALLOC_FAILED; +// return 0; +// } + +// for (int j = 0; j < test_edns_data[i].option_count; j++) { +// if (knot_edns_add_option(edns, +// test_edns_data[i].options[j].code, +// test_edns_data[i].options[j].length, +// test_edns_data[i].options[j]. +// data) != 0) { +// diag("Could not add option"); +// return 0; +// } + +// if (knot_edns_has_option(edns, +// test_edns_data[i].options[j].code) != 1) { +// diag("Option not found!"); +// return 0; +// } +// } +// knot_edns_free(&edns); +// } +// return 1; +//} + +static const int KNOT_EDNS_TESTS_COUNT = 0; + +///*! This helper routine should report number of +// * scheduled tests for given parameters. +// */ +static int knot_edns_tests_count(int argc, char *argv[]) +{ + return KNOT_EDNS_TESTS_COUNT; +} + +///*! Run all scheduled tests for given parameters. +// */ +static int knot_edns_tests_run(int argc, char *argv[]) +{ +// int res = 0; + int res_final = 1; + +// res = test_edns_getters(0); +// ok(res, "EDNS: get payload"); +// res_final *= res; + +// res = test_edns_getters(1); +// ok(res, "EDNS: get extenden RCODE"); +// res_final *= res; + +// res = test_edns_getters(2); +// ok(res, "EDNS: get flags"); +// res_final *= res; + +// res = test_edns_getters(3); +// ok(res, "EDNS: get version"); +// res_final *= res; + +// res = test_edns_getters(4); +// ok(res, "EDNS: do"); +// res_final *= res; + +// res = test_edns_getters(5); +// ok(res, "EDNS: size"); +// res_final *= res; + +// res = test_edns_setters(0); +// ok(res, "EDNS: set payload"); +// res_final *= res; + +// res = test_edns_setters(1); +// ok(res, "EDNS: set extended RCODE"); +// res_final *= res; + +// res = test_edns_setters(2); +// ok(res, "EDNS: set version"); +// res_final *= res; + +// res = test_edns_setters(3); +// ok(res, "EDNS: set DO"); +// res_final *= res; + +// res = test_edns_add_option(); +// ok(res, "EDNS: add option"); +// res_final *= res; + +// res = test_edns_has_option(); +// ok(res, "EDNS: has option"); +// res_final *= res; + +// res = test_edns_wire(); +// ok(res, "EDNS: to_wire and from_wire"); +// res_final *= res; + + return res_final; +} diff --git a/src/tests/libknot/realdata/libknot/edns_tests_realdata.h b/src/tests/libknot/realdata/libknot/edns_tests_realdata.h new file mode 100644 index 0000000..cfa64b0 --- /dev/null +++ b/src/tests/libknot/realdata/libknot/edns_tests_realdata.h @@ -0,0 +1,35 @@ +/*! + * \file edns_tests.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * Contains unit tests for ENDS API + * + * Contains tests for: + * - ENDS API + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD__EDNS_TESTS_H_ +#define _KNOTD__EDNS_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api edns_tests_api; + +#endif /* _KNOTD__EDNS_TESTS_H_ */ diff --git a/src/tests/libknot/realdata/libknot/node_tests_realdata.c b/src/tests/libknot/realdata/libknot/node_tests_realdata.c new file mode 100644 index 0000000..3218c8e --- /dev/null +++ b/src/tests/libknot/realdata/libknot/node_tests_realdata.c @@ -0,0 +1,385 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "tests/libknot/realdata/libknot/node_tests_realdata.h" +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" +#include "libknot/dname.h" +#include "libknot/zone/node.h" +#include "libknot/util/descriptor.h" + +static int knot_node_tests_count(int argc, char *argv[]); +static int knot_node_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api node_tests_api = { + "DNS library - node", //! Unit name + &knot_node_tests_count, //! Count scheduled tests + &knot_node_tests_run //! Run scheduled tests +}; + +/* + * Unit implementation. + */ + +/* TODO It would be wise not to name variables totally same as structures ... */ +knot_dname_t *dname_from_test_dname(const test_dname_t *test_dname) +{ + assert(test_dname != NULL); + knot_dname_t *ret = knot_dname_new_from_wire(test_dname->wire, + test_dname->size, + NULL); + CHECK_ALLOC(ret, NULL); + + return ret; +} + +static knot_rrset_t *rrset_from_test_rrset(const test_rrset_t *test_rrset) +{ + assert(test_rrset != NULL); + knot_dname_t *owner = dname_from_test_dname(test_rrset->owner); + CHECK_ALLOC(owner, NULL); + + knot_rrset_t *ret = knot_rrset_new(owner, test_rrset->type, + test_rrset->rclass, + test_rrset->ttl); + if (ret == NULL) { + ERR_ALLOC_FAILED; + knot_dname_free(&owner); + return NULL; + } + + return ret; +} + +static int test_node_create(const list *node_list) +{ + /* Tests creation of node by comparing with test_node struct */ + knot_node_t *tmp; + int errors = 0; + + node *n = NULL; + WALK_LIST(n, *node_list) { + const test_node_t *tmp_node = (test_node_t *)n; + assert(tmp_node); + + knot_dname_t *owner = + dname_from_test_dname(tmp_node->owner); + if (owner == NULL) { + return 0; + } + tmp = knot_node_new(owner, + (knot_node_t *)tmp_node->parent, 0); + if (tmp == NULL || + (strncmp((char *)tmp->owner->name, + (char *)tmp_node->owner->wire, + tmp->owner->size) != 0) || + tmp->parent != (knot_node_t *)tmp_node->parent || + tmp->rrset_tree == NULL) { + errors++; + diag("Failed to create node structure"); + } + knot_node_free(&tmp, 0, 0); + } + + return (errors == 0); +} + +static int test_node_add_rrset(list *rrset_list) +{ + knot_node_t *tmp; + knot_rrset_t *rrset; + int errors = 0; + + node *n = NULL; + WALK_LIST(n, *rrset_list) { + test_rrset_t *test_rrset = (test_rrset_t *)n; + rrset = rrset_from_test_rrset(test_rrset); + if (rrset == NULL) { + diag("Could not create rrset from test data"); + return 0; + } + + /* create node from test_node structure. Always the first one.*/ + knot_dname_t *owner = + dname_from_test_dname(test_rrset->owner); + if (owner == NULL) { + diag("Could not create owner from test data"); + return 0; + } + + tmp = knot_node_new(owner, NULL, 0); + + if (knot_node_add_rrset(tmp, rrset, 0) != 0) { + errors++; + diag("Failed to insert rrset into node"); + } + + /* check if rrset is really there */ + + const knot_rrset_t *rrset_from_node = NULL; + if ((rrset_from_node = + knot_node_rrset(tmp, rrset->type)) == NULL) { + errors++; + diag("Inserted rrset could not be found"); + continue; + } + + /* compare rrset from node with original rrset */ + + const knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrset->type); + + int cmp = 0; + + if ((rrset_from_node->rdata == NULL) && + (rrset->rdata == NULL)) { + cmp = 0; + } else if ((rrset_from_node->rdata != NULL) && + (rrset->rdata != NULL)) { + cmp = knot_rdata_compare(rrset_from_node->rdata, + rrset->rdata, + desc->wireformat); + } else { /* one is not NULL and other is -> error */ + cmp = 1; + } + + if (!((rrset_from_node->type == rrset->type) && + (rrset_from_node->rclass == rrset->rclass) && + (rrset_from_node->ttl == rrset->ttl) && + (rrset_from_node->rrsigs == rrset->rrsigs) && + (cmp == 0))) { + errors++; + diag("Values in found rrset are wrong"); + } + + knot_node_free(&tmp, 1, 0); + } + + return (errors == 0); +} + +//static int test_node_get_rrset() +//{ +// knot_node_t *tmp; +// knot_rrset_t *rrset; +// int errors = 0; + +// knot_node_t *nodes[TEST_NODES]; + +// for (int i = 0; i < TEST_NODES && !errors; i++) { +// tmp = knot_node_new(&test_nodes[i].owner, +// test_nodes[i].parent); +// nodes[i] = tmp; +// for (int j = 0; j < RRSETS; j++) { +// knot_node_add_rrset(tmp, &rrsets[j]); +// } +// } + +// for (int i = 0; i < TEST_NODES && !errors; i++) { +// for (int j = 0; j < RRSETS; j++) { +// rrset = &rrsets[j]; +// if (knot_node_rrset(nodes[i], rrset->type) +// != rrset) { +// errors++; +// diag("Failed to get proper rrset from node"); +// } +// } +// knot_node_free(&nodes[i], 0); +// } + +// return (errors == 0); +//} + +//static int test_node_get_parent() +//{ +// knot_node_t *tmp; +// knot_rrset_t *rrset; +// int errors = 0; + +// knot_node_t *nodes[TEST_NODES]; + +// for (int i = 0; i < TEST_NODES && !errors; i++) { +// tmp = knot_node_new(&test_nodes[i].owner, +// test_nodes[i].parent); +// nodes[i] = tmp; +// rrset = &rrsets[i]; +// knot_node_add_rrset(tmp, rrset); +// } + +// for (int i = 0; i < TEST_NODES && !errors; i++) { +// rrset = &rrsets[i]; +// if (knot_node_parent(nodes[i]) != test_nodes[i].parent) { +// errors++; +// diag("Failed to get proper parent from node"); +// } +// knot_node_free(&nodes[i], 0); +// } +// return (errors == 0); +//} + +//static int test_node_sorting() +//{ +// knot_node_t *tmp = NULL; +// knot_rrset_t *rrset = NULL; +// int errors = 0; + +// knot_dname_t *owner = dname_from_test_dname(test_nodes[0].owner); + +// tmp = knot_node_new(owner, +// (knot_node_t *)test_nodes[0].parent); + +// /* Will add rrsets to node. */ +// knot_node_add_rrset(tmp, rrset); +// } + +// const skip_node_t *node = skip_first(tmp->rrsets); + +// int last = *((uint16_t *)node->key); + +// /* TODO there is now an API function knot_node_rrsets ... */ + +// /* Iterates through skip list and checks, whether it is sorted. */ + +// while ((node = skip_next(node)) != NULL) { +// if (last > *((uint16_t *)node->key)) { +// errors++; +// diag("RRset sorting error"); +// } +// last = *((uint16_t *)node->key); +// } + +// knot_node_free(&tmp, 1); +// return (errors == 0); +//} + +//static int test_node_delete() +//{ +// int errors = 0; + +// knot_node_t *tmp_node; + +// for (int i = 0; i < TEST_NODES; i++) { +// knot_dname_t *owner = +// dname_from_test_dname(test_nodes[i].owner); +// tmp_node = knot_node_new(owner, +// (knot_node_t *)test_nodes[i].parent); + +// knot_node_free(&tmp_node, 1); + +// errors += (tmp_node != NULL); +// } + +// return (errors == 0); +//} + +//static int test_node_set_parent() +//{ +// knot_node_t *tmp_parent = (knot_node_t *)0xABCDEF; +// int errors = 0; + +// knot_node_t *tmp_node; + +// for (int i = 0; i < TEST_NODES; i++) { +// tmp_node = knot_node_new(&test_nodes[i].owner, +// test_nodes[i].parent); + +// knot_node_set_parent(tmp_node, tmp_parent); + +// if (tmp_node->parent != tmp_node->parent) { +// diag("Parent node is wrongly set."); +// errors++; +// } +// knot_node_free(&tmp_node, 0); +// } +// return (errors == 0); +//} + +//static int test_node_free_rrsets() +//{ +// int errors = 0; + +// knot_node_t *tmp_node; + +// for (int i = 0; i < TEST_NODES; i++) { +// knot_dname_t *owner = +// dname_from_test_dname(test_nodes[i].owner); +// if (owner == NULL) { +// return 0; +// } + +// tmp_node = knot_node_new(owner, +// (knot_node_t *)test_nodes[i].parent); + +// knot_node_free_rrsets(tmp_node, 0); + +// errors += (tmp_node->rrsets != NULL); + +// knot_node_free(&tmp_node, 1); +// } +// return (errors == 0); +//} + +static const int KNOT_NODE_TEST_COUNT = 2; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_node_tests_count(int argc, char *argv[]) +{ + return KNOT_NODE_TEST_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_node_tests_run(int argc, char *argv[]) +{ + test_data_t *data = data_for_knot_tests; + int res = 0, + res_final = 1; + + res = test_node_create(&data->node_list); + ok(res, "node: create"); + res_final *= res; + + + ok((res = test_node_add_rrset(&data->rrset_list)), "node: add"); + res_final *= res; + +// ok((res = test_node_get_rrset()), "node: get"); +// res_final *= res; + +// ok((res = test_node_get_parent()), "node: get parent"); +// res_final *= res; + +// ok((res = test_node_set_parent()), "node: set parent"); +// res_final *= res; + +// ok((res = test_node_sorting()), "node: sort"); +// res_final *= res; + +// ok((res = test_node_free_rrsets()), "node: free rrsets"); +// res_final *= res; + +// endskip; + +// ok((res = test_node_delete()), "node: delete"); +// //res_final *= res; + + return res_final; +} diff --git a/src/tests/libknot/realdata/libknot/node_tests_realdata.h b/src/tests/libknot/realdata/libknot/node_tests_realdata.h new file mode 100644 index 0000000..a90179f --- /dev/null +++ b/src/tests/libknot/realdata/libknot/node_tests_realdata.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_NODE_TESTS_H_ +#define _KNOTD_NODE_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api node_tests_api; + +#endif /* _KNOTD_NODE_TESTS_H_ */ diff --git a/src/tests/libknot/realdata/libknot/packet_tests_realdata.c b/src/tests/libknot/realdata/libknot/packet_tests_realdata.c new file mode 100644 index 0000000..08c0882 --- /dev/null +++ b/src/tests/libknot/realdata/libknot/packet_tests_realdata.c @@ -0,0 +1,679 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/* blame: jan.kadlec@nic.cz */ + +#include <assert.h> + +#include <config.h> +#include "knot/common.h" +#include "packet_tests_realdata.h" +#include "libknot/util/error.h" +#include "libknot/packet/packet.h" +#include "libknot/packet/response.h" +/* *test_t structures */ +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" +#ifdef TEST_WITH_LDNS +#include "ldns/ldns.h" +#endif + +static int packet_tests_count(int argc, char *argv[]); +static int packet_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api packet_tests_api = { + "DNS library - packet", //! Unit name + &packet_tests_count, //! Count scheduled tests + &packet_tests_run //! Run scheduled tests +}; + +#ifdef TEST_WITH_LDNS +/* Compares one rdata knot with rdata from ldns. + * Comparison is done through comparing wireformats. + * Returns 0 if rdata are the same, 1 otherwise + */ +int compare_rr_rdata(knot_rdata_t *rdata, ldns_rr *rr, + uint16_t type) +{ + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(type); + for (int i = 0; i < rdata->count; i++) { + /* check for ldns "descriptors" as well */ + + if (desc->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME) { + if (rdata->items[i].dname->size != + ldns_rdf_size(ldns_rr_rdf(rr, i))) { + diag("%s", rdata->items[i].dname->name); + diag("%s", ldns_rdf_data(ldns_rr_rdf(rr, i))); + diag("%d", ldns_rdf_size(ldns_rr_rdf(rr, i))); + diag("%d", rdata->items[i].dname->size); + diag("Dname sizes in rdata differ"); + return 1; + } + if (compare_wires_simple(rdata->items[i].dname->name, + ldns_rdf_data(ldns_rr_rdf(rr, i)), + rdata->items[i].dname->size) != 0) { + diag("%s", rdata->items[i].dname->name); + diag("%s", ldns_rdf_data(ldns_rr_rdf(rr, i))); + diag("Dname wires in rdata differ"); + return 1; + } + } else { + /* Compare sizes first, then actual data */ + if (rdata->items[i].raw_data[0] != + ldns_rdf_size(ldns_rr_rdf(rr, i))) { + /* \note ldns stores the size including the + * length, knot does not */ + diag("Raw data sizes in rdata differ"); + diag("knot: %d ldns: %d", + rdata->items[i].raw_data[0], + ldns_rdf_size(ldns_rr_rdf(rr, i))); +// hex_print((char *) +// (rdata->items[i].raw_data + 1), +// rdata->items[i].raw_data[0]); +// hex_print((char *)ldns_rdf_data(ldns_rr_rdf(rr, +// i)), +// ldns_rdf_size(ldns_rr_rdf(rr, i))); + if (abs(rdata->items[i].raw_data[0] - + ldns_rdf_size(ldns_rr_rdf(rr, i))) != 1) { + return 1; + } + } + if (compare_wires_simple((uint8_t *) + (rdata->items[i].raw_data + 1), + ldns_rdf_data(ldns_rr_rdf(rr, i)), + rdata->items[i].raw_data[0]) != 0) { +// hex_print((char *) +// (rdata->items[i].raw_data + 1), +// rdata->items[i].raw_data[0]); +// hex_print((char *) +// ldns_rdf_data(ldns_rr_rdf(rr, i)), +// rdata->items[i].raw_data[0]); + diag("Raw data wires in rdata differ in item " + "%d", i); + + return 1; + } + } + } + + return 0; +} + +int compare_rrset_w_ldns_rr(const knot_rrset_t *rrset, + ldns_rr_list *rr_set, char check_rdata) +{ + /* We should have only one rrset from ldns, although it is + * represented as rr_list ... */ + + int errors = 0; + + ldns_rr *rr = ldns_rr_list_rr(rr_set, 0); + assert(rr); + assert(rrset); + + /* compare headers */ + + if (rrset->owner->size != ldns_rdf_size(ldns_rr_owner(rr))) { + char *tmp_dname = knot_dname_to_str(rrset->owner); + diag("RRSet owner names differ in length"); + diag("ldns: %d, knot: %d", ldns_rdf_size(ldns_rr_owner(rr)), + rrset->owner->size); + diag("%s", tmp_dname); + diag("%s", ldns_rdf_data(ldns_rr_owner(rr))); + free(tmp_dname); + errors++; + } + + if (compare_wires_simple(rrset->owner->name, + ldns_rdf_data(ldns_rr_owner(rr)), + rrset->owner->size) != 0) { + diag("RRSet owner wireformats differ"); + diag("%s \\w %s\n", rrset->owner->name, + ldns_rdf_data(ldns_rr_owner(rr))); + errors++; + } + + if (rrset->type != ldns_rr_get_type(rr)) { + diag("RRset types differ"); + diag("knot type: %d Ldns type: %d", rrset->type, + ldns_rr_get_type(rr)); + errors++; + } + + if (rrset->rclass != ldns_rr_get_class(rr)) { + diag("RRset classes differ"); + errors++; + } + + if (rrset->ttl != ldns_rr_ttl(rr)) { + diag("RRset TTLs differ"); + diag("knot: %d ldns: %d", rrset->ttl, ldns_rr_ttl(rr)); + errors++; + } + + /* compare rdatas */ + + if (rrset->rdata == NULL) { + diag("RRSet has no RDATA!"); + return errors; + } + knot_rdata_t *tmp_rdata = rrset->rdata; + + int i = 0; + + while ((rr = ldns_rr_list_pop_rr(rr_set))) { + assert(rr); + + if (compare_rr_rdata(tmp_rdata, rr, rrset->type) != 0) { + diag("Rdata differ"); + return 1; + } + + tmp_rdata = tmp_rdata->next; + i++; + } + +//// if (check_rdata) { +//// if (compare_rr_rdata(rrset->rdata, rr, rrset->type) != 0) { +//// diag("Rdata differ"); +//// errors++; +//// } +//// } + + return errors; +} + +int compare_rrsets_w_ldns_rrlist(const knot_rrset_t **rrsets, + ldns_rr_list *rrlist, int count) +{ + int errors = 0; + + /* There are no rrsets currenty. Everything is just rr */ + + ldns_rr_list *rr_set = NULL; + + ldns_rr_list_sort(rrlist); + + if (count < 0) { + return 0; + } + + for (int i = 0; i < count ; i++) { + /* normally ldns_pop_rrset or such should be here */ + + rr_set = ldns_rr_list_pop_rrset(rrlist); + /* Get one rr from list. */ + ldns_rr *rr = ldns_rr_list_rr(rr_set, 0); + assert(rr); + + if (rr_set == NULL) { + diag("Ldns and knot structures have different " + "counts of rrsets."); + diag("knot: %d ldns: %d", + count, (count - 1) - i); + return -1; + } + +// diag("RRset from ldns is %d long", ldns_rr_list_rr_count(rr_set)); + +// diag("Got type from ldns: %d (%d)\n", ldns_rr_get_type(rr), i); + + int j = 0; + for (j = 0; j < count; j++) { +// diag("Got type from knot: %d\n", rrsets[j]->type); + if (rrsets[j]->type == ldns_rr_get_type(rr) && + rrsets[j]->owner->size == + ldns_rdf_size(ldns_rr_owner(rr)) && + (compare_wires_simple(ldns_rdf_data(ldns_rr_owner(rr)), rrsets[j]->owner->name, + rrsets[j]->owner->size) == 0)) { + errors += compare_rrset_w_ldns_rr(rrsets[j], + rr_set, 1); + break; + } + } + if (j == count) { + diag("There was no RRSet of the same type!"); +// errors++; + } + } + + return errors; +} + +int check_packet_w_ldns_packet(knot_packet_t *packet, + ldns_pkt *ldns_packet, + int check_header, + int check_question, + int check_body, + int check_edns) +{ + int errors = 0; + if (check_header) { +// if (packet->header.id != ldns_pkt_id(ldns_packet)) { +// diag("response ID does not match - %d %d", +// packet->header.id, +// ldns_pkt_id(ldns_packet)); +// errors++; +// } + + /* qdcount is always 1 in knot's case */ + + /* TODO check flags1 and flags2 - no API for that, + * write my own */ + + if (packet->header.ancount != + ldns_pkt_ancount(ldns_packet)) { + diag("Answer RRSet count wrongly converted"); + errors++; + } + + if (packet->header.nscount != + ldns_pkt_nscount(ldns_packet)) { + diag("Authority RRSet count wrongly converted.\n" + "got %d should be %d", + packet->header.nscount, + ldns_pkt_nscount(ldns_packet)); + errors++; + } + + /* - 1 because ldns does not include OPT_RR to additional " + "section */ + int minus = (!ldns_pkt_edns_version(ldns_packet)) ? 1 : 0; +// int minus = 0; + + if ((packet->header.arcount - minus) != + ldns_pkt_arcount(ldns_packet)) { + diag("Additional RRSet count wrongly converted.\n" + "got %d should be %d", + packet->header.arcount, + ldns_pkt_arcount(ldns_packet)); + errors++; + } + + /*!< \todo Check OPT RR! */ + + if (errors) { + return errors; + } + } + /* Header checked */ + + /* Question section */ + + int ret = 0; + if (check_question) { + knot_rrset_t *question_rrset = + knot_rrset_new(packet-> + question.qname, + packet-> + question.qtype, + packet-> + question.qclass, + 3600); + + if ((ret = compare_rrset_w_ldns_rr(question_rrset, + ldns_pkt_question(ldns_packet), 0)) != 0) { + diag("Question rrsets wrongly converted"); + errors++; + } + knot_rrset_free(&question_rrset); + } + + if (check_body) { + + /* other RRSets */ + + if ((ret = + compare_rrsets_w_ldns_rrlist(packet->answer, + ldns_pkt_answer(ldns_packet), + knot_packet_answer_rrset_count(packet))) != 0) { + diag("Answer rrsets wrongly converted"); + errors++; + } + + + + if ((ret = compare_rrsets_w_ldns_rrlist(packet->authority, + ldns_pkt_authority(ldns_packet), + knot_packet_authority_rrset_count(packet))) != 0) { + diag("Authority rrsets wrongly converted - %d", ret); + errors++; + } + + /* We don't want to test OPT RR, which is the last rrset + * in the additional section */ + + if ((ret = compare_rrsets_w_ldns_rrlist(packet->additional, + ldns_pkt_additional(ldns_packet), + knot_packet_additional_rrset_count(packet) - 1)) != 0) { + diag("Additional rrsets wrongly converted"); + errors++; + } + + } + + if (check_edns) { + + /* OPT RR */ + + if (ldns_pkt_edns(ldns_packet)) { + /* if (packet->edns_packet == NULL) { + diag("ldns has edns section, knot has not"); + return 1; + } */ + + knot_opt_rr_t *opt = &(packet->opt_rr); + + if (ldns_pkt_edns_udp_size(ldns_packet) != + knot_edns_get_payload(opt)) { + diag("Payloads in EDNS are different"); + errors++; + } + + if (ldns_pkt_edns_version(ldns_packet) != + knot_edns_get_version(opt)) { + diag("Versions in EDNS are different"); + errors++; + } + + if (ldns_pkt_edns_extended_rcode(ldns_packet) != + knot_edns_get_ext_rcode(opt)) { + diag("Extended rcodes in EDNS are different"); + errors++; + } + + /* TODO parse flags do bit, z value ... */ + } + } + + return errors; +} +#endif + +extern knot_rrset_t *rrset_from_test_rrset(const test_rrset_t *test_rrset); +extern knot_dname_t *dname_from_test_dname(const test_dname_t *test_dname); + +/* Converts knot_rrset_t to knot_opt_rr */ +static knot_opt_rr_t *opt_rrset_to_opt_rr(knot_rrset_t *rrset) +{ + if (rrset == NULL) { + return NULL; + } + + knot_opt_rr_t *opt_rr = knot_edns_new(); + assert(opt_rr); + + knot_edns_set_payload(opt_rr, rrset->rclass); + + knot_edns_set_ext_rcode(opt_rr, rrset->ttl); + + /* TODO rdata? mostly empty, I guess, but should be done */ + + return opt_rr; +} + +knot_packet_t *packet_from_test_response(test_response_t *test_packet) +{ + knot_rrset_t *parsed_opt = NULL; + + for (int j = 0; j < test_packet->arcount; j++) { + if (test_packet->additional[j]->type == + KNOT_RRTYPE_OPT) { + parsed_opt = + rrset_from_test_rrset( + test_packet->additional[j]); + assert(parsed_opt); + break; + } + } + + knot_opt_rr_t *opt_rr = NULL; + if (parsed_opt != NULL) { + opt_rr = + opt_rrset_to_opt_rr(parsed_opt); + assert(opt_rr); + } else { + opt_rr = NULL; + } + + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(packet); + knot_packet_set_max_size(packet, 1024 * 10); + + if (opt_rr != NULL) { + packet->opt_rr = *opt_rr; + } + + packet->header.id = test_packet->id; + packet->header.qdcount = test_packet->qdcount; + + packet->question.qname = dname_from_test_dname(test_packet->qname); + packet->size += test_packet->qname->size; + packet->question.qtype = test_packet->qtype; + packet->question.qclass = test_packet->qclass; + + packet->size += 4; + + packet->answer = + malloc(sizeof(knot_rrset_t *) * test_packet->ancount); + assert(packet->answer); + + for (int j = 0; j < test_packet->ancount; j++) { + if (&(test_packet->answer[j])) { + packet->answer[packet->an_rrsets++] = + rrset_from_test_rrset(test_packet->answer[j]); + } + } + + packet->authority = + malloc(sizeof(knot_rrset_t *) * test_packet->nscount); + assert(packet->answer); + + for (int j = 0; j < test_packet->nscount; j++) { + if (&(test_packet->authority[j])) { + packet->authority[packet->ns_rrsets++] = + rrset_from_test_rrset(test_packet->authority[j]); + } + } + + packet->authority = + malloc(sizeof(knot_rrset_t *) * test_packet->arcount); + assert(packet->answer); + + for (int j = 0; j < test_packet->arcount; j++) { + if (&(test_packet->additional[j])) { + if (test_packet->additional[j]->type == + KNOT_RRTYPE_OPT) { + continue; + } + packet->additional[packet->ar_rrsets++] = + rrset_from_test_rrset(test_packet->additional[j]); + } + } + + return packet; +} + +static int test_packet_parse_from_wire(list raw_response_list) +{ +#ifdef TEST_WITH_LDNS + int errors = 0; + + node *n = NULL; + WALK_LIST(n ,raw_response_list) { + test_raw_packet_t *raw_packet = (test_raw_packet_t *)n; + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + int ret = 0; + if ((ret = + knot_packet_parse_from_wire(packet, raw_packet->data, + raw_packet->size, 0)) != + KNOT_EOK) { + diag("Warning: could not parse wire! " + "(might be caused by malformed dump) - " + "knot error: %s", knot_strerror(ret)); +// hex_print(raw_packet->data, +// raw_packet->size); + continue; + } + + ldns_pkt *ldns_packet = NULL; + + if (ldns_wire2pkt(&ldns_packet, raw_packet->data, + raw_packet->size) != LDNS_STATUS_OK) { + diag("Could not parse wire using ldns"); + diag("%s", + ldns_get_errorstr_by_id(ldns_wire2pkt(&ldns_packet, + raw_packet->data, + raw_packet->size))); + return 0; + } + + if (check_packet_w_ldns_packet(packet, ldns_packet, 1, + 1, 1, 1) != 0) { + diag("Wrongly created packet"); + errors++; + } + + ldns_pkt_free(ldns_packet); + knot_packet_free(&packet); + } + + return (errors == 0); +#endif +#ifndef TEST_WITH_LDNS + diag("Enable ldns to test this feature"); + return 0; +#endif +} + +static int test_packet_to_wire(list raw_response_list) +{ +#ifdef TEST_WITH_LDNS + int errors = 0; + /*!< \todo test queries too! */ +// /* We'll need data from both lists. */ +// test_packet_t **test_packets = NULL; +// uint test_packet_count = 0; +// node *n = NULL; +// WALK_LIST(n, response_list) { +// test_packet_count++; +// } + +// test_packets = +// malloc(sizeof(test_packet_t *) * test_packet_count); +// assert(test_packets); +// int i = 0; +// WALK_LIST(n, response_list) { +// test_packets[i++] = (test_response_t *)n; +// } + +// test_raw_packet_t **test_packets = NULL; +// uint test_packet_count = 0; +// n = NULL; +// WALK_LIST(n, raw_response_list) { +// test_packet_count++; +// } + +// test_packets = +// malloc(sizeof(test_raw_packet_t *) * test_packet_count); +// assert(test_packets); +// i = 0; +// WALK_LIST(n, raw_response_list) { +// test_packets[i++] = (test_raw_packet_t *)n; +// } + +// assert(test_response_count == test_packet_count); + node *n = NULL; + WALK_LIST(n, raw_response_list) { + /* Create packet from raw response. */ + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(packet); + test_raw_packet_t *raw_packet = (test_raw_packet_t *)n; + if (knot_packet_parse_from_wire(packet, raw_packet->data, + raw_packet->size, 0) != + KNOT_EOK) { + diag("Warning: could not parse wire! " + "(might be caused be malformed dump)"); + continue; + } + knot_packet_set_max_size(packet, 1024 * 10); + /* Use this packet to create wire */ + uint8_t *wire = NULL; + size_t size = 0; + if (knot_packet_to_wire(packet, &wire ,&size) != KNOT_EOK) { + diag("Could not convert packet to wire"); + } + /* Create ldns packet from created wire */ + ldns_pkt *ldns_packet = NULL; + + if (ldns_wire2pkt(&ldns_packet, wire, + size) != LDNS_STATUS_OK) { + diag("Could not parse wire using ldns"); + /*!< \todo get rid of this */ + diag("%s", + ldns_get_errorstr_by_id(ldns_wire2pkt(&ldns_packet, + wire, + size))); + return 0; + } + + if (check_packet_w_ldns_packet(packet, ldns_packet, 1, 1, 1, + 1) != 0) { + diag("Packet wrongly converted to wire!"); + errors++; + } + knot_packet_free(&packet); + ldns_pkt_free(ldns_packet); + } + + return (errors == 0); +#endif +#ifndef TEST_WITH_LDNS + diag("Enable ldns to test this feature!"); + return 0; +#endif +} + +static const uint KNOT_PACKET_TEST_COUNT = 2; + +static int packet_tests_count(int argc, char *argv[]) +{ + return KNOT_PACKET_TEST_COUNT; +} + +static int packet_tests_run(int argc, char *argv[]) +{ + const test_data_t *data = data_for_knot_tests; + + int res = 0; + todo(); + ok(res = test_packet_parse_from_wire(data->raw_packet_list), + "packet: from wire"); + diag("Resolve issue with arcount."); + endtodo; +// skip(!res, 1); + ok(test_packet_to_wire(data->raw_packet_list), "packet: to wire"); +// endskip; + + return 1; +} + diff --git a/src/tests/libknot/realdata/libknot/packet_tests_realdata.h b/src/tests/libknot/realdata/libknot/packet_tests_realdata.h new file mode 100644 index 0000000..c0e0479 --- /dev/null +++ b/src/tests/libknot/realdata/libknot/packet_tests_realdata.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_PACKET_REALDATA_TESTS_H_ +#define _KNOTD_PACKET_REALDATA_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api packet_tests_api; + +#endif /* _KNOTD_PACKET_REALDATA_TESTS_H_ */ diff --git a/src/tests/libknot/realdata/libknot/rdata_tests_realdata.c b/src/tests/libknot/realdata/libknot/rdata_tests_realdata.c new file mode 100644 index 0000000..f4ba64c --- /dev/null +++ b/src/tests/libknot/realdata/libknot/rdata_tests_realdata.c @@ -0,0 +1,329 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <assert.h> + +#include "tests/libknot/realdata/libknot/rdata_tests_realdata.h" +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" +#include "libknot/common.h" +#include "libknot/rdata.h" +#include "libknot/util/descriptor.h" +#include "libknot/util/utils.h" +#include "libknot/util/error.h" + +static int knot_rdata_tests_count(int argc, char *argv[]); +static int knot_rdata_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api rdata_tests_api = { + "DNS library - rdata", //! Unit name + &knot_rdata_tests_count, //! Count scheduled tests + &knot_rdata_tests_run //! Run scheduled tests +}; + +/* + * Unit implementation. + */ + +extern int check_domain_name(const knot_dname_t *dname, + const test_dname_t *test_dname); + +extern int compare_wires_simple(uint8_t *wire1, uint8_t *wire2, uint count); + +/*! + * \brief Checks if all RDATA items in the given RDATA structure are correct. + * + * \return Number of errors encountered. Error is either if some RDATA item + * is not set (i.e. NULL) or if it has other than the expected value. + */ +static int check_rdata(const knot_rdata_t *rdata, + const test_rdata_t *test_rdata) +{ + assert(rdata != NULL); + assert(test_rdata != NULL); + + int errors = 0; + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(test_rdata->type); + //note("check_rdata(), RRType: %u", rrtype); + + for (int i = 0; i < desc->length; ++i) { + + switch (desc->wireformat[i]) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: + if (check_domain_name(rdata->items[i].dname, + test_rdata->items[i].dname) != 0) { + errors++; + diag("Rdata contains wrong dname item"); + } + break; + default: + if (test_rdata->items[i].raw_data[0] != + rdata->items[i].raw_data[0]) { + diag("Raw rdata in items have different " + "sizes!"); + return 0; + } + + errors += + compare_wires_simple( + (uint8_t *)test_rdata->items[i].raw_data, + (uint8_t *)rdata->items[i].raw_data, + (uint)rdata->items[i].raw_data[0]); + } + } + return errors; +} + +extern knot_dname_t *dname_from_test_dname(test_dname_t *test_dname); + +///*! +// * \brief Tests knot_rdata_set_item(). +// * +// * \retval > 0 on success. +// * \retval 0 otherwise. +// */ +//static int test_rdata_set_item(list rdata_list) +//{ +// node *n = NULL; +// WALK_LIST(n, rdata_list) { +// knot_rdata_t *rdata = knot_rdata_new(); +// assert(rdata); +// test_rdata_t *test_rdata = (test_rdata_t *)n; + +// knot_rrtype_descriptor_t *desc = +// knot_rrtype_descriptor_by_type(test_rdata->type); +// for (int i = 0; i < test_rdata->count; i++) { +// knot_rdata_item_t item; +// if (test_rdata->items[i].type == TEST_ITEM_DNAME) { +// item.dname = +// dname_from_test_dname( +// test_rdata->items[i].dname); +// } else { +// item.raw_data = test_rdata->items[i].raw_data; +// } +// if (knot_rdata_set_item(rdata, i, item) != 0) { +// diag("Could not set item, rdata count: %d", +// rdata->count); +// return 0; +// } +// } + +// /* Check that all items are OK */ +// if (check_rdata(rdata, test_rdata) != 0) { +// return 0; +// } +// } +// return 1; +//} + +static knot_rdata_item_t *items_from_test_items(test_item_t *test_items, + size_t count) +{ + knot_rdata_item_t *items = + malloc(sizeof(knot_rdata_item_t) * count); + assert(items); + for (int i = 0; i < count; i++) { + if (test_items[i].type == TEST_ITEM_DNAME) { + items[i].dname = + dname_from_test_dname(test_items[i].dname); + } else { + items[i].raw_data = test_items[i].raw_data; + } + } + + return items; +} + +static int test_rdata_set_items(list rdata_list) +{ + int errors = 0; + + // check error return values + knot_rdata_t *rdata = knot_rdata_new(); + assert(rdata); + + node *n = NULL; + WALK_LIST(n, rdata_list) { + test_rdata_t *test_rdata = (test_rdata_t *)n; + knot_rdata_t *rdata = knot_rdata_new(); + + /* create dnslib items from tests items. */ + knot_rdata_item_t *items = + items_from_test_items(test_rdata->items, + test_rdata->count); + + assert(items); + assert(test_rdata->count > 0); + assert(rdata->items == NULL); + + if (knot_rdata_set_items(rdata, items, + test_rdata->count) != 0) { + diag("Could not set items!"); + errors++; + } + + if (check_rdata(rdata, test_rdata) != 0) { + diag("Wrong rdata after knot_rdata_set_items!"); + errors++; + } + + knot_rdata_free(&rdata); + } + + return (errors == 0); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tests knot_rdata_get_item(). + * + * \retval > 0 on success. + * \retval 0 otherwise. + */ +static int test_rdata_get_item(list rdata_list) +{ + node *n = NULL; + WALK_LIST(n, rdata_list) { + test_rdata_t *test_rdata = (test_rdata_t *)n; + knot_rdata_t *rdata = knot_rdata_new(); + assert(rdata); + knot_rdata_item_t *items = + items_from_test_items(test_rdata->items, + test_rdata->count); + assert(knot_rdata_set_items(rdata, items, + test_rdata->count) == 0); + knot_rdata_item_t *new_items = + malloc(sizeof(knot_rdata_item_t) * test_rdata->count); + for (int i = 0; i < test_rdata->count; i++) { + knot_rdata_item_t *item = + knot_rdata_get_item(rdata, i); + if (item == NULL) { + diag("Could not get item"); + return 0; + } + new_items[i] = *item; + } + + knot_rdata_free(&rdata); + free(items); + + knot_rdata_t *new_rdata = knot_rdata_new(); + assert(new_rdata); + assert(knot_rdata_set_items(new_rdata, + new_items, + test_rdata->count) == 0); + + if (check_rdata(new_rdata, test_rdata) != 0) { + diag("Wrong rdata created using rdata_get_item"); + return 0; + } + + knot_rdata_free(&new_rdata); + free(new_items); + } + + return 1; +} + +//static int test_rdata_wire_size() +//{ +// knot_rdata_t *rdata; +// int errors = 0; + +// // generate some random data +// uint8_t data[KNOT_MAX_RDATA_WIRE_SIZE]; +// generate_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE); + +// for (int i = 0; i <= KNOT_RRTYPE_LAST; ++i) { +// rdata = knot_rdata_new(); + +// int size = +// fill_rdata(data, KNOT_MAX_RDATA_WIRE_SIZE, i, rdata); + +// if (size < 0) { +// ++errors; +// } else { +// int counted_size = knot_rdata_wire_size(rdata, +// knot_rrtype_descriptor_by_type(i)->wireformat); +// if (size != counted_size) { +// diag("Wrong wire size computed (type %d):" +// " %d (should be %d)", +// i, counted_size, size); +// ++errors; +// } +// } + +// knot_rrtype_descriptor_t *desc = +// knot_rrtype_descriptor_by_type(i); + +// for (int x = 0; x < desc->length; x++) { +// if (desc->wireformat[x] == +// KNOT_RDATA_WF_UNCOMPRESSED_DNAME || +// desc->wireformat[x] == +// KNOT_RDATA_WF_COMPRESSED_DNAME || +// desc->wireformat[x] == +// KNOT_RDATA_WF_LITERAL_DNAME) { +// knot_dname_free(&(rdata->items[x].dname)); +// } +// } +// knot_rdata_free(&rdata); +// } + +// return (errors == 0); +//} + +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ + +static const int KNOT_RDATA_TEST_COUNT = 2; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_rdata_tests_count(int argc, char *argv[]) +{ + return KNOT_RDATA_TEST_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_rdata_tests_run(int argc, char *argv[]) +{ + test_data_t *data = data_for_knot_tests; + int res = 0, + res_final = 1; + + ok(res = test_rdata_set_items(data->rdata_list), + "rdata: set items all at once"); + res_final *= res; + + ok(res = test_rdata_get_item(data->rdata_list), + "rdata: get item"); + res_final *= res; + +// ok(res = test_rdata_set_item(data->rdata_list), +// "rdata: set items one-by-one"); +// res_final *= res; + + return res_final; +} diff --git a/src/tests/libknot/realdata/libknot/rdata_tests_realdata.h b/src/tests/libknot/realdata/libknot/rdata_tests_realdata.h new file mode 100644 index 0000000..570b2b1 --- /dev/null +++ b/src/tests/libknot/realdata/libknot/rdata_tests_realdata.h @@ -0,0 +1,53 @@ +/*! + * \file rdata_tests.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * Contains unit tests for RDATA (knot_rdata_t) and RDATA item + * (knot_rdata_item_t) structures. + * + * Contains tests for: + * - creating empty RDATA structure with or without reserved space. + * - setting RDATA items one-by-one + * - setting RDATA items all at once + * + * As for now, the tests use several (TEST_RDATAS) RDATA structures, each + * with different number of RDATA items (given by test_rdatas). These are all + * initialized to pointers derived from RDATA_ITEM_PTR (first is RDATA_ITEM_PTR, + * second RDATA_ITEM_PTR + 1, etc.). The functions only test if the pointer + * is set properly. + * + * \todo It may be better to test also some RDATAs with predefined contents, + * such as some numbers, some domain name, etc. For this purpose, we'd + * need RDATA descriptors (telling the types of each RDATA item within an + * RDATA). + * + * \todo It will be fine to test all possible output values of all functions, + * e.g. test whether knot_rdata_get_item() returns NULL when passed an + * illegal position, etc. + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_RDATA_TESTS_H_ +#define _KNOTD_RDATA_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api rdata_tests_api; + +#endif /* _KNOTD_RDATA_TESTS_H_ */ diff --git a/src/tests/libknot/realdata/libknot/response_tests_realdata.c b/src/tests/libknot/realdata/libknot/response_tests_realdata.c new file mode 100644 index 0000000..5bbda36 --- /dev/null +++ b/src/tests/libknot/realdata/libknot/response_tests_realdata.c @@ -0,0 +1,173 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ +/* blame: jan.kadlec@nic.cz */ + +#include <assert.h> + +#include "packet_tests_realdata.h" +#include "knot/common.h" +#include "libknot/util/error.h" +#include "libknot/packet/packet.h" +#include "libknot/packet/response.h" +/* *test_t structures */ +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" +#ifdef TEST_WITH_LDNS +#include "ldns/packet.h" +#endif + +static int response_tests_count(int argc, char *argv[]); +static int response_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api response_tests_api = { + "DNS library - response", //! Unit name + &response_tests_count, //! Count scheduled tests + &response_tests_run //! Run scheduled tests +}; + +#ifdef TEST_WITH_LDNS +extern int compare_wires_simple(uint8_t *wire1, uint8_t *wire2, uint count); +extern int compare_rr_rdata(knot_rdata_t *rdata, ldns_rr *rr, uint16_t type); +extern int compare_rrset_w_ldns_rr(const knot_rrset_t *rrset, + ldns_rr *rr, char check_rdata); +extern int compare_rrsets_w_ldns_rrlist(const knot_rrset_t **rrsets, + ldns_rr_list *rrlist, int count); + +extern int check_packet_w_ldns_packet(knot_packet_t *packet, + ldns_pkt *ldns_packet, + int check_header, + int check_question, + int check_body, + int check_edns); +#endif + +extern knot_packet_t *packet_from_test_response(test_response_t *response); + +static int test_response_init_from_query(list query_list) +{ + int errors = 0; + node *n = NULL; + WALK_LIST(n, query_list) { + knot_packet_t *response = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(response); + knot_packet_t *query = + packet_from_test_response((test_response_t *)n); + assert(query); + knot_packet_set_max_size(response, 1024 * 10); + if (knot_response_init_from_query(response, + query) != KNOT_EOK) { + diag("Could not init response from query!"); + errors++; + } + knot_packet_free(&response); + knot_packet_free(&query); + } + return (errors == 0); +} + +//static int test_response_add_opt(list opt_list) +//{ +// int errors = 0; +// node *n = NULL; +// WALK_LIST(n, query_list) { +// knot_packet_t *response = +// knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); +// assert(response); +// knot_opt_rr_t *opt = +// opt_from_test_opt((test_opt_t *)n); +// assert(query); +// if (knot_response_add_opt(response, +// opt, 1)!= KNOT_EOK) { +// diag("Could not add OPT RR to response!"); +// errors++; +// } +// knot_packet_free(&response); +// knot_opt_rr_free(&opt); +// } +// return (errors == 0); +//} + +extern knot_rrset_t *rrset_from_test_rrset(test_rrset_t *test_rrset); + +static int test_response_add_generic(int (*func)(knot_packet_t *, + const knot_rrset_t *, + int, int, int), + list rrset_list) +{ + /*!< \todo Now adding only one RRSet at the time, try more, use nodes */ + int errors = 0; + node *n = NULL; + WALK_LIST(n, rrset_list) { + knot_packet_t *response = + knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + assert(response); + knot_packet_set_max_size(response, + KNOT_PACKET_PREALLOC_RESPONSE * 100); + assert(knot_response_init(response) == KNOT_EOK); + + knot_rrset_t *rrset = + rrset_from_test_rrset((test_rrset_t *)n); + assert(rrset); + + int ret = 0; + if ((ret = func(response, rrset, 0, 1, 0)) != KNOT_EOK) { + diag("Could not add RRSet to response! Returned: %d", + ret); + diag("(owner: %s type %s)", + ((test_rrset_t *)n)->owner->str, + knot_rrtype_to_string(( + (test_rrset_t *)n)->type)); + errors++; + } + knot_packet_free(&response); + knot_rrset_deep_free(&rrset, 1, 1, 1); + } + + return (errors == 0); +} + +static void test_response_add_rrset(list rrset_list) +{ + ok(test_response_add_generic(knot_response_add_rrset_answer, + rrset_list), + "response: add answer rrset"); + ok(test_response_add_generic(knot_response_add_rrset_authority, + rrset_list), + "response: add authority rrset"); + ok(test_response_add_generic(knot_response_add_rrset_additional, + rrset_list), + "response: add additional rrset"); +} + +static const uint KNOT_response_TEST_COUNT = 4; + +static int response_tests_count(int argc, char *argv[]) +{ + return KNOT_response_TEST_COUNT; +} + +static int response_tests_run(int argc, char *argv[]) +{ + const test_data_t *data = data_for_knot_tests; + +// int res = 0; + ok(test_response_init_from_query(data->query_list), + "response: init from query"); + test_response_add_rrset(data->rrset_list); + return 1; +} diff --git a/src/tests/libknot/realdata/libknot/response_tests_realdata.h b/src/tests/libknot/realdata/libknot/response_tests_realdata.h new file mode 100644 index 0000000..731604b --- /dev/null +++ b/src/tests/libknot/realdata/libknot/response_tests_realdata.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_PACKET_response_TESTS_H_ +#define _KNOTD_PACKET_response_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api response_tests_api; + +#endif /* _KNOTD_PACKET_response_TESTS_H_ */ diff --git a/src/tests/libknot/realdata/libknot/rrset_tests_realdata.c b/src/tests/libknot/realdata/libknot/rrset_tests_realdata.c new file mode 100644 index 0000000..cb59f4c --- /dev/null +++ b/src/tests/libknot/realdata/libknot/rrset_tests_realdata.c @@ -0,0 +1,289 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "tests/libknot/realdata/libknot/rrset_tests_realdata.h" +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" +#include "libknot/common.h" +#include "libknot/util/descriptor.h" +#include "libknot/rrset.h" +#include "libknot/dname.h" +#include "libknot/rdata.h" +#include "libknot/util/utils.h" +#include "libknot/zone/node.h" +#include "libknot/util/debug.h" + +static int knot_rrset_tests_count(int argc, char *argv[]); +static int knot_rrset_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api rrset_tests_api = { + "DNS library - rrset", //! Unit name + &knot_rrset_tests_count, //! Count scheduled tests + &knot_rrset_tests_run //! Run scheduled tests +}; + +/*----------------------------------------------------------------------------*/ +/* + * Unit implementation. + */ + +/* count1 == count2 */ +int compare_wires_simple(uint8_t *wire1, uint8_t *wire2, uint count) +{ + int i = 0; + while (i < count && + wire1[i] == wire2[i]) { + i++; + } + return (!(count == i)); +} + + +knot_rrset_t *rrset_from_test_rrset(const test_rrset_t *test_rrset) +{ +// diag("owner: %s\n", test_rrset->owner->str); + knot_dname_t *owner = + knot_dname_new_from_wire(test_rrset->owner->wire, + test_rrset->owner->size, NULL); + +// diag("Created owner: %s (%p) from %p\n", knot_dname_to_str(owner), +// owner, test_rrset->owner); + + if (!owner) { + return NULL; + } + + knot_rrset_t *ret = knot_rrset_new(owner, test_rrset->type, + test_rrset->rclass, + test_rrset->ttl); + + /* Add rdata to rrset. */ + knot_rdata_t *rdata = knot_rdata_new(); + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(test_rrset->type); + + node *n = NULL; + WALK_LIST(n, test_rrset->rdata_list) { + test_rdata_t *test_rdata = (test_rdata_t *)n; + if (test_rdata->count != desc->length) { + diag("Malformed RRSet data!"); + knot_rdata_free(&rdata); + return ret; + } + assert(test_rdata->type == test_rrset->type); + /* Add items to the actual rdata. */ + rdata->items = malloc(sizeof(knot_rdata_item_t) * desc->length); + if (rdata->items == NULL) { + return NULL; + } +// diag("Rdata type: %s\n", knot_rrtype_to_string(test_rrset->type)); + for (int i = 0; i < desc->length; i++) { + if (desc->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME) { +// diag("%p\n", test_rdata->items[i].raw_data); + assert(test_rdata->items[i].type == TEST_ITEM_DNAME); + rdata->items[i].dname = + knot_dname_new_from_wire(test_rdata->items[i].dname->wire, + test_rdata->items[i].dname->size, + NULL); + } else { +// diag("%p\n", test_rdata->items[i].dname); + assert(test_rdata->items[i].type == TEST_ITEM_RAW_DATA); + assert(test_rdata->items[i].raw_data != NULL); + rdata->items[i].raw_data = test_rdata->items[i].raw_data; + } + } + } + + rdata->next = rdata; + + ret->rdata = rdata; + + return ret; +} + +extern int check_domain_name(knot_dname_t *dname, test_dname_t *test_dname); + +int check_rrset(const knot_rrset_t *rrset, + const test_rrset_t *test_rrset, + int check_rdata, int check_items, + int check_rrsigs) +{ + /* following implementation should be self-explanatory */ + int errors = 0; + + if (rrset == NULL) { + diag("RRSet not created!"); + return 1; + } + + errors += check_domain_name(rrset->owner, test_rrset->owner); + + if (rrset->type != test_rrset->type) { + diag("TYPE wrong: %u (should be: %u)", rrset->type, + test_rrset->type); + ++errors; + } + + if (rrset->rclass != test_rrset->rclass) { + diag("CLASS wrong: %u (should be: %u)", rrset->rclass, + test_rrset->rclass); + ++errors; + } + + if (rrset->ttl != test_rrset->ttl) { + diag("TTL wrong: %u (should be: %u)", rrset->ttl, + test_rrset->ttl); + ++errors; + } + + if (check_rdata) { + /* TODO use rdata_compare */ + knot_rdata_t *rdata = rrset->rdata; + + if (rdata == NULL) { + diag("There are no RDATAs in the RRSet"); + ++errors; + } + + if (rdata != NULL) { + while (rdata->next != NULL && + rdata->next != rrset->rdata) { + rdata = rdata->next; + } + if (rdata->next == NULL) { + diag("The list of RDATAs is not cyclic!"); + ++errors; + } else { + assert(rdata->next == rrset->rdata); + } + } + } + + /* Iterate rrset rdata list and compare items. */ + if (check_items && rrset->rdata != NULL) { + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrset->type); + node *n = NULL; + knot_rdata_t *tmp_rdata = rrset->rdata; + WALK_LIST(n, test_rrset->rdata_list) { + test_rdata_t *test_rdata = (test_rdata_t *)n; + for (int i = 0; i < desc->length; i++) { + if (desc->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME) { + errors += check_domain_name(tmp_rdata->items[i].dname, + test_rdata->items[i].dname); + } else { + assert(tmp_rdata != NULL); + errors += compare_wires_simple((uint8_t *)tmp_rdata->items[i].raw_data, + (uint8_t *)test_rdata->items[i].raw_data, + test_rdata->items[i].raw_data[0]); + } + } + } + } else if (check_items && rrset->rdata == NULL) { + diag("Could not test items, since rdata is empty!"); + } + + if (check_rrsigs) { + /* there are currently no rrsigs */ + } + return errors; +} + +extern knot_dname_t *dname_from_test_dname(test_dname_t *test_dname); + +static int test_rrset_create(const list rrset_list) +{ + int errors = 0; + + /* Test with real data. */ + node *n = NULL; + WALK_LIST(n, rrset_list) { + test_rrset_t *test_rrset = (test_rrset_t *)n; + knot_rrset_t *rrset = + knot_rrset_new(dname_from_test_dname + (test_rrset->owner), + test_rrset->type, + test_rrset->rclass, + test_rrset->ttl); + assert(rrset); + errors += check_rrset(rrset, test_rrset, 0, 0, 0); + knot_rrset_deep_free(&rrset, 1, 0, 0); + } + + return (errors == 0); +} + +static int test_rrset_add_rdata(list rrset_list) +{ + int errors = 0; + node *n = NULL; + WALK_LIST(n, rrset_list) { + test_rrset_t *test_rrset = (test_rrset_t *)n; + knot_rrset_t *tmp_rrset = rrset_from_test_rrset(test_rrset); + /* TODO use all the rdata */ + assert(tmp_rrset->rdata->next = tmp_rrset->rdata); + knot_rrset_t *rrset = + knot_rrset_new(dname_from_test_dname + (test_rrset->owner), + test_rrset->type, + test_rrset->rclass, + test_rrset->ttl); + assert(rrset); + knot_rrset_add_rdata(rrset, tmp_rrset->rdata); + errors += check_rrset(rrset, test_rrset, 1, 1, 1); + knot_rrset_free(&tmp_rrset); + knot_rrset_deep_free(&rrset, 1, 1, 0); + + } + return (errors == 0); +} + +static const int KNOT_RRSET_TEST_COUNT = 2; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_rrset_tests_count(int argc, char *argv[]) +{ + return KNOT_RRSET_TEST_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_rrset_tests_run(int argc, char *argv[]) +{ + test_data_t *data = data_for_knot_tests; + + int res = 0, + res_final = 1; + + res = test_rrset_create(data->rrset_list); + ok(res, "rrset: create"); + res_final *= res; + + ok(res = test_rrset_add_rdata(data->rrset_list), "rrset: add_rdata"); + res_final *= res; + + return res_final; +} diff --git a/src/tests/libknot/realdata/libknot/rrset_tests_realdata.h b/src/tests/libknot/realdata/libknot/rrset_tests_realdata.h new file mode 100644 index 0000000..cc3b705 --- /dev/null +++ b/src/tests/libknot/realdata/libknot/rrset_tests_realdata.h @@ -0,0 +1,35 @@ +/*! + * \file rrset_tests.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * Contains unit tests for RRSet (knot_rrset_t) and its API. + * + * Contains tests for: + * - + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_RRSET_TESTS_H_ +#define _KNOTD_RRSET_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api rrset_tests_api; + +#endif /* _KNOTD_RRSET_TESTS_H_ */ diff --git a/src/tests/libknot/realdata/libknot/zone_tests_realdata.c b/src/tests/libknot/realdata/libknot/zone_tests_realdata.c new file mode 100644 index 0000000..445997f --- /dev/null +++ b/src/tests/libknot/realdata/libknot/zone_tests_realdata.c @@ -0,0 +1,335 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "tests/libknot/realdata/libknot/zone_tests_realdata.h" +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" +#include "libknot/common.h" +#include "libknot/zone/zone.h" +#include "libknot/util/error.h" +#include "libknot/zone/node.h" + +static int knot_zone_tests_count(int argc, char *argv[]); +static int knot_zone_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api zone_tests_api = { + "DNS library - zone", //! Unit name + &knot_zone_tests_count, //! Count scheduled tests + &knot_zone_tests_run //! Run scheduled tests +}; + +/* + * Unit implementation. + */ + +extern knot_dname_t *dname_from_test_dname(test_dname_t *test_dname); +extern knot_rrset_t *rrset_from_test_rrset(test_rrset_t *test_rrset); + +static knot_node_t *node_from_test_node(const test_node_t *test_node) +{ + knot_dname_t *owner = dname_from_test_dname(test_node->owner); + /* TODO parent? */ + knot_node_t *new_node = knot_node_new(owner, NULL, 0); + node *n = NULL; + WALK_LIST(n, test_node->rrset_list) { + test_rrset_t *test_rrset = (test_rrset_t *)n; + knot_rrset_t *rrset = rrset_from_test_rrset(test_rrset); + assert(rrset); + assert(knot_node_add_rrset(new_node, rrset, 0) == 0); + } + + return new_node; +} + +static int test_zone_create(list node_list) +{ +// knot_dname_t *dname = knot_dname_new_from_wire( +// test_apex.owner.name, test_apex.owner.size, NULL); +// assert(dname); + int errors = 0; + + node *n = NULL; + WALK_LIST(n, node_list) { + test_node_t *test_node = (test_node_t *)n; + knot_node_t *node = node_from_test_node(test_node); + assert(node); + + knot_zone_t *zone = knot_zone_new(node, 0, 0); + if (zone == NULL) { + diag("Could not create zone with owner: %s\n", + test_node->owner->str); + errors++; + } + knot_node_free_rrsets(node, 1); + knot_node_free(&node, 0, 0); + } + + return (errors == 0); +} + +//static int test_zone_add_node(knot_zone_t *zone, int nsec3) +//{ +// /* +// * NSEC3 nodes are de facto identical to normal nodes, so there is no +// * need for separate tests. The only difference is where are they stored +// * in the zone structure. +// */ + +// int errors = 0; +// int res = 0; + +// //note("Good nodes"); + +// for (int i = 0; i < TEST_NODES_GOOD; ++i) { +// knot_node_t *node = knot_node_new(&test_nodes_good[i].owner, +// test_nodes_good[i].parent); +// if (node == NULL) { +// diag("zone: Could not create node."); +// return 0; +// } + +// if ((res = ((nsec3) ? knot_zone_add_nsec3_node(zone, node) +// : knot_zone_add_node(zone, node))) != 0) { +// diag("zone: Failed to insert node into zone (returned" +// " %d).", res); +// knot_node_free(&node, 0); +// ++errors; +// } +// /* TODO check values in the node as well */ +// } + +// //note("Bad nodes"); + +// for (int i = 0; i < TEST_NODES_BAD; ++i) { +// knot_node_t *node = knot_node_new(&test_nodes_bad[i].owner, +// test_nodes_bad[i].parent); +// if (node == NULL) { +// diag("zone: Could not create node."); +// return 0; +// } + +// if ((res = ((nsec3) ? knot_zone_add_nsec3_node(zone, node) +// : knot_zone_add_node(zone, node))) != +// KNOT_EBADZONE) { +// diag("zone: Inserting wrong node did not result in" +// "proper return value (%d instead of %d).", res, +// KNOT_EBADZONE); +// ++errors; +// } +// knot_node_free(&node, 0); +// } + +// // check if all nodes are inserted +// //int nodes = 0; +// if (!nsec3 +// && !test_zone_check_node(knot_zone_apex(zone), &test_apex)) { +// diag("zone: Apex of zone not right."); +//// diag("Apex owner: %s (%p), apex parent: %p\n", +//// knot_dname_to_str(knot_zone_apex(zone)->owner), +//// knot_zone_apex(zone)->owner, +//// knot_zone_apex(zone)->parent); +//// diag("Should be: owner: %s (%p), parent: %p\n", +//// knot_dname_to_str(&test_apex.owner), +//// &test_apex.owner, +//// test_apex.parent); +// ++errors; +// } +// //++nodes; +// for (int i = 0; i < TEST_NODES_GOOD; ++i) { +// const knot_node_t *n = ((nsec3) ? knot_zone_find_nsec3_node( +// zone, &test_nodes_good[i].owner) : +// knot_zone_find_node(zone, &test_nodes_good[i].owner)); +// if (n == NULL) { +// diag("zone: Missing node with owner %s", +// test_nodes_good[i].owner.name); +// ++errors; +// continue; +// } + +// if (!test_zone_check_node(n, &test_nodes_good[i])) { +// diag("zone: Node does not match: owner: %s (should be " +// "%s), parent: %p (should be %p)", +// node->owner->name, test_nodes_good[i].owner.name, +// node->parent, test_nodes_good[i].parent); +// ++errors; +// } +// //++nodes; +// } + +// //note("zone: %d nodes in the zone (including apex)", nodes); + +// return (errors == 0); +//} + +//static int test_zone_get_node(knot_zone_t *zone, int nsec3) +//{ +// int errors = 0; + +// for (int i = 0; i < TEST_NODES_GOOD; ++i) { +// if (((nsec3) ? knot_zone_get_nsec3_node( +// zone, &test_nodes_good[i].owner) +// : knot_zone_get_node(zone, &test_nodes_good[i].owner)) +// == NULL) { +// diag("zone: Node (%s) not found in zone.", +// (char *)test_nodes_good[i].owner.name); +// ++errors; +// } +// } + +// for (int i = 0; i < TEST_NODES_BAD; ++i) { +// if (((nsec3) ? knot_zone_get_nsec3_node( +// zone, &test_nodes_bad[i].owner) +// : knot_zone_get_node(zone, &test_nodes_bad[i].owner)) +// != NULL) { +// diag("zone: Node (%s) found in zone even if it should" +// "not be there.", +// (char *)test_nodes_bad[i].owner.name); +// ++errors; +// } +// } + +// if (((nsec3) +// ? knot_zone_get_nsec3_node(NULL, &test_nodes_good[0].owner) +// : knot_zone_get_node(NULL, &test_nodes_good[0].owner)) != NULL) { +// diag("zone: Getting node from NULL zone did not result in" +// "proper return value (NULL)"); +// ++errors; +// } + +// if (((nsec3) ? knot_zone_get_nsec3_node(zone, NULL) +// : knot_zone_get_node(zone, NULL)) != NULL) { +// diag("zone: Getting node with NULL owner from zone did not " +// "result in proper return value (NULL)"); +// ++errors; +// } + +// if (!nsec3 && knot_zone_get_node(zone, &test_apex.owner) == NULL) { +// diag("zone: Getting zone apex from the zone failed"); +// ++errors; +// } + +// return (errors == 0); +//} + +//static int test_zone_find_node(knot_zone_t *zone, int nsec3) +//{ +// int errors = 0; + +// for (int i = 0; i < TEST_NODES_GOOD; ++i) { +// if (((nsec3) ? knot_zone_find_nsec3_node( +// zone, &test_nodes_good[i].owner) +// : knot_zone_find_node(zone, &test_nodes_good[i].owner)) +// == NULL) { +// diag("zone: Node (%s) not found in zone.", +// (char *)test_nodes_good[i].owner.name); +// ++errors; +// } +// } + +// for (int i = 0; i < TEST_NODES_BAD; ++i) { +// if (((nsec3) ? knot_zone_find_nsec3_node( +// zone, &test_nodes_bad[i].owner) +// : knot_zone_find_node(zone, &test_nodes_bad[i].owner)) +// != NULL) { +// diag("zone: Node (%s) found in zone even if it should" +// "not be there.", +// (char *)test_nodes_bad[i].owner.name); +// ++errors; +// } +// } + +// if (((nsec3) +// ? knot_zone_find_nsec3_node(NULL, &test_nodes_good[0].owner) +// : knot_zone_find_node(NULL, &test_nodes_good[0].owner)) != NULL) { +// diag("zone: Finding node from NULL zone did not result in" +// "proper return value (NULL)"); +// ++errors; +// } + +// if (((nsec3) ? knot_zone_find_nsec3_node(zone, NULL) +// : knot_zone_find_node(zone, NULL)) != NULL) { +// diag("zone: Finding node with NULL owner from zone did not " +// "result in proper return value (NULL)"); +// ++errors; +// } + +// if (!nsec3 && knot_zone_find_node(zone, &test_apex.owner) == NULL) { +// diag("zone: Finding zone apex from the zone failed"); +// ++errors; +// } + +// return (errors == 0); +//} + +static const int KNOT_ZONE_TEST_COUNT = 1; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int knot_zone_tests_count(int argc, char *argv[]) +{ + return KNOT_ZONE_TEST_COUNT; +} + +/*! Run all scheduled tests for given parameters. + */ +static int knot_zone_tests_run(int argc, char *argv[]) +{ + int res = 0, + res_final = 0; + + test_data_t *data = data_for_knot_tests; + + ok((res = test_zone_create(data->node_list)), "zone: create"); + res_final *= res; + +// skip(!res, 6); + +// ok((res = test_zone_add_node(zone, 0)), "zone: add node"); +// res_final *= res; + +// skip(!res, 2); + +// skip(!res, 1); + +// ok((res = test_zone_find_node(zone, 0)), "zone: find node"); +// res_final *= res; + +// endskip; // get node failed + +// endskip; // add node failed + +// ok((res = test_zone_add_node(zone, 1)), "zone: add nsec3 node"); +// res_final *= res; + +// skip(!res, 2); + +// skip(!res, 1); + +// ok((res = test_zone_find_node(zone, 1)), "zone: find nsec3 node"); +// res_final *= res; + +// endskip; // get nsec3 node failed + +// endskip; // add nsec3 node failed + +// endskip; // create failed + + return res_final; +} diff --git a/src/tests/libknot/realdata/libknot/zone_tests_realdata.h b/src/tests/libknot/realdata/libknot/zone_tests_realdata.h new file mode 100644 index 0000000..5539709 --- /dev/null +++ b/src/tests/libknot/realdata/libknot/zone_tests_realdata.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_ZONE_TESTS_H_ +#define _KNOTD_ZONE_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api zone_tests_api; + +#endif /* _KNOTD_ZONE_TESTS_H_ */ diff --git a/src/tests/libknot/realdata/libknot/zonedb_tests_realdata.c b/src/tests/libknot/realdata/libknot/zonedb_tests_realdata.c new file mode 100644 index 0000000..96d1517 --- /dev/null +++ b/src/tests/libknot/realdata/libknot/zonedb_tests_realdata.c @@ -0,0 +1,44 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include "tests/libknot/realdata/libknot/zonedb_tests_realdata.h" + + +static int zonedb_tests_count(int argc, char *argv[]); +static int zonedb_tests_run(int argc, char *argv[]); + +/*! Exported unit API. + */ +unit_api zonedb_tests_api = { + "Zone database", //! Unit name + &zonedb_tests_count, //! Count scheduled tests + &zonedb_tests_run //! Run scheduled tests +}; + +/*! This helper routine should report number of + * scheduled tests for given parameters. + */ +static int zonedb_tests_count(int argc, char *argv[]) +{ + return 0; +} + +/*! Run all scheduled tests for given parameters. + */ +static int zonedb_tests_run(int argc, char *argv[]) +{ + return 0; +} diff --git a/src/tests/libknot/realdata/libknot/zonedb_tests_realdata.h b/src/tests/libknot/realdata/libknot/zonedb_tests_realdata.h new file mode 100644 index 0000000..0c4f8ef --- /dev/null +++ b/src/tests/libknot/realdata/libknot/zonedb_tests_realdata.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_ZONEDB_TESTS_H_ +#define _KNOTD_ZONEDB_TESTS_H_ + +#include "common/libtap/tap_unit.h" + +/* Unit API. */ +unit_api zonedb_tests_api; + +#endif /* _KNOTD_ZONEDB_TESTS_H_ */ diff --git a/src/tests/libknot/realdata/libknot_tests_loader_realdata.c b/src/tests/libknot/realdata/libknot_tests_loader_realdata.c new file mode 100644 index 0000000..71a9c3d --- /dev/null +++ b/src/tests/libknot/realdata/libknot_tests_loader_realdata.c @@ -0,0 +1,1300 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> + +#include "common/libtap/tap.h" +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" +#include "libknot/util/descriptor.h" + +#include "tests/libknot/realdata/parsed_data.rc" +#include "tests/libknot/realdata/raw_data.rc" +TREE_DEFINE(test_node, avl); + +/* Virtual I/O over memory. */ +static int mem_read(void *dst, size_t n, const char **src, + unsigned *remaining) +{ +// printf("reading %u\n", n); + if (n > *remaining) { + return 0; + } + + + memcpy(dst, *src, n); + *src += n; + *remaining -= n; +// printf("remaining %u\n", *remaining); + return 1; +} + +static int load_raw_packets(test_data_t *data, uint32_t *count, + const char *src, unsigned src_size) +{ + + uint16_t tmp_size = 0; + + /* Packets are stored like this: [size][packet_data]+ */ + + if(!mem_read(count, sizeof(uint32_t), &src, &src_size)) { + return -1; + } + + for (int i = 0; i < *count; i++) { + uint16_t query = 0; + if (!mem_read(&query, sizeof(query), &src, &src_size)) { + return -1; + } + + if(!mem_read(&tmp_size, sizeof(uint16_t), &src, &src_size)) { + return -1; + } + + test_raw_packet_t *packet = malloc(sizeof(test_raw_packet_t)); + + + packet->size = tmp_size; + packet->data = malloc(sizeof(uint8_t) * (tmp_size)); + if(!mem_read(packet->data, + sizeof(uint8_t) * tmp_size, &src, &src_size)) { + return -1; + } + + if (query) { + add_tail(&data->raw_query_list, (void *)packet); + } else { + add_tail(&data->raw_response_list, (void *)packet); + } + + test_raw_packet_t *new_packet = + malloc(sizeof(test_raw_packet_t)); + assert(new_packet); + new_packet->data = packet->data; + new_packet->size = packet->size; + + add_tail(&data->raw_packet_list, (void *)new_packet); + } + + return 0; +} + +/* Returns size of type where avalailable */ +size_t wireformat_size_load(uint wire_type) +{ + switch(wire_type) { + case KNOT_RDATA_WF_BYTE: + return 1; + break; + case KNOT_RDATA_WF_SHORT: + return 2; + break; + case KNOT_RDATA_WF_LONG: + return 4; + break; + case KNOT_RDATA_WF_A: + return 4; + break; + case KNOT_RDATA_WF_AAAA: + return 16; + break; + default: /* unknown size */ + return 0; + break; + } /* switch */ +} + +static int add_label(uint8_t **labels, const uint8_t label, + uint *label_count) +{ + void *ret = realloc(*labels, sizeof(uint8_t) * (*label_count + 1)); + if (ret == NULL) { + return -1; + } + + *labels = ret; + (*labels)[(*label_count)++] = label; + + return 0; +} +/* Dnames are stored label by label in the dump */ +/* TODO STRING AS WELL */ +static test_dname_t *load_test_dname(const char **src, + unsigned *src_size) +{ + test_dname_t *ret = malloc(sizeof(test_dname_t)); + CHECK_ALLOC_LOG(ret, NULL); + + ret->size = 0; + ret->str = NULL; + ret->labels = NULL; + ret->wire = NULL; + ret->label_count = 0; + ret->next = NULL; + ret->prev = NULL; + + uint8_t label_size = 0; + uint8_t *label_wire = NULL; + uint8_t *labels = NULL; + char *dname_str = NULL; + uint label_count = 0; + uint dname_length = 0; + do { + /* Read label size */ + if (!mem_read(&label_size, + sizeof(uint8_t), + src, + src_size)) { + fprintf(stderr, "Faulty read\n"); + return NULL; + } + +// diag("%d", label_size); + + add_label(&labels, ret->size, &label_count); + + dname_length += label_size + 1; + + label_wire = malloc(sizeof(uint8_t) * (label_size + 2)); + + if (label_wire == NULL) { + ERR_ALLOC_FAILED; + free(ret); + return NULL; + } + + label_wire[0] = label_size; + + /* Read label wire */ + if (!mem_read(label_wire + 1, + sizeof(uint8_t) * + label_size, + src, + src_size)) { + free(label_wire); + fprintf(stderr, "Faulty read\n"); + return NULL; + } + + label_wire[label_size + 1] = '\0'; + + dname_str = malloc(sizeof(char) * (label_size + 2)); + + if (label_size != 0) { + /* n - 1 : . */ + dname_str[label_size] = '.'; + dname_str[label_size + 1] = '\0'; + + memcpy(dname_str, label_wire + 1, label_size); + } + + if (ret->size == 0) { + ret->wire = malloc(sizeof(uint8_t) * (label_size + 2)); + if (ret->wire == NULL) { + ERR_ALLOC_FAILED; + free(ret); + return NULL; + } + + memcpy(ret->wire, label_wire, label_size + 2); + + if (label_size != 0) { + + ret->str = + malloc(sizeof(char) * (label_size + 2)); + if (ret->str == NULL) { + ERR_ALLOC_FAILED; + free(ret->wire); + free(ret); + return NULL; + } + + memcpy(ret->str, dname_str, label_size + 2); + } + + ret->size = label_size + 2; + } else { + /* Concatenate */ + void *p = realloc(ret->wire, + ret->size + (label_size + 2)); + if (p == NULL) { + ERR_ALLOC_FAILED; + free(ret->wire); + free(ret->labels); + free(ret); + return NULL; + } + ret->wire = p; + + /* TODO Safe concat? But I set the values myself, right? */ + /* or maybe memcpy... */ + strcat((char *)ret->wire, (char *)label_wire); + assert(ret->wire); + + + if (label_size != 0) { + + p = realloc(ret->str, + ret->size + (label_size + 2)); + if (p == NULL) { + ERR_ALLOC_FAILED; + free(ret->wire); + free(ret->str); + free(ret->labels); + free(ret); + return NULL; + } + ret->str = p; + + strcat(ret->str, dname_str); + assert(ret->str); + } + + ret->size += label_size + 2; + } + + free(label_wire); + free(dname_str); + + } while (label_size != 0); + + /*!< \warning even wireformat is ended with 0 every time !!! */ + + /* Root domain */ +// if (ret->size == 0) { +// assert(ret->wire == NULL); + +// ret->wire = malloc(sizeof(uint8_t) * 1); +// if (ret->wire == NULL) { +// ERR_ALLOC_FAILED; +// free(ret); +// return NULL; +// } + +// ret->wire[0] = '\0'; + +// ret->labels = malloc(sizeof(uint8_t) * 1); +// if (ret->labels == NULL) { +// ERR_ALLOC_FAILED; +// free(ret->wire); +// free(ret); +// return NULL; +// } + +// ret->labels[0] = '\0'; +// ret->label_count = 1; +// } + +// printf("OK: %s (%d)\n",ret->str, ret->size); + + ret->labels = labels; + ret->size = ret->size - (label_count); + ret->label_count = --label_count; + ret->next = NULL; + ret->prev = NULL; + + assert(ret != NULL); + + return ret; +} + +/*! + * \brief Reads dname label by label + */ +static test_rdata_t *load_response_rdata(uint16_t type, + const char **src, + unsigned *src_size) +{ + +#ifdef RESP_TEST_DEBUG + fprintf(stderr, "reading rdata for type: %s\n", + knot_rrtype_to_string(type)); +#endif + /* + * Binary format of rdata is as following: + * [total_length(except for some types) - see below][rdata_item]+ + * Dname items are read label by label + */ + + test_rdata_t *rdata = malloc(sizeof(test_rdata_t)); + + CHECK_ALLOC_LOG(rdata, NULL); + + rdata->count = 0; + rdata->items = NULL; + rdata->type = 0; + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(type); + assert(desc != NULL); + + rdata->type = type; + + test_item_t *items = + malloc(sizeof(test_item_t) * desc->length); + + if (items == NULL) { + ERR_ALLOC_FAILED; + free(rdata); + return NULL; + } + + /* TODO consider realloc */ + + uint16_t total_raw_data_length = 0; + uint8_t raw_data_length; + + /* + * These types have no length, unfortunatelly (python library + * does not provide this) + */ + /* TODO the are more types with no length for sure ... */ + + if (type != KNOT_RRTYPE_A && + type != KNOT_RRTYPE_NS && + type != KNOT_RRTYPE_AAAA) { + if (!mem_read(&total_raw_data_length, + sizeof(total_raw_data_length), src, src_size)) { + free(rdata); + free(items); + fprintf(stderr, "Faulty read\n"); + return NULL; + } + } + + size_t total_read = 0; + + int i; + + /* + * TODO save number of items + * in the dump - of minor importance, however + */ + for (i = 0; i < desc->length; i++) { + if ((desc->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME)) { + unsigned tmp_remaining = *src_size; + items[i].dname = load_test_dname(src, src_size); + + if (items[i].dname == NULL) { + fprintf(stderr, "Could not load DNAME!\n"); + free(rdata); + free(items); + + /* TODO something like Marek's purge */ + + return NULL; + } + +// diag("Created DNAME %p item: %d %s %s\n", +// items[i].dname, i, knot_rrtype_to_string(type), +// items[i].dname->str); + + rdata->count++; + items[i].type = TEST_ITEM_DNAME; + items[i].raw_data = NULL; + total_read += tmp_remaining - *src_size; + } else { + if (desc->wireformat[i] == + KNOT_RDATA_WF_BINARYWITHLENGTH) { + if (!mem_read(&raw_data_length, + sizeof(raw_data_length), src, src_size)) { + return NULL; + } + + total_read++; + + items[i].raw_data = + malloc(sizeof(uint8_t) * + (raw_data_length + 3)); + + items[i].raw_data[0] = + (uint16_t) raw_data_length + 1; + + /* let's store the length again */ + + ((uint8_t *)items[i].raw_data)[2] = + raw_data_length; + + if (!mem_read(((uint8_t *) + items[i].raw_data) + 3, + sizeof(uint8_t) * (raw_data_length), + src, src_size)) { + fprintf(stderr, "Wrong read!\n"); + return NULL; + } + + rdata->count++; + items[i].type = TEST_ITEM_RAW_DATA; + items[i].dname = NULL; + total_read += sizeof(uint8_t) * raw_data_length; +/* printf("read len (from wire): %d\n", + items[i].raw_data[0]); + hex_print((char *)items[i].raw_data + 1, + items[i].raw_data[0]); + */ + } else { + /* Other type than dname or BINARYWITHLENGTH */ + /* Set dname to NULL */ + items[i].dname = NULL; + + uint16_t size_fr_desc = + (uint16_t) + wireformat_size_load(desc->wireformat[i]); +#ifdef RESP_TEST_DEBUG + fprintf(stderr, "reading %d\n", size_fr_desc); +#endif + + if (size_fr_desc == 0) { /* unknown length */ +/* size_fr_desc = wireformat_size_n(type, + items, + i); + */ + if ((i != desc->length - 1) && + desc->wireformat[i] != + KNOT_RDATA_WF_TEXT ) { + fprintf(stderr, + "I dont know how " + "to parse this type: %d\n", + type); + return NULL; + } else { + size_fr_desc = + total_raw_data_length - + total_read; + if (desc->wireformat[i] == + KNOT_RDATA_WF_TEXT) { + break; + } + +// fprintf(stderr, +// "Guessed size: %d" +// " for type: %s" +// " and index: %d\n", +// size_fr_desc, +// knot_rrtype_to_string(type), +// i); + } + } + + items[i].raw_data = + malloc(sizeof(uint8_t) * size_fr_desc + 2); + +// diag("creating raw_data for item %d type %s %p\n", +// i, knot_rrtype_to_string(type), +// items[i].raw_data); + + if (items[i].raw_data == NULL) { + ERR_ALLOC_FAILED; + free(rdata); + free(items); + return NULL; + } + + items[i].raw_data[0] = size_fr_desc; + + if (!mem_read(items[i].raw_data + 1, + size_fr_desc, + src, src_size)) { + fprintf(stderr, "Wrong read\n!"); + return NULL; + } + + rdata->count++; + items[i].type = TEST_ITEM_RAW_DATA; + items[i].dname = NULL; + total_read += size_fr_desc; + +#ifdef RESP_TEST_DEBUG + fprintf(stderr, + "read len (from descriptor): %d\n", + items[i].raw_data[0]); +/* hex_print((char *)items[i].raw_data + 1, + items[i].raw_data[0]); */ + + if (desc->zoneformat[i] == + KNOT_RDATA_ZF_ALGORITHM) { + hex_print((char *)items[i].raw_data, + items[i].raw_data[0] + 2); + } else { + hex_print((char *)items[i].raw_data, + items[i].raw_data[0] + 2); + } +#endif + } + } + } + +/* if (knot_rdata_set_items(rdata, items, i) != 0) { + diag("Error: could not set items\n"); + return NULL; + } */ + + rdata->items = items; + + return rdata; +} + +static test_rrset_t *load_response_rrset(const char **src, unsigned *src_size, + char is_question) +{ + test_rrset_t *rrset = NULL; + uint16_t rrset_type = 0; + uint16_t rrset_class = 0; + uint32_t rrset_ttl = 0; + + /* Each rrset will only have one rdata entry */ + + /* + * RRSIGs will be read as separate RRSets because that's the way they + * are stored in responses + */ + + /* Read owner first */ + + uint8_t dname_size; +// uint8_t *dname_wire = NULL; + + rrset = malloc(sizeof(test_rrset_t)); + + rrset->rrsigs = NULL; + + CHECK_ALLOC_LOG(rrset, NULL); + + init_list(&rrset->rdata_list); + + /* TODO change in dump, size is useless now! */ + if (!mem_read(&dname_size, sizeof(dname_size), src, src_size)) { + free(rrset); + return NULL; + } + +/* dname_wire = malloc(sizeof(uint8_t) * dname_size); + + CHECK_ALLOC_LOG(dname_wire, NULL); + + if (!mem_read(dname_wire, sizeof(uint8_t) * dname_size, src, + src_size)) { + free(dname_wire); + return NULL; + } */ + + test_dname_t *owner = load_test_dname(src, src_size); + + if (owner == NULL) { + free(rrset); + return NULL; + } + +#ifdef RESP_TEST_DEBUG + { + fprintf(stderr, "Got owner: %s", owner->str); + } +#endif + /* Read other data */ + + if (!mem_read(&rrset_type, sizeof(rrset_type), src, src_size)) { + return NULL; + } + + if (!mem_read(&rrset_class, sizeof(rrset_class), src, src_size)) { + return NULL; + } + + if (!is_question) { + if (!mem_read(&rrset_ttl, sizeof(rrset_ttl), src, src_size)) { + return NULL; + } + } else { + rrset_ttl = 0; + } + +// rrset = knot_rrset_new(owner, rrset_type, rrset_class, rrset_ttl); + + rrset->owner = owner; + rrset->type = rrset_type; + rrset->rclass = rrset_class; + rrset->ttl = rrset_ttl; + + /* Question rrsets have no rdata */ + + if (!is_question) { + test_rdata_t *tmp_rdata; + + tmp_rdata = load_response_rdata(rrset->type, src, src_size); + + if (tmp_rdata == NULL) { + fprintf(stderr, + "Could not load rrset rdata - type: %d", + rrset->type); + free(rrset); + return NULL; + } + + assert(tmp_rdata->type == rrset->type); + + add_tail(&rrset->rdata_list, (node *)tmp_rdata); + } + + return rrset; +} + +static test_response_t *load_parsed_response(const char **src, + unsigned *src_size) +{ + /* Loads parsed response/query from binary format, + * which is as following: + * [id][qdcount][ancount][nscount][arcount] + * [question_rrset+][answer_rrset+][authority_rrset+] + * [additional_rrset]+ + */ + + test_response_t *resp = malloc(sizeof(test_response_t)); + + CHECK_ALLOC_LOG(resp, NULL); + + if (!mem_read(&resp->id, sizeof(resp->id), src, src_size)) { + free(resp); + return NULL; + } + +#ifdef RESP_TEST_DEBUG + fprintf(stderr, "id %d\n", resp->id); +#endif + + if (!mem_read(&resp->qdcount, sizeof(resp->qdcount), src, src_size)) { + free(resp); + return NULL; + } + +#ifdef RESP_TEST_DEBUG + fprintf(stderr, "qdcount: %d\n", resp->qdcount); +#endif + + if (!mem_read(&resp->ancount, sizeof(resp->ancount), src, src_size)) { + free(resp); + return NULL; + } + +#ifdef RESP_TEST_DEBUG + fprintf(stderr, "ancount: %d\n", resp->ancount); +#endif + + if (!mem_read(&resp->nscount, sizeof(resp->nscount), src, src_size)) { + free(resp); + return NULL; + } + +#ifdef RESP_TEST_DEBUG + fprintf(stderr, "nscount: %d\n", resp->nscount); +#endif + + if (!mem_read(&resp->arcount, sizeof(resp->arcount), src, src_size)) { + free(resp); + return NULL; + } + +#ifdef RESP_TEST_DEBUG + fprintf(stderr, "arcount: %d\n", resp->arcount); +#endif + + if (!mem_read(&resp->query, sizeof(resp->query), src, src_size)) { + free(resp); + return NULL; + } + + test_rrset_t **question_rrsets; + + question_rrsets = malloc(sizeof(test_rrset_t *) * resp->qdcount); + + for (int i = 0; i < resp->qdcount; i++) { + question_rrsets[i] = load_response_rrset(src, src_size, 1); + if (question_rrsets[i] == NULL) { + fprintf(stderr, "Could not load question rrsets\n"); + + for (int j = 0; j < i; j++) { + free(question_rrsets[i]); + } + free(question_rrsets); + free(resp); + return NULL; + } + } + + /* only one question in our case */ + + resp->qname = question_rrsets[0]->owner; + resp->qtype = question_rrsets[0]->type; + resp->qclass = question_rrsets[0]->rclass; + + resp->question = NULL; + +/* for (int i = 0; i < resp->qdcount; i++) { + knot_rrset_free(&(question_rrsets[i])); + } */ + + free(question_rrsets); + + test_rrset_t *tmp_rrset = NULL; + + if (resp->ancount > 0) { + resp->answer = + malloc(sizeof(test_rrset_t *) * resp->ancount); + } else { + resp->answer = NULL; + } + + for (int i = 0; i < resp->ancount; i++) { + tmp_rrset = load_response_rrset(src, src_size, 0); + resp->answer[i] = tmp_rrset; + if (resp->answer[i] == NULL) { + fprintf(stderr, "Could not load answer rrsets\n"); + free(resp->answer); + free(resp); + return NULL; + } + } + + if (resp->nscount > 0) { + resp->authority = + malloc(sizeof(test_rrset_t *) * resp->nscount); + } else { + resp->authority = NULL; + } + + for (int i = 0; i < resp->nscount; i++) { + tmp_rrset = load_response_rrset(src, src_size, 0); + resp->authority[i] = tmp_rrset; + if (resp->authority[i] == NULL) { + fprintf(stderr, "Could not load authority rrsets\n"); + free(resp->authority); + free(resp->answer); + free(resp); + return NULL; + } + } + + if (resp->arcount > 0) { + resp->additional = + malloc(sizeof(test_rrset_t *) * resp->arcount); + } else { + resp->additional = NULL; + } + + for (int i = 0; i < resp->arcount; i++) { + tmp_rrset = load_response_rrset(src, src_size, 0); + if (tmp_rrset == NULL) { + fprintf(stderr, "Could not load rrset (additional)\n"); + free(resp->additional); + free(resp->authority); + free(resp->answer); + free(resp); + return NULL; + } + + resp->additional[i] = tmp_rrset; + } + + /* this will never be used */ + + resp->flags1 = 0; + resp->flags2 = 0; + + return resp; +} + +static void test_dname_free(test_dname_t **dname) +{ + assert(dname != NULL && *dname != NULL); + free((*dname)->labels); +// free((*dname)->str); + free((*dname)->wire); + + free(*dname); + *dname = NULL; +} + +static int wire_is_dname(uint type) +{ + return (type == KNOT_RDATA_WF_COMPRESSED_DNAME || + type == KNOT_RDATA_WF_UNCOMPRESSED_DNAME || + type == KNOT_RDATA_WF_LITERAL_DNAME); +} + +static void test_rdata_free(test_rdata_t **rdata) +{ + assert(rdata != NULL && *rdata != NULL); + + /* Free all the items */ + const knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type((*rdata)->type); + + for (int i = 0; i < (*rdata)->count; i++) { + if ((wire_is_dname(desc->wireformat[i])) && + ((*rdata)->items[i].dname != NULL)) { + test_dname_free(&(*rdata)->items[i].dname); + } else if ((*rdata)->items[i].raw_data != NULL) { + free((*rdata)->items[i].raw_data); + (*rdata)->items[i].raw_data = NULL; + } + } +// free((*rdata)->items); +// free(*rdata); + *rdata = NULL; +} + +static void test_rrset_free(test_rrset_t **rrset) +{ + assert(rrset && *rrset); + + test_dname_free(&(*rrset)->owner); + /* Free all the rdatas */ + node *n = NULL, *nxt = NULL; + WALK_LIST_DELSAFE(n, nxt, (*rrset)->rdata_list) { + test_rdata_t *tmp_rdata = (test_rdata_t *)n; + assert(tmp_rdata); + if (tmp_rdata != NULL) { + test_rdata_free(&tmp_rdata); + } + } + + free(*rrset); + *rrset = NULL; +} + +static void test_response_free(test_response_t **response) +{ + assert(response && *response); + if ((*response)->qname != NULL) { + test_dname_free(&(*response)->qname); + } + + if ((*response)->additional != NULL) { + for (int j = 0; j < (*response)->arcount; j++) { + test_rrset_free(&((*response)->additional[j])); + } + + free((*response)->additional); + } + + if ((*response)->answer != NULL) { + for (int j = 0; j < (*response)->ancount; j++) { + test_rrset_free(&((*response)->answer[j])); + } + + free((*response)->answer); + } + + if ((*response)->authority != NULL) { + for (int j = 0; j < (*response)->nscount; j++) { + test_rrset_free(&((*response)->authority[j])); + } + + free((*response)->authority); + } + + free((*response)); + *response = NULL; +} + +static void get_and_save_data_from_rdata(test_rdata_t *rdata, + test_data_t *data, uint16_t type) +{ + /* We only want to extract dnames */ + const knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(type); + + if (rdata->count == 0) { +// diag("Rdata count not set!\n"); + rdata->count = desc->length; + } + + for(int i = 0; i < rdata->count; i++) { + if ((desc->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME)) { + add_tail(&data->dname_list, + (node *)rdata->items[i].dname); + test_item_t *temp_item = malloc(sizeof(test_item_t)); + temp_item->dname = rdata->items[i].dname; + temp_item->type = TEST_ITEM_DNAME; + temp_item->raw_data = NULL; + add_tail(&data->item_list, (node *)temp_item); + } else { + test_item_t *temp_item = malloc(sizeof(test_item_t)); + temp_item->dname = NULL; + temp_item->type = TEST_ITEM_RAW_DATA; + temp_item->raw_data = rdata->items[i].raw_data; + add_tail(&data->item_list, (node *)temp_item); + } + } +} + +static void get_and_save_data_from_rrset(const test_rrset_t *rrset, + test_data_t *data) +{ +// knot_rrtype_descriptor_t *desc = +// knot_rrtype_descriptor_by_type(rrset->type); + /* RDATA are in a list. */ + node *n = NULL; + int i = 0; + WALK_LIST(n, rrset->rdata_list) { + test_rdata_t *tmp_rdata = (test_rdata_t *)n; + assert(tmp_rdata); + assert(&data->rdata_list); + assert(&data->rdata_list != &rrset->rdata_list); + assert(tmp_rdata->type == rrset->type); + + test_rdata_t *new_rdata = malloc(sizeof(test_rdata_t)); + new_rdata->count = tmp_rdata->count; + new_rdata->items = tmp_rdata->items; + new_rdata->type = tmp_rdata->type; + + add_tail(&data->rdata_list, (node *)new_rdata); + get_and_save_data_from_rdata(tmp_rdata, data, rrset->type); + i++; + } + assert(i == 1); +} + +static int add_rrset_to_node(const test_rrset_t *rrset, test_data_t *data) +{ + /* First, create node from rrset */ + test_node_t *tmp_node = malloc(sizeof(test_node_t)); + memset(tmp_node, 0, sizeof(test_node_t)); + CHECK_ALLOC_LOG(tmp_node, -1); + + tmp_node->owner = rrset->owner; + tmp_node->parent = NULL; + tmp_node->rrset_count = 0; + + /* Will not be used in list now */ + tmp_node->prev = NULL; + tmp_node->next = NULL; + + +// printf("%s\n", rrset->owner->wire); +// getchar(); + +/* tmp_node->avl_left = NULL; + tmp_node->avl_right = NULL; + tmp_node->avl_height = 0; */ + + test_node_t *found_node = + TREE_FIND(data->node_tree, test_node, avl, tmp_node); + + if (found_node == NULL) { + /* Insert new node with current rrset */ + init_list(&tmp_node->rrset_list); + add_tail(&tmp_node->rrset_list, (node *)rrset); + tmp_node->rrset_count++; + + TREE_INSERT(data->node_tree, test_node, avl, tmp_node); + } else { + free(tmp_node); + /* append rrset */ + + add_tail(&found_node->rrset_list, (node *)rrset); + found_node->rrset_count++; + } + + return 0; +} + +static void get_and_save_data_from_response(const test_response_t *response, + test_data_t *data) +{ + /* Go through all the rrsets in the response */ + + for (int i = 0; i < response->ancount; i++) { + assert(response->answer[i]); + /* Add rrset to the list of rrsets - there will be duplicates + * But not the same pointers */ + add_tail(&data->rrset_list, (node *)response->answer[i]); + get_and_save_data_from_rrset(response->answer[i], data); + if (add_rrset_to_node(response->answer[i], data) != 0) { + return; + } + } + + for (int i = 0; i < response->arcount; i++) { + /* Add rrset to the list of rrsets - there will be duplicates */ + assert(response->additional[i]); + add_tail(&data->rrset_list, (node *)response->additional[i]); + get_and_save_data_from_rrset(response->additional[i], data); + if (add_rrset_to_node(response->additional[i], data) != 0) { + return; + } + } + + for (int i = 0; i < response->nscount; i++) { + assert(response->authority[i]); + /* Add rrset to the list of rrsets - there will be duplicates */ + add_tail(&data->rrset_list, (node *)response->authority[i]); + get_and_save_data_from_rrset(response->authority[i], data); + if (add_rrset_to_node(response->authority[i], data) != 0) { + return; + } + } + +// for (int i = 0; i < response->qdcount; i++) { +// /* Add rrset to the list of rrsets - there will be duplicates */ +// add_tail(&data->rrset_list, (node *)response->question[i]); +// get_and_save_data_from_rrset(response->question[i], data); +// } +} + +static int load_parsed_responses(test_data_t *data, uint32_t *count, + const char* src, unsigned src_size) +{ + if (!mem_read(count, sizeof(*count), &src, &src_size)) { + fprintf(stderr, "Wrong read\n"); + return -1; + } + +// *responses = malloc(sizeof(test_response_t *) * *count); + + for (int i = 0; i < *count; i++) { + test_response_t *tmp_response = + load_parsed_response(&src, &src_size); + + if (tmp_response == NULL) { + fprintf(stderr, "Could not load response - %d" + "- returned NULL\n", + i); + return -1; + } + + if (tmp_response->query) { + add_tail(&data->query_list, (node *)tmp_response); + } else { + add_tail(&data->response_list, (node *)tmp_response); + } + + /* Create new node */ + test_response_t *resp = malloc(sizeof(test_response_t)); + assert(resp); + memcpy(resp, tmp_response, sizeof(test_response_t)); + add_tail(&data->packet_list, + (node *)resp); + } + + return 0; +} + +//void free_parsed_responses(test_response_t ***responses, uint32_t *count) +//{ +// if (*responses != NULL) { +// for (int i = 0; i < *count; i++) { +// free_parsed_response((*responses)[i]); +// } +// free(*responses); +// } +//} + +static int compare_nodes(test_node_t *node1, test_node_t *node2) +{ + assert(node1->owner && node2->owner); + /*!< \warning Wires have to be \0 terminated. */ + return (strcmp((char *)node1->owner->wire, (char *)node2->owner->wire)); +} + +static int init_data(test_data_t *data) +{ + if (data == NULL) { + return 0; + } + + /* Initialize all the lists */ + init_list(&data->dname_list); + init_list(&data->edns_list); + init_list(&data->node_list); + init_list(&data->response_list); + init_list(&data->rdata_list); + init_list(&data->rrset_list); + init_list(&data->item_list); + init_list(&data->raw_response_list); + init_list(&data->raw_query_list); + init_list(&data->raw_packet_list); + init_list(&data->query_list); + init_list(&data->packet_list); + + data->node_tree = malloc(sizeof(avl_tree_test_t)); + CHECK_ALLOC_LOG(data->node_tree, 0); + + TREE_INIT(data->node_tree, compare_nodes); + + return 1; +} + +static void print_stats(test_data_t *data) +{ + uint resp_count = 0, dname_count = 0, node_count = 0, + rdata_count = 0, rrset_count = 0, item_count = 0, query_count = 0, + raw_query_count = 0, response_count = 0, packet_count = 0, + raw_packet_count = 0, raw_response_count = 0; + + node *n = NULL; /* Will not be used */ + + WALK_LIST(n, data->response_list) { + resp_count++; + } + + WALK_LIST(n, data->rrset_list) { +// node *tmp = NULL; +// assert(((test_rrset_t *)n)->owner); +// WALK_LIST(tmp, ((test_rrset_t *)n)->rdata_list) { +// test_rdata_t *rdata = (test_rdata_t *)tmp; +// assert(rdata->type == ((test_rrset_t *)n)->type); +// } + rrset_count++; + } + + WALK_LIST(n, data->rdata_list) { + rdata_count++; + } + + WALK_LIST(n, data->dname_list) { + dname_count++; + } + + WALK_LIST(n, data->node_list) { + node_count++; + } + + WALK_LIST(n, data->item_list) { + item_count++; + } + + WALK_LIST(n, data->raw_response_list) { + raw_response_count++; + } + + WALK_LIST(n, data->query_list) { + query_count++; + } + + WALK_LIST(n, data->response_list) { + response_count++; + } + + WALK_LIST(n, data->raw_query_list) { + raw_query_count++; + } + + WALK_LIST(n, data->packet_list) { + packet_count++; + } + + WALK_LIST(n, data->raw_packet_list) { + raw_packet_count++; + } + + printf("Loaded: Responses: %d RRSets: %d RDATAs: %d Dnames: %d " + "Nodes: %d Items: %d Raw_responses: %d Queries: %d \n" + "Raw_queries; %d Total packets: %d Total_raw_packets: %d\n", resp_count, rrset_count, + rdata_count, dname_count, node_count, item_count, + raw_response_count, query_count, raw_query_count, packet_count, + raw_packet_count); +} + +static void save_node_to_list(test_node_t *n, void *p) +{ + test_data_t *data = (test_data_t *)p; + + add_tail(&data->node_list, (node *)n); +} + +static void del_node(test_node_t *n, void *p) +{ +// test_data_t *data = (test_data_t *)p; + free(n); +} + + +void free_data(test_data_t **data) +{ + assert(data && *data); + /* We will free all the data using responses + * (others are just references )*/ + node *n = NULL, *nxt = NULL; + WALK_LIST_DELSAFE(n, nxt, (*data)->response_list) { + test_response_t *tmp_response = (test_response_t *)n; + if (tmp_response != NULL) { + test_response_free(&tmp_response); + } + } + + TREE_POST_ORDER_APPLY((*data)->node_tree, test_node, avl, del_node, + NULL); + + free((*data)->node_tree); + + free(*data); + *data = NULL; +} + +test_data_t *create_test_data_from_dump() +{ + test_data_t *ret = malloc(sizeof(test_data_t)); + CHECK_ALLOC_LOG(ret, NULL); + + if (!init_data(ret)) { + free(ret); + return NULL; + } + + uint32_t raw_packet_count = 0; + + if (load_raw_packets(ret, &raw_packet_count, raw_data_rc, + raw_data_rc_size) != 0) { + fprintf(stderr, "Could not load raw_data, quitting"); + /* TODO walk the lists*/ + free(ret); + return NULL; + } + + uint32_t response_count = 0; + + if (load_parsed_responses(ret, &response_count, parsed_data_rc, + parsed_data_rc_size) != 0) { + fprintf(stderr, "Could not load responses, quitting"); + /* TODO walk the lists*/ + free(ret); + return NULL; + } + + /* For each parsed response - create more data from it. */ + /* Probably not the most effective way, but it is better than to + * rewrite most of the code .*/ + + node *n = NULL; + + WALK_LIST(n , ret->response_list) { + get_and_save_data_from_response((test_response_t *)n, ret); + } + + /* Create list from AVL tree */ + + TREE_FORWARD_APPLY(ret->node_tree, test_node, avl, + save_node_to_list, ret); + + print_stats(ret); + + return ret; +} diff --git a/src/tests/libknot/realdata/libknot_tests_loader_realdata.h b/src/tests/libknot/realdata/libknot_tests_loader_realdata.h new file mode 100644 index 0000000..8f57944 --- /dev/null +++ b/src/tests/libknot/realdata/libknot_tests_loader_realdata.h @@ -0,0 +1,179 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef KNOT_TESTS_LOADER_H +#define KNOT_TESTS_LOADER_H + +#include <stdint.h> + +#include "libknot/common.h" +#include "common/lists.h" +#include "common/tree.h" + + +/* Parsed raw packet*/ +struct test_raw_packet { + struct node *next, *prev; + uint size; + uint8_t *data; +}; + +typedef struct test_raw_packet test_raw_packet_t; + +/* Test type definitions */ + +struct test_dname { + struct node *next, *prev; + char *str; + uint8_t *wire; + uint size; + uint8_t *labels; + short label_count; +}; + +typedef struct test_dname test_dname_t; + +struct test_edns_options { + struct node *next, *prev; + uint16_t code; + uint16_t length; + uint8_t *data; +}; + +struct test_edns { + struct node *next, *prev; + struct test_edns_options *options; + uint16_t payload; + uint8_t ext_rcode; + uint8_t version; + uint16_t flags; + uint16_t *wire; + short option_count; + short options_max; + short size; +}; + +typedef struct test_edns test_edns_t; + +typedef TREE_HEAD(avl_tree_test, test_node) avl_tree_test_t; + +struct test_node { + struct node *next, *prev; + test_dname_t *owner; + short rrset_count; + struct test_node *parent; + list rrset_list; + + TREE_ENTRY(test_node) avl; +}; + +typedef struct test_node test_node_t; + +enum item_type { + TEST_ITEM_DNAME, + TEST_ITEM_RAW_DATA +}; + +typedef enum item_type item_type_t; + +struct test_item { + uint16_t *raw_data; + test_dname_t *dname; + item_type_t type; +}; + +typedef struct test_item test_item_t; + +struct test_rdata { + struct node *next, *prev; + uint count; + uint type; /*!< Might be handy */ + test_item_t *items; +}; + +typedef struct test_rdata test_rdata_t; + +struct test_rrset { + struct node *next, *prev; + test_dname_t *owner; + uint16_t type; + uint16_t rclass; + uint32_t ttl; + struct test_rrset *rrsigs; + uint16_t *wire; + list rdata_list; +}; + +typedef struct test_rrset test_rrset_t; + +struct test_response { + struct node *next, *prev; + /* This is basically same thing as actual response structure */ + uint16_t query; + test_dname_t *qname; + uint16_t qclass; + uint16_t qtype; + uint16_t id; + uint8_t flags1; + uint8_t flags2; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; + + /* Arrays of rrsets */ + + test_rrset_t **question; + test_rrset_t **answer; + test_rrset_t **authority; + test_rrset_t **additional; + + short size; + + /* what about the rest of the values? + * they cannot be modified from API, but this is probably the best + * place to test them as well */ +}; + +typedef struct test_response test_response_t; + +/*!< \brief contains lists of all the structures */ +struct test_data { + list dname_list; + list edns_list; + list rdata_list; + list node_list; + list rrset_list; + list response_list; + list raw_response_list; + list query_list; + list raw_query_list; + list item_list; + /* responses and queries together */ + list packet_list; + list raw_packet_list; + + avl_tree_test_t *node_tree; +}; + +typedef struct test_data test_data_t; + +/*!< \brief Parses resource with data and creates all possible structures. */ +test_data_t *create_test_data_from_dump(); + +test_data_t *data_for_knot_tests; + +#endif // KNOT_TESTS_LOADER_H diff --git a/src/tests/libknot/realdata/unittests_libknot_realdata.c b/src/tests/libknot/realdata/unittests_libknot_realdata.c new file mode 100644 index 0000000..e557c43 --- /dev/null +++ b/src/tests/libknot/realdata/unittests_libknot_realdata.c @@ -0,0 +1,93 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +//#include "knot/common.h" +#include "common/libtap/tap_unit.h" + +#include "tests/libknot/realdata/libknot_tests_loader_realdata.h" + +// Units to test +#include "tests/libknot/realdata/libknot/dname_tests_realdata.h" +#include "tests/libknot/realdata/libknot/response_tests_realdata.h" +//#include "libknot/edns_tests.h" +#include "tests/libknot/realdata/libknot/node_tests_realdata.h" +#include "tests/libknot/realdata/libknot/rdata_tests_realdata.h" +//#include "libknot/response_tests_realdata.h" +#include "tests/libknot/realdata/libknot/rrset_tests_realdata.h" +//#include "libknot/zone_tests_realdata.h" +#include "tests/libknot/realdata/libknot/zonedb_tests_realdata.h" +#include "tests/libknot/realdata/libknot/packet_tests_realdata.h" + +#include "common/lists.h" +// Run all loaded units +int main(int argc, char *argv[]) +{ + data_for_knot_tests = create_test_data_from_dump(); + + if (data_for_knot_tests == NULL) { + diag("Data could not be loaded!"); + return 0; + } + + // Open log +// log_init(LOG_UPTO(LOG_ERR), LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING)); + + // Build test set + unit_api *tests[] = { + + /* DNS units */ +// &cuckoo_tests_api, //! Cuckoo hashing unit + &dname_tests_api, //! DNS library (dname) unit +// &edns_tests_api, //! DNS library (EDNS0) unit + &node_tests_api, //! DNS library (node) unit + &rdata_tests_api, //! DNS library (rdata) unit + &packet_tests_api, +// &response_tests_api, //! DNS library (response) unit + &response_tests_api, //! DNS library (response) unit + &rrset_tests_api, //! DNS library (rrset) unit +// &zone_tests_api, //! DNS library (zone) unit +// &zonedb_tests_api, //! DNS library (zonedb) unit + NULL + }; + + // Plan number of tests + int id = 0; + int test_count = 0; + note("Units:"); + while (tests[id] != NULL) { + note("- %s : %d tests", tests[id]->name, + tests[id]->count(argc, argv)); + test_count += tests[id]->count(argc, argv); + ++id; + } + + plan(test_count); + + // Run tests + id = 0; + while (tests[id] != NULL) { + diag("Testing unit: %s", tests[id]->name); + tests[id]->run(argc, argv); + ++id; + } + +// log_close(); + + // Evaluate + return exit_status(); +} + diff --git a/src/tests/libknot/unittests_libknot.c b/src/tests/libknot/unittests_libknot.c new file mode 100644 index 0000000..62d4b90 --- /dev/null +++ b/src/tests/libknot/unittests_libknot.c @@ -0,0 +1,90 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include "knot/common.h" +#include "common/libtap/tap_unit.h" + +// Units to test +#include "tests/libknot/libknot/cuckoo_tests.h" +#include "tests/libknot/libknot/dname_tests.h" +#include "tests/libknot/libknot/edns_tests.h" +#include "tests/libknot/libknot/node_tests.h" +#include "tests/libknot/libknot/rdata_tests.h" +#include "tests/libknot/libknot/response_tests.h" +#include "tests/libknot/libknot/rrset_tests.h" +#include "tests/libknot/libknot/zone_tests.h" +#include "tests/libknot/libknot/dname_table_tests.h" +#include "tests/libknot/libknot/nsec3_tests.h" +#include "tests/libknot/libknot/packet_tests.h" +#include "tests/libknot/libknot/query_tests.h" +#include "tests/libknot/libknot/zonedb_tests.h" +#include "tests/libknot/libknot/zone_tree_tests.h" + +// Run all loaded units +int main(int argc, char *argv[]) +{ + // Open log +// log_init(LOG_UPTO(LOG_ERR), LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING)); + + // Build test set + unit_api *tests[] = { + + /* DNS units */ + &cuckoo_tests_api, //! Cuckoo hashing unit + &dname_tests_api, //! DNS library (dname) unit + &edns_tests_api, //! DNS library (EDNS0) unit + &zone_tests_api, //! DNS library (zone) unit + &node_tests_api, //! DNS library (node) unit + &rdata_tests_api, //! DNS library (rdata) unit + &response_tests_api, //! DNS library (response) unit + &rrset_tests_api, //! DNS library (rrset) unit + &dname_table_tests_api, + &nsec3_tests_api, + &packet_tests_api, + &query_tests_api, + &zonedb_tests_api, //! DNS library (zonedb) unit + &zone_tree_tests_api, + NULL + }; + + // Plan number of tests + int id = 0; + int test_count = 0; + note("Units:"); + while (tests[id] != NULL) { + note("- %s : %d tests", tests[id]->name, + tests[id]->count(argc, argv)); + test_count += tests[id]->count(argc, argv); + ++id; + } + + plan(test_count); + + // Run tests + id = 0; + while (tests[id] != NULL) { + diag("Testing unit: %s", tests[id]->name); + tests[id]->run(argc, argv); + ++id; + } + +// log_close(); + + // Evaluate + return exit_status(); +} + diff --git a/src/tests/unittests_main.c b/src/tests/unittests_main.c new file mode 100644 index 0000000..2d57ed2 --- /dev/null +++ b/src/tests/unittests_main.c @@ -0,0 +1,84 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include "knot/common.h" +#include "common/libtap/tap_unit.h" + +// Units to test +#include "tests/common/slab_tests.h" +#include "tests/common/skiplist_tests.h" +#include "tests/common/events_tests.h" +#include "tests/common/da_tests.h" +#include "tests/common/acl_tests.h" +#include "tests/common/fdset_tests.h" +#include "tests/knot/dthreads_tests.h" +#include "tests/knot/journal_tests.h" +#include "tests/knot/server_tests.h" +#include "tests/knot/conf_tests.h" + +// Run all loaded units +int main(int argc, char *argv[]) +{ + // Open log + log_init(); + log_levels_set(LOGT_SYSLOG, LOG_ANY, 0); + + // Build test set + unit_api *tests[] = { + /* Core data structures. */ + &journal_tests_api, //! Journal unit + &slab_tests_api, //! SLAB allocator unit + &skiplist_tests_api, //! Skip list unit + &dthreads_tests_api, //! DThreads testing unit + &events_tests_api, //! Events testing unit + &da_tests_api, //! Dynamic array unit + &acl_tests_api, //! ACLs + &fdset_tests_api, //! FDSET polling wrapper + + /* Server parts. */ + &conf_tests_api, //! Configuration parser tests + &server_tests_api, //! Server unit + NULL + }; + + // Plan number of tests + int id = 0; + int test_count = 0; + note("Units:"); + while (tests[id] != NULL) { + note("- %s : %d tests", tests[id]->name, + tests[id]->count(argc, argv)); + test_count += tests[id]->count(argc, argv); + ++id; + } + + plan(test_count); + + // Run tests + id = 0; + while (tests[id] != NULL) { + diag("Testing unit: %s", tests[id]->name); + tests[id]->run(argc, argv); + ++id; + } + + log_close(); + + // Evaluate + return exit_status(); +} + diff --git a/src/zcompile/LICENSE b/src/zcompile/LICENSE new file mode 100644 index 0000000..55faacf --- /dev/null +++ b/src/zcompile/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2001-2006, NLnet Labs. All rights reserved. + +This software is open source. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +Neither the name of the NLNET LABS nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/src/zcompile/parser-descriptor.c b/src/zcompile/parser-descriptor.c new file mode 100644 index 0000000..41e7f2d --- /dev/null +++ b/src/zcompile/parser-descriptor.c @@ -0,0 +1,535 @@ +/*! + * \file parser-descriptor.c + * + * \author Modifications by Jan Kadlec <jan.kadlec@nic.cz>, + * most of the work by NLnet Labs. + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * \brief Contains resource record descriptor and its API + * + * \addtogroup zoneparser + * @{ + */ + +/* + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <config.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <assert.h> +#include <string.h> +#include <sys/types.h> + +//#include "common.h" +#include "zcompile/parser-descriptor.h" +/* TODO this has to be removed - move tokens to separate file + but can it be done?) */ +#include "zcompile/zcompile.h" +/* FIXME: Generate .y and .l to zoneparser/ */ +#include "zparser.h" + +enum desclen { PARSER_RRTYPE_DESCRIPTORS_LENGTH = 32770 }; // used to be 101 + +/* Taken from RFC 1035, section 3.2.4. */ +static knot_lookup_table_t dns_rrclasses[] = { + { PARSER_CLASS_IN, "IN" }, /* the Internet */ + { PARSER_CLASS_CS, "CS" }, /* the CSNET class (Obsolete) */ + { PARSER_CLASS_CH, "CH" }, /* the CHAOS class */ + { PARSER_CLASS_HS, "HS" }, /* Hesiod */ + { 0, NULL } +}; +static parser_rrtype_descriptor_t + knot_rrtype_descriptors[PARSER_RRTYPE_DESCRIPTORS_LENGTH] = { + /* 0 */ + { 0, 0, NULL, 1, { PARSER_RDATA_WF_BINARY }, true }, + /* 1 */ + { PARSER_RRTYPE_A, T_A, "A", 1, { PARSER_RDATA_WF_A }, true }, + /* 2 */ + { PARSER_RRTYPE_NS, T_NS, "NS", 1, + { PARSER_RDATA_WF_COMPRESSED_DNAME }, true }, + /* 3 */ + { PARSER_RRTYPE_MD, T_MD, "MD", 1, + { PARSER_RDATA_WF_UNCOMPRESSED_DNAME }, true }, + /* 4 */ + { PARSER_RRTYPE_MF, T_MF, "MF", 1, + { PARSER_RDATA_WF_UNCOMPRESSED_DNAME }, true }, + /* 5 */ + { PARSER_RRTYPE_CNAME, T_CNAME, "CNAME", 1, + { PARSER_RDATA_WF_COMPRESSED_DNAME }, true }, + /* 6 */ + { PARSER_RRTYPE_SOA, T_SOA, "SOA", 7, + { PARSER_RDATA_WF_COMPRESSED_DNAME, PARSER_RDATA_WF_COMPRESSED_DNAME, + PARSER_RDATA_WF_LONG, PARSER_RDATA_WF_LONG, PARSER_RDATA_WF_LONG, + PARSER_RDATA_WF_LONG, PARSER_RDATA_WF_LONG }, true }, + /* 7 */ + { PARSER_RRTYPE_MB, T_MB, "MB", 1, + { PARSER_RDATA_WF_COMPRESSED_DNAME }, true }, + /* 8 */ + { PARSER_RRTYPE_MG, T_MG, "MG", 1, + { PARSER_RDATA_WF_COMPRESSED_DNAME }, true }, + /* 9 */ + { PARSER_RRTYPE_MR, T_MR, "MR", 1, + { PARSER_RDATA_WF_COMPRESSED_DNAME }, true }, + /* 10 */ + { PARSER_RRTYPE_NULL, T_NULL, NULL, 1, + { PARSER_RDATA_WF_BINARY }, true }, + /* 11 */ + { PARSER_RRTYPE_WKS, T_WKS, "WKS", 2, + { PARSER_RDATA_WF_A, PARSER_RDATA_WF_BINARY }, true }, + /* 12 */ + { PARSER_RRTYPE_PTR, T_PTR, "PTR", 1, + { PARSER_RDATA_WF_COMPRESSED_DNAME }, true }, + /* 13 */ + { PARSER_RRTYPE_HINFO, T_HINFO, "HINFO", 2, + { PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT }, true }, + /* 14 */ + { PARSER_RRTYPE_MINFO, T_MINFO, "MINFO", 2, + { PARSER_RDATA_WF_COMPRESSED_DNAME, + PARSER_RDATA_WF_COMPRESSED_DNAME }, true }, + /* 15 */ + { PARSER_RRTYPE_MX, T_MX, "MX", 2, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_COMPRESSED_DNAME }, true }, + /* 16 */ /* This is obscure, but I guess there's no other way */ + { PARSER_RRTYPE_TXT, T_TXT, "TXT", PARSER_MAX_RDATA_ITEMS, + { PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT }, false }, + /* 17 */ + { PARSER_RRTYPE_RP, T_RP, "RP", 2, + { PARSER_RDATA_WF_COMPRESSED_DNAME, + PARSER_RDATA_WF_COMPRESSED_DNAME }, true }, + /* 18 */ + { PARSER_RRTYPE_AFSDB, T_AFSDB, "AFSDB", 2, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_COMPRESSED_DNAME }, true }, + /* 19 */ + { PARSER_RRTYPE_X25, T_X25, "X25", 1, + { PARSER_RDATA_WF_TEXT }, true }, + /* 20 */ + { PARSER_RRTYPE_ISDN, T_ISDN, "ISDN", 2, + { PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT }, false }, + /* 21 */ + { PARSER_RRTYPE_RT, T_RT, "RT", 2, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_COMPRESSED_DNAME }, true }, + /* 22 */ + { PARSER_RRTYPE_NSAP, T_NSAP, "NSAP", 1, + { PARSER_RDATA_WF_BINARY }, true }, + /* 23 */ + { 23, 0, NULL, 1, { PARSER_RDATA_WF_BINARY }, true }, + /* 24 */ + { PARSER_RRTYPE_SIG, T_SIG, "SIG", 9, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_BYTE, PARSER_RDATA_WF_BYTE, + PARSER_RDATA_WF_LONG, PARSER_RDATA_WF_LONG, PARSER_RDATA_WF_LONG, + PARSER_RDATA_WF_SHORT,PARSER_RDATA_WF_UNCOMPRESSED_DNAME, + PARSER_RDATA_WF_BINARY }, true }, + /* 25 */ + { PARSER_RRTYPE_KEY, T_KEY, "KEY", 4, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_BYTE, + PARSER_RDATA_WF_BYTE, PARSER_RDATA_WF_BINARY }, true }, + /* 26 */ + { PARSER_RRTYPE_PX, T_PX, "PX", 3, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_UNCOMPRESSED_DNAME, + PARSER_RDATA_WF_UNCOMPRESSED_DNAME }, true }, + /* 27 */ + { 27, 0, NULL, 1, { PARSER_RDATA_WF_BINARY }, true }, + /* 28 */ + { PARSER_RRTYPE_AAAA, T_AAAA, "AAAA", 1, + { PARSER_RDATA_WF_AAAA }, true }, + /* 29 */ + { PARSER_RRTYPE_LOC, T_LOC, "LOC", 1, + { PARSER_RDATA_WF_BINARY }, true }, + /* 30 */ + { PARSER_RRTYPE_NXT, T_NXT, "NXT", 2, + { PARSER_RDATA_WF_UNCOMPRESSED_DNAME, + PARSER_RDATA_WF_BINARY }, true }, + /* 31 */ + { 31, 0, NULL, 1, { PARSER_RDATA_WF_BINARY }, true }, + /* 32 */ + { 32, 0, NULL, 1, { PARSER_RDATA_WF_BINARY }, true }, + /* 33 */ + { PARSER_RRTYPE_SRV, T_SRV, "SRV", 4, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_SHORT, + PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_UNCOMPRESSED_DNAME }, + true }, + /* 34 */ + { 34, 0, NULL, 1, { PARSER_RDATA_WF_BINARY }, true }, + /* 35 */ + { PARSER_RRTYPE_NAPTR, T_NAPTR, "NAPTR", 6, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_UNCOMPRESSED_DNAME }, true }, + /* 36 */ + { PARSER_RRTYPE_KX, T_KX, "KX", 2, + { PARSER_RDATA_WF_SHORT, + PARSER_RDATA_WF_UNCOMPRESSED_DNAME }, true }, + /* 37 */ + { PARSER_RRTYPE_CERT, T_CERT, "CERT", 4, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_SHORT, + PARSER_RDATA_WF_BYTE, PARSER_RDATA_WF_BINARY }, true }, + /* 38 */ + { PARSER_RRTYPE_A6, T_A6, NULL, 1, { PARSER_RDATA_WF_BINARY }, true }, + /* 39 */ + { PARSER_RRTYPE_DNAME, T_DNAME, "DNAME", 1, + { PARSER_RDATA_WF_UNCOMPRESSED_DNAME }, true }, + /* 40 */ + { 40, 0, NULL, 1, { PARSER_RDATA_WF_BINARY }, true }, + /* 41 */ + /* OPT has its parser token, but should never be in zone file... */ + { PARSER_RRTYPE_OPT, T_OPT, "OPT", 1, + { PARSER_RDATA_WF_BINARY }, true }, + /* 42 */ + { PARSER_RRTYPE_APL, T_APL, "APL", PARSER_MAX_RDATA_ITEMS, + { PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL, + PARSER_RDATA_WF_APL, PARSER_RDATA_WF_APL }, false }, + /* 43 */ + { PARSER_RRTYPE_DS, T_DS, "DS", 4, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_BYTE, + PARSER_RDATA_WF_BYTE, PARSER_RDATA_WF_BINARY }, true }, + /* 44 */ + { PARSER_RRTYPE_SSHFP, T_SSHFP, "SSHFP", 3, + { PARSER_RDATA_WF_BYTE, PARSER_RDATA_WF_BYTE, + PARSER_RDATA_WF_BINARY }, true }, + /* 45 */ + { PARSER_RRTYPE_IPSECKEY, T_IPSECKEY, "IPSECKEY", 5, + { PARSER_RDATA_WF_BYTE, PARSER_RDATA_WF_BYTE, + PARSER_RDATA_WF_BYTE, PARSER_RDATA_WF_IPSECGATEWAY, + PARSER_RDATA_WF_BINARY }, false }, + /* 46 */ + { PARSER_RRTYPE_RRSIG, T_RRSIG, "RRSIG", 9, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_BYTE, + PARSER_RDATA_WF_BYTE, PARSER_RDATA_WF_LONG, + PARSER_RDATA_WF_LONG, PARSER_RDATA_WF_LONG, + PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_BINARY, + PARSER_RDATA_WF_BINARY }, true }, + /* 47 */ + { PARSER_RRTYPE_NSEC, T_NSEC, "NSEC", 2, + { PARSER_RDATA_WF_BINARY, PARSER_RDATA_WF_BINARY }, true }, + /* 48 */ + { PARSER_RRTYPE_DNSKEY, T_DNSKEY, "DNSKEY", 4, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_BYTE, + PARSER_RDATA_WF_BYTE, PARSER_RDATA_WF_BINARY }, true }, + /* 49 */ + { PARSER_RRTYPE_DHCID, T_DHCID, "DHCID", 1, { PARSER_RDATA_WF_BINARY }, true }, + /* 50 */ + { PARSER_RRTYPE_NSEC3, T_NSEC3, "NSEC3", 6, + { PARSER_RDATA_WF_BYTE, /* hash type */ + PARSER_RDATA_WF_BYTE, /* flags */ + PARSER_RDATA_WF_SHORT, /* iterations */ + PARSER_RDATA_WF_BINARYWITHLENGTH, /* salt */ + PARSER_RDATA_WF_BINARYWITHLENGTH, /* next hashed name */ + PARSER_RDATA_WF_BINARY /* type bitmap */ }, true }, + /* 51 */ + { PARSER_RRTYPE_NSEC3PARAM, T_NSEC3PARAM, "NSEC3PARAM", 4, + { PARSER_RDATA_WF_BYTE, /* hash type */ + PARSER_RDATA_WF_BYTE, /* flags */ + PARSER_RDATA_WF_SHORT, /* iterations */ + PARSER_RDATA_WF_BINARYWITHLENGTH /* salt */ }, true }, + /* 52 */ + + + /* In NSD they have indices between 52 and 99 filled with + unknown types. TODO add here if it's really needed? */ + /* it is indeed needed, in rrtype_from_string */ + + /* There's a GNU extension that works like this: [first ... last] = value */ + + [53 ... 98] = { PARSER_RRTYPE_TYPEXXX, T_UTYPE, NULL, 1, { PARSER_RDATA_WF_BINARY }}, + + /* 99 */ + [99] = { PARSER_RRTYPE_SPF, T_SPF, "SPF", PARSER_MAX_RDATA_ITEMS, + { PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT, + PARSER_RDATA_WF_TEXT, PARSER_RDATA_WF_TEXT }, false }, + [100 ... 32768] = { PARSER_RRTYPE_TYPEXXX, T_UTYPE, NULL, 1, { PARSER_RDATA_WF_BINARY }}, + /* 32769 */ + [32769] = { PARSER_RRTYPE_DLV, T_DLV, "DLV", 4, + { PARSER_RDATA_WF_SHORT, PARSER_RDATA_WF_BYTE, + PARSER_RDATA_WF_BYTE, PARSER_RDATA_WF_BINARY } }, +}; + +parser_rrtype_descriptor_t *parser_rrtype_descriptor_by_type(uint16_t type) +{ + if (type <= PARSER_RRTYPE_DLV) { + return &knot_rrtype_descriptors[type]; + } + return &knot_rrtype_descriptors[0]; +} + +/* I see a lot of potential here to speed up zone parsing - this is O(n) * + * could be better */ +parser_rrtype_descriptor_t *parser_rrtype_descriptor_by_name(const char *name) +{ + if (!name) { + return NULL; + } + + if (strcasecmp(name, "IN") == 0) { + return NULL; + } + + if (isdigit((int)name[0])) { + return NULL; + } + +// /* The most common - A and NS. */ +// if (strcasecmp(name, "NS") == 0) { +// return &knot_rrtype_descriptors[2]; +// } + +// if (strcasecmp(name, "A") == 0) { +// return &knot_rrtype_descriptors[1]; +// } + +// /* Then RRSIG */ +// if (strcasecmp(name, "RRSIG") == 0) { +// return &knot_rrtype_descriptors[46]; +// } + +// /* Then DS */ +// if (strcasecmp(name, "DS") == 0) { +// return &knot_rrtype_descriptors[43]; +// } +// /* Then NSEC3 */ +// if (strcasecmp(name, "NSEC3") == 0) { +// return &knot_rrtype_descriptors[50]; +// } +// /* Then NSEC */ +// if (strcasecmp(name, "NSEC") == 0) { +// return &knot_rrtype_descriptors[47]; +// } + + int i; + + for (i = 0; i < PARSER_RRTYPE_LAST + 1; ++i) { + if (knot_rrtype_descriptors[i].name && + strcasecmp(knot_rrtype_descriptors[i].name, name) == 0) { + return &knot_rrtype_descriptors[i]; + } + } + + if (knot_rrtype_descriptors[PARSER_RRTYPE_DLV].name && + strcasecmp(knot_rrtype_descriptors[PARSER_RRTYPE_DLV].name, + name) == 0) { + return &knot_rrtype_descriptors[PARSER_RRTYPE_DLV]; + } + + return NULL; +} + +const char *parser_rrtype_to_string(uint16_t rrtype) +{ + static char buf[20]; + parser_rrtype_descriptor_t *descriptor = + parser_rrtype_descriptor_by_type(rrtype); + if (descriptor->name) { + return descriptor->name; + } else { + snprintf(buf, sizeof(buf), "TYPE%d", (int) rrtype); + return buf; + } +} + +uint16_t parser_rrtype_from_string(const char *name) +{ + char *end; + long rrtype; + parser_rrtype_descriptor_t *entry; + + entry = parser_rrtype_descriptor_by_name(name); + if (entry) { + return entry->type; + } + + if (strlen(name) < 5) { + return 0; + } + + if (strncasecmp(name, "TYPE", 4) != 0) { + return 0; + } + + if (!isdigit((int)name[4])) { + return 0; + } + + /* The rest from the string must be a number. */ + rrtype = strtol(name + 4, &end, 10); + if (*end != '\0') { + return 0; + } + if (rrtype < 0 || rrtype > 65535L) { + return 0; + } + + return (uint16_t) rrtype; +} + +const char *parser_rrclass_to_string(uint16_t rrclass) +{ + static char buf[20]; + knot_lookup_table_t *entry = knot_lookup_by_id(dns_rrclasses, + rrclass); + if (entry) { + assert(strlen(entry->name) < sizeof(buf)); + knot_strlcpy(buf, entry->name, sizeof(buf)); + } else { + snprintf(buf, sizeof(buf), "CLASS%d", (int) rrclass); + } + return buf; +} + +uint16_t parser_rrclass_from_string(const char *name) +{ + char *end; + long rrclass; + knot_lookup_table_t *entry; + + entry = knot_lookup_by_name(dns_rrclasses, name); + if (entry) { + return (uint16_t) entry->id; + } + + if (strlen(name) < 6) { + return 0; + } + + if (strncasecmp(name, "CLASS", 5) != 0) { + return 0; + } + + if (!isdigit((int)name[5])) { + return 0; + } + + // The rest from the string must be a number. + rrclass = strtol(name + 5, &end, 10); + if (*end != '\0') { + return 0; + } + if (rrclass < 0 || rrclass > 65535L) { + return 0; + } + + return (uint16_t) rrclass; +} + diff --git a/src/zcompile/parser-descriptor.h b/src/zcompile/parser-descriptor.h new file mode 100644 index 0000000..c7865dd --- /dev/null +++ b/src/zcompile/parser-descriptor.h @@ -0,0 +1,278 @@ +/*! + * \file parser-descriptor.h + * + * \author Modifications by Jan Kadlec <jan.kadlec@nic.cz>, + * most of the work by NLnet Labs. + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * \brief Contains resource record descriptor and its API + * + * \addtogroup zoneparser + * @{ + */ + +/* + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _KNOTD_PARSER_DESCRIPTOR_H_ +#define _KNOTD_PARSER_DESCRIPTOR_H_ + +#include <stdint.h> +#include <stdbool.h> + +#include "libknot/util/utils.h" + +enum parser_mxrdtln { + PARSER_MAX_RDATA_ITEMS = 64, + PARSER_MAX_RDATA_ITEM_SIZE = 255, + PARSER_MAX_RDATA_WIRE_SIZE = + PARSER_MAX_RDATA_ITEMS * PARSER_MAX_RDATA_ITEM_SIZE +}; +//#define MAXRDATALEN 64 + +/* 64 is in NSD. Seems a little too much, but I'd say it's not a real issue. */ + +/*! + * \brief Enum containing RR class codes. + */ +enum parser_rr_class { + PARSER_CLASS_IN = 1, + PARSER_CLASS_CS, + PARSER_CLASS_CH, + PARSER_CLASS_HS, + PARSER_CLASS_NONE = 254, + PARSER_CLASS_ANY = 255 +}; + +typedef enum parser_rr_class parser_rr_class_t; + +enum parser_rr_type { + PARSER_RRTYPE_UNKNOWN, /*!< 0 - an unknown type */ + PARSER_RRTYPE_A, /*!< 1 - a host address */ + PARSER_RRTYPE_NS, /*!< 2 - an authoritative name server */ + PARSER_RRTYPE_MD, /*!< 3 - a mail destination (Obsolete - use MX) */ + PARSER_RRTYPE_MF, /*!< 4 - a mail forwarder (Obsolete - use MX) */ + PARSER_RRTYPE_CNAME, /*!< 5 - the canonical name for an alias */ + PARSER_RRTYPE_SOA, /*!< 6 - marks the start of a zone of authority */ + PARSER_RRTYPE_MB, /*!< 7 - a mailbox domain name (EXPERIMENTAL) */ + PARSER_RRTYPE_MG, /*!< 8 - a mail group member (EXPERIMENTAL) */ + PARSER_RRTYPE_MR, /*!< 9 - a mail rename domain name (EXPERIMENTAL) */ + PARSER_RRTYPE_NULL, /*!< 10 - a null RR (EXPERIMENTAL) */ + PARSER_RRTYPE_WKS, /*!< 11 - a well known service description */ + PARSER_RRTYPE_PTR, /*!< 12 - a domain name pointer */ + PARSER_RRTYPE_HINFO, /*!< 13 - host information */ + PARSER_RRTYPE_MINFO, /*!< 14 - mailbox or mail list information */ + PARSER_RRTYPE_MX, /*!< 15 - mail exchange */ + PARSER_RRTYPE_TXT, /*!< 16 - text strings */ + PARSER_RRTYPE_RP, /*!< 17 - RFC1183 */ + PARSER_RRTYPE_AFSDB, /*!< 18 - RFC1183 */ + PARSER_RRTYPE_X25, /*!< 19 - RFC1183 */ + PARSER_RRTYPE_ISDN, /*!< 20 - RFC1183 */ + PARSER_RRTYPE_RT, /*!< 21 - RFC1183 */ + PARSER_RRTYPE_NSAP, /*!< 22 - RFC1706 */ + + PARSER_RRTYPE_SIG = 24, /*!< 24 - 2535typecode */ + PARSER_RRTYPE_KEY, /*!< 25 - 2535typecode */ + PARSER_RRTYPE_PX, /*!< 26 - RFC2163 */ + + PARSER_RRTYPE_AAAA = 28, /*!< 28 - ipv6 address */ + PARSER_RRTYPE_LOC, /*!< 29 - LOC record RFC1876 */ + PARSER_RRTYPE_NXT, /*!< 30 - 2535typecode */ + + PARSER_RRTYPE_SRV = 33, /*!< 33 - SRV record RFC2782 */ + + PARSER_RRTYPE_NAPTR = 35, /*!< 35 - RFC2915 */ + PARSER_RRTYPE_KX, /*!< 36 - RFC2230 Key Exchange Delegation Record */ + PARSER_RRTYPE_CERT, /*!< 37 - RFC2538 */ + PARSER_RRTYPE_A6, /*!< 38 - RFC2874 */ + PARSER_RRTYPE_DNAME, /*!< 39 - RFC2672 */ + + PARSER_RRTYPE_OPT = 41, /*!< 41 - Pseudo OPT record... */ + PARSER_RRTYPE_APL, /*!< 42 - RFC3123 */ + PARSER_RRTYPE_DS, /*!< 43 - RFC 4033, 4034, and 4035 */ + PARSER_RRTYPE_SSHFP, /*!< 44 - SSH Key Fingerprint */ + PARSER_RRTYPE_IPSECKEY, /*!< 45 - public key for ipsec use. RFC 4025 */ + PARSER_RRTYPE_RRSIG, /*!< 46 - RFC 4033, 4034, and 4035 */ + PARSER_RRTYPE_NSEC, /*!< 47 - RFC 4033, 4034, and 4035 */ + PARSER_RRTYPE_DNSKEY, /*!< 48 - RFC 4033, 4034, and 4035 */ + PARSER_RRTYPE_DHCID, /*!< 49 - RFC4701 DHCP information */ + /*! + * \brief 50 - NSEC3, secure denial, prevents zonewalking + */ + PARSER_RRTYPE_NSEC3, + /*! + * \brief 51 - NSEC3PARAM at zone apex nsec3 parameters + */ + PARSER_RRTYPE_NSEC3PARAM, + + /* TODO consider some better way of doing this, indices too high */ + + PARSER_RRTYPE_SPF = 99, /*!< RFC 4408 */ + + // not designating any RRs + PARSER_RRTYPE_TSIG = 250, + PARSER_RRTYPE_IXFR = 251, + PARSER_RRTYPE_AXFR = 252, + /*! + * \brief A request for mailbox-related records (MB, MG or MR) + */ + PARSER_RRTYPE_MAILB = 253, + /*! + * \brief A request for mail agent RRs (Obsolete - see MX) + */ + PARSER_RRTYPE_MAILA = 254, + PARSER_RRTYPE_ANY = 255, /*!< any type (wildcard) */ + + // totally weird numbers (cannot use for indexing) + PARSER_RRTYPE_TA = 32768, /*!< DNSSEC Trust Authorities */ + PARSER_RRTYPE_DLV = 32769, /*!< RFC 4431 */ + PARSER_RRTYPE_TYPEXXX = 32770 +}; + +/*! + * \brief Enum containing RR type codes. + * + * \todo Not all indices can be used for indexing. + */ +typedef enum parser_rr_type parser_rr_type_t; + +static uint const PARSER_RRTYPE_LAST = PARSER_RRTYPE_NSEC3PARAM; + +enum parser_rdata_wireformat { + /*! + * \brief Possibly compressed domain name. + */ + PARSER_RDATA_WF_COMPRESSED_DNAME = 50, + PARSER_RDATA_WF_UNCOMPRESSED_DNAME = 51, /*!< Uncompressed domain name. */ + PARSER_RDATA_WF_LITERAL_DNAME = 52, /*!< Literal (not downcased) dname. */ + PARSER_RDATA_WF_BYTE = 1, /*!< 8-bit integer. */ + PARSER_RDATA_WF_SHORT = 2, /*!< 16-bit integer. */ + PARSER_RDATA_WF_LONG = 4, /*!< 32-bit integer. */ + PARSER_RDATA_WF_TEXT = 53, /*!< Text string. */ + PARSER_RDATA_WF_A = 58, /*!< 32-bit IPv4 address. */ + PARSER_RDATA_WF_AAAA = 16, /*!< 128-bit IPv6 address. */ + PARSER_RDATA_WF_BINARY = 54, /*!< Binary data (unknown length). */ + /*! + * \brief Binary data preceded by 1 byte length + */ + PARSER_RDATA_WF_BINARYWITHLENGTH = 55, + PARSER_RDATA_WF_APL = 56, /*!< APL data. */ + PARSER_RDATA_WF_IPSECGATEWAY = 57 /*!< IPSECKEY gateway ip4, ip6 or dname. */ +}; + +/*! + * \brief Enum containing wireformat codes. Taken from NSD's "dns.h" + */ +typedef enum parser_rdatawireformat parser_rdata_wireformat_t; + +struct parser_rrtype_descriptor { + uint16_t type; /*!< RR type */ + int token; /*< Token used in zoneparser */ + const char *name; /*!< Textual name. */ + uint8_t length; /*!< Maximum number of RDATA items. */ + /*! + * \brief rdata_wireformat_type + */ + uint8_t wireformat[PARSER_MAX_RDATA_ITEMS]; + bool fixed_items; /*!< Has fixed number of RDATA items? */ +}; + +/*! + * \brief Structure holding RR descriptor + */ +typedef struct parser_rrtype_descriptor parser_rrtype_descriptor_t; + +/*! + * \brief Gets RR descriptor for given RR type. + * + * \param type Code of RR type whose descriptor should be returned. + * + * \return RR descriptor for given type code, NULL descriptor if + * unknown type. + * + * \todo Change return value to const. + */ +parser_rrtype_descriptor_t *parser_rrtype_descriptor_by_type(uint16_t type); + +/*! + * \brief Gets RR descriptor for given RR name. + * + * \param name Mnemonic of RR type whose descriptor should be returned. + * + * \return RR descriptor for given name, NULL descriptor if + * unknown type. + * + * \todo Change return value to const. + */ +parser_rrtype_descriptor_t *parser_rrtype_descriptor_by_name(const char *name); + +/*! + * \brief Converts numeric type representation to mnemonic string. + * + * \param rrtype Type RR type code to be converted. + * + * \return Mnemonic string if found, str(TYPE[rrtype]) otherwise. + */ +const char *parser_rrtype_to_string(uint16_t rrtype); + +/*! + * \brief Converts mnemonic string representation of a type to numeric one. + * + * \param name Mnemonic string to be converted. + * + * \return Correct code if found, 0 otherwise. + */ +uint16_t parser_rrtype_from_string(const char *name); + +/*! + * \brief Converts numeric class representation to string one. + * + * \param rrclass Class code to be converted. + * + * \return String represenation of class if found, + * str(CLASS[rrclass]) otherwise. + */ +const char *parser_rrclass_to_string(uint16_t rrclass); + +/*! + * \brief Converts string representation of a class to numeric one. + * + * \param name Class string to be converted. + * + * \return Correct code if found, 0 otherwise. + */ +uint16_t parser_rrclass_from_string(const char *name); + +#endif /* _KNOTD_PARSER_DESCRIPTOR_H_ */ + +/*! @} */ diff --git a/src/zcompile/parser-util.c b/src/zcompile/parser-util.c new file mode 100644 index 0000000..e7733ef --- /dev/null +++ b/src/zcompile/parser-util.c @@ -0,0 +1,2435 @@ +/*! + * \file parser-util.c + * + * \author NLnet Labs + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * \brief utility functions for zone parser. + * + * \addtogroup zoneparser + * @{ + */ + +/* + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <config.h> +#include <assert.h> +#include <fcntl.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <time.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <netdb.h> + +//#include "common.h" +#include "common/base32hex.h" +#include "zcompile/parser-util.h" +#include "zcompile/zcompile.h" +#include "libknot/util/descriptor.h" +#include "libknot/util/utils.h" +#include "zcompile/zcompile-error.h" + +#define IP6ADDRLEN (128/8) +#define NS_INT16SZ 2 +#define NS_INADDRSZ 4 +#define NS_IN6ADDRSZ 16 +#define APL_NEGATION_MASK 0x80U + +/* int + * inet_pton(af, src, dst) + * convert from presentation format (which usually means ASCII printable) + * to network format (which is usually some kind of binary format). + * return: + * 1 if the address was valid for the specified address family + * 0 if the address wasn't valid (`dst' is untouched in this case) + * -1 if some other error occurred (`dst' is untouched in this case, too) + * author: + * Paul Vixie, 1996. + */ +int inet_pton(int af, const char *src, void *dst) +{ + switch (af) { + case AF_INET: + return (inet_pton4(src, dst)); + case AF_INET6: + return (inet_pton6(src, dst)); + default: + errno = EAFNOSUPPORT; + return (-1); + } + /* NOTREACHED */ +} + +//int my_b32_pton(const char *src, uint8_t *target, size_t tsize) +//{ +// char ch; +// size_t p = 0; + +// memset(target, '\0', tsize); +// while ((ch = *src++)) { +// uint8_t d; +// size_t b; +// size_t n; + +// if (p + 5 >= tsize * 8) { +// return -1; +// } + +// if (isspace(ch)) { +// continue; +// } + +// if (ch >= '0' && ch <= '9') { +// d = ch - '0'; +// } else if (ch >= 'A' && ch <= 'V') { +// d = ch - 'A' + 10; +// } else if (ch >= 'a' && ch <= 'v') { +// d = ch - 'a' + 10; +// } else { +// return -1; +// } + +// b = 7 - p % 8; +// n = p / 8; + +// if (b >= 4) { +// target[n] |= d << (b - 4); +// } else { +// target[n] |= d >> (4 - b); +// target[n+1] |= d << (b + 4); +// } +// p += 5; +// } +// return (p + 7) / 8; +//} + + +#define Assert(Cond) if (!(Cond)) abort() + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +/* int + * inet_pton4(src, dst) + * like inet_aton() but without all the hexadecimal and shorthand. + * return: + * 1 if `src' is a valid dotted quad, else 0. + * notice: + * does not touch `dst' unless it's returning 1. + * author: + * Paul Vixie, 1996. + */ +int inet_pton4(const char *src, uint8_t *dst) +{ + static const char digits[] = "0123456789"; + int saw_digit, octets, ch; + uint8_t tmp[NS_INADDRSZ], *tp; + + saw_digit = 0; + octets = 0; + *(tp = tmp) = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr(digits, ch)) != NULL) { + uint32_t new = *tp * 10 + (pch - digits); + + if (new > 255) { + return (0); + } + *tp = new; + if (! saw_digit) { + if (++octets > 4) { + return (0); + } + saw_digit = 1; + } + } else if (ch == '.' && saw_digit) { + if (octets == 4) { + return (0); + } + *++tp = 0; + saw_digit = 0; + } else { + return (0); + } + } + if (octets < 4) { + return (0); + } + + memcpy(dst, tmp, NS_INADDRSZ); + return (1); +} + +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it's returning 1. + * (2) :: in a full address is silently ignored. + * credit: + * inspired by Mark Andrews. + * author: + * Paul Vixie, 1996. + */ +int inet_pton6(const char *src, uint8_t *dst) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + uint8_t tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp; + const char *xdigits, *curtok; + int ch, saw_xdigit; + uint32_t val; + + memset((tp = tmp), '\0', NS_IN6ADDRSZ); + endp = tp + NS_IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if (*src == ':') + if (*++src != ':') { + return (0); + } + curtok = src; + saw_xdigit = 0; + val = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) { + pch = strchr((xdigits = xdigits_u), ch); + } + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) { + return (0); + } + saw_xdigit = 1; + continue; + } + if (ch == ':') { + curtok = src; + if (!saw_xdigit) { + if (colonp) { + return (0); + } + colonp = tp; + continue; + } + if (tp + NS_INT16SZ > endp) { + return (0); + } + *tp++ = (uint8_t)(val >> 8) & 0xff; + *tp++ = (uint8_t) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && + inet_pton4(curtok, tp) > 0) { + tp += NS_INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if (saw_xdigit) { + if (tp + NS_INT16SZ > endp) { + return (0); + } + *tp++ = (uint8_t)(val >> 8) & 0xff; + *tp++ = (uint8_t) val & 0xff; + } + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + int i; + + for (i = 1; i <= n; i++) { + endp[- i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) { + return (0); + } + memcpy(dst, tmp, NS_IN6ADDRSZ); + return (1); +} + + +#ifndef IN6ADDRSZ +#define IN6ADDRSZ 16 /* IPv6 T_AAAA */ +#endif + +#ifndef INT16SZ +#define INT16SZ 2 /* for systems without 16-bit ints */ +#endif + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + + +/* char * + * inet_ntop(af, src, dst, size) + * convert a network format address to presentation format. + * return: + * pointer to presentation format address (`dst'), or NULL (see errno). + * author: + * Paul Vixie, 1996. + */ +//const char *inet_ntop(int af, const void *src, char *dst, size_t size) +//{ +// switch (af) { +// case AF_INET: +// return (inet_ntop4(src, dst, size)); +// case AF_INET6: +// return (inet_ntop6(src, dst, size)); +// default: +// errno = EAFNOSUPPORT; +// return (NULL); +// } +// /* NOTREACHED */ +//} + +/* const char * + * inet_ntop4(src, dst, size) + * format an IPv4 address, more or less like inet_ntoa() + * return: + * `dst' (as a const) + * notes: + * (1) uses no statics + * (2) takes a u_char* not an in_addr as input + * author: + * Paul Vixie, 1996. + */ +const char *inet_ntop4(const u_char *src, char *dst, size_t size) +{ + static const char fmt[] = "%u.%u.%u.%u"; + char tmp[sizeof "255.255.255.255"]; + int l; + + l = snprintf(tmp, size, fmt, src[0], src[1], src[2], src[3]); + if (l <= 0 || l >= (int)size) { + errno = ENOSPC; + return (NULL); + } + knot_strlcpy(dst, tmp, size); + return (dst); +} + +/* const char * + * inet_ntop6(src, dst, size) + * convert IPv6 binary address into presentation (printable) format + * author: + * Paul Vixie, 1996. + */ +const char *inet_ntop6(const u_char *src, char *dst, size_t size) +{ + /* + * Note that int32_t and int16_t need only be "at least" large enough + * to contain a value of the specified size. On some systems, like + * Crays, there is no such thing as an integer variable with 16 bits. + * Keep this in mind if you think this function should have been coded + * to use pointer overlays. All the world's not a VAX. + */ + char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"]; + char *tp, *ep; + struct { + int base, len; + } best, cur; + best.base = cur.base =-1; + best.len = cur.len = 0; + u_int words[IN6ADDRSZ / INT16SZ]; + int i; + int advance; + + /* + * Preprocess: + * Copy the input (bytewise) array into a wordwise array. + * Find the longest run of 0x00's in src[] for :: shorthanding. + */ + memset(words, '\0', sizeof words); + for (i = 0; i < IN6ADDRSZ; i++) { + words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); + } + + for (i = 0; i < (IN6ADDRSZ / INT16SZ); i++) { + if (words[i] == 0) { + if (cur.base == -1) { + cur.base = i, cur.len = 1; + } else { + cur.len++; + } + } else { + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) { + best = cur; + } + cur.base = -1; + } + } + } + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) { + best = cur; + } + } + if (best.base != -1 && best.len < 2) { + best.base = -1; + } + + /* + * Format the result. + */ + tp = tmp; + ep = tmp + sizeof(tmp); + for (i = 0; i < (IN6ADDRSZ / INT16SZ) && tp < ep; i++) { + /* Are we inside the best run of 0x00's? */ + if (best.base != -1 && i >= best.base && + i < (best.base + best.len)) { + if (i == best.base) { + if (tp + 1 >= ep) { + return (NULL); + } + *tp++ = ':'; + } + continue; + } + /* Are we following an initial run of 0x00s or any real hex? */ + if (i != 0) { + if (tp + 1 >= ep) { + return (NULL); + } + *tp++ = ':'; + } + /* Is this address an encapsulated IPv4? */ + if (i == 6 && best.base == 0 && + (best.len == 6 || + (best.len == 5 && words[5] == 0xffff))) { + if (!inet_ntop4(src + 12, tp, (size_t)(ep - tp))) { + return (NULL); + } + tp += strlen(tp); + break; + } + advance = snprintf(tp, ep - tp, "%x", words[i]); + if (advance <= 0 || advance >= ep - tp) { + return (NULL); + } + tp += advance; + } + /* Was it a trailing run of 0x00's? */ + if (best.base != -1 && (best.base + best.len) == + (IN6ADDRSZ / INT16SZ)) { + if (tp + 1 >= ep) { + return (NULL); + } + *tp++ = ':'; + } + if (tp + 1 >= ep) { + return (NULL); + } + *tp++ = '\0'; + + /* + * Check for overflow, copy, and we're done. + */ + if ((size_t)(tp - tmp) > size) { + errno = ENOSPC; + return (NULL); + } + knot_strlcpy(dst, tmp, size); + return (dst); +} + + +static int b64rmap_initialized = 0; +static uint8_t b64rmap[256]; + +static const uint8_t b64rmap_special = 0xf0; +static const uint8_t b64rmap_end = 0xfd; +static const uint8_t b64rmap_space = 0xfe; +static const uint8_t b64rmap_invalid = 0xff; + +/** + * Initializing the reverse map is not thread safe. + * Which is fine for NSD. For now... + **/ +void b64_initialize_rmap() +{ + int i; + char ch; + + /* Null: end of string, stop parsing */ + b64rmap[0] = b64rmap_end; + + for (i = 1; i < 256; ++i) { + ch = (char)i; + /* Whitespaces */ + if (isspace(ch)) { + b64rmap[i] = b64rmap_space; + } + /* Padding: stop parsing */ + else if (ch == Pad64) { + b64rmap[i] = b64rmap_end; + } + /* Non-base64 char */ + else { + b64rmap[i] = b64rmap_invalid; + } + } + + /* Fill reverse mapping for base64 chars */ + for (i = 0; Base64[i] != '\0'; ++i) { + b64rmap[(uint8_t)Base64[i]] = i; + } + + b64rmap_initialized = 1; +} + +int b64_pton_do(char const *src, uint8_t *target, size_t targsize) +{ + int tarindex, state, ch; + uint8_t ofs; + + state = 0; + tarindex = 0; + + while (1) { + ch = *src++; + ofs = b64rmap[ch]; + + if (ofs >= b64rmap_special) { + /* Ignore whitespaces */ + if (ofs == b64rmap_space) { + continue; + } + /* End of base64 characters */ + if (ofs == b64rmap_end) { + break; + } + /* A non-base64 character. */ + return (-1); + } + + switch (state) { + case 0: + if ((size_t)tarindex >= targsize) { + return (-1); + } + target[tarindex] = ofs << 2; + state = 1; + break; + case 1: + if ((size_t)tarindex + 1 >= targsize) { + return (-1); + } + target[tarindex] |= ofs >> 4; + target[tarindex+1] = (ofs & 0x0f) + << 4 ; + tarindex++; + state = 2; + break; + case 2: + if ((size_t)tarindex + 1 >= targsize) { + return (-1); + } + target[tarindex] |= ofs >> 2; + target[tarindex+1] = (ofs & 0x03) + << 6; + tarindex++; + state = 3; + break; + case 3: + if ((size_t)tarindex >= targsize) { + return (-1); + } + target[tarindex] |= ofs; + tarindex++; + state = 0; + break; + default: + abort(); + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (b64rmap[ch] != b64rmap_space) { + break; + } + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) { + return (-1); + } + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (b64rmap[ch] != b64rmap_space) { + return (-1); + } + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target[tarindex] != 0) { + return (-1); + } + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) { + return (-1); + } + } + + return (tarindex); +} + + +int b64_pton_len(char const *src) +{ + int tarindex, state, ch; + uint8_t ofs; + + state = 0; + tarindex = 0; + + while (1) { + ch = *src++; + ofs = b64rmap[ch]; + + if (ofs >= b64rmap_special) { + /* Ignore whitespaces */ + if (ofs == b64rmap_space) { + continue; + } + /* End of base64 characters */ + if (ofs == b64rmap_end) { + break; + } + /* A non-base64 character. */ + return (-1); + } + + switch (state) { + case 0: + state = 1; + break; + case 1: + tarindex++; + state = 2; + break; + case 2: + tarindex++; + state = 3; + break; + case 3: + tarindex++; + state = 0; + break; + default: + abort(); + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (b64rmap[ch] != b64rmap_space) { + break; + } + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) { + return (-1); + } + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (b64rmap[ch] != b64rmap_space) { + return (-1); + } + + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) { + return (-1); + } + } + + return (tarindex); +} + +int b64_pton(char const *src, uint8_t *target, size_t targsize) +{ + if (!b64rmap_initialized) { + b64_initialize_rmap(); + } + + if (target) { + return b64_pton_do(src, target, targsize); + } else { + return b64_pton_len(src); + } +} + +void set_bit(uint8_t bits[], size_t index) +{ + /* + * The bits are counted from left to right, so bit #0 is the + * left most bit. + */ + bits[index / 8] |= (1 << (7 - index % 8)); +} + +uint32_t strtoserial(const char *nptr, const char **endptr) +{ + uint32_t i = 0; + uint32_t serial = 0; + + for (*endptr = nptr; **endptr; (*endptr)++) { + switch (**endptr) { + case ' ': + case '\t': + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + i *= 10; + i += (**endptr - '0'); + break; + default: + break; + } + } + serial += i; + return serial; +} + +inline void write_uint32(void *dst, uint32_t data) +{ +#ifdef ALLOW_UNALIGNED_ACCESSES + *(uint32_t *) dst = htonl(data); +#else + uint8_t *p = (uint8_t *) dst; + p[0] = (uint8_t)((data >> 24) & 0xff); + p[1] = (uint8_t)((data >> 16) & 0xff); + p[2] = (uint8_t)((data >> 8) & 0xff); + p[3] = (uint8_t)(data & 0xff); +#endif +} + +uint32_t strtottl(const char *nptr, const char **endptr) +{ + uint32_t i = 0; + uint32_t seconds = 0; + + for (*endptr = nptr; **endptr; (*endptr)++) { + switch (**endptr) { + case ' ': + case '\t': + break; + case 's': + case 'S': + seconds += i; + i = 0; + break; + case 'm': + case 'M': + seconds += i * 60; + i = 0; + break; + case 'h': + case 'H': + seconds += i * 60 * 60; + i = 0; + break; + case 'd': + case 'D': + seconds += i * 60 * 60 * 24; + i = 0; + break; + case 'w': + case 'W': + seconds += i * 60 * 60 * 24 * 7; + i = 0; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + i *= 10; + i += (**endptr - '0'); + break; + default: + seconds += i; + return seconds; + } + } + seconds += i; + return seconds; +} + +/* Number of days per month (except for February in leap years). */ +static const int mdays[] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +static int is_leap_year(int year) +{ + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); +} + +static int leap_days(int y1, int y2) +{ + --y1; + --y2; + return (y2/4 - y1/4) - (y2/100 - y1/100) + (y2/400 - y1/400); +} + +/* + * Code adapted from Python 2.4.1 sources (Lib/calendar.py). + */ +time_t mktime_from_utc(const struct tm *tm) +{ + int year = 1900 + tm->tm_year; + time_t days = 365 * (year - 1970) + leap_days(1970, year); + time_t hours; + time_t minutes; + time_t seconds; + int i; + + for (i = 0; i < tm->tm_mon; ++i) { + days += mdays[i]; + } + if (tm->tm_mon > 1 && is_leap_year(year)) { + ++days; + } + days += tm->tm_mday - 1; + + hours = days * 24 + tm->tm_hour; + minutes = hours * 60 + tm->tm_min; + seconds = minutes * 60 + tm->tm_sec; + + return seconds; +} + +/*!< Following functions are conversions from text to wire. */ + +//#define DEBUG_UNKNOWN_RDATA + +#ifdef DEBUG_UNKNOWN_RDATA +#define dbg_rdata(msg...) fprintf(stderr, msg) +#define DBG_RDATA(cmds) do { cmds } while (0) +#else +#define dbg_rdata(msg...) +#define DBG_RDATA(cmds) +#endif + + + +#define IP6ADDRLEN (128/8) +#define NS_INT16SZ 2 +#define NS_INADDRSZ 4 +#define NS_IN6ADDRSZ 16 +#define APL_NEGATION_MASK 0x80U +#define APL_LENGTH_MASK (~APL_NEGATION_MASK) + +//#define ZP_DEBUG + +#ifdef ZP_DEBUG +#define dbg_zp(msg...) fprintf(stderr, msg) +#else +#define dbg_zp(msg...) +#endif + + +/*! + * \brief Return data of raw data item. + * + * \param item Item. + * \return uint16_t * Raw data. + */ +static inline uint16_t * rdata_atom_data(knot_rdata_item_t item) +{ + return (uint16_t *)(item.raw_data + 1); +} + +/*! + * \brief Return type of RRSet covered by given RRSIG. + * + * \param rrset RRSIG. + * \return uint16_t Type covered. + */ +uint16_t rrsig_type_covered(knot_rrset_t *rrset) +{ + assert(rrset->rdata->items[0].raw_data[0] == sizeof(uint16_t)); + + return ntohs(*(uint16_t *) rdata_atom_data(rrset->rdata->items[0])); +} + +/*! + * \brief Checks if item contains domain. + * + * \param type Type of RRSet. + * \param index Index to check. + * + * \return > 1 if item is domain, 0 otherwise. + */ +static inline int rdata_atom_is_domain(uint16_t type, size_t index) +{ + const knot_rrtype_descriptor_t *descriptor + = knot_rrtype_descriptor_by_type(type); + return (index < descriptor->length + && (descriptor->wireformat[index] == + KNOT_RDATA_WF_COMPRESSED_DNAME || + descriptor->wireformat[index] == + KNOT_RDATA_WF_UNCOMPRESSED_DNAME)); +} + +/*! + * \brief Returns which wireformat type is on given index. + * + * \param type Type of RRSet. + * \param index Index. + * + * \return uint8_t Wireformat type. + */ +static inline uint8_t rdata_atom_wireformat_type(uint16_t type, size_t index) +{ + const knot_rrtype_descriptor_t *descriptor = + knot_rrtype_descriptor_by_type(type); + assert(index < descriptor->length); + return descriptor->wireformat[index]; +} + +/*! + * \brief Converts rdata wireformat to rdata items. + * + * \param wireformat Wireformat/. + * \param rrtype RR type. + * \param data_size Size of wireformat. + * \param items created rdata items. + * + * \return Number of items converted. + */ +static ssize_t rdata_wireformat_to_rdata_atoms(const uint16_t *wireformat, + uint16_t rrtype, + const uint16_t data_size, + knot_rdata_item_t **items) +{ + dbg_rdata("read length: %d\n", data_size); + uint16_t const *end = (uint16_t *)((uint8_t *)wireformat + (data_size)); + dbg_rdata("set end pointer: %p which means length: %d\n", end, + (uint8_t *)end - (uint8_t *)wireformat); + size_t i; + knot_rdata_item_t *temp_rdatas = + malloc(sizeof(*temp_rdatas) * MAXRDATALEN); + if (temp_rdatas == NULL) { + ERR_ALLOC_FAILED; + return KNOTDZCOMPILE_ENOMEM; + } + memset(temp_rdatas, 0, sizeof(*temp_rdatas) * MAXRDATALEN); + + knot_rrtype_descriptor_t *descriptor = + knot_rrtype_descriptor_by_type(rrtype); + + assert(descriptor->length <= MAXRDATALEN); + + dbg_rdata("will be parsing %d items, total size: %d\n", + descriptor->length, data_size); + + for (i = 0; i < descriptor->length; ++i) { + int is_domain = 0; + int is_wirestore = 0; + size_t length = 0; + length = 0; + int required = descriptor->length; + + switch (rdata_atom_wireformat_type(rrtype, i)) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + is_domain = 1; + break; + case KNOT_RDATA_WF_LITERAL_DNAME: + is_domain = 1; + is_wirestore = 1; + break; + case KNOT_RDATA_WF_BYTE: + length = sizeof(uint8_t); + break; + case KNOT_RDATA_WF_SHORT: + length = sizeof(uint16_t); + break; + case KNOT_RDATA_WF_LONG: + length = sizeof(uint32_t); + break; + case KNOT_RDATA_WF_TEXT: + case KNOT_RDATA_WF_BINARYWITHLENGTH: + /* Length is stored in the first byte. */ + length = 1; + if ((uint8_t *)wireformat + length <= (uint8_t *)end) { + // length += wireformat[length - 1]; + length += *((uint8_t *)wireformat); + dbg_rdata("%d: set new length: %d\n", i, + length); + } + /*if (buffer_position(packet) + length <= end) { + length += buffer_current(packet)[length - 1]; + }*/ + break; + case KNOT_RDATA_WF_A: + length = sizeof(in_addr_t); + break; + case KNOT_RDATA_WF_AAAA: + length = IP6ADDRLEN; + break; + case KNOT_RDATA_WF_BINARY: + /* Remaining RDATA is binary. */ + dbg_rdata("%d: guessing length from pointers: %p %p\n", + i, + wireformat, end); + length = (uint8_t *)end - (uint8_t *)wireformat; +// length = end - buffer_position(packet); + break; + case KNOT_RDATA_WF_APL: + length = (sizeof(uint16_t) /* address family */ + + sizeof(uint8_t) /* prefix */ + + sizeof(uint8_t)); /* length */ + if ((uint8_t *)wireformat + length <= (uint8_t *)end) { + /* Mask out negation bit. */ + length += (wireformat[length - 1] + & APL_LENGTH_MASK); + } + break; + case KNOT_RDATA_WF_IPSECGATEWAY: + switch (rdata_atom_data(temp_rdatas[1])[0]) { + /* gateway type */ + default: + case IPSECKEY_NOGATEWAY: + length = 0; + break; + case IPSECKEY_IP4: + length = 4; + break; + case IPSECKEY_IP6: + length = IP6ADDRLEN; + break; + case IPSECKEY_DNAME: + is_domain = 1; + is_wirestore = 1; + break; + } + break; + } + + if (is_domain) { + knot_dname_t *dname; + + if (!required && (wireformat == end)) { + break; + } + + dname = knot_dname_new_from_str((char *)wireformat, + length, + NULL); + + if (dname == NULL) { + dbg_rdata("malformed dname!\n"); + /*! \todo rdata purge */ + free(temp_rdatas); + return KNOTDZCOMPILE_EBRDATA; + } + dbg_rdata("%d: created dname: %s\n", i, + knot_dname_to_str(dname)); + + if (is_wirestore) { + /*temp_rdatas[i].raw_data = + (uint16_t *) region_alloc( + region, sizeof(uint16_t) + dname->name_size); + temp_rdatas[i].data[0] = dname->name_size; + memcpy(temp_rdatas[i].data+1, dname_name(dname), + dname->name_size); */ + temp_rdatas[i].raw_data = + malloc(sizeof(uint16_t) + + sizeof(uint8_t) * dname->size); + if (temp_rdatas[i].raw_data == NULL) { + ERR_ALLOC_FAILED; + /*! \todo rdata purge */ + free(temp_rdatas); + return KNOTDZCOMPILE_ENOMEM; + } + + temp_rdatas[i].raw_data[0] = dname->size; + memcpy(temp_rdatas[i].raw_data + 1, + dname->name, dname->size); + + knot_dname_release(dname); + } else { + temp_rdatas[i].dname = dname; + } + + } else { + dbg_rdata("%d :length: %d %d %p %p\n", i, length, + end - wireformat, + wireformat, end); + if ((uint8_t *)wireformat + length > (uint8_t *)end) { + if (required) { + /* Truncated RDATA. */ + /*! \todo rdata purge */ + free(temp_rdatas); + dbg_rdata("truncated rdata\n"); + return KNOTDZCOMPILE_EBRDATA; + } else { + break; + } + } + + assert(wireformat <= end); /*!< \todo remove! */ + dbg_rdata("calling init with: %p and length : %d\n", + wireformat, length); + temp_rdatas[i].raw_data = alloc_rdata_init(wireformat, + length); + if (temp_rdatas[i].raw_data == NULL) { + ERR_ALLOC_FAILED; + /*! \todo rdata purge */ + free(temp_rdatas); + return -1; + } + +// temp_rdatas[i].raw_data[0] = length; +// memcpy(temp_rdatas[i].raw_data + 1, wireformat, length); + +/* temp_rdatas[i].data = (uint16_t *) region_alloc( + region, sizeof(uint16_t) + length); + temp_rdatas[i].data[0] = length; + buffer_read(packet, + temp_rdatas[i].data + 1, length); */ + } + dbg_rdata("%d: adding length: %d (remaining: %d)\n", i, length, + (uint8_t *)end - ((uint8_t *)wireformat + length)); +// hex_print(temp_rdatas[i].raw_data + 1, length); + wireformat = (uint16_t *)((uint8_t *)wireformat + length); +// wireformat = wireformat + length; + dbg_rdata("wire: %p\n", wireformat); + dbg_rdata("remaining now: %d\n", + end - wireformat); + + } + + dbg_rdata("%p %p\n", wireformat, (uint8_t *)wireformat); + + if (wireformat < end) { + /* Trailing garbage. */ + dbg_rdata("w: %p e: %p %d\n", wireformat, end, end - wireformat); +// region_destroy(temp_region); + free(temp_rdatas); + return KNOTDZCOMPILE_EBRDATA; + } + + *items = temp_rdatas; + /* *rdatas = (rdata_atom_type *) region_alloc_init( + region, temp_rdatas, i * sizeof(rdata_atom_type)); */ + return (ssize_t)i; +} + +/* Taken from RFC 2535, section 7. */ +knot_lookup_table_t dns_algorithms[] = { + { 1, "RSAMD5" }, /* RFC 2537 */ + { 2, "DH" }, /* RFC 2539 */ + { 3, "DSA" }, /* RFC 2536 */ + { 4, "ECC" }, + { 5, "RSASHA1" }, /* RFC 3110 */ + { 252, "INDIRECT" }, + { 253, "PRIVATEDNS" }, + { 254, "PRIVATEOID" }, + { 0, NULL } +}; + +/* Taken from RFC 4398, section 2.1. */ +knot_lookup_table_t dns_certificate_types[] = { + /* 0 Reserved */ + { 1, "PKIX" }, /* X.509 as per PKIX */ + { 2, "SPKI" }, /* SPKI cert */ + { 3, "PGP" }, /* OpenPGP packet */ + { 4, "IPKIX" }, /* The URL of an X.509 data object */ + { 5, "ISPKI" }, /* The URL of an SPKI certificate */ + { 6, "IPGP" }, /* The fingerprint and URL of an OpenPGP packet */ + { 7, "ACPKIX" }, /* Attribute Certificate */ + { 8, "IACPKIX" }, /* The URL of an Attribute Certificate */ + { 253, "URI" }, /* URI private */ + { 254, "OID" }, /* OID private */ + /* 255 Reserved */ + /* 256-65279 Available for IANA assignment */ + /* 65280-65534 Experimental */ + /* 65535 Reserved */ + { 0, NULL } +}; + +/* Imported from lexer. */ +extern int hexdigit_to_int(char ch); + +extern uint8_t nsecbits[NSEC_WINDOW_COUNT][NSEC_WINDOW_BITS_SIZE]; +extern uint16_t nsec_highest_rcode; + +/*! + * \brief Allocate SIZE+sizeof(uint16_t) bytes and store SIZE in the first + * element. Return a pointer to the allocation. + * + * \param size How many bytes to allocate. + */ +static uint16_t * alloc_rdata(size_t size) +{ + uint16_t *result = malloc(sizeof(uint16_t) + size); + *result = size; + return result; +} + +uint16_t *alloc_rdata_init(const void *data, size_t size) +{ + uint16_t *result = malloc(sizeof(uint16_t) + size); + if (result == NULL) { + return NULL; + } + *result = size; + memcpy(result + 1, data, size); + return result; +} + +/* + * These are parser function for generic zone file stuff. + */ +uint16_t * zparser_conv_hex(const char *hex, size_t len) +{ + /* convert a hex value to wireformat */ + uint16_t *r = NULL; + uint8_t *t; + int i; + + if (len % 2 != 0) { + zc_error_prev_line("number of hex digits " + "must be a multiple of 2"); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else if (len > MAX_RDLENGTH * 2) { + zc_error_prev_line("hex data exceeds maximum rdata length (%d)", + MAX_RDLENGTH); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else { + /* the length part */ + + r = alloc_rdata(len / 2); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + t = (uint8_t *)(r + 1); + + /* Now process octet by octet... */ + while (*hex) { + *t = 0; + for (i = 16; i >= 1; i -= 15) { + if (isxdigit((int)*hex)) { + *t += hexdigit_to_int(*hex) * i; + } else { + zc_error_prev_line( + "illegal hex character '%c'", + (int) *hex); + parser->error_occurred = + KNOTDZCOMPILE_EBRDATA; + free(r); + return NULL; + } + ++hex; + } + ++t; + } + } + + return r; +} + +/* convert hex, precede by a 1-byte length */ +uint16_t * zparser_conv_hex_length(const char *hex, size_t len) +{ + uint16_t *r = NULL; + uint8_t *t; + int i; + if (len % 2 != 0) { + zc_error_prev_line("number of hex digits must be a " + "multiple of 2"); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else if (len > 255 * 2) { + zc_error_prev_line("hex data exceeds 255 bytes"); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else { + uint8_t *l; + + /* the length part */ + r = alloc_rdata(len / 2 + 1); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + + t = (uint8_t *)(r + 1); + + l = t++; + *l = '\0'; + + /* Now process octet by octet... */ + while (*hex) { + *t = 0; + for (i = 16; i >= 1; i -= 15) { + if (isxdigit((int)*hex)) { + *t += hexdigit_to_int(*hex) * i; + } else { + zc_error_prev_line( + "illegal hex character '%c'", + (int) *hex); + parser->error_occurred = + KNOTDZCOMPILE_EBRDATA; + free(r); + return NULL; + } + ++hex; + } + ++t; + ++*l; + } + } + return r; +} + +uint16_t * zparser_conv_time(const char *time) +{ + /* convert a time YYHM to wireformat */ + uint16_t *r = NULL; + struct tm tm; + + /* Try to scan the time... */ + if (!strptime(time, "%Y%m%d%H%M%S", &tm)) { + zc_error_prev_line("date and time is expected"); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else { + uint32_t l = htonl(mktime_from_utc(&tm)); + r = alloc_rdata_init(&l, sizeof(l)); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + } + return r; +} + +uint16_t * zparser_conv_services(const char *protostr, char *servicestr) +{ + /* + * Convert a protocol and a list of service port numbers + * (separated by spaces) in the rdata to wireformat + */ + uint16_t *r = NULL; + uint8_t *p; + uint8_t bitmap[65536/8]; + char sep[] = " "; + char *word; + int max_port = -8; + /* convert a protocol in the rdata to wireformat */ + struct protoent *proto; + + memset(bitmap, 0, sizeof(bitmap)); + + proto = getprotobyname(protostr); + if (!proto) { + proto = getprotobynumber(atoi(protostr)); + } + if (!proto) { + zc_error_prev_line("unknown protocol '%s'", protostr); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + return NULL; + } + + char *sp = 0; + while ((word = strtok_r(servicestr, sep, &sp))) { + struct servent *service; + int port; + + service = getservbyname(word, proto->p_name); + if (service) { + /* Note: ntohs not ntohl! Strange but true. */ + port = ntohs((uint16_t) service->s_port); + } else { + char *end; + port = strtol(word, &end, 10); + if (*end != '\0') { + zc_error_prev_line( + "unknown service '%s' for" + " protocol '%s'", + word, protostr); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + continue; + } + } + + if (port < 0 || port > 65535) { + zc_error_prev_line("bad port number %d", port); + } else { + set_bit(bitmap, port); + if (port > max_port) { + max_port = port; + } + } + } + + r = alloc_rdata(sizeof(uint8_t) + max_port / 8 + 1); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + + p = (uint8_t *)(r + 1); + *p = proto->p_proto; + memcpy(p + 1, bitmap, *r); + + return r; +} + +uint16_t * zparser_conv_serial(const char *serialstr) +{ + uint16_t *r = NULL; + uint32_t serial; + const char *t; + + serial = strtoserial(serialstr, &t); + if (*t != '\0') { + zc_error_prev_line("serial is expected"); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else { + serial = htonl(serial); + r = alloc_rdata_init(&serial, sizeof(serial)); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + } + return r; +} + +uint16_t * zparser_conv_period(const char *periodstr) +{ + /* convert a time period (think TTL's) to wireformat) */ + uint16_t *r = NULL; + uint32_t period; + const char *end; + + /* Allocate required space... */ + period = strtottl(periodstr, &end); + if (*end != '\0') { + zc_error_prev_line("time period is expected"); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else { + period = htonl(period); + r = alloc_rdata_init(&period, sizeof(period)); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + } + return r; +} + +uint16_t * zparser_conv_short(const char *text) +{ + uint16_t *r = NULL; + uint16_t value; + char *end; + + value = htons((uint16_t) strtol(text, &end, 10)); + if (*end != '\0') { + zc_error_prev_line("integer value is expected"); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else { + r = alloc_rdata_init(&value, sizeof(value)); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + } + return r; +} + +uint16_t * zparser_conv_byte(const char *text) +{ + uint16_t *r = NULL; + uint8_t value; + char *end; + + value = (uint8_t) strtol(text, &end, 10); + if (*end != '\0') { + zc_error_prev_line("integer value is expected"); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else { + r = alloc_rdata_init(&value, sizeof(value)); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + } + return r; +} + +uint16_t * zparser_conv_algorithm(const char *text) +{ + const knot_lookup_table_t *alg; + uint8_t id; + + alg = knot_lookup_by_name(dns_algorithms, text); + if (alg) { + id = (uint8_t) alg->id; + } else { + char *end; + id = (uint8_t) strtol(text, &end, 10); + if (*end != '\0') { + zc_error_prev_line("algorithm is expected"); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + return NULL; + } + } + + uint16_t *r = alloc_rdata_init(&id, sizeof(id)); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + + return r; +} + +uint16_t * zparser_conv_certificate_type(const char *text) +{ + /* convert a algoritm string to integer */ + const knot_lookup_table_t *type; + uint16_t id; + + type = knot_lookup_by_name(dns_certificate_types, text); + if (type) { + id = htons((uint16_t) type->id); + } else { + char *end; + id = htons((uint16_t) strtol(text, &end, 10)); + if (*end != '\0') { + zc_error_prev_line("certificate type is expected"); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + return NULL; + } + } + + uint16_t *r = alloc_rdata_init(&id, sizeof(id)); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + + return r; +} + +uint16_t * zparser_conv_a(const char *text) +{ + in_addr_t address; + uint16_t *r = NULL; + + if (inet_pton(AF_INET, text, &address) != 1) { + zc_error_prev_line("invalid IPv4 address '%s'", text); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else { + r = alloc_rdata_init(&address, sizeof(address)); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + } + + return r; +} + +uint16_t * zparser_conv_aaaa(const char *text) +{ + uint8_t address[IP6ADDRLEN]; + uint16_t *r = NULL; + + if (inet_pton(AF_INET6, text, address) != 1) { + zc_error_prev_line("invalid IPv6 address '%s'", text); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else { + r = alloc_rdata_init(address, sizeof(address)); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + } + return r; +} + +uint16_t * zparser_conv_text(const char *text, size_t len) +{ + uint16_t *r = NULL; + + dbg_zp("Converting text: %s\n", text); + + if (len > 255) { + zc_error_prev_line("text string is longer than 255 characters," + " try splitting it into multiple parts"); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else { + uint8_t *p; + r = alloc_rdata(len + 1); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + p = (uint8_t *)(r + 1); + *p = len; + memcpy(p + 1, text, len); + } + return r; +} + +uint16_t * zparser_conv_dns_name(const uint8_t *name, size_t len) +{ + uint16_t *r = NULL; + uint8_t *p = NULL; + r = alloc_rdata(len); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + p = (uint8_t *)(r + 1); + memcpy(p, name, len); + + return r; +} + +uint16_t * zparser_conv_b32(const char *b32) +{ + uint8_t buffer[B64BUFSIZE]; + uint16_t *r = NULL; + size_t i = B64BUFSIZE - 1; + + if (strcmp(b32, "-") == 0) { + r = alloc_rdata_init("", 1); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + return r; + } + + /*!< \todo BLEEDING EYES! */ + + char b32_copy[strlen(b32) + 1]; + + for (int i = 0; i < strlen(b32); i++) { + b32_copy[i] = toupper(b32[i]); + } + + /*!< \todo BLEEDING EYES! */ + b32_copy[strlen(b32)] = '\0'; + + if (!base32hex_decode(b32_copy, + strlen(b32_copy), (char *)buffer + 1, &i)) { + zc_error_prev_line("invalid base32 data"); + parser->error_occurred = 1; + } else { + buffer[0] = i; /* store length byte */ + r = alloc_rdata_init(buffer, i + 1); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + } + return r; +} + +uint16_t * zparser_conv_b64(const char *b64) +{ + uint8_t buffer[B64BUFSIZE]; + uint16_t *r = NULL; + int i; + + i = b64_pton(b64, buffer, B64BUFSIZE); + if (i == -1) { + zc_error_prev_line("invalid base64 data\n"); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else { + r = alloc_rdata_init(buffer, i); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + } + return r; +} + +uint16_t * zparser_conv_rrtype(const char *text) +{ + uint16_t *r = NULL; + uint16_t type = knot_rrtype_from_string(text); + + if (type == 0) { + zc_error_prev_line("unrecognized RR type '%s'", text); + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + } else { + type = htons(type); + r = alloc_rdata_init(&type, sizeof(type)); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + } + return r; +} + +uint16_t * zparser_conv_nxt(uint8_t nxtbits[]) +{ + /* nxtbits[] consists of 16 bytes with some zero's in it + * copy every byte with zero to r and write the length in + * the first byte + */ + uint16_t i; + uint16_t last = 0; + + for (i = 0; i < 16; i++) { + if (nxtbits[i] != 0) { + last = i + 1; + } + } + + uint16_t *r = alloc_rdata_init(nxtbits, last); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + return NULL; + } + + return r; +} + + +/* we potentially have 256 windows, each one is numbered. empty ones + * should be discarded + */ +uint16_t * zparser_conv_nsec(uint8_t nsecbits[NSEC_WINDOW_COUNT] + [NSEC_WINDOW_BITS_SIZE]) +{ + /* nsecbits contains up to 64K of bits which represent the + * types available for a name. Walk the bits according to + * nsec++ draft from jakob + */ + uint16_t *r; + uint8_t *ptr; + size_t i, j; + uint16_t window_count = 0; + uint16_t total_size = 0; + uint16_t window_max = 0; + + /* The used windows. */ + int used[NSEC_WINDOW_COUNT]; + /* The last byte used in each the window. */ + int size[NSEC_WINDOW_COUNT]; + + window_max = 1 + (nsec_highest_rcode / 256); + + /* used[i] is the i-th window included in the nsec + * size[used[0]] is the size of window 0 + */ + + /* walk through the 256 windows */ + for (i = 0; i < window_max; ++i) { + int empty_window = 1; + /* check each of the 32 bytes */ + for (j = 0; j < NSEC_WINDOW_BITS_SIZE; ++j) { + if (nsecbits[i][j] != 0) { + size[i] = j + 1; + empty_window = 0; + } + } + if (!empty_window) { + used[window_count] = i; + window_count++; + } + } + + for (i = 0; i < window_count; ++i) { + total_size += sizeof(uint16_t) + size[used[i]]; + } + + r = alloc_rdata(total_size); + if (r == NULL) { + ERR_ALLOC_FAILED; + parser->error_occurred = KNOTDZCOMPILE_EBRDATA; + return NULL; + } + ptr = (uint8_t *)(r + 1); + + /* now walk used and copy it */ + for (i = 0; i < window_count; ++i) { + ptr[0] = used[i]; + ptr[1] = size[used[i]]; + memcpy(ptr + 2, &nsecbits[used[i]], size[used[i]]); + ptr += size[used[i]] + 2; + } + + return r; +} + +/* Parse an int terminated in the specified range. */ +static int parse_int(const char *str, + char **end, + int *result, + const char *name, + int min, + int max) +{ + long value; + value = strtol(str, end, 10); + if (value < min || value > max) { + zc_error_prev_line("%s must be within the range [%d .. %d]", + name, + min, + max); + return 0; + } else { + *result = (int) value; + return 1; + } +} + +/* RFC1876 conversion routines */ +static uint32_t poweroften[10] = {1, 10, 100, 1000, 10000, 100000, + 1000000, 10000000, 100000000, 1000000000 + }; + +/* + * Converts ascii size/precision X * 10**Y(cm) to 0xXY. + * Sets the given pointer to the last used character. + * + */ +static uint8_t precsize_aton(char *cp, char **endptr) +{ + unsigned int mval = 0, cmval = 0; + uint8_t retval = 0; + int exponent; + int mantissa; + + while (isdigit((int)*cp)) { + mval = mval * 10 + hexdigit_to_int(*cp++); + } + + if (*cp == '.') { /* centimeters */ + cp++; + if (isdigit((int)*cp)) { + cmval = hexdigit_to_int(*cp++) * 10; + if (isdigit((int)*cp)) { + cmval += hexdigit_to_int(*cp++); + } + } + } + + if (mval >= poweroften[7]) { + /* integer overflow possible for *100 */ + mantissa = mval / poweroften[7]; + exponent = 9; /* max */ + } else { + cmval = (mval * 100) + cmval; + + for (exponent = 0; exponent < 9; exponent++) + if (cmval < poweroften[exponent+1]) { + break; + } + + mantissa = cmval / poweroften[exponent]; + } + if (mantissa > 9) { + mantissa = 9; + } + + retval = (mantissa << 4) | exponent; + + if (*cp == 'm') { + cp++; + } + + *endptr = cp; + + return (retval); +} + +/* + * Parses a specific part of rdata. + * + * Returns: + * + * number of elements parsed + * zero on error + * + */ +uint16_t * zparser_conv_loc(char *str) +{ + uint16_t *r; + uint32_t *p; + int i; + int deg, min, secs; /* Secs is stored times 1000. */ + uint32_t lat = 0, lon = 0, alt = 0; + /* encoded defaults: version=0 sz=1m hp=10000m vp=10m */ + uint8_t vszhpvp[4] = {0, 0x12, 0x16, 0x13}; + char *start; + double d; + + for (;;) { + deg = min = secs = 0; + + /* Degrees */ + if (*str == '\0') { + zc_error_prev_line("unexpected end of LOC data"); + return NULL; + } + + if (!parse_int(str, &str, °, "degrees", 0, 180)) { + return NULL; + } + if (!isspace((int)*str)) { + zc_error_prev_line("space expected after degrees"); + return NULL; + } + ++str; + + /* Minutes? */ + if (isdigit((int)*str)) { + if (!parse_int(str, &str, &min, "minutes", 0, 60)) { + return NULL; + } + if (!isspace((int)*str)) { + zc_error_prev_line("space expected after minutes"); + return NULL; + } + ++str; + } + + /* Seconds? */ + if (isdigit((int)*str)) { + start = str; + if (!parse_int(str, &str, &i, "seconds", 0, 60)) { + return NULL; + } + + if (*str == '.' && !parse_int(str + 1, &str, &i, + "seconds fraction", + 0, 999)) { + return NULL; + } + + if (!isspace((int)*str)) { + zc_error_prev_line("space expected after seconds"); + return NULL; + } + + if (sscanf(start, "%lf", &d) != 1) { + zc_error_prev_line("error parsing seconds"); + } + + if (d < 0.0 || d > 60.0) { + zc_error_prev_line( + "seconds not in range 0.0 .. 60.0"); + } + + secs = (int)(d * 1000.0 + 0.5); + ++str; + } + + switch (*str) { + case 'N': + case 'n': + lat = ((uint32_t)1 << 31) + + (deg * 3600000 + min * 60000 + secs); + break; + case 'E': + case 'e': + lon = ((uint32_t)1 << 31) + + (deg * 3600000 + min * 60000 + secs); + break; + case 'S': + case 's': + lat = ((uint32_t)1 << 31) - + (deg * 3600000 + min * 60000 + secs); + break; + case 'W': + case 'w': + lon = ((uint32_t)1 << 31) - + (deg * 3600000 + min * 60000 + secs); + break; + default: + zc_error_prev_line( + "invalid latitude/longtitude: '%c'", *str); + return NULL; + } + ++str; + + if (lat != 0 && lon != 0) { + break; + } + + if (!isspace((int)*str)) { + zc_error_prev_line("space expected after" + " latitude/longitude"); + return NULL; + } + ++str; + } + + /* Altitude */ + if (*str == '\0') { + zc_error_prev_line("unexpected end of LOC data"); + return NULL; + } + + if (!isspace((int)*str)) { + zc_error_prev_line("space expected before altitude"); + return NULL; + } + ++str; + + start = str; + + /* Sign */ + if (*str == '+' || *str == '-') { + ++str; + } + + /* Meters of altitude... */ + int ret = strtol(str, &str, 10); + UNUSED(ret); // Result checked in following switch + + switch (*str) { + case ' ': + case '\0': + case 'm': + break; + case '.': + if (!parse_int(str + 1, &str, &i, "altitude fraction", 0, 99)) { + return NULL; + } + if (!isspace((int)*str) && *str != '\0' && *str != 'm') { + zc_error_prev_line("altitude fraction must be a number"); + return NULL; + } + break; + default: + zc_error_prev_line("altitude must be expressed in meters"); + return NULL; + } + if (!isspace((int)*str) && *str != '\0') { + ++str; + } + + if (sscanf(start, "%lf", &d) != 1) { + zc_error_prev_line("error parsing altitude"); + } + + alt = (uint32_t)(10000000.0 + d * 100 + 0.5); + + if (!isspace((int)*str) && *str != '\0') { + zc_error_prev_line("unexpected character after altitude"); + return NULL; + } + + /* Now parse size, horizontal precision and vertical precision if any */ + for (i = 1; isspace((int)*str) && i <= 3; i++) { + vszhpvp[i] = precsize_aton(str + 1, &str); + + if (!isspace((int)*str) && *str != '\0') { + zc_error_prev_line("invalid size or precision"); + return NULL; + } + } + + /* Allocate required space... */ + r = alloc_rdata(16); + if (r == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + p = (uint32_t *)(r + 1); + + memmove(p, vszhpvp, 4); + write_uint32(p + 1, lat); + write_uint32(p + 2, lon); + write_uint32(p + 3, alt); + + return r; +} + +/* + * Convert an APL RR RDATA element. + */ +uint16_t * zparser_conv_apl_rdata(char *str) +{ + int negated = 0; + uint16_t address_family; + uint8_t prefix; + uint8_t maximum_prefix; + uint8_t length; + uint8_t address[IP6ADDRLEN]; + char *colon = strchr(str, ':'); + char *slash = strchr(str, '/'); + int af; + int rc; + uint16_t rdlength; + uint16_t *r; + uint8_t *t; + char *end; + long p; + + if (!colon) { + zc_error_prev_line("address family separator is missing"); + return NULL; + } + if (!slash) { + zc_error_prev_line("prefix separator is missing"); + return NULL; + } + + *colon = '\0'; + *slash = '\0'; + + if (*str == '!') { + negated = 1; + ++str; + } + + if (strcmp(str, "1") == 0) { + address_family = htons(1); + af = AF_INET; + length = sizeof(in_addr_t); + maximum_prefix = length * 8; + } else if (strcmp(str, "2") == 0) { + address_family = htons(2); + af = AF_INET6; + length = IP6ADDRLEN; + maximum_prefix = length * 8; + } else { + zc_error_prev_line("invalid address family '%s'", str); + return NULL; + } + + rc = inet_pton(af, colon + 1, address); + if (rc == 0) { + zc_error_prev_line("invalid address '%s'", colon + 1); + return NULL; + } else if (rc == -1) { + char ebuf[256]; + zc_error_prev_line("inet_pton failed: %s", + strerror_r(errno, ebuf, sizeof(ebuf))); + return NULL; + } + + /* Strip trailing zero octets. */ + while (length > 0 && address[length - 1] == 0) { + --length; + } + + + p = strtol(slash + 1, &end, 10); + if (p < 0 || p > maximum_prefix) { + zc_error_prev_line("prefix not in the range 0 .. %d", + maximum_prefix); + return NULL; + } else if (*end != '\0') { + zc_error_prev_line("invalid prefix '%s'", slash + 1); + return NULL; + } + prefix = (uint8_t) p; + + rdlength = (sizeof(address_family) + sizeof(prefix) + sizeof(length) + + length); + r = alloc_rdata(rdlength); + if (r == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + t = (uint8_t *)(r + 1); + + memcpy(t, &address_family, sizeof(address_family)); + t += sizeof(address_family); + memcpy(t, &prefix, sizeof(prefix)); + t += sizeof(prefix); + memcpy(t, &length, sizeof(length)); + if (negated) { + *t |= APL_NEGATION_MASK; + } + t += sizeof(length); + memcpy(t, address, length); + + return r; +} + +/* + * Below some function that also convert but not to wireformat + * but to "normal" (int,long,char) types + */ + +uint32_t zparser_ttl2int(const char *ttlstr, int *error) +{ + /* convert a ttl value to a integer + * return the ttl in a int + * -1 on error + */ + + uint32_t ttl; + const char *t; + + ttl = strtottl(ttlstr, &t); + if (*t != 0) { + zc_error_prev_line("invalid TTL value: %s", ttlstr); + *error = 1; + } + + return ttl; +} + +void zadd_rdata_wireformat(uint16_t *data) +{ + parser->temporary_items[parser->rdata_count].raw_data = data; + parser->rdata_count++; +} + +/** + * Used for TXT RR's to grow with undefined number of strings. + */ +void zadd_rdata_txt_wireformat(uint16_t *data, int first) +{ + dbg_zp("Adding text!\n"); +// hex_print(data + 1, data[0]); + knot_rdata_item_t *rd; + + /* First STR in str_seq, allocate 65K in first unused rdata + * else find last used rdata */ + if (first) { + rd = &parser->temporary_items[parser->rdata_count]; +// if ((rd->data = (uint8_t *) region_alloc(parser->rr_region, +// sizeof(uint8_t) + 65535 * sizeof(uint8_t))) == NULL) { +// zc_error_prev_line("Could not allocate memory for TXT RR"); +// return; +// } + rd->raw_data = alloc_rdata(65535 * sizeof(uint8_t)); + if (rd->raw_data == NULL) { + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + } + parser->rdata_count++; + rd->raw_data[0] = 0; + } else { +// assert(0); + rd = &parser->temporary_items[parser->rdata_count-1]; + } + + if ((size_t)rd->raw_data[0] + (size_t)data[0] > 65535) { + zc_error_prev_line("too large rdata element"); + return; + } + + memcpy((uint8_t *)rd->raw_data + 2 + rd->raw_data[0], + data + 1, data[0]); + rd->raw_data[0] += data[0]; + free(data); + dbg_zp("Item after add\n"); +// hex_print(rd->raw_data + 1, rd->raw_data[0]); +} + +void zadd_rdata_domain(knot_dname_t *dname) +{ + knot_dname_retain(dname); +// printf("Adding rdata name: %s %p\n", dname->name, dname); + parser->temporary_items[parser->rdata_count].dname = dname; + parser->rdata_count++; +} + +void parse_unknown_rdata(uint16_t type, uint16_t *wireformat) +{ + dbg_rdata("parsing unknown rdata for type: %d\n", type); +// buffer_type packet; + uint16_t size; + ssize_t rdata_count; + ssize_t i; + knot_rdata_item_t *items = NULL; + + if (wireformat) { + size = *wireformat; + } else { + return; + } + +// buffer_create_from(&packet, wireformat + 1, *wireformat); + rdata_count = rdata_wireformat_to_rdata_atoms(wireformat + 1, type, + size, &items); +// dbg_rdata("got %d items\n", rdata_count); + dbg_rdata("wf to items returned error: %s (%d)\n", + error_to_str(knot_zcompile_error_msgs, rdata_count), + rdata_count); + if (rdata_count < 0) { + zc_error_prev_line("bad unknown RDATA\n"); + /*!< \todo leaks */ + return; + } + + for (i = 0; i < rdata_count; ++i) { + if (rdata_atom_is_domain(type, i)) { + zadd_rdata_domain(items[i].dname); + } else { + //XXX won't this create size two times? + zadd_rdata_wireformat((uint16_t *)items[i].raw_data); + } + } + free(items); + /* Free wireformat */ + free(wireformat); +} + +void set_bitnsec(uint8_t bits[NSEC_WINDOW_COUNT][NSEC_WINDOW_BITS_SIZE], + uint16_t index) +{ + /* + * The bits are counted from left to right, so bit #0 is the + * left most bit. + */ + uint8_t window = index / 256; + uint8_t bit = index % 256; + + bits[window][bit / 8] |= (1 << (7 - bit % 8)); +} + diff --git a/src/zcompile/parser-util.h b/src/zcompile/parser-util.h new file mode 100644 index 0000000..57258dc --- /dev/null +++ b/src/zcompile/parser-util.h @@ -0,0 +1,357 @@ +/*! + * \file parser-util.h + * + * \author NLnet Labs + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * Minor modifications by CZ.NIC, z.s.p.o. + * + * \brief Zone compiler utility functions. + * + * \addtogroup zoneparser + * @{ + */ + +/* + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _KNOTD_PARSER_UTIL_H_ +#define _KNOTD_PARSER_UTIL_H_ + +#include <assert.h> +#include <fcntl.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <time.h> +#include <netinet/in.h> +#include <netdb.h> + +#include "zcompile/zcompile.h" +#include "libknot/util/descriptor.h" + +int inet_pton4(const char *src, uint8_t *dst); +int inet_pton6(const char *src, uint8_t *dst); +//int my_b32_pton(const char *src, uint8_t *target, size_t tsize); +const char *inet_ntop4(const u_char *src, char *dst, size_t size); +const char *inet_ntop6(const u_char *src, char *dst, size_t size); +int inet_pton(int af, const char *src, void *dst); +void b64_initialize_rmap(); +int b64_pton_do(char const *src, uint8_t *target, size_t targsize); +int b64_pton_len(char const *src); +int b64_pton(char const *src, uint8_t *target, size_t targsize); +void set_bit(uint8_t bits[], size_t index); +uint32_t strtoserial(const char *nptr, const char **endptr); +void write_uint32(void *dst, uint32_t data); +uint32_t strtottl(const char *nptr, const char **endptr); +time_t mktime_from_utc(const struct tm *tm); + +/*!< Conversions from text to wire. */ +/*! + * \brief Converts hex text format to wireformat. + * + * \param hex String to be converted. + * \param len Length of string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_hex(const char *hex, size_t len); + +/*! + * \brief Converts hex text format with length to wireformat. + * + * \param hex String to be converted/. + * \param len Length of string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_hex_length(const char *hex, size_t len); + +/*! + * \brief Converts time string to wireformat. + * + * \param time Time string to be converted. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_time(const char *time); +/*! + * \brief Converts a protocol and a list of service port numbers + * (separated by spaces) in the rdata to wireformat + * + * \param protostr Protocol string. + * \param servicestr Service string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_services(const char *protostr, char *servicestr); + +/*! + * \brief Converts serial to wireformat. + * + * \param serialstr Serial string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_serial(const char *serialstr); +/*! + * \brief Converts period to wireformat. + * + * \param periodstr Period string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_period(const char *periodstr); + +/*! + * \brief Converts short int to wireformat. + * + * \param text String containing short int. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_short(const char *text); + +/*! + * \brief Converts long int to wireformat. + * + * \param text String containing long int. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_long(const char *text); + +/*! + * \brief Converts byte to wireformat. + * + * \param text String containing byte. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_byte(const char *text); + +/*! + * \brief Converts A rdata string to wireformat. + * + * \param text String containing A rdata. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_a(const char *text); + +/*! + * \brief Converts AAAA rdata string to wireformat. + * + * \param text String containing AAAA rdata. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_aaaa(const char *text); + +/*! + * \brief Converts text string to wireformat. + * + * \param text Text string. + * \param len Length of string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_text(const char *text, size_t len); + +/*! + * \brief Converts domain name string to wireformat. + * + * \param name Domain name string. + * \param len Length of string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_dns_name(const uint8_t* name, size_t len); + +/*! + * \brief Converts base32 encoded string to wireformat. + * TODO consider replacing with our implementation. + * + * \param b32 Base32 encoded string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_b32(const char *b32); + +/*! + * \brief Converts base64 encoded string to wireformat. + * TODO consider replacing with our implementation. + * + * \param b64 Base64 encoded string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_b64(const char *b64); + +/*! + * \brief Converts RR type string to wireformat. + * + * \param rr RR type string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_rrtype(const char *rr); + +/*! + * \brief Converts NXT string to wireformat. + * + * \param nxtbits NXT string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_nxt(uint8_t *nxtbits); + +/*! + * \brief Converts NSEC bitmap to wireformat. + * + * \param nsecbits[][] NSEC bits. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_nsec(uint8_t nsecbits[NSEC_WINDOW_COUNT] + [NSEC_WINDOW_BITS_SIZE]); +/*! + * \brief Converts LOC string to wireformat. + * + * \param str LOC string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_loc(char *str); + +/*! + * \brief Converts algorithm string to wireformat. + * + * \param algstr Algorithm string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_algorithm(const char *algstr); + +/*! + * \brief Converts certificate type string to wireformat. + * + * \param typestr Certificate type mnemonic string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_certificate_type(const char *typestr); + +/*! + * \brief Converts APL data to wireformat. + * + * \param str APL data string. + * + * \return Converted wireformat. + */ +uint16_t *zparser_conv_apl_rdata(char *str); + +/*! + * \brief Parses unknown rdata. + * + * \param type Type of data. + * \param wireformat Wireformat of data. + * + * \return Converted wireformat. + */ +void parse_unknown_rdata(uint16_t type, uint16_t *wireformat); + +/*! + * \brief Converts TTL string to int. + * + * \param ttlstr String + * \param error Error code. + * + * \return Converted wireformat. + */ +uint32_t zparser_ttl2int(const char *ttlstr, int* error); + +/*! + * \brief Adds wireformat to temporary list of rdata items. + * + * \param data Wireformat to be added. + */ +void zadd_rdata_wireformat(uint16_t *data); + +/*! + * \brief Adds TXT wireformat to temporary list of rdata items. + * + * \param data Wireformat to be added. + * \param first This is first text to be added. + */ +void zadd_rdata_txt_wireformat(uint16_t *data, int first); + +/*! + * \brief Cleans after using zadd_rdata_txt_wireformat(). + */ +void zadd_rdata_txt_clean_wireformat(); + +/*! + * \brief Adds domain name to temporary list of rdata items. + * + * \param domain Domain name to be added. + */ +void zadd_rdata_domain(knot_dname_t *domain); + +/*! + * \brief Sets bit in NSEC bitmap. + * + * \param bits[][] NSEC bitmaps. + * \param index Index on which bit is to be set. + */ +void set_bitnsec(uint8_t bits[NSEC_WINDOW_COUNT][NSEC_WINDOW_BITS_SIZE], + uint16_t index); + +/*! + * \brief Allocate and init wireformat. + * + * \param data Data to be copied into newly created wireformat. + * \param size Size of data. + * + * \return Allocated wireformat. + */ +uint16_t *alloc_rdata_init(const void *data, size_t size); +uint16_t rrsig_type_covered(knot_rrset_t *rrset); + + +#endif /* _KNOTD_PARSER_UTIL_H_ */ + +/*! @} */ diff --git a/src/zcompile/tests/unittests_zp_main.c b/src/zcompile/tests/unittests_zp_main.c new file mode 100644 index 0000000..5d8c5e9 --- /dev/null +++ b/src/zcompile/tests/unittests_zp_main.c @@ -0,0 +1,62 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include "knot/common.h" +#include "common/libtap/tap_unit.h" + +// Units to test +#include "zcompile_tests.c" + +// Run all loaded units +int main(int argc, char *argv[]) +{ + // Open log + //log_init(LOG_UPTO(LOG_ERR), LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING)); + + // Build test set + unit_api *tests[] = { + &zoneparser_tests_api, //! Zoneparser unit + NULL + }; + + // Plan number of tests + int id = 0; + int test_count = 0; + note("Units:"); + while (tests[id] != NULL) { + note("- %s : %d tests", tests[id]->name, + tests[id]->count(argc, argv)); + test_count += tests[id]->count(argc, argv); + ++id; + } + + plan(test_count); + + // Run tests + id = 0; + while (tests[id] != NULL) { + diag("Testing unit: %s", tests[id]->name); + tests[id]->run(argc, argv); + ++id; + } + + //log_close(); + + // Evaluate + return exit_status(); +} + diff --git a/src/zcompile/tests/zcompile_tests.c b/src/zcompile/tests/zcompile_tests.c new file mode 100644 index 0000000..5d3dce6 --- /dev/null +++ b/src/zcompile/tests/zcompile_tests.c @@ -0,0 +1,425 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "libknot/zone/zone.h" +#include "knot/zone/zone-load.h" +#include "knot/common.h" +#include "libknot/rrset.h" +#include "libknot/util/descriptor.h" +#include "zcompile/zcompile.h" + +#ifdef TEST_WITH_LDNS +#include "ldns/ldns.h" +#endif + +static int zoneparser_tests_count(int argc, char *argv[]); +static int zoneparser_tests_run(int argc, char *argv[]); + +/* + * Unit API. + */ +unit_api zoneparser_tests_api = { + "Zoneparser", + &zoneparser_tests_count, + &zoneparser_tests_run +}; + +#ifdef TEST_WITH_LDNS +/* + * Unit implementation. + */static int compare_wires_simple_zp(uint8_t *wire1, + uint8_t *wire2, uint count) +{ + int i = 0; + while (i < count && + wire1[i] == wire2[i]) { + i++; + } + return (!(count == i)); +} + +/* compares only one rdata */ +static int compare_rr_rdata_silent(knot_rdata_t *rdata, ldns_rr *rr, + uint16_t type) +{ + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(type); + for (int i = 0; i < rdata->count; i++) { + /* TODO check for ldns "descriptors" as well */ + if (desc->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME) { + assert(ldns_rr_rdf(rr, i)); + if (rdata->items[i].dname->size != + ldns_rdf_size(ldns_rr_rdf(rr, i))) { + return 1; + } + if (compare_wires_simple_zp(rdata->items[i].dname->name, + ldns_rdf_data(ldns_rr_rdf(rr, i)), + rdata->items[i].dname->size) != 0) { + return 1; + } + } else { + if (ldns_rr_rdf(rr, i) == NULL && + rdata->items[i].raw_data[0] != 0) { + return 1; + } else { + continue; + } + if (rdata->items[i].raw_data[0] != + ldns_rdf_size(ldns_rr_rdf(rr, i))) { + + /* ldns stores the size including the + * length, dnslib does not */ + if (abs(rdata->items[i].raw_data[0] - + ldns_rdf_size(ldns_rr_rdf(rr, i))) != 1) { + return 1; + } + } + if (compare_wires_simple_zp((uint8_t *) + (rdata->items[i].raw_data + 1), + ldns_rdf_data(ldns_rr_rdf(rr, i)), + rdata->items[i].raw_data[0]) != 0) { + return 1; + } + } + } + return 0; +} + +static int compare_rrset_w_ldns_rrset(const knot_rrset_t *rrset, + ldns_rr_list *rrs, + char check_rdata, char verbose) +{ + /* We should have only one rrset from ldns, although it is + * represented as rr_list ... */ + + /* TODO errors */ + + assert(rrs); + assert(rrset); + + ldns_rr_list_sort(rrs); + + /* compare headers */ + + ldns_rr *rr = ldns_rr_list_rr(rrs, 0); + + if (rrset->owner->size != ldns_rdf_size(ldns_rr_owner(rr))) { + diag("RRSet owner names differ in length"); + if (!verbose) { + return 1; + } + diag("ldns: %d, dnslib: %d", ldns_rdf_size(ldns_rr_owner(rr)), + rrset->owner->size); + diag("%s", knot_dname_to_str(rrset->owner)); + diag("%s", ldns_rdf_data(ldns_rr_owner(rr))); + return 1; + } + + if (compare_wires_simple_zp(rrset->owner->name, + ldns_rdf_data(ldns_rr_owner(rr)), + rrset->owner->size) != 0) { + diag("RRSet owner wireformats differ"); + return 1; + } + + if (rrset->type != ldns_rr_get_type(rr)) { + diag("RRset types differ"); + if (!verbose) { + return 1; + } + diag("Dnslib type: %d Ldns type: %d", rrset->type, + ldns_rr_get_type(rr)); + return 1; + } + + if (rrset->rclass != ldns_rr_get_class(rr)) { + diag("RRset classes differ"); + return 1; + } + + if (rrset->ttl != ldns_rr_ttl(rr)) { + diag("RRset TTLs differ"); + if (!verbose) { + return 1; + } + diag("dnslib: %d ldns: %d", rrset->ttl, ldns_rr_ttl(rr)); + return 1; + } + + if (!check_rdata) { + return 0; + } + + /* compare rdatas */ + + /* sort dnslib rdata */ + + knot_rdata_t *tmp_rdata = rrset->rdata; + + rr = ldns_rr_list_pop_rr(rrs); + + char found; + + while (rr != NULL) { + found = 0; + tmp_rdata = rrset->rdata; + while (!found && + tmp_rdata->next != rrset->rdata) { + if (compare_rr_rdata_silent(tmp_rdata, rr, + rrset->type) == 0) { + found = 1; + } + tmp_rdata = tmp_rdata->next; + } + + if (!found && + compare_rr_rdata_silent(tmp_rdata, rr, rrset->type) == 0) { + found = 1; + } + + /* remove the found rdata from list */ + if (!found) { + diag("RRsets rdata differ"); + return 1; + } + ldns_rr_free(rr); + + rr = ldns_rr_list_pop_rr(rrs); + } + + return 0; +} + +int compare_zones(knot_zone_contents_t *zone, + ldns_rr_list *ldns_list, char verbose) +{ + /* TODO currently test fail when encountering first error - + * it should finish going through the zone */ + knot_rrset_t *tmp_rrset = NULL; + + knot_dname_t *tmp_dname = NULL; + + knot_node_t *node = NULL; + + ldns_rr_list *ldns_rrset = ldns_rr_list_pop_rrset(ldns_list); + + if (ldns_rrset == NULL) { + diag("Error: empty node"); + return 1; + } + + ldns_rr *rr = NULL; + + /* + * Following cycle works like this: First, we get RR from ldns rrset, + * then we search for the node containing the rrset, then we get the + * rrset, which is then compared with whole ldns rrset. + */ + + /* ldns_rr_list_pop_rrset should pop the first rrset */ + while (ldns_rrset != NULL) { + rr = ldns_rr_list_rr(ldns_rrset, 0); + tmp_dname = + knot_dname_new_from_wire(ldns_rdf_data(ldns_rr_owner(rr)), + ldns_rdf_size(ldns_rr_owner(rr)), + NULL); + + node = knot_zone_contents_get_node(zone, tmp_dname); + + if (node == NULL) { + node = knot_zone_contents_get_nsec3_node(zone, + tmp_dname); + } + + if (node == NULL) { + diag("Could not find node"); + diag("%s", knot_dname_to_str(tmp_dname)); + return 1; + } + + knot_dname_free(&tmp_dname); + + tmp_rrset = knot_node_get_rrset(node, + ldns_rr_get_type(ldns_rr_list_rr(ldns_rrset, + 0))); + + if (tmp_rrset == NULL && + (uint)(ldns_rr_get_type(ldns_rr_list_rr(ldns_rrset, 0))) != + (uint)KNOT_RRTYPE_RRSIG) { + diag("Could not find rrset"); + if (!verbose) { + return 1; + } + ldns_rr_list_print(stdout, ldns_rrset); + diag("%s", knot_dname_to_str(node->owner)); + return 1; + } else if ((uint)(ldns_rr_get_type(ldns_rr_list_rr(ldns_rrset, + 0))) == + (uint)KNOT_RRTYPE_RRSIG) { + knot_rrset_t *rrsigs = NULL; + /* read type covered from ldns rrset */ + for (int i = 0; i < ldns_rrset->_rr_count; i++) { + uint16_t type_covered = + ldns_rdf_data(ldns_rr_rdf( + ldns_rr_list_rr(ldns_rrset, i), 0))[1]; + + /* + * Dnslib stores RRSIGs separately - + * we have to find get it from its "parent" + * rrset. + */ + + tmp_rrset = knot_node_get_rrset(node, + type_covered); + + if (tmp_rrset == NULL) { + if (!verbose) { + return 1; + } + diag("following rrset " + "could not be found"); + ldns_rr_list_print(stdout, ldns_rrset); + return 1; + } + + if (rrsigs == NULL) { + rrsigs = tmp_rrset->rrsigs; + } else { + knot_rrset_merge((void *)&rrsigs, + (void *)&(tmp_rrset->rrsigs)); + } + } + tmp_rrset = rrsigs; + } + +/* diag("dnslib type: %d", tmp_rrset->type); + diag("dnslib dname: %s", tmp_rrset->owner->name); + + diag("ldns type: %d", + ldns_rr_get_type(ldns_rr_list_rr(ldns_rrset, 0))); + diag("ldns dname : %s", ldns_rdf_data(ldns_rr_owner( + ldns_rr_list_rr(ldns_rrset, 0)))); */ + +// knot_rrset_dump(tmp_rrset, 1); + + if (compare_rrset_w_ldns_rrset(tmp_rrset, ldns_rrset, + 1, 0) != 0) { + diag("RRSets did not match"); +// knot_rrset_dump(tmp_rrset, 1); + return 1; + } + + ldns_rr_list_deep_free(ldns_rrset); + + ldns_rrset = ldns_rr_list_pop_rrset(ldns_list); + + if (ldns_rrset == NULL) { + ldns_rrset = ldns_rr_list_pop_rrset(ldns_list); + } + } + + return 0; +} + +#endif + +static int test_zoneparser_zone_read(const char *origin, const char *filename, + const char *outfile) +{ +#ifndef TEST_WITH_LDNS + diag("Zoneparser tests without usage of ldns are not implemented"); + return 0; +#endif + +#ifdef TEST_WITH_LDNS + /* Calls zcompile. */ + parser = zparser_create(); + int ret = zone_read(origin, filename, outfile, 0); + if (ret != 0) { + diag("Could not load zone from file: %s", filename); + return 0; + } + + knot_zone_t *dnsl_zone = NULL; + zloader_t *loader = NULL; + if (knot_zload_open(&loader, outfile) != 0) { + diag("Could not create zone loader.\n"); + return 0; + } + dnsl_zone = knot_zload_load(loader); + remove(outfile); + if (!dnsl_zone) { + diag("Could not load dumped zone.\n"); + return 0; + } + + ldns_zone *ldns_zone = NULL; + FILE *f = fopen(filename, "r"); + if (ldns_zone_new_frm_fp(&ldns_zone, f, NULL, + 0, LDNS_RR_CLASS_IN) != LDNS_STATUS_OK) { + diag("Could not load zone from file: %s (ldns)", filename); + return 0; + } + +// ldns_zone_sort(ldns_zone); + + /* + * LDNS stores SOA record independently - create a list with all + * records in it. + */ + + ldns_rr_list *ldns_list = ldns_zone_rrs(ldns_zone); + + ldns_rr_list_push_rr(ldns_list, ldns_zone_soa(ldns_zone)); + + if (compare_zones(dnsl_zone->contents, ldns_list, 0) != 0) { + return 0; + } + + knot_zone_deep_free(&dnsl_zone, 0); + ldns_zone_free(ldns_zone); + fclose(f); + return 1; +#endif +} + +static const int ZONEPARSER_TEST_COUNT = 1; + +/*! API: return number of tests. */ +static int zoneparser_tests_count(int argc, char *argv[]) +{ + return ZONEPARSER_TEST_COUNT; +} + +/*! API: run tests. */ +static int zoneparser_tests_run(int argc, char *argv[]) +{ + if (argc == 3) { + ok(test_zoneparser_zone_read(argv[1], argv[2], + "foo_test_zone"), + "zoneparser: read (%s)", + argv[2]); + } else { + diag("Wrong parameters\n usage: " + "knot-zcompile-unittests origin zonefile"); + return 0; + } + return 1; +} diff --git a/src/zcompile/zcompile-error.c b/src/zcompile/zcompile-error.c new file mode 100644 index 0000000..9357cde --- /dev/null +++ b/src/zcompile/zcompile-error.c @@ -0,0 +1,52 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include "zcompile/zcompile-error.h" + +#include "common/errors.h" + +/*! \brief Table linking error messages to error codes. */ +const error_table_t knot_zcompile_error_msgs[KNOTDZCOMPILE_ERROR_COUNT] = { + + /* Mapped errors. */ + {KNOTDZCOMPILE_EOK, "OK"}, + {KNOTDZCOMPILE_ENOMEM, "Not enough memory."}, + {KNOTDZCOMPILE_EINVAL, "Invalid parameter passed."}, + {KNOTDZCOMPILE_ENOTSUP, "Parameter not supported."}, + {KNOTDZCOMPILE_EBUSY, "Requested resource is busy."}, + {KNOTDZCOMPILE_EAGAIN, + "The system lacked the necessary resource, try again."}, + {KNOTDZCOMPILE_EACCES, + "Permission to perform requested operation is denied."}, + {KNOTDZCOMPILE_ECONNREFUSED, "Connection is refused."}, + {KNOTDZCOMPILE_EISCONN, "Already connected."}, + {KNOTDZCOMPILE_EADDRINUSE, "Address already in use."}, + {KNOTDZCOMPILE_ENOENT, "Resource not found."}, + {KNOTDZCOMPILE_ERANGE, "Value is out of range."}, + + /* Custom errors. */ + {KNOTDZCOMPILE_ERROR, "Generic error."}, + {KNOTDZCOMPILE_EBRDATA, "Malformed RDATA."}, + {KNOTDZCOMPILE_ESOA, "Multiple SOA records."}, + {KNOTDZCOMPILE_EBADSOA, "SOA record has different owner " + "than in config - parser will not continue!"}, + {KNOTDZCOMPILE_EBADNODE, "Error handling node."}, + {KNOTDZCOMPILE_EZONEINVAL, "Invalid zone file."}, + {KNOTDZCOMPILE_EPARSEFAIL, "Parser failed."}, + {KNOTDZCOMPILE_ENOIPV6, "IPv6 support disabled."}, + {KNOTDZCOMPILE_ESYNT, "Parser syntactic error."}, + {KNOTDZCOMPILE_ERROR, 0} +}; diff --git a/src/zcompile/zcompile-error.h b/src/zcompile/zcompile-error.h new file mode 100644 index 0000000..c6d999c --- /dev/null +++ b/src/zcompile/zcompile-error.h @@ -0,0 +1,90 @@ +/*! + * \file zcompile-error.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * \brief Error codes and function for getting error message. + * + * \addtogroup zoneparser + * @{ + */ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOTD_ZCOMPILE_ERROR_H_ +#define _KNOTD_ZCOMPILE_ERROR_H_ + +#include "common/errors.h" + +/*! + * \brief Error codes used in the server. + * + * Some viable errors are directly mapped + * to libc errno codes. + */ +enum knot_zcompile_error { + + /* Directly mapped error codes. */ + KNOTDZCOMPILE_EOK = 0, + KNOTDZCOMPILE_ENOMEM = -ENOMEM, /*!< \brief Out of memory. */ + KNOTDZCOMPILE_EINVAL = -EINVAL, /*!< \brief Invalid parameter passed. */ + /*! + * \brief Parameter not supported. + */ + KNOTDZCOMPILE_ENOTSUP = -ENOTSUP, + KNOTDZCOMPILE_EBUSY = -EBUSY, /*!< \brief Requested resource is busy. */ + /*! + * \brief OS lacked necessary resources. + */ + KNOTDZCOMPILE_EAGAIN = -EAGAIN, + KNOTDZCOMPILE_EACCES = -EACCES, /*!< \brief Permission is denied. */ + /*! + * \brief Connection is refused. + */ + KNOTDZCOMPILE_ECONNREFUSED = -ECONNREFUSED, + KNOTDZCOMPILE_EISCONN = -EISCONN, /*!< \brief Already connected. */ + /*! + * \brief Address already in use. + */ + KNOTDZCOMPILE_EADDRINUSE = -EADDRINUSE, + KNOTDZCOMPILE_ENOENT = -ENOENT, /*!< \brief Resource not found. */ + KNOTDZCOMPILE_ERANGE = -ERANGE, /*!< \brief Value is out of range. */ + + /* Custom error codes. */ + KNOTDZCOMPILE_ERROR = -16384, /*!< \brief Generic error. */ + KNOTDZCOMPILE_ESYNT, /*!< \brief Syntax error. */ + KNOTDZCOMPILE_EBADNODE, /*!< \brief Node error. */ + KNOTDZCOMPILE_EBRDATA, /*!< \brief RDATA error. */ + KNOTDZCOMPILE_EBADSOA, /*!< \brief SOA owner error. */ + KNOTDZCOMPILE_ESOA, /*!< \brief Multiple SOA records. */ + + KNOTDZCOMPILE_EZONEINVAL, /*!< \brief Invalid zone file. */ + KNOTDZCOMPILE_EPARSEFAIL, /*!< \brief Parser fail. */ + KNOTDZCOMPILE_ENOIPV6, /*! \brief No IPv6 support. */ + + KNOTDZCOMPILE_ERROR_COUNT = 22 +}; + +typedef enum knot_zcompile_error knot_zcompile_error_t; + +/*! \brief Table linking error messages to error codes. */ +extern const error_table_t knot_zcompile_error_msgs[KNOTDZCOMPILE_ERROR_COUNT]; + +#endif /* _KNOTD_ZCOMPILE_ERROR_H_ */ + +/*! @} */ diff --git a/src/zcompile/zcompile.c b/src/zcompile/zcompile.c new file mode 100644 index 0000000..ec3cbe2 --- /dev/null +++ b/src/zcompile/zcompile.c @@ -0,0 +1,639 @@ +/*! + * \file zcompile.c + * + * \author Jan Kadlec <jan.kadlec@nic.cz>. Minor portions of code taken from + * NSD. + * + * \brief Zone compiler. + * + * \addtogroup zoneparser + * @{ + */ + +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <assert.h> +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +#include "common/base32hex.h" +#include "zcompile/zcompile.h" +#include "zcompile/parser-util.h" +#include "knot/zone/zone-dump-text.h" +#include "zparser.h" +#include "zcompile/zcompile-error.h" +#include "knot/zone/zone-dump.h" +#include "libknot/libknot.h" +#include "libknot/util/utils.h" + +/* Some global flags... */ +static int vflag = 0; +/* if -v then print progress each 'progress' RRs */ +static int progress = 10000; + +/* Total errors counter */ +static long int totalerrors = 0; +static long int totalrrs = 0; + +extern FILE *zp_get_in(void *scanner); + +//#define ZP_DEBUG + +#ifdef ZP_DEBUG +#define dbg_zp(msg...) fprintf(stderr, msg) +#else +#define dbg_zp(msg...) +#endif + +/*! + * \brief Adds RRSet to list. + * + * \param head Head of list. + * \param rrsig RRSet to be added. + */ +static int rrset_list_add(rrset_list_t **head, knot_rrset_t *rrsig) +{ + if (*head == NULL) { + *head = malloc(sizeof(rrset_list_t)); + if (*head == NULL) { + ERR_ALLOC_FAILED; + return KNOTDZCOMPILE_ENOMEM; + } + (*head)->next = NULL; + (*head)->data = rrsig; + } else { + rrset_list_t *tmp = malloc(sizeof(*tmp)); + if (tmp == NULL) { + ERR_ALLOC_FAILED; + return KNOTDZCOMPILE_ENOMEM; + } + tmp->next = *head; + tmp->data = rrsig; + *head = tmp; + } + + return KNOTDZCOMPILE_EOK; +} + +/*! + * \brief Deletes RRSet list. Sets pointer to NULL. + * + * \param head Head of list to be deleted. + */ +static void rrset_list_delete(rrset_list_t **head) +{ + rrset_list_t *tmp; + if (*head == NULL) { + return; + } + + while (*head != NULL) { + tmp = *head; + *head = (*head)->next; + free(tmp); + } + + *head = NULL; +} + +static int find_rrset_for_rrsig_in_zone(knot_zone_contents_t *zone, + knot_rrset_t *rrsig) +{ + assert(rrsig != NULL); + assert(rrsig->rdata->items[0].raw_data); + + knot_node_t *tmp_node = NULL; + + if (rrsig->type != KNOT_RRTYPE_NSEC3) { + tmp_node = knot_zone_contents_get_node(zone, rrsig->owner); + } else { + tmp_node = knot_zone_contents_get_nsec3_node(zone, + rrsig->owner); + } + + if (tmp_node == NULL) { + return KNOTDZCOMPILE_EINVAL; + } + + knot_rrset_t *tmp_rrset = + knot_node_get_rrset(tmp_node, rrsig->type); + + if (tmp_rrset == NULL) { + return KNOTDZCOMPILE_EINVAL; + } + + if (tmp_rrset->rrsigs != NULL) { + knot_zone_contents_add_rrsigs(zone, rrsig, &tmp_rrset, &tmp_node, + KNOT_RRSET_DUPL_MERGE, 1); + knot_rrset_free(&rrsig); + } else { + knot_zone_contents_add_rrsigs(zone, rrsig, &tmp_rrset, &tmp_node, + KNOT_RRSET_DUPL_SKIP, 1); + } + + return KNOTDZCOMPILE_EOK; +} + +static int find_rrset_for_rrsig_in_node(knot_zone_contents_t *zone, + knot_node_t *node, + knot_rrset_t *rrsig) +{ + assert(rrsig != NULL); + assert(rrsig->rdata->items[0].raw_data); + assert(node); + + assert(knot_dname_compare(rrsig->owner, node->owner) == 0); + + knot_rrset_t *tmp_rrset = + knot_node_get_rrset(node, rrsig_type_covered(rrsig)); + + if (tmp_rrset == NULL) { + return KNOTDZCOMPILE_EINVAL; + } + + if (tmp_rrset->rrsigs != NULL) { + if (knot_zone_contents_add_rrsigs(zone, rrsig, &tmp_rrset, &node, + KNOT_RRSET_DUPL_MERGE, 1) < 0) { + return KNOTDZCOMPILE_EINVAL; + } + knot_rrset_free(&rrsig); + } else { + if (knot_zone_contents_add_rrsigs(zone, rrsig, &tmp_rrset, &node, + KNOT_RRSET_DUPL_SKIP, 1) < 0) { + return KNOTDZCOMPILE_EINVAL; + } + } + + assert(tmp_rrset->rrsigs != NULL); + + return KNOTDZCOMPILE_EOK; +} + +static knot_node_t *create_node(knot_zone_contents_t *zone, + knot_rrset_t *current_rrset, + int (*node_add_func)(knot_zone_contents_t *zone, knot_node_t *node, + int create_parents, uint8_t, int), + knot_node_t *(*node_get_func)(const knot_zone_contents_t *zone, + const knot_dname_t *owner)) +{ + knot_node_t *node = + knot_node_new(current_rrset->owner, NULL, 0); + if (node_add_func(zone, node, 1, 0, 1) != 0) { + return NULL; + } + + current_rrset->owner = node->owner; + + return node; +} + +static void process_rrsigs_in_node(knot_zone_contents_t *zone, + knot_node_t *node) +{ + rrset_list_t *tmp = parser->node_rrsigs; + while (tmp != NULL) { + if (find_rrset_for_rrsig_in_node(zone, node, + tmp->data) != 0) { + rrset_list_add(&parser->rrsig_orphans, + tmp->data); + parser->rrsig_orphan_count++; + } + tmp = tmp->next; + } +} + +int process_rr(void) +{ + knot_zone_t *zone = parser->current_zone; + assert(zone != NULL); + knot_zone_contents_t *contents = knot_zone_get_contents(zone); + assert(contents != NULL); + knot_rrset_t *current_rrset = parser->current_rrset; + knot_rrset_t *rrset; + knot_rrtype_descriptor_t *descriptor = + knot_rrtype_descriptor_by_type(current_rrset->type); + + dbg_zp("%s\n", knot_dname_to_str(parser->current_rrset->owner)); + dbg_zp("type: %s\n", knot_rrtype_to_string(parser->current_rrset->type)); + dbg_zp("rdata count: %d\n", parser->current_rrset->rdata->count); +// hex_print(parser->current_rrset->rdata->items[0].raw_data, +// parser->current_rrset->rdata->items[0].raw_data[0]); + + if (descriptor->fixed_items) { + assert(current_rrset->rdata->count == descriptor->length); + } + + assert(current_rrset->rdata->count > 0); + + assert(knot_dname_is_fqdn(current_rrset->owner)); + + int (*node_add_func)(knot_zone_contents_t *, knot_node_t *, int, + uint8_t, int); + knot_node_t *(*node_get_func)(const knot_zone_contents_t *, + const knot_dname_t *); + + + /* If we have RRSIG of NSEC3 type first node will have + * to be created in NSEC3 part of the zone */ + + uint16_t type_covered = 0; + if (current_rrset->type == KNOT_RRTYPE_RRSIG) { + type_covered = rrsig_type_covered(current_rrset); + } + + if (current_rrset->type != KNOT_RRTYPE_NSEC3 && + type_covered != KNOT_RRTYPE_NSEC3) { + node_add_func = &knot_zone_contents_add_node; + node_get_func = &knot_zone_contents_get_node; + } else { + node_add_func = &knot_zone_contents_add_nsec3_node; + node_get_func = &knot_zone_contents_get_nsec3_node; + } + + if ((current_rrset->type == KNOT_RRTYPE_SOA) && (zone != NULL)) { + if (knot_node_rrset(knot_zone_contents_apex(contents), + KNOT_RRTYPE_SOA) != NULL) { + /* Receiving another SOA. */ + if (!knot_rrset_compare(current_rrset, + knot_node_rrset(knot_zone_contents_apex(contents), + KNOT_RRTYPE_SOA), KNOT_RRSET_COMPARE_WHOLE)) { + return KNOTDZCOMPILE_ESOA; + } else { + zc_warning_prev_line("encountered identical " + "extra SOA record"); + return KNOTDZCOMPILE_EOK; + } + } + } + + /*!< \todo Make sure the maximum RDLENGTH does not exceed 65535 bytes.*/ + + if (current_rrset->type == KNOT_RRTYPE_SOA) { + if (knot_dname_compare(current_rrset->owner, + parser->origin_from_config) != 0) { + zc_error_prev_line("SOA record has a different " + "owner than the one specified " + "in config! \n"); + /* Such SOA cannot even be added, because + * it would not be in the zone apex. */ + return KNOTDZCOMPILE_EBADSOA; + } + } + + if (current_rrset->type == KNOT_RRTYPE_RRSIG) { + /*!< \todo Still a leak somewhere. */ + knot_rrset_t *tmp_rrsig = + knot_rrset_new(current_rrset->owner, + KNOT_RRTYPE_RRSIG, + current_rrset->rclass, + current_rrset->ttl); + if (tmp_rrsig == NULL) { + return KNOTDZCOMPILE_ENOMEM; + } + + if (knot_rrset_add_rdata(tmp_rrsig, + current_rrset->rdata) != 0) { + return KNOTDZCOMPILE_EBRDATA; + } + + if (parser->last_node && + knot_dname_compare(parser->last_node->owner, + current_rrset->owner) != 0) { + /* RRSIG is first in the node, so we have to create it + * before we return + */ + if (parser->node_rrsigs != NULL) { + process_rrsigs_in_node(contents, + parser->last_node); + rrset_list_delete(&parser->node_rrsigs); + } + + if ((parser->last_node = create_node(contents, + current_rrset, node_add_func, + node_get_func)) == NULL) { + knot_rrset_free(&tmp_rrsig); + return KNOTDZCOMPILE_EBADNODE; + } + } + + if (rrset_list_add(&parser->node_rrsigs, tmp_rrsig) != 0) { + return KNOTDZCOMPILE_ENOMEM; + } + + return KNOTDZCOMPILE_EOK; + } + + assert(current_rrset->type != KNOT_RRTYPE_RRSIG); + + knot_node_t *node = NULL; + /* \note this could probably be much simpler */ + if (parser->last_node && current_rrset->type != KNOT_RRTYPE_SOA && + knot_dname_compare(parser->last_node->owner, + current_rrset->owner) == + 0) { + node = parser->last_node; + } else { + if (parser->last_node && parser->node_rrsigs) { + process_rrsigs_in_node(contents, + parser->last_node); + } + + rrset_list_delete(&parser->node_rrsigs); + + /* new node */ + node = node_get_func(contents, current_rrset->owner); + } + + if (node == NULL) { + if (parser->last_node && parser->node_rrsigs) { + process_rrsigs_in_node(contents, + parser->last_node); + } + + if ((node = create_node(contents, current_rrset, + node_add_func, + node_get_func)) == NULL) { + return KNOTDZCOMPILE_EBADNODE; + } + } + + rrset = knot_node_get_rrset(node, current_rrset->type); + if (!rrset) { + rrset = knot_rrset_new(current_rrset->owner, + current_rrset->type, + current_rrset->rclass, + current_rrset->ttl); + if (rrset == NULL) { + return KNOTDZCOMPILE_ENOMEM; + } + + if (knot_rrset_add_rdata(rrset, current_rrset->rdata) != 0) { + free(rrset); + return KNOTDZCOMPILE_EBRDATA; + } + + /* I chose skip, but there should not really be + * any rrset to skip */ + if (knot_zone_contents_add_rrset(contents, rrset, &node, + KNOT_RRSET_DUPL_SKIP, 1) < 0) { + free(rrset); + return KNOTDZCOMPILE_EBRDATA; + } + } else { + if (current_rrset->type != + KNOT_RRTYPE_RRSIG && rrset->ttl != + current_rrset->ttl) { + zc_error_prev_line( + "TTL does not match the TTL of the RRset"); + } + + if (knot_zone_contents_add_rrset(contents, current_rrset, + &node, + KNOT_RRSET_DUPL_MERGE, 1) < 0) { + return KNOTDZCOMPILE_EBRDATA; + } + } + + if (vflag > 1 && totalrrs > 0 && (totalrrs % progress == 0)) { + zc_error_prev_line("Total errors: %ld\n", totalrrs); + } + + parser->last_node = node; + + ++totalrrs; + + return KNOTDZCOMPILE_EOK; +} + +static uint find_rrsets_orphans(knot_zone_contents_t *zone, rrset_list_t + *head) +{ + uint found_rrsets = 0; + while (head != NULL) { + if (find_rrset_for_rrsig_in_zone(zone, head->data) == 0) { + found_rrsets += 1; + dbg_zp("RRSET succesfully found: owner %s type %s\n", + knot_dname_to_str(head->data->owner), + knot_rrtype_to_string(head->data->type)); + } + else { /* we can throw it away now */ + knot_rrset_free(&head->data); + } + head = head->next; + } + return found_rrsets; +} + +/* + * + * Opens a zone file. + * + * Returns: + * + * - pointer to the parser structure + * - NULL on error and errno set + * + */ +static int zone_open(const char *filename, uint32_t ttl, uint16_t rclass, + knot_node_t *origin, void *scanner, knot_dname_t *origin_from_config) +{ + /* Open the zone file... */ + if (strcmp(filename, "-") == 0) { + zp_set_in(stdin, scanner); + filename = "<stdin>"; + } else { + FILE *f = fopen(filename, "r"); + if (f == NULL) { + return 0; + } + zp_set_in(f, scanner); + if (zp_get_in(scanner) == 0) { + return 0; + } + } + +// int fd = fileno(zp_get_in(scanner)); +// if (fd == -1) { +// return 0; +// } + +// if (fcntl(fd, F_SETLK, knot_file_lock(F_RDLCK, SEEK_SET)) == -1) { +// fprintf(stderr, "Could not lock zone file for read!\n"); +// return 0; +// } + + zparser_init(filename, ttl, rclass, origin, origin_from_config); + + return 1; +} + +/* + * Reads the specified zone into the memory + * + */ +int zone_read(const char *name, const char *zonefile, const char *outfile, + int semantic_checks) +{ + if (!outfile) { + zc_error_prev_line("Missing output file for '%s'\n", + zonefile); + return KNOTDZCOMPILE_EINVAL; + } + + /* Check that we can write to outfile. */ + FILE *f = fopen(outfile, "wb"); + if (f == NULL) { + fprintf(stderr, "Cannot write zone db to file '%s'\n", + outfile); + return KNOTDZCOMPILE_EINVAL; + } + fclose(f); + + +// char ebuf[256]; + + knot_dname_t *dname = + knot_dname_new_from_str(name, strlen(name), NULL); + if (dname == NULL) { + return KNOTDZCOMPILE_ENOMEM; + } + + knot_node_t *origin_node = knot_node_new(dname, NULL, 0); + + /*!< \todo Another copy is probably not needed. */ + knot_dname_t *origin_from_config = + knot_dname_new_from_str(name, strlen(name), NULL); + if (origin_from_config == NULL) { + return KNOTDZCOMPILE_ENOMEM; + } + + //assert(origin_node->next == NULL); + + assert(knot_node_parent(origin_node, 0) == NULL); + if (origin_node == NULL) { + knot_dname_release(dname); + return KNOTDZCOMPILE_ENOMEM; + } + + void *scanner = NULL; + zp_lex_init(&scanner); + if (scanner == NULL) { + return KNOTDZCOMPILE_ENOMEM; + } + + if (!zone_open(zonefile, 3600, KNOT_CLASS_IN, origin_node, scanner, + origin_from_config)) { + zc_error_prev_line("Cannot open '%s'\n", + zonefile); + zparser_free(); + return KNOTDZCOMPILE_EZONEINVAL; + } + + if (zp_parse(scanner) != 0) { +// int fd = fileno(zp_get_in(scanner)); +// if (fcntl(fd, F_SETLK, +// knot_file_lock(F_UNLCK, SEEK_SET)) == -1) { +// return KNOTDZCOMPILE_EACCES; +// } + + FILE *in_file = (FILE *)zp_get_in(scanner); + fclose(in_file); + zp_lex_destroy(scanner); + + return KNOTDZCOMPILE_ESYNT; + } + + knot_zone_contents_t *contents = + knot_zone_get_contents(parser->current_zone); + + FILE *in_file = (FILE *)zp_get_in(scanner); + fclose(in_file); + zp_lex_destroy(scanner); + + /* Unlock zone file. */ +// int fd = fileno(zp_get_in(scanner)); +// if (fcntl(fd, F_SETLK, knot_file_lock(F_UNLCK, SEEK_SET)) == -1) { +// fprintf(stderr, "Could not lock zone file for read!\n"); +// return 0; +// } + + dbg_zp("zp complete %p\n", parser->current_zone); + + if (parser->last_node && parser->node_rrsigs != NULL) { + /* assign rrsigs to last node in the zone*/ + process_rrsigs_in_node(contents, + parser->last_node); + rrset_list_delete(&parser->node_rrsigs); + } + + dbg_zp("zone parsed\n"); + + if (!(parser->current_zone && + knot_node_rrset(parser->current_zone->contents->apex, + KNOT_RRTYPE_SOA))) { + zc_error_prev_line("Zone file does not contain SOA record!\n"); + knot_zone_deep_free(&parser->current_zone, 1); + zparser_free(); + return KNOTDZCOMPILE_EZONEINVAL; + } + + uint found_orphans; + found_orphans = find_rrsets_orphans(contents, + parser->rrsig_orphans); + + dbg_zp("%u orphans found\n", found_orphans); + + rrset_list_delete(&parser->rrsig_orphans); + + if (found_orphans != parser->rrsig_orphan_count) { + fprintf(stderr, + "There are unassigned RRSIGs in the zone!\n"); + parser->errors++; + } + + knot_zone_contents_adjust(contents, 0); + + dbg_zp("rdata adjusted\n"); + + if (parser->errors != 0) { + fprintf(stderr, + "Parser finished with error, not dumping the zone!\n"); + } else { + if (knot_zdump_binary(contents, + outfile, semantic_checks, + zonefile) != 0) { + fprintf(stderr, "Could not dump zone!\n"); + totalerrors++; + } + dbg_zp("zone dumped.\n"); + } + + /* This is *almost* unnecessary */ + knot_zone_deep_free(&(parser->current_zone), 1); + + fflush(stdout); + totalerrors += parser->errors; + zparser_free(); + + return totalerrors; +} + +/*! @} */ diff --git a/src/zcompile/zcompile.h b/src/zcompile/zcompile.h new file mode 100644 index 0000000..33dd3ee --- /dev/null +++ b/src/zcompile/zcompile.h @@ -0,0 +1,207 @@ +/*! + * \file zoneparser.h + * + * \author modifications by Jan Kadlec <jan.kadlec@nic.cz>, most of the code + * by NLnet Labs. + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * \brief Zone compiler. + * + * \addtogroup zoneparser + * @{ + */ + +/* + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _KNOTD_ZONEPARSER_H_ +#define _KNOTD_ZONEPARSER_H_ + +#include <stdio.h> + +#include "libknot/dname.h" +#include "libknot/rrset.h" +#include "libknot/zone/node.h" +#include "libknot/rdata.h" +#include "libknot/zone/zone.h" +#include "libknot/zone/dname-table.h" +#include "libknot/zone/dname-table.h" +#include "common/slab/slab.h" + +#define MAXRDATALEN 64 /*!< Maximum number of RDATA items. */ +#define MAXLABELLEN 63 /*!< Maximum label length. */ +#define MAXDOMAINLEN 255 /*!< Maximum domain name length */ +#define MAX_RDLENGTH 65535 /*!< Maximum length of RDATA item */ +#define MAXTOKENSLEN 512 /*!< Maximum number of tokens per entry. */ +#define B64BUFSIZE 65535 /*!< Buffer size for b64 conversion. */ +#define ROOT (const uint8_t *)"\001" /*!< Root domain name. */ + +#define NSEC_WINDOW_COUNT 256 /*!< Number of NSEC windows. */ +#define NSEC_WINDOW_BITS_COUNT 256 /*!< Number of bits in NSEC window. */ +/*! \brief Size of NSEC window in bytes. */ +#define NSEC_WINDOW_BITS_SIZE (NSEC_WINDOW_BITS_COUNT / 8) + +/* + * RFC 4025 - codes for different types that IPSECKEY can hold. + */ +#define IPSECKEY_NOGATEWAY 0 +#define IPSECKEY_IP4 1 +#define IPSECKEY_IP6 2 +#define IPSECKEY_DNAME 3 + +#define LINEBUFSZ 1024 /*!< Buffer size for one line in zone file. */ + +struct lex_data { + size_t len; /*!< holds the label length */ + char *str; /*!< holds the data */ +}; + +#define DEFAULT_TTL 3600 + +int yylex_destroy(void *scanner); +int zp_parse(void *scanner); +void zp_set_in(FILE *f, void *scanner); +int zp_lex_init(void **scanner); +int zp_lex_destroy(void *scanner); + +/*! \todo Implement ZoneDB. */ +typedef void namedb_type; + +/*! + * \brief One-purpose linked list holding pointers to RRSets. + */ +struct rrset_list { + knot_rrset_t *data; /*!< List data. */ + struct rrset_list *next; /*!< Next node. */ +}; + +typedef struct rrset_list rrset_list_t; + +/*! + * \brief Main zoneparser structure. + */ +struct zparser { + const char *filename; /*!< File with zone. */ + uint32_t default_ttl; /*!< Default TTL. */ + uint16_t default_class; /*!< Default class. */ + knot_zone_t *current_zone; /*!< Current zone. */ + knot_node_t *origin; /*!< Origin node. */ + knot_dname_t *prev_dname; /*!< Previous dname. */ + knot_dname_t *origin_from_config; /*!< Zone origin from config. */ + knot_node_t *default_apex; /*!< Zone default apex. */ + + knot_node_t *last_node; /*!< Last processed node. */ + + char *dname_str; /*!< Temporary dname. */ + + int error_occurred; /*!< Error occured flag */ + unsigned int errors; /*!< Number of errors. */ + unsigned int line; /*!< Current line */ + + knot_rrset_t *current_rrset; /*!< Current RRSet. */ + knot_rdata_item_t *temporary_items; /*!< Temporary rdata items. */ + + /*! + * \brief list of RRSIGs that were not inside their nodes in zone file + */ + rrset_list_t *rrsig_orphans; + unsigned long rrsig_orphan_count; /*!< RRSIG orphan count */ + + knot_dname_t *root_domain; /*!< Root domain name. */ + slab_cache_t *parser_slab; /*!< Slab for parser. */ + rrset_list_t *node_rrsigs; /*!< List of RRSIGs in current node. */ + + int rdata_count; /*!< Count of parsed rdata. */ +}; + +typedef struct zparser zparser_type; + +extern zparser_type *parser; + +extern void zc_error_prev_line(const char *fmt, ...); + +/* used in zonec.lex */ + +void zc_error_prev_line(const char *fmt, ...); +void zc_warning_prev_line(const char *fmt, ...); + +/*! + * \brief Does all the processing of RR - saves to zone, assigns RRSIGs etc. + */ +int process_rr(); + +/*! + * \brief Parses and creates zone from given file. + * + * \param name Origin domain name string. + * \param zonefile File containing the zone. + * \param outfile File to save dump of the zone to. + * \param semantic_checks Enables or disables sematic checks. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int zone_read(const char *name, const char *zonefile, const char *outfile, + int semantic_checks); + +/*! + * \brief Creates zparser instance. + * + * + * \return Created zparser instance. + */ +zparser_type *zparser_create(); + +/*! + * \brief Inits zoneparser structure. + * + * \param filename Name of file with zone. + * \param ttl Default TTL. + * \param rclass Default class. + * \param origin Zone origin. + */ +void zparser_init(const char *filename, uint32_t ttl, uint16_t rclass, + knot_node_t *origin, knot_dname_t *owner_from_config); + +/*! + * \brief Frees zoneparser structure. + * + */ +void zparser_free(); + +int save_dnames_in_table(knot_dname_table_t *table, + knot_rrset_t *rrset); + +#endif /* _KNOTD_ZONEPARSER_H_ */ + +/*! @} */ diff --git a/src/zcompile/zcompile_main.c b/src/zcompile/zcompile_main.c new file mode 100644 index 0000000..cb862f8 --- /dev/null +++ b/src/zcompile/zcompile_main.c @@ -0,0 +1,116 @@ +/* Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <unistd.h> +#include <stdlib.h> + +#include "zcompile/zcompile.h" +#include "zcompile/zcompile-error.h" +#include "common/errors.h" +#include <config.h> + +static void help(int argc, char **argv) +{ + printf("Usage: %s [parameters] origin zonefile\n", + argv[0]); + printf("Parameters:\n" + " -o <outfile> Override output file.\n" + " -v Verbose mode - additional runtime information.\n" + " -s Enable semantic checks.\n" + " -V Print version of the server.\n" + " -h Print help and usage.\n"); +} + +int main(int argc, char **argv) +{ + // Parse command line arguments + int c = 0; + int verbose = 0; + int semantic_checks = 0; + const char* origin = 0; + const char* zonefile = 0; + const char* outfile = 0; + while ((c = getopt (argc, argv, "o:vVsh")) != -1) { + switch (c) + { + case 'o': + outfile = optarg; + break; + case 'v': + verbose = 1; + break; + case 'V': + printf("%s, version %s\n", "Knot DNS", PACKAGE_VERSION); + return 0; + case 's': + semantic_checks = 1; + break; + case 'h': + case '?': + default: + if (optopt == 'o') { + fprintf (stderr, + "Option -%c requires an argument.\n", + optopt); + } + help(argc, argv); + return 1; + } + } + + UNUSED(verbose); + + // Check if there's at least two remaining non-option + if (argc - optind < 2) { + help(argc, argv); + return 1; + } + + origin = argv[optind]; + zonefile = argv[optind + 1]; + + // Initialize log (no output) + //log_init(0); + //log_levels_set(LOGT_STDOUT, LOG_ANY, LOG_MASK(LOG_DEBUG)); + + printf("Parsing file '%s', origin '%s' ...\n", + zonefile, origin); + + parser = zparser_create(); + if (!parser) { + fprintf(stderr, "Failed to create parser.\n"); + //log_close(); + return 1; + } + + int error = zone_read(origin, zonefile, outfile, semantic_checks); + + if (error) { + /* FIXME! */ +// if (error < 0) { +// fprintf(stderr, "Finished with error: %s.\n", +// error_to_str(knot_zcompile_error_msgs, error)); +// } else { +// fprintf(stderr, "Finished with %u errors.\n"); +// } + } else { + printf("Compilation successful.\n"); + } + //log_close(); + + return error; +} diff --git a/src/zcompile/zlexer.l b/src/zcompile/zlexer.l new file mode 100644 index 0000000..c6c01fb --- /dev/null +++ b/src/zcompile/zlexer.l @@ -0,0 +1,531 @@ +%{ +/*! + * \file zlexer.l + * + * \author minor modifications by Jan Kadlec <jan.kadlec@nic.cz>, + * most of the code by NLnet Labs + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * \brief lexical analyzer for (DNS) zone files. + * + * \addtogroup zoneparser + * @{ + */ + +/* + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +//#include "common.h" + +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <strings.h> +#include <assert.h> + +#include "zcompile/zcompile.h" +#include "libknot/dname.h" +#include "zcompile/parser-descriptor.h" +#include "zparser.h" + +#define YY_NO_INPUT + +/* Utils */ +extern void zc_error(const char *fmt, ...); +extern void zc_warning(const char *fmt, ...); + +void strip_string(char *str) +{ + char *start = str; + char *end = str + strlen(str) - 1; + + while (isspace(*start)) + ++start; + if (start > end) { + /* Completely blank. */ + str[0] = '\0'; + } else { + while (isspace(*end)) + --end; + *++end = '\0'; + + if (str != start) + memmove(str, start, end - start + 1); + } +} + +int hexdigit_to_int(char ch) +{ + switch (ch) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + default: + abort(); + } +} + +extern uint32_t strtottl(const char *nptr, const char **endptr); + +#define YY_NO_UNPUT +#define MAXINCLUDES 10 + +#define scanner yyscanner +extern int zp_lex(YYSTYPE *lvalp, void *scanner); + +#if 0 +#define LEXOUT(s) printf s /* used ONLY when debugging */ +#else +#define LEXOUT(s) +#endif + +enum lexer_state { + EXPECT_OWNER, + PARSING_OWNER, + PARSING_TTL_CLASS_TYPE, + PARSING_RDATA +}; + +static YY_BUFFER_STATE include_stack[MAXINCLUDES]; +static zparser_type zparser_stack[MAXINCLUDES]; +static int include_stack_ptr = 0; + +static void pop_parser_state(void *scanner); +static void push_parser_state(FILE *input, void *scanner); +static int parse_token(void *scanner, int token, char *in_str, + enum lexer_state *lexer_state); + + +/*!< \todo does not compile */ +#ifndef yy_set_bol // compat definition, for flex 2.4.6 +#define yy_set_bol(at_bol) \ +{ \ + if (!yy_current_buffer ) \ + yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \ + yy_current_buffer->yy_ch_buf[0] = ((at_bol)?'\n':' '); \ +} +#endif + +%} + +%option nounput +%option reentrant bison-bridge +%option prefix = "zp_" +%option outfile = "lex.yy.c" + +SPACE [ \t] +LETTER [a-zA-Z] +NEWLINE [\n\r] +ZONESTR [^ \t\n\r();.\"\$] +DOLLAR \$ +COMMENT ; +DOT \. +BIT [^\]\n]|\\. +ANY [^\"\n\\]|\\. + +%x incl bitlabel quotedstring + +%% + static int paren_open = 0; + static enum lexer_state lexer_state = EXPECT_OWNER; + +{SPACE}*{COMMENT}.* /* ignore */ +^{DOLLAR}TTL { lexer_state = PARSING_RDATA; return DOLLAR_TTL; } +^{DOLLAR}ORIGIN { lexer_state = PARSING_RDATA; return DOLLAR_ORIGIN; } + + /* + * Handle $INCLUDE directives. See + * http://dinosaur.compilertools.net/flex/flex_12.html#SEC12. + */ +^{DOLLAR}INCLUDE { + BEGIN(incl); +} +<incl>\n | +<incl><<EOF>> { + int error_occurred = parser->error_occurred; + BEGIN(INITIAL); + zc_error("missing file name in $INCLUDE directive"); + yy_set_bol(1); /* Set beginning of line, so "^" rules match. */ + ++parser->line; + parser->error_occurred = error_occurred; +} +<incl>.+ { + char *tmp; + /*! \todo pointer to origin. */ + void *origin = parser->origin; + /* domain_type *origin = parser->origin; */ + int error_occurred = parser->error_occurred; + + BEGIN(INITIAL); + if (include_stack_ptr >= MAXINCLUDES ) { + zc_error("includes nested too deeply, skipped (>%d)", + MAXINCLUDES); + } else { + FILE *input; + + /* Remove trailing comment. */ + tmp = strrchr(yytext, ';'); + if (tmp) { + *tmp = '\0'; + } + strip_string(yytext); + + /* Parse origin for include file. */ + tmp = strrchr(yytext, ' '); + if (!tmp) { + tmp = strrchr(yytext, '\t'); + } + if (tmp) { + /* split the original yytext */ + *tmp = '\0'; + strip_string(yytext); + + /*! \todo knot_dname_new_from_wire() (dname.h) + * which knot_node to pass as node? + */ + knot_dname_t *dname; + dname = knot_dname_new_from_wire((uint8_t*)tmp + 1, + strlen(tmp + 1), + NULL); + if (!dname) { + zc_error("incorrect include origin '%s'", + tmp + 1); + } else { + /*! \todo insert to zonedb. */ + /* origin = domain_table_insert( + parser->db->domains, dname); */ + } + } + + if (strlen(yytext) == 0) { + zc_error("missing file name in $INCLUDE directive"); + } else if (!(input = fopen(yytext, "r"))) { + char ebuf[256]; + zc_error("cannot open include file '%s': %s", + yytext, strerror_r(errno, ebuf, sizeof(ebuf))); + } else { + /* Initialize parser for include file. */ + char *filename = strdup(yytext); + push_parser_state(input, scanner); /* Destroys yytext. */ + parser->filename = filename; + parser->line = 1; + parser->origin = origin; + lexer_state = EXPECT_OWNER; + } + } + + parser->error_occurred = error_occurred; +} +<INITIAL><<EOF>> { + yy_set_bol(1); /* Set beginning of line, so "^" rules match. */ + if (include_stack_ptr == 0) { + // from: http://stackoverflow.com/questions/1756275/bison-end-of-file + static int once = 0; + once++; + if (once > 1) { + yyterminate(); + } else { + return NL; + } + } else { + fclose(yyin); + pop_parser_state(scanner); + } +} +^{DOLLAR}{LETTER}+ { zc_warning("Unknown directive: %s", yytext); } +{DOT} { + LEXOUT((". ")); + return parse_token(scanner, '.', yytext, &lexer_state); +} +@ { + LEXOUT(("@ ")); + return parse_token(scanner, '@', yytext, &lexer_state); +} +\\# { + LEXOUT(("\\# ")); + return parse_token(scanner, URR, yytext, &lexer_state); +} +{NEWLINE} { + ++parser->line; + if (!paren_open) { + lexer_state = EXPECT_OWNER; + LEXOUT(("NL\n")); + return NL; + } else { + LEXOUT(("SP ")); + return SP; + } +} +\( { + if (paren_open) { + zc_error("nested parentheses"); + yyterminate(); + } + LEXOUT(("( ")); + paren_open = 1; + return SP; +} +\) { + if (!paren_open) { + zc_error("closing parentheses without opening parentheses"); + yyterminate(); + } + LEXOUT((") ")); + paren_open = 0; + return SP; +} +{SPACE}+ { + if (!paren_open && lexer_state == EXPECT_OWNER) { + lexer_state = PARSING_TTL_CLASS_TYPE; + LEXOUT(("PREV ")); + return PREV; + } + if (lexer_state == PARSING_OWNER) { + lexer_state = PARSING_TTL_CLASS_TYPE; + } + LEXOUT(("SP ")); + return SP; +} + + /* Bitlabels. Strip leading and ending brackets. */ +\\\[ { BEGIN(bitlabel); } +<bitlabel><<EOF>> { + zc_error("EOF inside bitlabel"); + BEGIN(INITIAL); +} +<bitlabel>{BIT}* { yymore(); } +<bitlabel>\n { ++parser->line; yymore(); } +<bitlabel>\] { + BEGIN(INITIAL); + yytext[yyleng - 1] = '\0'; + return parse_token(scanner, BITLAB, yytext, &lexer_state); +} + + /* Quoted strings. Strip leading and ending quotes. */ +\" { BEGIN(quotedstring); LEXOUT(("\" ")); } +<quotedstring><<EOF>> { + zc_error("EOF inside quoted string"); + BEGIN(INITIAL); +} +<quotedstring>{ANY}* { LEXOUT(("STR ")); yymore(); } +<quotedstring>\n { ++parser->line; yymore(); } +<quotedstring>\" { + LEXOUT(("\" ")); + BEGIN(INITIAL); + yytext[yyleng - 1] = '\0'; + return parse_token(scanner, STR, yytext, &lexer_state); +} + +({ZONESTR}|\\.|\\\n)+ { + /* Any allowed word. */ + return parse_token(scanner, STR, yytext, &lexer_state); +} +. { + zc_error("unknown character '%c' (\\%03d) seen - is this a zonefile?", + (int) yytext[0], (int) yytext[0]); +} +%% + +/* + * Analyze "word" to see if it matches an RR type, possibly by using + * the "TYPExxx" notation. If it matches, the corresponding token is + * returned and the TYPE parameter is set to the RR type value. + */ +static int +rrtype_to_token(const char *word, uint16_t *type) +{ + uint16_t t = parser_rrtype_from_string(word); + if (t != 0) { + parser_rrtype_descriptor_t *entry = 0; + entry = parser_rrtype_descriptor_by_type(t); + *type = t; + + /*! \todo entry should return associated token. + see nsd/dns.c */ + return entry->token; + } + + return 0; +} + + +/* + * Remove \DDD constructs from the input. See RFC 1035, section 5.1. + */ +static size_t +zoctet(char *text) +{ + /* + * s follows the string, p lags behind and rebuilds the new + * string + */ + char *s; + char *p; + + for (s = p = text; *s; ++s, ++p) { + assert(p <= s); + if (s[0] != '\\') { + /* Ordinary character. */ + *p = *s; + } else if (isdigit((int)s[1]) && isdigit((int)s[2]) && isdigit((int)s[3])) { + /* \DDD escape. */ + int val = (hexdigit_to_int(s[1]) * 100 + + hexdigit_to_int(s[2]) * 10 + + hexdigit_to_int(s[3])); + if (0 <= val && val <= 255) { + s += 3; + *p = val; + } else { + zc_warning("text escape \\DDD overflow"); + *p = *++s; + } + } else if (s[1] != '\0') { + /* \X where X is any character, keep X. */ + *p = *++s; + } else { + /* Trailing backslash, ignore it. */ + zc_warning("trailing backslash ignored"); + --p; + } + } + *p = '\0'; + return p - text; +} + +static int parse_token(void *scanner, int token, char *in_str, + enum lexer_state *lexer_state) +{ + size_t len = 0; + char *str = NULL; + + struct yyguts_t *yyg = (struct yyguts_t *)scanner; + + if (*lexer_state == EXPECT_OWNER) { + *lexer_state = PARSING_OWNER; + } else if (*lexer_state == PARSING_TTL_CLASS_TYPE) { + const char *t; + int token; + uint16_t rrclass; + + /* type */ + token = rrtype_to_token(in_str, &yylval->type); + if (token != 0) { + *lexer_state = PARSING_RDATA; + LEXOUT(("%d[%s] ", token, in_str)); + return token; + } + + /* class */ + rrclass = parser_rrclass_from_string(in_str); + if (rrclass != 0) { + yylval->rclass = rrclass; + LEXOUT(("CLASS ")); + return T_RRCLASS; + } + + /* ttl */ + yylval->ttl = strtottl(in_str, &t); + if (*t == '\0') { + LEXOUT(("TTL ")); + return T_TTL; + } + } + + str = strdup(yytext); + if (str == NULL) { + /* Out of memory */ + ERR_ALLOC_FAILED; + return NO_MEM; + } + len = zoctet(str); + + yylval->data.str = str; + assert(yylval->data.str != NULL); + yylval->data.len = len; + + if (strcmp(yytext, ".") == 0) { + free(str); + yylval->data.str="."; + } else if (strcmp(str, "@") == 0) { + free(str); + yylval->data.str="@"; + } else if (strcmp(str, "\\#") == 0) { + free(str); + yylval->data.str="\\#"; + } + + LEXOUT(("%d[%s] ", token, in_str)); + return token; +} + +/* + * Saves the file specific variables on the include stack. + */ +static void push_parser_state(FILE *input, void *scanner) +{ + struct yyguts_t *yyg = (struct yyguts_t *)scanner; + zparser_stack[include_stack_ptr].filename = parser->filename; + zparser_stack[include_stack_ptr].line = parser->line; + zparser_stack[include_stack_ptr].origin = parser->origin; + include_stack[include_stack_ptr] = YY_CURRENT_BUFFER; + yy_switch_to_buffer(yy_create_buffer(input, YY_BUF_SIZE, scanner), + scanner); + ++include_stack_ptr; +} + +/* + * Restores the file specific variables from the include stack. + */ +void pop_parser_state(void *scanner) +{ + struct yyguts_t *yyg = (struct yyguts_t *)scanner; + --include_stack_ptr; + parser->filename = zparser_stack[include_stack_ptr].filename; + parser->line = zparser_stack[include_stack_ptr].line; + parser->origin = zparser_stack[include_stack_ptr].origin; + yy_delete_buffer(YY_CURRENT_BUFFER, scanner); + yy_switch_to_buffer(include_stack[include_stack_ptr], scanner); +} diff --git a/src/zcompile/zparser.y b/src/zcompile/zparser.y new file mode 100644 index 0000000..6f081e5 --- /dev/null +++ b/src/zcompile/zparser.y @@ -0,0 +1,1730 @@ +%{ +/*! + * \file zparser.y + * + * \author modifications by Jan Kadlec <jan.kadlec@nic.cz>, + * notable changes: normal allocation, parser is reentrant. + * most of the code by NLnet Labs + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * \brief yacc grammar for (DNS) zone files + * + * \addtogroup zoneparser + * @{ + */ + +/* + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +//#include "common.h" + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include "zcompile/parser-util.h" + +#include "libknot/libknot.h" +#include "zcompile/zcompile.h" +#include "zcompile/parser-descriptor.h" +#include "zcompile/zcompile-error.h" +#include "zparser.h" + +/* these need to be global, otherwise they cannot be used inside yacc */ +zparser_type *parser; + +#ifdef __cplusplus +extern "C" +#endif /* __cplusplus */ +int zp_wrap(void); + +/* this hold the nxt bits */ +static uint8_t nxtbits[16]; +static int dlv_warn = 1; + +/* 256 windows of 256 bits (32 bytes) */ +/* still need to reset the bastard somewhere */ +static uint8_t nsecbits[NSEC_WINDOW_COUNT][NSEC_WINDOW_BITS_SIZE]; + +/* hold the highest rcode seen in a NSEC rdata , BUG #106 */ +uint16_t nsec_highest_rcode; + +void zp_error(void *scanner, const char *message); +int zp_lex(YYSTYPE *lvalp, void *scanner); + +/* helper functions */ +void zc_error(const char *fmt, ...); +void zc_warning(const char *fmt, ...); +void zc_error_prev_line(const char *fmt, ...); +void zc_warning_prev_line(const char *fmt, ...); + +#define NSEC3 +#ifdef NSEC3 +/* parse nsec3 parameters and add the (first) rdata elements */ +static void +nsec3_add_params(const char* hash_algo_str, const char* flag_str, + const char* iter_str, const char* salt_str, int salt_len); +#endif /* NSEC3 */ + +knot_dname_t *error_dname; //XXX used to be const +knot_dname_t *error_domain; + +%} +%union { + knot_dname_t *domain; + knot_dname_t *dname; + struct lex_data data; + uint32_t ttl; + uint16_t rclass; + uint16_t type; + uint16_t *unknown; +} + +%pure-parser +%parse-param {void *scanner} +%lex-param {void *scanner} +%name-prefix = "zp_" + +/* + * Tokens to represent the known RR types of DNS. + */ +%token <type> T_A T_NS T_MX T_TXT T_CNAME T_AAAA T_PTR T_NXT T_KEY T_SOA T_SIG +%token <type> T_SRV T_CERT T_LOC T_MD T_MF T_MB T_MG T_MR T_NULL T_WKS T_HINFO +%token <type> T_MINFO T_RP T_AFSDB T_X25 T_ISDN T_RT T_NSAP T_NSAP_PTR T_PX +%token <type> T_GPOS T_EID T_NIMLOC T_ATMA T_NAPTR T_KX T_A6 T_DNAME T_SINK +%token <type> T_OPT T_APL T_UINFO T_UID T_GID T_UNSPEC T_TKEY T_TSIG T_IXFR +%token <type> T_AXFR T_MAILB T_MAILA T_DS T_DLV T_SSHFP T_RRSIG T_NSEC T_DNSKEY +%token <type> T_SPF T_NSEC3 T_IPSECKEY T_DHCID T_NSEC3PARAM + +/* other tokens */ +%token DOLLAR_TTL DOLLAR_ORIGIN NL SP NO_MEM +%token <data> STR PREV BITLAB +%token <ttl> T_TTL +%token <rclass> T_RRCLASS + +/* unknown RRs */ +%token URR +%token <type> T_UTYPE + +%type <type> type_and_rdata +%type <domain> owner dname abs_dname +%type <dname> rel_dname label +%type <data> wire_dname wire_abs_dname wire_rel_dname wire_label +%type <data> concatenated_str_seq str_sp_seq str_dot_seq dotted_str +%type <data> nxt_seq nsec_more +%type <unknown> rdata_unknown + +%% +lines: /* empty file */ + | lines line + ; + +line: NL + | sp NL + | NO_MEM { + zc_error_prev_line("Parser ran out of memory!"); + YYABORT; + } + | PREV NL {} /* Lines containing only whitespace. */ + | ttl_directive + { + parser->error_occurred = 0; + } + | origin_directive + { + parser->error_occurred = 0; + } + | rr + { /* rr should be fully parsed */ + if (!parser->error_occurred) { + /*!< \todo assign error to error occurred */ + /*! \todo Make sure this does not crash */ + if (parser->current_rrset->owner == NULL) { + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + knot_rdata_t *tmp_rdata = knot_rdata_new(); + if (tmp_rdata == NULL) { + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + + if (knot_rdata_set_items(tmp_rdata, + parser->temporary_items, + parser->rdata_count) != 0) { + knot_rdata_free(&tmp_rdata); + knot_rrset_deep_free(&(parser->current_rrset), 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), 1); + YYABORT; + } + + assert(parser->current_rrset->rdata == NULL); + if (knot_rrset_add_rdata(parser->current_rrset, tmp_rdata) + != 0) { + fprintf(stderr, "Could not add rdata!\n"); + } +// tmp_rdata->next = tmp_rdata; +// parser->current_rrset->rdata = tmp_rdata; + + if (!knot_dname_is_fqdn(parser->current_rrset->owner)) { + knot_dname_t *tmp_dname = + knot_dname_cat(parser->current_rrset->owner, + parser->root_domain); + if (tmp_dname == NULL) { + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } +// knot_rrset_set_owner(parser->current_rrset, tmp_dname); + } + + assert(parser->current_rrset->owner != NULL); + knot_dname_retain(parser->current_rrset->owner); + int ret = 0; + if ((ret = process_rr()) != 0) { + char *name = + knot_dname_to_str(parser->current_rrset->owner); + fprintf(stderr, "Error: could not process RRSet\n" + "owner: %s reason: %s\n", + name, + error_to_str(knot_zcompile_error_msgs, ret)); + free(name); + + /* If the owner is not already in the table, free it. */ +// if (dnslib_dname_table_find_dname(parser->dname_table, +// parser->current_rrset->owner) == NULL) { +// dnslib_dname_free(&parser-> +// current_rrset->owner); +// } /* This would never happen */ + + if (ret == KNOTDZCOMPILE_EBADSOA) { + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } else { + YYABORT; + /* Free rdata, it will not be added + * and hence cannot be + * freed with rest of the zone. */ +/* knot_rdata_deep_free(&tmp_rdata, + parser-> + current_rrset->type, + 0); */ + } + } + } else { + /* Error occured. This could either be lack of memory, or one + * of the converting function was not able to convert. */ + if (parser->error_occurred == KNOTDZCOMPILE_ENOMEM) { + /* Ran out of memory in converting functions. */ + fprintf(stderr, "Parser ran out " + "of memory, aborting!\n"); + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + } + +// printf("Current rrset name: %p (%s)\n", parser->current_rrset->owner->name, +// knot_dname_to_str(parser->current_rrset->owner)); +// getchar(); + +// knot_dname_release(parser->current_rrset->owner); + + parser->current_rrset->type = 0; + parser->rdata_count = 0; + parser->current_rrset->rdata = NULL; + parser->error_occurred = 0; + } + | error NL + ; + +/* needed to cope with ( and ) in arbitary places */ +sp: SP + | sp SP + ; + +trail: NL + | sp NL + ; + +ttl_directive: DOLLAR_TTL sp STR trail + { + parser->default_ttl = zparser_ttl2int($3.str, + &(parser->error_occurred)); + if (parser->error_occurred == 1) { + parser->default_ttl = DEFAULT_TTL; + parser->error_occurred = 0; + } + + free($3.str); + } + ; + + + +origin_directive: DOLLAR_ORIGIN sp abs_dname trail + { + knot_node_t *origin_node = knot_node_new($3 ,NULL, 0); + if (parser->origin != NULL) { +// knot_node_free(&parser->origin, 1); + } + parser->origin = origin_node; + } + | DOLLAR_ORIGIN sp rel_dname trail + { + zc_error_prev_line("$ORIGIN directive requires" + "absolute domain name"); + } + ; + +rr: owner classttl type_and_rdata + { + /* Save the pointer, it might get freed! */ + parser->current_rrset->owner = $1; +// parser->current_rrset->owner = $1; +// printf("new owner assigned: %p\n", $1); + parser->current_rrset->type = $3; + } + ; + +owner: dname sp + { +// char *name = knot_dname_to_str($1); +// printf("Totally new dname: %p %s\n", $1, +// name); +// free(name); + if (parser->prev_dname != NULL) { + // knot_dname_release(parser->prev_dname); + } + parser->prev_dname = $1;//knot_dname_deep_copy($1); +// knot_dname_retain(parser->prev_dname); + $$ = $1; + } + | PREV + { +// printf("Name from prev_dname!: %p %s\n", parser->prev_dname, +// knot_dname_to_str(parser->prev_dname)); + knot_dname_retain(parser->prev_dname); + $$ = parser->prev_dname;//knot_dname_deep_copy(parser->prev_dname); + } + ; + +classttl: /* empty - fill in the default, def. ttl and IN class */ + { + parser->current_rrset->ttl = parser->default_ttl; + parser->current_rrset->rclass = parser->default_class; + } + | T_RRCLASS sp /* no ttl */ + { + parser->current_rrset->ttl = parser->default_ttl; + parser->current_rrset->rclass = $1; + } + | T_TTL sp /* no class */ + { + parser->current_rrset->ttl = $1; + parser->current_rrset->rclass = parser->default_class; + } + | T_TTL sp T_RRCLASS sp /* the lot */ + { + parser->current_rrset->ttl = $1; + parser->current_rrset->rclass = $3; + } + | T_RRCLASS sp T_TTL sp /* the lot - reversed */ + { + parser->current_rrset->ttl = $3; + parser->current_rrset->rclass = $1; + } + ; + +dname: abs_dname + | rel_dname + { + if ($1 == error_dname) { + $$ = error_domain; + } else if ($1->size + parser->origin->owner->size - 1 > + MAXDOMAINLEN) { + zc_error("domain name exceeds %d character limit", + MAXDOMAINLEN); + $$ = error_domain; + } else { + $$ = knot_dname_cat($1, + parser->origin->owner); +// printf("leak: %s\n", knot_dname_to_str($$)); +// getchar(); + } + } + ; + +abs_dname: '.' + { + $$ = parser->root_domain; + /* TODO how about concatenation now? */ + } + | '@' + { + $$ = parser->origin->owner; + } + | rel_dname '.' + { + if ($1 != error_dname) { + $$ = $1; + } else { + $$ = error_domain; + } + } + ; + +label: STR + { + if ($1.len > MAXLABELLEN) { + zc_error("label exceeds %d character limit", MAXLABELLEN); + $$ = error_dname; + } else { +// printf("%s\n", $1.str); + $$ = knot_dname_new_from_str($1.str, $1.len, NULL); +// printf("Creating new (label): %s %p\n", $1.str, $$); +// printf("new: %p %s\n", $$, $1.str); + $$->ref.count = 0; + } + + free($1.str); + + } + | BITLAB + { + zc_error("bitlabels are not supported." + "RFC2673 has status experimental."); + $$ = error_dname; + } + ; + +rel_dname: label + | rel_dname '.' label + { + if ($1 == error_dname || $3 == error_dname) { + $$ = error_dname; + } else if ($1->size + $3->size - 1 > MAXDOMAINLEN) { + zc_error("domain name exceeds %d character limit", + MAXDOMAINLEN); + $$ = error_dname; + } else { + $$ = knot_dname_cat($1, $3); +// knot_dname_release($1); /*!< \todo check! */ + knot_dname_free(&$3); + } + } + ; + +/* + * Some dnames in rdata are handled as opaque blobs + */ + +wire_dname: wire_abs_dname + | wire_rel_dname + ; + +wire_abs_dname: '.' + { + char *result = malloc(2 * sizeof(char)); + if (result == NULL) { + ERR_ALLOC_FAILED; + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + result[0] = 0; + result[1] = '\0'; + $$.str = result; + $$.len = 1; + } + | wire_rel_dname '.' + { + char *result = malloc($1.len + 2 * sizeof(char)); + if (result == NULL) { + ERR_ALLOC_FAILED; + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + memcpy(result, $1.str, $1.len); + result[$1.len] = 0; + result[$1.len+1] = '\0'; + $$.str = result; + $$.len = $1.len + 1; + + free($1.str); +; + } + ; + +wire_label: STR + { + char *result = malloc($1.len + sizeof(char)); + if (result == NULL) { + ERR_ALLOC_FAILED; + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + + if ($1.len > MAXLABELLEN) + zc_error("label exceeds %d character limit", MAXLABELLEN); + + /* make label anyway */ + result[0] = $1.len; + memcpy(result+1, $1.str, $1.len); + + $$.str = result; + $$.len = $1.len + 1; + + free($1.str); + } + ; + +wire_rel_dname: wire_label + | wire_rel_dname '.' wire_label + { + if ($1.len + $3.len - 3 > MAXDOMAINLEN) + zc_error("domain name exceeds %d character limit", + MAXDOMAINLEN); + + /* make dname anyway */ + $$.len = $1.len + $3.len; + $$.str = malloc($$.len + sizeof(char)); + if ($$.str == NULL) { + ERR_ALLOC_FAILED; + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + memcpy($$.str, $1.str, $1.len); + memcpy($$.str + $1.len, $3.str, $3.len); + $$.str[$$.len] = '\0'; + + free($1.str); + free($3.str); + } + ; + + + +str_seq: STR + { + zadd_rdata_txt_wireformat(zparser_conv_text($1.str, $1.len), 1); + + free($1.str); + } + | str_seq sp STR + { + zadd_rdata_txt_wireformat(zparser_conv_text($3.str, $3.len), 0); +// zc_warning("multiple TXT entries are currently not supported!"); + + free($3.str); + } + ; + +/* + * Generate a single string from multiple STR tokens, separated by + * spaces or dots. + */ +concatenated_str_seq: STR + | '.' + { + $$.len = 1; + $$.str = strdup("."); + } + | concatenated_str_seq sp STR + { + $$.len = $1.len + $3.len + 1; + $$.str = malloc($$.len + 1); + if ($$.str == NULL) { + ERR_ALLOC_FAILED; + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + + memcpy($$.str, $1.str, $1.len); + memcpy($$.str + $1.len, " ", 1); + memcpy($$.str + $1.len + 1, $3.str, $3.len); + $$.str[$$.len] = '\0'; + + free($1.str); + free($3.str); + } + | concatenated_str_seq '.' STR + { + $$.len = $1.len + $3.len + 1; + $$.str = malloc($$.len + 1); + if ($$.str == NULL) { + ERR_ALLOC_FAILED; + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + memcpy($$.str, $1.str, $1.len); + memcpy($$.str + $1.len, ".", 1); + memcpy($$.str + $1.len + 1, $3.str, $3.len); + + free($1.str); + free($3.str); + + $$.str[$$.len] = '\0'; + } + ; + +/* used to convert a nxt list of types */ +nxt_seq: STR + { + uint16_t type = knot_rrtype_from_string($1.str); + if (type != 0 && type < 128) { + set_bit(nxtbits, type); + } else { + zc_error("bad type %d in NXT record", (int) type); + } + + free($1.str); + } + | nxt_seq sp STR + { + uint16_t type = knot_rrtype_from_string($3.str); + if (type != 0 && type < 128) { + set_bit(nxtbits, type); + } else { + zc_error("bad type %d in NXT record", (int) type); + } + + free($3.str); + } + ; + +nsec_more: SP nsec_more + { + } + | NL + { + } + | STR nsec_seq + { + uint16_t type = knot_rrtype_from_string($1.str); + if (type != 0) { + if (type > nsec_highest_rcode) { + nsec_highest_rcode = type; + } + set_bitnsec(nsecbits, type); + } else { + zc_error("bad type %d in NSEC record", (int) type); + } + + free($1.str); + } + ; + +nsec_seq: NL + | SP nsec_more + ; + +/* + * Sequence of STR tokens separated by spaces. The spaces are not + * preserved during concatenation. + */ +str_sp_seq: STR + | str_sp_seq sp STR + { + char *result = malloc($1.len + $3.len + 1); + if (result == NULL) { + ERR_ALLOC_FAILED; + fprintf(stderr, "Parser ran out of memory, aborting!\n"); + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + memcpy(result, $1.str, $1.len); + memcpy(result + $1.len, $3.str, $3.len); + $$.str = result; + $$.len = $1.len + $3.len; + $$.str[$$.len] = '\0'; + + free($1.str); + free($3.str); + } + ; + +/* + * Sequence of STR tokens separated by dots. The dots are not + * preserved during concatenation. + */ +str_dot_seq: STR + | str_dot_seq '.' STR + { + char *result = malloc($1.len + $3.len + 1); + if (result == NULL) { + ERR_ALLOC_FAILED; + fprintf(stderr, "Parser ran out of memory, aborting!\n"); + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + memcpy(result, $1.str, $1.len); + memcpy(result + $1.len, $3.str, $3.len); + $$.str = result; + $$.len = $1.len + $3.len; + $$.str[$$.len] = '\0'; + + free($1.str); + free($3.str); + } + ; + +/* + * A string that can contain dots. + */ +dotted_str: STR + | '.' + { + $$.str = "."; + $$.len = 1; + } + | dotted_str '.' + { + char *result = malloc($1.len + 2); + if (result == NULL) { + ERR_ALLOC_FAILED; + fprintf(stderr, "Parser ran out of memory, aborting!\n"); + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + memcpy(result, $1.str, $1.len); + result[$1.len] = '.'; + $$.str = result; + $$.len = $1.len + 1; + $$.str[$$.len] = '\0'; + + free($1.str); + } + | dotted_str '.' STR + { + char *result = malloc($1.len + $3.len + 2); + if (result == NULL) { + ERR_ALLOC_FAILED; + fprintf(stderr, "Parser ran out of memory, aborting!\n"); + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + memcpy(result, $1.str, $1.len); + result[$1.len] = '.'; + memcpy(result + $1.len + 1, $3.str, $3.len); + $$.str = result; + $$.len = $1.len + $3.len + 1; + $$.str[$$.len] = '\0'; + + + free($1.str); + free($3.str); + } + ; + +/* define what we can parse */ +type_and_rdata: + /* + * All supported RR types. We don't support NULL and types marked obsolete. + */ + T_A sp rdata_a + | T_A sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_NS sp rdata_domain_name + | T_NS sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_MD sp rdata_domain_name { zc_warning_prev_line("MD is obsolete"); } + | T_MD sp rdata_unknown + { + zc_warning_prev_line("MD is obsolete"); + $$ = $1; parse_unknown_rdata($1, $3); + } + | T_MF sp rdata_domain_name { zc_warning_prev_line("MF is obsolete"); } + | T_MF sp rdata_unknown + { + zc_warning_prev_line("MF is obsolete"); + $$ = $1; + parse_unknown_rdata($1, $3); + } + | T_CNAME sp rdata_domain_name + | T_CNAME sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_SOA sp rdata_soa + | T_SOA sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_MB sp rdata_domain_name { zc_warning_prev_line("MB is obsolete"); } + | T_MB sp rdata_unknown + { + zc_warning_prev_line("MB is obsolete"); + $$ = $1; + parse_unknown_rdata($1, $3); + } + | T_MG sp rdata_domain_name + | T_MG sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_MR sp rdata_domain_name + | T_MR sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + /* NULL */ + | T_WKS sp rdata_wks + | T_WKS sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_PTR sp rdata_domain_name + | T_PTR sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_HINFO sp rdata_hinfo + | T_HINFO sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_MINFO sp rdata_minfo /* Experimental */ + | T_MINFO sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_MX sp rdata_mx + | T_MX sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_TXT sp rdata_txt + | T_TXT sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_SPF sp rdata_txt + | T_SPF sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_RP sp rdata_rp /* RFC 1183 */ + | T_RP sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_AFSDB sp rdata_afsdb /* RFC 1183 */ + | T_AFSDB sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_X25 sp rdata_x25 /* RFC 1183 */ + | T_X25 sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_ISDN sp rdata_isdn /* RFC 1183 */ + | T_ISDN sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_IPSECKEY sp rdata_ipseckey /* RFC 4025 */ + | T_IPSECKEY sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_DHCID sp rdata_dhcid + | T_DHCID sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_RT sp rdata_rt /* RFC 1183 */ + | T_RT sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_NSAP sp rdata_nsap /* RFC 1706 */ + | T_NSAP sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_SIG sp rdata_rrsig + | T_SIG sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_KEY sp rdata_dnskey + | T_KEY sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_PX sp rdata_px /* RFC 2163 */ + | T_PX sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_AAAA sp rdata_aaaa + | T_AAAA sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_LOC sp rdata_loc + | T_LOC sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_NXT sp rdata_nxt + | T_NXT sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_SRV sp rdata_srv + | T_SRV sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_NAPTR sp rdata_naptr /* RFC 2915 */ + | T_NAPTR sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_KX sp rdata_kx /* RFC 2230 */ + | T_KX sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_CERT sp rdata_cert /* RFC 2538 */ + | T_CERT sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_DNAME sp rdata_domain_name /* RFC 2672 */ + | T_DNAME sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_APL trail /* RFC 3123 */ + | T_APL sp rdata_apl /* RFC 3123 */ + | T_APL sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_DS sp rdata_ds + | T_DS sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_DLV sp rdata_dlv + { + if (dlv_warn) { + dlv_warn = 0; + zc_warning_prev_line("DLV is experimental"); + } + } + | T_DLV sp rdata_unknown + { + if (dlv_warn) { + dlv_warn = 0; + zc_warning_prev_line("DLV is experimental"); + } + $$ = $1; + parse_unknown_rdata($1, $3); + } + | T_SSHFP sp rdata_sshfp + | T_SSHFP sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_RRSIG sp rdata_rrsig + | T_RRSIG sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_NSEC sp rdata_nsec + | T_NSEC sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_NSEC3 sp rdata_nsec3 + | T_NSEC3 sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_NSEC3PARAM sp rdata_nsec3_param + | T_NSEC3PARAM sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_DNSKEY sp rdata_dnskey + | T_DNSKEY sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + | T_UTYPE sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); } + + | STR error NL + { + zc_error_prev_line("unrecognized RR type '%s'", $1.str); + free($1.str); + } + | NO_MEM + { + zc_error_prev_line("parser ran out of memory!"); + YYABORT; + } + ; + +/* + * + * below are all the definition for all the different rdata + * + */ + +rdata_a: dotted_str trail + { + zadd_rdata_wireformat(zparser_conv_a($1.str)); + free($1.str); + } + ; + +rdata_domain_name: dname trail + { + /* convert a single dname record */ + if ($1 != NULL) { + if (!knot_dname_is_fqdn($1)) { + knot_dname_cat($1, parser->root_domain); +// parser->current_rrset->owner = +// knot_dname_cat($1, parser->root_domain); + } + } + zadd_rdata_domain($1); + } + ; + +rdata_soa: dname sp dname sp STR sp STR sp STR sp STR sp STR trail + { + /* convert the soa data */ + if (!knot_dname_is_fqdn($1)) { + knot_dname_cat($1, parser->root_domain); +// parser->current_rrset->owner = +// knot_dname_cat($1, parser->root_domain); + + } + if (!knot_dname_is_fqdn($3)) { + knot_dname_cat($3, parser->root_domain); +// parser->current_rrset->owner = +// knot_dname_cat($3, parser->root_domain); + + } + zadd_rdata_domain($1); /* prim. ns */ + zadd_rdata_domain($3); /* email */ + zadd_rdata_wireformat(zparser_conv_serial($5.str)); /* serial */ + zadd_rdata_wireformat(zparser_conv_period($7.str)); /* refresh */ + zadd_rdata_wireformat(zparser_conv_period($9.str)); /* retry */ + zadd_rdata_wireformat(zparser_conv_period($11.str)); /* expire */ + zadd_rdata_wireformat(zparser_conv_period($13.str)); /* minimum */ + + free($5.str); + free($7.str); + free($9.str); + free($11.str); + free($13.str); + } + ; + +rdata_wks: dotted_str sp STR sp concatenated_str_seq trail + { + zadd_rdata_wireformat(zparser_conv_a($1.str)); /* address */ + zadd_rdata_wireformat(zparser_conv_services($3.str, $5.str)); + /* protocol and services */ + + free($1.str); + free($3.str); + free($5.str); + } + ; + +rdata_hinfo: STR sp STR trail + { + zadd_rdata_wireformat(zparser_conv_text($1.str, $1.len)); /* CPU */ + zadd_rdata_wireformat(zparser_conv_text($3.str, $3.len)); /* OS*/ + + free($1.str); + free($3.str); + } + ; + +rdata_minfo: dname sp dname trail + { + if (!knot_dname_is_fqdn($1)) { + + knot_dname_cat($1, parser->root_domain); + + } + if (!knot_dname_is_fqdn($3)) { + + knot_dname_cat($3, parser->root_domain); + + } + + /* convert a single dname record */ + zadd_rdata_domain($1); + zadd_rdata_domain($3); + } + ; + +rdata_mx: STR sp dname trail + { + if (!knot_dname_is_fqdn($3)) { + knot_dname_cat($3, parser->root_domain); + } + + zadd_rdata_wireformat(zparser_conv_short($1.str)); /* priority */ + zadd_rdata_domain($3); /* MX host */ + + free($1.str); + } + ; + +rdata_txt: str_seq trail + { + ; //zadd_rdata_txt_clean_wireformat(); + } + ; + +/* RFC 1183 */ +rdata_rp: dname sp dname trail + { + if (!knot_dname_is_fqdn($1)) { + knot_dname_cat($1, parser->root_domain); + } + if (!knot_dname_is_fqdn($3)) { + knot_dname_cat($3, parser->root_domain); + } + + zadd_rdata_domain($1); /* mbox d-name */ + zadd_rdata_domain($3); /* txt d-name */ + } + ; + +/* RFC 1183 */ +rdata_afsdb: STR sp dname trail + { + if (!knot_dname_is_fqdn($3)) { + knot_dname_cat($3, parser->root_domain); + } + + zadd_rdata_wireformat(zparser_conv_short($1.str)); /* subtype */ + zadd_rdata_domain($3); /* domain name */ + + free($1.str); + } + ; + +/* RFC 1183 */ +rdata_x25: STR trail + { + zadd_rdata_wireformat(zparser_conv_text($1.str, $1.len)); + /* X.25 address. */ + + free($1.str); + } + ; + +/* RFC 1183 */ +rdata_isdn: STR trail + { + zadd_rdata_wireformat(zparser_conv_text($1.str, $1.len)); + /* address */ + + free($1.str); + } + | STR sp STR trail + { + zadd_rdata_wireformat(zparser_conv_text($1.str, $1.len)); + /* address */ + zadd_rdata_wireformat(zparser_conv_text($3.str, $3.len)); + /* sub-address */ + + free($1.str); + free($3.str); + } + ; + +/* RFC 1183 */ +rdata_rt: STR sp dname trail + { + if (!knot_dname_is_fqdn($3)) { + knot_dname_cat($3, parser->root_domain); + } + + zadd_rdata_wireformat(zparser_conv_short($1.str)); /* preference */ + zadd_rdata_domain($3); /* intermediate host */ + + free($1.str); + } + ; + +/* RFC 1706 */ +rdata_nsap: str_dot_seq trail + { + /* String must start with "0x" or "0X". */ + if (strncasecmp($1.str, "0x", 2) != 0) { + zc_error_prev_line("NSAP rdata must start with '0x'"); + } else { + zadd_rdata_wireformat(zparser_conv_hex($1.str + 2, + $1.len - 2)); + /* NSAP */ + } + + free($1.str); + } + ; + +/* RFC 2163 */ +rdata_px: STR sp dname sp dname trail + { + if (!knot_dname_is_fqdn($3)) { + knot_dname_cat($3, parser->root_domain); + } + if (!knot_dname_is_fqdn($5)) { + knot_dname_cat($5, parser->root_domain); + } + zadd_rdata_wireformat(zparser_conv_short($1.str)); /* preference */ + zadd_rdata_domain($3); /* MAP822 */ + zadd_rdata_domain($5); /* MAPX400 */ + + free($1.str); + } + ; + +rdata_aaaa: dotted_str trail + { + zadd_rdata_wireformat(zparser_conv_aaaa($1.str)); + /* IPv6 address */ + + free($1.str); + } + ; + +rdata_loc: concatenated_str_seq trail + { + zadd_rdata_wireformat(zparser_conv_loc($1.str)); /* Location */ + + free($1.str); + } + ; + +rdata_nxt: dname sp nxt_seq trail + { + if (!knot_dname_is_fqdn($1)) { + knot_dname_cat($1, parser->root_domain); + } + zadd_rdata_domain($1); /* nxt name */ + zadd_rdata_wireformat(zparser_conv_nxt(nxtbits)); /* nxt bitlist */ + memset(nxtbits, 0, sizeof(nxtbits)); + } + ; + +rdata_srv: STR sp STR sp STR sp dname trail + { + if (!knot_dname_is_fqdn($7)) { + knot_dname_cat($7, parser->root_domain); + + } + zadd_rdata_wireformat(zparser_conv_short($1.str)); /* prio */ + zadd_rdata_wireformat(zparser_conv_short($3.str)); /* weight */ + zadd_rdata_wireformat(zparser_conv_short($5.str)); /* port */ + zadd_rdata_domain($7); /* target name */ + + free($1.str); + free($3.str); + free($5.str); + } + ; + +/* RFC 2915 */ +rdata_naptr: STR sp STR sp STR sp STR sp STR sp dname trail + { + if (!knot_dname_is_fqdn($11)) { + knot_dname_cat($11, parser->root_domain); + + } + zadd_rdata_wireformat(zparser_conv_short($1.str)); /* order */ + zadd_rdata_wireformat(zparser_conv_short($3.str)); /* preference */ + zadd_rdata_wireformat(zparser_conv_text($5.str, $5.len)); + /* flags */ + zadd_rdata_wireformat(zparser_conv_text($7.str, $7.len)); + /* service */ + zadd_rdata_wireformat(zparser_conv_text($9.str, $9.len)); + /* regexp */ + zadd_rdata_domain($11); /* target name */ + + free($1.str); + free($3.str); + free($5.str); + free($7.str); + free($9.str); + } + ; + +/* RFC 2230 */ +rdata_kx: STR sp dname trail + { + if (!knot_dname_is_fqdn($3)) { + knot_dname_cat($3, parser->root_domain); + } + zadd_rdata_wireformat(zparser_conv_short($1.str)); /* preference */ + zadd_rdata_domain($3); /* exchanger */ + + free($1.str); + } + ; + +/* RFC 2538 */ +rdata_cert: STR sp STR sp STR sp str_sp_seq trail + { + zadd_rdata_wireformat(zparser_conv_certificate_type($1.str)); + /* type */ + zadd_rdata_wireformat(zparser_conv_short($3.str)); /* key tag */ + zadd_rdata_wireformat(zparser_conv_algorithm($5.str)); + /* algorithm */ + zadd_rdata_wireformat(zparser_conv_b64($7.str)); + /* certificate or CRL */ + + free($1.str); + free($3.str); + free($5.str); + free($7.str); + } + ; + +/* RFC 3123 */ +rdata_apl: rdata_apl_seq trail + ; + +rdata_apl_seq: dotted_str + { + zadd_rdata_wireformat(zparser_conv_apl_rdata($1.str)); + + free($1.str); + } + | rdata_apl_seq sp dotted_str + { + zadd_rdata_wireformat(zparser_conv_apl_rdata($3.str)); + + free($3.str); + } + ; + +rdata_ds: STR sp STR sp STR sp str_sp_seq trail + { + zadd_rdata_wireformat(zparser_conv_short($1.str)); /* keytag */ + zadd_rdata_wireformat(zparser_conv_algorithm($3.str)); /* alg */ + zadd_rdata_wireformat(zparser_conv_byte($5.str)); /* type */ + zadd_rdata_wireformat(zparser_conv_hex($7.str, $7.len)); /* hash */ + + free($1.str); + free($3.str); + free($5.str); + free($7.str); + } + ; + +rdata_dlv: STR sp STR sp STR sp str_sp_seq trail + { + zadd_rdata_wireformat(zparser_conv_short($1.str)); /* keytag */ + zadd_rdata_wireformat(zparser_conv_algorithm($3.str)); /* alg */ + zadd_rdata_wireformat(zparser_conv_byte($5.str)); /* type */ + zadd_rdata_wireformat(zparser_conv_hex($7.str, $7.len)); /* hash */ + + free($1.str); + free($3.str); + free($5.str); + free($7.str); + } + ; + +rdata_sshfp: STR sp STR sp str_sp_seq trail + { + zadd_rdata_wireformat(zparser_conv_byte($1.str)); /* alg */ + zadd_rdata_wireformat(zparser_conv_byte($3.str)); /* fp type */ + zadd_rdata_wireformat(zparser_conv_hex($5.str, $5.len)); /* hash */ + + free($1.str); + free($3.str); + free($5.str); + } + ; + +rdata_dhcid: str_sp_seq trail + { + zadd_rdata_wireformat(zparser_conv_b64($1.str)); /* data blob */ + + free($1.str); + } + ; + +rdata_rrsig: STR sp STR sp STR sp STR sp STR sp STR + sp STR sp wire_dname sp str_sp_seq trail + { + zadd_rdata_wireformat(zparser_conv_rrtype($1.str)); + /* rr covered */ + zadd_rdata_wireformat(zparser_conv_algorithm($3.str)); /* alg */ + zadd_rdata_wireformat(zparser_conv_byte($5.str)); /* # labels */ + zadd_rdata_wireformat(zparser_conv_period($7.str)); + /* # orig TTL */ + zadd_rdata_wireformat(zparser_conv_time($9.str)); /* sig exp */ + zadd_rdata_wireformat(zparser_conv_time($11.str)); /* sig inc */ + zadd_rdata_wireformat(zparser_conv_short($13.str)); /* key id */ +/* zadd_rdata_wireformat(zparser_conv_dns_name((const uint8_t*) + $15.str, + $15.len));*/ + knot_dname_t *dname = + knot_dname_new_from_wire((uint8_t *)$15.str, $15.len, NULL); + if (dname == NULL) { + parser->error_occurred = KNOTDZCOMPILE_ENOMEM; + } else { + knot_dname_cat(dname, parser->root_domain); + } + + zadd_rdata_domain(dname); + /* sig name */ + zadd_rdata_wireformat(zparser_conv_b64($17.str)); /* sig data */ + + free($1.str); + free($3.str); + free($5.str); + free($7.str); + free($9.str); + free($11.str); + free($13.str); + free($15.str); + free($17.str); + } + ; + +rdata_nsec: wire_dname nsec_seq + { +/* zadd_rdata_wireformat(zparser_conv_dns_name((const uint8_t*) + $1.str, + $1.len));*/ + + knot_dname_t *dname = + knot_dname_new_from_wire((uint8_t *)$1.str, $1.len, NULL); + + free($1.str); + + knot_dname_cat(dname, parser->root_domain); + + zadd_rdata_domain(dname); + /* nsec name */ + zadd_rdata_wireformat(zparser_conv_nsec(nsecbits)); + /* nsec bitlist */ + memset(nsecbits, 0, sizeof(nsecbits)); + nsec_highest_rcode = 0; + } + ; + +rdata_nsec3: STR sp STR sp STR sp STR sp STR nsec_seq + { +#ifdef NSEC3 + nsec3_add_params($1.str, $3.str, $5.str, $7.str, $7.len); + +/* knot_dname_t *dname = + knot_dname_new_from_str($9.str, $9.len, NULL); + + zadd_rdata_domain(dname); */ + + zadd_rdata_wireformat(zparser_conv_b32($9.str)); + /* next hashed name */ + zadd_rdata_wireformat(zparser_conv_nsec(nsecbits)); + /* nsec bitlist */ + memset(nsecbits, 0, sizeof(nsecbits)); + nsec_highest_rcode = 0; +#else + zc_error_prev_line("nsec3 not supported"); +#endif /* NSEC3 */ + + free($1.str); + free($3.str); + free($5.str); + free($7.str); + free($9.str); + } + ; + +rdata_nsec3_param: STR sp STR sp STR sp STR trail + { +#ifdef NSEC3 + nsec3_add_params($1.str, $3.str, $5.str, $7.str, $7.len); +#else + zc_error_prev_line("nsec3 not supported"); +#endif /* NSEC3 */ + + free($1.str); + free($3.str); + free($5.str); + free($7.str); + } + ; + +rdata_dnskey: STR sp STR sp STR sp str_sp_seq trail + { + zadd_rdata_wireformat(zparser_conv_short($1.str)); /* flags */ + zadd_rdata_wireformat(zparser_conv_byte($3.str)); /* proto */ + zadd_rdata_wireformat(zparser_conv_algorithm($5.str)); /* alg */ + zadd_rdata_wireformat(zparser_conv_b64($7.str)); /* hash */ + + free($1.str); + free($3.str); + free($5.str); + free($7.str); + } + ; + +rdata_ipsec_base: STR sp STR sp STR sp dotted_str + { + knot_dname_t* name = 0; + zadd_rdata_wireformat(zparser_conv_byte($1.str)); /* precedence */ + zadd_rdata_wireformat(zparser_conv_byte($3.str)); + /* gateway type */ + zadd_rdata_wireformat(zparser_conv_byte($5.str)); /* algorithm */ + switch(atoi($3.str)) { + case IPSECKEY_NOGATEWAY: + zadd_rdata_wireformat(alloc_rdata_init("", 0)); + break; + case IPSECKEY_IP4: + zadd_rdata_wireformat(zparser_conv_a($7.str)); + break; + case IPSECKEY_IP6: + zadd_rdata_wireformat(zparser_conv_aaaa($7.str)); + break; + case IPSECKEY_DNAME: + /* convert and insert the dname */ + if(strlen($7.str) == 0) + zc_error_prev_line("IPSECKEY must specify" + "gateway name"); + name = knot_dname_new_from_wire((uint8_t*)$7.str + 1, + strlen($7.str + 1), + NULL); + if(!name) { + zc_error_prev_line("IPSECKEY bad gateway" + "dname %s", $7.str); + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + if($7.str[strlen($7.str)-1] != '.') { + knot_dname_t* tmpd = + knot_dname_new_from_wire(name->name, + name->size, + NULL); + if (tmpd == NULL) { + zc_error_prev_line("Could not create dname!"); + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + name = knot_dname_cat(tmpd, + knot_node_parent(parser->origin, 0)->owner); + } + + free($1.str); + free($3.str); + free($5.str); + free($7.str); + + uint8_t* dncpy = malloc(sizeof(uint8_t) * name->size); + if (dncpy == NULL) { + ERR_ALLOC_FAILED; + knot_rrset_deep_free(&(parser->current_rrset), + 0, 0, 0); + knot_zone_deep_free(&(parser->current_zone), + 1); + YYABORT; + } + memcpy(dncpy, name->name, name->size); + zadd_rdata_wireformat((uint16_t *)dncpy); + //knot_dname_free(&name); + break; + default: + zc_error_prev_line("unknown IPSECKEY gateway type"); + } + } + ; + +rdata_ipseckey: rdata_ipsec_base sp str_sp_seq trail + { + zadd_rdata_wireformat(zparser_conv_b64($3.str)); /* public key */ + + free($3.str); + } + | rdata_ipsec_base trail + ; + +rdata_unknown: URR sp STR sp str_sp_seq trail + { + /* $2 is the number of octects, currently ignored */ + $$ = zparser_conv_hex($5.str, $5.len); + free($5.str); + free($3.str); + } + | URR sp STR trail + { + $$ = zparser_conv_hex("", 0); + free($3.str); + } + | URR error NL + { + $$ = zparser_conv_hex("", 0); + } + ; +%% + +int zp_wrap(void) +{ + return 1; +} + +/* + * Create the parser. + */ +zparser_type *zparser_create() +{ + zparser_type *result = malloc(sizeof(zparser_type)); + if (result == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + result->temporary_items = malloc(MAXRDATALEN * + sizeof(knot_rdata_item_t)); + if (result->temporary_items == NULL) { + ERR_ALLOC_FAILED; + free(result); + return NULL; + } + + result->current_rrset = knot_rrset_new(NULL, 0, 0, 0); + if (result->current_rrset == NULL) { + ERR_ALLOC_FAILED; + free(result->temporary_items); + free(result); + return NULL; + } + + result->root_domain = knot_dname_new_from_str(".", 1, NULL); +// printf("THE NEW ROOT: %p\n", result->root_domain); + if (result->root_domain == NULL) { + ERR_ALLOC_FAILED; + free(result->temporary_items); + free(result->current_rrset); + free(result); + return NULL; + } + + return result; +} + +/* + * Initialize the parser for a new zone file. + */ +void +zparser_init(const char *filename, uint32_t ttl, uint16_t rclass, + knot_node_t *origin, knot_dname_t *origin_from_config) +{ + memset(nxtbits, 0, sizeof(nxtbits)); + memset(nsecbits, 0, sizeof(nsecbits)); + nsec_highest_rcode = 0; + + parser->current_zone = NULL; + parser->prev_dname = NULL; + + parser->default_ttl = ttl; + parser->default_class = rclass; + + parser->origin = origin; + parser->prev_dname = NULL;//parser->origin->owner; + + parser->default_apex = origin; + parser->error_occurred = 0; + parser->errors = 0; + parser->line = 1; + parser->filename = filename; + parser->rdata_count = 0; + parser->origin_from_config = origin_from_config; + + parser->last_node = origin; +// parser->root_domain = NULL; + + /* Create zone */ + parser->current_zone = knot_zone_new(origin, 0, 1); + + parser->node_rrsigs = NULL; + parser->rrsig_orphans = NULL; + parser->rrsig_orphan_count = 0; + + parser->current_rrset->rclass = parser->default_class; + parser->current_rrset->rdata = NULL; +} + + +void zparser_free() +{ +// knot_dname_release(parser->root_domain); +// knot_dname_release(parser->prev_dname); + knot_dname_free(&parser->origin_from_config); + free(parser->temporary_items); + if (parser->current_rrset != NULL) { + free(parser->current_rrset); + } + free(parser); +} + +void +yyerror(void *scanner, const char *message) +{ + zc_error("%s", message); +} + +static void +error_va_list(unsigned line, const char *fmt, va_list args) +{ + if (parser->filename) { + fprintf(stderr, "%s:%u: ", parser->filename, line); + } + fprintf(stderr, "error: "); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + + ++parser->errors; + parser->error_occurred = 1; +} + +/* the line counting sux, to say the least + * with this grose hack we try do give sane + * numbers back */ +void +zc_error_prev_line(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + error_va_list(parser->line - 1, fmt, args); + va_end(args); +} + +void +zc_error(const char *fmt, ...) +{ + /* send an error message to stderr */ + va_list args; + va_start(args, fmt); + error_va_list(parser->line, fmt, args); + va_end(args); +} + +static void +warning_va_list(unsigned line, const char *fmt, va_list args) +{ + if (parser->filename) { + fprintf(stderr, "%s:%u: ", parser->filename, line); + } + fprintf(stderr, "warning: "); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); +} + +void +zc_warning_prev_line(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + warning_va_list(parser->line - 1, fmt, args); + va_end(args); +} + +void +zc_warning(const char *fmt, ... ) +{ + va_list args; + va_start(args, fmt); + warning_va_list(parser->line, fmt, args); + va_end(args); +} + +#ifdef NSEC3 +static void +nsec3_add_params(const char* hashalgo_str, const char* flag_str, + const char* iter_str, const char* salt_str, int salt_len) +{ + zadd_rdata_wireformat(zparser_conv_byte(hashalgo_str)); + zadd_rdata_wireformat(zparser_conv_byte(flag_str)); + zadd_rdata_wireformat(zparser_conv_short(iter_str)); + + /* salt */ + if(strcmp(salt_str, "-") != 0) + zadd_rdata_wireformat(zparser_conv_hex_length(salt_str, + salt_len)); + else + zadd_rdata_wireformat(alloc_rdata_init("", 1)); + +} +#endif /* NSEC3 */ |