]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/net/tun.c
tun: Use iovec iterators
[karo-tx-linux.git] / drivers / net / tun.c
index 186ce541c65762f8ee1720aae7f573f145982406..2ff769bf3f3512ea0e4d3d53cce2a4c4abb65cef 100644 (file)
 #include <linux/nsproxy.h>
 #include <linux/virtio_net.h>
 #include <linux/rcupdate.h>
+#include <net/ipv6.h>
 #include <net/net_namespace.h>
 #include <net/netns/generic.h>
 #include <net/rtnetlink.h>
 #include <net/sock.h>
 #include <linux/seq_file.h>
+#include <linux/uio.h>
 
 #include <asm/uaccess.h>
 
@@ -174,7 +176,7 @@ struct tun_struct {
        struct net_device       *dev;
        netdev_features_t       set_features;
 #define TUN_USER_FEATURES (NETIF_F_HW_CSUM|NETIF_F_TSO_ECN|NETIF_F_TSO| \
-                         NETIF_F_TSO6|NETIF_F_UFO)
+                         NETIF_F_TSO6)
 
        int                     vnet_hdr_sz;
        int                     sndbuf;
@@ -1139,6 +1141,8 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
                break;
        }
 
+       skb_reset_network_header(skb);
+
        if (gso.gso_type != VIRTIO_NET_HDR_GSO_NONE) {
                pr_debug("GSO!\n");
                switch (gso.gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
@@ -1149,8 +1153,20 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
                        skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6;
                        break;
                case VIRTIO_NET_HDR_GSO_UDP:
+               {
+                       static bool warned;
+
+                       if (!warned) {
+                               warned = true;
+                               netdev_warn(tun->dev,
+                                           "%s: using disabled UFO feature; please fix this program\n",
+                                           current->comm);
+                       }
                        skb_shinfo(skb)->gso_type = SKB_GSO_UDP;
+                       if (skb->protocol == htons(ETH_P_IPV6))
+                               ipv6_proxy_select_ident(skb);
                        break;
+               }
                default:
                        tun->dev->stats.rx_frame_errors++;
                        kfree_skb(skb);
@@ -1179,7 +1195,6 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
                skb_shinfo(skb)->tx_flags |= SKBTX_SHARED_FRAG;
        }
 
-       skb_reset_network_header(skb);
        skb_probe_transport_header(skb, 0);
 
        rxhash = skb_get_hash(skb);
@@ -1216,29 +1231,39 @@ static ssize_t tun_chr_aio_write(struct kiocb *iocb, const struct iovec *iv,
 static ssize_t tun_put_user(struct tun_struct *tun,
                            struct tun_file *tfile,
                            struct sk_buff *skb,
-                           const struct iovec *iv, int len)
+                           struct iov_iter *iter)
 {
        struct tun_pi pi = { 0, skb->protocol };
-       ssize_t total = 0;
-       int vlan_offset = 0, copied;
+       ssize_t total;
+       int vlan_offset;
+       int vlan_hlen = 0;
+       int vnet_hdr_sz = 0;
+
+       if (vlan_tx_tag_present(skb))
+               vlan_hlen = VLAN_HLEN;
+
+       if (tun->flags & TUN_VNET_HDR)
+               vnet_hdr_sz = tun->vnet_hdr_sz;
+
+       total = skb->len + vlan_hlen + vnet_hdr_sz;
 
        if (!(tun->flags & TUN_NO_PI)) {
-               if ((len -= sizeof(pi)) < 0)
+               if (iov_iter_count(iter) < sizeof(pi))
                        return -EINVAL;
 
-               if (len < skb->len) {
+               total += sizeof(pi);
+               if (iov_iter_count(iter) < total) {
                        /* Packet will be striped */
                        pi.flags |= TUN_PKT_STRIP;
                }
 
-               if (memcpy_toiovecend(iv, (void *) &pi, 0, sizeof(pi)))
+               if (copy_to_iter(&pi, sizeof(pi), iter) != sizeof(pi))
                        return -EFAULT;
-               total += sizeof(pi);
        }
 
-       if (tun->flags & TUN_VNET_HDR) {
+       if (vnet_hdr_sz) {
                struct virtio_net_hdr gso = { 0 }; /* no info leak */
-               if ((len -= tun->vnet_hdr_sz) < 0)
+               if (iov_iter_count(iter) < vnet_hdr_sz)
                        return -EINVAL;
 
                if (skb_is_gso(skb)) {
@@ -1251,8 +1276,6 @@ static ssize_t tun_put_user(struct tun_struct *tun,
                                gso.gso_type = VIRTIO_NET_HDR_GSO_TCPV4;
                        else if (sinfo->gso_type & SKB_GSO_TCPV6)
                                gso.gso_type = VIRTIO_NET_HDR_GSO_TCPV6;
-                       else if (sinfo->gso_type & SKB_GSO_UDP)
-                               gso.gso_type = VIRTIO_NET_HDR_GSO_UDP;
                        else {
                                pr_err("unexpected GSO type: "
                                       "0x%x, gso_size %d, hdr_len %d\n",
@@ -1272,24 +1295,19 @@ static ssize_t tun_put_user(struct tun_struct *tun,
 
                if (skb->ip_summed == CHECKSUM_PARTIAL) {
                        gso.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
-                       gso.csum_start = skb_checksum_start_offset(skb);
+                       gso.csum_start = skb_checksum_start_offset(skb) +
+                                        vlan_hlen;
                        gso.csum_offset = skb->csum_offset;
                } else if (skb->ip_summed == CHECKSUM_UNNECESSARY) {
                        gso.flags = VIRTIO_NET_HDR_F_DATA_VALID;
                } /* else everything is zero */
 
-               if (unlikely(memcpy_toiovecend(iv, (void *)&gso, total,
-                                              sizeof(gso))))
+               if (copy_to_iter(&gso, sizeof(gso), iter) != sizeof(gso))
                        return -EFAULT;
-               total += tun->vnet_hdr_sz;
        }
 
-       copied = total;
-       total += skb->len;
-       if (!vlan_tx_tag_present(skb)) {
-               len = min_t(int, skb->len, len);
-       } else {
-               int copy, ret;
+       if (vlan_hlen) {
+               int ret;
                struct {
                        __be16 h_vlan_proto;
                        __be16 h_vlan_TCI;
@@ -1299,39 +1317,33 @@ static ssize_t tun_put_user(struct tun_struct *tun,
                veth.h_vlan_TCI = htons(vlan_tx_tag_get(skb));
 
                vlan_offset = offsetof(struct vlan_ethhdr, h_vlan_proto);
-               len = min_t(int, skb->len + VLAN_HLEN, len);
-               total += VLAN_HLEN;
-
-               copy = min_t(int, vlan_offset, len);
-               ret = skb_copy_datagram_const_iovec(skb, 0, iv, copied, copy);
-               len -= copy;
-               copied += copy;
-               if (ret || !len)
+
+               ret = skb_copy_datagram_iter(skb, 0, iter, vlan_offset);
+               if (ret || !iov_iter_count(iter))
                        goto done;
 
-               copy = min_t(int, sizeof(veth), len);
-               ret = memcpy_toiovecend(iv, (void *)&veth, copied, copy);
-               len -= copy;
-               copied += copy;
-               if (ret || !len)
+               ret = copy_to_iter(&veth, sizeof(veth), iter);
+               if (ret != sizeof(veth) || !iov_iter_count(iter))
                        goto done;
        }
 
-       skb_copy_datagram_const_iovec(skb, vlan_offset, iv, copied, len);
+       skb_copy_datagram_iter(skb, vlan_offset, iter, skb->len - vlan_offset);
 
 done:
        tun->dev->stats.tx_packets++;
-       tun->dev->stats.tx_bytes += len;
+       tun->dev->stats.tx_bytes += skb->len + vlan_hlen;
 
        return total;
 }
 
 static ssize_t tun_do_read(struct tun_struct *tun, struct tun_file *tfile,
-                          const struct iovec *iv, ssize_t len, int noblock)
+                          const struct iovec *iv, unsigned long segs,
+                          ssize_t len, int noblock)
 {
        struct sk_buff *skb;
        ssize_t ret = 0;
        int peeked, err, off = 0;
+       struct iov_iter iter;
 
        tun_debug(KERN_INFO, tun, "tun_do_read\n");
 
@@ -1344,11 +1356,12 @@ static ssize_t tun_do_read(struct tun_struct *tun, struct tun_file *tfile,
        /* Read frames from queue */
        skb = __skb_recv_datagram(tfile->socket.sk, noblock ? MSG_DONTWAIT : 0,
                                  &peeked, &off, &err);
-       if (skb) {
-               ret = tun_put_user(tun, tfile, skb, iv, len);
-               kfree_skb(skb);
-       } else
-               ret = err;
+       if (!skb)
+               return ret;
+
+       iov_iter_init(&iter, READ, iv, segs, len);
+       ret = tun_put_user(tun, tfile, skb, &iter);
+       kfree_skb(skb);
 
        return ret;
 }
@@ -1369,7 +1382,7 @@ static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv,
                goto out;
        }
 
-       ret = tun_do_read(tun, tfile, iv, len,
+       ret = tun_do_read(tun, tfile, iv, count, len,
                          file->f_flags & O_NONBLOCK);
        ret = min_t(ssize_t, ret, len);
        if (ret > 0)
@@ -1470,7 +1483,7 @@ static int tun_recvmsg(struct kiocb *iocb, struct socket *sock,
                                         SOL_PACKET, TUN_TX_TIMESTAMP);
                goto out;
        }
-       ret = tun_do_read(tun, tfile, m->msg_iov, total_len,
+       ret = tun_do_read(tun, tfile, m->msg_iov, m->msg_iovlen, total_len,
                          flags & MSG_DONTWAIT);
        if (ret > total_len) {
                m->msg_flags |= MSG_TRUNC;
@@ -1762,11 +1775,6 @@ static int set_offload(struct tun_struct *tun, unsigned long arg)
                                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