mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 16:29:59 +00:00
virtio_net: xdp, add slowpath case for non contiguous buffers
virtio_net XDP support expects receive buffers to be contiguous. If this is not the case we enable a slowpath to allow connectivity to continue but at a significan performance overhead associated with linearizing data. To make it painfully aware to users that XDP is running in a degraded mode we throw an xdp buffer error. To linearize packets we allocate a page and copy the segments of the data, including the header, into it. After this the page can be handled by XDP code flow as normal. Then depending on the return code the page is either freed or sent to the XDP xmit path. There is no attempt to optimize this path. This case is being handled simple as a precaution in case some unknown backend were to generate packets in this form. To test this I had to hack qemu and force it to generate these packets. I do not expect this case to be generated by "real" backends. Signed-off-by: John Fastabend <john.r.fastabend@intel.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
56434a01b1
commit
72979a6c35
1 changed files with 74 additions and 1 deletions
|
@ -471,6 +471,64 @@ static struct sk_buff *receive_big(struct net_device *dev,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The conditions to enable XDP should preclude the underlying device from
|
||||||
|
* sending packets across multiple buffers (num_buf > 1). However per spec
|
||||||
|
* it does not appear to be illegal to do so but rather just against convention.
|
||||||
|
* So in order to avoid making a system unresponsive the packets are pushed
|
||||||
|
* into a page and the XDP program is run. This will be extremely slow and we
|
||||||
|
* push a warning to the user to fix this as soon as possible. Fixing this may
|
||||||
|
* require resolving the underlying hardware to determine why multiple buffers
|
||||||
|
* are being received or simply loading the XDP program in the ingress stack
|
||||||
|
* after the skb is built because there is no advantage to running it here
|
||||||
|
* anymore.
|
||||||
|
*/
|
||||||
|
static struct page *xdp_linearize_page(struct receive_queue *rq,
|
||||||
|
u16 num_buf,
|
||||||
|
struct page *p,
|
||||||
|
int offset,
|
||||||
|
unsigned int *len)
|
||||||
|
{
|
||||||
|
struct page *page = alloc_page(GFP_ATOMIC);
|
||||||
|
unsigned int page_off = 0;
|
||||||
|
|
||||||
|
if (!page)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
memcpy(page_address(page) + page_off, page_address(p) + offset, *len);
|
||||||
|
page_off += *len;
|
||||||
|
|
||||||
|
while (--num_buf) {
|
||||||
|
unsigned int buflen;
|
||||||
|
unsigned long ctx;
|
||||||
|
void *buf;
|
||||||
|
int off;
|
||||||
|
|
||||||
|
ctx = (unsigned long)virtqueue_get_buf(rq->vq, &buflen);
|
||||||
|
if (unlikely(!ctx))
|
||||||
|
goto err_buf;
|
||||||
|
|
||||||
|
/* guard against a misconfigured or uncooperative backend that
|
||||||
|
* is sending packet larger than the MTU.
|
||||||
|
*/
|
||||||
|
if ((page_off + buflen) > PAGE_SIZE)
|
||||||
|
goto err_buf;
|
||||||
|
|
||||||
|
buf = mergeable_ctx_to_buf_address(ctx);
|
||||||
|
p = virt_to_head_page(buf);
|
||||||
|
off = buf - page_address(p);
|
||||||
|
|
||||||
|
memcpy(page_address(page) + page_off,
|
||||||
|
page_address(p) + off, buflen);
|
||||||
|
page_off += buflen;
|
||||||
|
}
|
||||||
|
|
||||||
|
*len = page_off;
|
||||||
|
return page;
|
||||||
|
err_buf:
|
||||||
|
__free_pages(page, 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static struct sk_buff *receive_mergeable(struct net_device *dev,
|
static struct sk_buff *receive_mergeable(struct net_device *dev,
|
||||||
struct virtnet_info *vi,
|
struct virtnet_info *vi,
|
||||||
struct receive_queue *rq,
|
struct receive_queue *rq,
|
||||||
|
@ -491,6 +549,7 @@ static struct sk_buff *receive_mergeable(struct net_device *dev,
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
xdp_prog = rcu_dereference(rq->xdp_prog);
|
xdp_prog = rcu_dereference(rq->xdp_prog);
|
||||||
if (xdp_prog) {
|
if (xdp_prog) {
|
||||||
|
struct page *xdp_page;
|
||||||
u32 act;
|
u32 act;
|
||||||
|
|
||||||
/* No known backend devices should send packets with
|
/* No known backend devices should send packets with
|
||||||
|
@ -500,7 +559,15 @@ static struct sk_buff *receive_mergeable(struct net_device *dev,
|
||||||
*/
|
*/
|
||||||
if (unlikely(num_buf > 1)) {
|
if (unlikely(num_buf > 1)) {
|
||||||
bpf_warn_invalid_xdp_buffer();
|
bpf_warn_invalid_xdp_buffer();
|
||||||
|
|
||||||
|
/* linearize data for XDP */
|
||||||
|
xdp_page = xdp_linearize_page(rq, num_buf,
|
||||||
|
page, offset, &len);
|
||||||
|
if (!xdp_page)
|
||||||
goto err_xdp;
|
goto err_xdp;
|
||||||
|
offset = 0;
|
||||||
|
} else {
|
||||||
|
xdp_page = page;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Transient failure which in theory could occur if
|
/* Transient failure which in theory could occur if
|
||||||
|
@ -514,12 +581,18 @@ static struct sk_buff *receive_mergeable(struct net_device *dev,
|
||||||
act = do_xdp_prog(vi, rq, xdp_prog, page, offset, len);
|
act = do_xdp_prog(vi, rq, xdp_prog, page, offset, len);
|
||||||
switch (act) {
|
switch (act) {
|
||||||
case XDP_PASS:
|
case XDP_PASS:
|
||||||
|
if (unlikely(xdp_page != page))
|
||||||
|
__free_pages(xdp_page, 0);
|
||||||
break;
|
break;
|
||||||
case XDP_TX:
|
case XDP_TX:
|
||||||
|
if (unlikely(xdp_page != page))
|
||||||
|
goto err_xdp;
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
goto xdp_xmit;
|
goto xdp_xmit;
|
||||||
case XDP_DROP:
|
case XDP_DROP:
|
||||||
default:
|
default:
|
||||||
|
if (unlikely(xdp_page != page))
|
||||||
|
__free_pages(xdp_page, 0);
|
||||||
goto err_xdp;
|
goto err_xdp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue