tun: TUNSETFEATURES to set gso features.

ethtool is useful for setting (some) device fields, but it's
root-only.  Finer feature control is available through a tun-specific
ioctl.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
---
 drivers/net/tun.c      |   49 +++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/if_tun.h |    8 ++++++++
 2 files changed, 57 insertions(+)

diff -r ff3d5082a041 drivers/net/tun.c
--- a/drivers/net/tun.c	Tue Apr 22 13:21:35 2008 +1000
+++ b/drivers/net/tun.c	Tue Apr 22 13:41:32 2008 +1000
@@ -1035,6 +1035,52 @@ static int tun_set_iff(struct net *net, 
 	return err;
 }
 
+/* This is like a cut-down ethtool ops, except done via tun fd so no
+ * privs required. */
+static int set_features(struct net_device *dev, unsigned long arg)
+{
+	unsigned int old_features, features;
+
+	old_features = dev->features;
+	/* Unset features, set them as we chew on the arg. */
+	features = (old_features & ~(NETIF_F_HW_CSUM|NETIF_F_SG|NETIF_F_FRAGLIST
+				     |NETIF_F_TSO_ECN|NETIF_F_TSO|NETIF_F_TSO6
+				     |NETIF_F_UFO));
+
+	if (arg & TUN_F_CSUM) {
+		features |= NETIF_F_HW_CSUM|NETIF_F_SG|NETIF_F_FRAGLIST;
+		arg &= ~TUN_F_CSUM;
+
+		if (arg & (TUN_F_TSO4|TUN_F_TSO6)) {
+			if (arg & TUN_F_TSO_ECN) {
+				features |= NETIF_F_TSO_ECN;
+				arg &= ~TUN_F_TSO_ECN;
+			}
+			if (arg & TUN_F_TSO4)
+				features |= NETIF_F_TSO;
+			if (arg & TUN_F_TSO6)
+				features |= NETIF_F_TSO6;
+			arg &= ~(TUN_F_TSO4|TUN_F_TSO6);
+		}
+
+		if (arg & TUN_F_UFO) {
+			features |= NETIF_F_UFO;
+			arg &= ~TUN_F_UFO;
+		}
+	}
+
+	/* This gives the user a way to test for new features in future by
+	 * trying to set them. */
+	if (arg)
+		return -EINVAL;
+
+	dev->features = features;
+	if (old_features != dev->features)
+		netdev_features_change(dev);
+
+	return 0;
+}
+
 static int tun_chr_ioctl(struct inode *inode, struct file *file,
 			 unsigned int cmd, unsigned long arg)
 {
@@ -1129,6 +1175,9 @@ static int tun_chr_ioctl(struct inode *i
 
 	case TUNSETXMITVRING:
 		return set_xmit_vring(tun, arg);
+
+	case TUNSETFEATURES:
+		return set_features(tun->dev, arg);
 
 	case SIOCGIFFLAGS:
 		ifr.ifr_flags = tun->if_flags;
diff -r ff3d5082a041 include/linux/if_tun.h
--- a/include/linux/if_tun.h	Tue Apr 22 13:21:35 2008 +1000
+++ b/include/linux/if_tun.h	Tue Apr 22 13:41:32 2008 +1000
@@ -44,12 +44,20 @@
 #define TUNSETGROUP   _IOW('T', 206, int)
 #define TUNSETRECVVRING _IOW('T', 207, int)
 #define TUNSETXMITVRING _IOW('T', 208, int)
+#define TUNSETFEATURES _IOW('T', 209, unsigned int)
 
 /* TUNSETIFF ifr flags */
 #define IFF_TUN		0x0001
 #define IFF_TAP		0x0002
 #define IFF_NO_PI	0x1000
 #define IFF_ONE_QUEUE	0x2000
+
+/* Features for GSO (TUNSETFEATURES). */
+#define TUN_F_CSUM	0x01	/* You can hand me unchecksummed packets. */
+#define TUN_F_TSO4	0x02	/* I can handle TSO for IPv4 packets */
+#define TUN_F_TSO6	0x04	/* I can handle TSO for IPv6 packets */
+#define TUN_F_TSO_ECN	0x08	/* I can handle TSO with ECN bits. */
+#define TUN_F_UFO	0x10	/* I can handle UDP fragment offload. */
 
 struct tun_pi {
 	unsigned short flags;
