diff --git a/include/Makefile.am b/include/Makefile.am index eb61484..d39998c 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -31,6 +31,7 @@ nobase_libnlinclude_HEADERS = \ netlink/netlink-kernel.h \ netlink/netlink.h \ netlink/object.h \ + netlink/route/action.h \ netlink/route/cls/ematch/cmp.h \ netlink/route/cls/ematch/meta.h \ netlink/route/cls/ematch/nbyte.h \ diff --git a/include/netlink-private/types.h b/include/netlink-private/types.h index 3635e0f..6d38c1a 100644 --- a/include/netlink-private/types.h +++ b/include/netlink-private/types.h @@ -508,6 +508,12 @@ struct rtnl_cls uint16_t c_protocol; }; +struct rtnl_act +{ + NL_TC_GENERIC(c); + struct rtnl_act * a_next; +}; + struct rtnl_u32 { uint32_t cu_divisor; diff --git a/include/netlink/route/action.h b/include/netlink/route/action.h new file mode 100644 index 0000000..be52767 --- /dev/null +++ b/include/netlink/route/action.h @@ -0,0 +1,48 @@ +/* + * netlink/route/action.h Actions + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * Copyright (c) 2013 Cong Wang + */ + +#ifndef NETLINK_ACTION_H_ +#define NETLINK_ACTION_H_ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct rtnl_act *rtnl_act_alloc(void); +extern void rtnl_act_put(struct rtnl_act *); + +extern int rtnl_act_alloc_cache(struct nl_sock *, int, uint32_t, + struct nl_cache **); + +extern int rtnl_act_build_add_request(struct rtnl_act *, int, + struct nl_msg **); +extern int rtnl_act_add(struct nl_sock *, struct rtnl_act *, int); + +extern int rtnl_act_build_change_request(struct rtnl_act *, int, + struct nl_msg **); +extern int rtnl_act_build_delete_request(struct rtnl_act *, int, + struct nl_msg **); +extern int rtnl_act_delete(struct nl_sock *, struct rtnl_act *, + int); +extern int rtnl_act_append(struct rtnl_act **, struct rtnl_act *); +extern int rtnl_act_fill(struct nl_msg *, int, struct rtnl_act *); +extern void rtnl_act_put_all(struct rtnl_act **); +extern int rtnl_act_parse(struct rtnl_act **, struct nlattr *); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/netlink/route/tc.h b/include/netlink/route/tc.h index 836f7b1..870c1f2 100644 --- a/include/netlink/route/tc.h +++ b/include/netlink/route/tc.h @@ -27,6 +27,7 @@ enum rtnl_tc_type { RTNL_TC_TYPE_QDISC, RTNL_TC_TYPE_CLASS, RTNL_TC_TYPE_CLS, + RTNL_TC_TYPE_ACT, __RTNL_TC_TYPE_MAX, }; diff --git a/lib/Makefile.am b/lib/Makefile.am index af2aa55..949d3ac 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -60,7 +60,7 @@ route/cls/ematch_syntax.c: route/cls/ematch_syntax.y libnl_route_3_la_LIBADD = libnl-3.la libnl_route_3_la_SOURCES = \ - route/addr.c route/class.c route/cls.c route/link.c \ + route/addr.c route/class.c route/cls.c route/act.c route/link.c \ route/neigh.c route/neightbl.c route/nexthop.c route/qdisc.c \ route/route.c route/route_obj.c route/route_utils.c route/rtnl.c \ route/rule.c route/tc.c route/classid.c \ diff --git a/lib/route/act.c b/lib/route/act.c new file mode 100644 index 0000000..6b7a615 --- /dev/null +++ b/lib/route/act.c @@ -0,0 +1,600 @@ +/* + * lib/route/act.c Action + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * Copyright (c) 2013 Cong Wang + */ + +/** + * @ingroup tc + * @defgroup act Action + * @{ + */ + +#include +#include +#include +#include +#include +#include + + +static struct nl_object_ops act_obj_ops; +static struct nl_cache_ops rtnl_act_ops; + +int rtnl_act_append(struct rtnl_act **head, struct rtnl_act *new) +{ + struct rtnl_act *p_act; + int count = 1; + + if (*head == NULL) { + *head = new; + return 0; + } + + p_act = *head; + while (p_act->a_next) { + ++count; + p_act = p_act->a_next; + } + + if (count > TCA_ACT_MAX_PRIO) + return -NLE_RANGE; + + p_act->a_next = new; + return 0; +} + +static int rtnl_act_fill_one(struct nl_msg *msg, struct rtnl_act *act, int order) +{ + struct rtnl_tc *tc = TC_CAST(act); + struct rtnl_tc_ops *ops; + struct nlattr *nest; + int err = -NLE_NOMEM; + + nest = nla_nest_start(msg, order); + if (!nest) + goto nla_put_failure; + + if (tc->ce_mask & TCA_ATTR_KIND) + NLA_PUT_STRING(msg, TCA_ACT_KIND, tc->tc_kind); + + ops = rtnl_tc_get_ops(tc); + if (ops && (ops->to_msg_fill || ops->to_msg_fill_raw)) { + struct nlattr *opts; + void *data = rtnl_tc_data(tc); + + if (ops->to_msg_fill) { + if (!(opts = nla_nest_start(msg, TCA_ACT_OPTIONS))) + goto nla_put_failure; + + if ((err = ops->to_msg_fill(tc, data, msg)) < 0) + goto nla_put_failure; + + nla_nest_end(msg, opts); + } else if ((err = ops->to_msg_fill_raw(tc, data, msg)) < 0) + goto nla_put_failure; + } + nla_nest_end(msg, nest); + return 0; + +nla_put_failure: + return err; +} + +int rtnl_act_fill(struct nl_msg *msg, int attrtype, struct rtnl_act *act) +{ + struct rtnl_act *p_act = act; + struct nlattr *nest; + int err, order = 0; + + nest = nla_nest_start(msg, attrtype); + if (!nest) + return -NLE_MSGSIZE; + + while (p_act) { + err = rtnl_act_fill_one(msg, act, ++order); + if (err) + return err; + p_act = p_act->a_next; + } + + nla_nest_end(msg, nest); + return 0; +} + +static int rtnl_act_msg_build(struct rtnl_act *act, int type, int flags, + struct nl_msg **result) +{ + struct nl_msg *msg; + struct tcamsg tcahdr = { + .tca_family = AF_UNSPEC, + }; + int err = -NLE_MSGSIZE; + + msg = nlmsg_alloc_simple(type, flags); + if (!msg) + return -NLE_NOMEM; + + if (nlmsg_append(msg, &tcahdr, sizeof(tcahdr), NLMSG_ALIGNTO) < 0) + goto nla_put_failure; + + err = rtnl_act_fill(msg, TCA_ACT_TAB, act); + if (err < 0) + goto nla_put_failure; + + *result = msg; + return 0; + +nla_put_failure: + nlmsg_free(msg); + return err; +} + +static int act_build(struct rtnl_act *act, int type, int flags, + struct nl_msg **result) +{ + int err; + + err = rtnl_act_msg_build(act, type, flags, result); + if (err < 0) + return err; + return 0; +} + +/** + * @name Allocation/Freeing + * @{ + */ + +struct rtnl_act *rtnl_act_alloc(void) +{ + struct rtnl_tc *tc; + + tc = TC_CAST(nl_object_alloc(&act_obj_ops)); + if (tc) + tc->tc_type = RTNL_TC_TYPE_ACT; + + return (struct rtnl_act *) tc; +} + +void rtnl_act_put(struct rtnl_act *act) +{ + nl_object_put((struct nl_object *) act); +} + +/** @} */ + +/** + * @name Addition/Modification/Deletion + * @{ + */ + +/** + * Build a netlink message requesting the addition of an action + * @arg act Action to add + * @arg flags Additional netlink message flags + * @arg result Pointer to store resulting netlink message + * + * The behaviour of this function is identical to rtnl_act_add() with + * the exception that it will not send the message but return it int the + * provided return pointer instead. + * + * @see rtnl_act_add() + * + * @return 0 on success or a negative error code. + */ +int rtnl_act_build_add_request(struct rtnl_act *act, int flags, + struct nl_msg **result) +{ + return act_build(act, RTM_NEWACTION, flags, result); +} + +/** + * Add/Update action + * @arg sk Netlink socket + * @arg act Action to add/update + * @arg flags Additional netlink message flags + * + * Builds a \c RTM_NEWTFILTER netlink message requesting the addition + * of a new action and sends the message to the kernel. The + * configuration of the action is derived from the attributes of + * the specified traffic class. + * + * The following flags may be specified: + * - \c NLM_F_CREATE: Create action if it does not exist, + * otherwise -NLE_OBJ_NOTFOUND is returned. + * - \c NLM_F_EXCL: Return -NLE_EXISTS if an action with + * matching handle exists already. + * + * Existing actions with matching handles will be updated, unless + * the flag \c NLM_F_EXCL is specified. If no matching action + * exists, it will be created if the flag \c NLM_F_CREATE is set, + * otherwise the error -NLE_OBJ_NOTFOUND is returned. + * + * If the parent qdisc does not support classes, the error + * \c NLE_OPNOTSUPP is returned. + * + * After sending, the function will wait for the ACK or an eventual + * error message to be received and will therefore block until the + * operation has been completed. + * + * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause + * this function to return immediately after sending. In this case, + * it is the responsibility of the caller to handle any error + * messages returned. + * + * @return 0 on success or a negative error code. + */ +int rtnl_act_add(struct nl_sock *sk, struct rtnl_act *act, int flags) +{ + struct nl_msg *msg; + int err; + + if ((err = rtnl_act_build_add_request(act, flags, &msg)) < 0) + return err; + + return nl_send_sync(sk, msg); +} + +/** + * Build a netlink message to change action attributes + * @arg act Action to change + * @arg flags additional netlink message flags + * @arg result Pointer to store resulting message. + * + * Builds a new netlink message requesting a change of a neigh + * attributes. The netlink message header isn't fully equipped with + * all relevant fields and must thus be sent out via nl_send_auto_complete() + * or supplemented as needed. + * + * @return 0 on success or a negative error code. + */ +int rtnl_act_build_change_request(struct rtnl_act *act, int flags, + struct nl_msg **result) +{ + return act_build(act, RTM_NEWACTION, NLM_F_REPLACE | flags, result); +} + +/** + * Change an action + * @arg sk Netlink socket. + * @arg act action to change + * @arg flags additional netlink message flags + * + * Builds a netlink message by calling rtnl_act_build_change_request(), + * sends the request to the kernel and waits for the next ACK to be + * received and thus blocks until the request has been processed. + * + * @return 0 on sucess or a negative error if an error occured. + */ +int rtnl_act_change(struct nl_sock *sk, struct rtnl_act *act, int flags) +{ + struct nl_msg *msg; + int err; + + if ((err = rtnl_act_build_change_request(act, flags, &msg)) < 0) + return err; + + return nl_send_sync(sk, msg); +} + +/** + * Build netlink message requesting the deletion of an action + * @arg act Action to delete + * @arg flags Additional netlink message flags + * @arg result Pointer to store resulting netlink message + * + * The behaviour of this function is identical to rtnl_act_delete() with + * the exception that it will not send the message but return it in the + * provided return pointer instead. + * + * @see rtnl_act_delete() + * + * @return 0 on success or a negative error code. + */ +int rtnl_act_build_delete_request(struct rtnl_act *act, int flags, + struct nl_msg **result) +{ + return act_build(act, RTM_DELTFILTER, flags, result); +} + +/** + * Delete action + * @arg sk Netlink socket + * @arg act Action to delete + * @arg flags Additional netlink message flags + * + * Builds a \c RTM_DELTFILTER netlink message requesting the deletion + * of an action and sends the message to the kernel. + * + * The message is constructed out of the following attributes: + * - \c ifindex (required) + * - \c prio (required) + * - \c protocol (required) + * - \c handle (required) + * - \c parent (optional, if not specified parent equals root-qdisc) + * - \c kind (optional, must match if provided) + * + * All other action attributes including all class type specific + * attributes are ignored. + * + * After sending, the function will wait for the ACK or an eventual + * error message to be received and will therefore block until the + * operation has been completed. + * + * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause + * this function to return immediately after sending. In this case, + * it is the responsibility of the caller to handle any error + * messages returned. + * + * @return 0 on success or a negative error code. + */ +int rtnl_act_delete(struct nl_sock *sk, struct rtnl_act *act, int flags) +{ + struct nl_msg *msg; + int err; + + if ((err = rtnl_act_build_delete_request(act, flags, &msg)) < 0) + return err; + + return nl_send_sync(sk, msg); +} + +/** @} */ + +/** + * @name Cache Related Functions + * @{ + */ + +/** + * Allocate a cache and fill it with all configured actions + * @arg sk Netlink socket + * @arg ifindex Interface index of the network device + * @arg parent Parent qdisc/traffic class class + * @arg result Pointer to store the created cache + * + * Allocates a new action cache and fills it with a list of all + * configured action attached to the specified parent qdisc/traffic + * class on the specified network device. Release the cache with + * nl_cache_free(). + * + * @return 0 on success or a negative error code. + */ +int rtnl_act_alloc_cache(struct nl_sock *sk, int ifindex, uint32_t parent, + struct nl_cache **result) +{ + struct nl_cache * cache; + int err; + + if (!(cache = nl_cache_alloc(&rtnl_act_ops))) + return -NLE_NOMEM; + + cache->c_iarg1 = ifindex; + cache->c_iarg2 = parent; + + if (sk && (err = nl_cache_refill(sk, cache)) < 0) { + nl_cache_free(cache); + return err; + } + + *result = cache; + return 0; +} + +/** @} */ + +static void act_dump_line(struct rtnl_tc *tc, struct nl_dump_params *p) +{ +} + +void rtnl_act_put_all(struct rtnl_act **head) +{ + struct rtnl_act *curr, *next; + + curr = *head; + while (curr) { + next = curr->a_next; + rtnl_act_put(curr); + curr = next; + } + *head = NULL; +} + +int rtnl_act_parse(struct rtnl_act **head, struct nlattr *tb) +{ + struct rtnl_tc_ops *ops; + struct nlattr *tb2[TCA_ACT_MAX + 1]; + struct nlattr *nla[TCA_ACT_MAX_PRIO + 1]; + char kind[TCKINDSIZ]; + int err, i; + + err = nla_parse(nla, TCA_ACT_MAX_PRIO, nla_data(tb), + NLMSG_ALIGN(nla_len(tb)), NULL); + if (err < 0) + return err; + + for (i = 1; i <= TCA_ACT_MAX_PRIO && nla[i]; i++) { + struct rtnl_act *act; + struct rtnl_tc *tc; + + act = rtnl_act_alloc(); + if (!act) { + err = -NLE_NOMEM; + goto err_free; + } + tc = TC_CAST(act); + err = nla_parse(tb2, TCA_ACT_MAX, nla_data(nla[i]), + nla_len(nla[i]), NULL); + if (err < 0) + goto err_free; + + if (tb2[TCA_ACT_KIND] == NULL) { + err = -NLE_MISSING_ATTR; + goto err_free; + } + + nla_strlcpy(kind, tb2[TCA_ACT_KIND], sizeof(kind)); + rtnl_tc_set_kind(tc, kind); + + if (tb2[TCA_ACT_OPTIONS]) { + tc->tc_opts = nl_data_alloc_attr(tb2[TCA_ACT_OPTIONS]); + if (!tc->tc_opts) { + err = -NLE_NOMEM; + goto err_free; + } + tc->ce_mask |= TCA_ATTR_OPTS; + } + + ops = rtnl_tc_get_ops(tc); + if (ops && ops->to_msg_parser) { + void *data = rtnl_tc_data(tc); + + if (!data) { + err = -NLE_NOMEM; + goto err_free; + } + + err = ops->to_msg_parser(tc, data); + if (err < 0) + goto err_free; + } + err = rtnl_act_append(head, act); + if (err < 0) + goto err_free; + } + return 0; + +err_free: + rtnl_act_put_all(head); + + return err; +} + +static int rtnl_act_msg_parse(struct nlmsghdr *n, struct rtnl_act **act) +{ + struct rtnl_tc *tc = TC_CAST(act); + struct nl_cache *link_cache; + struct nlattr *tb[TCAA_MAX + 1]; + struct tcamsg *tm; + int err; + + tc->ce_msgtype = n->nlmsg_type; + + err = nlmsg_parse(n, sizeof(*tm), tb, TCAA_MAX, NULL); + if (err < 0) + return err; + + tm = nlmsg_data(n); + tc->tc_family = tm->tca_family; + + if (tb[TCA_ACT_TAB] == NULL) + return -NLE_MISSING_ATTR; + + err = rtnl_act_parse(act, tb[TCA_ACT_TAB]); + if (err < 0) + return err; + + if ((link_cache = __nl_cache_mngt_require("route/link"))) { + struct rtnl_link *link; + + if ((link = rtnl_link_get(link_cache, tc->tc_ifindex))) { + rtnl_tc_set_link(tc, link); + + /* rtnl_tc_set_link incs refcnt */ + rtnl_link_put(link); + } + } + + return 0; +} +static int act_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who, + struct nlmsghdr *nlh, struct nl_parser_param *pp) +{ + struct rtnl_act *act, *p_act; + int err; + + if (!(act = rtnl_act_alloc())) + return -NLE_NOMEM; + + if ((err = rtnl_act_msg_parse(nlh, &act)) < 0) + goto errout; + + p_act = act; + while(p_act) { + err = pp->pp_cb(OBJ_CAST(act), pp); + if (err) + break; + p_act = p_act->a_next; + } +errout: + rtnl_act_put(act); + + return err; +} + +static int act_request_update(struct nl_cache *cache, struct nl_sock *sk) +{ + struct tcamsg tcahdr = { + .tca_family = AF_UNSPEC, + }; + + return nl_send_simple(sk, RTM_GETACTION, NLM_F_DUMP, &tcahdr, + sizeof(tcahdr)); +} + +static struct rtnl_tc_type_ops act_ops = { + .tt_type = RTNL_TC_TYPE_ACT, + .tt_dump_prefix = "act", + .tt_dump = { + [NL_DUMP_LINE] = act_dump_line, + }, +}; + +static struct nl_cache_ops rtnl_act_ops = { + .co_name = "route/act", + .co_hdrsize = sizeof(struct tcmsg), + .co_msgtypes = { + { RTM_NEWACTION, NL_ACT_NEW, "new" }, + { RTM_DELACTION, NL_ACT_DEL, "del" }, + { RTM_GETACTION, NL_ACT_GET, "get" }, + END_OF_MSGTYPES_LIST, + }, + .co_protocol = NETLINK_ROUTE, + .co_request_update = act_request_update, + .co_msg_parser = act_msg_parser, + .co_obj_ops = &act_obj_ops, +}; + +static struct nl_object_ops act_obj_ops = { + .oo_name = "route/act", + .oo_size = sizeof(struct rtnl_act), + .oo_free_data = rtnl_tc_free_data, + .oo_clone = rtnl_tc_clone, + .oo_dump = { + [NL_DUMP_LINE] = rtnl_tc_dump_line, + [NL_DUMP_DETAILS] = rtnl_tc_dump_details, + [NL_DUMP_STATS] = rtnl_tc_dump_stats, + }, + .oo_compare = rtnl_tc_compare, + .oo_id_attrs = (TCA_ATTR_IFINDEX | TCA_ATTR_HANDLE), +}; + +static void __init act_init(void) +{ + rtnl_tc_type_register(&act_ops); + nl_cache_mngt_register(&rtnl_act_ops); +} + +static void __exit act_exit(void) +{ + nl_cache_mngt_unregister(&rtnl_act_ops); + rtnl_tc_type_unregister(&act_ops); +} + +/** @} */