/* * FST TC related routines implementation * * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of The Linux Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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 #include #include #include #include #include "utils/list.h" #define FST_MGR_COMPONENT "TC" #include "fst_manager.h" #include "fst_tc.h" #define IF_INDEX_NONE (-1) #define MULTIQ_QDISC_HANDLE 0x00010000 #define INGRESS_QDISC_HANDLE 0xFFFF0000 /* NOTE: we use priorities as an unique identifier of the filter in case we want * to modify/delete specific one. * This is because using filter's handle (struct tcmsg->tcm_handle) to identify * filters appeared to be very tricky, so sometimes kernel refuses the handles * provided by userspace applications. */ #define PRIO_BOND_TX_DUP_FILTER 1 #define PRIO_BOND_TX_BASE PRIO_BOND_TX_DUP_FILTER #define PRIO_WLAN_RX_EAPOL_FILTER 1 #define PRIO_WLAN_RX_DEDUP_FILTER 2 #define PRIO_MAX ((u16)-1) #ifdef CONFIG_LIBNL20 #define nlmsg_datalen(hdr) nlmsg_len(hdr) #define nl_send_auto(sk, msg) nl_send_auto_complete(sk, msg) #endif struct fst_tc_iface { char ifname[IFNAMSIZ]; int ifidx; struct dl_list ifaces_lentry; }; struct fst_tc { struct nl_sock *nl; char ifname[IFNAMSIZ]; int ifidx; Boolean is_sta; struct dl_list ifaces; struct dl_list filters; }; static u16 fst_tc_get_lowest_unused_prio(struct fst_tc *f) { struct fst_tc_filter_handle *h; u16 prio; int avail; for (prio = PRIO_BOND_TX_BASE; prio < PRIO_MAX; prio++) { avail = 1; dl_list_for_each(h, &f->filters, struct fst_tc_filter_handle, filters_lentry) { if (h->prio == prio) { avail = 0; break; } } if (avail) break; } return prio; } static void tc_set_ifidx(struct fst_tc *f, char *ifname, int ifidx) { struct fst_tc_iface *i; if (!os_strcmp(f->ifname, ifname)) { f->ifidx = ifidx; return; } dl_list_for_each(i, &f->ifaces, struct fst_tc_iface, ifaces_lentry) if (!strcmp(ifname, i->ifname)) { i->ifidx = ifidx; break; } } static int cb_network_link(struct nl_msg *msg, void *arg) { struct fst_tc *f = arg; struct nlmsghdr *hdr = nlmsg_hdr(msg); struct ifinfomsg *ifm = nlmsg_data(hdr); struct nlattr *attr; int remaining; if(hdr->nlmsg_type != RTM_NEWLINK && hdr->nlmsg_type != RTM_DELLINK) return NL_OK; attr = nlmsg_attrdata(hdr, sizeof(struct ifinfomsg)); remaining = nlmsg_attrlen(hdr, sizeof(struct ifinfomsg)); while(nla_ok(attr, remaining)) { if(nla_type(attr) == IFLA_IFNAME) if(nla_len(attr) > 0) tc_set_ifidx(f, nla_data(attr), ifm->ifi_index); attr = nla_next(attr, &remaining); } return NL_OK; } static int tc_qdisc_modify(struct fst_tc *f, unsigned add, const char *kind, int ifindex, u32 handle, u32 parent) { struct tc_multiq_qopt opt; struct tcmsg t; int res=0; struct nl_msg *msg; int nlmsgtype = RTM_DELQDISC; int nlmsflags = NLM_F_REQUEST | NLM_F_ACK; memset(&opt, 0, sizeof(opt)); if (add) { nlmsgtype = RTM_NEWQDISC; nlmsflags |= NLM_F_EXCL | NLM_F_CREATE; } msg = nlmsg_alloc_simple(nlmsgtype, nlmsflags); if(msg == NULL) { fst_mgr_printf(MSG_ERROR, "nlmsg_alloc_simple failed"); return -1; } t.tcm_family = AF_UNSPEC; t.tcm_handle = handle; t.tcm_parent = parent; t.tcm_ifindex = ifindex; nlmsg_append(msg, &t, sizeof(t), NLMSG_ALIGNTO); nla_put(msg, TCA_OPTIONS, sizeof(opt), &opt); nla_put(msg, TCA_KIND, os_strlen(kind) + 1, kind); res = nl_send_auto(f->nl, msg); if(res < 0) { fst_mgr_printf(MSG_ERROR, "nl_send_auto failed: %s", nl_geterror(res)); res = -1; goto tmqm_ret; } res = nl_recvmsgs_default(f->nl); if(res < 0) { if (add && errno == EEXIST) fst_mgr_printf(MSG_WARNING, "%s qdisc already exists", kind); else if (!add && errno == ENOENT) fst_mgr_printf(MSG_WARNING, "%s qdisc doesn't exist", kind); else { fst_mgr_printf(MSG_ERROR, "nl_recvmsgs_default failed: %s", nl_geterror(res)); res = -1; goto tmqm_ret; } } tmqm_ret: nlmsg_free(msg); return res; } static inline int tc_multiq_qdisc_modify(struct fst_tc *f, unsigned add) { int res = tc_qdisc_modify(f, add, "multiq", f->ifidx, MULTIQ_QDISC_HANDLE, TC_H_ROOT); if (res) fst_mgr_printf(MSG_ERROR, "%s: cannot %s multiq qdisc", f->ifname, add ? "add": "remove"); else fst_mgr_printf(MSG_DEBUG, "%s: multiq qdisc %s", f->ifname, add ? "added": "removed"); return res; } static int tc_ingress_qdisc_modify(struct fst_tc *f, unsigned add, struct fst_tc_iface *i) { int res = tc_qdisc_modify(f, add, "ingress", i->ifidx, INGRESS_QDISC_HANDLE, TC_H_INGRESS); if (res) fst_mgr_printf(MSG_ERROR, "%s: cannot %s ingress qdisc", i->ifname, add ? "add": "remove"); else fst_mgr_printf(MSG_DEBUG, "%s: ingress qdisc %s", i->ifname, add ? "added": "removed"); return res; } typedef int (*tc_filter_fill_clb)(struct fst_tc *f, unsigned add, struct nl_msg *msg, void *ctx); static int tc_filter_modify(struct fst_tc *f, unsigned add, u32 parent, int ifidx, uint16_t prio, const char *classifier, tc_filter_fill_clb clb, void *clb_ctx) { struct tcmsg t; int res=0; struct nl_msg *msg; int nlmsgtype = RTM_DELTFILTER; int nlmsflags = NLM_F_REQUEST | NLM_F_ACK; if (add) { nlmsgtype = RTM_NEWTFILTER; nlmsflags |= NLM_F_EXCL | NLM_F_CREATE; } msg = nlmsg_alloc_simple(nlmsgtype, nlmsflags); if(msg == NULL) { fst_mgr_printf(MSG_ERROR, "nlmsg_alloc_simple failed"); return -1; } os_memset(&t, 0 , sizeof(t)); t.tcm_family = AF_UNSPEC; t.tcm_parent = parent; t.tcm_ifindex = ifidx; t.tcm_info = TC_H_MAKE(((uint32_t) prio) << 16, htons(ETH_P_ALL)); nlmsg_append(msg, &t, sizeof(t), NLMSG_ALIGNTO); nla_put(msg, TCA_KIND, os_strlen(classifier) + 1, classifier); if (clb) { res = clb(f, add, msg, clb_ctx); if (res < 0) { fst_mgr_printf(MSG_ERROR, "clb failed: %d", res); goto tfm_ret; } } res = nl_send_auto(f->nl, msg); if(res < 0) { fst_mgr_printf(MSG_ERROR, "nl_send_auto failed: %s", nl_geterror(res)); res = -1; goto tfm_ret; } res = nl_recvmsgs_default(f->nl); if(res < 0) { fst_mgr_printf(MSG_ERROR, "nl_recvmsgs_default failed: %s", nl_geterror(res)); res = -1; goto tfm_ret; } tfm_ret: nlmsg_free(msg); return res; } struct tc_l2da_filter_modify_ctx { const uint8_t * mac; uint16_t queue_id; }; static int tc_l2da_filter_modify_clb(struct fst_tc *f, unsigned add, struct nl_msg *msg, void *ctx) { const char qdisc_action[] = "skbedit"; if (add) { struct tc_l2da_filter_modify_ctx *c = ctx; struct { struct tc_u32_sel sel; struct tc_u32_key keys[2]; } sel; struct tc_skbedit skbsel; memset(&sel, 0, sizeof(sel)); memset(&skbsel, 0, sizeof(skbsel)); if (c->mac) { uint32_t mac32; uint16_t mac16; mac16 = ((uint16_t) c->mac[0] << 8) | c->mac[1]; mac32 = ((uint32_t) c->mac[2] << 24) | ((uint32_t) c->mac[3] << 16) | ((uint32_t) c->mac[4] << 8) | c->mac[5]; sel.keys[0].val = htonl(mac32); sel.keys[0].mask = htonl((uint32_t) 0xFFFFFFFF); sel.keys[0].off = -12; sel.keys[0].offmask = 0; sel.keys[1].val = htonl((uint32_t) mac16); sel.keys[1].mask = htonl((uint32_t) 0xFFFF); sel.keys[1].off = -16; sel.keys[1].offmask = 0; sel.sel.flags |= TC_U32_TERMINAL; sel.sel.nkeys = 2; } else { /* NOTE: NULL mac means filter all packets. This is * achieved by ((h_proto & 0) == 0) which is * always true. */ sel.keys[0].val = 0; sel.keys[0].mask = 0; sel.keys[0].off = -2; /* h_proto */ sel.keys[0].offmask = 0; sel.sel.flags |= TC_U32_TERMINAL; sel.sel.nkeys = 1; } struct nlattr *t_opt, *t_act, *t_1, *t_act_opt; t_opt = nla_nest_start(msg, TCA_OPTIONS); if (t_opt == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } t_act = nla_nest_start(msg, TCA_U32_ACT); if (t_act == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } t_1 = nla_nest_start(msg, 1); if (t_1 == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } nla_put(msg, TCA_ACT_KIND, sizeof(qdisc_action), qdisc_action); t_act_opt = nla_nest_start(msg, TCA_ACT_OPTIONS); if (t_act_opt == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } nla_put(msg, TCA_SKBEDIT_PARMS, sizeof(skbsel), &skbsel); nla_put(msg, TCA_SKBEDIT_QUEUE_MAPPING, sizeof(c->queue_id), &c->queue_id); nla_nest_end(msg, t_act_opt); nla_nest_end(msg, t_1); nla_nest_end(msg, t_act); nla_put(msg, TCA_U32_SEL, sizeof(sel), &sel); nla_nest_end(msg, t_opt); } return 0; } static int tc_l2da_filter_modify(struct fst_tc *f, unsigned add, const uint8_t * mac, uint16_t queue_id, u16 prio) { struct tc_l2da_filter_modify_ctx ctx = { .mac = mac, .queue_id = queue_id, }; int res; res = tc_filter_modify(f, add, MULTIQ_QDISC_HANDLE, f->ifidx, prio, "u32", tc_l2da_filter_modify_clb, &ctx); if (res) fst_mgr_printf(MSG_ERROR, "%s: cannot %s L2DA filter#%u", f->ifname, add ? "add" : "remove", prio); else if (add && mac) fst_mgr_printf(MSG_DEBUG, "%s: L2DA filter#%u for " MACSTR " added", f->ifname, prio, MAC2STR(mac)); else if (add) fst_mgr_printf(MSG_DEBUG, "%s: L2DA filter#%u (universal) added", f->ifname, prio); else fst_mgr_printf(MSG_DEBUG, "%s: L2DA filter#%u removed", f->ifname, prio); return res; } static int tc_filter_add_mirred_action(struct nl_msg *msg, const char *ifname, int prio) { const char kind[] = "mirred"; struct nlattr *t_prio, *t_act_opt; struct tc_mirred p; memset(&p, 0, sizeof(p)); p.eaction = TCA_EGRESS_MIRROR; p.action = TC_ACT_PIPE; p.ifindex = if_nametoindex(ifname); t_prio = nla_nest_start(msg, prio); if (t_prio == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } nla_put(msg, TCA_ACT_KIND, sizeof(kind), kind); t_act_opt = nla_nest_start(msg, TCA_ACT_OPTIONS); if (t_act_opt == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } nla_put(msg, TCA_MIRRED_PARMS, sizeof(p), &p); nla_nest_end(msg, t_act_opt); nla_nest_end(msg, t_prio); return 0; } static int tc_filter_add_generic_action(struct nl_msg *msg, int prio, int action) { const char kind[] = "gact"; struct nlattr *t_prio, *t_act_opt; struct tc_gact p; memset(&p, 0, sizeof(p)); p.action = action; t_prio = nla_nest_start(msg, prio); if (t_prio == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } nla_put(msg, TCA_ACT_KIND, sizeof(kind), kind); t_act_opt = nla_nest_start(msg, TCA_ACT_OPTIONS); if (t_act_opt == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } nla_put(msg, TCA_GACT_PARMS, sizeof(p), &p); nla_nest_end(msg, t_act_opt); nla_nest_end(msg, t_prio); return 0; } static int tc_mc_filter_modify_clb(struct fst_tc *f, unsigned add, struct nl_msg *msg, void *ctx) { if (add) { struct fst_tc_iface *i; struct nlattr *t_opt, *t_act; int prio = 1; struct { struct tc_u32_sel sel; struct tc_u32_key keys[1]; } sel; int res; memset(&sel, 0, sizeof(sel)); sel.keys[0].val = htonl((uint32_t) 0x0100); sel.keys[0].mask = htonl((uint32_t) 0x0100); sel.keys[0].off = -16; sel.keys[0].offmask = 0; sel.sel.flags |= TC_U32_TERMINAL; sel.sel.nkeys = 1; t_opt = nla_nest_start(msg, TCA_OPTIONS); if (t_opt == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } t_act = nla_nest_start(msg, TCA_U32_ACT); if (t_act == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } dl_list_for_each(i, &f->ifaces, struct fst_tc_iface, ifaces_lentry) { res = tc_filter_add_mirred_action(msg, i->ifname, prio); if (res < 0) return res; ++prio; } res = tc_filter_add_generic_action(msg, prio, TC_ACT_SHOT); if (res < 0) return res; nla_nest_end(msg, t_act); nla_put(msg, TCA_U32_SEL, sizeof(sel), &sel); nla_nest_end(msg, t_opt); } return 0; } static int tc_mc_filter_modify(struct fst_tc *f, unsigned add) { int res = tc_filter_modify(f, add, MULTIQ_QDISC_HANDLE, f->ifidx, PRIO_BOND_TX_DUP_FILTER, "u32", tc_mc_filter_modify_clb, NULL); if (res) fst_mgr_printf(MSG_ERROR, "%s: cannot %s MC filter", f->ifname, add ? "add": "remove"); else fst_mgr_printf(MSG_DEBUG, "%s: MC filter %s", f->ifname, add ? "added": "removed"); return res; } static int fst_tc_get_iface_idxs(struct fst_tc *f) { int res; struct rtgenmsg rt_hdr = { .rtgen_family = AF_UNSPEC, }; struct fst_tc_iface *i; /* Set the receiving messages callback to get and process the links */ nl_socket_modify_cb(f->nl, NL_CB_VALID, NL_CB_CUSTOM, cb_network_link, (void*)f); res = nl_send_simple(f->nl, RTM_GETLINK, NLM_F_REQUEST | NLM_F_DUMP, &rt_hdr, sizeof(rt_hdr)); if (res < 0) { fst_mgr_printf(MSG_ERROR, "nl_send_simple failed: %s", nl_geterror(res)); return -1; } res = nl_recvmsgs_default(f->nl); if (res < 0) { fst_mgr_printf(MSG_ERROR, "nl_recvmsgs_default failed: %s", nl_geterror(res)); return -1; } /* We've done with the receiving callback */ nl_socket_modify_cb(f->nl, NL_CB_VALID, NL_CB_DEFAULT , NULL, NULL); if (f->ifidx == IF_INDEX_NONE) { fst_mgr_printf(MSG_ERROR, "Cannot find interface %s index", f->ifname); return -1; } fst_mgr_printf(MSG_INFO, "Interface %s index = %d", f->ifname, f->ifidx); dl_list_for_each(i, &f->ifaces, struct fst_tc_iface, ifaces_lentry) { if (i->ifidx == IF_INDEX_NONE) { fst_mgr_printf(MSG_ERROR, "Cannot find interface %s index", i->ifname); return -1; } fst_mgr_printf(MSG_INFO, "Interface %s index = %d", i->ifname, i->ifidx); } return 0; } static int fst_tc_add_multiq_qdisc(struct fst_tc *f) { return tc_multiq_qdisc_modify(f, 1); } static int fst_tc_del_multiq_qdisc(struct fst_tc *f) { return tc_multiq_qdisc_modify(f, 0); } static int fst_tc_add_ingress_qdisc(struct fst_tc *f) { struct fst_tc_iface *i, *j; dl_list_for_each(i, &f->ifaces, struct fst_tc_iface, ifaces_lentry) { if (!tc_ingress_qdisc_modify(f, 1, i)) fst_mgr_printf(MSG_WARNING, "%s: ingress qdisc added", i->ifname); else { fst_mgr_printf(MSG_WARNING, "%s: cannot remove ingress qdisc", i->ifname); goto add_failed; } } return 0; add_failed: dl_list_for_each(j, &f->ifaces, struct fst_tc_iface, ifaces_lentry) tc_ingress_qdisc_modify(f, 0, j); return -1; } static void fst_tc_del_ingress_qdisc(struct fst_tc *f) { struct fst_tc_iface *i; dl_list_for_each(i, &f->ifaces, struct fst_tc_iface, ifaces_lentry) if (!tc_ingress_qdisc_modify(f, 0, i)) fst_mgr_printf(MSG_WARNING, "%s: ingress qdisc removed", i->ifname); else fst_mgr_printf(MSG_WARNING, "%s: cannot remove ingress qdisc", i->ifname); } struct tc_rx_mc_filter_modify_ctx { const uint8_t *mac; }; static int tc_rx_mc_filter_modify_clb(struct fst_tc *f, unsigned add, struct nl_msg *msg, void *ctx) { if (add) { struct tc_l2da_filter_modify_ctx *c = ctx; uint32_t mac32; uint16_t mac16; struct { struct tc_u32_sel sel; struct tc_u32_key keys[2]; } sel; int res; memset(&sel, 0, sizeof(sel)); mac32 = ((uint32_t) c->mac[0] << 24) | ((uint32_t) c->mac[1] << 16) | ((uint32_t) c->mac[2] << 8) | c->mac[3]; mac16 = ((uint16_t) c->mac[4] << 8) | c->mac[5]; sel.keys[0].val = htonl(mac32); sel.keys[0].mask = htonl((uint32_t) 0xFFFFFFFF); sel.keys[0].off = -8; sel.keys[0].offmask = 0; sel.keys[1].val = htonl((uint32_t) mac16); sel.keys[1].mask = htonl((uint32_t) 0xFFFF); sel.keys[1].off = -4; sel.keys[1].offmask = 0; sel.sel.flags |= TC_U32_TERMINAL; sel.sel.nkeys = 2; struct nlattr *t_opt, *t_act; t_opt = nla_nest_start(msg, TCA_OPTIONS); if (t_opt == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } t_act = nla_nest_start(msg, TCA_U32_ACT); if (t_act == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } res = tc_filter_add_generic_action(msg, 1, TC_ACT_SHOT); if (res < 0) return res; nla_nest_end(msg, t_act); nla_put(msg, TCA_U32_SEL, sizeof(sel), &sel); nla_nest_end(msg, t_opt); } return 0; } static int fst_tc_modify_rx_mc_filters(struct fst_tc *f, unsigned add, const u8 * mac, const char *active_ifname, u16 prio) { struct fst_tc_iface *i; struct tc_rx_mc_filter_modify_ctx ctx = { .mac = mac, }; dl_list_for_each(i, &f->ifaces, struct fst_tc_iface, ifaces_lentry) { int res; if (!os_strcmp(i->ifname, active_ifname)) continue; res = tc_filter_modify(f, add, INGRESS_QDISC_HANDLE, i->ifidx, prio, "u32", tc_rx_mc_filter_modify_clb, &ctx); if (res) fst_mgr_printf(MSG_WARNING, "%s: cannot %s ingress filter#%u", i->ifname, add ? "add" : "remove", prio); else if (add) fst_mgr_printf(MSG_DEBUG, "%s: ingress filter#%u for " MACSTR " added", i->ifname, prio, MAC2STR(mac)); else fst_mgr_printf(MSG_DEBUG, "%s: ingress filter#%u removed", i->ifname, prio); } return 0; } static int tc_rx_eapol_filter_modify_clb(struct fst_tc *f, unsigned add, struct nl_msg *msg, void *ctx) { if (add) { struct nlattr *t_opt, *t_act; struct { struct tc_u32_sel sel; struct tc_u32_key keys[1]; } sel; int res; memset(&sel, 0, sizeof(sel)); sel.keys[0].val = htonl((uint32_t) ETH_P_PAE); sel.keys[0].mask = htonl((uint32_t) 0xFFFF); sel.keys[0].off = -2; sel.keys[0].offmask = 0; sel.sel.flags |= TC_U32_TERMINAL; sel.sel.nkeys = 1; t_opt = nla_nest_start(msg, TCA_OPTIONS); if (t_opt == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } t_act = nla_nest_start(msg, TCA_U32_ACT); if (t_act == NULL) { fst_mgr_printf(MSG_ERROR, "nla_nest_start failed"); return -1; } res = tc_filter_add_generic_action(msg, 1, TC_ACT_OK); if (res < 0) return res; nla_nest_end(msg, t_act); nla_put(msg, TCA_U32_SEL, sizeof(sel), &sel); nla_nest_end(msg, t_opt); } return 0; } static int fst_tc_modify_rx_eapol_filters(struct fst_tc *f, unsigned add) { struct fst_tc_iface *i; dl_list_for_each(i, &f->ifaces, struct fst_tc_iface, ifaces_lentry) { int res; res = tc_filter_modify(f, add, INGRESS_QDISC_HANDLE, i->ifidx, PRIO_WLAN_RX_EAPOL_FILTER, "u32", tc_rx_eapol_filter_modify_clb, NULL); if (res) fst_mgr_printf(MSG_WARNING, "%s: cannot %s ingress EAPOL filter", i->ifname, add ? "add" : "remove"); else if (add) fst_mgr_printf(MSG_DEBUG, "%s: ingress EAPOL filter added", i->ifname); else fst_mgr_printf(MSG_DEBUG, "%s: ingress EAPOL filter removed", i->ifname); } return 0; } struct fst_tc *fst_tc_create(Boolean is_sta) { struct fst_tc *f; int res; f = malloc(sizeof(*f)); if (!f) { fst_mgr_printf(MSG_ERROR, "Cannot allocate TC Ctrl"); goto alloc_failed; } f->ifidx = IF_INDEX_NONE; f->nl = nl_socket_alloc(); if (f->nl == NULL) { fst_mgr_printf(MSG_ERROR, "nl_socket_alloc failed"); goto socket_alloc_failed; } nl_socket_disable_seq_check(f->nl); res = nl_connect(f->nl, NETLINK_ROUTE); if(res != 0) { fst_mgr_printf(MSG_ERROR, "nl_connect failed: %s", nl_geterror(res)); goto connect_failed; } dl_list_init(&f->ifaces); dl_list_init(&f->filters); f->is_sta = is_sta; return f; connect_failed: nl_socket_free(f->nl); socket_alloc_failed: os_free(f); alloc_failed: return NULL; } int fst_tc_start(struct fst_tc *f, const char *ifname) { if (!ifname || f->ifidx != IF_INDEX_NONE) return -1; os_strlcpy(f->ifname, ifname, sizeof(f->ifname)); if (fst_tc_get_iface_idxs(f) != 0) { fst_mgr_printf(MSG_ERROR, "Cannot get iface indexes for bond#%s", ifname); goto fail_get_iface_idxs; } /* cleanup previously installed qdisc if any. ignore errors */ fst_tc_del_multiq_qdisc(f); fst_tc_del_ingress_qdisc(f); if (fst_tc_add_multiq_qdisc(f)) { fst_mgr_printf(MSG_ERROR, "Cannot add multiq qdisc for bond#%s", ifname); goto fail_add_muliq; } if (!f->is_sta) { /* AP can have multiple STAs connected over multiple interfaces. * Thus it needs to duplicate multicast frames to all interfaces * to make sure that all the STAs receive it, no matter which * interfaces are currently to which STA. */ if (tc_mc_filter_modify(f, 1)) { fst_mgr_printf(MSG_ERROR, "Cannot set MC filter"); goto fail_l2mc_filter; } } else { /* STA can only be connected to one AP, so there's no need for * duplication. However, it has to de-duplicate RX in order to * drop out packets sent by AP over inactive interface(s) due to * AP side duplication. */ if (fst_tc_add_ingress_qdisc(f)) { fst_mgr_printf(MSG_ERROR, "Cannot add ingress qdisc for bond#%s", ifname); goto fail_add_ingress; } if (fst_tc_modify_rx_eapol_filters(f, 1)) { fst_mgr_printf(MSG_ERROR, "Cannot add RX EAPOL filter"); goto fail_add_rx_eapol_filter; } } return 0; fail_add_rx_eapol_filter: if (f->is_sta) fst_tc_del_ingress_qdisc(f); fail_add_ingress: if (!f->is_sta) tc_mc_filter_modify(f, 0); fail_l2mc_filter: fst_tc_del_multiq_qdisc(f); fail_add_muliq: f->ifidx = IF_INDEX_NONE; fail_get_iface_idxs: memset(f->ifname, 0, sizeof(f->ifname)); return -1; } void fst_tc_stop(struct fst_tc *f) { if (f->is_sta) { fst_tc_modify_rx_eapol_filters(f, 0); fst_tc_del_ingress_qdisc(f); } else tc_mc_filter_modify(f, 0); fst_tc_del_multiq_qdisc(f); f->ifidx = IF_INDEX_NONE; memset(f->ifname, 0, sizeof(f->ifname)); } void fst_tc_delete(struct fst_tc *f) { struct fst_tc_iface *i; nl_close(f->nl); nl_socket_free(f->nl); while ((i = dl_list_first(&f->ifaces, struct fst_tc_iface, ifaces_lentry)) != NULL) fst_tc_unregister_iface(f, i->ifname); free(f); } int fst_tc_register_iface(struct fst_tc *f, const char *ifname) { struct fst_tc_iface *i; i = os_zalloc(sizeof(*i)); if (!i) { fst_mgr_printf(MSG_ERROR, "Cannot register iface %s", ifname); return -1; } os_strlcpy(i->ifname, ifname, sizeof(i->ifname)); i->ifidx = IF_INDEX_NONE; dl_list_add_tail(&f->ifaces, &i->ifaces_lentry); return 0; } void fst_tc_unregister_iface(struct fst_tc *f, const char *ifname) { struct fst_tc_iface *i; dl_list_for_each(i, &f->ifaces, struct fst_tc_iface, ifaces_lentry) { if (!os_strcmp(i->ifname, ifname)) { dl_list_del(&i->ifaces_lentry); os_free(i); break; } } } int fst_tc_add_l2da_filter(struct fst_tc *f, const uint8_t * mac, int queue_id, const char *ifname, struct fst_tc_filter_handle *filter_handle) { int res; filter_handle->prio = fst_tc_get_lowest_unused_prio(f); if (filter_handle->prio == PRIO_MAX) { fst_mgr_printf(MSG_ERROR, "%s: cannot find prio for " MACSTR, ifname, MAC2STR(mac)); goto get_avail_prio_fail; } if (!f->is_sta) { /* AP: * - can have many peers (STAs) connected and, in turn, filters * - traffic should be redirected on per-STA basis * - shouldn't de-duplicate RX, as STAs don't duplicate it */ res = tc_l2da_filter_modify(f, 1, mac, queue_id, filter_handle->prio); if (res) { fst_mgr_printf(MSG_ERROR, "%s: cannot add UC filter for " MACSTR, ifname, MAC2STR(mac)); goto l2da_filter_fail; } } else { /* STA: * - only has 1 peer (AP), so it should only have one filter * installed * - all the traffic should be redirected to the active * interface (NULL indicates this). * - should de-duplicate RX, as AP duplicates it */ WPA_ASSERT(filter_handle->prio == PRIO_BOND_TX_BASE); res = tc_l2da_filter_modify(f, 1, NULL, queue_id, filter_handle->prio); if (res) { fst_mgr_printf(MSG_ERROR, "%s: cannot add universal filter", ifname); goto l2da_filter_fail; } res = fst_tc_modify_rx_mc_filters(f, 1, mac, ifname, filter_handle->prio); if (res != 0) { fst_mgr_printf(MSG_ERROR, "%s: cannot add RX MC filter", ifname); goto rx_mc_filter_fail; } } os_strlcpy(filter_handle->ifname, ifname, sizeof(filter_handle->ifname)); dl_list_add(&f->filters, &filter_handle->filters_lentry); return 0; rx_mc_filter_fail: if (f->is_sta) tc_l2da_filter_modify(f, 0, NULL, 0, filter_handle->prio); l2da_filter_fail: get_avail_prio_fail: os_memset(filter_handle, 0, sizeof(*filter_handle)); return -1; } int fst_tc_del_l2da_filter(struct fst_tc *f, struct fst_tc_filter_handle *filter_handle) { int res = 0; if (tc_l2da_filter_modify(f, 0, NULL, 0, filter_handle->prio)) { fst_mgr_printf(MSG_ERROR, "%s: cannot del UC filter#%u", filter_handle->ifname, filter_handle->prio); res = -1; } if (f->is_sta && fst_tc_modify_rx_mc_filters(f, 0, NULL, filter_handle->ifname, filter_handle->prio) != 0) { fst_mgr_printf(MSG_ERROR, "%s: cannot del MC RX filter#%u", filter_handle->ifname, filter_handle->prio); res = -1; } dl_list_del(&filter_handle->filters_lentry); os_memset(filter_handle, 0, sizeof(*filter_handle)); return res; }