/* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at https://mozilla.org/MPL/2.0/. * * See the COPYRIGHT file distributed with this work for additional * information regarding copyright ownership. */ #pragma once #include #include #include #include #include #include ISC_LANG_BEGINDECLS /*! \file dns/compress.h * Direct manipulation of the structures is strongly discouraged. * * A name compression context handles compression of multiple DNS names in * relation to a single DNS message. The context can be used to selectively * turn on/off compression for specific names (depending on the RR type, * according to RFC 3597) by using \c dns_compress_setpermitted(). * * The nameserver can be configured not to use compression at all using * \c dns_compress_disable(). * * DNS name compression only needs exact matches on (suffixes of) names. We * could use a data structure that supports longest-match lookups, but that * would introduce a lot of heavyweight machinery, and all we need is * something that exists very briefly to store a few names before it is * thrown away. * * In the abstract we need a map from DNS names to compression offsets. But * a compression offset refers to a point in the message where the name has * been written. So in fact all we need is a hash set of compression offsets. * * Typical messages do not contain more than a few dozen names, so by * default our hash set is small (64 entries, 256 bytes). It can be * enlarged when a message is likely to contain a lot of names, such as for * outgoing zone transfers (which are handled in lib/ns/xfrout.c) and * update requests (for which nsupdate uses DNS_REQUESTOPT_LARGE - see * request.h). */ /* * Logarithms of hash set sizes. In the usual (small) case, allow for a * few dozen names in the hash set. (We can't actually use every slot because * space is reserved for performance reasons.) For large messages, the number * of names is limited by the minimum size of an RR (owner, type, class, ttl, * length) which is 12 bytes - call it 16 bytes to make space for a new label. * Divide the maximum compression offset 0x4000 by 16 and you get 0x400 == 1024. * In practice, the root zone (for example) uses less than 200 distinct names * per message. */ enum { DNS_COMPRESS_SMALLBITS = 6, DNS_COMPRESS_LARGEBITS = 10, }; /* * Compression context flags */ enum dns_compress_flags { /* affecting the whole message */ DNS_COMPRESS_DISABLED = 0x00000001U, DNS_COMPRESS_CASE = 0x00000002U, DNS_COMPRESS_LARGE = 0x00000004U, /* can toggle while rendering a message */ DNS_COMPRESS_PERMITTED = 0x00000008U, }; /* * The hash may be any 16 bit value. Unused slots have coff == 0. (Valid * compression offsets cannot be zero because of the DNS message header.) */ struct dns_compress_slot { uint16_t hash; uint16_t coff; }; struct dns_compress { unsigned int magic; dns_compress_flags_t flags; uint16_t mask; uint16_t count; isc_mem_t *mctx; dns_compress_slot_t *set; dns_compress_slot_t smallset[1 << DNS_COMPRESS_SMALLBITS]; }; /* * Deompression context */ enum dns_decompress { DNS_DECOMPRESS_DEFAULT, DNS_DECOMPRESS_PERMITTED, DNS_DECOMPRESS_NEVER, DNS_DECOMPRESS_ALWAYS, }; void dns_compress_init(dns_compress_t *cctx, isc_mem_t *mctx, dns_compress_flags_t flags); /*%< * Initialise the compression context structure pointed to by * 'cctx'. * * The `flags` argument is usually zero; or some combination of: *\li DNS_COMPRESS_DISABLED, so the whole message is uncompressed *\li DNS_COMPRESS_CASE, for case-sensitive compression *\li DNS_COMPRESS_LARGE, for messages with many names * * (See also dns_request_create()'s options argument) * * Requires: *\li 'cctx' is a dns_compress_t structure on the stack. *\li 'mctx' is an initialized memory context. * Ensures: *\li 'cctx' is initialized. *\li 'dns_compress_getpermitted(cctx)' is true */ void dns_compress_invalidate(dns_compress_t *cctx); /*%< * Invalidate the compression structure pointed to by * 'cctx', freeing any memory that has been allocated. * * Requires: *\li 'cctx' is an initialized dns_compress_t */ void dns_compress_setpermitted(dns_compress_t *cctx, bool permitted); /*%< * Sets whether compression is allowed, according to RFC 3597. * This can vary depending on the rdata type. * * Requires: *\li 'cctx' to be initialized. */ bool dns_compress_getpermitted(dns_compress_t *cctx); /*%< * Find out whether compression is allowed, according to RFC 3597. * * Requires: *\li 'cctx' to be initialized. * * Returns: *\li allowed compression bitmap. */ void dns_compress_name(dns_compress_t *cctx, isc_buffer_t *buffer, const dns_name_t *name, unsigned int *return_prefix, unsigned int *return_coff); /*%< * Finds longest suffix matching 'name' in the compression table, * and adds any remaining prefix of 'name' to the table. * * This is used by dns_name_towire() for both compressed and uncompressed * names; for uncompressed names, dns_name_towire() does not need to know * about the matching suffix, but it still needs to add the name for use * by later compression pointers. For example, an owner name of a record * in the additional section will often need to refer back to an RFC 3597 * uncompressed name in the rdata of a record in the answer section. * * Requires: *\li 'cctx' to be initialized. *\li 'buffer' contains the rendered message. *\li 'name' to be a absolute name. *\li 'return_prefix' points to an unsigned int. *\li 'return_coff' points to an unsigned int, which must be zero. * * Ensures: *\li When no suffix is found, the return variables * 'return_prefix' and 'return_coff' are unchanged * *\li Otherwise, '*return_prefix' is set to the length of the * prefix of the name that did not match, and '*suffix_coff' * is set to a nonzero compression offset of the match. */ void dns_compress_rollback(dns_compress_t *cctx, unsigned int offset); /*%< * Remove any compression pointers from the table that are >= offset. * * Requires: *\li 'cctx' is initialized. */ /*% * Set whether decompression is allowed, according to RFC 3597 */ static inline dns_decompress_t /* inline to suppress code generation */ dns_decompress_setpermitted(dns_decompress_t dctx, bool permitted) { if (dctx == DNS_DECOMPRESS_NEVER || dctx == DNS_DECOMPRESS_ALWAYS) { return dctx; } else if (permitted) { return DNS_DECOMPRESS_PERMITTED; } else { return DNS_DECOMPRESS_DEFAULT; } } /*% * Returns whether decompression is allowed here */ static inline bool /* inline to suppress code generation */ dns_decompress_getpermitted(dns_decompress_t dctx) { return dctx == DNS_DECOMPRESS_ALWAYS || dctx == DNS_DECOMPRESS_PERMITTED; } ISC_LANG_ENDDECLS