Page MenuHomeFreeBSD

unix: retire LOCAL_CONNWAIT
ClosedPublic

Authored by glebius on Feb 2 2024, 5:32 AM.
Tags
None
Referenced Files
Unknown Object (File)
Oct 3 2024, 4:11 PM
Unknown Object (File)
Sep 5 2024, 1:17 AM
Unknown Object (File)
Aug 21 2024, 5:36 PM
Unknown Object (File)
Aug 6 2024, 7:53 AM
Unknown Object (File)
Jul 28 2024, 2:38 PM
Unknown Object (File)
Jul 27 2024, 8:26 PM
Unknown Object (File)
Jul 7 2024, 7:13 PM
Unknown Object (File)
Jul 6 2024, 1:01 PM
Subscribers

Details

Summary

This socket option was added in 6a2989fd54a9 together with LOCAL_CREDS.
Both options originate from NetBSD. The LOCAL_CREDS seems to be used by
some software and is covered by our test suite.

The main problem with LOCAL_CONNWAIT is that it doesn't work as
documented. A basic test shows that connect(2) indeed blocks, but
accept(2) on the other side does not wake it up. Indeed, I don't see what
code in the accept(2) path would go into the peer socket of a unix/stream
listener's child and would make wakeup(&so->so_timeo). I tried the test
even on a FreeBSD 6.4-RELEASE and it produced the same results as on
CURRENT.

The other thing that puzzles me is why that option would be useful even if
it worked? Because on unix/stream you can send(2) immediately after
connect(2) and that would put data on the peer receive buffer even before
listener had done accept(2). In other words, one side can do connect(2)
then send(2), only after the remote side would make accept(2) and the
remote would see the data sent before the accept(2). Again this
undocumented feature of unix(4) is present on all versions from FreeBSD 6
to CURRENT.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

The test I used:

static struct sockaddr_un sun = {
        .sun_family = AF_LOCAL,
        .sun_len = sizeof(sun),
        .sun_path = "unix_stream_test",
};

static const char buf[] = "hello";

void *
client(void *arg __unused)
{
        ssize_t sz;
        int c;

        assert(c = socket(PF_UNIX, SOCK_STREAM, 0));
#if 1
        setsockopt(c, SOL_LOCAL, LOCAL_CONNWAIT, &(int){1}, sizeof(int));
#endif
        assert(connect(c, (struct sockaddr *)&sun, sizeof(sun)) == 0);
        assert(send(c, &buf, sizeof(buf), 0) == sizeof(buf));

        return (NULL);
}

int
main(int argc, char *argv[])
{
        char repl[10];
        pthread_t t;
        int s, a;

        assert(s = socket(PF_UNIX, SOCK_STREAM, 0));

        unlink(sun.sun_path);
        assert(bind(s, (struct sockaddr *)&sun, sizeof(sun)) == 0);
        assert(listen(s, -1) == 0);

        assert(pthread_create(&t, NULL, client, NULL) == 0);
        usleep(100000);

        assert((a = accept(s, NULL, NULL)) != 1);
        assert(recv(a, &repl, sizeof(repl), 0) == sizeof(buf));
        printf("%s\n", repl);

        return (0);
}

Indeed, I don't see what code in the accept(2) path would go into the peer socket of a unix/stream listener's child and would make wakeup(&so->so_timeo).

In NetBSD, unp_accept() calls soisconnected(), which should wake up the sleeper. In FreeBSD, I don't see how the connection would transition to SS_ISCONNECTED after the accept call. So I agree that our implementation appears to be broken. Going back to the commit which introduced LOCAL_CONNWAIT, I see the same problem.

It might still be a good idea to try an exp-run. Maybe there's some code which uses this option and is silently broken.

This revision is now accepted and ready to land.Feb 2 2024, 3:22 PM
This revision was automatically updated to reflect the committed changes.