diff --git a/sbin/route/route_netlink.c b/sbin/route/route_netlink.c --- a/sbin/route/route_netlink.c +++ b/sbin/route/route_netlink.c @@ -533,14 +533,14 @@ return; } - if (r.rta_multipath != NULL) { + if (r.rta_multipath.num_nhops != 0) { bool first = true; memset(buf, ' ', sizeof(buf)); buf[len] = '\0'; - for (int i = 0; i < r.rta_multipath->num_nhops; i++) { - struct rta_mpath_nh *nh = &r.rta_multipath->nhops[i]; + for (uint32_t i = 0; i < r.rta_multipath.num_nhops; i++) { + struct rta_mpath_nh *nh = r.rta_multipath.nhops[i]; if (!first) printf("%s", buf); @@ -834,9 +834,9 @@ print_nlmsg(h, hdr, &attrs); } else { - if (r->rta_multipath != NULL) { - for (int i = 0; i < r->rta_multipath->num_nhops; i++) { - struct rta_mpath_nh *nh = &r->rta_multipath->nhops[i]; + if (r->rta_multipath.num_nhops != 0) { + for (uint32_t i = 0; i < r->rta_multipath.num_nhops; i++) { + struct rta_mpath_nh *nh = r->rta_multipath.nhops[i]; print_flushed_route(r, nh->gw); } diff --git a/sys/netlink/netlink_snl.h b/sys/netlink/netlink_snl.h --- a/sys/netlink/netlink_snl.h +++ b/sys/netlink/netlink_snl.h @@ -149,33 +149,40 @@ typedef bool snl_parse_post_f(struct snl_state *ss, void *target); struct snl_hdr_parser { - int hdr_off; /* aligned header size */ - int fp_size; - int np_size; + uint16_t in_hdr_size; /* Input header size */ + uint16_t out_size; /* Output structure size */ + uint16_t fp_size; /* Number of items in field parser */ + uint16_t np_size; /* Number of items in attribute parser */ const struct snl_field_parser *fp; /* array of header field parsers */ const struct snl_attr_parser *np; /* array of attribute parsers */ snl_parse_post_f *cb_post; /* post-parse callback */ }; -#define SNL_DECLARE_PARSER_EXT(_name, _t, _fp, _np, _cb) \ -static const struct snl_hdr_parser _name = { \ - .hdr_off = sizeof(_t), \ - .fp = &((_fp)[0]), \ - .np = &((_np)[0]), \ - .fp_size = NL_ARRAY_LEN(_fp), \ - .np_size = NL_ARRAY_LEN(_np), \ - .cb_post = _cb, \ +#define SNL_DECLARE_PARSER_EXT(_name, _sz_h_in, _sz_out, _fp, _np, _cb) \ +static const struct snl_hdr_parser _name = { \ + .in_hdr_size = _sz_h_in, \ + .out_size = _sz_out, \ + .fp = &((_fp)[0]), \ + .np = &((_np)[0]), \ + .fp_size = NL_ARRAY_LEN(_fp), \ + .np_size = NL_ARRAY_LEN(_np), \ + .cb_post = _cb, \ } -#define SNL_DECLARE_PARSER(_name, _t, _fp, _np) \ - SNL_DECLARE_PARSER_EXT(_name, _t, _fp, _np, NULL) +#define SNL_DECLARE_PARSER(_name, _t, _fp, _np) \ + SNL_DECLARE_PARSER_EXT(_name, sizeof(_t), 0, _fp, _np, NULL) -#define SNL_DECLARE_ATTR_PARSER(_name, _np) \ -static const struct snl_hdr_parser _name = { \ - .np = &((_np)[0]), \ - .np_size = NL_ARRAY_LEN(_np), \ +#define SNL_DECLARE_ATTR_PARSER_EXT(_name, _sz_out, _np, _cb) \ +static const struct snl_hdr_parser _name = { \ + .out_size = _sz_out, \ + .np = &((_np)[0]), \ + .np_size = NL_ARRAY_LEN(_np), \ + .cb_post = _cb, \ } +#define SNL_DECLARE_ATTR_PARSER(_name, _np) \ + SNL_DECLARE_ATTR_PARSER_EXT(_name, 0, _np, NULL) + static inline void * snl_allocz(struct snl_state *ss, int len) @@ -471,11 +478,13 @@ snl_parse_header(struct snl_state *ss, void *hdr, int len, const struct snl_hdr_parser *parser, void *target) { + struct nlattr *nla_head; + /* Extract fields first (if any) */ - snl_parse_fields(ss, hdr, parser->hdr_off, parser->fp, parser->fp_size, target); + snl_parse_fields(ss, hdr, parser->in_hdr_size, parser->fp, parser->fp_size, target); - struct nlattr *nla_head = (struct nlattr *)(void *)((char *)hdr + parser->hdr_off); - bool result = snl_parse_attrs_raw(ss, nla_head, len - parser->hdr_off, + nla_head = (struct nlattr *)(void *)((char *)hdr + parser->in_hdr_size); + bool result = snl_parse_attrs_raw(ss, nla_head, len - parser->in_hdr_size, parser->np, parser->np_size, target); if (result && parser->cb_post != NULL) @@ -642,6 +651,78 @@ return (snl_parse_header(ss, NLA_DATA(nla), NLA_DATA_LEN(nla), p, target)); } +struct snl_parray { + uint32_t count; + void **items; +}; + +static inline bool +snl_attr_get_parray_sz(struct snl_state *ss, struct nlattr *container_nla, + uint32_t start_size, const void *arg, void *target) +{ + const struct snl_hdr_parser *p = (const struct snl_hdr_parser *)arg; + struct snl_parray *array = target; + struct nlattr *nla; + uint32_t count = 0, size = start_size; + + if (p->out_size == 0) + return (false); + + array->items = snl_allocz(ss, size * sizeof(void *)); + if (array->items == NULL) + return (false); + + /* + * If the provided parser is an attribute parser, assume that each + * nla in the container nla is the container nla itself and parse + * the contents of this nla. + * Otherwise, run the parser on raw data, assuming the header of this + * data has u16 field with total size in the beginning. + */ + uint32_t data_off = 0; + + if (p->in_hdr_size == 0) + data_off = sizeof(struct nlattr); + + NLA_FOREACH(nla, NLA_DATA(container_nla), NLA_DATA_LEN(container_nla)) { + void *item = snl_allocz(ss, p->out_size); + + if (item == NULL) + return (false); + + void *data = (char *)(void *)nla + data_off; + int data_len = nla->nla_len - data_off; + + if (!(snl_parse_header(ss, data, data_len, p, item))) + return (false); + + if (count == size) { + uint32_t new_size = size * 2; + void **new_array = snl_allocz(ss, new_size *sizeof(void *)); + + memcpy(new_array, array->items, size * sizeof(void *)); + array->items = new_array; + size = new_size; + } + array->items[count++] = item; + } + array->count = count; + + return (true); +} + +/* + * Parses and stores the unknown-size array. + * Assumes each array item is a container and the NLAs in the container are parsable + * by the parser provided in @arg. + * Assumes @target is struct snl_parray + */ +static inline bool +snl_attr_get_parray(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target) +{ + return (snl_attr_get_parray_sz(ss, nla, 8, arg, target)); +} + static inline bool snl_attr_get_nla(struct snl_state *ss __unused, struct nlattr *nla, const void *arg __unused, void *target) @@ -878,6 +959,7 @@ memcpy(new_base, nw->base, nw->offset); if (nw->hdr != NULL) { int hdr_off = (char *)(nw->hdr) - nw->base; + nw->hdr = (struct nlmsghdr *)(void *)((char *)new_base + hdr_off); } nw->base = new_base; diff --git a/sys/netlink/netlink_snl_generic.h b/sys/netlink/netlink_snl_generic.h --- a/sys/netlink/netlink_snl_generic.h +++ b/sys/netlink/netlink_snl_generic.h @@ -52,9 +52,30 @@ #define SNL_DECLARE_GENL_PARSER(_name, _np) SNL_DECLARE_PARSER(_name,\ struct genlmsghdr, snl_fp_genl, _np) +struct snl_genl_ctrl_mcast_group { + uint32_t mcast_grp_id; + char *mcast_grp_name; +}; + +struct snl_genl_ctrl_mcast_groups { + uint32_t num_groups; + struct snl_genl_ctrl_mcast_group **groups; +}; + +#define _OUT(_field) offsetof(struct snl_genl_ctrl_mcast_group, _field) +static struct snl_attr_parser _nla_p_getmc[] = { + { .type = CTRL_ATTR_MCAST_GRP_NAME, .off = _OUT(mcast_grp_name), .cb = snl_attr_get_string }, + { .type = CTRL_ATTR_MCAST_GRP_ID, .off = _OUT(mcast_grp_id), .cb = snl_attr_get_uint32 }, +}; +#undef _OUT +SNL_DECLARE_ATTR_PARSER_EXT(_genl_ctrl_mc_parser, + sizeof(struct snl_genl_ctrl_mcast_group), + _nla_p_getmc, NULL); + struct _getfamily_attrs { uint16_t family_id; char *family_name; + struct snl_genl_ctrl_mcast_groups mcast_groups; }; #define _IN(_field) offsetof(struct genlmsghdr, _field) @@ -62,36 +83,52 @@ static struct snl_attr_parser _nla_p_getfam[] = { { .type = CTRL_ATTR_FAMILY_ID , .off = _OUT(family_id), .cb = snl_attr_get_uint16 }, { .type = CTRL_ATTR_FAMILY_NAME, .off = _OUT(family_name), .cb = snl_attr_get_string }, + { + .type = CTRL_ATTR_MCAST_GROUPS, + .off = _OUT(mcast_groups), + .cb = snl_attr_get_parray, + .arg = &_genl_ctrl_mc_parser, + }, }; #undef _IN #undef _OUT SNL_DECLARE_GENL_PARSER(_genl_ctrl_getfam_parser, _nla_p_getfam); -static inline uint16_t -snl_get_genl_family(struct snl_state *ss, const char *family_name) +static bool +snl_get_genl_family_info(struct snl_state *ss, const char *family_name, + struct _getfamily_attrs *attrs) { struct snl_writer nw; struct nlmsghdr *hdr; + memset(attrs, 0, sizeof(*attrs)); + snl_init_writer(ss, &nw); hdr = snl_create_genl_msg_request(&nw, GENL_ID_CTRL, CTRL_CMD_GETFAMILY); snl_add_msg_attr_string(&nw, CTRL_ATTR_FAMILY_NAME, family_name); if (snl_finalize_msg(&nw) == NULL || !snl_send_message(ss, hdr)) - return (0); + return (false); hdr = snl_read_reply(ss, hdr->nlmsg_seq); if (hdr != NULL && hdr->nlmsg_type != NLMSG_ERROR) { - struct _getfamily_attrs attrs = {}; - - if (snl_parse_nlmsg(ss, hdr, &_genl_ctrl_getfam_parser, &attrs)) - return (attrs.family_id); + if (snl_parse_nlmsg(ss, hdr, &_genl_ctrl_getfam_parser, attrs)) + return (true); } - return (0); + return (false); +} + +static inline uint16_t +snl_get_genl_family(struct snl_state *ss, const char *family_name) +{ + struct _getfamily_attrs attrs = {}; + + snl_get_genl_family_info(ss, family_name, &attrs); + return (attrs.family_id); } static const struct snl_hdr_parser *snl_all_genl_parsers[] = { - &_genl_ctrl_getfam_parser, + &_genl_ctrl_getfam_parser, &_genl_ctrl_mc_parser, }; #endif diff --git a/sys/netlink/netlink_snl_route_parsers.h b/sys/netlink/netlink_snl_route_parsers.h --- a/sys/netlink/netlink_snl_route_parsers.h +++ b/sys/netlink/netlink_snl_route_parsers.h @@ -85,51 +85,32 @@ } #undef _IN #undef _OUT -SNL_DECLARE_PARSER_EXT(_mpath_nh_parser, struct rtnexthop, _fp_p_mp_nh, _nla_p_mp_nh, _cb_p_mp_nh); +SNL_DECLARE_PARSER_EXT(_mpath_nh_parser, sizeof(struct rtnexthop), + sizeof(struct rta_mpath_nh), _fp_p_mp_nh, _nla_p_mp_nh, + _cb_p_mp_nh); struct rta_mpath { - int num_nhops; - struct rta_mpath_nh nhops[0]; + uint32_t num_nhops; + struct rta_mpath_nh **nhops; }; static bool -nlattr_get_multipath(struct snl_state *ss, struct nlattr *nla, const void *arg __unused, +nlattr_get_multipath(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target) { - int data_len = nla->nla_len - sizeof(struct nlattr); - struct rtnexthop *rtnh; + uint32_t start_size = 4; - int max_nhops = data_len / sizeof(struct rtnexthop); - size_t sz = (max_nhops + 2) * sizeof(struct rta_mpath_nh); + while (start_size < NLA_DATA_LEN(nla) / sizeof(struct rtnexthop)) + start_size *= 2; - struct rta_mpath *mp = snl_allocz(ss, sz); - if (mp == NULL) - return (false); - mp->num_nhops = 0; - - for (rtnh = (struct rtnexthop *)(void *)(nla + 1); data_len > 0; ) { - struct rta_mpath_nh *mpnh = &mp->nhops[mp->num_nhops++]; - - if (!snl_parse_header(ss, rtnh, rtnh->rtnh_len, &_mpath_nh_parser, mpnh)) - return (false); - - int len = NL_ITEM_ALIGN(rtnh->rtnh_len); - data_len -= len; - rtnh = (struct rtnexthop *)(void *)((char *)rtnh + len); - } - if (data_len != 0 || mp->num_nhops == 0) { - return (false); - } - - *((struct rta_mpath **)target) = mp; - return (true); + return (snl_attr_get_parray_sz(ss, nla, start_size, &_mpath_nh_parser, target)); } struct snl_parsed_route { struct sockaddr *rta_dst; struct sockaddr *rta_gw; struct nlattr *rta_metrics; - struct rta_mpath *rta_multipath; + struct rta_mpath rta_multipath; uint32_t rta_expires; uint32_t rta_oif; uint32_t rta_expire; @@ -185,7 +166,9 @@ } #undef _IN #undef _OUT -SNL_DECLARE_PARSER_EXT(snl_rtm_route_parser, struct rtmsg, _fp_p_route, _nla_p_route, _cb_p_route); +SNL_DECLARE_PARSER_EXT(snl_rtm_route_parser, sizeof(struct rtmsg), + sizeof(struct snl_parsed_route), _fp_p_route, _nla_p_route, + _cb_p_route); /* RTM_LINK message parser */ struct snl_parsed_link { @@ -299,7 +282,9 @@ } #undef _IN #undef _OUT -SNL_DECLARE_PARSER_EXT(snl_rtm_neigh_parser, struct ndmsg, _fp_p_neigh_s, _nla_p_neigh_s, _cb_p_neigh); +SNL_DECLARE_PARSER_EXT(snl_rtm_neigh_parser, sizeof(struct ndmsg), + sizeof(struct snl_parsed_neigh), _fp_p_neigh_s, _nla_p_neigh_s, + _cb_p_neigh); struct snl_parsed_addr { uint8_t ifa_family; @@ -347,7 +332,9 @@ } #undef _IN #undef _OUT -SNL_DECLARE_PARSER_EXT(snl_rtm_addr_parser, struct ifaddrmsg, _fp_p_addr_s, _nla_p_addr_s, _cb_p_addr); +SNL_DECLARE_PARSER_EXT(snl_rtm_addr_parser, sizeof(struct ifaddrmsg), + sizeof(struct snl_parsed_addr), _fp_p_addr_s, _nla_p_addr_s, + _cb_p_addr); struct snl_parsed_nhop { uint32_t nha_id; @@ -397,7 +384,8 @@ } #undef _IN #undef _OUT -SNL_DECLARE_PARSER_EXT(snl_nhmsg_parser, struct nhmsg, _fp_p_nh, _nla_p_nh, _cb_p_nh); +SNL_DECLARE_PARSER_EXT(snl_nhmsg_parser, sizeof(struct nhmsg), + sizeof(struct snl_parsed_nhop), _fp_p_nh, _nla_p_nh, _cb_p_nh); static const struct snl_hdr_parser *snl_all_route_parsers[] = { &_metrics_mp_nh_parser, &_mpath_nh_parser, &_metrics_parser, &snl_rtm_route_parser, diff --git a/tests/sys/netlink/test_snl_generic.c b/tests/sys/netlink/test_snl_generic.c --- a/tests/sys/netlink/test_snl_generic.c +++ b/tests/sys/netlink/test_snl_generic.c @@ -66,11 +66,50 @@ ATF_CHECK_EQ(snl_get_genl_family(&ss, "no-such-family"), 0); } +ATF_TC(test_snl_get_genl_family_groups); +ATF_TC_HEAD(test_snl_get_genl_family_groups, tc) +{ + atf_tc_set_md_var(tc, "descr", "Tests getting 'nlctrl' groups"); +} + +ATF_TC_BODY(test_snl_get_genl_family_groups, tc) +{ + struct snl_state ss; + struct snl_writer nw; + struct nlmsghdr *hdr; + + require_netlink(); + + if (!snl_init(&ss, NETLINK_GENERIC)) + atf_tc_fail("snl_init() failed"); + + snl_init_writer(&ss, &nw); + hdr = snl_create_genl_msg_request(&nw, GENL_ID_CTRL, CTRL_CMD_GETFAMILY); + snl_add_msg_attr_string(&nw, CTRL_ATTR_FAMILY_NAME, "nlctrl"); + snl_finalize_msg(&nw); + snl_send_message(&ss, hdr); + + hdr = snl_read_reply(&ss, hdr->nlmsg_seq); + ATF_CHECK(hdr != NULL); + ATF_CHECK(hdr->nlmsg_type != NLMSG_ERROR); + + struct _getfamily_attrs attrs = {}; + + ATF_CHECK(snl_parse_nlmsg(&ss, hdr, &_genl_ctrl_getfam_parser, &attrs)); + ATF_CHECK_EQ(attrs.mcast_groups.num_groups, 1); + + struct snl_genl_ctrl_mcast_group *group = attrs.mcast_groups.groups[0]; + + ATF_CHECK(group->mcast_grp_id > 0); + ATF_CHECK(!strcmp(group->mcast_grp_name, "notify")); +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, snl_verify_genl_parsers); ATF_TP_ADD_TC(tp, test_snl_get_genl_family_success); ATF_TP_ADD_TC(tp, test_snl_get_genl_family_failure); + ATF_TP_ADD_TC(tp, test_snl_get_genl_family_groups); return (atf_no_error()); } diff --git a/usr.bin/netstat/route_netlink.c b/usr.bin/netstat/route_netlink.c --- a/usr.bin/netstat/route_netlink.c +++ b/usr.bin/netstat/route_netlink.c @@ -237,11 +237,11 @@ if (rt.rtax_weight == 0) rt.rtax_weight = rt_default_weight; - if (rt.rta_multipath != NULL) { + if (rt.rta_multipath.num_nhops != 0) { uint32_t orig_rtflags = rt.rta_rtflags; uint32_t orig_mtu = rt.rtax_mtu; - for (int i = 0; i < rt.rta_multipath->num_nhops; i++) { - struct rta_mpath_nh *nhop = &rt.rta_multipath->nhops[i]; + for (uint32_t i = 0; i < rt.rta_multipath.num_nhops; i++) { + struct rta_mpath_nh *nhop = rt.rta_multipath.nhops[i]; rt.rta_gw = nhop->gw; rt.rta_oif = nhop->ifindex;