aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGleb Smirnoff <glebius@FreeBSD.org>2024-04-25 04:50:58 +0000
committerGleb Smirnoff <glebius@FreeBSD.org>2024-04-25 04:55:58 +0000
commit19307b86d31b7dea4bb5c3f7e233ee0e59049258 (patch)
tree847c0e2223fb1ac8cb05dde7406ab319736fa5c3
parentc68eed82a3dcadf0c826e9e150f59769f4c44f24 (diff)
downloadsrc-19307b86d31b7dea4bb5c3f7e233ee0e59049258.tar.gz
src-19307b86d31b7dea4bb5c3f7e233ee0e59049258.zip
accept_filter: return different errors for non-listener and a busy socket
The fact that an accept filter needs to be cleared first before setting to a different one isn't properly documented. The requirement that the socket needs already be listening, although trivial, isn't documented either. At least return a more meaningful error than EINVAL for an existing filter. Cover this with a test case.
-rw-r--r--sys/kern/uipc_accf.c6
-rw-r--r--tests/sys/kern/socket_accf.c32
2 files changed, 37 insertions, 1 deletions
diff --git a/sys/kern/uipc_accf.c b/sys/kern/uipc_accf.c
index c63b5a1179bc..9e30e7839103 100644
--- a/sys/kern/uipc_accf.c
+++ b/sys/kern/uipc_accf.c
@@ -276,10 +276,14 @@ accept_filt_setopt(struct socket *so, struct sockopt *sopt)
* without first removing it.
*/
SOCK_LOCK(so);
- if (!SOLISTENING(so) || so->sol_accept_filter != NULL) {
+ if (__predict_false(!SOLISTENING(so))) {
error = EINVAL;
goto out;
}
+ if (__predict_false(so->sol_accept_filter != NULL)) {
+ error = EBUSY;
+ goto out;
+ }
/*
* Invoke the accf_create() method of the filter if required. The
diff --git a/tests/sys/kern/socket_accf.c b/tests/sys/kern/socket_accf.c
index 747bcda87010..ae6522397cf7 100644
--- a/tests/sys/kern/socket_accf.c
+++ b/tests/sys/kern/socket_accf.c
@@ -209,11 +209,43 @@ ATF_TC_BODY(tls, tc)
ATF_REQUIRE((a = accept(l, NULL, 0)) > 0);
}
+/* Check changing to a different filter. */
+ATF_TC_WITHOUT_HEAD(change);
+ATF_TC_BODY(change, tc)
+{
+ struct accept_filter_arg dfa = {
+ .af_name = "dataready"
+ };
+ struct accept_filter_arg hfa = {
+ .af_name = "httpready"
+ };
+ struct sockaddr_in sin;
+ int n, l;
+
+ l = listensock(&sin);
+ accfon(l, &dfa);
+
+ /* Refuse to change filter without explicit removal of the old one. */
+ ATF_REQUIRE(setsockopt(l, SOL_SOCKET, SO_ACCEPTFILTER, &hfa,
+ sizeof(hfa)) != 0 && errno == EBUSY);
+
+ /* But allow after clearing. */
+ ATF_REQUIRE(setsockopt(l, SOL_SOCKET, SO_ACCEPTFILTER, NULL, 0) == 0);
+ ATF_REQUIRE(setsockopt(l, SOL_SOCKET, SO_ACCEPTFILTER, &hfa,
+ sizeof(hfa)) == 0);
+
+ /* Must be listening socket. */
+ ATF_REQUIRE((n = socket(PF_INET, SOCK_STREAM, 0)) > 0);
+ ATF_REQUIRE(setsockopt(n, SOL_SOCKET, SO_ACCEPTFILTER, &dfa,
+ sizeof(dfa)) != 0 && errno == EINVAL);
+}
+
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, data);
ATF_TP_ADD_TC(tp, http);
ATF_TP_ADD_TC(tp, tls);
+ ATF_TP_ADD_TC(tp, change);
return (atf_no_error());
}