---
 net/ipv4/af_inet.c |    2 +
 net/ipv4/udp.c     |   98 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 100 insertions(+)

diff -r eb60dd8c39b4 net/ipv4/af_inet.c
--- a/net/ipv4/af_inet.c	Wed May 07 08:27:22 2008 +1000
+++ b/net/ipv4/af_inet.c	Wed May 07 09:57:13 2008 +1000
@@ -1331,6 +1331,8 @@ static struct net_protocol udp_protocol 
 static struct net_protocol udp_protocol = {
 	.handler =	udp_rcv,
 	.err_handler =	udp_err,
+	.gso_send_check = udp_gso_send_check,
+	.gso_segment =	udp_tso_segment,
 	.no_policy =	1,
 	.netns_ok =	1,
 };
diff -r eb60dd8c39b4 net/ipv4/udp.c
--- a/net/ipv4/udp.c	Wed May 07 08:27:22 2008 +1000
+++ b/net/ipv4/udp.c	Wed May 07 09:57:13 2008 +1000
@@ -470,6 +470,104 @@ static void udp4_hwcsum_outgoing(struct 
 		if (uh->check == 0)
 			uh->check = CSUM_MANGLED_0;
 	}
+}
+
+int udp_gso_send_check(struct sk_buff *skb)
+{
+	const struct iphdr *iph;
+	struct udphdr *uh;
+
+	if (!pskb_may_pull(skb, sizeof(*uh)))
+		return -EINVAL;
+
+	iph = ip_hdr(skb);
+	uh = udp_hdr(skb);
+
+	uh->check = 0;
+	uh->check = ~csum_tcpudp_magic(iph->saddr, iph->daddr, skb->len,
+				       IPPROTO_UDP, 0);
+	skb->csum_start = skb_transport_header(skb) - skb->head;
+	skb->csum_offset = offsetof(struct udphdr, check);
+	skb->ip_summed = CHECKSUM_PARTIAL;
+	return 0;
+}
+
+/* This is actually IP fragmentation, with the UDP checksum fixed up. */
+struct sk_buff *udp_gso_segment(struct sk_buff *skb, int features)
+{
+	struct sk_buff *segs = ERR_PTR(-EINVAL);
+	struct udphdr *uh;
+
+	if (!pskb_may_pull(skb, sizeof(*uh)))
+		goto out;
+
+	BUG_ON(skb->ip_summed != CHECKSUM_PARTIAL);
+
+	/* Checksum entire packet, */
+	uh->check = csum_fold(skb_checksum(skb, 0, skb->len, 0));
+	if (uh->check == 0)
+		uh->check = CSUM_MANGLED_0;
+
+	if (skb_checksum(skb, 0, skb->len, 0) != 0) {
+		printk("Skb len %u check 0x%04x checksum after gso = %u\n",
+		       skb->len, uh->check, skb_checksum(skb, 0, skb->len, 0));
+	}
+
+	if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
+		/* Packet is from an untrusted source, reset gso_segs. */
+		int type = skb_shinfo(skb)->gso_type;
+		int mss;
+
+		if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY) ||
+			     !(type & SKB_GSO_UDP)))
+			goto out;
+
+		mss = skb_shinfo(skb)->gso_size;
+		skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
+
+		segs = NULL;
+		goto out;
+	}
+
+	segs = skb_segment(skb, features);
+	if (IS_ERR(segs))
+		goto out;
+
+	len = skb_shinfo(skb)->gso_size;
+	delta = htonl(oldlen + (sizeof(*udhp) + len));
+
+	skb = segs;
+	uh = udp_hdr(skb);
+	seq = ntohl(th->seq);
+
+	do {
+		th->fin = th->psh = 0;
+
+		th->check = ~csum_fold((__force __wsum)((__force u32)th->check +
+				       (__force u32)delta));
+		if (skb->ip_summed != CHECKSUM_PARTIAL)
+			th->check =
+			     csum_fold(csum_partial(skb_transport_header(skb),
+						    thlen, skb->csum));
+
+		seq += len;
+		skb = skb->next;
+		th = tcp_hdr(skb);
+
+		th->seq = htonl(seq);
+		th->cwr = 0;
+	} while (skb->next);
+
+	delta = htonl(oldlen + (skb->tail - skb->transport_header) +
+		      skb->data_len);
+	th->check = ~csum_fold((__force __wsum)((__force u32)th->check +
+				(__force u32)delta));
+	if (skb->ip_summed != CHECKSUM_PARTIAL)
+		th->check = csum_fold(csum_partial(skb_transport_header(skb),
+						   thlen, skb->csum));
+
+out:
+	return segs;
 }
 
 /*
