| // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause | |
| #include <errno.h> | |
| #include <poll.h> | |
| #include <string.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <unistd.h> | |
| #include <linux/types.h> | |
| #include <linux/genetlink.h> | |
| #include <sys/socket.h> | |
| #include "ynl.h" | |
| #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*arr)) | |
| #define __yerr_msg(yse, _msg...) \ | |
| ({ \ | |
| struct ynl_error *_yse = (yse); \ | |
| \ | |
| if (_yse) { \ | |
| snprintf(_yse->msg, sizeof(_yse->msg) - 1, _msg); \ | |
| _yse->msg[sizeof(_yse->msg) - 1] = 0; \ | |
| } \ | |
| }) | |
| #define __yerr_code(yse, _code...) \ | |
| ({ \ | |
| struct ynl_error *_yse = (yse); \ | |
| \ | |
| if (_yse) { \ | |
| _yse->code = _code; \ | |
| } \ | |
| }) | |
| #define __yerr(yse, _code, _msg...) \ | |
| ({ \ | |
| __yerr_msg(yse, _msg); \ | |
| __yerr_code(yse, _code); \ | |
| }) | |
| #define __perr(yse, _msg) __yerr(yse, errno, _msg) | |
| #define yerr_msg(_ys, _msg...) __yerr_msg(&(_ys)->err, _msg) | |
| #define yerr(_ys, _code, _msg...) __yerr(&(_ys)->err, _code, _msg) | |
| #define perr(_ys, _msg) __yerr(&(_ys)->err, errno, _msg) | |
| /* -- Netlink boiler plate */ | |
| static bool | |
| ynl_err_walk_is_sel(const struct ynl_policy_nest *policy, | |
| const struct nlattr *attr) | |
| { | |
| unsigned int type = ynl_attr_type(attr); | |
| return policy && type <= policy->max_attr && | |
| policy->table[type].is_selector; | |
| } | |
| static const struct ynl_policy_nest * | |
| ynl_err_walk_sel_policy(const struct ynl_policy_attr *policy_attr, | |
| const struct nlattr *selector) | |
| { | |
| const struct ynl_policy_nest *policy = policy_attr->nest; | |
| const char *sel; | |
| unsigned int i; | |
| if (!policy_attr->is_submsg) | |
| return policy; | |
| sel = ynl_attr_get_str(selector); | |
| for (i = 0; i <= policy->max_attr; i++) { | |
| if (!strcmp(sel, policy->table[i].name)) | |
| return policy->table[i].nest; | |
| } | |
| return NULL; | |
| } | |
| static int | |
| ynl_err_walk_report_one(const struct ynl_policy_nest *policy, | |
| const struct nlattr *selector, unsigned int type, | |
| char *str, int str_sz, int *n) | |
| { | |
| if (!policy) { | |
| if (*n < str_sz) | |
| *n += snprintf(str, str_sz, "!policy"); | |
| return 1; | |
| } | |
| if (type > policy->max_attr) { | |
| if (*n < str_sz) | |
| *n += snprintf(str, str_sz, "!oob"); | |
| return 1; | |
| } | |
| if (!policy->table[type].name) { | |
| if (*n < str_sz) | |
| *n += snprintf(str, str_sz, "!name"); | |
| return 1; | |
| } | |
| if (*n < str_sz) { | |
| int sz; | |
| sz = snprintf(str, str_sz - *n, | |
| ".%s", policy->table[type].name); | |
| *n += sz; | |
| str += sz; | |
| } | |
| if (policy->table[type].is_submsg) { | |
| if (!selector) { | |
| if (*n < str_sz) | |
| *n += snprintf(str, str_sz, "(!selector)"); | |
| return 1; | |
| } | |
| if (ynl_attr_type(selector) != | |
| policy->table[type].selector_type) { | |
| if (*n < str_sz) | |
| *n += snprintf(str, str_sz, "(!=selector)"); | |
| return 1; | |
| } | |
| if (*n < str_sz) | |
| *n += snprintf(str, str_sz - *n, "(%s)", | |
| ynl_attr_get_str(selector)); | |
| } | |
| return 0; | |
| } | |
| static int | |
| ynl_err_walk(struct ynl_sock *ys, void *start, void *end, unsigned int off, | |
| const struct ynl_policy_nest *policy, char *str, int str_sz, | |
| const struct ynl_policy_nest **nest_pol) | |
| { | |
| const struct ynl_policy_nest *next_pol; | |
| const struct nlattr *selector = NULL; | |
| unsigned int astart_off, aend_off; | |
| const struct nlattr *attr; | |
| unsigned int data_len; | |
| unsigned int type; | |
| bool found = false; | |
| int n = 0; | |
| if (!policy) { | |
| if (n < str_sz) | |
| n += snprintf(str, str_sz, "!policy"); | |
| return n; | |
| } | |
| data_len = end - start; | |
| ynl_attr_for_each_payload(start, data_len, attr) { | |
| astart_off = (char *)attr - (char *)start; | |
| aend_off = (char *)ynl_attr_data_end(attr) - (char *)start; | |
| if (ynl_err_walk_is_sel(policy, attr)) | |
| selector = attr; | |
| if (aend_off <= off) | |
| continue; | |
| found = true; | |
| break; | |
| } | |
| if (!found) | |
| return 0; | |
| off -= astart_off; | |
| type = ynl_attr_type(attr); | |
| if (ynl_err_walk_report_one(policy, selector, type, str, str_sz, &n)) | |
| return n; | |
| next_pol = ynl_err_walk_sel_policy(&policy->table[type], selector); | |
| if (!next_pol) | |
| return n; | |
| if (!off) { | |
| if (nest_pol) | |
| *nest_pol = next_pol; | |
| return n; | |
| } | |
| if (!next_pol) { | |
| if (n < str_sz) | |
| n += snprintf(str, str_sz, "!nest"); | |
| return n; | |
| } | |
| off -= sizeof(struct nlattr); | |
| start = ynl_attr_data(attr); | |
| end = start + ynl_attr_data_len(attr); | |
| return n + ynl_err_walk(ys, start, end, off, next_pol, | |
| &str[n], str_sz - n, nest_pol); | |
| } | |
| #define NLMSGERR_ATTR_MISS_TYPE (NLMSGERR_ATTR_POLICY + 1) | |
| #define NLMSGERR_ATTR_MISS_NEST (NLMSGERR_ATTR_POLICY + 2) | |
| #define NLMSGERR_ATTR_MAX (NLMSGERR_ATTR_MAX + 2) | |
| static int | |
| ynl_ext_ack_check(struct ynl_sock *ys, const struct nlmsghdr *nlh, | |
| unsigned int hlen) | |
| { | |
| const struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {}; | |
| char miss_attr[sizeof(ys->err.msg)]; | |
| char bad_attr[sizeof(ys->err.msg)]; | |
| const struct nlattr *attr; | |
| const char *str = NULL; | |
| if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS)) { | |
| yerr_msg(ys, "%s", strerror(ys->err.code)); | |
| return YNL_PARSE_CB_OK; | |
| } | |
| ynl_attr_for_each(attr, nlh, hlen) { | |
| unsigned int len, type; | |
| len = ynl_attr_data_len(attr); | |
| type = ynl_attr_type(attr); | |
| if (type > NLMSGERR_ATTR_MAX) | |
| continue; | |
| tb[type] = attr; | |
| switch (type) { | |
| case NLMSGERR_ATTR_OFFS: | |
| case NLMSGERR_ATTR_MISS_TYPE: | |
| case NLMSGERR_ATTR_MISS_NEST: | |
| if (len != sizeof(__u32)) | |
| return YNL_PARSE_CB_ERROR; | |
| break; | |
| case NLMSGERR_ATTR_MSG: | |
| str = ynl_attr_get_str(attr); | |
| if (str[len - 1]) | |
| return YNL_PARSE_CB_ERROR; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| bad_attr[0] = '\0'; | |
| miss_attr[0] = '\0'; | |
| if (tb[NLMSGERR_ATTR_OFFS]) { | |
| unsigned int n, off; | |
| void *start, *end; | |
| ys->err.attr_offs = ynl_attr_get_u32(tb[NLMSGERR_ATTR_OFFS]); | |
| n = snprintf(bad_attr, sizeof(bad_attr), "%sbad attribute: ", | |
| str ? " (" : ""); | |
| start = ynl_nlmsg_data_offset(ys->nlh, ys->req_hdr_len); | |
| end = ynl_nlmsg_end_addr(ys->nlh); | |
| off = ys->err.attr_offs; | |
| off -= sizeof(struct nlmsghdr); | |
| off -= ys->req_hdr_len; | |
| n += ynl_err_walk(ys, start, end, off, ys->req_policy, | |
| &bad_attr[n], sizeof(bad_attr) - n, NULL); | |
| if (n >= sizeof(bad_attr)) | |
| n = sizeof(bad_attr) - 1; | |
| bad_attr[n] = '\0'; | |
| } | |
| if (tb[NLMSGERR_ATTR_MISS_TYPE]) { | |
| const struct ynl_policy_nest *nest_pol = NULL; | |
| unsigned int n, off, type; | |
| void *start, *end; | |
| int n2; | |
| type = ynl_attr_get_u32(tb[NLMSGERR_ATTR_MISS_TYPE]); | |
| n = snprintf(miss_attr, sizeof(miss_attr), "%smissing attribute: ", | |
| bad_attr[0] ? ", " : (str ? " (" : "")); | |
| start = ynl_nlmsg_data_offset(ys->nlh, ys->req_hdr_len); | |
| end = ynl_nlmsg_end_addr(ys->nlh); | |
| nest_pol = ys->req_policy; | |
| if (tb[NLMSGERR_ATTR_MISS_NEST]) { | |
| off = ynl_attr_get_u32(tb[NLMSGERR_ATTR_MISS_NEST]); | |
| off -= sizeof(struct nlmsghdr); | |
| off -= ys->req_hdr_len; | |
| n += ynl_err_walk(ys, start, end, off, ys->req_policy, | |
| &miss_attr[n], sizeof(miss_attr) - n, | |
| &nest_pol); | |
| } | |
| n2 = 0; | |
| ynl_err_walk_report_one(nest_pol, NULL, type, &miss_attr[n], | |
| sizeof(miss_attr) - n, &n2); | |
| n += n2; | |
| if (n >= sizeof(miss_attr)) | |
| n = sizeof(miss_attr) - 1; | |
| miss_attr[n] = '\0'; | |
| } | |
| /* Implicitly depend on ys->err.code already set */ | |
| if (str) | |
| yerr_msg(ys, "Kernel %s: '%s'%s%s%s", | |
| ys->err.code ? "error" : "warning", | |
| str, bad_attr, miss_attr, | |
| bad_attr[0] || miss_attr[0] ? ")" : ""); | |
| else if (bad_attr[0] || miss_attr[0]) | |
| yerr_msg(ys, "Kernel %s: %s%s", | |
| ys->err.code ? "error" : "warning", | |
| bad_attr, miss_attr); | |
| else | |
| yerr_msg(ys, "%s", strerror(ys->err.code)); | |
| return YNL_PARSE_CB_OK; | |
| } | |
| static int | |
| ynl_cb_error(const struct nlmsghdr *nlh, struct ynl_parse_arg *yarg) | |
| { | |
| const struct nlmsgerr *err = ynl_nlmsg_data(nlh); | |
| unsigned int hlen; | |
| int code; | |
| code = err->error >= 0 ? err->error : -err->error; | |
| yarg->ys->err.code = code; | |
| errno = code; | |
| hlen = sizeof(*err); | |
| if (!(nlh->nlmsg_flags & NLM_F_CAPPED)) | |
| hlen += ynl_nlmsg_data_len(&err->msg); | |
| ynl_ext_ack_check(yarg->ys, nlh, hlen); | |
| return code ? YNL_PARSE_CB_ERROR : YNL_PARSE_CB_STOP; | |
| } | |
| static int ynl_cb_done(const struct nlmsghdr *nlh, struct ynl_parse_arg *yarg) | |
| { | |
| int err; | |
| err = *(int *)NLMSG_DATA(nlh); | |
| if (err < 0) { | |
| yarg->ys->err.code = -err; | |
| errno = -err; | |
| ynl_ext_ack_check(yarg->ys, nlh, sizeof(int)); | |
| return YNL_PARSE_CB_ERROR; | |
| } | |
| return YNL_PARSE_CB_STOP; | |
| } | |
| /* Attribute validation */ | |
| int __ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr, | |
| unsigned int type) | |
| { | |
| const struct ynl_policy_attr *policy; | |
| unsigned char *data; | |
| unsigned int len; | |
| data = ynl_attr_data(attr); | |
| len = ynl_attr_data_len(attr); | |
| if (type > yarg->rsp_policy->max_attr) { | |
| yerr(yarg->ys, YNL_ERROR_INTERNAL, | |
| "Internal error, validating unknown attribute"); | |
| return -1; | |
| } | |
| policy = &yarg->rsp_policy->table[type]; | |
| switch (policy->type) { | |
| case YNL_PT_REJECT: | |
| yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, | |
| "Rejected attribute (%s)", policy->name); | |
| return -1; | |
| case YNL_PT_IGNORE: | |
| break; | |
| case YNL_PT_U8: | |
| if (len == sizeof(__u8)) | |
| break; | |
| yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, | |
| "Invalid attribute (u8 %s)", policy->name); | |
| return -1; | |
| case YNL_PT_U16: | |
| if (len == sizeof(__u16)) | |
| break; | |
| yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, | |
| "Invalid attribute (u16 %s)", policy->name); | |
| return -1; | |
| case YNL_PT_U32: | |
| if (len == sizeof(__u32)) | |
| break; | |
| yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, | |
| "Invalid attribute (u32 %s)", policy->name); | |
| return -1; | |
| case YNL_PT_U64: | |
| if (len == sizeof(__u64)) | |
| break; | |
| yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, | |
| "Invalid attribute (u64 %s)", policy->name); | |
| return -1; | |
| case YNL_PT_UINT: | |
| if (len == sizeof(__u32) || len == sizeof(__u64)) | |
| break; | |
| yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, | |
| "Invalid attribute (uint %s)", policy->name); | |
| return -1; | |
| case YNL_PT_FLAG: | |
| /* Let flags grow into real attrs, why not.. */ | |
| break; | |
| case YNL_PT_NEST: | |
| if (!len || len >= sizeof(*attr)) | |
| break; | |
| yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, | |
| "Invalid attribute (nest %s)", policy->name); | |
| return -1; | |
| case YNL_PT_BINARY: | |
| if (!policy->len || len == policy->len) | |
| break; | |
| yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, | |
| "Invalid attribute (binary %s)", policy->name); | |
| return -1; | |
| case YNL_PT_NUL_STR: | |
| if (len && (!policy->len || len <= policy->len) && !data[len - 1]) | |
| break; | |
| yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, | |
| "Invalid attribute (string %s)", policy->name); | |
| return -1; | |
| case YNL_PT_BITFIELD32: | |
| if (len == sizeof(struct nla_bitfield32)) | |
| break; | |
| yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, | |
| "Invalid attribute (bitfield32 %s)", policy->name); | |
| return -1; | |
| default: | |
| yerr(yarg->ys, YNL_ERROR_ATTR_INVALID, | |
| "Invalid attribute (unknown %s)", policy->name); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| int ynl_submsg_failed(struct ynl_parse_arg *yarg, const char *field_name, | |
| const char *sel_name) | |
| { | |
| yerr(yarg->ys, YNL_ERROR_SUBMSG_KEY, | |
| "Parsing error: Sub-message key not set (msg %s, key %s)", | |
| field_name, sel_name); | |
| return YNL_PARSE_CB_ERROR; | |
| } | |
| /* Generic code */ | |
| static void ynl_err_reset(struct ynl_sock *ys) | |
| { | |
| ys->err.code = 0; | |
| ys->err.attr_offs = 0; | |
| ys->err.msg[0] = 0; | |
| } | |
| struct nlmsghdr *ynl_msg_start(struct ynl_sock *ys, __u32 id, __u16 flags) | |
| { | |
| struct nlmsghdr *nlh; | |
| ynl_err_reset(ys); | |
| nlh = ys->nlh = ynl_nlmsg_put_header(ys->tx_buf); | |
| nlh->nlmsg_type = id; | |
| nlh->nlmsg_flags = flags; | |
| nlh->nlmsg_seq = ++ys->seq; | |
| /* This is a local YNL hack for length checking, we put the buffer | |
| * length in nlmsg_pid, since messages sent to the kernel always use | |
| * PID 0. Message needs to be terminated with ynl_msg_end(). | |
| */ | |
| nlh->nlmsg_pid = YNL_SOCKET_BUFFER_SIZE; | |
| return nlh; | |
| } | |
| static int ynl_msg_end(struct ynl_sock *ys, struct nlmsghdr *nlh) | |
| { | |
| /* We stash buffer length in nlmsg_pid. */ | |
| if (nlh->nlmsg_pid == 0) { | |
| yerr(ys, YNL_ERROR_INPUT_INVALID, | |
| "Unknown input buffer length"); | |
| return -EINVAL; | |
| } | |
| if (nlh->nlmsg_pid == YNL_MSG_OVERFLOW) { | |
| yerr(ys, YNL_ERROR_INPUT_TOO_BIG, | |
| "Constructed message longer than internal buffer"); | |
| return -EMSGSIZE; | |
| } | |
| nlh->nlmsg_pid = 0; | |
| return 0; | |
| } | |
| struct nlmsghdr * | |
| ynl_gemsg_start(struct ynl_sock *ys, __u32 id, __u16 flags, | |
| __u8 cmd, __u8 version) | |
| { | |
| struct genlmsghdr gehdr; | |
| struct nlmsghdr *nlh; | |
| void *data; | |
| nlh = ynl_msg_start(ys, id, flags); | |
| memset(&gehdr, 0, sizeof(gehdr)); | |
| gehdr.cmd = cmd; | |
| gehdr.version = version; | |
| data = ynl_nlmsg_put_extra_header(nlh, sizeof(gehdr)); | |
| memcpy(data, &gehdr, sizeof(gehdr)); | |
| return nlh; | |
| } | |
| struct nlmsghdr *ynl_msg_start_req(struct ynl_sock *ys, __u32 id, __u16 flags) | |
| { | |
| return ynl_msg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK | flags); | |
| } | |
| struct nlmsghdr *ynl_msg_start_dump(struct ynl_sock *ys, __u32 id) | |
| { | |
| return ynl_msg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); | |
| } | |
| struct nlmsghdr * | |
| ynl_gemsg_start_req(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version) | |
| { | |
| return ynl_gemsg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK, cmd, version); | |
| } | |
| struct nlmsghdr * | |
| ynl_gemsg_start_dump(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version) | |
| { | |
| return ynl_gemsg_start(ys, id, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, | |
| cmd, version); | |
| } | |
| static int ynl_cb_null(const struct nlmsghdr *nlh, struct ynl_parse_arg *yarg) | |
| { | |
| yerr(yarg->ys, YNL_ERROR_UNEXPECT_MSG, | |
| "Received a message when none were expected"); | |
| return YNL_PARSE_CB_ERROR; | |
| } | |
| static int | |
| __ynl_sock_read_msgs(struct ynl_parse_arg *yarg, ynl_parse_cb_t cb, int flags) | |
| { | |
| struct ynl_sock *ys = yarg->ys; | |
| const struct nlmsghdr *nlh; | |
| ssize_t len, rem; | |
| int ret; | |
| len = recv(ys->socket, ys->rx_buf, YNL_SOCKET_BUFFER_SIZE, flags); | |
| if (len < 0) { | |
| if (flags & MSG_DONTWAIT && errno == EAGAIN) | |
| return YNL_PARSE_CB_STOP; | |
| return len; | |
| } | |
| ret = YNL_PARSE_CB_STOP; | |
| for (rem = len; rem > 0; NLMSG_NEXT(nlh, rem)) { | |
| nlh = (struct nlmsghdr *)&ys->rx_buf[len - rem]; | |
| if (!NLMSG_OK(nlh, rem)) { | |
| yerr(yarg->ys, YNL_ERROR_INV_RESP, | |
| "Invalid message or trailing data in the response."); | |
| return YNL_PARSE_CB_ERROR; | |
| } | |
| if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) { | |
| /* TODO: handle this better */ | |
| yerr(yarg->ys, YNL_ERROR_DUMP_INTER, | |
| "Dump interrupted / inconsistent, please retry."); | |
| return YNL_PARSE_CB_ERROR; | |
| } | |
| switch (nlh->nlmsg_type) { | |
| case 0: | |
| yerr(yarg->ys, YNL_ERROR_INV_RESP, | |
| "Invalid message type in the response."); | |
| return YNL_PARSE_CB_ERROR; | |
| case NLMSG_NOOP: | |
| case NLMSG_OVERRUN ... NLMSG_MIN_TYPE - 1: | |
| ret = YNL_PARSE_CB_OK; | |
| break; | |
| case NLMSG_ERROR: | |
| ret = ynl_cb_error(nlh, yarg); | |
| break; | |
| case NLMSG_DONE: | |
| ret = ynl_cb_done(nlh, yarg); | |
| break; | |
| default: | |
| ret = cb(nlh, yarg); | |
| break; | |
| } | |
| } | |
| return ret; | |
| } | |
| static int ynl_sock_read_msgs(struct ynl_parse_arg *yarg, ynl_parse_cb_t cb) | |
| { | |
| return __ynl_sock_read_msgs(yarg, cb, 0); | |
| } | |
| static int ynl_recv_ack(struct ynl_sock *ys, int ret) | |
| { | |
| struct ynl_parse_arg yarg = { .ys = ys, }; | |
| if (!ret) { | |
| yerr(ys, YNL_ERROR_EXPECT_ACK, | |
| "Expecting an ACK but nothing received"); | |
| return -1; | |
| } | |
| return ynl_sock_read_msgs(&yarg, ynl_cb_null); | |
| } | |
| /* Init/fini and genetlink boiler plate */ | |
| static int | |
| ynl_get_family_info_mcast(struct ynl_sock *ys, const struct nlattr *mcasts) | |
| { | |
| const struct nlattr *entry, *attr; | |
| unsigned int i; | |
| ynl_attr_for_each_nested(attr, mcasts) | |
| ys->n_mcast_groups++; | |
| if (!ys->n_mcast_groups) | |
| return 0; | |
| ys->mcast_groups = calloc(ys->n_mcast_groups, | |
| sizeof(*ys->mcast_groups)); | |
| if (!ys->mcast_groups) | |
| return YNL_PARSE_CB_ERROR; | |
| i = 0; | |
| ynl_attr_for_each_nested(entry, mcasts) { | |
| ynl_attr_for_each_nested(attr, entry) { | |
| if (ynl_attr_type(attr) == CTRL_ATTR_MCAST_GRP_ID) | |
| ys->mcast_groups[i].id = ynl_attr_get_u32(attr); | |
| if (ynl_attr_type(attr) == CTRL_ATTR_MCAST_GRP_NAME) { | |
| strncpy(ys->mcast_groups[i].name, | |
| ynl_attr_get_str(attr), | |
| GENL_NAMSIZ - 1); | |
| ys->mcast_groups[i].name[GENL_NAMSIZ - 1] = 0; | |
| } | |
| } | |
| i++; | |
| } | |
| return 0; | |
| } | |
| static int | |
| ynl_get_family_info_cb(const struct nlmsghdr *nlh, struct ynl_parse_arg *yarg) | |
| { | |
| struct ynl_sock *ys = yarg->ys; | |
| const struct nlattr *attr; | |
| bool found_id = true; | |
| ynl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr)) { | |
| if (ynl_attr_type(attr) == CTRL_ATTR_MCAST_GROUPS) | |
| if (ynl_get_family_info_mcast(ys, attr)) | |
| return YNL_PARSE_CB_ERROR; | |
| if (ynl_attr_type(attr) != CTRL_ATTR_FAMILY_ID) | |
| continue; | |
| if (ynl_attr_data_len(attr) != sizeof(__u16)) { | |
| yerr(ys, YNL_ERROR_ATTR_INVALID, "Invalid family ID"); | |
| return YNL_PARSE_CB_ERROR; | |
| } | |
| ys->family_id = ynl_attr_get_u16(attr); | |
| found_id = true; | |
| } | |
| if (!found_id) { | |
| yerr(ys, YNL_ERROR_ATTR_MISSING, "Family ID missing"); | |
| return YNL_PARSE_CB_ERROR; | |
| } | |
| return YNL_PARSE_CB_OK; | |
| } | |
| static int ynl_sock_read_family(struct ynl_sock *ys, const char *family_name) | |
| { | |
| struct ynl_parse_arg yarg = { .ys = ys, }; | |
| struct nlmsghdr *nlh; | |
| int err; | |
| nlh = ynl_gemsg_start_req(ys, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, 1); | |
| ynl_attr_put_str(nlh, CTRL_ATTR_FAMILY_NAME, family_name); | |
| err = ynl_msg_end(ys, nlh); | |
| if (err < 0) | |
| return err; | |
| err = send(ys->socket, nlh, nlh->nlmsg_len, 0); | |
| if (err < 0) { | |
| perr(ys, "failed to request socket family info"); | |
| return err; | |
| } | |
| err = ynl_sock_read_msgs(&yarg, ynl_get_family_info_cb); | |
| if (err < 0) { | |
| free(ys->mcast_groups); | |
| perr(ys, "failed to receive the socket family info - no such family?"); | |
| return err; | |
| } | |
| err = ynl_recv_ack(ys, err); | |
| if (err < 0) { | |
| free(ys->mcast_groups); | |
| return err; | |
| } | |
| return 0; | |
| } | |
| struct ynl_sock * | |
| ynl_sock_create(const struct ynl_family *yf, struct ynl_error *yse) | |
| { | |
| struct sockaddr_nl addr; | |
| struct ynl_sock *ys; | |
| socklen_t addrlen; | |
| int sock_type; | |
| int one = 1; | |
| ys = malloc(sizeof(*ys) + 2 * YNL_SOCKET_BUFFER_SIZE); | |
| if (!ys) | |
| return NULL; | |
| memset(ys, 0, sizeof(*ys)); | |
| ys->family = yf; | |
| ys->tx_buf = &ys->raw_buf[0]; | |
| ys->rx_buf = &ys->raw_buf[YNL_SOCKET_BUFFER_SIZE]; | |
| ys->ntf_last_next = &ys->ntf_first; | |
| sock_type = yf->is_classic ? yf->classic_id : NETLINK_GENERIC; | |
| ys->socket = socket(AF_NETLINK, SOCK_RAW, sock_type); | |
| if (ys->socket < 0) { | |
| __perr(yse, "failed to create a netlink socket"); | |
| goto err_free_sock; | |
| } | |
| if (setsockopt(ys->socket, SOL_NETLINK, NETLINK_CAP_ACK, | |
| &one, sizeof(one))) { | |
| __perr(yse, "failed to enable netlink ACK"); | |
| goto err_close_sock; | |
| } | |
| if (setsockopt(ys->socket, SOL_NETLINK, NETLINK_EXT_ACK, | |
| &one, sizeof(one))) { | |
| __perr(yse, "failed to enable netlink ext ACK"); | |
| goto err_close_sock; | |
| } | |
| memset(&addr, 0, sizeof(addr)); | |
| addr.nl_family = AF_NETLINK; | |
| if (bind(ys->socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) { | |
| __perr(yse, "unable to bind to a socket address"); | |
| goto err_close_sock; | |
| } | |
| memset(&addr, 0, sizeof(addr)); | |
| addrlen = sizeof(addr); | |
| if (getsockname(ys->socket, (struct sockaddr *)&addr, &addrlen) < 0) { | |
| __perr(yse, "unable to read socket address"); | |
| goto err_close_sock; | |
| } | |
| ys->portid = addr.nl_pid; | |
| ys->seq = random(); | |
| if (yf->is_classic) { | |
| ys->family_id = yf->classic_id; | |
| } else if (ynl_sock_read_family(ys, yf->name)) { | |
| if (yse) | |
| memcpy(yse, &ys->err, sizeof(*yse)); | |
| goto err_close_sock; | |
| } | |
| return ys; | |
| err_close_sock: | |
| close(ys->socket); | |
| err_free_sock: | |
| free(ys); | |
| return NULL; | |
| } | |
| void ynl_sock_destroy(struct ynl_sock *ys) | |
| { | |
| struct ynl_ntf_base_type *ntf; | |
| close(ys->socket); | |
| while ((ntf = ynl_ntf_dequeue(ys))) | |
| ynl_ntf_free(ntf); | |
| free(ys->mcast_groups); | |
| free(ys); | |
| } | |
| /* YNL multicast handling */ | |
| void ynl_ntf_free(struct ynl_ntf_base_type *ntf) | |
| { | |
| ntf->free(ntf); | |
| } | |
| int ynl_subscribe(struct ynl_sock *ys, const char *grp_name) | |
| { | |
| unsigned int i; | |
| int err; | |
| for (i = 0; i < ys->n_mcast_groups; i++) | |
| if (!strcmp(ys->mcast_groups[i].name, grp_name)) | |
| break; | |
| if (i == ys->n_mcast_groups) { | |
| yerr(ys, ENOENT, "Multicast group '%s' not found", grp_name); | |
| return -1; | |
| } | |
| err = setsockopt(ys->socket, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, | |
| &ys->mcast_groups[i].id, | |
| sizeof(ys->mcast_groups[i].id)); | |
| if (err < 0) { | |
| perr(ys, "Subscribing to multicast group failed"); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| int ynl_socket_get_fd(struct ynl_sock *ys) | |
| { | |
| return ys->socket; | |
| } | |
| struct ynl_ntf_base_type *ynl_ntf_dequeue(struct ynl_sock *ys) | |
| { | |
| struct ynl_ntf_base_type *ntf; | |
| if (!ynl_has_ntf(ys)) | |
| return NULL; | |
| ntf = ys->ntf_first; | |
| ys->ntf_first = ntf->next; | |
| if (ys->ntf_last_next == &ntf->next) | |
| ys->ntf_last_next = &ys->ntf_first; | |
| return ntf; | |
| } | |
| static int ynl_ntf_parse(struct ynl_sock *ys, const struct nlmsghdr *nlh) | |
| { | |
| struct ynl_parse_arg yarg = { .ys = ys, }; | |
| const struct ynl_ntf_info *info; | |
| struct ynl_ntf_base_type *rsp; | |
| __u32 cmd; | |
| int ret; | |
| if (ys->family->is_classic) { | |
| cmd = nlh->nlmsg_type; | |
| } else { | |
| struct genlmsghdr *gehdr; | |
| gehdr = ynl_nlmsg_data(nlh); | |
| cmd = gehdr->cmd; | |
| } | |
| if (cmd >= ys->family->ntf_info_size) | |
| return YNL_PARSE_CB_ERROR; | |
| info = &ys->family->ntf_info[cmd]; | |
| if (!info->cb) | |
| return YNL_PARSE_CB_ERROR; | |
| rsp = calloc(1, info->alloc_sz); | |
| if (!rsp) | |
| return YNL_PARSE_CB_ERROR; | |
| rsp->free = info->free; | |
| yarg.data = rsp->data; | |
| yarg.rsp_policy = info->policy; | |
| ret = info->cb(nlh, &yarg); | |
| if (ret <= YNL_PARSE_CB_STOP) | |
| goto err_free; | |
| rsp->family = nlh->nlmsg_type; | |
| rsp->cmd = cmd; | |
| *ys->ntf_last_next = rsp; | |
| ys->ntf_last_next = &rsp->next; | |
| return YNL_PARSE_CB_OK; | |
| err_free: | |
| info->free(rsp); | |
| return YNL_PARSE_CB_ERROR; | |
| } | |
| static int | |
| ynl_ntf_trampoline(const struct nlmsghdr *nlh, struct ynl_parse_arg *yarg) | |
| { | |
| return ynl_ntf_parse(yarg->ys, nlh); | |
| } | |
| int ynl_ntf_check(struct ynl_sock *ys) | |
| { | |
| struct ynl_parse_arg yarg = { .ys = ys, }; | |
| int err; | |
| do { | |
| err = __ynl_sock_read_msgs(&yarg, ynl_ntf_trampoline, | |
| MSG_DONTWAIT); | |
| if (err < 0) | |
| return err; | |
| } while (err > 0); | |
| return 0; | |
| } | |
| /* YNL specific helpers used by the auto-generated code */ | |
| struct ynl_dump_list_type *YNL_LIST_END = (void *)(0xb4d123); | |
| void ynl_error_unknown_notification(struct ynl_sock *ys, __u8 cmd) | |
| { | |
| yerr(ys, YNL_ERROR_UNKNOWN_NTF, | |
| "Unknown notification message type '%d'", cmd); | |
| } | |
| int ynl_error_parse(struct ynl_parse_arg *yarg, const char *msg) | |
| { | |
| yerr(yarg->ys, YNL_ERROR_INV_RESP, "Error parsing response: %s", msg); | |
| return YNL_PARSE_CB_ERROR; | |
| } | |
| static int | |
| ynl_check_alien(struct ynl_sock *ys, const struct nlmsghdr *nlh, __u32 rsp_cmd) | |
| { | |
| if (ys->family->is_classic) { | |
| if (nlh->nlmsg_type != rsp_cmd) | |
| return ynl_ntf_parse(ys, nlh); | |
| } else { | |
| struct genlmsghdr *gehdr; | |
| if (ynl_nlmsg_data_len(nlh) < sizeof(*gehdr)) { | |
| yerr(ys, YNL_ERROR_INV_RESP, | |
| "Kernel responded with truncated message"); | |
| return -1; | |
| } | |
| gehdr = ynl_nlmsg_data(nlh); | |
| if (gehdr->cmd != rsp_cmd) | |
| return ynl_ntf_parse(ys, nlh); | |
| } | |
| return 0; | |
| } | |
| static | |
| int ynl_req_trampoline(const struct nlmsghdr *nlh, struct ynl_parse_arg *yarg) | |
| { | |
| struct ynl_req_state *yrs = (void *)yarg; | |
| int ret; | |
| ret = ynl_check_alien(yrs->yarg.ys, nlh, yrs->rsp_cmd); | |
| if (ret) | |
| return ret < 0 ? YNL_PARSE_CB_ERROR : YNL_PARSE_CB_OK; | |
| return yrs->cb(nlh, &yrs->yarg); | |
| } | |
| int ynl_exec(struct ynl_sock *ys, struct nlmsghdr *req_nlh, | |
| struct ynl_req_state *yrs) | |
| { | |
| int err; | |
| err = ynl_msg_end(ys, req_nlh); | |
| if (err < 0) | |
| return err; | |
| err = send(ys->socket, req_nlh, req_nlh->nlmsg_len, 0); | |
| if (err < 0) | |
| return err; | |
| do { | |
| err = ynl_sock_read_msgs(&yrs->yarg, ynl_req_trampoline); | |
| } while (err > 0); | |
| return err; | |
| } | |
| static int | |
| ynl_dump_trampoline(const struct nlmsghdr *nlh, struct ynl_parse_arg *data) | |
| { | |
| struct ynl_dump_state *ds = (void *)data; | |
| struct ynl_dump_list_type *obj; | |
| struct ynl_parse_arg yarg = {}; | |
| int ret; | |
| ret = ynl_check_alien(ds->yarg.ys, nlh, ds->rsp_cmd); | |
| if (ret) | |
| return ret < 0 ? YNL_PARSE_CB_ERROR : YNL_PARSE_CB_OK; | |
| obj = calloc(1, ds->alloc_sz); | |
| if (!obj) | |
| return YNL_PARSE_CB_ERROR; | |
| if (!ds->first) | |
| ds->first = obj; | |
| if (ds->last) | |
| ds->last->next = obj; | |
| ds->last = obj; | |
| yarg = ds->yarg; | |
| yarg.data = &obj->data; | |
| return ds->cb(nlh, &yarg); | |
| } | |
| static void *ynl_dump_end(struct ynl_dump_state *ds) | |
| { | |
| if (!ds->first) | |
| return YNL_LIST_END; | |
| ds->last->next = YNL_LIST_END; | |
| return ds->first; | |
| } | |
| int ynl_exec_dump(struct ynl_sock *ys, struct nlmsghdr *req_nlh, | |
| struct ynl_dump_state *yds) | |
| { | |
| int err; | |
| err = ynl_msg_end(ys, req_nlh); | |
| if (err < 0) | |
| return err; | |
| err = send(ys->socket, req_nlh, req_nlh->nlmsg_len, 0); | |
| if (err < 0) | |
| return err; | |
| do { | |
| err = ynl_sock_read_msgs(&yds->yarg, ynl_dump_trampoline); | |
| if (err < 0) | |
| goto err_close_list; | |
| } while (err > 0); | |
| yds->first = ynl_dump_end(yds); | |
| return 0; | |
| err_close_list: | |
| yds->first = ynl_dump_end(yds); | |
| return -1; | |
| } |