Contributors: 2
Author Tokens Token Proportion Commits Commit Proportion
Emmanuel Grumbach 1521 99.02% 1 33.33%
Johannes Berg 15 0.98% 2 66.67%
Total 1536 3


// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2021-2022 Intel Corporation
 */

#include <uapi/linux/if_ether.h>
#include <uapi/linux/if_arp.h>
#include <uapi/linux/icmp.h>

#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/ieee80211.h>

#include <net/cfg80211.h>
#include <net/ip.h>

#include <linux/if_arp.h>
#include <linux/icmp.h>
#include <linux/udp.h>
#include <linux/ip.h>
#include <linux/mm.h>

#include "internal.h"
#include "sap.h"
#include "iwl-mei.h"

/*
 * Returns true if further filtering should be stopped. Only in that case
 * pass_to_csme and rx_handler_res are set. Otherwise, next level of filters
 * should be checked.
 */
static bool iwl_mei_rx_filter_eth(const struct ethhdr *ethhdr,
				  const struct iwl_sap_oob_filters *filters,
				  bool *pass_to_csme,
				  rx_handler_result_t *rx_handler_res)
{
	const struct iwl_sap_eth_filter *filt;

	/* This filter is not relevant for UCAST packet */
	if (!is_multicast_ether_addr(ethhdr->h_dest) ||
	    is_broadcast_ether_addr(ethhdr->h_dest))
		return false;

	for (filt = &filters->eth_filters[0];
	     filt < &filters->eth_filters[0] + ARRAY_SIZE(filters->eth_filters);
	     filt++) {
		/* Assume there are no enabled filter after a disabled one */
		if (!(filt->flags & SAP_ETH_FILTER_ENABLED))
			break;

		if (compare_ether_header(filt->mac_address, ethhdr->h_dest))
			continue;

		/* Packet needs to reach the host's stack */
		if (filt->flags & SAP_ETH_FILTER_COPY)
			*rx_handler_res = RX_HANDLER_PASS;
		else
			*rx_handler_res = RX_HANDLER_CONSUMED;

		/* We have an authoritative answer, stop filtering */
		if (filt->flags & SAP_ETH_FILTER_STOP) {
			*pass_to_csme = true;
			return true;
		}

		return false;
	}

	 /* MCAST frames that don't match layer 2 filters are not sent to ME */
	*pass_to_csme  = false;

	return true;
}

/*
 * Returns true iff the frame should be passed to CSME in which case
 * rx_handler_res is set.
 */
static bool iwl_mei_rx_filter_arp(struct sk_buff *skb,
				  const struct iwl_sap_oob_filters *filters,
				  rx_handler_result_t *rx_handler_res)
{
	const struct iwl_sap_ipv4_filter *filt = &filters->ipv4_filter;
	const struct arphdr *arp;
	const __be32 *target_ip;
	u32 flags = le32_to_cpu(filt->flags);

	if (!pskb_may_pull(skb, arp_hdr_len(skb->dev)))
		return false;

	arp = arp_hdr(skb);

	/* Handle only IPv4 over ethernet ARP frames */
	if (arp->ar_hrd != htons(ARPHRD_ETHER) ||
	    arp->ar_pro != htons(ETH_P_IP))
		return false;

	/*
	 * After the ARP header, we have:
	 * src MAC address   - 6 bytes
	 * src IP address    - 4 bytes
	 * target MAC addess - 6 bytes
	 */
	target_ip = (const void *)((const u8 *)(arp + 1) +
				   ETH_ALEN + sizeof(__be32) + ETH_ALEN);

	/*
	 * ARP request is forwarded to ME only if IP address match in the
	 * ARP request's target ip field.
	 */
	if (arp->ar_op == htons(ARPOP_REQUEST) &&
	    (filt->flags & cpu_to_le32(SAP_IPV4_FILTER_ARP_REQ_PASS)) &&
	    (filt->ipv4_addr == 0 || filt->ipv4_addr == *target_ip)) {
		if (flags & SAP_IPV4_FILTER_ARP_REQ_COPY)
			*rx_handler_res = RX_HANDLER_PASS;
		else
			*rx_handler_res = RX_HANDLER_CONSUMED;

		return true;
	}

	/* ARP reply is always forwarded to ME regardless of the IP */
	if (flags & SAP_IPV4_FILTER_ARP_RESP_PASS &&
	    arp->ar_op == htons(ARPOP_REPLY)) {
		if (flags & SAP_IPV4_FILTER_ARP_RESP_COPY)
			*rx_handler_res = RX_HANDLER_PASS;
		else
			*rx_handler_res = RX_HANDLER_CONSUMED;

		return true;
	}

	return false;
}

static bool
iwl_mei_rx_filter_tcp_udp(struct sk_buff *skb, bool  ip_match,
			  const struct iwl_sap_oob_filters *filters,
			  rx_handler_result_t *rx_handler_res)
{
	const struct iwl_sap_flex_filter *filt;

	for (filt = &filters->flex_filters[0];
	     filt < &filters->flex_filters[0] + ARRAY_SIZE(filters->flex_filters);
	     filt++) {
		if (!(filt->flags & SAP_FLEX_FILTER_ENABLED))
			break;

		/*
		 * We are required to have a match on the IP level and we didn't
		 * have such match.
		 */
		if ((filt->flags &
		     (SAP_FLEX_FILTER_IPV4 | SAP_FLEX_FILTER_IPV6)) &&
		    !ip_match)
			continue;

		if ((filt->flags & SAP_FLEX_FILTER_UDP) &&
		    ip_hdr(skb)->protocol != IPPROTO_UDP)
			continue;

		if ((filt->flags & SAP_FLEX_FILTER_TCP) &&
		    ip_hdr(skb)->protocol != IPPROTO_TCP)
			continue;

		/*
		 * We must have either a TCP header or a UDP header, both
		 * starts with a source port and then a destination port.
		 * Both are big endian words.
		 * Use a UDP header and that will work for TCP as well.
		 */
		if ((filt->src_port && filt->src_port != udp_hdr(skb)->source) ||
		    (filt->dst_port && filt->dst_port != udp_hdr(skb)->dest))
			continue;

		if (filt->flags & SAP_FLEX_FILTER_COPY)
			*rx_handler_res = RX_HANDLER_PASS;
		else
			*rx_handler_res = RX_HANDLER_CONSUMED;

		return true;
	}

	return false;
}

static bool iwl_mei_rx_filter_ipv4(struct sk_buff *skb,
				   const struct iwl_sap_oob_filters *filters,
				   rx_handler_result_t *rx_handler_res)
{
	const struct iwl_sap_ipv4_filter *filt = &filters->ipv4_filter;
	const struct iphdr *iphdr;
	unsigned int iphdrlen;
	bool match;

	if (!pskb_may_pull(skb, skb_network_offset(skb) + sizeof(*iphdr)) ||
	    !pskb_may_pull(skb, skb_network_offset(skb) + ip_hdrlen(skb)))
		return false;

	iphdrlen = ip_hdrlen(skb);
	iphdr = ip_hdr(skb);
	match = !filters->ipv4_filter.ipv4_addr ||
		filters->ipv4_filter.ipv4_addr == iphdr->daddr;

	skb_set_transport_header(skb, skb_network_offset(skb) + iphdrlen);

	switch (ip_hdr(skb)->protocol) {
	case IPPROTO_UDP:
	case IPPROTO_TCP:
		/*
		 * UDP header is shorter than TCP header and we look at the first bytes
		 * of the header anyway (see below).
		 * If we have a truncated TCP packet, let CSME handle this.
		 */
		if (!pskb_may_pull(skb, skb_transport_offset(skb) +
				   sizeof(struct udphdr)))
			return false;

		return iwl_mei_rx_filter_tcp_udp(skb, match,
						 filters, rx_handler_res);

	case IPPROTO_ICMP: {
		struct icmphdr *icmp;

		if (!pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(*icmp)))
			return false;

		icmp = icmp_hdr(skb);

		/*
		 * Don't pass echo requests to ME even if it wants it as we
		 * want the host to answer.
		 */
		if ((filt->flags & cpu_to_le32(SAP_IPV4_FILTER_ICMP_PASS)) &&
		    match && (icmp->type != ICMP_ECHO || icmp->code != 0)) {
			if (filt->flags & cpu_to_le32(SAP_IPV4_FILTER_ICMP_COPY))
				*rx_handler_res = RX_HANDLER_PASS;
			else
				*rx_handler_res = RX_HANDLER_CONSUMED;

			return true;
		}
		break;
		}
	case IPPROTO_ICMPV6:
		/* TODO: Should we have the same ICMP request logic here too? */
		if ((filters->icmpv6_flags & cpu_to_le32(SAP_ICMPV6_FILTER_ENABLED) &&
		     match)) {
			if (filters->icmpv6_flags &
			    cpu_to_le32(SAP_ICMPV6_FILTER_COPY))
				*rx_handler_res = RX_HANDLER_PASS;
			else
				*rx_handler_res = RX_HANDLER_CONSUMED;

			return true;
		}
		break;
	default:
		return false;
	}

	return false;
}

static bool iwl_mei_rx_filter_ipv6(struct sk_buff *skb,
				   const struct iwl_sap_oob_filters *filters,
				   rx_handler_result_t *rx_handler_res)
{
	*rx_handler_res = RX_HANDLER_PASS;

	/* TODO */

	return false;
}

