123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667 |
- /*
- * Copyright (c) 2016 Fabien Siron <fabien.siron@epita.fr>
- * Copyright (c) 2016 Dmitry V. Levin <ldv@altlinux.org>
- * Copyright (c) 2016-2018 The strace developers.
- * All rights reserved.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
- #include "defs.h"
- #include "netlink.h"
- #include "nlattr.h"
- #include <linux/audit.h>
- #include <linux/rtnetlink.h>
- #include <linux/xfrm.h>
- #include "xlat/netlink_ack_flags.h"
- #include "xlat/netlink_delete_flags.h"
- #include "xlat/netlink_flags.h"
- #include "xlat/netlink_get_flags.h"
- #include "xlat/netlink_new_flags.h"
- #include "xlat/netlink_protocols.h"
- #include "xlat/netlink_types.h"
- #include "xlat/nf_acct_msg_types.h"
- #include "xlat/nf_cthelper_msg_types.h"
- #include "xlat/nf_ctnetlink_exp_msg_types.h"
- #include "xlat/nf_ctnetlink_msg_types.h"
- #include "xlat/nf_cttimeout_msg_types.h"
- #include "xlat/nf_ipset_msg_types.h"
- #include "xlat/nf_nft_compat_msg_types.h"
- #include "xlat/nf_nftables_msg_types.h"
- #include "xlat/nf_osf_msg_types.h"
- #include "xlat/nf_queue_msg_types.h"
- #include "xlat/nf_ulog_msg_types.h"
- #include "xlat/nl_audit_types.h"
- #include "xlat/nl_crypto_types.h"
- #include "xlat/nl_netfilter_subsys_ids.h"
- #include "xlat/nl_selinux_types.h"
- #include "xlat/nl_sock_diag_types.h"
- #include "xlat/nl_xfrm_types.h"
- #include "xlat/nlmsgerr_attrs.h"
-
- /*
- * Fetch a struct nlmsghdr from the given address.
- */
- static bool
- fetch_nlmsghdr(struct tcb *const tcp, struct nlmsghdr *const nlmsghdr,
- const kernel_ulong_t addr, const kernel_ulong_t len,
- const bool in_array)
- {
- if (len < sizeof(struct nlmsghdr)) {
- printstr_ex(tcp, addr, len, QUOTE_FORCE_HEX);
- return false;
- }
-
- if (tfetch_obj(tcp, addr, nlmsghdr))
- return true;
-
- if (in_array) {
- tprints("...");
- printaddr_comment(addr);
- } else {
- printaddr(addr);
- }
-
- return false;
- }
-
- static int
- get_fd_nl_family(struct tcb *const tcp, const int fd)
- {
- const unsigned long inode = getfdinode(tcp, fd);
- if (!inode)
- return -1;
-
- const char *const details = get_sockaddr_by_inode(tcp, fd, inode);
- if (!details)
- return -1;
-
- const char *const nl_details = STR_STRIP_PREFIX(details, "NETLINK:[");
- if (nl_details == details)
- return -1;
-
- const struct xlat *xlats = netlink_protocols;
- for (; xlats->str; ++xlats) {
- const char *name = STR_STRIP_PREFIX(xlats->str, "NETLINK_");
- if (!strncmp(nl_details, name, strlen(name)))
- return xlats->val;
- }
-
- if (*nl_details >= '0' && *nl_details <= '9')
- return atoi(nl_details);
-
- return -1;
- }
-
- static void
- decode_nlmsg_type_default(struct tcb *tcp, const struct xlat *const xlat,
- const uint16_t type,
- const char *const dflt)
- {
- printxval(xlat, type, dflt);
- }
-
- static void
- decode_nlmsg_type_generic(struct tcb *tcp, const struct xlat *const xlat,
- const uint16_t type,
- const char *const dflt)
- {
- printxval(genl_families_xlat(tcp), type, dflt);
- }
-
- static const struct {
- const struct xlat *const xlat;
- const char *const dflt;
- } nf_nlmsg_types[] = {
- [NFNL_SUBSYS_CTNETLINK] = {
- nf_ctnetlink_msg_types,
- "IPCTNL_MSG_CT_???"
- },
- [NFNL_SUBSYS_CTNETLINK_EXP] = {
- nf_ctnetlink_exp_msg_types,
- "IPCTNL_MSG_EXP_???"
- },
- [NFNL_SUBSYS_QUEUE] = { nf_queue_msg_types, "NFQNL_MSG_???" },
- [NFNL_SUBSYS_ULOG] = { nf_ulog_msg_types, "NFULNL_MSG_???" },
- [NFNL_SUBSYS_OSF] = { nf_osf_msg_types, "OSF_MSG_???" },
- [NFNL_SUBSYS_IPSET] = { nf_ipset_msg_types, "IPSET_CMD_???" },
- [NFNL_SUBSYS_ACCT] = { nf_acct_msg_types, "NFNL_MSG_ACCT_???" },
- [NFNL_SUBSYS_CTNETLINK_TIMEOUT] = {
- nf_cttimeout_msg_types,
- "IPCTNL_MSG_TIMEOUT_???"
- },
- [NFNL_SUBSYS_CTHELPER] = {
- nf_cthelper_msg_types,
- "NFNL_MSG_CTHELPER_???"
- },
- [NFNL_SUBSYS_NFTABLES] = { nf_nftables_msg_types, "NFT_MSG_???" },
- [NFNL_SUBSYS_NFT_COMPAT] = {
- nf_nft_compat_msg_types,
- "NFNL_MSG_COMPAT_???"
- }
- };
-
- static void
- decode_nlmsg_type_netfilter(struct tcb *tcp, const struct xlat *const xlat,
- const uint16_t type,
- const char *const dflt)
- {
- /* Reserved control nfnetlink messages first. */
- const char *const text = xlookup(nl_netfilter_msg_types, type);
- if (text) {
- print_xlat_ex(type, text, XLAT_STYLE_DEFAULT);
- return;
- }
-
- /*
- * Other netfilter message types are split
- * in two pieces: 8 bits subsystem and 8 bits type.
- */
- const uint8_t subsys_id = (uint8_t) (type >> 8);
- const uint8_t msg_type = (uint8_t) type;
-
- printxval(xlat, subsys_id, dflt);
-
- tprints("<<8|");
- if (subsys_id < ARRAY_SIZE(nf_nlmsg_types))
- printxval(nf_nlmsg_types[subsys_id].xlat,
- msg_type, nf_nlmsg_types[subsys_id].dflt);
- else
- tprintf("%#x", msg_type);
- }
-
- typedef void (*nlmsg_types_decoder_t)(struct tcb *, const struct xlat *,
- uint16_t type,
- const char *dflt);
-
- static const struct {
- const nlmsg_types_decoder_t decoder;
- const struct xlat *const xlat;
- const char *const dflt;
- } nlmsg_types[] = {
- [NETLINK_AUDIT] = { NULL, nl_audit_types, "AUDIT_???" },
- [NETLINK_CRYPTO] = { NULL, nl_crypto_types, "CRYPTO_MSG_???" },
- [NETLINK_GENERIC] = {
- decode_nlmsg_type_generic,
- NULL,
- "GENERIC_FAMILY_???"
- },
- [NETLINK_NETFILTER] = {
- decode_nlmsg_type_netfilter,
- nl_netfilter_subsys_ids,
- "NFNL_SUBSYS_???"
- },
- [NETLINK_ROUTE] = { NULL, nl_route_types, "RTM_???" },
- [NETLINK_SELINUX] = { NULL, nl_selinux_types, "SELNL_MSG_???" },
- [NETLINK_SOCK_DIAG] = { NULL, nl_sock_diag_types, "SOCK_DIAG_???" },
- [NETLINK_XFRM] = { NULL, nl_xfrm_types, "XFRM_MSG_???" }
- };
-
- /*
- * As all valid netlink families are positive integers, use unsigned int
- * for family here to filter out -1.
- */
- static void
- decode_nlmsg_type(struct tcb *tcp, const uint16_t type,
- const unsigned int family)
- {
- nlmsg_types_decoder_t decoder = decode_nlmsg_type_default;
- const struct xlat *xlat = netlink_types;
- const char *dflt = "NLMSG_???";
-
- /*
- * type < NLMSG_MIN_TYPE are reserved control messages
- * that need no family-specific decoding.
- */
- if (type >= NLMSG_MIN_TYPE && family < ARRAY_SIZE(nlmsg_types)) {
- if (nlmsg_types[family].decoder)
- decoder = nlmsg_types[family].decoder;
- if (nlmsg_types[family].xlat)
- xlat = nlmsg_types[family].xlat;
- if (nlmsg_types[family].dflt)
- dflt = nlmsg_types[family].dflt;
- }
-
- decoder(tcp, xlat, type, dflt);
- }
-
- static const struct xlat *
- decode_nlmsg_flags_crypto(const uint16_t type)
- {
- switch (type) {
- case CRYPTO_MSG_NEWALG:
- return netlink_new_flags;
- case CRYPTO_MSG_DELALG:
- case CRYPTO_MSG_DELRNG:
- return netlink_delete_flags;
- case CRYPTO_MSG_GETALG:
- return netlink_get_flags;
- }
-
- return NULL;
- }
-
- static const struct xlat *
- decode_nlmsg_flags_netfilter(const uint16_t type)
- {
- const uint8_t subsys_id = (uint8_t) (type >> 8);
- const uint8_t msg_type = (uint8_t) type;
-
- switch (subsys_id) {
- case NFNL_SUBSYS_CTNETLINK:
- switch (msg_type) {
- case IPCTNL_MSG_CT_NEW:
- return netlink_new_flags;
- case IPCTNL_MSG_CT_GET:
- case IPCTNL_MSG_CT_GET_CTRZERO:
- case IPCTNL_MSG_CT_GET_STATS_CPU:
- case IPCTNL_MSG_CT_GET_STATS:
- case IPCTNL_MSG_CT_GET_DYING:
- case IPCTNL_MSG_CT_GET_UNCONFIRMED:
- return netlink_get_flags;
- case IPCTNL_MSG_CT_DELETE:
- return netlink_delete_flags;
- }
- break;
- case NFNL_SUBSYS_CTNETLINK_EXP:
- switch (msg_type) {
- case IPCTNL_MSG_EXP_NEW:
- return netlink_new_flags;
- case IPCTNL_MSG_EXP_GET:
- case IPCTNL_MSG_EXP_GET_STATS_CPU:
- return netlink_get_flags;
- case IPCTNL_MSG_EXP_DELETE:
- return netlink_delete_flags;
- }
- break;
- case NFNL_SUBSYS_ACCT:
- switch (msg_type) {
- case NFNL_MSG_ACCT_NEW:
- return netlink_new_flags;
- case NFNL_MSG_ACCT_GET:
- case NFNL_MSG_ACCT_GET_CTRZERO:
- return netlink_get_flags;
- case NFNL_MSG_ACCT_DEL:
- return netlink_delete_flags;
- }
- break;
- case NFNL_SUBSYS_CTNETLINK_TIMEOUT:
- switch (msg_type) {
- case IPCTNL_MSG_TIMEOUT_NEW:
- return netlink_new_flags;
- case IPCTNL_MSG_TIMEOUT_GET:
- return netlink_get_flags;
- case IPCTNL_MSG_TIMEOUT_DELETE:
- return netlink_delete_flags;
- }
- break;
- case NFNL_SUBSYS_CTHELPER:
- switch (msg_type) {
- case NFNL_MSG_CTHELPER_NEW:
- return netlink_new_flags;
- case NFNL_MSG_CTHELPER_GET:
- return netlink_get_flags;
- case NFNL_MSG_CTHELPER_DEL:
- return netlink_delete_flags;
- }
- break;
- case NFNL_SUBSYS_NFTABLES:
- switch (msg_type) {
- case NFT_MSG_NEWTABLE:
- case NFT_MSG_NEWCHAIN:
- case NFT_MSG_NEWRULE:
- case NFT_MSG_NEWSET:
- case NFT_MSG_NEWSETELEM:
- case NFT_MSG_NEWGEN:
- case NFT_MSG_NEWOBJ:
- return netlink_new_flags;
- case NFT_MSG_GETTABLE:
- case NFT_MSG_GETCHAIN:
- case NFT_MSG_GETRULE:
- case NFT_MSG_GETSET:
- case NFT_MSG_GETSETELEM:
- case NFT_MSG_GETGEN:
- case NFT_MSG_GETOBJ:
- case NFT_MSG_GETOBJ_RESET:
- return netlink_get_flags;
- case NFT_MSG_DELTABLE:
- case NFT_MSG_DELCHAIN:
- case NFT_MSG_DELRULE:
- case NFT_MSG_DELSET:
- case NFT_MSG_DELSETELEM:
- case NFT_MSG_DELOBJ:
- return netlink_delete_flags;
- }
- break;
- case NFNL_SUBSYS_NFT_COMPAT:
- switch (msg_type) {
- case NFNL_MSG_COMPAT_GET:
- return netlink_get_flags;
- }
- break;
- }
-
- return NULL;
- }
-
- static const struct xlat *
- decode_nlmsg_flags_route(const uint16_t type)
- {
- /* RTM_DELACTION uses NLM_F_ROOT flags */
- if (type == RTM_DELACTION)
- return netlink_get_flags;
- switch (type & 3) {
- case 0:
- return netlink_new_flags;
- case 1:
- return netlink_delete_flags;
- case 2:
- return netlink_get_flags;
- }
-
- return NULL;
- }
-
- static const struct xlat *
- decode_nlmsg_flags_sock_diag(const uint16_t type)
- {
- return netlink_get_flags;
- }
-
- static const struct xlat *
- decode_nlmsg_flags_xfrm(const uint16_t type)
- {
- switch (type) {
- case XFRM_MSG_NEWSA:
- case XFRM_MSG_NEWPOLICY:
- case XFRM_MSG_NEWAE:
- case XFRM_MSG_NEWSADINFO:
- case XFRM_MSG_NEWSPDINFO:
- return netlink_new_flags;
- case XFRM_MSG_DELSA:
- case XFRM_MSG_DELPOLICY:
- return netlink_delete_flags;
- case XFRM_MSG_GETSA:
- case XFRM_MSG_GETPOLICY:
- case XFRM_MSG_GETAE:
- case XFRM_MSG_GETSADINFO:
- case XFRM_MSG_GETSPDINFO:
- return netlink_get_flags;
- }
-
- return NULL;
- }
-
- typedef const struct xlat *(*nlmsg_flags_decoder_t)(const uint16_t type);
-
- static const nlmsg_flags_decoder_t nlmsg_flags[] = {
- [NETLINK_CRYPTO] = decode_nlmsg_flags_crypto,
- [NETLINK_NETFILTER] = decode_nlmsg_flags_netfilter,
- [NETLINK_ROUTE] = decode_nlmsg_flags_route,
- [NETLINK_SOCK_DIAG] = decode_nlmsg_flags_sock_diag,
- [NETLINK_XFRM] = decode_nlmsg_flags_xfrm
- };
-
- /*
- * As all valid netlink families are positive integers, use unsigned int
- * for family here to filter out -1.
- */
- static void
- decode_nlmsg_flags(const uint16_t flags, const uint16_t type,
- const unsigned int family)
- {
- const struct xlat *table = NULL;
-
- if (type < NLMSG_MIN_TYPE) {
- if (type == NLMSG_ERROR)
- table = netlink_ack_flags;
- } else if (family < ARRAY_SIZE(nlmsg_flags) && nlmsg_flags[family])
- table = nlmsg_flags[family](type);
-
- printflags_ex(flags, "NLM_F_???", XLAT_STYLE_DEFAULT,
- netlink_flags, table, NULL);
- }
-
- static void
- print_nlmsghdr(struct tcb *tcp,
- const int fd,
- const int family,
- const struct nlmsghdr *const nlmsghdr)
- {
- /* print the whole structure regardless of its nlmsg_len */
-
- tprintf("{len=%u, type=", nlmsghdr->nlmsg_len);
-
- decode_nlmsg_type(tcp, nlmsghdr->nlmsg_type, family);
-
- tprints(", flags=");
- decode_nlmsg_flags(nlmsghdr->nlmsg_flags,
- nlmsghdr->nlmsg_type, family);
-
- tprintf(", seq=%u, pid=%u}", nlmsghdr->nlmsg_seq,
- nlmsghdr->nlmsg_pid);
- }
-
- static bool
- print_cookie(struct tcb *const tcp, void *const elem_buf,
- const size_t elem_size, void *const opaque_data)
- {
- tprintf("%" PRIu8, *(uint8_t *) elem_buf);
-
- return true;
- }
-
- static bool
- decode_nlmsgerr_attr_cookie(struct tcb *const tcp,
- const kernel_ulong_t addr,
- const unsigned int len,
- const void *const opaque_data)
- {
- uint8_t cookie;
- const size_t nmemb = len / sizeof(cookie);
-
- print_array(tcp, addr, nmemb, &cookie, sizeof(cookie),
- tfetch_mem, print_cookie, 0);
-
- return true;
- }
-
- static const nla_decoder_t nlmsgerr_nla_decoders[] = {
- [NLMSGERR_ATTR_MSG] = decode_nla_str,
- [NLMSGERR_ATTR_OFFS] = decode_nla_u32,
- [NLMSGERR_ATTR_COOKIE] = decode_nlmsgerr_attr_cookie
- };
-
- static void
- decode_nlmsghdr_with_payload(struct tcb *const tcp,
- const int fd,
- const int family,
- const struct nlmsghdr *const nlmsghdr,
- const kernel_ulong_t addr,
- const kernel_ulong_t len);
-
- static void
- decode_nlmsgerr(struct tcb *const tcp,
- const int fd,
- const int family,
- kernel_ulong_t addr,
- unsigned int len,
- const bool capped)
- {
- struct nlmsgerr err;
-
- if (len < sizeof(err.error)) {
- printstr_ex(tcp, addr, len, QUOTE_FORCE_HEX);
- return;
- }
-
- if (umove_or_printaddr(tcp, addr, &err.error))
- return;
-
- tprints("{error=");
- if (err.error < 0 && (unsigned) -err.error < nerrnos) {
- tprintf("-%s", errnoent[-err.error]);
- } else {
- tprintf("%d", err.error);
- }
-
- addr += offsetof(struct nlmsgerr, msg);
- len -= offsetof(struct nlmsgerr, msg);
-
- if (len) {
- tprints(", msg=");
- if (fetch_nlmsghdr(tcp, &err.msg, addr, len, false)) {
- unsigned int payload =
- capped ? sizeof(err.msg) : err.msg.nlmsg_len;
- if (payload > len)
- payload = len;
-
- decode_nlmsghdr_with_payload(tcp, fd, family,
- &err.msg, addr, payload);
- if (len > payload) {
- tprints(", ");
- decode_nlattr(tcp, addr + payload,
- len - payload, nlmsgerr_attrs,
- "NLMSGERR_ATTR_???",
- nlmsgerr_nla_decoders,
- ARRAY_SIZE(nlmsgerr_nla_decoders),
- NULL);
- }
- }
- }
-
- tprints("}");
- }
-
- static const netlink_decoder_t netlink_decoders[] = {
- #ifdef HAVE_LINUX_CRYPTOUSER_H
- [NETLINK_CRYPTO] = decode_netlink_crypto,
- #endif
- #ifdef HAVE_LINUX_NETFILTER_NFNETLINK_H
- [NETLINK_NETFILTER] = decode_netlink_netfilter,
- #endif
- [NETLINK_ROUTE] = decode_netlink_route,
- [NETLINK_SELINUX] = decode_netlink_selinux,
- [NETLINK_SOCK_DIAG] = decode_netlink_sock_diag
- };
-
- static void
- decode_payload(struct tcb *const tcp,
- const int fd,
- const int family,
- const struct nlmsghdr *const nlmsghdr,
- const kernel_ulong_t addr,
- const unsigned int len)
- {
- if (nlmsghdr->nlmsg_type == NLMSG_ERROR) {
- decode_nlmsgerr(tcp, fd, family, addr, len,
- nlmsghdr->nlmsg_flags & NLM_F_CAPPED);
- return;
- }
-
- /*
- * While most of NLMSG_DONE messages indeed have payloads
- * containing just a single integer, there are few exceptions,
- * so pass payloads of NLMSG_DONE messages to family-specific
- * netlink payload decoders.
- *
- * Other types of reserved control messages need no family-specific
- * netlink payload decoding.
- */
- if ((nlmsghdr->nlmsg_type >= NLMSG_MIN_TYPE
- || nlmsghdr->nlmsg_type == NLMSG_DONE)
- && (unsigned int) family < ARRAY_SIZE(netlink_decoders)
- && netlink_decoders[family]
- && netlink_decoders[family](tcp, nlmsghdr, addr, len)) {
- return;
- }
-
- if (nlmsghdr->nlmsg_type == NLMSG_DONE && len == sizeof(int)) {
- int num;
-
- if (!umove_or_printaddr(tcp, addr, &num))
- tprintf("%d", num);
- return;
- }
-
- printstr_ex(tcp, addr, len, QUOTE_FORCE_HEX);
- }
-
- static void
- decode_nlmsghdr_with_payload(struct tcb *const tcp,
- const int fd,
- const int family,
- const struct nlmsghdr *const nlmsghdr,
- const kernel_ulong_t addr,
- const kernel_ulong_t len)
- {
- const unsigned int nlmsg_len = MIN(nlmsghdr->nlmsg_len, len);
-
- if (nlmsg_len > NLMSG_HDRLEN)
- tprints("{");
-
- print_nlmsghdr(tcp, fd, family, nlmsghdr);
-
- if (nlmsg_len > NLMSG_HDRLEN) {
- tprints(", ");
- decode_payload(tcp, fd, family, nlmsghdr, addr + NLMSG_HDRLEN,
- nlmsg_len - NLMSG_HDRLEN);
- tprints("}");
- }
- }
-
- void
- decode_netlink(struct tcb *const tcp,
- const int fd,
- kernel_ulong_t addr,
- kernel_ulong_t len)
- {
- const int family = get_fd_nl_family(tcp, fd);
-
- if (family == NETLINK_KOBJECT_UEVENT) {
- decode_netlink_kobject_uevent(tcp, addr, len);
- return;
- }
-
- struct nlmsghdr nlmsghdr;
- bool is_array = false;
- unsigned int elt;
-
- for (elt = 0; fetch_nlmsghdr(tcp, &nlmsghdr, addr, len, is_array);
- elt++) {
- if (abbrev(tcp) && elt == max_strlen) {
- tprints("...");
- break;
- }
-
- unsigned int nlmsg_len = NLMSG_ALIGN(nlmsghdr.nlmsg_len);
- kernel_ulong_t next_addr = 0;
- kernel_ulong_t next_len = 0;
-
- if (nlmsghdr.nlmsg_len >= NLMSG_HDRLEN) {
- next_len = (len >= nlmsg_len) ? len - nlmsg_len : 0;
-
- if (next_len && addr + nlmsg_len > addr)
- next_addr = addr + nlmsg_len;
- }
-
- if (!is_array && next_addr) {
- tprints("[");
- is_array = true;
- }
-
- decode_nlmsghdr_with_payload(tcp, fd, family,
- &nlmsghdr, addr, len);
-
- if (!next_addr)
- break;
-
- tprints(", ");
- addr = next_addr;
- len = next_len;
- }
-
- if (is_array) {
- tprints("]");
- }
- }
|