server support for multiple tcp fragments
Subsystems (3)
I wanted to use pynfs to reproduce a bug with the server's handling of certain large compounds, but ran into trouble because pynfs likes to break up rpc's into 2k fragments. Rather than fixing pynfs I decided it's long past time we should have fixed this bug in the server....
I'm planning to queue this up for 3.8.--b.
PATCH 1/5 svcrpc: don't byte-swap sk_reclen in place
From: "J. Bruce Fields" <[email protected]>
Byte-swapping in place is always a little dubious.
Let's instead define this field to always be big-endian, and do the swapping on demand where we need it.
--- include/linux/sunrpc/svcsock.h | 12 +++++++++++- net/sunrpc/svcsock.c | 26 +++++++++++--------------- 2 files changed, 22 insertions(+), 16 deletions(-)
include/linux/sunrpc/svcsock.h +11 -1
@@ -26,11 +26,21 @@ struct svc_sock {void (*sk_owspace)(struct sock *);/* private TCP part */ -u32 sk_reclen;/* length of record */ +__be32 sk_reclen;/* length of record */ u32 sk_tcplen;/* current read length */ struct page * sk_pages[RPCSVC_MAXPAGES ];/* received data */ }; +static inline u32 svc_sock_reclen (struct svc_sock *svsk) +{ +return ntohl (svsk->sk_reclen) &RPC_FRAGMENT_SIZE_MASK ; +} + +static inline u32 svc_sock_final_rec (struct svc_sock *svsk) +{ +return ntohl (svsk->sk_reclen) &RPC_LAST_STREAM_FRAGMENT ; +} +/* * Function prototypes. */
net/sunrpc/svcsock.c +11 -15
@@ -950,8 +950,7 @@ static int svc_tcp_recv_record(struct svc_sock *svsk, struct svc_rqst *rqstp)return -EAGAIN ; } - svsk->sk_reclen =ntohl (svsk->sk_reclen); -if (!(svsk->sk_reclen &RPC_LAST_STREAM_FRAGMENT )) { +if (!(svc_sock_final_rec (svsk))) {/* FIXME: technically, a record can be fragmented, * and non-terminal fragments will not have the top * bit set in the fragment length header.
@@ -961,21 +960,18 @@ static int svc_tcp_recv_record(struct svc_sock *svsk, struct svc_rqst *rqstp) goto err_delete; } - svsk->sk_reclen &=RPC_FRAGMENT_SIZE_MASK ; -dprintk ("svc: TCP record, %d bytes\n" , svsk->sk_reclen); -if (svsk->sk_reclen > serv->sv_max_mesg) { +dprintk ("svc: TCP record, %d bytes\n" ,svc_sock_reclen (svsk)); +if (svc_sock_reclen (svsk) > serv->sv_max_mesg) {net_notice_ratelimited ("RPC: fragment too large: 0x%08lx\n" , - (unsigned long )svsk->sk_reclen); + (unsigned long )svc_sock_reclen (svsk)); goto err_delete; } } -if (svsk->sk_reclen <8 ) +if (svc_sock_reclen (svsk) <8 ) goto err_delete;/* client is nuts. */ - len = svsk->sk_reclen; - -return len; +return svc_sock_reclen (svsk); error:dprintk ("RPC: TCP recv_record got %d\n" , len);return len;
@@ -1019,7 +1015,7 @@ static int receive_cb_reply(struct svc_sock *svsk, struct svc_rqst *rqstp)if (dst->iov_len < src->iov_len)return -EAGAIN ;/* whatever; just giving up. */ memcpy (dst->iov_base, src->iov_base, src->iov_len); -xprt_complete_rqst (req->rq_task, svsk->sk_reclen); +xprt_complete_rqst (req->rq_task, rqstp->rq_arg.len); rqstp->rq_arg.len =0 ;return 0 ; }
@@ -1064,12 +1060,12 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp) goto error; base =svc_tcp_restore_pages (svsk, rqstp); - want = svsk->sk_reclen - base; + want =svc_sock_reclen (svsk) - base; vec = rqstp->rq_vec; pnum =copy_pages_to_kvecs (&vec[0 ], &rqstp->rq_pages[0 ], - svsk->sk_reclen); +svc_sock_reclen (svsk)); rqstp->rq_respages = &rqstp->rq_pages[pnum];
@@ -1082,11 +1078,11 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)if (len <0 && len != -EAGAIN ) goto err_other;dprintk ("svc: incomplete TCP record (%d of %d)\n" , - svsk->sk_tcplen, svsk->sk_reclen); + svsk->sk_tcplen,svc_sock_reclen (svsk)); goto err_noclose; } - rqstp->rq_arg.len = svsk->sk_reclen; + rqstp->rq_arg.len =svc_sock_reclen (svsk); rqstp->rq_arg.page_base =0 ;if (rqstp->rq_arg.len <= rqstp->rq_arg.head[0 ].iov_len) { rqstp->rq_arg.head[0 ].iov_len = rqstp->rq_arg.len;
Signature · 1 line
1.7.9.5
PATCH 2/5 svcrpc: delay minimum-rpc-size check till later
In reply to J. Bruce FieldsFrom: "J. Bruce Fields" <[email protected]>
Soon we want to support multiple fragments, in which case it may be legal for a single fragment to be smaller than 8 bytes, so we'll want to delay this check till we've reached the last fragment.
Also fix an outdated comment.
--- net/sunrpc/svcsock.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-)
net/sunrpc/svcsock.c +7 -9
@@ -921,10 +921,8 @@ out: }/* -* Receive data. +* Receive fragment record header. * If we haven't gotten the record length yet, get the next four bytes. -* Otherwise try to gobble up as much as possible up to the complete -* record length. */ static int svc_tcp_recv_record (struct svc_sock *svsk,struct svc_rqst *rqstp) {
@@ -968,9 +966,6 @@ static int svc_tcp_recv_record(struct svc_sock *svsk, struct svc_rqst *rqstp) } } -if (svc_sock_reclen (svsk) <8 ) - goto err_delete;/* client is nuts. */ -return svc_sock_reclen (svsk); error:dprintk ("RPC: TCP recv_record got %d\n" , len);
@@ -1076,12 +1071,15 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)if (len != want) {svc_tcp_save_pages (svsk, rqstp);if (len <0 && len != -EAGAIN ) - goto err_other; + goto err_delete;dprintk ("svc: incomplete TCP record (%d of %d)\n" , svsk->sk_tcplen,svc_sock_reclen (svsk)); goto err_noclose; } +if (svc_sock_reclen (svsk) <8 ) + goto err_delete;/* client is nuts. */ + rqstp->rq_arg.len =svc_sock_reclen (svsk); rqstp->rq_arg.page_base =0 ;if (rqstp->rq_arg.len <= rqstp->rq_arg.head[0 ].iov_len) {
@@ -1117,10 +1115,10 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp) error:if (len != -EAGAIN ) - goto err_other; + goto err_delete;dprintk ("RPC: TCP recvfrom got EAGAIN\n" );return 0 ; -err_other: +err_delete:printk (KERN_NOTICE "%s: recvfrom returned errno %d\n" , svsk->sk_xprt.xpt_server->sv_name, -len);set_bit (XPT_CLOSE , &svsk->sk_xprt.xpt_flags);
Signature · 1 line
1.7.9.5
PATCH 3/5 svcrpc: fix off-by-4 error in "incomplete TCP record" dprintk
In reply to J. Bruce FieldsFrom: "J. Bruce Fields" <[email protected]>
The full reclen doesn't include the fragment header, but sk_tcplen does. Fix this to make it an apples-to-apples comparison.
--- net/sunrpc/svcsock.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
net/sunrpc/svcsock.c +2 -1
@@ -1073,7 +1073,8 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)if (len <0 && len != -EAGAIN ) goto err_delete;dprintk ("svc: incomplete TCP record (%d of %d)\n" , - svsk->sk_tcplen,svc_sock_reclen (svsk)); + svsk->sk_tcplen -sizeof (rpc_fraghdr), +svc_sock_reclen (svsk)); goto err_noclose; }
Signature · 1 line
1.7.9.5
PATCH 4/5 svcrpc: track rpc data length separately from sk_tcplen
In reply to J. Bruce FieldsFrom: "J. Bruce Fields" <[email protected]>
Keep a separate field, sk_datalen, that tracks only the data contained in a fragment, not including the fragment header.
For now, this is always just max(0, sk_tcplen - 4), but after we allow multiple fragments sk_datalen will accumulate the total rpc data size while sk_tcplen only tracks progress receiving the current fragment.
--- include/linux/sunrpc/svcsock.h | 11 +++++++++-- net/sunrpc/svcsock.c | 19 ++++++++++++------- 2 files changed, 21 insertions(+), 9 deletions(-)
include/linux/sunrpc/svcsock.h +9 -2
@@ -26,8 +26,15 @@ struct svc_sock {void (*sk_owspace)(struct sock *);/* private TCP part */ -__be32 sk_reclen;/* length of record */ -u32 sk_tcplen;/* current read length */ +/* On-the-wire fragment header: */ +__be32 sk_reclen; +/* As we receive a record, this includes the length received so +* far (including the fragment header): */ +u32 sk_tcplen; +/* Total length of the data (not including fragment headers) +* received so far in the fragments making up this rpc: */ +u32 sk_datalen; +struct page * sk_pages[RPCSVC_MAXPAGES ];/* received data */ };
net/sunrpc/svcsock.c +12 -7
@@ -874,9 +874,9 @@ static unsigned int svc_tcp_restore_pages(struct svc_sock *svsk, struct svc_rqst {unsigned int i, len, npages; -if (svsk->sk_tcplen <=sizeof (rpc_fraghdr)) +if (svsk->sk_datalen ==0 )return 0 ; - len = svsk->sk_tcplen -sizeof (rpc_fraghdr); + len = svsk->sk_datalen; npages = (len +PAGE_SIZE -1 ) >>PAGE_SHIFT ;for (i =0 ; i < npages; i++) {if (rqstp->rq_pages[i] !=NULL )
@@ -893,9 +893,9 @@ static void svc_tcp_save_pages(struct svc_sock *svsk, struct svc_rqst *rqstp) {unsigned int i, len, npages; -if (svsk->sk_tcplen <=sizeof (rpc_fraghdr)) +if (svsk->sk_datalen ==0 )return ; - len = svsk->sk_tcplen -sizeof (rpc_fraghdr); + len = svsk->sk_datalen; npages = (len +PAGE_SIZE -1 ) >>PAGE_SHIFT ;for (i =0 ; i < npages; i++) { svsk->sk_pages[i] = rqstp->rq_pages[i];
@@ -907,9 +907,9 @@ static void svc_tcp_clear_pages(struct svc_sock *svsk) {unsigned int i, len, npages; -if (svsk->sk_tcplen <=sizeof (rpc_fraghdr)) +if (svsk->sk_datalen ==0 ) goto out; - len = svsk->sk_tcplen -sizeof (rpc_fraghdr); + len = svsk->sk_datalen; npages = (len +PAGE_SIZE -1 ) >>PAGE_SHIFT ;for (i =0 ; i < npages; i++) {BUG_ON (svsk->sk_pages[i] ==NULL );
@@ -918,6 +918,7 @@ static void svc_tcp_clear_pages(struct svc_sock *svsk) } out: svsk->sk_tcplen =0 ; + svsk->sk_datalen =0 ; }/*
@@ -1066,8 +1067,10 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)/* Now receive data */ len =svc_partial_recvfrom (rqstp, vec, pnum, want, base); -if (len >=0 ) +if (len >=0 ) { svsk->sk_tcplen += len; + svsk->sk_datalen += len; + }if (len != want) {svc_tcp_save_pages (svsk, rqstp);if (len <0 && len != -EAGAIN )
@@ -1100,6 +1103,7 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)/* Reset TCP read info */ svsk->sk_reclen =0 ; svsk->sk_tcplen =0 ; + svsk->sk_datalen =0 ;/* If we have more data, signal svc_xprt_enqueue() to try again */ if (svc_recv_available (svsk) >sizeof (rpc_fraghdr))set_bit (XPT_DATA , &svsk->sk_xprt.xpt_flags);
@@ -1296,6 +1300,7 @@ static void svc_tcp_init(struct svc_sock *svsk, struct svc_serv *serv) svsk->sk_reclen =0 ; svsk->sk_tcplen =0 ; + svsk->sk_datalen =0 ;memset (&svsk->sk_pages[0 ],0 ,sizeof (svsk->sk_pages));tcp_sk (sk)->nonagle |=TCP_NAGLE_OFF ;
Signature · 1 line
1.7.9.5
PATCH 5/5 svcrpc: support multiple-fragment rpc's
In reply to J. Bruce FieldsFrom: "J. Bruce Fields" <[email protected]>
Over TCP, RPC's are preceded by a single 4-byte field telling you how long the rpc is (in bytes). The spec also allows you to send an RPC in multiple such records (the high bit of the length field is used to tell you whether this is the final record).
We've survived for years without supporting this because in practice the clients we care about don't use it. But the userland rpc libraries do, and every now and then an experimental client will run into this. (Most recently I noticed it while trying to write a pynfs check.) And we're really on the wrong side of the spec here--let's fix this.
--- net/sunrpc/svcsock.c | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-)
net/sunrpc/svcsock.c +25 -25
@@ -949,20 +949,11 @@ static int svc_tcp_recv_record(struct svc_sock *svsk, struct svc_rqst *rqstp)return -EAGAIN ; } -if (!(svc_sock_final_rec (svsk))) { -/* FIXME: technically, a record can be fragmented, -* and non-terminal fragments will not have the top -* bit set in the fragment length header. -* But apparently no known nfs clients send fragmented -* records. */ -net_notice_ratelimited ("RPC: multiple fragments per record not supported\n" ); - goto err_delete; - } -dprintk ("svc: TCP record, %d bytes\n" ,svc_sock_reclen (svsk)); -if (svc_sock_reclen (svsk) > serv->sv_max_mesg) { +if (svc_sock_reclen (svsk) + svsk->sk_datalen > + serv->sv_max_mesg) {net_notice_ratelimited ("RPC: fragment too large: 0x%08lx\n" , - (unsigned long )svc_sock_reclen (svsk)); + (unsigned long )svsk->sk_reclen); goto err_delete; } }
@@ -1030,6 +1021,17 @@ static int copy_pages_to_kvecs(struct kvec *vec, struct page **pages, int len)return i; } +static void svc_tcp_fragment_received (struct svc_sock *svsk) +{ +/* If we have more data, signal svc_xprt_enqueue() to try again */ +if (svc_recv_available (svsk) >sizeof (rpc_fraghdr)) +set_bit (XPT_DATA , &svsk->sk_xprt.xpt_flags); +dprintk ("svc: TCP %s record (%d bytes)\n" , +svc_sock_final_rec (svsk) ?"final" :"nonfinal" , +svc_sock_reclen (svsk)); + svsk->sk_tcplen =0 ; + svsk->sk_reclen =0 ; +}/* * Receive data from a TCP socket.
@@ -1056,12 +1058,12 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp) goto error; base =svc_tcp_restore_pages (svsk, rqstp); - want =svc_sock_reclen (svsk) - base; + want =svc_sock_reclen (svsk) - (svsk->sk_tcplen -sizeof (rpc_fraghdr)); vec = rqstp->rq_vec; pnum =copy_pages_to_kvecs (&vec[0 ], &rqstp->rq_pages[0 ], -svc_sock_reclen (svsk)); + svsk->sk_datalen + want); rqstp->rq_respages = &rqstp->rq_pages[pnum];
@@ -1071,20 +1073,23 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp) svsk->sk_tcplen += len; svsk->sk_datalen += len; } -if (len != want) { +if (len != want || !svc_sock_final_rec (svsk)) {svc_tcp_save_pages (svsk, rqstp);if (len <0 && len != -EAGAIN ) goto err_delete; -dprintk ("svc: incomplete TCP record (%d of %d)\n" , - svsk->sk_tcplen -sizeof (rpc_fraghdr), -svc_sock_reclen (svsk)); +if (len == want) +svc_tcp_fragment_received (svsk); +else +dprintk ("svc: incomplete TCP record (%ld of %d)\n" , + svsk->sk_tcplen -sizeof (rpc_fraghdr), +svc_sock_reclen (svsk)); goto err_noclose; }if (svc_sock_reclen (svsk) <8 ) goto err_delete;/* client is nuts. */ - rqstp->rq_arg.len =svc_sock_reclen (svsk); + rqstp->rq_arg.len = svsk->sk_datalen; rqstp->rq_arg.page_base =0 ;if (rqstp->rq_arg.len <= rqstp->rq_arg.head[0 ].iov_len) { rqstp->rq_arg.head[0 ].iov_len = rqstp->rq_arg.len;
@@ -1101,12 +1106,8 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp) len =receive_cb_reply (svsk, rqstp);/* Reset TCP read info */ - svsk->sk_reclen =0 ; - svsk->sk_tcplen =0 ; svsk->sk_datalen =0 ; -/* If we have more data, signal svc_xprt_enqueue() to try again */ -if (svc_recv_available (svsk) >sizeof (rpc_fraghdr)) -set_bit (XPT_DATA , &svsk->sk_xprt.xpt_flags); +svc_tcp_fragment_received (svsk);if (len <0 ) goto error;
@@ -1115,7 +1116,6 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)if (serv->sv_stats) serv->sv_stats->nettcpcnt++; -dprintk ("svc: TCP complete record (%d bytes)\n" , rqstp->rq_arg.len);return rqstp->rq_arg.len; error:
Signature · 1 line
1.7.9.5
Quoted 6 lines · J. Bruce Fields
I wanted to use pynfs to reproduce a bug with the server's handling of certain large compounds, but ran into trouble because pynfs likes to break up rpc's into 2k fragments. Rather than fixing pynfs I decided it's long past time we should have fixed this bug in the server....
I'm planning to queue this up for 3.8.--b.
To help test I also added a test that does a maximum-size write to:
git://linux-nfs.org/~bfields/pynfs.git master
--b.