queue = &dp->queues[queue_no];
err = -ENOBUFS;
if (skb_queue_len(queue) >= DP_MAX_QUEUE_LEN)
- goto err;
+ goto err_kfree_skb;
/* If a checksum-deferred packet is forwarded to the controller,
* correct the pointers and checksum. This happens on a regular basis
*/
err = skb_checksum_setup(skb);
if (err)
- goto err;
+ goto err_kfree_skb;
#ifndef CHECKSUM_HW
if (skb->ip_summed == CHECKSUM_PARTIAL) {
WARN_ON_ONCE(1);
#endif
err = skb_checksum_help(skb);
if (err)
- goto err;
+ goto err_kfree_skb;
}
#else
if (skb->ip_summed == CHECKSUM_HW) {
err = skb_checksum_help(skb, 0);
if (err)
- goto err;
+ goto err_kfree_skb;
}
#endif
- err = skb_cow(skb, sizeof *header);
- if (err)
- goto err;
-
- header = (struct odp_msg*)__skb_push(skb, sizeof *header);
- header->type = queue_no;
- header->length = skb->len;
- header->port = (skb->dev && skb->dev->br_port
- ? skb->dev->br_port->port_no
- : ODPP_LOCAL);
- header->reserved = 0;
- header->arg = arg;
- skb_queue_tail(queue, skb);
+ /* Break apart GSO packets into their component pieces. Otherwise
+ * userspace may try to stuff a 64kB packet into a 1500-byte MTU. */
+ if (skb_is_gso(skb)) {
+ struct sk_buff *nskb = skb_gso_segment(skb, 0);
+ if (nskb) {
+ kfree_skb(skb);
+ skb = nskb;
+ if (unlikely(IS_ERR(skb))) {
+ err = PTR_ERR(skb);
+ goto err;
+ }
+ } else {
+ /* XXX This case might not be possible. It's hard to
+ * tell from the skb_gso_segment() code and comment. */
+ }
+ }
+
+ /* Append each packet to queue. There will be only one packet unless
+ * we broke up a GSO packet above. */
+ do {
+ struct sk_buff *nskb = skb->next;
+ skb->next = NULL;
+
+ err = skb_cow(skb, sizeof *header);
+ if (err) {
+ while (nskb) {
+ kfree_skb(skb);
+ skb = nskb;
+ nskb = skb->next;
+ }
+ goto err_kfree_skb;
+ }
+
+ header = (struct odp_msg*)__skb_push(skb, sizeof *header);
+ header->type = queue_no;
+ header->length = skb->len;
+ header->port = (skb->dev && skb->dev->br_port
+ ? skb->dev->br_port->port_no
+ : ODPP_LOCAL);
+ header->reserved = 0;
+ header->arg = arg;
+ skb_queue_tail(queue, skb);
+
+ skb = nskb;
+ } while (skb);
+
wake_up_interruptible(&dp->waitqueue);
return 0;
+err_kfree_skb:
+ kfree_skb(skb);
err:
stats = percpu_ptr(dp->stats_percpu, get_cpu());
stats->n_lost++;
put_cpu();
- kfree_skb(skb);
return err;
}