resolved: add "static RRs" concept (#41213)

split out of #40980
This commit is contained in:
Lennart Poettering
2026-03-24 22:45:22 +01:00
committed by GitHub
22 changed files with 921 additions and 7 deletions
+23 -1
View File
@@ -363,12 +363,33 @@ DNSStubListenerExtra=udp:[2001:db8:0:f102::13]:9953</programlisting>
<term><varname>ReadEtcHosts=</varname></term>
<listitem><para>Takes a boolean argument. If <literal>yes</literal> (the default),
<command>systemd-resolved</command> will read <filename>/etc/hosts</filename>, and try to resolve
hosts or address by using the entries in the file before sending query to DNS servers.
hosts or addresses by using the entries in the file before sending query to DNS servers.
</para>
<xi:include href="version-info.xml" xpointer="v240"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>ReadStaticRecords=</varname></term>
<listitem><para>Takes a boolean argument. If <literal>yes</literal> (the default),
<command>systemd-resolved</command> will read
<filename>/etc/systemd/resolve/static.d/*.rr</filename>,
<filename>/run/systemd/resolve/static.d/*.rr</filename>,
<filename>/usr/local/lib/systemd/resolve/static.d/*.rr</filename>,
<filename>/usr/lib/systemd/resolve/static.d/*.rr</filename>, and try to resolve lookups by using the
entries in these files before sending query to DNS servers. This functionality is very similar to the
one controlled by <varname>ReadEtcHosts=</varname>, but allows more flexible control of DNS resource
records fields beyond just A/AAAA/PTR. See
<citerefentry><refentrytitle>systemd.rr</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details.</para>
<para>If both this option and <varname>ReadEtcHosts=</varname> are enabled then this mechanism takes
precedence: any records discovered via static resource records will take precedence over records
under the same name from <filename>/etc/hosts</filename>.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>ResolveUnicastSingleLabel=</varname></term>
<listitem><para>Takes a boolean argument. When false (the default),
@@ -418,6 +439,7 @@ DNSStubListenerExtra=udp:[2001:db8:0:f102::13]:9953</programlisting>
<member><citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>dnssec-trust-anchors.d</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.rr</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry project='man-pages'><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
+1
View File
@@ -1280,6 +1280,7 @@ manpages = [
['systemd.pcrlock', '5', ['systemd.pcrlock.d'], ''],
['systemd.preset', '5', [], ''],
['systemd.resource-control', '5', [], ''],
['systemd.rr', '5', [], 'ENABLE_RESOLVE'],
['systemd.scope', '5', [], ''],
['systemd.service', '5', [], ''],
['systemd.slice', '5', [], ''],
+11
View File
@@ -131,6 +131,16 @@
<listitem><para>The hostname <literal>_localdnsproxy</literal> is resolved to the IP address 127.0.0.54,
i.e. the address the local DNS proxy (see above) is listening on.</para></listitem>
<listitem><para>The files matching <filename>/etc/systemd/resolve/static.d/*.rr</filename>,
<filename>/run/systemd/resolve/static.d/*.rr</filename>,
<filename>/usr/local/lib/systemd/resolve/static.d/*.rr</filename>,
<filename>/usr/lib/systemd/resolve/static.d/*.rr</filename> may be used to define arbitrary
records. See
<citerefentry><refentrytitle>systemd.rr</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details. Support for this may be disabled with <varname>ReadStaticRecords=no</varname>, see
<citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para></listitem>
<listitem><para>The mappings defined in <filename>/etc/hosts</filename> are resolved to their
configured addresses and back, but they will not affect lookups for non-address types (like MX).
Support for <filename>/etc/hosts</filename> may be disabled with <varname>ReadEtcHosts=no</varname>,
@@ -510,6 +520,7 @@ search foobar.com barbar.com
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.dns-delegate</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.rr</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.dnssd</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>dnssec-trust-anchors.d</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
+95
View File
@@ -0,0 +1,95 @@
<?xml version='1.0'?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd.rr"
xmlns:xi="http://www.w3.org/2001/XInclude"
conditional='ENABLE_RESOLVE'>
<refentryinfo>
<title>systemd.rr</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd.rr</refentrytitle>
<manvolnum>5</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd.rr</refname>
<refpurpose>Local static DNS resource record definitions</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><simplelist>
<member><filename>/etc/systemd/resolve/static.d/*.rr</filename></member>
<member><filename>/run/systemd/resolve/static.d/*.rr</filename></member>
<member><filename>/usr/local/lib/systemd/resolve/static.d/*.rr</filename></member>
<member><filename>/usr/lib/systemd/resolve/static.d/*.rr</filename></member>
</simplelist></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>*.rr</filename> files may be used to define resource record sets ("RRsets") that shall be
resolvable locally, similar in style to address records defined by <filename>/etc/hosts</filename> (see
<citerefentry><refentrytitle>hosts</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details). These files are read by
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
and are used to synthesize local responses to local queries matching the defined resource record set.</para>
<para>These drop-in files are in JSON format. Each file may either contain a single top-level DNS RR
object, or an array of one or more DNS RR objects. Each RR object has at least a <literal>key</literal>
subobject consisting of a <literal>name</literal> string field and a <literal>type</literal> integer
field (which contains the RR type in numeric form). Depending on the chosen type the RR object also has
the following fields:</para>
<itemizedlist>
<listitem><para>For A/AAAA RRs, the RR object should have an <literal>address</literal> field set to
either an IP address formatted as string, or an array consisting of 4 or 16 8-bit unsigned integers for
the IP address.</para></listitem>
<listitem><para>For PTR/NS/CNAME/DNAME RRs, the RR object should have a <literal>name</literal> field
set to the name the record shall point to.</para></listitem>
</itemizedlist>
<para>This JSON serialization of DNS RRs matches the one returned by <command>resolvectl</command>.</para>
<para>Currently no other RR types are supported.</para>
</refsect1>
<refsect1>
<title>Examples</title>
<example>
<title>Simple A Record</title>
<para>To make local address lookups for <literal>foobar.example.com</literal> resolve to the
192.168.100.1 IPv4 address, create
<filename>/run/systemd/resolve/static.d/foobar_example_com.rr</filename>:
<programlisting>
{
"key" : {
"type" : 1,
"name" : "foobar.example.com"
},
"address" : [ 192, 168, 100, 1 ]
}</programlisting></para>
</example>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>hosts</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>resolvectl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
+62 -1
View File
@@ -709,6 +709,10 @@ nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) {
void inode_hash_func(const struct stat *q, struct siphash *state) {
siphash24_compress_typesafe(q->st_dev, state);
siphash24_compress_typesafe(q->st_ino, state);
/* Also include inode type, to mirror stat_inode_same() */
mode_t type = q->st_mode & S_IFMT;
siphash24_compress_typesafe(type, state);
}
int inode_compare_func(const struct stat *a, const struct stat *b) {
@@ -718,11 +722,68 @@ int inode_compare_func(const struct stat *a, const struct stat *b) {
if (r != 0)
return r;
return CMP(a->st_ino, b->st_ino);
r = CMP(a->st_ino, b->st_ino);
if (r != 0)
return r;
return CMP(a->st_mode & S_IFMT, b->st_mode & S_IFMT);
}
DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
void inode_unmodified_hash_func(const struct stat *q, struct siphash *state) {
inode_hash_func(q, state);
siphash24_compress_typesafe(q->st_mtim.tv_sec, state);
siphash24_compress_typesafe(q->st_mtim.tv_nsec, state);
if (S_ISREG(q->st_mode))
siphash24_compress_typesafe(q->st_size, state);
else {
uint64_t invalid = UINT64_MAX;
siphash24_compress_typesafe(invalid, state);
}
if (S_ISCHR(q->st_mode) || S_ISBLK(q->st_mode))
siphash24_compress_typesafe(q->st_rdev, state);
else {
dev_t invalid = (dev_t) -1;
siphash24_compress_typesafe(invalid, state);
}
}
int inode_unmodified_compare_func(const struct stat *a, const struct stat *b) {
int r;
r = inode_compare_func(a, b);
if (r != 0)
return r;
r = CMP(a->st_mtim.tv_sec, b->st_mtim.tv_sec);
if (r != 0)
return r;
r = CMP(a->st_mtim.tv_nsec, b->st_mtim.tv_nsec);
if (r != 0)
return r;
if (S_ISREG(a->st_mode)) {
r = CMP(a->st_size, b->st_size);
if (r != 0)
return r;
}
if (S_ISCHR(a->st_mode) || S_ISBLK(a->st_mode)) {
r = CMP(a->st_rdev, b->st_rdev);
if (r != 0)
return r;
}
return 0;
}
DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_unmodified_hash_ops, struct stat, inode_unmodified_hash_func, inode_unmodified_compare_func, free);
const char* inode_type_to_string(mode_t m) {
/* Returns a short string for the inode type. We use the same name as the underlying macros for each
+7
View File
@@ -122,10 +122,17 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret);
usec_t statx_timestamp_load(const struct statx_timestamp *ts) _pure_;
nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) _pure_;
/* This compares inode number, backing device and inode type, but not modification info */
void inode_hash_func(const struct stat *q, struct siphash *state);
int inode_compare_func(const struct stat *a, const struct stat *b);
extern const struct hash_ops inode_hash_ops;
/* This is a more thorough version of the above, and also checks the mtimes, the size, and the rdev. It does
* not check "external" attributes such as access mode or ownership. */
void inode_unmodified_hash_func(const struct stat *q, struct siphash *state);
int inode_unmodified_compare_func(const struct stat *a, const struct stat *b);
extern const struct hash_ops inode_unmodified_hash_ops;
DECLARE_STRING_TABLE_LOOKUP(inode_type, mode_t);
/* Macros that check whether the stat/statx structures have been initialized already. For "struct stat" we
+49 -1
View File
@@ -9,6 +9,7 @@
#include "errno-util.h"
#include "fd-util.h"
#include "glyph-util.h"
#include "in-addr-util.h"
#include "iovec-util.h"
#include "json-util.h"
#include "log.h"
@@ -189,12 +190,59 @@ int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_di
return 0;
}
/* We support a more human readable string based encoding, and an array based encoding */
if (sd_json_variant_is_string(variant)) {
union in_addr_union a;
r = in_addr_from_string(AF_INET, sd_json_variant_string(variant), &a);
if (r < 0)
return json_log(variant, flags, r,
"JSON field '%s' is not a valid IPv4 address string: %s", strna(name), sd_json_variant_string(variant));
*address = a.in;
return 0;
}
r = json_dispatch_byte_array_iovec(name, variant, flags, &iov);
if (r < 0)
return r;
if (iov.iov_len != sizeof(struct in_addr))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name));
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
"Expected JSON field '%s' to be an array of %zu bytes.", strna(name), sizeof(struct in_addr));
memcpy(address, iov.iov_base, iov.iov_len);
return 0;
}
int json_dispatch_in6_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
struct in6_addr *address = ASSERT_PTR(userdata);
_cleanup_(iovec_done) struct iovec iov = {};
int r;
if (sd_json_variant_is_null(variant)) {
*address = (struct in6_addr) {};
return 0;
}
/* We support both a more human readable string based encoding and an array based encoding */
if (sd_json_variant_is_string(variant)) {
union in_addr_union a;
r = in_addr_from_string(AF_INET6, sd_json_variant_string(variant), &a);
if (r < 0)
return json_log(variant, flags, r,
"JSON field '%s' is not a valid IPv6 address string: %s", strna(name), sd_json_variant_string(variant));
*address = a.in6;
return 0;
}
r = json_dispatch_byte_array_iovec(name, variant, flags, &iov);
if (r < 0)
return r;
if (iov.iov_len != sizeof(struct in6_addr))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
"Expected JSON field '%s' to be an array of %zu bytes.", strna(name), sizeof(struct in6_addr));
memcpy(address, iov.iov_base, iov.iov_len);
return 0;
+1
View File
@@ -115,6 +115,7 @@ int json_dispatch_user_group_name(const char *name, sd_json_variant *variant, sd
int json_dispatch_const_user_group_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
int json_dispatch_const_unit_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
int json_dispatch_in6_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
int json_dispatch_const_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
int json_dispatch_strv_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
+1
View File
@@ -36,6 +36,7 @@ systemd_resolved_extract_sources = files(
'resolved-mdns.c',
'resolved-resolv-conf.c',
'resolved-socket-graveyard.c',
'resolved-static-records.c',
'resolved-util.c',
'resolved-varlink.c',
)
+36
View File
@@ -21,6 +21,7 @@
#include "resolved-etc-hosts.h"
#include "resolved-hook.h"
#include "resolved-manager.h"
#include "resolved-static-records.h"
#include "resolved-timeouts.h"
#include "set.h"
#include "string-util.h"
@@ -910,6 +911,33 @@ static int dns_query_try_etc_hosts(DnsQuery *q) {
return 1;
}
static int dns_query_try_static_records(DnsQuery *q) {
int r;
assert(q);
if (FLAGS_SET(q->flags, SD_RESOLVED_NO_SYNTHESIZE))
return 0;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
r = manager_static_records_lookup(
q->manager,
q->question_bypass ? q->question_bypass->question : q->question_utf8,
&answer);
if (r <= 0)
return r;
dns_query_reset_answer(q);
q->answer = TAKE_PTR(answer);
q->answer_rcode = DNS_RCODE_SUCCESS;
q->answer_protocol = dns_synthesize_protocol(q->flags);
q->answer_family = dns_synthesize_family(q->flags);
q->answer_query_flags = SD_RESOLVED_AUTHENTICATED|SD_RESOLVED_CONFIDENTIAL|SD_RESOLVED_SYNTHETIC;
return 1;
}
static int dns_query_go_scopes(DnsQuery *q) {
int r;
@@ -1038,6 +1066,14 @@ int dns_query_go(DnsQuery *q) {
q->state != DNS_TRANSACTION_NULL)
return 0;
r = dns_query_try_static_records(q);
if (r < 0)
return r;
if (r > 0) {
dns_query_complete(q, DNS_TRANSACTION_SUCCESS);
return 1;
}
r = dns_query_try_etc_hosts(q);
if (r < 0)
return r;
+1
View File
@@ -72,6 +72,7 @@ void etc_hosts_clear(EtcHosts *hosts) {
void manager_etc_hosts_flush(Manager *m) {
etc_hosts_clear(&m->etc_hosts);
m->etc_hosts_stat = (struct stat) {};
m->etc_hosts_last = USEC_INFINITY;
}
static int parse_line(EtcHosts *hosts, unsigned nr, const char *line) {
+1
View File
@@ -31,6 +31,7 @@ Resolve.DNSOverTLS, config_parse_dns_over_tls_mode, 0,
Resolve.Cache, config_parse_dns_cache_mode, DNS_CACHE_MODE_YES, offsetof(Manager, enable_cache)
Resolve.DNSStubListener, config_parse_dns_stub_listener_mode, 0, offsetof(Manager, dns_stub_listener_mode)
Resolve.ReadEtcHosts, config_parse_bool, 0, offsetof(Manager, read_etc_hosts)
Resolve.ReadStaticRecords, config_parse_bool, 0, offsetof(Manager, read_static_records)
Resolve.ResolveUnicastSingleLabel, config_parse_bool, 0, offsetof(Manager, resolve_unicast_single_label)
Resolve.DNSStubListenerExtra, config_parse_dns_stub_listener_extra, 0, offsetof(Manager, dns_extra_stub_listeners)
Resolve.CacheFromLocalhost, config_parse_bool, 0, offsetof(Manager, cache_from_localhost)
+6
View File
@@ -49,6 +49,7 @@
#include "resolved-mdns.h"
#include "resolved-resolv-conf.h"
#include "resolved-socket-graveyard.h"
#include "resolved-static-records.h"
#include "resolved-util.h"
#include "resolved-varlink.h"
#include "set.h"
@@ -637,6 +638,7 @@ static void manager_set_defaults(Manager *m) {
m->enable_cache = DNS_CACHE_MODE_YES;
m->dns_stub_listener_mode = DNS_STUB_LISTENER_YES;
m->read_etc_hosts = true;
m->read_static_records = true;
m->resolve_unicast_single_label = false;
m->cache_from_localhost = false;
m->stale_retention_usec = 0;
@@ -659,6 +661,8 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa
m->unicast_scope = dns_scope_free(m->unicast_scope);
m->delegates = hashmap_free(m->delegates);
dns_trust_anchor_flush(&m->trust_anchor);
manager_etc_hosts_flush(m);
manager_static_records_flush(m);
manager_set_defaults(m);
@@ -729,6 +733,7 @@ int manager_new(Manager **ret) {
.read_resolv_conf = true,
.need_builtin_fallbacks = true,
.etc_hosts_last = USEC_INFINITY,
.static_records_last = USEC_INFINITY,
.sigrtmin18_info.memory_pressure_handler = manager_memory_pressure,
.sigrtmin18_info.memory_pressure_userdata = m,
@@ -917,6 +922,7 @@ Manager* manager_free(Manager *m) {
dns_trust_anchor_flush(&m->trust_anchor);
manager_etc_hosts_flush(m);
manager_static_records_flush(m);
while ((sb = hashmap_first(m->dns_service_browsers)))
dns_service_browser_free(sb);
+6
View File
@@ -123,6 +123,12 @@ typedef struct Manager {
struct stat etc_hosts_stat;
bool read_etc_hosts;
/* Data from {/etc,/run,/usr/local/lib,/usr/lib}/systemd/resolve/static.d/ */
Hashmap *static_records;
usec_t static_records_last;
Set *static_records_stat;
bool read_static_records;
/* List of refused DNS Record Types */
Set *refuse_record_types;
+226
View File
@@ -0,0 +1,226 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-json.h"
#include "alloc-util.h"
#include "conf-files.h"
#include "constants.h"
#include "dns-answer.h"
#include "dns-domain.h"
#include "dns-question.h"
#include "dns-rr.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "hashmap.h"
#include "json-util.h"
#include "log.h"
#include "resolved-manager.h"
#include "resolved-static-records.h"
#include "set.h"
#include "stat-util.h"
/* This implements a mechanism to extend what systemd-resolved resolves locally, via .rr drop-ins in
* {/etc,/run,/usr/local/lib,/usr/lib}/systemd/resolve/static.d/. These files are in JSON format, and are RR
* serializations, that match the usual way we serialize RRs to JSON.
*
* Note that this deliberately doesn't use the (probably more user-friendly) classic DNS zone file format,
* to keep things a bit simpler, and symmetric to the places we currently already generate JSON
* serializations of DNS RRs. Also note the semantics are different from DNS zone file format, for example
* regarding delegation (i.e. the RRs defined here have no effect on subdomains), which is probably nicer for
* one-off mappings of domains to specific resources. Or in other words, this is supposed to be a drop-in
* based alternative to /etc/hosts, not a one to DNS zone files. (The JSON format is also a lot more
* extensible to us, for example we could teach it to map certain lookups to specific DNS errors, or extend
* it so that subdomains always get NXDOMAIN or similar).
*
* (That said, if there's a good reason, we can also support *.zone files too one day).
*/
/* Recheck static records at most once every 2s */
#define STATIC_RECORDS_RECHECK_USEC (2*USEC_PER_SEC)
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
answer_by_name_hash_ops,
char,
dns_name_hash_func,
dns_name_compare_func,
DnsAnswer,
dns_answer_unref);
static int load_static_record_file_item(sd_json_variant *rj, Hashmap **records) {
int r;
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
r = dns_resource_record_from_json(rj, &rr);
if (r < 0)
return log_error_errno(r, "Failed to parse DNS record from JSON: %m");
_cleanup_(dns_answer_unrefp) DnsAnswer *a =
hashmap_remove(*records, dns_resource_key_name(rr->key));
r = dns_answer_add_extend_full(&a, rr, /* ifindex= */ 0, DNS_ANSWER_AUTHENTICATED, /* rrsig= */ NULL, /* until= */ USEC_INFINITY);
if (r < 0)
return log_error_errno(r, "Failed to append RR to DNS answer: %m");
DnsAnswerItem *item = ASSERT_PTR(ordered_set_first(a->items));
r = hashmap_ensure_put(records, &answer_by_name_hash_ops, dns_resource_key_name(item->rr->key), a);
if (r < 0)
return log_error_errno(r, "Failed to add RR to static record set: %m");
TAKE_PTR(a);
log_debug("Added static resource record: %s", dns_resource_record_to_string(rr));
return 1;
}
static int load_static_record_file(const ConfFile *cf, Hashmap **records, Set **stats) {
int r;
assert(cf);
assert(records);
assert(stats);
/* Have we seen this file before? Then we might as well skip loading it again, it wouldn't have any
* additional effect anyway. (Note: masking/overriding has already been applied before we reach this
* point, here everything is purely additive.) */
if (set_contains(*stats, &cf->st))
return 0;
_cleanup_free_ struct stat *st_copy = memdup(&cf->st, sizeof(cf->st));
if (!st_copy)
return log_oom();
if (set_ensure_consume(stats, &inode_unmodified_hash_ops, TAKE_PTR(st_copy)) < 0)
return log_oom();
_cleanup_fclose_ FILE *f = NULL;
r = xfopenat(cf->fd, /* path= */ NULL, "re", /* open_flags= */ 0, &f);
if (r < 0) {
log_warning_errno(r, "Failed to open '%s', skipping: %m", cf->result);
return 0;
}
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
unsigned line = 0, column = 0;
r = sd_json_parse_file(f, cf->result, /* flags= */ 0, &j, &line, &column);
if (r < 0) {
if (line > 0)
log_syntax(/* unit= */ NULL, LOG_WARNING, cf->result, line, r, "Failed to parse JSON, skipping: %m");
else
log_warning_errno(r, "Failed to parse JSON file '%s', skipping: %m", cf->result);
return 0;
}
if (sd_json_variant_is_array(j)) {
sd_json_variant *i;
int ret = 0;
JSON_VARIANT_ARRAY_FOREACH(i, j)
RET_GATHER(ret, load_static_record_file_item(i, records));
if (ret < 0)
return ret;
} else if (sd_json_variant_is_object(j)) {
r = load_static_record_file_item(j, records);
if (r < 0)
return r;
} else {
log_warning("JSON file '%s' contains neither array nor object, skipping.", cf->result);
return 0;
}
return 1;
}
static int manager_static_records_read(Manager *m) {
int r;
usec_t ts;
assert_se(sd_event_now(m->event, CLOCK_BOOTTIME, &ts) >= 0);
/* See if we checked the static records db recently already */
if (m->static_records_last != USEC_INFINITY && usec_add(m->static_records_last, STATIC_RECORDS_RECHECK_USEC) > ts)
return 0;
m->static_records_last = ts;
ConfFile **files = NULL;
size_t n_files = 0;
CLEANUP_ARRAY(files, n_files, conf_file_free_many);
r = conf_files_list_nulstr_full(
".rr",
/* root= */ NULL,
CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN,
CONF_PATHS_NULSTR("systemd/resolve/static.d/"),
&files,
&n_files);
if (r < 0)
return log_error_errno(r, "Failed to enumerate static record drop-ins: %m");
/* Let's suppress reloads if nothing changed. For that keep the set of inodes from the previous
* reload around, and see if there are any changes on them. */
bool reload;
if (set_size(m->static_records_stat) != n_files)
reload = true;
else {
reload = false;
FOREACH_ARRAY(f, files, n_files)
if (!set_contains(m->static_records_stat, &(*f)->st)) {
reload = true;
break;
}
}
if (!reload) {
log_debug("No static record files changed, not re-reading.");
return 0;
}
_cleanup_(hashmap_freep) Hashmap *records = NULL;
_cleanup_(set_freep) Set *stats = NULL;
FOREACH_ARRAY(f, files, n_files)
(void) load_static_record_file(*f, &records, &stats);
hashmap_free(m->static_records);
m->static_records = TAKE_PTR(records);
set_free(m->static_records_stat);
m->static_records_stat = TAKE_PTR(stats);
return 0;
}
int manager_static_records_lookup(Manager *m, DnsQuestion *q, DnsAnswer **answer) {
int r;
assert(m);
assert(q);
assert(answer);
if (!m->read_static_records)
return 0;
(void) manager_static_records_read(m);
const char *n = dns_question_first_name(q);
if (!n)
return 0;
DnsAnswer *f = hashmap_get(m->static_records, n);
if (!f)
return 0;
r = dns_answer_extend(answer, f);
if (r < 0)
return r;
return 1;
}
void manager_static_records_flush(Manager *m) {
assert(m);
m->static_records = hashmap_free(m->static_records);
m->static_records_stat = set_free(m->static_records_stat);
m->static_records_last = USEC_INFINITY;
}
+7
View File
@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "resolved-forward.h"
void manager_static_records_flush(Manager *m);
int manager_static_records_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer);
+1
View File
@@ -39,6 +39,7 @@
#DNSStubListener=yes
#DNSStubListenerExtra=
#ReadEtcHosts=yes
#ReadStaticRecords=yes
#ResolveUnicastSingleLabel=no
#StaleRetentionSec=0
#RefuseRecordTypes=
+41 -2
View File
@@ -7,6 +7,16 @@
#include "dns-type.h"
#include "tests.h"
static void test_to_json_from_json(DnsResourceRecord *rr) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
ASSERT_OK(dns_resource_record_to_json(rr, &j));
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr2 = NULL;
ASSERT_OK(dns_resource_record_from_json(j, &rr2));
ASSERT_TRUE(dns_resource_record_equal(rr, rr2));
}
/* ================================================================
* DNS_RESOURCE_RECORD_RDATA()
* ================================================================ */
@@ -802,6 +812,8 @@ TEST(dns_resource_record_new_address_ipv4) {
ASSERT_EQ(rr->key->type, DNS_TYPE_A);
ASSERT_STREQ(dns_resource_key_name(rr->key), "www.example.com");
ASSERT_EQ(rr->a.in_addr.s_addr, addr.in.s_addr);
test_to_json_from_json(rr);
}
TEST(dns_resource_record_new_address_ipv6) {
@@ -818,6 +830,8 @@ TEST(dns_resource_record_new_address_ipv6) {
ASSERT_EQ(rr->key->type, DNS_TYPE_AAAA);
ASSERT_STREQ(dns_resource_key_name(rr->key), "www.example.com");
ASSERT_EQ(memcmp(&rr->aaaa.in6_addr, &addr.in6, sizeof(struct in6_addr)), 0);
test_to_json_from_json(rr);
}
/* ================================================================
@@ -1003,11 +1017,13 @@ TEST(dns_resource_record_equal_cname_copy) {
a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_CNAME, "www.example.com");
ASSERT_NOT_NULL(a);
a->cname.name = strdup("example.com");
a->cname.name = ASSERT_PTR(strdup("example.com"));
b = dns_resource_record_copy(a);
ASSERT_NOT_NULL(b);
ASSERT_TRUE(dns_resource_record_equal(a, b));
test_to_json_from_json(a);
}
TEST(dns_resource_record_equal_cname_fail) {
@@ -1220,11 +1236,13 @@ TEST(dns_resource_record_equal_ptr_copy) {
a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, "127.1.168.192.in-addr-arpa");
ASSERT_NOT_NULL(a);
a->ptr.name = strdup("example.com");
a->ptr.name = ASSERT_PTR(strdup("example.com"));
b = dns_resource_record_copy(a);
ASSERT_NOT_NULL(b);
ASSERT_TRUE(dns_resource_record_equal(a, b));
test_to_json_from_json(a);
}
TEST(dns_resource_record_equal_ptr_fail) {
@@ -2461,4 +2479,25 @@ TEST(dns_resource_record_clamp_ttl_copy) {
ASSERT_EQ(orig->ttl, 3600u);
}
static void test_from_json(const char *text, int expected) {
log_notice("Trying to parse as JSON RR: %s", text);
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
ASSERT_OK(sd_json_parse(text, /* flags= */ 0, &j, /* reterr_line= */ NULL, /* reterr_column= */ NULL));
ASSERT_EQ(dns_resource_record_from_json(j, NULL), expected);
}
TEST(from_bad_json) {
test_from_json("{}", -EBADMSG);
test_from_json("{\"key\":{}}", -ENXIO);
test_from_json("{\"key\":{\"name\":\"foobar\",\"type\":9}}", -EOPNOTSUPP);
test_from_json("{\"key\":{\"name\":\"foobar\"}}", -ENXIO);
test_from_json("{\"key\":{\"type\":9}}", -ENXIO);
test_from_json("{\"key\":{\"name\":\"foobar\",\"type\":1}}", -ENXIO);
test_from_json("{\"key\":{\"name\":\"foobar\",\"type\":1},\"address\":[1,2,3,4]}", 0);
test_from_json("{\"key\":{\"name\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"type\":1},\"address\":[1,2,3,4]}", 0);
test_from_json("{\"key\":{\"name\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"type\":1},\"address\":[1,2,3,4]}", -EBADMSG);
test_from_json("{\"key\":{\"name\":\"a.a\",\"type\":1},\"address\":[1,2,3,4]}", 0);
test_from_json("{\"key\":{\"name\":\"a..a\",\"type\":1},\"address\":[1,2,3,4]}", -EBADMSG);
}
DEFINE_TEST_MAIN(LOG_DEBUG);
+99 -2
View File
@@ -2215,6 +2215,12 @@ int dns_resource_key_from_json(sd_json_variant *v, DnsResourceKey **ret) {
if (r < 0)
return r;
r = dns_name_is_valid(p.name);
if (r < 0)
return r;
if (r == 0)
return -EBADMSG;
key = dns_resource_key_new(p.class, p.type, p.name);
if (!key)
return -ENOMEM;
@@ -2302,7 +2308,6 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret) {
int r;
assert(rr);
assert(ret);
r = dns_resource_key_to_json(rr->key, &k);
if (r < 0)
@@ -2508,11 +2513,103 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret) {
default:
/* Can't provide broken-down format */
*ret = NULL;
if (ret)
*ret = NULL;
return 0;
}
}
int dns_resource_record_from_json(sd_json_variant *v, DnsResourceRecord **ret) {
int r;
assert(v);
sd_json_variant *k = sd_json_variant_by_key(v, "key");
if (!k)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Resource record entry lacks key field, refusing.");
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
r = dns_resource_key_from_json(k, &key);
if (r < 0)
return r;
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
rr = dns_resource_record_new(key);
if (!rr)
return log_oom_debug();
/* Note, for now we only support the most common subset of RRs for decoding here. Please send patches for more. */
switch (key->type) {
case DNS_TYPE_PTR:
case DNS_TYPE_NS:
case DNS_TYPE_CNAME:
case DNS_TYPE_DNAME: {
_cleanup_free_ char *name = NULL;
static const struct sd_json_dispatch_field table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0, SD_JSON_MANDATORY },
{ "key", SD_JSON_VARIANT_OBJECT, NULL, 0, SD_JSON_MANDATORY },
{}
};
r = sd_json_dispatch(v, table, /* flags= */ 0, &name);
if (r < 0)
return r;
r = dns_name_is_valid(name);
if (r < 0)
return r;
if (r == 0)
return -EBADMSG;
rr->ptr.name = TAKE_PTR(name);
break;
}
case DNS_TYPE_A: {
struct in_addr addr = {};
static const struct sd_json_dispatch_field table[] = {
{ "address", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0, SD_JSON_MANDATORY },
{ "key", SD_JSON_VARIANT_OBJECT, NULL, 0, SD_JSON_MANDATORY },
{}
};
r = sd_json_dispatch(v, table, /* flags= */ 0, &addr);
if (r < 0)
return r;
rr->a.in_addr = addr;
break;
}
case DNS_TYPE_AAAA: {
struct in6_addr addr = {};
static const struct sd_json_dispatch_field table[] = {
{ "address", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0, SD_JSON_MANDATORY },
{ "key", SD_JSON_VARIANT_OBJECT, NULL, 0, SD_JSON_MANDATORY },
{}
};
r = sd_json_dispatch(v, table, /* flags= */ 0, &addr);
if (r < 0)
return r;
rr->aaaa.in6_addr = addr;
break;
}
default:
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Decoding DNS type %s is currently not supported.", dns_type_to_string(key->type));
}
if (ret)
*ret = TAKE_PTR(rr);
return 0;
}
static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = {
/* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */
[DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5",
+1
View File
@@ -419,6 +419,7 @@ int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data,
int dns_resource_key_to_json(DnsResourceKey *key, sd_json_variant **ret);
int dns_resource_key_from_json(sd_json_variant *v, DnsResourceKey **ret);
int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret);
int dns_resource_record_from_json(sd_json_variant *v, DnsResourceRecord **ret);
void dns_resource_key_hash_func(const DnsResourceKey *k, struct siphash *state);
int dns_resource_key_compare_func(const DnsResourceKey *x, const DnsResourceKey *y);
+197
View File
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <float.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/sysmacros.h>
#include <unistd.h>
@@ -1630,4 +1631,200 @@ TEST(must_be) {
ASSERT_OK(sd_json_parse("[]", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL));
}
TEST(json_dispatch_in_addr) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
/* 192.168.1.1 = { 192, 168, 1, 1 } */
ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR("addr", JSON_BUILD_IN4_ADDR(&(const struct in_addr) { .s_addr = htobe32(0xC0A80101U) })),
SD_JSON_BUILD_PAIR("null_addr", SD_JSON_BUILD_NULL))));
struct {
struct in_addr addr;
struct in_addr null_addr;
} data = {};
ASSERT_OK(sd_json_dispatch(j,
(const sd_json_dispatch_field[]) {
{ "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, offsetof(typeof(data), addr) },
{ "null_addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, offsetof(typeof(data), null_addr) },
{},
},
/* flags= */ 0,
&data));
ASSERT_EQ(be32toh(data.addr.s_addr), 0xC0A80101U);
ASSERT_EQ(data.null_addr.s_addr, 0U);
struct in_addr dummy = {};
/* Too few bytes (3 instead of 4) */
j = sd_json_variant_unref(j);
ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(192), SD_JSON_BUILD_UNSIGNED(168), SD_JSON_BUILD_UNSIGNED(1))))));
ASSERT_ERROR(sd_json_dispatch(j,
(const sd_json_dispatch_field[]) {
{ "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 },
{},
},
/* flags= */ 0,
&dummy), EINVAL);
/* Too many bytes (5 instead of 4) */
j = sd_json_variant_unref(j);
ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(192), SD_JSON_BUILD_UNSIGNED(168), SD_JSON_BUILD_UNSIGNED(1), SD_JSON_BUILD_UNSIGNED(1), SD_JSON_BUILD_UNSIGNED(0))))));
ASSERT_ERROR(sd_json_dispatch(j,
(const sd_json_dispatch_field[]) {
{ "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 },
{},
},
/* flags= */ 0,
&dummy), EINVAL);
/* Not an array or string */
j = sd_json_variant_unref(j);
ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_BOOLEAN(true)))));
ASSERT_ERROR(sd_json_dispatch(j,
(const sd_json_dispatch_field[]) {
{ "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 },
{},
},
/* flags= */ 0,
&dummy), EINVAL);
/* A string */
j = sd_json_variant_unref(j);
ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR("addr", JSON_BUILD_CONST_STRING("192.168.1.1")))));
zero(data);
ASSERT_OK(sd_json_dispatch(j,
(const sd_json_dispatch_field[]) {
{ "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 },
{},
},
/* flags= */ 0,
&data));
ASSERT_EQ(be32toh(data.addr.s_addr), 0xC0A80101U);
/* Byte value out of range (> 255) */
j = sd_json_variant_unref(j);
ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(256), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1))))));
ASSERT_ERROR(sd_json_dispatch(j,
(const sd_json_dispatch_field[]) {
{ "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 },
{},
},
/* flags= */ 0,
&dummy), EINVAL);
/* Negative element */
j = sd_json_variant_unref(j);
ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_INTEGER(-1), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1))))));
ASSERT_ERROR(sd_json_dispatch(j,
(const sd_json_dispatch_field[]) {
{ "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 },
{},
},
/* flags= */ 0,
&dummy), EINVAL);
}
TEST(json_dispatch_in6_addr) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
/* ::1 */
ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR("addr", JSON_BUILD_IN6_ADDR(&(const struct in6_addr) { .s6_addr = { [15] = 1 } })),
SD_JSON_BUILD_PAIR("null_addr", SD_JSON_BUILD_NULL))));
struct {
struct in6_addr addr;
struct in6_addr null_addr;
} data = {};
ASSERT_OK(sd_json_dispatch(j,
(const sd_json_dispatch_field[]) {
{ "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, offsetof(typeof(data), addr) },
{ "null_addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, offsetof(typeof(data), null_addr) },
{},
},
/* flags= */ 0,
&data));
ASSERT_EQ(data.addr.s6_addr[15], 1);
for (size_t i = 0; i < 15; i++)
ASSERT_EQ(data.addr.s6_addr[i], 0);
for (size_t i = 0; i < 16; i++)
ASSERT_EQ(data.null_addr.s6_addr[i], 0);
struct in6_addr dummy = {};
/* Too few bytes (15 instead of 16) */
j = sd_json_variant_unref(j);
ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(
SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0),
SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0),
SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0),
SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1))))));
ASSERT_ERROR(sd_json_dispatch(j,
(const sd_json_dispatch_field[]) {
{ "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0 },
{},
},
/* flags= */ 0,
&dummy), EINVAL);
/* Too many bytes (17 instead of 16) */
j = sd_json_variant_unref(j);
ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(
SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0),
SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0),
SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0),
SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1),
SD_JSON_BUILD_UNSIGNED(0))))));
ASSERT_ERROR(sd_json_dispatch(j,
(const sd_json_dispatch_field[]) {
{ "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0 },
{},
},
/* flags= */ 0,
&dummy), EINVAL);
/* Not an array */
j = sd_json_variant_unref(j);
ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_BOOLEAN(true)))));
ASSERT_ERROR(sd_json_dispatch(j,
(const sd_json_dispatch_field[]) {
{ "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0 },
{},
},
/* flags= */ 0,
&dummy), EINVAL);
/* A string */
j = sd_json_variant_unref(j);
ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR("addr", JSON_BUILD_CONST_STRING("::1")))));
zero(data);
ASSERT_OK(sd_json_dispatch(j,
(const sd_json_dispatch_field[]) {
{ "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0 },
{},
},
/* flags= */ 0,
&data));
ASSERT_EQ(data.addr.s6_addr[15], 1);
for (size_t i = 0; i < 15; i++)
ASSERT_EQ(data.addr.s6_addr[i], 0);
}
DEFINE_TEST_MAIN(LOG_DEBUG);
+49
View File
@@ -1487,6 +1487,55 @@ EOF
grep -qF "1.2.3.4" "$RUN_OUT"
}
testcase_static_record() {
mkdir -p /run/systemd/resolve/static.d/
cat >/run/systemd/resolve/static.d/statictest.rr <<EOF
{
"key": { "name" : "statictest.waldo", "type" : 1 },
"address" : [ 5, 7, 9, 11 ]
}
EOF
cat >/run/systemd/resolve/static.d/statictest2.rr <<EOF
[
{
"key": { "name" : "statictest2.waldo", "type" : 1 },
"address" : [ 5, 7, 9, 12 ]
},
{
"key": { "name" : "statictest2.waldo", "type" : 28 },
"address" : [ 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 12 ]
}
]
EOF
cat >/run/systemd/resolve/static.d/garbage.rr <<EOF
[
{
"key": { "name" : "invalid...domain", "type" : 1 },
"address" : [ 5, 7, 9, 12 ]
},
]
EOF
cat >/run/systemd/resolve/static.d/garbage2.rr <<EOF
[
{
"key": { "name" : "piff", "type" : 9 },
},
]
EOF
systemctl reload systemd-resolved
run resolvectl query statictest.waldo
grep -qF 5.7.9.11 "$RUN_OUT"
run resolvectl query statictest2.waldo
grep -qF 5.7.9.12 "$RUN_OUT"
grep -qF a0b:a0b:a0b:a0b:a0b:a0b:a0b:a0c "$RUN_OUT"
rm /run/systemd/resolve/static.d/statictest*.rr /run/systemd/resolve/static.d/garbage*.rr
systemctl reload systemd-resolved
}
# PRE-SETUP
systemctl unmask systemd-resolved.service
systemctl enable --now systemd-resolved.service