Re: [PATCH v7 1/2] net/handshake: Create a NETLINK service for handling handshake requests
From: Chuck Lever III <hidden>
Date: 2023-03-21 13:59:37
On Mar 21, 2023, at 7:27 AM, Paolo Abeni [off-list ref] wrote: On Sat, 2023-03-18 at 12:18 -0400, Chuck Lever wrote:quoted
+/** + * handshake_req_alloc - consumer API to allocate a request + * @sock: open socket on which to perform a handshake + * @proto: security protocol + * @flags: memory allocation flags + * + * Returns an initialized handshake_req or NULL. + */ +struct handshake_req *handshake_req_alloc(struct socket *sock, + const struct handshake_proto *proto, + gfp_t flags) +{ + struct sock *sk = sock->sk; + struct net *net = sock_net(sk); + struct handshake_net *hn = handshake_pernet(net); + struct handshake_req *req; + + if (!hn) + return NULL; + + req = kzalloc(struct_size(req, hr_priv, proto->hp_privsize), flags); + if (!req) + return NULL; + + sock_hold(sk);The hr_sk reference counting is unclear to me. It looks like handshake_req retain a reference to such socket, but handshake_req_destroy()/handshake_sk_destruct() do not release it.
If we rely on sk_destruct to release the final reference count, it will never get invoked.
Perhaps is better moving such sock_hold() into handshake_req_submit(), once that the request is successful?
I will do that. Personally, I find it more clear to bump a reference count when saving a copy of the object's pointer, as is done in _alloc. But if others find it easier the other way, I have no problem changing it to suit community preferences.
quoted
+ + INIT_LIST_HEAD(&req->hr_list); + req->hr_sk = sk; + req->hr_proto = proto; + return req; +} +EXPORT_SYMBOL(handshake_req_alloc); + +/** + * handshake_req_private - consumer API to return per-handshake private data + * @req: handshake arguments + * + */ +void *handshake_req_private(struct handshake_req *req) +{ + return (void *)&req->hr_priv; +} +EXPORT_SYMBOL(handshake_req_private); + +static bool __add_pending_locked(struct handshake_net *hn, + struct handshake_req *req) +{ + if (!list_empty(&req->hr_list)) + return false; + hn->hn_pending++; + list_add_tail(&req->hr_list, &hn->hn_requests); + return true; +} + +void __remove_pending_locked(struct handshake_net *hn, + struct handshake_req *req) +{ + hn->hn_pending--; + list_del_init(&req->hr_list); +} + +/* + * Returns %true if the request was found on @net's pending list, + * otherwise %false. + * + * If @req was on a pending list, it has not yet been accepted. + */ +static bool remove_pending(struct handshake_net *hn, struct handshake_req *req) +{ + bool ret; + + ret = false;Nit: merge the initialization and the declarationquoted
+ + spin_lock(&hn->hn_lock); + if (!list_empty(&req->hr_list)) { + __remove_pending_locked(hn, req); + ret = true; + } + spin_unlock(&hn->hn_lock); + + return ret; +} + +/** + * handshake_req_submit - consumer API to submit a handshake request + * @req: handshake arguments + * @flags: memory allocation flags + * + * Return values: + * %0: Request queued + * %-EBUSY: A handshake is already under way for this socket + * %-ESRCH: No handshake agent is available + * %-EAGAIN: Too many pending handshake requests + * %-ENOMEM: Failed to allocate memory + * %-EMSGSIZE: Failed to construct notification message + * %-EOPNOTSUPP: Handshake module not initialized + * + * A zero return value from handshake_request() means that + * exactly one subsequent completion callback is guaranteed. + * + * A negative return value from handshake_request() means that + * no completion callback will be done and that @req has been + * destroyed. + */ +int handshake_req_submit(struct handshake_req *req, gfp_t flags) +{ + struct sock *sk = req->hr_sk; + struct net *net = sock_net(sk); + struct handshake_net *hn = handshake_pernet(net); + int ret;Nit: reverse xmas tree. In this case you have to split declaration and initialization ;)
Interesting. I like reverse-xmas, but I thought that the initialization of these variables would take precedent. I'll clean this up.
quoted
+ + if (!hn) + return -EOPNOTSUPP; + + ret = -EAGAIN; + if (READ_ONCE(hn->hn_pending) >= hn->hn_pending_max) + goto out_err; + + req->hr_odestruct = sk->sk_destruct; + sk->sk_destruct = handshake_sk_destruct; + spin_lock(&hn->hn_lock); + ret = -EOPNOTSUPP; + if (test_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags)) + goto out_unlock; + ret = -EBUSY; + if (!handshake_req_hash_add(req)) + goto out_unlock; + if (!__add_pending_locked(hn, req)) + goto out_unlock; + spin_unlock(&hn->hn_lock); + + ret = handshake_genl_notify(net, req->hr_proto->hp_handler_class, + flags); + if (ret) { + trace_handshake_notify_err(net, req, sk, ret); + if (remove_pending(hn, req)) + goto out_err; + } + + trace_handshake_submit(net, req, sk); + return 0; + +out_unlock: + spin_unlock(&hn->hn_lock); +out_err: + trace_handshake_submit_err(net, req, sk, ret); + handshake_req_destroy(req); + return ret; +} +EXPORT_SYMBOL(handshake_req_submit); + +void handshake_complete(struct handshake_req *req, unsigned int status, + struct genl_info *info) +{ + struct sock *sk = req->hr_sk; + struct net *net = sock_net(sk); + + if (!test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) { + trace_handshake_complete(net, req, sk, status); + req->hr_proto->hp_done(req, status, info); + __sock_put(sk);Is unclear to me who acquired the reference released above?!? If that is the reference acquire by handshake_req_alloc(), I think it's cleaner moving the sock_put() in handshake_req_destroy() or handshake_req_destroy()quoted
+ } +} + +/** + * handshake_req_cancel - consumer API to cancel an in-progress handshake + * @sock: socket on which there is an ongoing handshake + * + * XXX: Perhaps killing the user space agent might also be necessary? + * + * Request cancellation races with request completion. To determine + * who won, callers examine the return value from this function. + * + * Return values: + * %true - Uncompleted handshake request was canceled or not found + * %false - Handshake request already completed + */ +bool handshake_req_cancel(struct socket *sock) +{ + struct handshake_req *req; + struct handshake_net *hn; + struct sock *sk; + struct net *net; + + sk = sock->sk; + net = sock_net(sk); + req = handshake_req_hash_lookup(sk); + if (!req) { + trace_handshake_cancel_none(net, req, sk); + return true; + } + + hn = handshake_pernet(net); + if (hn && remove_pending(hn, req)) { + /* Request hadn't been accepted */ + trace_handshake_cancel(net, req, sk); + return true; + } + if (test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) { + /* Request already completed */ + trace_handshake_cancel_busy(net, req, sk); + return false; + } + + __sock_put(sk);Same here.
I'll move the sock_hold() to _submit, and cook up a comment or two.
Side note, I think at this point some tests could surface here? If user-space-based self-tests are too cumbersome and/or do not offer adequate coverage perhaps you could consider using kunit?
I'm comfortable with Kunit, having just added a bunch of tests for the kernel's SunRPC GSS Kerberos implementation. There, however, I had clearly defined test cases to add, thanks to the RFCs. I guess I'm a little unclear on what specific tests would be necessary or valuable here. Suggestions and existing examples are very welcome. -- Chuck Lever