| /* SPDX-License-Identifier: LGPL-2.1-only */ |
| /* |
| * Copyright (c) 2003-2012 Thomas Graf <[email protected]> |
| */ |
| |
| /** |
| * @ingroup core |
| * @defgroup msg Message Construction & Parsing |
| * Netlink Message Construction/Parsing Interface |
| * |
| * Related sections in the development guide: |
| * - @core_doc{_message_parsing_amp_construction,Message Parsing & Construction} |
| * |
| * @{ |
| * |
| * Header |
| * ------ |
| * ~~~~{.c} |
| * #include <netlink/msg.h> |
| * ~~~~ |
| */ |
| |
| #include "nl-default.h" |
| |
| #include <ctype.h> |
| |
| #include <linux/socket.h> |
| |
| #include <netlink/netlink.h> |
| #include <netlink/utils.h> |
| #include <netlink/cache.h> |
| #include <netlink/attr.h> |
| |
| #include "nl-core.h" |
| #include "nl-priv-dynamic-core/nl-core.h" |
| #include "nl-priv-dynamic-core/cache-api.h" |
| #include "nl-aux-core/nl-core.h" |
| |
| static size_t default_msg_size; /* GLOBAL! */ |
| |
| static void _nl_init init_msg_size(void) |
| { |
| default_msg_size = getpagesize(); |
| } |
| |
| /** |
| * @name Size Calculations |
| * @{ |
| */ |
| |
| /** |
| * Calculates size of netlink message based on payload length. |
| * @arg payload Length of payload |
| * |
| * @return size of netlink message without padding. |
| */ |
| int nlmsg_size(int payload) |
| { |
| return NLMSG_HDRLEN + payload; |
| } |
| |
| static int nlmsg_msg_size(int payload) |
| { |
| return nlmsg_size(payload); |
| } |
| |
| /** |
| * Calculates size of netlink message including padding based on payload length |
| * @arg payload Length of payload |
| * |
| * This function is idential to nlmsg_size() + nlmsg_padlen(). |
| * |
| * @return Size of netlink message including padding. |
| */ |
| int nlmsg_total_size(int payload) |
| { |
| return NLMSG_ALIGN(nlmsg_msg_size(payload)); |
| } |
| |
| /** |
| * Size of padding that needs to be added at end of message |
| * @arg payload Length of payload |
| * |
| * Calculates the number of bytes of padding which is required to be added to |
| * the end of the message to ensure that the next netlink message header begins |
| * properly aligned to NLMSG_ALIGNTO. |
| * |
| * @return Number of bytes of padding needed. |
| */ |
| int nlmsg_padlen(int payload) |
| { |
| return nlmsg_total_size(payload) - nlmsg_msg_size(payload); |
| } |
| |
| /** @} */ |
| |
| /** |
| * @name Access to Message Payload |
| * @{ |
| */ |
| |
| /** |
| * Return pointer to message payload |
| * @arg nlh Netlink message header |
| * |
| * @return Pointer to start of message payload. |
| */ |
| void *nlmsg_data(const struct nlmsghdr *nlh) |
| { |
| return (unsigned char *) nlh + NLMSG_HDRLEN; |
| } |
| |
| void *nlmsg_tail(const struct nlmsghdr *nlh) |
| { |
| return (unsigned char *) nlh + NLMSG_ALIGN(nlh->nlmsg_len); |
| } |
| |
| /** |
| * Return length of message payload |
| * @arg nlh Netlink message header |
| * |
| * @return Length of message payload in bytes. |
| */ |
| int nlmsg_datalen(const struct nlmsghdr *nlh) |
| { |
| return nlh->nlmsg_len - NLMSG_HDRLEN; |
| } |
| |
| static int nlmsg_len(const struct nlmsghdr *nlh) |
| { |
| return nlmsg_datalen(nlh); |
| } |
| |
| /** @} */ |
| |
| /** |
| * @name Attribute Access |
| * @{ |
| */ |
| |
| /** |
| * head of attributes data |
| * @arg nlh netlink message header |
| * @arg hdrlen length of family specific header |
| */ |
| struct nlattr *nlmsg_attrdata(const struct nlmsghdr *nlh, int hdrlen) |
| { |
| unsigned char *data = nlmsg_data(nlh); |
| return (struct nlattr *) (data + NLMSG_ALIGN(hdrlen)); |
| } |
| |
| /** |
| * length of attributes data |
| * @arg nlh netlink message header |
| * @arg hdrlen length of family specific header |
| */ |
| int nlmsg_attrlen(const struct nlmsghdr *nlh, int hdrlen) |
| { |
| return _NL_MAX(nlmsg_len(nlh) - NLMSG_ALIGN(hdrlen), 0u); |
| } |
| |
| /** @} */ |
| |
| /** |
| * @name Message Parsing |
| * @{ |
| */ |
| |
| int nlmsg_valid_hdr(const struct nlmsghdr *nlh, int hdrlen) |
| { |
| int s; |
| |
| s = nlmsg_msg_size(hdrlen); |
| if (s < 0 || nlh->nlmsg_len < ((unsigned)s)) |
| return 0; |
| |
| return 1; |
| } |
| |
| /** |
| * check if the netlink message fits into the remaining bytes |
| * @arg nlh netlink message header |
| * @arg remaining number of bytes remaining in message stream |
| */ |
| int nlmsg_ok(const struct nlmsghdr *nlh, int remaining) |
| { |
| return (remaining >= (int)sizeof(struct nlmsghdr) && |
| nlh->nlmsg_len >= sizeof(struct nlmsghdr) && |
| nlh->nlmsg_len <= ((unsigned)remaining)); |
| } |
| |
| /** |
| * next netlink message in message stream |
| * @arg nlh netlink message header |
| * @arg remaining number of bytes remaining in message stream |
| * |
| * @returns the next netlink message in the message stream and |
| * decrements remaining by the size of the current message. |
| */ |
| struct nlmsghdr *nlmsg_next(struct nlmsghdr *nlh, int *remaining) |
| { |
| int totlen = NLMSG_ALIGN(nlh->nlmsg_len); |
| |
| *remaining -= totlen; |
| |
| return (struct nlmsghdr *) ((unsigned char *) nlh + totlen); |
| } |
| |
| /** |
| * parse attributes of a netlink message |
| * @arg nlh netlink message header |
| * @arg hdrlen length of family specific header |
| * @arg tb destination array with maxtype+1 elements |
| * @arg maxtype maximum attribute type to be expected |
| * @arg policy validation policy |
| * |
| * See nla_parse() |
| */ |
| int nlmsg_parse(struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[], |
| int maxtype, const struct nla_policy *policy) |
| { |
| if (!nlmsg_valid_hdr(nlh, hdrlen)) |
| return -NLE_MSG_TOOSHORT; |
| |
| return nla_parse(tb, maxtype, nlmsg_attrdata(nlh, hdrlen), |
| nlmsg_attrlen(nlh, hdrlen), policy); |
| } |
| |
| /** |
| * nlmsg_find_attr - find a specific attribute in a netlink message |
| * @arg nlh netlink message header |
| * @arg hdrlen length of familiy specific header |
| * @arg attrtype type of attribute to look for |
| * |
| * Returns the first attribute which matches the specified type. |
| */ |
| struct nlattr *nlmsg_find_attr(struct nlmsghdr *nlh, int hdrlen, int attrtype) |
| { |
| return nla_find(nlmsg_attrdata(nlh, hdrlen), |
| nlmsg_attrlen(nlh, hdrlen), attrtype); |
| } |
| |
| /** |
| * nlmsg_validate - validate a netlink message including attributes |
| * @arg nlh netlinket message header |
| * @arg hdrlen length of familiy specific header |
| * @arg maxtype maximum attribute type to be expected |
| * @arg policy validation policy |
| */ |
| int nlmsg_validate(struct nlmsghdr *nlh, int hdrlen, int maxtype, |
| const struct nla_policy *policy) |
| { |
| if (!nlmsg_valid_hdr(nlh, hdrlen)) |
| return -NLE_MSG_TOOSHORT; |
| |
| return nla_validate(nlmsg_attrdata(nlh, hdrlen), |
| nlmsg_attrlen(nlh, hdrlen), maxtype, policy); |
| } |
| |
| /** @} */ |
| |
| /** |
| * @name Message Building/Access |
| * @{ |
| */ |
| |
| static struct nl_msg *__nlmsg_alloc(size_t len) |
| { |
| struct nl_msg *nm; |
| |
| if (len < sizeof(struct nlmsghdr)) |
| len = sizeof(struct nlmsghdr); |
| |
| nm = calloc(1, sizeof(*nm)); |
| if (!nm) |
| goto errout; |
| |
| nm->nm_refcnt = 1; |
| |
| nm->nm_nlh = calloc(1, len); |
| if (!nm->nm_nlh) |
| goto errout; |
| |
| nm->nm_protocol = -1; |
| nm->nm_size = len; |
| nm->nm_nlh->nlmsg_len = nlmsg_total_size(0); |
| |
| NL_DBG(2, "msg %p: Allocated new message, maxlen=%zu\n", nm, len); |
| |
| return nm; |
| errout: |
| free(nm); |
| return NULL; |
| } |
| |
| /** |
| * Allocate a new netlink message with the default maximum payload size. |
| * |
| * Allocates a new netlink message without any further payload. The |
| * maximum payload size defaults to PAGESIZE or as otherwise specified |
| * with nlmsg_set_default_size(). |
| * |
| * @return Newly allocated netlink message or NULL. |
| */ |
| struct nl_msg *nlmsg_alloc(void) |
| { |
| return __nlmsg_alloc(default_msg_size); |
| } |
| |
| /** |
| * Allocate a new netlink message with maximum payload size specified. |
| */ |
| struct nl_msg *nlmsg_alloc_size(size_t max) |
| { |
| return __nlmsg_alloc(max); |
| } |
| |
| /** |
| * Allocate a new netlink message and inherit netlink message header |
| * @arg hdr Netlink message header template |
| * |
| * Allocates a new netlink message and inherits the original message |
| * header. If \a hdr is not NULL it will be used as a template for |
| * the netlink message header, otherwise the header is left blank. |
| * |
| * @return Newly allocated netlink message or NULL |
| */ |
| struct nl_msg *nlmsg_inherit(struct nlmsghdr *hdr) |
| { |
| struct nl_msg *nm; |
| |
| nm = nlmsg_alloc(); |
| if (nm && hdr) { |
| struct nlmsghdr *new = nm->nm_nlh; |
| |
| new->nlmsg_type = hdr->nlmsg_type; |
| new->nlmsg_flags = hdr->nlmsg_flags; |
| new->nlmsg_seq = hdr->nlmsg_seq; |
| new->nlmsg_pid = hdr->nlmsg_pid; |
| } |
| |
| return nm; |
| } |
| |
| /** |
| * Allocate a new netlink message |
| * @arg nlmsgtype Netlink message type |
| * @arg flags Message flags. |
| * |
| * @return Newly allocated netlink message or NULL. |
| */ |
| struct nl_msg *nlmsg_alloc_simple(int nlmsgtype, int flags) |
| { |
| struct nl_msg *msg; |
| struct nlmsghdr nlh = { |
| .nlmsg_type = nlmsgtype, |
| .nlmsg_flags = flags, |
| .nlmsg_seq = NL_AUTO_SEQ, |
| .nlmsg_pid = NL_AUTO_PID, |
| }; |
| |
| msg = nlmsg_inherit(&nlh); |
| if (msg) |
| NL_DBG(2, "msg %p: Allocated new simple message\n", msg); |
| |
| return msg; |
| } |
| |
| /** |
| * Set the default maximum message payload size for allocated messages |
| * @arg max Size of payload in bytes. |
| */ |
| void nlmsg_set_default_size(size_t max) |
| { |
| size_t s; |
| |
| s = nlmsg_total_size(0); |
| if (max < s) |
| max = s; |
| |
| default_msg_size = max; |
| } |
| |
| /** |
| * Convert a netlink message received from a netlink socket to a nl_msg |
| * @arg hdr Netlink message received from netlink socket. |
| * |
| * Allocates a new netlink message and copies all of the data pointed to |
| * by \a hdr into the new message object. |
| * |
| * @return Newly allocated netlink message or NULL. |
| */ |
| struct nl_msg *nlmsg_convert(struct nlmsghdr *hdr) |
| { |
| struct nl_msg *nm; |
| |
| nm = __nlmsg_alloc(NLMSG_ALIGN(hdr->nlmsg_len)); |
| if (!nm) |
| return NULL; |
| |
| memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len); |
| |
| return nm; |
| } |
| |
| /** |
| * Reserve room for additional data in a netlink message |
| * @arg n netlink message |
| * @arg len length of additional data to reserve room for |
| * @arg pad number of bytes to align data to |
| * |
| * Reserves room for additional data at the tail of the an |
| * existing netlink message. Eventual padding required will |
| * be zeroed out. |
| * |
| * @return Pointer to start of additional data tailroom or NULL. |
| */ |
| void *nlmsg_reserve(struct nl_msg *n, size_t len, int pad) |
| { |
| char *buf = (char *) n->nm_nlh; |
| size_t nlmsg_len = n->nm_nlh->nlmsg_len; |
| size_t tlen; |
| |
| if (len > n->nm_size) |
| return NULL; |
| |
| tlen = pad ? ((len + (pad - 1)) & ~(pad - 1)) : len; |
| |
| if ((tlen + nlmsg_len) > n->nm_size) |
| return NULL; |
| |
| buf += nlmsg_len; |
| n->nm_nlh->nlmsg_len += tlen; |
| |
| if (tlen > len) |
| memset(buf + len, 0, tlen - len); |
| |
| NL_DBG(2, "msg %p: Reserved %zu (%zu) bytes, pad=%d, nlmsg_len=%d\n", |
| n, tlen, len, pad, n->nm_nlh->nlmsg_len); |
| |
| return buf; |
| } |
| |
| /** |
| * Append data to tail of a netlink message |
| * @arg n netlink message |
| * @arg data data to add |
| * @arg len length of data |
| * @arg pad Number of bytes to align data to. |
| * |
| * Extends the netlink message as needed and appends the data of given |
| * length to the message. |
| * |
| * @return 0 on success or a negative error code |
| */ |
| int nlmsg_append(struct nl_msg *n, void *data, size_t len, int pad) |
| { |
| void *tmp; |
| |
| tmp = nlmsg_reserve(n, len, pad); |
| if (tmp == NULL) |
| return -NLE_NOMEM; |
| |
| memcpy(tmp, data, len); |
| NL_DBG(2, "msg %p: Appended %zu bytes with padding %d\n", n, len, pad); |
| |
| return 0; |
| } |
| |
| /** |
| * Expand maximum payload size of a netlink message |
| * @arg n Netlink message. |
| * @arg newlen New maximum payload size. |
| * |
| * Reallocates the payload section of a netlink message and increases |
| * the maximum payload size of the message. |
| * |
| * @note Any pointers pointing to old payload block will be stale and |
| * need to be refetched. Therfore, do not expand while constructing |
| * nested attributes or while reserved data blocks are held. |
| * |
| * @return 0 on success or a negative error code. |
| */ |
| int nlmsg_expand(struct nl_msg *n, size_t newlen) |
| { |
| void *tmp; |
| |
| if (newlen <= n->nm_size) |
| return -NLE_INVAL; |
| |
| tmp = realloc(n->nm_nlh, newlen); |
| if (tmp == NULL) |
| return -NLE_NOMEM; |
| |
| n->nm_nlh = tmp; |
| n->nm_size = newlen; |
| |
| return 0; |
| } |
| |
| /** |
| * Add a netlink message header to a netlink message |
| * @arg n netlink message |
| * @arg pid netlink process id or NL_AUTO_PID |
| * @arg seq sequence number of message or NL_AUTO_SEQ |
| * @arg type message type |
| * @arg payload length of message payload |
| * @arg flags message flags |
| * |
| * Adds or overwrites the netlink message header in an existing message |
| * object. If \a payload is greater-than zero additional room will be |
| * reserved, f.e. for family specific headers. It can be accesed via |
| * nlmsg_data(). |
| * |
| * @return A pointer to the netlink message header or NULL. |
| */ |
| struct nlmsghdr *nlmsg_put(struct nl_msg *n, uint32_t pid, uint32_t seq, |
| int type, int payload, int flags) |
| { |
| struct nlmsghdr *nlh; |
| |
| if (n->nm_nlh->nlmsg_len < NLMSG_HDRLEN) |
| BUG(); |
| |
| nlh = (struct nlmsghdr *) n->nm_nlh; |
| nlh->nlmsg_type = type; |
| nlh->nlmsg_flags = flags; |
| nlh->nlmsg_pid = pid; |
| nlh->nlmsg_seq = seq; |
| |
| NL_DBG(2, "msg %p: Added netlink header type=%d, flags=%d, pid=%d, " |
| "seq=%d\n", n, type, flags, pid, seq); |
| |
| if (payload > 0 && |
| nlmsg_reserve(n, payload, NLMSG_ALIGNTO) == NULL) |
| return NULL; |
| |
| return nlh; |
| } |
| |
| /** |
| * Return actual netlink message |
| * @arg n netlink message |
| * |
| * Returns the actual netlink message casted to the type of the netlink |
| * message header. |
| * |
| * @return A pointer to the netlink message. |
| */ |
| struct nlmsghdr *nlmsg_hdr(struct nl_msg *n) |
| { |
| return n->nm_nlh; |
| } |
| |
| /** |
| * Acquire a reference on a netlink message |
| * @arg msg message to acquire reference from |
| */ |
| void nlmsg_get(struct nl_msg *msg) |
| { |
| msg->nm_refcnt++; |
| NL_DBG(4, "New reference to message %p, total %d\n", |
| msg, msg->nm_refcnt); |
| } |
| |
| /** |
| * Release a reference from an netlink message |
| * @arg msg message to release reference from |
| * |
| * Frees memory after the last reference has been released. |
| */ |
| void nlmsg_free(struct nl_msg *msg) |
| { |
| if (!msg) |
| return; |
| |
| msg->nm_refcnt--; |
| NL_DBG(4, "Returned message reference %p, %d remaining\n", |
| msg, msg->nm_refcnt); |
| |
| if (msg->nm_refcnt < 0) |
| BUG(); |
| |
| if (msg->nm_refcnt <= 0) { |
| free(msg->nm_nlh); |
| NL_DBG(2, "msg %p: Freed\n", msg); |
| free(msg); |
| } |
| } |
| |
| /** @} */ |
| |
| /** |
| * @name Attributes |
| * @{ |
| */ |
| |
| void nlmsg_set_proto(struct nl_msg *msg, int protocol) |
| { |
| msg->nm_protocol = protocol; |
| } |
| |
| int nlmsg_get_proto(struct nl_msg *msg) |
| { |
| return msg->nm_protocol; |
| } |
| |
| size_t nlmsg_get_max_size(struct nl_msg *msg) |
| { |
| return msg->nm_size; |
| } |
| |
| void nlmsg_set_src(struct nl_msg *msg, struct sockaddr_nl *addr) |
| { |
| memcpy(&msg->nm_src, addr, sizeof(*addr)); |
| } |
| |
| struct sockaddr_nl *nlmsg_get_src(struct nl_msg *msg) |
| { |
| return &msg->nm_src; |
| } |
| |
| void nlmsg_set_dst(struct nl_msg *msg, struct sockaddr_nl *addr) |
| { |
| memcpy(&msg->nm_dst, addr, sizeof(*addr)); |
| } |
| |
| struct sockaddr_nl *nlmsg_get_dst(struct nl_msg *msg) |
| { |
| return &msg->nm_dst; |
| } |
| |
| void nlmsg_set_creds(struct nl_msg *msg, struct ucred *creds) |
| { |
| memcpy(&msg->nm_creds, creds, sizeof(*creds)); |
| msg->nm_flags |= NL_MSG_CRED_PRESENT; |
| } |
| |
| struct ucred *nlmsg_get_creds(struct nl_msg *msg) |
| { |
| if (msg->nm_flags & NL_MSG_CRED_PRESENT) |
| return &msg->nm_creds; |
| return NULL; |
| } |
| |
| /** @} */ |
| |
| /** |
| * @name Netlink Message Type Translations |
| * @{ |
| */ |
| |
| static const struct trans_tbl nl_msgtypes[] = { |
| __ADD(NLMSG_NOOP,NOOP), |
| __ADD(NLMSG_ERROR,ERROR), |
| __ADD(NLMSG_DONE,DONE), |
| __ADD(NLMSG_OVERRUN,OVERRUN), |
| }; |
| |
| char *nl_nlmsgtype2str(int type, char *buf, size_t size) |
| { |
| return __type2str(type, buf, size, nl_msgtypes, |
| ARRAY_SIZE(nl_msgtypes)); |
| } |
| |
| int nl_str2nlmsgtype(const char *name) |
| { |
| return __str2type(name, nl_msgtypes, ARRAY_SIZE(nl_msgtypes)); |
| } |
| |
| /** @} */ |
| |
| /** |
| * @name Netlink Message Flags Translations |
| * @{ |
| */ |
| |
| char *nl_nlmsg_flags2str(int flags, char *buf, size_t len) |
| { |
| memset(buf, 0, len); |
| |
| #define PRINT_FLAG(f) \ |
| if (flags & NLM_F_##f) { \ |
| flags &= ~NLM_F_##f; \ |
| strncat(buf, #f, len - strlen(buf) - 1); \ |
| if (flags) \ |
| strncat(buf, ",", len - strlen(buf) - 1); \ |
| } |
| |
| PRINT_FLAG(REQUEST); |
| PRINT_FLAG(MULTI); |
| PRINT_FLAG(ACK); |
| PRINT_FLAG(ECHO); |
| PRINT_FLAG(ROOT); |
| PRINT_FLAG(MATCH); |
| PRINT_FLAG(ATOMIC); |
| PRINT_FLAG(REPLACE); |
| PRINT_FLAG(EXCL); |
| PRINT_FLAG(CREATE); |
| PRINT_FLAG(APPEND); |
| |
| if (flags) { |
| char s[32]; |
| snprintf(s, sizeof(s), "0x%x", flags); |
| strncat(buf, s, len - strlen(buf) - 1); |
| } |
| #undef PRINT_FLAG |
| |
| return buf; |
| } |
| |
| /** @} */ |
| |
| /** |
| * @name Direct Parsing |
| * @{ |
| */ |
| |
| /** @cond SKIP */ |
| struct dp_xdata { |
| void (*cb)(struct nl_object *, void *); |
| void *arg; |
| }; |
| /** @endcond */ |
| |
| static int parse_cb(struct nl_object *obj, struct nl_parser_param *p) |
| { |
| struct dp_xdata *x = p->pp_arg; |
| |
| x->cb(obj, x->arg); |
| return 0; |
| } |
| |
| int nl_msg_parse(struct nl_msg *msg, void (*cb)(struct nl_object *, void *), |
| void *arg) |
| { |
| struct nl_cache_ops *ops; |
| struct nl_parser_param p = { |
| .pp_cb = parse_cb |
| }; |
| struct dp_xdata x = { |
| .cb = cb, |
| .arg = arg, |
| }; |
| int err; |
| |
| ops = nl_cache_ops_associate_safe(nlmsg_get_proto(msg), |
| nlmsg_hdr(msg)->nlmsg_type); |
| if (ops == NULL) |
| return -NLE_MSGTYPE_NOSUPPORT; |
| p.pp_arg = &x; |
| |
| err = nl_cache_parse(ops, NULL, nlmsg_hdr(msg), &p); |
| nl_cache_ops_put(ops); |
| |
| return err; |
| } |
| |
| /** @} */ |
| |
| /** |
| * @name Dumping |
| * @{ |
| */ |
| |
| static void prefix_line(FILE *ofd, int prefix) |
| { |
| int i; |
| |
| for (i = 0; i < prefix; i++) |
| fprintf(ofd, " "); |
| } |
| |
| static inline void dump_hex(FILE *ofd, char *start, int len, int prefix) |
| { |
| int i, a, c, limit; |
| char ascii[21] = {0}; |
| |
| limit = 16 - (prefix * 2); |
| prefix_line(ofd, prefix); |
| fprintf(ofd, " "); |
| |
| for (i = 0, a = 0, c = 0; i < len; i++) { |
| int v = *(uint8_t *) (start + i); |
| |
| fprintf(ofd, "%02x ", v); |
| ascii[a++] = isprint(v) ? v : '.'; |
| |
| if (++c >= limit) { |
| fprintf(ofd, "%s\n", ascii); |
| if (i < (len - 1)) { |
| prefix_line(ofd, prefix); |
| fprintf(ofd, " "); |
| } |
| a = c = 0; |
| memset(ascii, 0, sizeof(ascii)); |
| } |
| } |
| |
| if (c != 0) { |
| for (i = 0; i < (limit - c); i++) |
| fprintf(ofd, " "); |
| fprintf(ofd, "%s\n", ascii); |
| } |
| } |
| |
| static void print_hdr(FILE *ofd, struct nl_msg *msg) |
| { |
| struct nlmsghdr *nlh = nlmsg_hdr(msg); |
| struct nl_cache_ops *ops; |
| struct nl_msgtype *mt; |
| char buf[128]; |
| |
| fprintf(ofd, " .nlmsg_len = %d\n", nlh->nlmsg_len); |
| |
| ops = nl_cache_ops_associate_safe(nlmsg_get_proto(msg), nlh->nlmsg_type); |
| if (ops) { |
| mt = nl_msgtype_lookup(ops, nlh->nlmsg_type); |
| if (!mt) |
| BUG(); |
| |
| snprintf(buf, sizeof(buf), "%s::%s", ops->co_name, mt->mt_name); |
| nl_cache_ops_put(ops); |
| } else |
| nl_nlmsgtype2str(nlh->nlmsg_type, buf, sizeof(buf)); |
| |
| fprintf(ofd, " .type = %d <%s>\n", nlh->nlmsg_type, buf); |
| fprintf(ofd, " .flags = %d <%s>\n", nlh->nlmsg_flags, |
| nl_nlmsg_flags2str(nlh->nlmsg_flags, buf, sizeof(buf))); |
| fprintf(ofd, " .seq = %d\n", nlh->nlmsg_seq); |
| fprintf(ofd, " .port = %d\n", nlh->nlmsg_pid); |
| |
| } |
| |
| static void print_genl_hdr(FILE *ofd, void *start) |
| { |
| struct genlmsghdr *ghdr = start; |
| |
| fprintf(ofd, " [GENERIC NETLINK HEADER] %zu octets\n", GENL_HDRLEN); |
| fprintf(ofd, " .cmd = %u\n", ghdr->cmd); |
| fprintf(ofd, " .version = %u\n", ghdr->version); |
| fprintf(ofd, " .unused = %#x\n", ghdr->reserved); |
| } |
| |
| static void *print_genl_msg(struct nl_msg *msg, FILE *ofd, struct nlmsghdr *hdr, |
| struct nl_cache_ops *ops, int *payloadlen) |
| { |
| char *data = nlmsg_data(hdr); |
| |
| if (*payloadlen < (int)GENL_HDRLEN) |
| return data; |
| |
| print_genl_hdr(ofd, data); |
| |
| *payloadlen -= GENL_HDRLEN; |
| data += GENL_HDRLEN; |
| |
| if (ops) { |
| int hdrsize = ops->co_hdrsize - GENL_HDRLEN; |
| |
| if (hdrsize > 0) { |
| if (*payloadlen < hdrsize) |
| return data; |
| |
| fprintf(ofd, " [HEADER] %d octets\n", hdrsize); |
| dump_hex(ofd, data, hdrsize, 0); |
| |
| *payloadlen -= hdrsize; |
| data += hdrsize; |
| } |
| } |
| |
| return data; |
| } |
| |
| static void dump_attr(FILE *ofd, struct nlattr *attr, int prefix) |
| { |
| int len = nla_len(attr); |
| |
| dump_hex(ofd, nla_data(attr), len, prefix); |
| } |
| |
| static void dump_attrs(FILE *ofd, struct nlattr *attrs, int attrlen, |
| int prefix) |
| { |
| int rem; |
| struct nlattr *nla; |
| |
| nla_for_each_attr(nla, attrs, attrlen, rem) { |
| int padlen, alen = nla_len(nla); |
| |
| prefix_line(ofd, prefix); |
| |
| if (nla->nla_type == 0) |
| fprintf(ofd, " [ATTR PADDING] %d octets\n", alen); |
| else |
| fprintf(ofd, " [ATTR %02d%s] %d octets\n", nla_type(nla), |
| nla_is_nested(nla) ? " NESTED" : "", |
| alen); |
| |
| if (nla_is_nested(nla)) |
| dump_attrs(ofd, nla_data(nla), alen, prefix+1); |
| else |
| dump_attr(ofd, nla, prefix); |
| |
| padlen = nla_padlen(alen); |
| if (padlen > 0) { |
| prefix_line(ofd, prefix); |
| fprintf(ofd, " [PADDING] %d octets\n", |
| padlen); |
| dump_hex(ofd, (char *) nla_data(nla) + alen, |
| padlen, prefix); |
| } |
| } |
| |
| if (rem) { |
| prefix_line(ofd, prefix); |
| fprintf(ofd, " [LEFTOVER] %d octets\n", rem); |
| } |
| } |
| |
| static void dump_error_msg(struct nl_msg *msg, FILE *ofd) |
| { |
| struct nlmsghdr *hdr = nlmsg_hdr(msg); |
| struct nlmsgerr *err = nlmsg_data(hdr); |
| int l; |
| |
| fprintf(ofd, " [ERRORMSG] %zu octets\n", sizeof(*err)); |
| |
| l = nlmsg_len(hdr); |
| if (l >= 0 && ((unsigned)l) >= sizeof(*err)) { |
| struct nl_msg *errmsg; |
| |
| fprintf(ofd, " .error = %d \"%s\"\n", err->error, |
| nl_strerror_l(-err->error)); |
| fprintf(ofd, " [ORIGINAL MESSAGE] %zu octets\n", sizeof(*hdr)); |
| |
| errmsg = nlmsg_inherit(&err->msg); |
| print_hdr(ofd, errmsg); |
| nlmsg_free(errmsg); |
| } |
| } |
| |
| static void print_msg(struct nl_msg *msg, FILE *ofd, struct nlmsghdr *hdr) |
| { |
| struct nl_cache_ops *ops; |
| int payloadlen = nlmsg_len(hdr); |
| int attrlen = 0; |
| void *data; |
| |
| data = nlmsg_data(hdr); |
| ops = nl_cache_ops_associate_safe(nlmsg_get_proto(msg), |
| hdr->nlmsg_type); |
| if (ops) { |
| attrlen = nlmsg_attrlen(hdr, ops->co_hdrsize); |
| payloadlen -= attrlen; |
| } |
| |
| if (msg->nm_protocol == NETLINK_GENERIC) |
| data = print_genl_msg(msg, ofd, hdr, ops, &payloadlen); |
| |
| if (payloadlen) { |
| fprintf(ofd, " [PAYLOAD] %d octets\n", payloadlen); |
| dump_hex(ofd, data, payloadlen, 0); |
| } |
| |
| if (attrlen) { |
| struct nlattr *attrs; |
| int attrlen; |
| |
| attrs = nlmsg_attrdata(hdr, ops->co_hdrsize); |
| attrlen = nlmsg_attrlen(hdr, ops->co_hdrsize); |
| dump_attrs(ofd, attrs, attrlen, 0); |
| } |
| |
| if (ops) |
| nl_cache_ops_put(ops); |
| } |
| |
| /** |
| * Dump message in human readable format to file descriptor |
| * @arg msg Message to print |
| * @arg ofd File descriptor. |
| */ |
| void nl_msg_dump(struct nl_msg *msg, FILE *ofd) |
| { |
| struct nlmsghdr *hdr = nlmsg_hdr(msg); |
| |
| fprintf(ofd, |
| "-------------------------- BEGIN NETLINK MESSAGE ---------------------------\n"); |
| |
| fprintf(ofd, " [NETLINK HEADER] %zu octets\n", sizeof(struct nlmsghdr)); |
| print_hdr(ofd, msg); |
| |
| if (hdr->nlmsg_type == NLMSG_ERROR) |
| dump_error_msg(msg, ofd); |
| else if (nlmsg_len(hdr) > 0) |
| print_msg(msg, ofd, hdr); |
| |
| fprintf(ofd, |
| "--------------------------- END NETLINK MESSAGE ---------------------------\n"); |
| } |
| |
| /** @} */ |
| |
| /** @} */ |