static rx_handler_result_t
iwl_mei_rx_pass_to_csme(struct sk_buff *skb,
			const struct iwl_sap_oob_filters *filters,
			bool *pass_to_csme)
{
	const struct ethhdr *ethhdr = (void *)skb_mac_header(skb);
	rx_handler_result_t rx_handler_res = RX_HANDLER_PASS;
	bool (*filt_handler)(struct sk_buff *skb,
			     const struct iwl_sap_oob_filters *filters,
			     rx_handler_result_t *rx_handler_res);

	/*
	 * skb->data points the IP header / ARP header and the ETH header
	 * is in the headroom.
	 */
	skb_reset_network_header(skb);

	/*
	 * MCAST IP packets sent by us are received again here without
	 * an ETH header. Drop them here.
	 */
	if (!skb_mac_offset(skb))
		return RX_HANDLER_PASS;

	if (skb_headroom(skb) < sizeof(*ethhdr))
		return RX_HANDLER_PASS;

	if (iwl_mei_rx_filter_eth(ethhdr, filters,
				  pass_to_csme, &rx_handler_res))
		return rx_handler_res;

	switch (skb->protocol) {
	case htons(ETH_P_IP):
		filt_handler = iwl_mei_rx_filter_ipv4;
		break;
	case htons(ETH_P_ARP):
		filt_handler = iwl_mei_rx_filter_arp;
		break;
	case htons(ETH_P_IPV6):
		filt_handler = iwl_mei_rx_filter_ipv6;
		break;
	default:
		*pass_to_csme = false;
		return rx_handler_res;
	}

	*pass_to_csme = filt_handler(skb, filters, &rx_handler_res);

	return rx_handler_res;
}

rx_handler_result_t iwl_mei_rx_filter(struct sk_buff *orig_skb,
				      const struct iwl_sap_oob_filters *filters,
				      bool *pass_to_csme)
{
	rx_handler_result_t ret;
	struct sk_buff *skb;

	ret = iwl_mei_rx_pass_to_csme(orig_skb, filters, pass_to_csme);

	if (!*pass_to_csme)
		return RX_HANDLER_PASS;

	if (ret == RX_HANDLER_PASS) {
		skb = skb_copy(orig_skb, GFP_ATOMIC);

		if (!skb)
			return RX_HANDLER_PASS;
	} else {
		skb = orig_skb;
	}

	/* CSME wants the MAC header as well, push it back */
	skb_push(skb, skb->data - skb_mac_header(skb));

	/*
	 * Add the packet that CSME wants to get to the ring. Don't send the
	 * Check Shared Area HECI message since this is not possible from the
	 * Rx context. The caller will schedule a worker to do just that.
	 */
	iwl_mei_add_data_to_ring(skb, false);

	/*
	 * In case we drop the packet, don't free it, the caller will do that
	 * for us
	 */
	if (ret == RX_HANDLER_PASS)
		dev_kfree_skb(skb);

	return ret;
}

#define DHCP_SERVER_PORT 67
#define DHCP_CLIENT_PORT 68
void iwl_mei_tx_copy_to_csme(struct sk_buff *origskb, unsigned int ivlen)
{
	struct ieee80211_hdr *hdr;
	struct sk_buff *skb;
	struct ethhdr ethhdr;
	struct ethhdr *eth;

	/* Catch DHCP packets */
	if (origskb->protocol != htons(ETH_P_IP) ||
	    ip_hdr(origskb)->protocol != IPPROTO_UDP ||
	    udp_hdr(origskb)->source != htons(DHCP_CLIENT_PORT) ||
	    udp_hdr(origskb)->dest != htons(DHCP_SERVER_PORT))
		return;

	/*
	 * We could be a bit less aggressive here and not copy everything, but
	 * this is very rare anyway, do don't bother much.
	 */
	skb = skb_copy(origskb, GFP_ATOMIC);
	if (!skb)
		return;

	skb->protocol = origskb->protocol;

	hdr = (void *)skb->data;

	memcpy(ethhdr.h_dest, ieee80211_get_DA(hdr), ETH_ALEN);
	memcpy(ethhdr.h_source, ieee80211_get_SA(hdr), ETH_ALEN);

	/*
	 * Remove the ieee80211 header + IV + SNAP but leave the ethertype
	 * We still have enough headroom for the sap header.
	 */
	pskb_pull(skb, ieee80211_hdrlen(hdr->frame_control) + ivlen + 6);
	eth = skb_push(skb, sizeof(ethhdr.h_dest) + sizeof(ethhdr.h_source));
	memcpy(eth, &ethhdr, sizeof(ethhdr.h_dest) + sizeof(ethhdr.h_source));

	iwl_mei_add_data_to_ring(skb, true);

	dev_kfree_skb(skb);
}
EXPORT_SYMBOL_GPL(iwl_mei_tx_copy_to_csme);