diff --git a/sys/netlink/netlink_message_writer.c b/sys/netlink/netlink_message_writer.c index 092e3798f8e7..1aebc4690c2d 100644 --- a/sys/netlink/netlink_message_writer.c +++ b/sys/netlink/netlink_message_writer.c @@ -1,394 +1,398 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 Alexander V. Chernikov * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_MOD_NAME nl_writer #define DEBUG_MAX_LEVEL LOG_DEBUG3 #include _DECLARE_DEBUG(LOG_INFO); static bool nlmsg_get_buf(struct nl_writer *nw, size_t len, bool waitok) { const int mflag = waitok ? M_WAITOK : M_NOWAIT; MPASS(nw->buf == NULL); NL_LOG(LOG_DEBUG3, "Setting up nw %p len %zu %s", nw, len, waitok ? "wait" : "nowait"); nw->buf = nl_buf_alloc(len, mflag); if (__predict_false(nw->buf == NULL)) return (false); nw->hdr = NULL; nw->malloc_flag = mflag; nw->num_messages = 0; nw->enomem = false; return (true); } static bool nl_send_one(struct nl_writer *nw) { return (nl_send(nw, nw->nlp)); } bool _nl_writer_unicast(struct nl_writer *nw, size_t size, struct nlpcb *nlp, bool waitok) { *nw = (struct nl_writer){ .nlp = nlp, .cb = nl_send_one, }; return (nlmsg_get_buf(nw, size, waitok)); } bool _nl_writer_group(struct nl_writer *nw, size_t size, uint16_t protocol, uint16_t group_id, bool waitok) { *nw = (struct nl_writer){ .group.proto = protocol, .group.id = group_id, .cb = nl_send_group, }; return (nlmsg_get_buf(nw, size, waitok)); } void _nlmsg_ignore_limit(struct nl_writer *nw) { nw->ignore_limit = true; } bool _nlmsg_flush(struct nl_writer *nw) { bool result; if (__predict_false(nw->hdr != NULL)) { /* Last message has not been completed, skip it. */ int completed_len = (char *)nw->hdr - nw->buf->data; /* Send completed messages */ nw->buf->datalen -= nw->buf->datalen - completed_len; nw->hdr = NULL; } if (nw->buf->datalen == 0) { MPASS(nw->num_messages == 0); nl_buf_free(nw->buf); nw->buf = NULL; return (true); } result = nw->cb(nw); nw->num_messages = 0; if (!result) { NL_LOG(LOG_DEBUG, "nw %p flush with %p() failed", nw, nw->cb); } return (result); } /* * Flushes previous data and allocates new underlying storage * sufficient for holding at least @required_len bytes. * Return true on success. */ bool _nlmsg_refill_buffer(struct nl_writer *nw, size_t required_len) { struct nl_buf *new; size_t completed_len, new_len, last_len; MPASS(nw->buf != NULL); if (nw->enomem) return (false); NL_LOG(LOG_DEBUG3, "no space at offset %u/%u (want %zu), trying to " "reclaim", nw->buf->datalen, nw->buf->buflen, required_len); /* Calculate new buffer size and allocate it. */ completed_len = (nw->hdr != NULL) ? (char *)nw->hdr - nw->buf->data : nw->buf->datalen; if (completed_len > 0 && required_len < NLMBUFSIZE) { /* We already ran out of space, use largest effective size. */ new_len = max(nw->buf->buflen, NLMBUFSIZE); } else { if (nw->buf->buflen < NLMBUFSIZE) /* XXXGL: does this happen? */ new_len = NLMBUFSIZE; else new_len = nw->buf->buflen * 2; while (new_len < required_len) new_len *= 2; } new = nl_buf_alloc(new_len, nw->malloc_flag | M_ZERO); if (__predict_false(new == NULL)) { nw->enomem = true; NL_LOG(LOG_DEBUG, "getting new buf failed, setting ENOMEM"); return (false); } /* Copy last (unfinished) header to the new storage. */ last_len = nw->buf->datalen - completed_len; if (last_len > 0) { memcpy(new->data, nw->hdr, last_len); new->datalen = last_len; } NL_LOG(LOG_DEBUG2, "completed: %zu bytes, copied: %zu bytes", completed_len, last_len); if (completed_len > 0) { nlmsg_flush(nw); MPASS(nw->buf == NULL); } else nl_buf_free(nw->buf); nw->buf = new; nw->hdr = (last_len > 0) ? (struct nlmsghdr *)new->data : NULL; NL_LOG(LOG_DEBUG2, "switched buffer: used %u/%u bytes", new->datalen, new->buflen); return (true); } bool _nlmsg_add(struct nl_writer *nw, uint32_t portid, uint32_t seq, uint16_t type, uint16_t flags, uint32_t len) { struct nl_buf *nb = nw->buf; struct nlmsghdr *hdr; size_t required_len; MPASS(nw->hdr == NULL); required_len = NETLINK_ALIGN(len + sizeof(struct nlmsghdr)); if (__predict_false(nb->datalen + required_len > nb->buflen)) { if (!nlmsg_refill_buffer(nw, required_len)) return (false); nb = nw->buf; } hdr = (struct nlmsghdr *)(&nb->data[nb->datalen]); hdr->nlmsg_len = len; hdr->nlmsg_type = type; hdr->nlmsg_flags = flags; hdr->nlmsg_seq = seq; hdr->nlmsg_pid = portid; nw->hdr = hdr; nb->datalen += sizeof(struct nlmsghdr); return (true); } bool _nlmsg_end(struct nl_writer *nw) { struct nl_buf *nb = nw->buf; MPASS(nw->hdr != NULL); if (nw->enomem) { NL_LOG(LOG_DEBUG, "ENOMEM when dumping message"); nlmsg_abort(nw); return (false); } nw->hdr->nlmsg_len = nb->data + nb->datalen - (char *)nw->hdr; NL_LOG(LOG_DEBUG2, "wrote msg len: %u type: %d: flags: 0x%X seq: %u pid: %u", nw->hdr->nlmsg_len, nw->hdr->nlmsg_type, nw->hdr->nlmsg_flags, nw->hdr->nlmsg_seq, nw->hdr->nlmsg_pid); nw->hdr = NULL; nw->num_messages++; return (true); } void _nlmsg_abort(struct nl_writer *nw) { struct nl_buf *nb = nw->buf; if (nw->hdr != NULL) { nb->datalen = (char *)nw->hdr - nb->data; nw->hdr = NULL; } } void nlmsg_ack(struct nlpcb *nlp, int error, struct nlmsghdr *hdr, struct nl_pstate *npt) { struct nlmsgerr *errmsg; int payload_len; uint32_t flags = nlp->nl_flags; struct nl_writer *nw = npt->nw; bool cap_ack; payload_len = sizeof(struct nlmsgerr); /* * The only case when we send the full message in the * reply is when there is an error and NETLINK_CAP_ACK * is not set. */ cap_ack = (error == 0) || (flags & NLF_CAP_ACK); if (!cap_ack) payload_len += hdr->nlmsg_len - sizeof(struct nlmsghdr); payload_len = NETLINK_ALIGN(payload_len); uint16_t nl_flags = cap_ack ? NLM_F_CAPPED : 0; if ((npt->err_msg || npt->err_off) && nlp->nl_flags & NLF_EXT_ACK) nl_flags |= NLM_F_ACK_TLVS; NL_LOG(LOG_DEBUG3, "acknowledging message type %d seq %d", hdr->nlmsg_type, hdr->nlmsg_seq); if (!nlmsg_add(nw, nlp->nl_port, hdr->nlmsg_seq, NLMSG_ERROR, nl_flags, payload_len)) goto enomem; errmsg = nlmsg_reserve_data(nw, payload_len, struct nlmsgerr); errmsg->error = error; /* In case of error copy the whole message, else just the header */ memcpy(&errmsg->msg, hdr, cap_ack ? sizeof(*hdr) : hdr->nlmsg_len); if (npt->err_msg != NULL && nlp->nl_flags & NLF_EXT_ACK) nlattr_add_string(nw, NLMSGERR_ATTR_MSG, npt->err_msg); if (npt->err_off != 0 && nlp->nl_flags & NLF_EXT_ACK) nlattr_add_u32(nw, NLMSGERR_ATTR_OFFS, npt->err_off); if (npt->cookie != NULL) nlattr_add_raw(nw, npt->cookie); if (nlmsg_end(nw)) return; enomem: NLP_LOG(LOG_DEBUG, nlp, "error allocating ack data for message %d seq %u", hdr->nlmsg_type, hdr->nlmsg_seq); nlmsg_abort(nw); } bool _nlmsg_end_dump(struct nl_writer *nw, int error, struct nlmsghdr *hdr) { if (!nlmsg_add(nw, hdr->nlmsg_pid, hdr->nlmsg_seq, NLMSG_DONE, 0, sizeof(int))) { NL_LOG(LOG_DEBUG, "Error finalizing table dump"); return (false); } /* Save operation result */ int *perror = nlmsg_reserve_object(nw, int); NL_LOG(LOG_DEBUG2, "record error=%d at off %d (%p)", error, nw->buf->datalen, perror); *perror = error; nlmsg_end(nw); nw->suppress_ack = true; return (true); } /* * KPI functions. */ u_int nlattr_save_offset(const struct nl_writer *nw) { return (nw->buf->datalen - ((char *)nw->hdr - nw->buf->data)); } void * nlmsg_reserve_data_raw(struct nl_writer *nw, size_t sz) { struct nl_buf *nb = nw->buf; void *data; sz = NETLINK_ALIGN(sz); if (__predict_false(nb->datalen + sz > nb->buflen)) { if (!nlmsg_refill_buffer(nw, sz)) return (NULL); nb = nw->buf; } data = &nb->data[nb->datalen]; bzero(data, sz); nb->datalen += sz; return (data); } bool -nlattr_add(struct nl_writer *nw, int attr_type, int attr_len, const void *data) +nlattr_add(struct nl_writer *nw, uint16_t attr_type, uint16_t attr_len, + const void *data) { struct nl_buf *nb = nw->buf; struct nlattr *nla; - u_int required_len; + size_t required_len; + + KASSERT(attr_len <= UINT16_MAX - sizeof(struct nlattr), + ("%s: invalid attribute length %u", __func__, attr_len)); required_len = NLA_ALIGN(attr_len + sizeof(struct nlattr)); if (__predict_false(nb->datalen + required_len > nb->buflen)) { if (!nlmsg_refill_buffer(nw, required_len)) return (false); nb = nw->buf; } nla = (struct nlattr *)(&nb->data[nb->datalen]); nla->nla_len = attr_len + sizeof(struct nlattr); nla->nla_type = attr_type; if (attr_len > 0) { if ((attr_len % 4) != 0) { /* clear padding bytes */ bzero((char *)nla + required_len - 4, 4); } memcpy((nla + 1), data, attr_len); } nb->datalen += required_len; return (true); } #include diff --git a/sys/netlink/netlink_message_writer.h b/sys/netlink/netlink_message_writer.h index 9469883feaa7..1655acb53fef 100644 --- a/sys/netlink/netlink_message_writer.h +++ b/sys/netlink/netlink_message_writer.h @@ -1,308 +1,310 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Ng Peng Nam Sean * Copyright (c) 2022 Alexander V. Chernikov * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _NETLINK_NETLINK_MESSAGE_WRITER_H_ #define _NETLINK_NETLINK_MESSAGE_WRITER_H_ #ifdef _KERNEL #include /* * It is not meant to be included directly */ struct nl_buf; struct nl_writer; typedef bool nl_writer_cb(struct nl_writer *nw); struct nl_writer { struct nl_buf *buf; /* Underlying storage pointer */ struct nlmsghdr *hdr; /* Pointer to the currently-filled msg */ nl_writer_cb *cb; /* Callback to flush data */ union { struct nlpcb *nlp; struct { uint16_t proto; uint16_t id; } group; }; u_int num_messages; /* Number of messages in the buffer */ int malloc_flag; /* M_WAITOK or M_NOWAIT */ bool ignore_limit; /* If true, ignores RCVBUF limit */ bool enomem; /* True if ENOMEM occured */ bool suppress_ack; /* If true, don't send NLMSG_ERR */ }; #define NLMSG_SMALL 128 #define NLMSG_LARGE 2048 /* Message and attribute writing */ #if defined(NETLINK) || defined(NETLINK_MODULE) /* Provide optimized calls to the functions inside the same linking unit */ bool _nl_writer_unicast(struct nl_writer *, size_t, struct nlpcb *nlp, bool); bool _nl_writer_group(struct nl_writer *, size_t, uint16_t, uint16_t, bool); bool _nlmsg_flush(struct nl_writer *nw); void _nlmsg_ignore_limit(struct nl_writer *nw); bool _nlmsg_refill_buffer(struct nl_writer *nw, size_t required_len); bool _nlmsg_add(struct nl_writer *nw, uint32_t portid, uint32_t seq, uint16_t type, uint16_t flags, uint32_t len); bool _nlmsg_end(struct nl_writer *nw); void _nlmsg_abort(struct nl_writer *nw); bool _nlmsg_end_dump(struct nl_writer *nw, int error, struct nlmsghdr *hdr); static inline bool nl_writer_unicast(struct nl_writer *nw, size_t size, struct nlpcb *nlp, bool waitok) { return (_nl_writer_unicast(nw, size, nlp, waitok)); } static inline bool nl_writer_group(struct nl_writer *nw, size_t size, uint16_t proto, uint16_t group_id, bool waitok) { return (_nl_writer_group(nw, size, proto, group_id, waitok)); } static inline bool nlmsg_flush(struct nl_writer *nw) { return (_nlmsg_flush(nw)); } static inline void nlmsg_ignore_limit(struct nl_writer *nw) { _nlmsg_ignore_limit(nw); } static inline bool nlmsg_refill_buffer(struct nl_writer *nw, size_t required_size) { return (_nlmsg_refill_buffer(nw, required_size)); } static inline bool nlmsg_add(struct nl_writer *nw, uint32_t portid, uint32_t seq, uint16_t type, uint16_t flags, uint32_t len) { return (_nlmsg_add(nw, portid, seq, type, flags, len)); } static inline bool nlmsg_end(struct nl_writer *nw) { return (_nlmsg_end(nw)); } static inline void nlmsg_abort(struct nl_writer *nw) { return (_nlmsg_abort(nw)); } static inline bool nlmsg_end_dump(struct nl_writer *nw, int error, struct nlmsghdr *hdr) { return (_nlmsg_end_dump(nw, error, hdr)); } #else /* Provide access to the functions via netlink_glue.c */ bool nl_writer_unicast(struct nl_writer *, size_t, struct nlpcb *, bool waitok); bool nl_writer_group(struct nl_writer *, size_t, uint16_t, uint16_t, bool waitok); bool nlmsg_flush(struct nl_writer *nw); void nlmsg_ignore_limit(struct nl_writer *nw); bool nlmsg_refill_buffer(struct nl_writer *nw, size_t required_size); bool nlmsg_add(struct nl_writer *nw, uint32_t portid, uint32_t seq, uint16_t type, uint16_t flags, uint32_t len); bool nlmsg_end(struct nl_writer *nw); void nlmsg_abort(struct nl_writer *nw); bool nlmsg_end_dump(struct nl_writer *nw, int error, struct nlmsghdr *hdr); #endif /* defined(NETLINK) || defined(NETLINK_MODULE) */ static inline bool nlmsg_reply(struct nl_writer *nw, const struct nlmsghdr *hdr, int payload_len) { return (nlmsg_add(nw, hdr->nlmsg_pid, hdr->nlmsg_seq, hdr->nlmsg_type, hdr->nlmsg_flags, payload_len)); } /* * KPI similar to mtodo(): * current (uncompleted) header is guaranteed to be contiguous, * but can be reallocated, thus pointers may need to be readjusted. */ u_int nlattr_save_offset(const struct nl_writer *nw); static inline void * _nlattr_restore_offset(const struct nl_writer *nw, int off) { return ((void *)((char *)nw->hdr + off)); } #define nlattr_restore_offset(_ns, _off, _t) ((_t *)_nlattr_restore_offset(_ns, _off)) static inline void nlattr_set_len(const struct nl_writer *nw, int off) { struct nlattr *nla = nlattr_restore_offset(nw, off, struct nlattr); nla->nla_len = nlattr_save_offset(nw) - off; } void *nlmsg_reserve_data_raw(struct nl_writer *nw, size_t sz); #define nlmsg_reserve_object(_ns, _t) ((_t *)nlmsg_reserve_data_raw(_ns, sizeof(_t))) #define nlmsg_reserve_data(_ns, _sz, _t) ((_t *)nlmsg_reserve_data_raw(_ns, _sz)) static inline int nlattr_add_nested(struct nl_writer *nw, uint16_t nla_type) { int off = nlattr_save_offset(nw); struct nlattr *nla = nlmsg_reserve_data(nw, sizeof(struct nlattr), struct nlattr); if (__predict_false(nla == NULL)) return (0); nla->nla_type = nla_type; return (off); } static inline void * _nlmsg_reserve_attr(struct nl_writer *nw, uint16_t nla_type, uint16_t sz) { sz += sizeof(struct nlattr); struct nlattr *nla = nlmsg_reserve_data(nw, sz, struct nlattr); if (__predict_false(nla == NULL)) return (NULL); nla->nla_type = nla_type; nla->nla_len = sz; return ((void *)(nla + 1)); } #define nlmsg_reserve_attr(_ns, _at, _t) ((_t *)_nlmsg_reserve_attr(_ns, _at, NLA_ALIGN(sizeof(_t)))) -bool nlattr_add(struct nl_writer *nw, int attr_type, int attr_len, +bool nlattr_add(struct nl_writer *nw, uint16_t attr_type, uint16_t attr_len, const void *data); static inline bool nlattr_add_raw(struct nl_writer *nw, const struct nlattr *nla_src) { - int attr_len = nla_src->nla_len - sizeof(struct nlattr); + MPASS(nla_src->nla_len >= sizeof(struct nlattr)); - MPASS(attr_len >= 0); - - return (nlattr_add(nw, nla_src->nla_type, attr_len, (const void *)(nla_src + 1))); + return (nlattr_add(nw, nla_src->nla_type, + nla_src->nla_len - sizeof(struct nlattr), + (const void *)(nla_src + 1))); } static inline bool -nlattr_add_bool(struct nl_writer *nw, int attrtype, bool value) +nlattr_add_bool(struct nl_writer *nw, uint16_t attrtype, bool value) { return (nlattr_add(nw, attrtype, sizeof(bool), &value)); } static inline bool -nlattr_add_u8(struct nl_writer *nw, int attrtype, uint8_t value) +nlattr_add_u8(struct nl_writer *nw, uint16_t attrtype, uint8_t value) { return (nlattr_add(nw, attrtype, sizeof(uint8_t), &value)); } static inline bool -nlattr_add_u16(struct nl_writer *nw, int attrtype, uint16_t value) +nlattr_add_u16(struct nl_writer *nw, uint16_t attrtype, uint16_t value) { return (nlattr_add(nw, attrtype, sizeof(uint16_t), &value)); } static inline bool -nlattr_add_u32(struct nl_writer *nw, int attrtype, uint32_t value) +nlattr_add_u32(struct nl_writer *nw, uint16_t attrtype, uint32_t value) { return (nlattr_add(nw, attrtype, sizeof(uint32_t), &value)); } static inline bool -nlattr_add_u64(struct nl_writer *nw, int attrtype, uint64_t value) +nlattr_add_u64(struct nl_writer *nw, uint16_t attrtype, uint64_t value) { return (nlattr_add(nw, attrtype, sizeof(uint64_t), &value)); } static inline bool -nlattr_add_s8(struct nl_writer *nw, int attrtype, int8_t value) +nlattr_add_s8(struct nl_writer *nw, uint16_t attrtype, int8_t value) { return (nlattr_add(nw, attrtype, sizeof(int8_t), &value)); } static inline bool -nlattr_add_s16(struct nl_writer *nw, int attrtype, int16_t value) +nlattr_add_s16(struct nl_writer *nw, uint16_t attrtype, int16_t value) { return (nlattr_add(nw, attrtype, sizeof(int16_t), &value)); } static inline bool -nlattr_add_s32(struct nl_writer *nw, int attrtype, int32_t value) +nlattr_add_s32(struct nl_writer *nw, uint16_t attrtype, int32_t value) { return (nlattr_add(nw, attrtype, sizeof(int32_t), &value)); } static inline bool -nlattr_add_s64(struct nl_writer *nw, int attrtype, int64_t value) +nlattr_add_s64(struct nl_writer *nw, uint16_t attrtype, int64_t value) { return (nlattr_add(nw, attrtype, sizeof(int64_t), &value)); } static inline bool -nlattr_add_flag(struct nl_writer *nw, int attrtype) +nlattr_add_flag(struct nl_writer *nw, uint16_t attrtype) { return (nlattr_add(nw, attrtype, 0, NULL)); } static inline bool -nlattr_add_string(struct nl_writer *nw, int attrtype, const char *str) +nlattr_add_string(struct nl_writer *nw, uint16_t attrtype, const char *str) { return (nlattr_add(nw, attrtype, strlen(str) + 1, str)); } static inline bool -nlattr_add_in_addr(struct nl_writer *nw, int attrtype, const struct in_addr *in) +nlattr_add_in_addr(struct nl_writer *nw, uint16_t attrtype, + const struct in_addr *in) { return (nlattr_add(nw, attrtype, sizeof(*in), in)); } static inline bool -nlattr_add_in6_addr(struct nl_writer *nw, int attrtype, const struct in6_addr *in6) +nlattr_add_in6_addr(struct nl_writer *nw, uint16_t attrtype, + const struct in6_addr *in6) { return (nlattr_add(nw, attrtype, sizeof(*in6), in6)); } #endif #endif