Add cfg80211_flow_parse(), which reads the Ethernet header, an optional VLAN tag and the IPv4 or IPv6 header of an MSDU into the key layout that the TCLAS parser produces, plus the ports of TCP, UDP and SCTP. It also records which parameters the frame carries, so that a classifier that selects a missing one, such as the ports of a later IP fragment, does not match on a zero value. The parser starts from skb->data instead of skb->protocol and the network header offset, because a frame forwarded between two stations of an AP arrives with skb->protocol set to ETH_P_802_3 and the network header reset. Add cfg80211_flow_key_build(), which masks the parsed frame down to the parameters that an MSCS selects. An AP learns a flow from an uplink frame and looks it up on the downlink, so the key can also be built for the mirrored direction, with the addresses and the ports swapped. Signed-off-by: Felix Fietkau --- include/net/cfg80211.h | 37 +++++++- net/wireless/scs.c | 224 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 261 insertions(+) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 0aaefb02a678..ab9f3ce06b88 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -4208,6 +4208,43 @@ struct cfg80211_tclas { }; /** + * struct cfg80211_flow_info - classifier parameters of one MSDU + * + * @key: every parameter the source carries, with nothing masked out + * @present: bitmap of &enum cfg80211_flow_field, the parameters the source + * actually carries. A classifier that names an absent parameter cannot + * match. + * @skb: the frame the parameters came from, or %NULL when the caller + * describes a flow rather than a frame. A filter classifier reads the + * frame again and cannot be evaluated without it. + * @l3_off: offset of the IP header in @skb + */ +struct cfg80211_flow_info { + struct cfg80211_flow_key key; + u32 present; + const struct sk_buff *skb; + u16 l3_off; +}; + +bool cfg80211_flow_parse(struct sk_buff *skb, struct cfg80211_flow_info *info); + +/** + * enum cfg80211_flow_dir - direction of a built flow key + * + * @CFG80211_FLOW_AS_IS: the key describes the frame that was parsed + * @CFG80211_FLOW_MIRRORED: the key describes the reverse direction, with the + * addresses and the ports swapped + */ +enum cfg80211_flow_dir { + CFG80211_FLOW_AS_IS, + CFG80211_FLOW_MIRRORED, +}; + +bool cfg80211_flow_key_build(const struct cfg80211_flow_info *info, u32 fields, + enum cfg80211_flow_dir dir, + struct cfg80211_flow_key *key); + +/** * DOC: Neighbor Awareness Networking (NAN) * * NAN uses two interface types: diff --git a/net/wireless/scs.c b/net/wireless/scs.c index b0d6037b1f5e..912a8cdc50a8 100644 --- a/net/wireless/scs.c +++ b/net/wireless/scs.c @@ -470,3 +470,227 @@ int cfg80211_parse_tclas_mask(const u8 *elems, size_t len, u32 *fields) return 0; } EXPORT_SYMBOL_IF_CFG80211_KUNIT(cfg80211_parse_tclas_mask); + +#define FLOW_F_PORTS (FLOW_F(SRC_PORT) | FLOW_F(DST_PORT)) + +static void cfg80211_flow_parse_ports(struct sk_buff *skb, + unsigned int offset, int proto, + struct cfg80211_flow_info *info) +{ + __be16 ports[2], *p; + + switch (proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + case IPPROTO_SCTP: + break; + default: + return; + } + + p = skb_header_pointer(skb, offset, sizeof(ports), ports); + if (!p) + return; + + info->key.src_port = p[0]; + info->key.dst_port = p[1]; + info->present |= FLOW_F_PORTS; +} + +static void cfg80211_flow_parse_ipv4(struct sk_buff *skb, unsigned int offset, + struct cfg80211_flow_info *info) +{ + struct iphdr iph_buf, *iph; + + iph = skb_header_pointer(skb, offset, sizeof(*iph), &iph_buf); + if (!iph || iph->version != 4 || iph->ihl < 5) + return; + + info->key.ip_version = 4; + info->key.dscp = ipv4_get_dsfield(iph) >> 2; + info->key.proto = iph->protocol; + memcpy(&info->key.src, &iph->saddr, sizeof(iph->saddr)); + memcpy(&info->key.dst, &iph->daddr, sizeof(iph->daddr)); + info->present |= FLOW_F(IP_VERSION) | FLOW_F(IP_SRC) | FLOW_F(IP_DST) | + FLOW_F(DSCP) | FLOW_F(PROTO); + + if (iph->frag_off & htons(IP_OFFSET)) + return; + + cfg80211_flow_parse_ports(skb, offset + iph->ihl * 4, iph->protocol, + info); +} + +static void cfg80211_flow_parse_ipv6(struct sk_buff *skb, unsigned int offset, + struct cfg80211_flow_info *info) +{ + struct ipv6hdr ip6h_buf, *ip6h; + __be16 frag_off = 0; + int l4_off; + u8 nexthdr; + + ip6h = skb_header_pointer(skb, offset, sizeof(*ip6h), &ip6h_buf); + if (!ip6h || ip6h->version != 6) + return; + + info->key.ip_version = 6; + info->key.dscp = ipv6_get_dsfield(ip6h) >> 2; + info->key.flow_label = ip6_flowlabel(ip6h); + info->key.src = ip6h->saddr; + info->key.dst = ip6h->daddr; + + /* The classifier matches Next Header, not the layer 4 protocol */ + info->key.proto = ip6h->nexthdr; + info->present |= FLOW_F(IP_VERSION) | FLOW_F(IP_SRC) | FLOW_F(IP_DST) | + FLOW_F(DSCP) | FLOW_F(FLOW_LABEL) | FLOW_F(PROTO); + + nexthdr = ip6h->nexthdr; + l4_off = ipv6_skip_exthdr(skb, offset + sizeof(*ip6h), &nexthdr, + &frag_off); + if (l4_off < 0 || frag_off & htons(IP6_OFFSET)) + return; + + cfg80211_flow_parse_ports(skb, l4_off, nexthdr, info); +} + +/** + * cfg80211_flow_parse - read the classifier parameters of an MSDU + * + * @skb: the frame, in IEEE 802.3 format + * @info: receives the parameters + * + * Parses from skb->data, since a frame forwarded between two stations + * arrives with skb->protocol set to ETH_P_802_3 and no network header. + * + * Return: %true when @info describes the frame. + */ +bool cfg80211_flow_parse(struct sk_buff *skb, struct cfg80211_flow_info *info) +{ + struct ethhdr eth_buf, *eth; + unsigned int offset = ETH_HLEN; + __be16 proto; + + memset(info, 0, sizeof(*info)); + + eth = skb_header_pointer(skb, 0, sizeof(*eth), ð_buf); + if (!eth) + return false; + + ether_addr_copy(info->key.sa, eth->h_source); + ether_addr_copy(info->key.da, eth->h_dest); + info->present = FLOW_F(ETH_SA) | FLOW_F(ETH_DA); + proto = eth->h_proto; + + if (skb_vlan_tag_present(skb)) { + info->key.vlan_tci = htons(skb_vlan_tag_get(skb)); + info->present |= FLOW_F_VLAN; + } else if (eth_type_vlan(proto)) { + struct vlan_hdr vhdr_buf, *vhdr; + + vhdr = skb_header_pointer(skb, offset, sizeof(*vhdr), + &vhdr_buf); + if (!vhdr) + return false; + + info->key.vlan_tci = vhdr->h_vlan_TCI; + info->present |= FLOW_F_VLAN; + proto = vhdr->h_vlan_encapsulated_proto; + offset += sizeof(*vhdr); + } + + /* Use the type behind the VLAN tag, whether it was stripped or not */ + info->key.eth_type = proto; + info->present |= FLOW_F(ETH_TYPE); + + info->skb = skb; + info->l3_off = offset; + + switch (proto) { + case htons(ETH_P_IP): + cfg80211_flow_parse_ipv4(skb, offset, info); + break; + case htons(ETH_P_IPV6): + cfg80211_flow_parse_ipv6(skb, offset, info); + break; + } + + return true; +} +EXPORT_SYMBOL(cfg80211_flow_parse); + +static void flow_key_swap(struct cfg80211_flow_key *k) +{ + u8 addr[ETH_ALEN]; + + memcpy(addr, k->sa, ETH_ALEN); + memcpy(k->sa, k->da, ETH_ALEN); + memcpy(k->da, addr, ETH_ALEN); + + swap(k->src, k->dst); + swap(k->src_port, k->dst_port); +} + +static u32 cfg80211_flow_fields_mirror(u32 fields) +{ + static const u8 pairs[][2] = { + { CFG80211_FLOW_F_ETH_SA, CFG80211_FLOW_F_ETH_DA }, + { CFG80211_FLOW_F_IP_SRC, CFG80211_FLOW_F_IP_DST }, + { CFG80211_FLOW_F_SRC_PORT, CFG80211_FLOW_F_DST_PORT }, + }; + unsigned int i; + u32 out = fields; + + for (i = 0; i < ARRAY_SIZE(pairs); i++) { + u32 lo = BIT(pairs[i][0]), hi = BIT(pairs[i][1]); + + out &= ~(lo | hi); + if (fields & lo) + out |= hi; + if (fields & hi) + out |= lo; + } + + return out; +} + +/** + * cfg80211_flow_key_build - build an MSCS lookup key + * + * @info: parsed frame, from cfg80211_flow_parse() + * @fields: classifier parameters of the MSCS, a bitmap of + * &enum cfg80211_flow_field + * @dir: %CFG80211_FLOW_MIRRORED when @info describes an uplink frame and the + * key must describe the downlink direction + * @key: receives the key + * + * Everything that @fields does not select stays zero, so the key is hashed and + * compared as a byte string. + * + * Return: %false when @info lacks a parameter that @fields selects, in which + * case the MSDU is not classified at all. + */ +bool cfg80211_flow_key_build(const struct cfg80211_flow_info *info, u32 fields, + enum cfg80211_flow_dir dir, + struct cfg80211_flow_key *key) +{ + struct cfg80211_flow_key mirrored; + + if (dir == CFG80211_FLOW_AS_IS) { + if (fields & ~info->present) + return false; + + flow_key_select(&info->key, fields, key); + + return true; + } + + if (cfg80211_flow_fields_mirror(fields) & ~info->present) + return false; + + mirrored = info->key; + flow_key_swap(&mirrored); + flow_key_select(&mirrored, fields, key); + + return true; +} +EXPORT_SYMBOL(cfg80211_flow_key_build); -- git-series 0.9.1