/* SPDX-License-Identifier: BSD-3-Clause * Copyright 2017 6WIND S.A. * Copyright 2017 Mellanox Technologies, Ltd */ #include #include #include #include #include #include #include "tap_log.h" struct qdisc { uint32_t handle; uint32_t parent; }; struct list_args { int nlsk_fd; unsigned int ifindex; void *custom_arg; }; struct qdisc_custom_arg { uint32_t handle; uint32_t parent; uint8_t exists; }; /** * Initialize a netlink message with a TC header. * * @param[in, out] msg * The netlink message to fill. * @param[in] ifindex * The netdevice ifindex where the rule will be applied. * @param[in] type * The type of TC message to create (RTM_NEWTFILTER, RTM_NEWQDISC, etc.). * @param[in] flags * Overrides the default netlink flags for this msg with those specified. */ void tc_init_msg(struct nlmsg *msg, unsigned int ifindex, uint16_t type, uint16_t flags) { struct nlmsghdr *n = &msg->nh; n->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); n->nlmsg_type = type; if (flags) n->nlmsg_flags = flags; else n->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; msg->t.tcm_family = AF_UNSPEC; msg->t.tcm_ifindex = ifindex; } /** * Delete a specific QDISC identified by its iface, and it's handle and parent. * * @param[in] nlsk_fd * The netlink socket file descriptor used for communication. * @param[in] ifindex * The netdevice ifindex on whom the deletion will happen. * @param[in] qinfo * Additional info to identify the QDISC (handle and parent). * * @return * 0 on success, -1 otherwise with errno set. */ static int qdisc_del(int nlsk_fd, unsigned int ifindex, struct qdisc *qinfo) { struct nlmsg msg; int fd = 0; tc_init_msg(&msg, ifindex, RTM_DELQDISC, 0); msg.t.tcm_handle = qinfo->handle; msg.t.tcm_parent = qinfo->parent; /* if no netlink socket is provided, create one */ if (!nlsk_fd) { fd = tap_nl_init(0); if (fd < 0) { TAP_LOG(ERR, "Could not delete QDISC: null netlink socket"); return -1; } } else { fd = nlsk_fd; } if (tap_nl_send(fd, &msg.nh) < 0) goto error; if (tap_nl_recv_ack(fd) < 0) goto error; if (!nlsk_fd) return tap_nl_final(fd); return 0; error: if (!nlsk_fd) tap_nl_final(fd); return -1; } /** * Add the multiqueue QDISC with MULTIQ_MAJOR_HANDLE handle. * * @param[in] nlsk_fd * The netlink socket file descriptor used for communication. * @param[in] ifindex * The netdevice ifindex where to add the multiqueue QDISC. * * @return * 0 on success, -1 otherwise with errno set. */ int qdisc_add_multiq(int nlsk_fd, unsigned int ifindex) { struct tc_multiq_qopt opt = {0}; struct nlmsg msg; tc_init_msg(&msg, ifindex, RTM_NEWQDISC, NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE); msg.t.tcm_handle = TC_H_MAKE(MULTIQ_MAJOR_HANDLE, 0); msg.t.tcm_parent = TC_H_ROOT; tap_nlattr_add(&msg.nh, TCA_KIND, sizeof("multiq"), "multiq"); tap_nlattr_add(&msg.nh, TCA_OPTIONS, sizeof(opt), &opt); if (tap_nl_send(nlsk_fd, &msg.nh) < 0) return -1; if (tap_nl_recv_ack(nlsk_fd) < 0) return -1; return 0; } /** * Add the ingress QDISC with default ffff: handle. * * @param[in] nlsk_fd * The netlink socket file descriptor used for communication. * @param[in] ifindex * The netdevice ifindex where the QDISC will be added. * * @return * 0 on success, -1 otherwise with errno set. */ int qdisc_add_ingress(int nlsk_fd, unsigned int ifindex) { struct nlmsg msg; tc_init_msg(&msg, ifindex, RTM_NEWQDISC, NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE); msg.t.tcm_handle = TC_H_MAKE(TC_H_INGRESS, 0); msg.t.tcm_parent = TC_H_INGRESS; tap_nlattr_add(&msg.nh, TCA_KIND, sizeof("ingress"), "ingress"); if (tap_nl_send(nlsk_fd, &msg.nh) < 0) return -1; if (tap_nl_recv_ack(nlsk_fd) < 0) return -1; return 0; } /** * Callback function to delete a QDISC. * * @param[in] nh * The netlink message to parse, received from the kernel. * @param[in] arg * Custom arguments for the callback. * * @return * 0 on success, -1 otherwise with errno set. */ static int qdisc_del_cb(struct nlmsghdr *nh, void *arg) { struct tcmsg *t = NLMSG_DATA(nh); struct list_args *args = arg; struct qdisc qinfo = { .handle = t->tcm_handle, .parent = t->tcm_parent, }; /* filter out other ifaces' qdiscs */ if (args->ifindex != (unsigned int)t->tcm_ifindex) return 0; /* * Use another nlsk_fd (0) to avoid tampering with the current list * iteration. */ return qdisc_del(0, args->ifindex, &qinfo); } /** * Iterate over all QDISC, and call the callback() function for each. * * @param[in] nlsk_fd * The netlink socket file descriptor used for communication. * @param[in] ifindex * The netdevice ifindex where to find QDISCs. * @param[in] callback * The function to call for each QDISC. * @param[in, out] arg * The arguments to provide the callback function with. * * @return * 0 on success, -1 otherwise with errno set. */ static int qdisc_iterate(int nlsk_fd, unsigned int ifindex, int (*callback)(struct nlmsghdr *, void *), void *arg) { struct nlmsg msg; struct list_args args = { .nlsk_fd = nlsk_fd, .ifindex = ifindex, .custom_arg = arg, }; tc_init_msg(&msg, ifindex, RTM_GETQDISC, NLM_F_REQUEST | NLM_F_DUMP); if (tap_nl_send(nlsk_fd, &msg.nh) < 0) return -1; if (tap_nl_recv(nlsk_fd, callback, &args) < 0) return -1; return 0; } /** * Delete all QDISCs for a given netdevice. * * @param[in] nlsk_fd * The netlink socket file descriptor used for communication. * @param[in] ifindex * The netdevice ifindex where to find QDISCs. * * @return * 0 on success, -1 otherwise with errno set. */ int qdisc_flush(int nlsk_fd, unsigned int ifindex) { return qdisc_iterate(nlsk_fd, ifindex, qdisc_del_cb, NULL); } /** * Create the multiqueue QDISC, only if it does not exist already. * * @param[in] nlsk_fd * The netlink socket file descriptor used for communication. * @param[in] ifindex * The netdevice ifindex where to add the multiqueue QDISC. * * @return * 0 if the qdisc exists or if has been successfully added. * Return -1 otherwise. */ int qdisc_create_multiq(int nlsk_fd, unsigned int ifindex) { int err = 0; err = qdisc_add_multiq(nlsk_fd, ifindex); if (err < 0 && errno != -EEXIST) { TAP_LOG(ERR, "Could not add multiq qdisc (%d): %s", errno, strerror(errno)); return -1; } return 0; } /** * Create the ingress QDISC, only if it does not exist already. * * @param[in] nlsk_fd * The netlink socket file descriptor used for communication. * @param[in] ifindex * The netdevice ifindex where to add the ingress QDISC. * * @return * 0 if the qdisc exists or if has been successfully added. * Return -1 otherwise. */ int qdisc_create_ingress(int nlsk_fd, unsigned int ifindex) { int err = 0; err = qdisc_add_ingress(nlsk_fd, ifindex); if (err < 0 && errno != -EEXIST) { TAP_LOG(ERR, "Could not add ingress qdisc (%d): %s", errno, strerror(errno)); return -1; } return 0; }