// socktest -- prove that Unix domain sockets carry data between processes. // // Every part of a socket is invisible from a return value. bind() returning 0 // proves nothing; a socket layer that accepts every call and drops every byte // would pass a test built on return codes alone. So each check here has one // process write and a *different* process read, and compares the bytes. // // socktest run every check // // Prints one line per check and a final verdict a script can grep for. #include "types.h" #include "user.h" #include "socket.h" static int failures; static void puts_(const char *s) { int n = 0; while (s[n]) n++; write(1, (void*)s, n); } static void putn_(int v) { char buf[12]; int i = 0, neg = 0; if (v < 0) { neg = 1; v = -v; } if (v == 0) buf[i++] = '0'; while (v) { buf[i++] = '0' + (v % 10); v /= 10; } if (neg) buf[i++] = '-'; while (i--) write(1, &buf[i], 1); } static void check(int ok, const char *what) { puts_(ok ? " [ok] " : " FAIL: "); puts_(what); puts_("\n"); if (!ok) failures++; } static int streq(const char *a, const char *b) { while (*a && *a == *b) { a++; b++; } return *a == *b; } // Fill a sockaddr_un and return the length to pass to bind/connect: the // family plus the path plus its NUL, which is what musl computes and what the // kernel side is written to expect. static int mkaddr(struct sockaddr_un *sa, const char *path) { int i = 0; for (i = 0; i < UNIX_PATH_MAX - 1 && path[i]; i++) sa->sun_path[i] = path[i]; sa->sun_path[i] = '\0'; sa->sun_family = AF_UNIX; return (int)sizeof(sa->sun_family) + i + 1; } // --------------------------------------------------------------------------- // 1. A round trip over a named socket, between two processes. // --------------------------------------------------------------------------- static void test_roundtrip(void) { struct sockaddr_un sa; int len = mkaddr(&sa, "/tmp/socktest"); int srv, pid, cli, n; char buf[64]; puts_("named socket, two processes:\n"); srv = socket(AF_UNIX, SOCK_STREAM, 0); check(srv >= 0, "socket() returns a descriptor"); if (srv < 0) return; check(bind(srv, (struct sockaddr *)&sa, len) == 0, "bind() to /tmp/socktest"); check(listen(srv, 4) == 0, "listen()"); // A second bind to the same name must be refused, or two servers can // silently steal each other's clients. { int dup_ = socket(AF_UNIX, SOCK_STREAM, 0); check(bind(dup_, (struct sockaddr *)&sa, len) < 0, "a second bind to the same name is refused"); close(dup_); } pid = fork(); if (pid == 0) { // Child: the client. Connects, sends a question, reads the answer. int c = socket(AF_UNIX, SOCK_STREAM, 0); char reply[64]; int r; close(srv); if (connect(c, (struct sockaddr *)&sa, len) < 0) { puts_(" FAIL: child connect()\n"); exit(); } write(c, "ping", 4); r = read(c, reply, sizeof(reply) - 1); if (r < 0) r = 0; reply[r] = '\0'; if (!streq(reply, "pong")) puts_(" FAIL: child did not receive pong\n"); close(c); exit(); } cli = accept(srv, 0, 0); check(cli >= 0, "accept() returns a connection"); if (cli < 0) { close(srv); wait(); return; } n = read(cli, buf, sizeof(buf) - 1); if (n < 0) n = 0; buf[n] = '\0'; check(streq(buf, "ping"), "server read the client's bytes"); check(write(cli, "pong", 4) == 4, "server replied on the same socket"); close(cli); close(srv); wait(); } // --------------------------------------------------------------------------- // 2. Errors that must actually be errors. // --------------------------------------------------------------------------- static void test_errors(void) { struct sockaddr_un sa; int len = mkaddr(&sa, "/tmp/nobody-listening"); int s; puts_("error paths:\n"); s = socket(AF_UNIX, SOCK_STREAM, 0); check(connect(s, (struct sockaddr *)&sa, len) < 0, "connect() to a name nobody bound fails"); close(s); check(socket(AF_INET, SOCK_STREAM, 0) < 0, "AF_INET is refused, not faked"); check(socket(AF_UNIX, SOCK_DGRAM, 0) < 0, "SOCK_DGRAM is refused, not faked"); // read/write on a plain file descriptor must not be mistaken for a socket, // and socket calls on one must be refused. check(listen(1, 4) < 0, "listen() on a non-socket fd fails"); } // --------------------------------------------------------------------------- // 3. End-of-file. A reader whose peer closes must be told, not left hanging. // --------------------------------------------------------------------------- static void test_eof(void) { struct sockaddr_un sa; int len = mkaddr(&sa, "/tmp/socktest-eof"); int srv, cli, pid, n; char buf[16]; puts_("end of file:\n"); srv = socket(AF_UNIX, SOCK_STREAM, 0); bind(srv, (struct sockaddr *)&sa, len); listen(srv, 4); pid = fork(); if (pid == 0) { int c = socket(AF_UNIX, SOCK_STREAM, 0); close(srv); connect(c, (struct sockaddr *)&sa, len); write(c, "x", 1); close(c); // the peer goes away with the byte still unread exit(); } cli = accept(srv, 0, 0); n = read(cli, buf, sizeof(buf)); check(n == 1, "the byte sent before close() is still delivered"); n = read(cli, buf, sizeof(buf)); check(n == 0, "the next read returns 0 rather than blocking"); close(cli); close(srv); wait(); } // --------------------------------------------------------------------------- // 4. socketpair: bidirectional, no name, no listener. // --------------------------------------------------------------------------- static void test_socketpair(void) { int sv[2], n; char buf[16]; puts_("socketpair:\n"); if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { check(0, "socketpair() succeeds"); return; } check(1, "socketpair() succeeds"); write(sv[0], "a", 1); n = read(sv[1], buf, sizeof(buf)); check(n == 1 && buf[0] == 'a', "0 -> 1 carries a byte"); write(sv[1], "b", 1); n = read(sv[0], buf, sizeof(buf)); check(n == 1 && buf[0] == 'b', "1 -> 0 carries a byte, on the same pair"); close(sv[0]); close(sv[1]); } // --------------------------------------------------------------------------- // 5. A message longer than the socket buffer, so the writer has to block and // the reader has to drain in more than one go. // --------------------------------------------------------------------------- #define BIG 12000 static void test_big(void) { int sv[2], pid, got = 0, ok = 1, i; static char out[BIG], in[BIG]; puts_("a transfer larger than the buffer:\n"); for (i = 0; i < BIG; i++) out[i] = (char)('A' + (i % 26)); if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { check(0, "socketpair() for the big transfer"); return; } pid = fork(); if (pid == 0) { int sent = 0, r; close(sv[1]); while (sent < BIG) { r = write(sv[0], out + sent, BIG - sent); if (r <= 0) break; sent += r; } close(sv[0]); exit(); } close(sv[0]); while (got < BIG) { int r = read(sv[1], in + got, BIG - got); if (r <= 0) break; got += r; } close(sv[1]); wait(); check(got == BIG, "every byte arrived"); if (got != BIG) { puts_(" got "); putn_(got); puts_(" of "); putn_(BIG); puts_("\n"); } for (i = 0; i < got; i++) if (in[i] != out[i]) { ok = 0; break; } check(ok, "and in the right order"); } int main(void) { puts_("socktest: unix domain sockets\n"); test_roundtrip(); test_errors(); test_eof(); test_socketpair(); test_big(); if (failures == 0) puts_("socktest: ALL CHECKS PASSED\n"); else { puts_("socktest: "); putn_(failures); puts_(" CHECK(S) FAILED\n"); } exit(); }