+62
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -39,6 +39,7 @@
|
||||
#DNSStubListener=yes
|
||||
#DNSStubListenerExtra=
|
||||
#ReadEtcHosts=yes
|
||||
#ReadStaticRecords=yes
|
||||
#ResolveUnicastSingleLabel=no
|
||||
#StaleRetentionSec=0
|
||||
#RefuseRecordTypes=
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user