Home | History | Annotate | Line # | Download | only in dns
      1 /*	$NetBSD: compress.h,v 1.9 2026/06/19 20:10:01 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5  *
      6  * SPDX-License-Identifier: MPL-2.0
      7  *
      8  * This Source Code Form is subject to the terms of the Mozilla Public
      9  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11  *
     12  * See the COPYRIGHT file distributed with this work for additional
     13  * information regarding copyright ownership.
     14  */
     15 
     16 #pragma once
     17 
     18 #include <inttypes.h>
     19 #include <stdbool.h>
     20 
     21 #include <isc/lang.h>
     22 #include <isc/region.h>
     23 
     24 #include <dns/name.h>
     25 #include <dns/types.h>
     26 
     27 ISC_LANG_BEGINDECLS
     28 
     29 /*! \file dns/compress.h
     30  * Direct manipulation of the structures is strongly discouraged.
     31  *
     32  * A name compression context handles compression of multiple DNS names in
     33  * relation to a single DNS message. The context can be used to selectively
     34  * turn on/off compression for specific names (depending on the RR type,
     35  * according to RFC 3597) by using \c dns_compress_setpermitted().
     36  *
     37  * The nameserver can be configured not to use compression at all using
     38  * \c dns_compress_disable().
     39  *
     40  * DNS name compression only needs exact matches on (suffixes of) names. We
     41  * could use a data structure that supports longest-match lookups, but that
     42  * would introduce a lot of heavyweight machinery, and all we need is
     43  * something that exists very briefly to store a few names before it is
     44  * thrown away.
     45  *
     46  * In the abstract we need a map from DNS names to compression offsets. But
     47  * a compression offset refers to a point in the message where the name has
     48  * been written. So in fact all we need is a hash set of compression offsets.
     49  *
     50  * Typical messages do not contain more than a few dozen names, so by
     51  * default our hash set is small (64 entries, 256 bytes). It can be
     52  * enlarged when a message is likely to contain a lot of names, such as for
     53  * outgoing zone transfers (which are handled in lib/ns/xfrout.c) and
     54  * update requests (for which nsupdate uses DNS_REQUESTOPT_LARGE - see
     55  * request.h).
     56  */
     57 
     58 /*
     59  * Logarithms of hash set sizes. In the usual (small) case, allow for a
     60  * few dozen names in the hash set. (We can't actually use every slot because
     61  * space is reserved for performance reasons.) For large messages, the number
     62  * of names is limited by the minimum size of an RR (owner, type, class, ttl,
     63  * length) which is 12 bytes - call it 16 bytes to make space for a new label.
     64  * Divide the maximum compression offset 0x4000 by 16 and you get 0x400 == 1024.
     65  * In practice, the root zone (for example) uses less than 200 distinct names
     66  * per message.
     67  */
     68 enum {
     69 	DNS_COMPRESS_SMALLBITS = 6,
     70 	DNS_COMPRESS_LARGEBITS = 10,
     71 };
     72 
     73 /*
     74  * Compression context flags
     75  */
     76 enum dns_compress_flags {
     77 	/* affecting the whole message */
     78 	DNS_COMPRESS_DISABLED = 0x00000001U,
     79 	DNS_COMPRESS_CASE = 0x00000002U,
     80 	DNS_COMPRESS_LARGE = 0x00000004U,
     81 	/* can toggle while rendering a message */
     82 	DNS_COMPRESS_PERMITTED = 0x00000008U,
     83 };
     84 
     85 /*
     86  * The hash may be any 16 bit value. Unused slots have coff == 0. (Valid
     87  * compression offsets cannot be zero because of the DNS message header.)
     88  */
     89 struct dns_compress_slot {
     90 	uint16_t hash;
     91 	uint16_t coff;
     92 };
     93 
     94 struct dns_compress {
     95 	unsigned int	     magic;
     96 	dns_compress_flags_t flags;
     97 	uint16_t	     mask;
     98 	uint16_t	     count;
     99 	isc_mem_t	    *mctx;
    100 	dns_compress_slot_t *set;
    101 	dns_compress_slot_t  smallset[1 << DNS_COMPRESS_SMALLBITS];
    102 };
    103 
    104 /*
    105  * Deompression context
    106  */
    107 enum dns_decompress {
    108 	DNS_DECOMPRESS_DEFAULT,
    109 	DNS_DECOMPRESS_PERMITTED,
    110 	DNS_DECOMPRESS_NEVER,
    111 	DNS_DECOMPRESS_ALWAYS,
    112 };
    113 
    114 void
    115 dns_compress_init(dns_compress_t *cctx, isc_mem_t *mctx,
    116 		  dns_compress_flags_t flags);
    117 /*%<
    118  *	Initialise the compression context structure pointed to by
    119  *	'cctx'.
    120  *
    121  *	The `flags` argument is usually zero; or some combination of:
    122  *\li		DNS_COMPRESS_DISABLED, so the whole message is uncompressed
    123  *\li		DNS_COMPRESS_CASE, for case-sensitive compression
    124  *\li		DNS_COMPRESS_LARGE, for messages with many names
    125  *
    126  *	(See also dns_request_create()'s options argument)
    127  *
    128  *	Requires:
    129  *\li		'cctx' is a dns_compress_t structure on the stack.
    130  *\li		'mctx' is an initialized memory context.
    131  *	Ensures:
    132  *\li		'cctx' is initialized.
    133  *\li		'dns_compress_getpermitted(cctx)' is true
    134  */
    135 
    136 void
    137 dns_compress_invalidate(dns_compress_t *cctx);
    138 
    139 /*%<
    140  *	Invalidate the compression structure pointed to by
    141  *	'cctx', freeing any memory that has been allocated.
    142  *
    143  *	Requires:
    144  *\li		'cctx' is an initialized dns_compress_t
    145  */
    146 
    147 void
    148 dns_compress_setpermitted(dns_compress_t *cctx, bool permitted);
    149 
    150 /*%<
    151  *	Sets whether compression is allowed, according to RFC 3597.
    152  *	This can vary depending on the rdata type.
    153  *
    154  *	Requires:
    155  *\li		'cctx' to be initialized.
    156  */
    157 
    158 bool
    159 dns_compress_getpermitted(dns_compress_t *cctx);
    160 
    161 /*%<
    162  *	Find out whether compression is allowed, according to RFC 3597.
    163  *
    164  *	Requires:
    165  *\li		'cctx' to be initialized.
    166  *
    167  *	Returns:
    168  *\li		allowed compression bitmap.
    169  */
    170 
    171 void
    172 dns_compress_name(dns_compress_t *cctx, isc_buffer_t *buffer,
    173 		  const dns_name_t *name, unsigned int *return_prefix,
    174 		  unsigned int *return_coff);
    175 /*%<
    176  *	Finds longest suffix matching 'name' in the compression table,
    177  *	and adds any remaining prefix of 'name' to the table.
    178  *
    179  *	This is used by dns_name_towire() for both compressed and uncompressed
    180  *	names; for uncompressed names, dns_name_towire() does not need to know
    181  *	about the matching suffix, but it still needs to add the name for use
    182  *	by later compression pointers. For example, an owner name of a record
    183  *	in the additional section will often need to refer back to an RFC 3597
    184  *	uncompressed name in the rdata of a record in the answer section.
    185  *
    186  *	Requires:
    187  *\li		'cctx' to be initialized.
    188  *\li		'buffer' contains the rendered message.
    189  *\li		'name' to be a absolute name.
    190  *\li		'return_prefix' points to an unsigned int.
    191  *\li		'return_coff' points to an unsigned int, which must be zero.
    192  *
    193  *	Ensures:
    194  *\li		When no suffix is found, the return variables
    195  *              'return_prefix' and 'return_coff' are unchanged
    196  *
    197  *\li		Otherwise, '*return_prefix' is set to the length of the
    198  *		prefix of the name that did not match, and '*suffix_coff'
    199  *		is set to a nonzero compression offset of the match.
    200  */
    201 
    202 void
    203 dns_compress_rollback(dns_compress_t *cctx, unsigned int offset);
    204 /*%<
    205  *	Remove any compression pointers from the table that are >= offset.
    206  *
    207  *	Requires:
    208  *\li		'cctx' is initialized.
    209  */
    210 
    211 /*%
    212  *	Set whether decompression is allowed, according to RFC 3597
    213  */
    214 static inline dns_decompress_t /* inline to suppress code generation */
    215 dns_decompress_setpermitted(dns_decompress_t dctx, bool permitted) {
    216 	if (dctx == DNS_DECOMPRESS_NEVER || dctx == DNS_DECOMPRESS_ALWAYS) {
    217 		return dctx;
    218 	} else if (permitted) {
    219 		return DNS_DECOMPRESS_PERMITTED;
    220 	} else {
    221 		return DNS_DECOMPRESS_DEFAULT;
    222 	}
    223 }
    224 
    225 /*%
    226  *	Returns whether decompression is allowed here
    227  */
    228 static inline bool /* inline to suppress code generation */
    229 dns_decompress_getpermitted(dns_decompress_t dctx) {
    230 	return dctx == DNS_DECOMPRESS_ALWAYS ||
    231 	       dctx == DNS_DECOMPRESS_PERMITTED;
    232 }
    233 
    234 ISC_LANG_ENDDECLS
    235