net: skb_copy_datagram_const_iovec()
Subsystems (4)
A message that is not in the archive (sent off-list or not yet received): [email protected]
PATCH 1/3 net: skb_copy_datagram_const_iovec()
There's an skb_copy_datagram_iovec() to copy out of a paged skb, but it modifies the iovec, and does not support starting at an offset in the destination. We want both in tun.c, so let's add the function.
It's a carbon copy of skb_copy_datagram_iovec() with enough changes to be annoying.
--- include/linux/skbuff.h | 5 +++ include/linux/socket.h | 2 + net/core/datagram.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ net/core/iovec.c | 26 +++++++++++++ 4 files changed, 125 insertions(+), 0 deletions(-)
include/linux/skbuff.h +5 -0
@@ -1717,6 +1717,11 @@ extern int skb_copy_datagram_from_iovec(struct sk_buff *skb,int offset, struct iovec *from, int len); +extern int skb_copy_datagram_const_iovec (const struct sk_buff *from, +int offset, +const struct iovec *to, +int to_offset, +int size);extern void skb_free_datagram (struct sock *sk,struct sk_buff *skb);extern int skb_kill_datagram (struct sock *sk,struct sk_buff *skb,unsigned int flags);
include/linux/socket.h +2 -0
@@ -318,6 +318,8 @@ extern int csum_partial_copy_fromiovecend(unsigned char *kdata,extern int verify_iovec (struct msghdr *m,struct iovec *iov,struct sockaddr *address,int mode);extern int memcpy_toiovec (struct iovec *v,unsigned char *kdata,int len); +extern int memcpy_toiovecend (const struct iovec *v,unsigned char *kdata, +int offset,int len);extern int move_addr_to_user (struct sockaddr *kaddr,int klen,void __user *uaddr,int __user *ulen);extern int move_addr_to_kernel (void __user *uaddr,int ulen,struct sockaddr *kaddr);extern int put_cmsg (struct msghdr *,int level,int type,int len,void *data);
net/core/datagram.c +92 -0
98 lines not on this page · raw message
net/core/iovec.c +26 -0
@@ -98,6 +98,31 @@ int memcpy_toiovec(struct iovec *iov, unsigned char *kdata, int len) }/* +* Copy kernel to iovec. Returns -EFAULT on error. +*/ + +int memcpy_toiovecend (const struct iovec *iov,unsigned char *kdata, +int offset,int len) +{ +int copy; +for (; len >0 ; ++iov) { +/* Skip over the finished iovecs */ +if (unlikely (offset >= iov->iov_len)) { + offset -= iov->iov_len; +continue ; + } + copy = min_t(unsigned int, iov->iov_len - offset, len); + offset =0 ; +if (copy_to_user (iov->iov_base, kdata, copy)) +return -EFAULT ; + kdata += copy; + len -= copy; + } + +return 0 ; +} + +/* * Copy iovec to kernel. Returns -EFAULT on error. * * Note: this modifies the original iovec.
@@ -236,3 +261,4 @@ EXPORT_SYMBOL(csum_partial_copy_fromiovecend);EXPORT_SYMBOL (memcpy_fromiovec);EXPORT_SYMBOL (memcpy_fromiovecend);EXPORT_SYMBOL (memcpy_toiovec); +EXPORT_SYMBOL (memcpy_toiovecend);
Signature · 1 line
1.6.0.6
PATCH 2/3 tun: fix tun_chr_aio_read so that aio works
In reply to a message not in the archiveaio_read gets const struct iovec * but tun_chr_aio_read casts this to struct iovec * and modifies the iovec. As a result, attempts to use io_submit to get packets from a tun device fail with weird errors such as EINVAL.
Fix by using the new skb_copy_datagram_const_iovec.
--- drivers/net/tun.c | 11 ++++++----- 1 files changed, 6 insertions(+), 5 deletions(-)
drivers/net/tun.c +6 -5
@@ -679,7 +679,7 @@ static ssize_t tun_chr_aio_write(struct kiocb *iocb, const struct iovec *iv,/* Put packet to the user space buffer */ static __inline__ssize_t tun_put_user (struct tun_struct *tun,struct sk_buff *skb, -struct iovec *iv,int len) +const struct iovec *iv,int len) {struct tun_pi pi = {0 , skb->protocol };ssize_t total =0 ;
@@ -693,7 +693,7 @@ static __inline__ ssize_t tun_put_user(struct tun_struct *tun, pi.flags |=TUN_PKT_STRIP ; } -if (memcpy_toiovec (iv, (void *) &pi,sizeof (pi))) +if (memcpy_toiovecend (iv, (void *) &pi,0 ,sizeof (pi)))return -EFAULT ; total +=sizeof (pi); }
@@ -726,14 +726,15 @@ static __inline__ ssize_t tun_put_user(struct tun_struct *tun, gso.csum_offset = skb->csum_offset; }/* else everything is zero */ -if (unlikely (memcpy_toiovec (iv, (void *)&gso,sizeof (gso)))) +if (unlikely (memcpy_toiovecend (iv, (void *)&gso, total, +sizeof (gso))))return -EFAULT ; total +=sizeof (gso); } len =min_t (int, skb->len, len); -skb_copy_datagram_iovec (skb,0 , iv, len); +skb_copy_datagram_const_iovec (skb,0 , iv, total, len); total += len; tun->dev->stats.tx_packets++;
@@ -788,7 +789,7 @@ static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv, }netif_wake_queue (tun->dev); - ret =tun_put_user (tun, skb, (struct iovec *) iv, len); + ret =tun_put_user (tun, skb, iv, len);kfree_skb (skb);break ; }
Signature · 1 line
1.6.0.6
PATCH 3/3 tun: fix tun_chr_aio_write so that aio works
In reply to a message not in the archiveaio_write gets const struct iovec * but tun_chr_aio_write casts this to struct iovec * and modifies the iovec. As a result, attempts to use io_submit to send packets to a tun device fail with weird errors such as EINVAL.
Since tun is the only user of skb_copy_datagram_from_iovec, we can fix this simply by changing the later so that it does not touch the iovec passed to it.
--- drivers/net/tun.c | 13 ++++++++----- include/linux/skbuff.h | 3 ++- include/linux/socket.h | 4 ++-- net/core/datagram.c | 20 ++++++++++++++------ net/core/iovec.c | 7 ++++--- 5 files changed, 30 insertions(+), 17 deletions(-)
drivers/net/tun.c +8 -5
@@ -536,31 +536,34 @@ static inline struct sk_buff *tun_alloc_skb(struct tun_struct *tun,/* Get packet from user space buffer */ static __inline__ssize_t tun_get_user (struct tun_struct *tun, -struct iovec *iv,size_t count, +const struct iovec *iv,size_t count,int noblock) {struct tun_pi pi = {0 ,cpu_to_be16 (ETH_P_IP ) };struct sk_buff *skb;size_t len = count, align =0 ;struct virtio_net_hdr gso = {0 }; +int offset =0 ;if (!(tun->flags &TUN_NO_PI )) {if ((len -=sizeof (pi)) > count)return -EINVAL ; -if (memcpy_fromiovec ((void *)&pi, iv,sizeof (pi))) +if (memcpy_fromiovecend ((void *)&pi, iv,0 ,sizeof (pi)))return -EFAULT ; + offset +=sizeof (pi); }if (tun->flags &TUN_VNET_HDR ) {if ((len -=sizeof (gso)) > count)return -EINVAL ; -if (memcpy_fromiovec ((void *)&gso, iv,sizeof (gso))) +if (memcpy_fromiovecend ((void *)&gso, iv, offset,sizeof (gso)))return -EFAULT ;if (gso.hdr_len > len)return -EINVAL ; + offset +=sizeof (pi); }if ((tun->flags &TUN_TYPE_MASK ) ==TUN_TAP_DEV ) {
@@ -577,7 +580,7 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun,return PTR_ERR (skb); } -if (skb_copy_datagram_from_iovec (skb,0 , iv, len)) { +if (skb_copy_datagram_from_iovec (skb,0 , iv, offset, len)) { tun->dev->stats.rx_dropped++;kfree_skb (skb);return -EFAULT ;
@@ -669,7 +672,7 @@ static ssize_t tun_chr_aio_write(struct kiocb *iocb, const struct iovec *iv,DBG (KERN_INFO "%s: tun_chr_write %ld\n" , tun->dev->name, count); - result =tun_get_user (tun, (struct iovec *)iv,iov_length (iv, count), + result =tun_get_user (tun, iv,iov_length (iv, count), file->f_flags &O_NONBLOCK );tun_put (tun);
include/linux/skbuff.h +2 -1
@@ -1715,7 +1715,8 @@ extern int skb_copy_and_csum_datagram_iovec(struct sk_buff *skb, struct iovec *iov);extern int skb_copy_datagram_from_iovec (struct sk_buff *skb,int offset, -struct iovec *from, +const struct iovec *from, +int from_offset,int len); extern int skb_copy_datagram_const_iovec(const struct sk_buff *from, int offset,
include/linux/socket.h +2 -2
@@ -309,8 +309,8 @@ struct ucred {#ifdef __KERNEL__extern int memcpy_fromiovec (unsigned char *kdata,struct iovec *iov,int len); -extern int memcpy_fromiovecend (unsigned char *kdata,struct iovec *iov, -int offset,int len); +extern int memcpy_fromiovecend (unsigned char *kdata,const struct iovec *iov, +int offset,int len); extern int csum_partial_copy_fromiovecend(unsigned char *kdata, struct iovec *iov, int offset,
net/core/datagram.c +14 -6
@@ -435,13 +435,15 @@ EXPORT_SYMBOL(skb_copy_datagram_const_iovec);* @skb: buffer to copy * @offset: offset in the buffer to start copying to * @from: io vector to copy to +* @from_offset: offset in the io vector to start copying from * @len: amount of data to copy to buffer from iovec * * Returns 0 or -EFAULT. -* Note: the iovec is modified during the copy. +* Note: the iovec is not modified during the copy. */ int skb_copy_datagram_from_iovec (struct sk_buff *skb,int offset, -struct iovec *from,int len) +const struct iovec *from,int from_offset, +int len) {int start =skb_headlen (skb);int i, copy = start - offset;
@@ -450,11 +452,12 @@ int skb_copy_datagram_from_iovec(struct sk_buff *skb, int offset,if (copy >0 ) {if (copy > len) copy = len; -if (memcpy_fromiovec (skb->data + offset, from, copy)) +if (memcpy_fromiovecend (skb->data + offset, from,0 , copy)) goto fault;if ((len -= copy) ==0 )return 0 ; offset += copy; + from_offset += copy; }/* Copy paged appendix. Hmm... why does this look so complicated? */
@@ -473,8 +476,9 @@ int skb_copy_datagram_from_iovec(struct sk_buff *skb, int offset,if (copy > len) copy = len; vaddr =kmap (page); - err =memcpy_fromiovec (vaddr + frag->page_offset + - offset - start, from, copy); + err =memcpy_fromiovecend (vaddr + frag->page_offset + + offset - start, + from, from_offset, copy);kunmap (page);if (err) goto fault;
@@ -482,6 +486,7 @@ int skb_copy_datagram_from_iovec(struct sk_buff *skb, int offset,if (!(len -= copy))return 0 ; offset += copy; + from_offset += copy; } start = end; }
@@ -500,11 +505,14 @@ int skb_copy_datagram_from_iovec(struct sk_buff *skb, int offset, copy = len;if (skb_copy_datagram_from_iovec (list, offset - start, - from, copy)) + from, + from_offset, + copy)) goto fault;if ((len -= copy) ==0 )return 0 ; offset += copy; + from_offset += copy; } start = end; }
net/core/iovec.c +4 -3
@@ -147,10 +147,11 @@ int memcpy_fromiovec(unsigned char *kdata, struct iovec *iov, int len) }/* -* For use with ip_build_xmit +* Copy iovec from kernel. Returns -EFAULT on error. */ -int memcpy_fromiovecend (unsigned char *kdata,struct iovec *iov,int offset, -int len) + +int memcpy_fromiovecend (unsigned char *kdata,const struct iovec *iov, +int offset,int len) {/* Skip over the finished iovecs */ while (offset >= iov->iov_len) {
Signature · 1 line
1.6.0.6