Skip to content

Commit d791ebc

Browse files
committed
Improve handling of retransmissions
It looks like the previous change was somewhat incomplete: - I made a small boolean mistake in OPEN_CONFIRM, using && instead of ||. This causes retransmissions to misbehave. - I overlooked the fact that OPEN_DOWNGRADE can also perform stricter checks on the incoming request. While there, add a utility function for doing these kinds of comparisons. This makes the code a bit more readable.
1 parent b1a8462 commit d791ebc

File tree

2 files changed

+141
-31
lines changed

2 files changed

+141
-31
lines changed

pkg/filesystem/virtual/nfsv4/base_program.go

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -949,13 +949,7 @@ func (s *compoundState) opClose(args *nfsv4.Close4args) nfsv4.Close4res {
949949
transaction, lastResponse, st := oos.startTransaction(p, args.Seqid, &ll, unconfirmedOpenOwnerPolicyDeny)
950950
if st != nfsv4.NFS4_OK {
951951
if r, ok := lastResponse.(nfsv4.Close4res); ok {
952-
// Only return a cached response if the request
953-
// contains the same state ID as provided
954-
// originally.
955-
//
956-
// More details: RFC 7530, section 9.1.9, bullet
957-
// point 3.
958-
if okResponse, ok := lastResponse.(*nfsv4.Close4res_NFS4_OK); !ok || (okResponse.OpenStateid.Other == args.OpenStateid.Other && okResponse.OpenStateid.Seqid == nextSeqID(args.OpenStateid.Seqid)) {
952+
if okResponse, ok := lastResponse.(*nfsv4.Close4res_NFS4_OK); !ok || isNextStateID(&okResponse.OpenStateid, &args.OpenStateid) {
959953
return r
960954
}
961955
}
@@ -1168,13 +1162,7 @@ func (s *compoundState) opLock(args *nfsv4.Lock4args) nfsv4.Lock4res {
11681162
transaction, lastResponse, st := los.startTransaction(p, owner.LockSeqid, false)
11691163
if st != nfsv4.NFS4_OK {
11701164
if r, ok := lastResponse.(nfsv4.Lock4res); ok {
1171-
// Only return a cached response if the
1172-
// request contains the same state ID as
1173-
// provided originally.
1174-
//
1175-
// More details: RFC 7530, section
1176-
// 9.1.9, bullet point 3.
1177-
if okResponse, ok := lastResponse.(*nfsv4.Lock4res_NFS4_OK); !ok || (okResponse.Resok4.LockStateid.Other == owner.LockStateid.Other && okResponse.Resok4.LockStateid.Seqid != nextSeqID(owner.LockStateid.Seqid)) {
1165+
if okResponse, ok := lastResponse.(*nfsv4.Lock4res_NFS4_OK); !ok || isNextStateID(&okResponse.Resok4.LockStateid, &owner.LockStateid) {
11781166
return r
11791167
}
11801168
}
@@ -1385,13 +1373,7 @@ func (s *compoundState) opLocku(args *nfsv4.Locku4args) nfsv4.Locku4res {
13851373
transaction, lastResponse, st := los.startTransaction(p, args.Seqid, false)
13861374
if st != nfsv4.NFS4_OK {
13871375
if r, ok := lastResponse.(nfsv4.Locku4res); ok {
1388-
// Only return a cached response if the request
1389-
// contains the same state ID as provided
1390-
// originally.
1391-
//
1392-
// More details: RFC 7530, section 9.1.9, bullet
1393-
// point 3.
1394-
if okResponse, ok := lastResponse.(*nfsv4.Locku4res_NFS4_OK); !ok || (okResponse.LockStateid.Other == args.LockStateid.Other && okResponse.LockStateid.Seqid != nextSeqID(args.LockStateid.Seqid)) {
1376+
if okResponse, ok := lastResponse.(*nfsv4.Locku4res_NFS4_OK); !ok || isNextStateID(&okResponse.LockStateid, &args.LockStateid) {
13951377
return r
13961378
}
13971379
}
@@ -1725,13 +1707,7 @@ func (s *compoundState) opOpenConfirm(args *nfsv4.OpenConfirm4args) nfsv4.OpenCo
17251707
transaction, lastResponse, st := oos.startTransaction(p, args.Seqid, &ll, unconfirmedOpenOwnerPolicyAllow)
17261708
if st != nfsv4.NFS4_OK {
17271709
if r, ok := lastResponse.(nfsv4.OpenConfirm4res); ok {
1728-
// Only return a cached response if the request
1729-
// contains the same state ID as provided
1730-
// originally.
1731-
//
1732-
// More details: RFC 7530, section 9.1.9, bullet
1733-
// point 3.
1734-
if okResponse, ok := lastResponse.(*nfsv4.OpenConfirm4res_NFS4_OK); !ok && (okResponse.Resok4.OpenStateid.Other == args.OpenStateid.Other && okResponse.Resok4.OpenStateid.Seqid == nextSeqID(args.OpenStateid.Seqid)) {
1710+
if okResponse, ok := lastResponse.(*nfsv4.OpenConfirm4res_NFS4_OK); !ok || isNextStateID(&okResponse.Resok4.OpenStateid, &args.OpenStateid) {
17351711
return r
17361712
}
17371713
}
@@ -1780,7 +1756,9 @@ func (s *compoundState) opOpenDowngrade(args *nfsv4.OpenDowngrade4args) nfsv4.Op
17801756
transaction, lastResponse, st := oos.startTransaction(p, args.Seqid, &ll, unconfirmedOpenOwnerPolicyDeny)
17811757
if st != nfsv4.NFS4_OK {
17821758
if r, ok := lastResponse.(nfsv4.OpenDowngrade4res); ok {
1783-
return r
1759+
if okResponse, ok := lastResponse.(*nfsv4.OpenDowngrade4res_NFS4_OK); !ok || isNextStateID(&okResponse.Resok4.OpenStateid, &args.OpenStateid) {
1760+
return r
1761+
}
17841762
}
17851763
return &nfsv4.OpenDowngrade4res_default{Status: st}
17861764
}
@@ -1893,8 +1871,11 @@ func (s *compoundState) opReaddir(ctx context.Context, args *nfsv4.Readdir4args)
18931871
}
18941872

18951873
// Validate the cookie verifier.
1874+
// TODO: The macOS NFSv4 client may sometimes not set the cookie
1875+
// verifier properly, so we allow it to be zero. Remove this
1876+
// logic in due time. Issue: rdar://91034875
18961877
p := s.program
1897-
if args.Cookie != 0 && args.Cookieverf != p.rebootVerifier {
1878+
if args.Cookie != 0 && args.Cookieverf != p.rebootVerifier && (args.Cookieverf != nfsv4.Verifier4{}) {
18981879
return &nfsv4.Readdir4res_default{Status: nfsv4.NFS4ERR_NOT_SAME}
18991880
}
19001881

@@ -3036,6 +3017,20 @@ func nextSeqID(seqID nfsv4.Seqid4) nfsv4.Seqid4 {
30363017
return seqID + 1
30373018
}
30383019

3020+
// isNextStateID returns true if state ID a is the successor of state ID b.
3021+
//
3022+
// At a minimum, the standard states that when returning a cached
3023+
// response to operations such as CLOSE, LOCK, LOCKU, OPEN_CONFIRM, and
3024+
// OPEN_DOWNGRADE, it is sufficient to compare the original operation
3025+
// type and the operation's sequence ID. This function can be used to
3026+
// increase strictness by ensuring that the state IDs in the request
3027+
// also match the originally provided values.
3028+
//
3029+
// More details: RFC 7530, section 9.1.9, bullet point 3.
3030+
func isNextStateID(a, b *nfsv4.Stateid4) bool {
3031+
return a.Other == b.Other && a.Seqid == nextSeqID(b.Seqid)
3032+
}
3033+
30393034
// toShareMask converts NFSv4 share_access values that are part of OPEN
30403035
// and OPEN_DOWNGRADE requests to our equivalent ShareMask values.
30413036
func shareAccessToShareMask(in uint32) (virtual.ShareMask, nfsv4.Nfsstat4) {

pkg/filesystem/virtual/nfsv4/base_program_test.go

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2764,7 +2764,122 @@ func TestBaseProgramCompound_OP_OPENATTR(t *testing.T) {
27642764
})
27652765
}
27662766

2767-
// TODO: OPEN_CONFIRM
2767+
func TestBaseProgramCompound_OP_OPEN_CONFIRM(t *testing.T) {
2768+
ctrl, ctx := gomock.WithContext(context.Background(), t)
2769+
2770+
rootDirectory := mock.NewMockVirtualDirectory(ctrl)
2771+
rootDirectory.EXPECT().VirtualGetAttributes(gomock.Any(), virtual.AttributesMaskFileHandle, gomock.Any()).
2772+
Do(func(ctx context.Context, requested virtual.AttributesMask, attributes *virtual.Attributes) {
2773+
attributes.SetFileHandle([]byte{0x2e, 0x8d, 0x48, 0x03, 0xc4, 0xc3, 0x2d, 0x6c})
2774+
})
2775+
handleResolver := mock.NewMockHandleResolver(ctrl)
2776+
randomNumberGenerator := mock.NewMockSingleThreadedGenerator(ctrl)
2777+
rebootVerifier := nfsv4_xdr.Verifier4{0x42, 0xa8, 0x3f, 0xd1, 0xde, 0x65, 0x74, 0x2a}
2778+
stateIDOtherPrefix := [...]byte{0xfa, 0xc3, 0xf7, 0x18}
2779+
clock := mock.NewMockClock(ctrl)
2780+
program := nfsv4.NewBaseProgram(rootDirectory, handleResolver.Call, randomNumberGenerator, rebootVerifier, stateIDOtherPrefix, clock, 2*time.Minute, time.Minute)
2781+
2782+
clock.EXPECT().Now().Return(time.Unix(1000, 0))
2783+
clock.EXPECT().Now().Return(time.Unix(1001, 0))
2784+
setClientIDForTesting(ctx, t, randomNumberGenerator, program, 0x2e5550c498b2b463)
2785+
2786+
t.Run("RetransmissionSuccess", func(t *testing.T) {
2787+
// It should be valid to send OPEN_CONFIRM repeatedly in
2788+
// case of connection drops.
2789+
leaf := mock.NewMockVirtualLeaf(ctrl)
2790+
clock.EXPECT().Now().Return(time.Unix(1002, 0))
2791+
clock.EXPECT().Now().Return(time.Unix(1003, 0))
2792+
openUnconfirmedFileForTesting(
2793+
ctx,
2794+
t,
2795+
randomNumberGenerator,
2796+
program,
2797+
rootDirectory,
2798+
leaf,
2799+
nfsv4_xdr.NfsFh4{0xff, 0x27, 0xc7, 0x8f, 0xd5, 0x6a, 0xfb, 0xee},
2800+
/* shortClientID = */ 0x2e5550c498b2b463,
2801+
/* seqID = */ 1205,
2802+
/* stateIDOther = */ [...]byte{
2803+
0xfa, 0xc3, 0xf7, 0x18,
2804+
0x80, 0x57, 0x5b, 0x95,
2805+
0x08, 0x16, 0x41, 0x0a,
2806+
})
2807+
2808+
for i := int64(0); i < 10; i++ {
2809+
clock.EXPECT().Now().Return(time.Unix(1004+i*2, 0))
2810+
clock.EXPECT().Now().Return(time.Unix(1005+i*2, 0))
2811+
openConfirmForTesting(
2812+
ctx,
2813+
t,
2814+
randomNumberGenerator,
2815+
program,
2816+
nfsv4_xdr.NfsFh4{0xff, 0x27, 0xc7, 0x8f, 0xd5, 0x6a, 0xfb, 0xee},
2817+
/* seqID = */ 1206,
2818+
/* stateIDOther = */ [...]byte{
2819+
0xfa, 0xc3, 0xf7, 0x18,
2820+
0x80, 0x57, 0x5b, 0x95,
2821+
0x08, 0x16, 0x41, 0x0a,
2822+
})
2823+
}
2824+
})
2825+
2826+
t.Run("RetransmissionWithMismatchingStateID", func(t *testing.T) {
2827+
// At a minimum, the standard states that when returning
2828+
// a cached response it is sufficient to compare the
2829+
// original operation type and sequence ID. Let's be a
2830+
// bit more strict and actually check whether the
2831+
// provided state ID matches the one that was provided
2832+
// as part of the original request.
2833+
//
2834+
// More details: RFC 7530, section 9.1.9, bullet point 3.
2835+
clock.EXPECT().Now().Return(time.Unix(1024, 0))
2836+
clock.EXPECT().Now().Return(time.Unix(1025, 0))
2837+
2838+
res, err := program.NfsV4Nfsproc4Compound(ctx, &nfsv4_xdr.Compound4args{
2839+
Tag: "close",
2840+
Argarray: []nfsv4_xdr.NfsArgop4{
2841+
&nfsv4_xdr.NfsArgop4_OP_PUTFH{
2842+
Opputfh: nfsv4_xdr.Putfh4args{
2843+
Object: nfsv4_xdr.NfsFh4{0xff, 0x27, 0xc7, 0x8f, 0xd5, 0x6a, 0xfb, 0xee},
2844+
},
2845+
},
2846+
&nfsv4_xdr.NfsArgop4_OP_OPEN_CONFIRM{
2847+
OpopenConfirm: nfsv4_xdr.OpenConfirm4args{
2848+
Seqid: 1206,
2849+
OpenStateid: nfsv4_xdr.Stateid4{
2850+
Seqid: 3,
2851+
Other: [...]byte{
2852+
0xfa, 0xc3, 0xf7, 0x18,
2853+
0x80, 0x57, 0x5b, 0x95,
2854+
0x08, 0x16, 0x41, 0x0a,
2855+
},
2856+
},
2857+
},
2858+
},
2859+
},
2860+
})
2861+
require.NoError(t, err)
2862+
require.Equal(t, &nfsv4_xdr.Compound4res{
2863+
Tag: "close",
2864+
Resarray: []nfsv4_xdr.NfsResop4{
2865+
&nfsv4_xdr.NfsResop4_OP_PUTFH{
2866+
Opputfh: nfsv4_xdr.Putfh4res{
2867+
Status: nfsv4_xdr.NFS4_OK,
2868+
},
2869+
},
2870+
&nfsv4_xdr.NfsResop4_OP_OPEN_CONFIRM{
2871+
OpopenConfirm: &nfsv4_xdr.OpenConfirm4res_default{
2872+
Status: nfsv4_xdr.NFS4ERR_BAD_SEQID,
2873+
},
2874+
},
2875+
},
2876+
Status: nfsv4_xdr.NFS4ERR_BAD_SEQID,
2877+
}, res)
2878+
})
2879+
2880+
// TODO: Any more cases we want to test?
2881+
}
2882+
27682883
// TODO: OPEN_DOWNGRADE
27692884
// TODO: PUTFH
27702885
// TODO: PUTPUBFH

0 commit comments

Comments
 (0)