Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 803afb31 authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'hv_netvsc_linearize'



Vitaly Kuznetsov says:

====================
hv_netvsc: linearize SKBs bigger than MAX_PAGE_BUFFER_COUNT-2 pages

This patch series fixes the same issue which was fixed in Xen with commit
97a6d1bb ("xen-netfront: Fix handling packets on
compound pages with skb_linearize").

It is relatively easy to create a packet which is small in size but occupies
more than 30 (MAX_PAGE_BUFFER_COUNT-2) pages. Here is a kernel-mode reproducer
which tries sending a packet with only 34 bytes of payload (but on 34 pages)
and fails:

static int __init sendfb_init(void)
{
	struct socket *sock;
	int i, ret;
	struct sockaddr_in in4_addr = { 0 };
	struct page *pages[17];
	unsigned long flags;

	ret = sock_create_kern(AF_INET, SOCK_STREAM, IPPROTO_TCP, &sock);
	if (ret) {
		pr_err("failed to create socket: %d!\n", ret);
		return ret;
	}

	in4_addr.sin_family = AF_INET;
	/* www.google.com, 74.125.133.99 */
	in4_addr.sin_addr.s_addr = cpu_to_be32(0x4a7d8563);
	in4_addr.sin_port = cpu_to_be16(80);

	ret = sock->ops->connect(sock, (struct sockaddr *)&in4_addr, sizeof(in4_addr), 0);
	if (ret) {
		pr_err("failed to connect: %d!\n", ret);
		return ret;
	}

	/* We can send up to 17 frags */
	flags = MSG_MORE;
	for (i = 0; i < 17; i++) {
		if (i == 16)
			flags = MSG_EOR;
		pages[i] = alloc_pages(GFP_KERNEL | __GFP_COMP, 1);
		if (!pages[i]) {
			pr_err("out of memory!");
			goto free_pages;
		}
		sock->ops->sendpage(sock, pages[i], PAGE_SIZE -1, 2, flags);
	}

free_pages:
	for (; i > 0; i--)
		__free_pages(pages[i - 1], 1);

	printk("sendfb_init: test done\n");
        return -1;
}

module_init(sendfb_init);

MODULE_LICENSE("GPL");

A try to load such module results in multiple
'kernel: hv_netvsc vmbus_15 eth0: Packet too big: 100' messages as all retries
fail as well. It should also be possible to trigger the issue from userspace, I
expect e.g. NFS under heavy load to get stuck sometimes.
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 8ae178fb e88f7e07
Loading
Loading
Loading
Loading
+26 −13
Original line number Diff line number Diff line
@@ -370,35 +370,49 @@ static u32 get_net_transport_info(struct sk_buff *skb, u32 *trans_off)
static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net)
{
	struct net_device_context *net_device_ctx = netdev_priv(net);
	struct hv_netvsc_packet *packet;
	struct hv_netvsc_packet *packet = NULL;
	int ret;
	unsigned int num_data_pgs;
	struct rndis_message *rndis_msg;
	struct rndis_packet *rndis_pkt;
	u32 rndis_msg_size;
	bool isvlan;
	bool linear = false;
	struct rndis_per_packet_info *ppi;
	struct ndis_tcp_ip_checksum_info *csum_info;
	struct ndis_tcp_lso_info *lso_info;
	int  hdr_offset;
	u32 net_trans_info;
	u32 hash;
	u32 skb_length = skb->len;
	u32 head_room = skb_headroom(skb);
	u32 skb_length;
	u32 head_room;
	u32 pkt_sz;
	struct hv_page_buffer page_buf[MAX_PAGE_BUFFER_COUNT];


	/* We will atmost need two pages to describe the rndis
	 * header. We can only transmit MAX_PAGE_BUFFER_COUNT number
	 * of pages in a single packet.
	 * of pages in a single packet. If skb is scattered around
	 * more pages we try linearizing it.
	 */

check_size:
	skb_length = skb->len;
	head_room = skb_headroom(skb);
	num_data_pgs = netvsc_get_slots(skb) + 2;
	if (num_data_pgs > MAX_PAGE_BUFFER_COUNT) {
		netdev_err(net, "Packet too big: %u\n", skb->len);
		dev_kfree_skb(skb);
		net->stats.tx_dropped++;
		return NETDEV_TX_OK;
	if (num_data_pgs > MAX_PAGE_BUFFER_COUNT && linear) {
		net_alert_ratelimited("packet too big: %u pages (%u bytes)\n",
				      num_data_pgs, skb->len);
		ret = -EFAULT;
		goto drop;
	} else if (num_data_pgs > MAX_PAGE_BUFFER_COUNT) {
		if (skb_linearize(skb)) {
			net_alert_ratelimited("failed to linearize skb\n");
			ret = -ENOMEM;
			goto drop;
		}
		linear = true;
		goto check_size;
	}

	pkt_sz = sizeof(struct hv_netvsc_packet) + RNDIS_AND_PPI_SIZE;
@@ -408,9 +422,8 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net)
		if (!packet) {
			/* out of memory, drop packet */
			netdev_err(net, "unable to alloc hv_netvsc_packet\n");
			dev_kfree_skb(skb);
			net->stats.tx_dropped++;
			return NETDEV_TX_OK;
			ret = -ENOMEM;
			goto drop;
		}
		packet->part_of_skb = false;
	} else {
@@ -574,7 +587,7 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net)
		net->stats.tx_bytes += skb_length;
		net->stats.tx_packets++;
	} else {
		if (!packet->part_of_skb)
		if (packet && !packet->part_of_skb)
			kfree(packet);
		if (ret != -EAGAIN) {
			dev_kfree_skb_any(skb);