From: Chuck Lever The DONE netlink op runs with parallel_ops, so duplicate or concurrent DONE downcalls for the same socket can both reach the same handshake_req. The split try/finish pair guarantees that exactly one caller drives completion to the consumer's hp_done callback; this invariant lets the session-tag publish step transfer ownership safely. Exercise the gate's idempotency with a kunit case: submit and accept a request, call handshake_try_complete() twice in sequence and assert the second returns false, drive handshake_finish_complete() once, then call handshake_complete() again and confirm hp_done fired exactly once across the sequence. Signed-off-by: Chuck Lever --- net/handshake/handshake-test.c | 72 ++++++++++++++++++++++++++++++++++++++++++ net/handshake/request.c | 2 ++ 2 files changed, 74 insertions(+) diff --git a/net/handshake/handshake-test.c b/net/handshake/handshake-test.c index 55442b2f518a..c172b7a9750f 100644 --- a/net/handshake/handshake-test.c +++ b/net/handshake/handshake-test.c @@ -430,6 +430,74 @@ static void handshake_req_cancel_test3(struct kunit *test) fput(filp); } +static int handshake_try_complete_done_count; + +static void test_done_count_func(struct handshake_req *req, unsigned int status, + struct genl_info *info) +{ + handshake_try_complete_done_count++; +} + +static struct handshake_proto handshake_req_alloc_proto_count = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_TLSHD, + .hp_accept = test_accept_func, + .hp_done = test_done_count_func, +}; + +static void handshake_try_complete_test1(struct kunit *test) +{ + struct handshake_req *req, *next; + struct handshake_net *hn; + struct socket *sock; + struct file *filp; + struct net *net; + bool first, second; + int err; + + /* Arrange */ + handshake_try_complete_done_count = 0; + + req = handshake_req_alloc(&handshake_req_alloc_proto_count, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, req); + + err = __sock_create(&init_net, PF_INET, SOCK_STREAM, IPPROTO_TCP, + &sock, 1); + KUNIT_ASSERT_EQ(test, err, 0); + + filp = sock_alloc_file(sock, O_NONBLOCK, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filp); + KUNIT_ASSERT_NOT_NULL(test, sock->sk); + sock->file = filp; + + err = handshake_req_submit(sock, req, GFP_KERNEL); + KUNIT_ASSERT_EQ(test, err, 0); + + net = sock_net(sock->sk); + hn = handshake_pernet(net); + KUNIT_ASSERT_NOT_NULL(test, hn); + + /* Pretend to accept this request */ + next = handshake_req_next(hn, HANDSHAKE_HANDLER_CLASS_TLSHD); + KUNIT_ASSERT_PTR_EQ(test, req, next); + + /* Act */ + first = handshake_try_complete(req); + second = handshake_try_complete(req); + handshake_finish_complete(req, 0, NULL); + /* handshake_complete() re-enters the gate via + * handshake_try_complete(). With the gate already taken, + * hp_done must not fire a second time. + */ + handshake_complete(req, 0, NULL); + + /* Assert */ + KUNIT_EXPECT_TRUE(test, first); + KUNIT_EXPECT_FALSE(test, second); + KUNIT_EXPECT_EQ(test, handshake_try_complete_done_count, 1); + + fput(filp); +} + static struct handshake_req *handshake_req_destroy_test; static void test_destroy_func(struct handshake_req *req) @@ -522,6 +590,10 @@ static struct kunit_case handshake_api_test_cases[] = { .name = "req_cancel after done", .run_case = handshake_req_cancel_test3, }, + { + .name = "try_complete gate is exclusive", + .run_case = handshake_try_complete_test1, + }, { .name = "req_destroy works", .run_case = handshake_req_destroy_test1, diff --git a/net/handshake/request.c b/net/handshake/request.c index 2215a9916727..96c27efa9958 100644 --- a/net/handshake/request.c +++ b/net/handshake/request.c @@ -311,6 +311,7 @@ bool handshake_try_complete(struct handshake_req *req) { return !test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags); } +EXPORT_SYMBOL_IF_KUNIT(handshake_try_complete); /** * handshake_finish_complete - Deliver completion to the consumer @@ -341,6 +342,7 @@ void handshake_finish_complete(struct handshake_req *req, unsigned int status, /* Handshake request is no longer pending */ sock_put(sk); } +EXPORT_SYMBOL_IF_KUNIT(handshake_finish_complete); void handshake_complete(struct handshake_req *req, unsigned int status, struct genl_info *info) -- 2.54.0