diff options
author | Lennart Poettering <lennart@poettering.net> | 2015-11-24 21:12:51 +0100 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2015-11-25 21:58:38 +0100 |
commit | a51c10485af349eb15faa4d1a63b9818bcf3e589 (patch) | |
tree | 86134465451ab8044208710c2c5018171100555d /src | |
parent | 0b58db658b5c3f586ac3a837427f1f7fec2abb2e (diff) |
resolved: add a generic DnsSearchDomain concept
With this change, we add a new object to resolved, "DnsSearchDomain="
which wraps a search domain. This is then used to introduce a global
search domain list, in addition to the existing per-link search domain
list which is reword to make use of this new object too.
This is preparation for implement proper unicast DNS search domain
support.
Diffstat (limited to 'src')
-rw-r--r-- | src/resolve/resolved-conf.c | 84 | ||||
-rw-r--r-- | src/resolve/resolved-conf.h | 7 | ||||
-rw-r--r-- | src/resolve/resolved-dns-scope.c | 18 | ||||
-rw-r--r-- | src/resolve/resolved-dns-scope.h | 4 | ||||
-rw-r--r-- | src/resolve/resolved-dns-search-domain.c | 223 | ||||
-rw-r--r-- | src/resolve/resolved-dns-search-domain.h | 71 | ||||
-rw-r--r-- | src/resolve/resolved-gperf.gperf | 1 | ||||
-rw-r--r-- | src/resolve/resolved-link.c | 46 | ||||
-rw-r--r-- | src/resolve/resolved-link.h | 6 | ||||
-rw-r--r-- | src/resolve/resolved-manager.c | 2 | ||||
-rw-r--r-- | src/resolve/resolved-manager.h | 5 | ||||
-rw-r--r-- | src/resolve/resolved-resolv-conf.c | 35 | ||||
-rw-r--r-- | src/resolve/resolved.conf.in | 1 |
13 files changed, 474 insertions, 29 deletions
diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index c11a8badd9..dbac45170f 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -71,10 +71,49 @@ int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, con break; r = manager_add_dns_server_by_string(m, type, word); - if (r < 0) { + if (r < 0) log_warning_errno(r, "Failed to add DNS server address '%s', ignoring.", word); - continue; - } + } + + return 0; +} + +int manager_add_search_domain_by_string(Manager *m, const char *domain) { + DnsSearchDomain *d; + int r; + + assert(m); + assert(domain); + + r = dns_search_domain_find(m->search_domains, domain, &d); + if (r < 0) + return r; + if (r > 0) { + dns_search_domain_move_back_and_unmark(d); + return 0; + } + + return dns_search_domain_new(m, NULL, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain); +} + +int manager_parse_search_domains_and_warn(Manager *m, const char *string) { + int r; + + assert(m); + assert(string); + + for(;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES); + if (r < 0) + return r; + if (r == 0) + break; + + r = manager_add_search_domain_by_string(m, word); + if (r < 0) + log_warning_errno(r, "Failed to add search domain '%s', ignoring.", word); } return 0; @@ -122,6 +161,45 @@ int config_parse_dns_servers( return 0; } +int config_parse_search_domains( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Manager *m = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(m); + + if (isempty(rvalue)) + /* Empty assignment means clear the list */ + dns_search_domain_unlink_all(m->search_domains); + else { + /* Otherwise, add to the list */ + r = manager_parse_search_domains_and_warn(m, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse search domains string '%s'. Ignoring.", rvalue); + return 0; + } + } + + /* If we have a manual setting, then we stop reading + * /etc/resolv.conf */ + m->read_resolv_conf = false; + + return 0; +} + int config_parse_support( const char *unit, const char *filename, diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index 170f3e94aa..28d2549d35 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -23,13 +23,16 @@ #include "resolved-manager.h" -int manager_parse_dns_server(Manager *m, DnsServerType type, const char *string); int manager_parse_config_file(Manager *m); -int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string); +int manager_add_search_domain_by_string(Manager *m, const char *domain); +int manager_parse_search_domains_and_warn(Manager *m, const char *string); + int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word); +int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string); const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned length); int config_parse_dns_servers(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_search_domains(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_support(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 5174502af0..2e47691c56 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -103,7 +103,6 @@ DnsScope* dns_scope_free(DnsScope *s) { dns_zone_flush(&s->zone); LIST_REMOVE(scopes, s->manager->dns_scopes, s); - strv_free(s->domains); free(s); return NULL; @@ -323,7 +322,7 @@ int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *add } DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { - char **i; + DnsSearchDomain *d; assert(s); assert(domain); @@ -345,8 +344,8 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co dns_name_equal(domain, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) return DNS_SCOPE_NO; - STRV_FOREACH(i, s->domains) - if (dns_name_endswith(domain, *i) > 0) + LIST_FOREACH(domains, d, dns_scope_get_search_domains(s)) + if (dns_name_endswith(domain, d->name) > 0) return DNS_SCOPE_YES; switch (s->protocol) { @@ -850,3 +849,14 @@ void dns_scope_dump(DnsScope *s, FILE *f) { dns_cache_dump(&s->cache, f); } } + +DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s) { + + if (s->protocol != DNS_PROTOCOL_DNS) + return NULL; + + if (s->link) + return s->link->search_domains; + + return NULL; +} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index f9b2bfda9d..f69d51b203 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -47,8 +47,6 @@ struct DnsScope { Link *link; - char **domains; - DnsCache cache; DnsZone zone; @@ -91,3 +89,5 @@ int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); void dns_scope_dump(DnsScope *s, FILE *f); + +DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s); diff --git a/src/resolve/resolved-dns-search-domain.c b/src/resolve/resolved-dns-search-domain.c new file mode 100644 index 0000000000..5d927bb2a1 --- /dev/null +++ b/src/resolve/resolved-dns-search-domain.c @@ -0,0 +1,223 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-search-domain.h" + +int dns_search_domain_new( + Manager *m, + DnsSearchDomain **ret, + DnsSearchDomainType type, + Link *l, + const char *name) { + + _cleanup_free_ char *normalized = NULL; + DnsSearchDomain *d, *tail; + int r; + + assert(m); + assert((type == DNS_SEARCH_DOMAIN_LINK) == !!l); + assert(name); + + r = dns_name_normalize(name, &normalized); + if (r < 0) + return r; + + r = dns_name_root(normalized); + if (r < 0) + return r; + if (r > 0) + return -EINVAL; + + d = new0(DnsSearchDomain, 1); + if (!d) + return -ENOMEM; + + d->n_ref = 1; + d->manager = m; + d->type = type; + d->name = normalized; + normalized = NULL; + + switch (type) { + + case DNS_SEARCH_DOMAIN_LINK: + d->link = l; + LIST_FIND_TAIL(domains, l->search_domains, tail); + LIST_INSERT_AFTER(domains, l->search_domains, tail, d); + break; + + case DNS_SERVER_SYSTEM: + LIST_FIND_TAIL(domains, m->search_domains, tail); + LIST_INSERT_AFTER(domains, m->search_domains, tail, d); + break; + + default: + assert_not_reached("Unknown search domain type"); + } + + d->linked = true; + + if (ret) + *ret = d; + + return 0; +} + +DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d) { + if (!d) + return NULL; + + assert(d->n_ref > 0); + d->n_ref++; + + return d; +} + +DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d) { + if (!d) + return NULL; + + assert(d->n_ref > 0); + d->n_ref--; + + if (d->n_ref > 0) + return NULL; + + free(d->name); + free(d); + + return NULL; +} + +void dns_search_domain_unlink(DnsSearchDomain *d) { + assert(d); + assert(d->manager); + + if (!d->linked) + return; + + switch (d->type) { + + case DNS_SEARCH_DOMAIN_LINK: + assert(d->link); + LIST_REMOVE(domains, d->link->search_domains, d); + break; + + case DNS_SEARCH_DOMAIN_SYSTEM: + LIST_REMOVE(domains, d->manager->search_domains, d); + break; + } + + d->linked = false; + + dns_search_domain_unref(d); +} + +void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d) { + DnsSearchDomain *tail; + + assert(d); + + if (!d->marked) + return; + + d->marked = false; + + if (!d->linked || !d->domains_next) + return; + + switch (d->type) { + + case DNS_SEARCH_DOMAIN_LINK: + assert(d->link); + LIST_FIND_TAIL(domains, d, tail); + LIST_REMOVE(domains, d->link->search_domains, d); + LIST_INSERT_AFTER(domains, d->link->search_domains, tail, d); + break; + + case DNS_SEARCH_DOMAIN_SYSTEM: + LIST_FIND_TAIL(domains, d, tail); + LIST_REMOVE(domains, d->manager->search_domains, d); + LIST_INSERT_AFTER(domains, d->manager->search_domains, tail, d); + break; + + default: + assert_not_reached("Unknown search domain type"); + } +} + +void dns_search_domain_unlink_all(DnsSearchDomain *first) { + DnsSearchDomain *next; + + if (!first) + return; + + next = first->domains_next; + dns_search_domain_unlink(first); + + dns_search_domain_unlink_all(next); +} + +void dns_search_domain_unlink_marked(DnsSearchDomain *first) { + DnsSearchDomain *next; + + if (!first) + return; + + next = first->domains_next; + + if (first->marked) + dns_search_domain_unlink(first); + + dns_search_domain_unlink_marked(next); +} + +void dns_search_domain_mark_all(DnsSearchDomain *first) { + if (!first) + return; + + first->marked = true; + dns_search_domain_mark_all(first->domains_next); +} + +int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret) { + DnsSearchDomain *d; + int r; + + assert(name); + assert(ret); + + LIST_FOREACH(domains, d, first) { + + r = dns_name_equal(name, d->name); + if (r < 0) + return r; + if (r > 0) { + *ret = d; + return 1; + } + } + + *ret = NULL; + return 0; +} diff --git a/src/resolve/resolved-dns-search-domain.h b/src/resolve/resolved-dns-search-domain.h new file mode 100644 index 0000000000..20b441206c --- /dev/null +++ b/src/resolve/resolved-dns-search-domain.h @@ -0,0 +1,71 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "macro.h" + +typedef struct DnsSearchDomain DnsSearchDomain; + +typedef enum DnsSearchDomainType { + DNS_SEARCH_DOMAIN_SYSTEM, + DNS_SEARCH_DOMAIN_LINK, +} DnsSearchDomainType; + +#include "resolved-link.h" +#include "resolved-manager.h" + +struct DnsSearchDomain { + Manager *manager; + + unsigned n_ref; + + DnsSearchDomainType type; + Link *link; + + char *name; + + bool marked:1; + + bool linked:1; + LIST_FIELDS(DnsSearchDomain, domains); +}; + +int dns_search_domain_new( + Manager *m, + DnsSearchDomain **ret, + DnsSearchDomainType type, + Link *link, + const char *name); + +DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d); +DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d); + +void dns_search_domain_unlink(DnsSearchDomain *d); +void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d); + +void dns_search_domain_unlink_all(DnsSearchDomain *first); +void dns_search_domain_unlink_marked(DnsSearchDomain *first); +void dns_search_domain_mark_all(DnsSearchDomain *first); + +int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsSearchDomain*, dns_search_domain_unref); diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index dc4ef74b8c..50662656d5 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -16,4 +16,5 @@ struct ConfigPerfItem; %% Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 +Resolve.Domains, config_parse_search_domains, 0, 0 Resolve.LLMNR, config_parse_support, 0, offsetof(Manager, llmnr_support) diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 4931fc9d3b..c367160d98 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -66,6 +66,7 @@ Link *link_free(Link *l) { return NULL; link_flush_dns_servers(l); + dns_search_domain_unlink_all(l->search_domains); while (l->addresses) link_address_free(l->addresses); @@ -219,29 +220,56 @@ clear: return r; } -static int link_update_domains(Link *l) { +static int link_update_search_domains(Link *l) { + _cleanup_strv_free_ char **domains = NULL; + char **i; int r; - if (!l->unicast_scope) - return 0; - - l->unicast_scope->domains = strv_free(l->unicast_scope->domains); + assert(l); - r = sd_network_link_get_domains(l->ifindex, - &l->unicast_scope->domains); + r = sd_network_link_get_domains(l->ifindex, &domains); if (r < 0) - return r; + goto clear; + + dns_search_domain_mark_all(l->search_domains); + + STRV_FOREACH(i, domains) { + DnsSearchDomain *d; + r = dns_search_domain_find(l->search_domains, *i, &d); + if (r < 0) + goto clear; + + if (r > 0) + dns_search_domain_move_back_and_unmark(d); + else { + r = dns_search_domain_new(l->manager, NULL, DNS_SEARCH_DOMAIN_LINK, l, *i); + if (r < 0) + goto clear; + } + } + + dns_search_domain_unlink_marked(l->search_domains); return 0; + +clear: + dns_search_domain_unlink_all(l->search_domains); + return r; } int link_update_monitor(Link *l) { + int r; + assert(l); link_update_dns_servers(l); link_update_llmnr_support(l); link_allocate_scopes(l); - link_update_domains(l); + + r = link_update_search_domains(l); + if (r < 0) + log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name); + link_add_rrs(l, false); return 0; diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index 76e96e733a..d72461de06 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -30,6 +30,8 @@ typedef struct Link Link; typedef struct LinkAddress LinkAddress; #include "resolved-dns-rr.h" +#include "resolved-dns-search-domain.h" +#include "resolved-dns-server.h" #include "resolved-manager.h" struct LinkAddress { @@ -57,6 +59,8 @@ struct Link { LIST_HEAD(DnsServer, dns_servers); DnsServer *current_dns_server; + LIST_HEAD(DnsSearchDomain, search_domains); + Support llmnr_support; DnsScope *unicast_scope; @@ -84,6 +88,8 @@ DnsServer* link_find_dns_server(Link *l, int family, const union in_addr_union * DnsServer* link_get_dns_server(Link *l); void link_next_dns_server(Link *l); +void link_flush_search_domains(Link *l); + int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr); LinkAddress *link_address_free(LinkAddress *a); int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index c3c61a0893..0771ac840f 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -536,6 +536,8 @@ Manager *manager_free(Manager *m) { manager_flush_dns_servers(m, DNS_SERVER_SYSTEM); manager_flush_dns_servers(m, DNS_SERVER_FALLBACK); + dns_search_domain_unlink_all(m->search_domains); + while ((l = hashmap_first(m->links))) link_free(l); diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 8a716b3ceb..0683e23f6c 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -40,6 +40,8 @@ enum Support { }; #include "resolved-dns-query.h" +#include "resolved-dns-search-domain.h" +#include "resolved-dns-server.h" #include "resolved-dns-stream.h" #include "resolved-link.h" @@ -70,7 +72,10 @@ struct Manager { LIST_HEAD(DnsServer, fallback_dns_servers); DnsServer *current_dns_server; + LIST_HEAD(DnsSearchDomain, search_domains); + bool need_builtin_fallbacks:1; + bool read_resolv_conf:1; usec_t resolv_conf_mtime; diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c index 98c1720f0f..2ab0008fce 100644 --- a/src/resolve/resolved-resolv-conf.c +++ b/src/resolve/resolved-resolv-conf.c @@ -86,13 +86,12 @@ int manager_read_resolv_conf(Manager *m) { } manager_mark_dns_servers(m, DNS_SERVER_SYSTEM); + dns_search_domain_mark_all(m->search_domains); FOREACH_LINE(line, f, r = -errno; goto clear) { const char *a; char *l; - truncate_nl(line); - l = strstrip(line); if (*l == '#' || *l == ';') continue; @@ -105,9 +104,22 @@ int manager_read_resolv_conf(Manager *m) { continue; } + + a = first_word(l, "domain"); + if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */ + a = first_word(l, "search"); + if (a) { + r = manager_parse_search_domains_and_warn(m, a); + if (r < 0) + log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a); + } } + /* Flush out all servers and search domains that are still + * marked. Those are then ones that didn't appear in the new + * /etc/resolv.conf */ manager_flush_marked_dns_servers(m, DNS_SERVER_SYSTEM); + dns_search_domain_unlink_marked(m->search_domains); /* Whenever /etc/resolv.conf changes, start using the first * DNS server of it. This is useful to deal with broken @@ -123,6 +135,7 @@ int manager_read_resolv_conf(Manager *m) { clear: manager_flush_dns_servers(m, DNS_SERVER_SYSTEM); + dns_search_domain_unlink_all(m->search_domains); return r; } @@ -211,6 +224,7 @@ int manager_write_resolv_conf(Manager *m) { _cleanup_free_ char *temp_path = NULL; _cleanup_fclose_ FILE *f = NULL; _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL; + DnsSearchDomain *d; DnsServer *s; Iterator i; Link *l; @@ -239,10 +253,16 @@ int manager_write_resolv_conf(Manager *m) { return r; } + LIST_FOREACH(domains, d, m->search_domains) { + r = ordered_set_put(domains, d->name); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + /* Then, add the per-link servers and domains */ HASHMAP_FOREACH(l, m->links, i) { - char **domain; - LIST_FOREACH(servers, s, l->dns_servers) { r = ordered_set_put(dns, s); if (r == -EEXIST) @@ -251,11 +271,8 @@ int manager_write_resolv_conf(Manager *m) { return r; } - if (!l->unicast_scope) - continue; - - STRV_FOREACH(domain, l->unicast_scope->domains) { - r = ordered_set_put(domains, *domain); + LIST_FOREACH(domains, d, l->search_domains) { + r = ordered_set_put(domains, d->name); if (r == -EEXIST) continue; if (r < 0) diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in index 3eb19e42b7..39ecf83217 100644 --- a/src/resolve/resolved.conf.in +++ b/src/resolve/resolved.conf.in @@ -14,4 +14,5 @@ [Resolve] #DNS= #FallbackDNS=@DNS_SERVERS@ +#Domains= #LLMNR=yes |