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
97a6d1bb2b ("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: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2015-04-08 12:27:26 -04:00
Родитель 8ae178fb1c e88f7e078e
Коммит 803afb316e
1 изменённых файлов: 26 добавлений и 13 удалений

Просмотреть файл

@ -370,35 +370,49 @@ not_ip:
static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net)
{ {
struct net_device_context *net_device_ctx = netdev_priv(net); struct net_device_context *net_device_ctx = netdev_priv(net);
struct hv_netvsc_packet *packet; struct hv_netvsc_packet *packet = NULL;
int ret; int ret;
unsigned int num_data_pgs; unsigned int num_data_pgs;
struct rndis_message *rndis_msg; struct rndis_message *rndis_msg;
struct rndis_packet *rndis_pkt; struct rndis_packet *rndis_pkt;
u32 rndis_msg_size; u32 rndis_msg_size;
bool isvlan; bool isvlan;
bool linear = false;
struct rndis_per_packet_info *ppi; struct rndis_per_packet_info *ppi;
struct ndis_tcp_ip_checksum_info *csum_info; struct ndis_tcp_ip_checksum_info *csum_info;
struct ndis_tcp_lso_info *lso_info; struct ndis_tcp_lso_info *lso_info;
int hdr_offset; int hdr_offset;
u32 net_trans_info; u32 net_trans_info;
u32 hash; u32 hash;
u32 skb_length = skb->len; u32 skb_length;
u32 head_room = skb_headroom(skb); u32 head_room;
u32 pkt_sz; u32 pkt_sz;
struct hv_page_buffer page_buf[MAX_PAGE_BUFFER_COUNT]; struct hv_page_buffer page_buf[MAX_PAGE_BUFFER_COUNT];
/* We will atmost need two pages to describe the rndis /* We will atmost need two pages to describe the rndis
* header. We can only transmit MAX_PAGE_BUFFER_COUNT number * 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; num_data_pgs = netvsc_get_slots(skb) + 2;
if (num_data_pgs > MAX_PAGE_BUFFER_COUNT) { if (num_data_pgs > MAX_PAGE_BUFFER_COUNT && linear) {
netdev_err(net, "Packet too big: %u\n", skb->len); net_alert_ratelimited("packet too big: %u pages (%u bytes)\n",
dev_kfree_skb(skb); num_data_pgs, skb->len);
net->stats.tx_dropped++; ret = -EFAULT;
return NETDEV_TX_OK; 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; 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) { if (!packet) {
/* out of memory, drop packet */ /* out of memory, drop packet */
netdev_err(net, "unable to alloc hv_netvsc_packet\n"); netdev_err(net, "unable to alloc hv_netvsc_packet\n");
dev_kfree_skb(skb); ret = -ENOMEM;
net->stats.tx_dropped++; goto drop;
return NETDEV_TX_OK;
} }
packet->part_of_skb = false; packet->part_of_skb = false;
} else { } else {
@ -574,7 +587,7 @@ drop:
net->stats.tx_bytes += skb_length; net->stats.tx_bytes += skb_length;
net->stats.tx_packets++; net->stats.tx_packets++;
} else { } else {
if (!packet->part_of_skb) if (packet && !packet->part_of_skb)
kfree(packet); kfree(packet);
if (ret != -EAGAIN) { if (ret != -EAGAIN) {
dev_kfree_skb_any(skb); dev_kfree_skb_any(skb);