Softpanorama

May the source be with you, but remember the KISS principle ;-)
Home Switchboard Unix Administration Red Hat TCP/IP Networks Neoliberalism Toxic Managers
(slightly skeptical) Educational society promoting "Back to basics" movement against IT overcomplexity and  bastardization of classic Unix

Unix Sockets

News See Also Recommended Links Pipes Sockets Nmap
ntop ngrep nettee Netcat Humor Etc
 

Remember FIFOs? Remember how they can only send data in one direction, just like a Pipes? Wouldn't it be grand if you could send data in both directions. In other works operate with network connection in a manner close to file.

Unix Domain Sockets is a two-way communications pipe.  This family of sockets is for local communication only, i.e. you cannot use them across a network. Please see Beej's Guide to Network Programming Using Internet Sockets about Internet sockets.

Unix sockets are a special file in the file system (just like FIFOs) but for historical reasons  instead of using open() and read() you'll be using socket(), bind(), recv(), etc. The telephone analogy is a very good one  and greatly simplify understanding socket behavior.

When programming with sockets, you'll usually create server and client programs. The server will sit listening for incoming connections from clients and handle them.

For instance, when describing which Unix socket you want to use (that is, the path to the special file that is the socket), you use a struct sockaddr_un, which has the following fields:

    struct sockaddr_un {
        unsigned short sun_family;  /* AF_UNIX */
        char sun_path[108];
    }

This is the structure you will be passing to the bind() function, which associates a socket descriptor (a file descriptor) with a certain file (the name for which is in the sun_path field).

Without going into too much detail, I'll outline the steps a server program usually has to go through to do it's thing. While I'm at it, I'll be trying to implement an "echo server" which just echos back everything it gets on the socket.

Here are the server steps:

  1. Call socket(): A call to socket() with the proper arguments creates the Unix socket:
        unsigned int s, s2;
        struct sockaddr_un local, remote;
        int len;
    
        s = socket(AF_UNIX, SOCK_STREAM, 0);

    The second argument, SOCK_STREAM, tells socket() to create a stream socket. Yes, datagram sockets (SOCK_DGRAM) are supported in the Unix domain, but I'm only going to cover stream sockets here. For the curious, see Beej's Guide to Network Programming for a good description of unconnected datagram sockets that applies perfectly well to Unix sockets. The only thing that changes is that you're now using a struct sockaddr_un instead of a struct sockaddr_in.

    One more note: all these calls return -1 on error and set the global variable errno to reflect whatever went wrong. Be sure to do you error checking.

     

  2. Call bind(): You got a socket descriptor from the call to socket(), now you want to bind that to an address in the Unix domain. (That address, as I said before, is a special file on disk.)

     

        local.sun_family = AF_UNIX;  /* local is declared before socket() ^ */
        local.sun_path = "/home/beej/mysocket";
        unlink(local.sun_path);
        len = strlen(local.sun_path) + sizeof(local.sun_family);
        bind(s, (struct sockaddr *)&local, len);
    

    This associates the socket descriptor "s" with the Unix socket address "/home/beej/mysocket". Notice that we called unlink() before bind() to remove the socket if it already exists. You will get an EINVAL error if the file is already there.
     

  3. Call listen(): This instructs the socket to listen for incoming connections from client programs:

     

        listen(s, 5);
    

    The second argument, 5, is the number of incoming connections that can be queued before you call accept(), below. If there are this many connections waiting to be accepted, additional clients will generate the error ECONNREFUSED.

     

  4. Call accept(): This will accept a connection from a client. This function returns another socket descriptor! The old descriptor is still listening for new connections, but this new one is connected to the client:

     

        len = sizeof(struct sockaddr_un);
        s2 = accept(s, &remote, &len);
    

    When accept() returns, the remote variable will be filled with the remote side's struct sockaddr_un, and len will be set to its length. The descriptor s2 is connected to the client, and is ready for send() and recv(), as described in the Network Programming Guide.

     

  5. Handle the connection and loop back to accept(): Usually you'll want to communicate to the client here (we'll just echo back everything it sends us), close the connection, then accept() a new one.

     

        while (len = recv(s2, &buf, 100, 0), len > 0)
            send(s2, &buf, len, 0);
    
        /* loop back to accept() from here */
    

     

  6. Close the connection: You can close the connection either by calling close(), or by calling shutdown().

With all that said, here is some source for an echoing server, echos.c. All it does is wait for a connection on a Unix socket (named, in this case, "echo_socket").

 

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/un.h>

    #define SOCK_PATH "echo_socket"

    int main(void)
    {
        int s, s2, t, len;
        struct sockaddr_un local, remote;
        char str[100];

        if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
            perror("socket");
            exit(1);
        }

        local.sun_family = AF_UNIX;
        strcpy(local.sun_path, SOCK_PATH);
        unlink(local.sun_path);
        len = strlen(local.sun_path) + sizeof(local.sun_family);
        if (bind(s, (struct sockaddr *)&local, len) == -1) {
            perror("bind");
            exit(1);
        }

        if (listen(s, 5) == -1) {
            perror("listen");
            exit(1);
        }

        for(;;) {
            int done, n;
            printf("Waiting for a connection...\n");
            t = sizeof(remote);
            if ((s2 = accept(s, (struct sockaddr *)&remote, &t)) == -1) {
                perror("accept");
                exit(1);
            }

            printf("Connected.\n");

            done = 0;
            do {
                n = recv(s2, str, 100, 0);
                if (n <= 0) {
                    if (n < 0) perror("recv");
                    done = 1;
                }

                if (!done) 
                    if (send(s2, str, n, 0) < 0) {
                        perror("send");
                        done = 1;
                    }
            } while (!done);

            close(s2);
        }

        return 0;
    }

As you can see, all the aforementioned steps are included in this program: call socket(), call bind(), call listen(), call accept(), and do some network send()s and recv()s.

There needs to be a program to talk to the above server, right? Except with the client, it's a lot easier because you don't have to do any pesky listen()ing or accept()ing. Here are the steps:

  1. Call socket() to get a Unix domain socket to communicate through.

     

  2. Set up a struct sockaddr_un with the remote address (where the server is listening) and call connect() with that as an argument

     

  3. Assuming no errors, you're connected to the remote side! Use send() and recv() to your heart's content!

How about code to talk to the echo server, above? No sweat, friends, here is echoc.c:

 

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    
    #define SOCK_PATH "echo_socket"

    int main(void)
    {
        int s, t, len;
        struct sockaddr_un remote;
        char str[100];

        if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
            perror("socket");
            exit(1);
        }

        printf("Trying to connect...\n");

        remote.sun_family = AF_UNIX;
        strcpy(remote.sun_path, SOCK_PATH);
        len = strlen(remote.sun_path) + sizeof(remote.sun_family);
        if (connect(s, (struct sockaddr *)&remote, len) == -1) {
            perror("connect");
            exit(1);
        }

        printf("Connected.\n");

        while(printf("> "), fgets(str, 100, stdin), !feof(stdin)) {
            if (send(s, str, strlen(str), 0) == -1) {
                perror("send");
                exit(1);
            }

            if ((t=recv(s, str, 100, 0)) > 0) {
                str[t] = '\0';
                printf("echo> %s", str);
            } else {
                if (t < 0) perror("recv");
                else printf("Server closed connection\n");
                exit(1);
            }
        }

        close(s);

        return 0;
    }

In the client code, of course you'll notice that there are only a few system calls used to set things up: socket() and connect(). Since the client isn't going to be accept()ing any incoming connections, there's no need for it to listen(). Of course, the client still uses send() and recv() for transferring data. That about sums it up.

socketpair()--quick full-duplex pipes

What if you wanted a pipe(), but you wanted to use a single pipe to send and recieve data from both sides? Since pipes are unidirectional (with exceptions in SYSV), you can't do it! There is a solution, though: use a Unix domain socket, since they can handle bi-directional data.

What a pain, though! Setting up all that code with listen() and connect() and all that just to pass data both ways! But guess what! You don't have to!

That's right, there's a beauty of a system call known as socketpair() this is nice enough to return to you a pair of already connected sockets! No extra work is needed on your part; you can immediately use these socket descriptors for interprocess communication.

For instance, lets set up two processes. The first sends a char to the second, and the second changes the character to uppercase and returns it. Here is some simple code to do just that, called spair.c (with no error checking for clarity):

    #include <stdio.h>
    #include <stdlib.h>
    #include <ctype.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>

    int main(void)
    {
        int sv[2]; /* the pair of socket descriptors */
        char buf; /* for data exchange between processes */

        socketpair(AF_UNIX, SOCK_STREAM, 0, sv);

        if (!fork()) {  /* child */
            read(sv[1], &buf, 1);
            printf("child: read '%c'\n", buf);
            buf = toupper(buf);  /* make it uppercase */
            write(sv[1], &buf, 1);
            printf("child: sent '%c'\n", buf);

        } else { /* parent */
            write(sv[0], "b", 1);
            printf("parent: sent 'b'\n");
            read(sv[0], &buf, 1);
            printf("parent: read '%c'\n", buf);
        }
        return 0;
    }

Sure, it's an expensive way to change a character to uppercase, but it's the fact that you have simple communication going on here that really matters.

One more thing to notice is that socketpair() takes both a domain (AF_UNIX) and socket type (SOCK_STREAM). These can be any legal values at all, depending on which routines in the kernel you want to handle your code, and whether you want stream or datagram sockets. I chose AF_UNIX sockets because this is a Unix sockets document and they're a bit faster than AF_INET sockets, I hear.

Finally, you might be curious as to why I'm using write() and read() instead of send() and recv(). Well, in short, I was being lazy. See, by using these system calls, I don't have to enter the flags argument that send() and recv() use, and I always set it to zero anyway. Of course, socket descriptors are just file descriptors like any other, so they respond just fine to many file manipulation system calls.


Top Visited
Switchboard
Latest
Past week
Past month

NEWS CONTENTS

Old News ;-)

Inter-process communication in Linux Sockets and signals by Marty Kalin

Apr 17, 2019 | Opensource.com

This is the third and final article in a series about interprocess communication (IPC) in Linux. The first article focused on IPC through shared storage (files and memory segments), and the second article does the same for basic channels: pipes (named and unnamed) and message queues. This article moves from IPC at the high end (sockets) to IPC at the low end (signals). Code examples flesh out the details.

Sockets

Just as pipes come in two flavors (named and unnamed), so do sockets. IPC sockets (aka Unix domain sockets) enable channel-based communication for processes on the same physical device (host), whereas network sockets enable this kind of IPC for processes that can run on different hosts, thereby bringing networking into play. Network sockets need support from an underlying protocol such as TCP (Transmission Control Protocol) or the lower-level UDP (User Datagram Protocol).

By contrast, IPC sockets rely upon the local system kernel to support communication; in particular, IPC sockets communicate using a local file as a socket address. Despite these implementation differences, the IPC socket and network socket APIs are the same in the essentials. The forthcoming example covers network sockets, but the sample server and client programs can run on the same machine because the server uses network address localhost (127.0.0.1), the address for the local machine on the local machine.

Sockets configured as streams (discussed below) are bidirectional, and control follows a client/server pattern: the client initiates the conversation by trying to connect to a server, which tries to accept the connection. If everything works, requests from the client and responses from the server then can flow through the channel until this is closed on either end, thereby breaking the connection.

An iterative server, which is suited for development only, handles connected clients one at a time to completion: the first client is handled from start to finish, then the second, and so on. The downside is that the handling of a particular client may hang, which then starves all the clients waiting behind. A production-grade server would be concurrent, typically using some mix of multi-processing and multi-threading. For example, the Nginx web server on my desktop machine has a pool of four worker processes that can handle client requests concurrently. The following code example keeps the clutter to a minimum by using an iterative server; the focus thus remains on the basic API, not on concurrency.

Finally, the socket API has evolved significantly over time as various POSIX refinements have emerged. The current sample code for server and client is deliberately simple but underscores the bidirectional aspect of a stream-based socket connection. Here's a summary of the flow of control, with the server started in a terminal then the client started in a separate terminal:

Example 1. The socket server

 
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include "sock.h"

void report(const char* msg, int terminate) {
perror(msg);
if (terminate) exit(-1); /* failure */
}

int main() {
int fd = socket(AF_INET, /* network versus AF_LOCAL */
SOCK_STREAM, /* reliable, bidirectional, arbitrary payload size */
0); /* system picks underlying protocol (TCP) */
if (fd < 0) report("socket", 1); /* terminate */

/* bind the server's local address in memory */
struct sockaddr_in saddr;
memset(&saddr, 0, sizeof(saddr)); /* clear the bytes */
saddr.sin_family = AF_INET; /* versus AF_LOCAL */
saddr.sin_addr.s_addr = htonl(INADDR_ANY); /* host-to-network endian */
saddr.sin_port = htons(PortNumber); /* for listening */

if (bind(fd, (struct sockaddr *) &saddr, sizeof(saddr)) < 0)
report("bind", 1); /* terminate */

/* listen to the socket */
if (listen(fd, MaxConnects) < 0) /* listen for clients, up to MaxConnects */
report("listen", 1); /* terminate */

fprintf(stderr, "Listening on port %i for clients...\n", PortNumber);
/* a server traditionally listens indefinitely */
while (1) {
struct sockaddr_in caddr; /* client address */
int len = sizeof(caddr); /* address length could change */

int client_fd = accept(fd, (struct sockaddr*) &caddr, &len); /* accept blocks */
if (client_fd < 0) {
report("accept", 0); /* don't terminate, though there's a problem */
continue;
}

/* read from client */
int i;
for (i = 0; i < ConversationLen; i++) {
char buffer[BuffSize + 1];
memset(buffer, '\0', sizeof(buffer));
int count = read(client_fd, buffer, sizeof(buffer));
if (count > 0) {
puts(buffer);
write(client_fd, buffer, sizeof(buffer)); /* echo as confirmation */
}
}
close(client_fd); /* break connection */
} /* while(1) */
return 0;
}

 

The server program above performs the classic four-step to ready itself for client requests and then to accept individual requests. Each step is named after a system function that the server calls:

  1. socket(…): get a file descriptor for the socket connection
  2. bind(…): bind the socket to an address on the server's host
  3. listen(…): listen for client requests
  4. accept(…): accept a particular client request

The socket call in full is:

 
int sockfd = socket(AF_INET, /* versus AF_LOCAL */
SOCK_STREAM, /* reliable, bidirectional */
0); /* system picks protocol (TCP) */
 

The first argument specifies a network socket as opposed to an IPC socket. There are several options for the second argument, but SOCK_STREAM and SOCK_DGRAM (datagram) are likely the most used. A stream-based socket supports a reliable channel in which lost or altered messages are reported; the channel is bidirectional, and the payloads from one side to the other can be arbitrary in size. By contrast, a datagram-based socket is unreliable (best try), unidirectional, and requires fixed-sized payloads. The third argument to socket specifies the protocol. For the stream-based socket in play here, there is a single choice, which the zero represents: TCP. Because a successful call to socket returns the familiar file descriptor, a socket is written and read with the same syntax as, for example, a local file.

The bind call is the most complicated, as it reflects various refinements in the socket API. The point of interest is that this call binds the socket to a memory address on the server machine. However, the listen call is straightforward:

if (listen(fd, MaxConnects) < 0)

The first argument is the socket's file descriptor and the second specifies how many client connections can be accommodated before the server issues a connection refused error on an attempted connection. (MaxConnects is set to 8 in the header file sock.h.)

The accept call defaults to a blocking wait: the server does nothing until a client attempts to connect and then proceeds. The accept function returns -1 to indicate an error. If the call succeeds, it returns another file descriptor-for a read/write socket in contrast to the accepting socket referenced by the first argument in the accept call. The server uses the read/write socket to read requests from the client and to write responses back. The accepting socket is used only to accept client connections.

By design, a server runs indefinitely. Accordingly, the server can be terminated with a Ctrl+C from the command line.

Example 2. The socket client

 
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include "sock.h"

const char* books[] = {"War and Peace",
"Pride and Prejudice",
"The Sound and the Fury"};

void report(const char* msg, int terminate) {
perror(msg);
if (terminate) exit(-1); /* failure */
}

int main() {
/* fd for the socket */
int sockfd = socket(AF_INET, /* versus AF_LOCAL */
SOCK_STREAM, /* reliable, bidirectional */
0); /* system picks protocol (TCP) */
if (sockfd < 0) report("socket", 1); /* terminate */

/* get the address of the host */
struct hostent* hptr = gethostbyname(Host); /* localhost: 127.0.0.1 */
if (!hptr) report("gethostbyname", 1); /* is hptr NULL? */
if (hptr->h_addrtype != AF_INET) /* versus AF_LOCAL */
report("bad address family", 1);

/* connect to the server: configure server's address 1st */
struct sockaddr_in saddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr =
((struct in_addr*) hptr->h_addr_list[0])->s_addr;
saddr.sin_port = htons(PortNumber); /* port number in big-endian */

if (connect(sockfd, (struct sockaddr*) &saddr, sizeof(saddr)) < 0)
report("connect", 1);

/* Write some stuff and read the echoes. */
puts("Connect to server, about to write some stuff...");
int i;
for (i = 0; i < ConversationLen; i++) {
if (write(sockfd, books[i], strlen(books[i])) > 0) {
/* get confirmation echoed from server and print */
char buffer[BuffSize + 1];
memset(buffer, '\0', sizeof(buffer));
if (read(sockfd, buffer, sizeof(buffer)) > 0)
puts(buffer);
}
}
puts("Client done, about to exit...");
close(sockfd); /* close the connection */
return 0;
}

 

The client program's setup code is similar to the server's. The principal difference between the two is that the client neither listens nor accepts, but instead connects:

if (connect(sockfd, (struct sockaddr*) &saddr, sizeof(saddr)) < 0)

The connect call might fail for several reasons; for example, the client has the wrong server address or too many clients are already connected to the server. If the connect operation succeeds, the client writes requests and then reads the echoed responses in a for loop. After the conversation, both the server and the client close the read/write socket, although a close operation on either side is sufficient to close the connection. The client exits thereafter but, as noted earlier, the server remains open for business.

The socket example, with request messages echoed back to the client, hints at the possibilities of arbitrarily rich conversations between the server and the client. Perhaps this is the chief appeal of sockets. It is common on modern systems for client applications (e.g., a database client) to communicate with a server through a socket. As noted earlier, local IPC sockets and network sockets differ only in a few implementation details; in general, IPC sockets have lower overhead and better performance. The communication API is essentially the same for both.

Signals

A signal interrupts an executing program and, in this sense, communicates with it. Most signals can be either ignored (blocked) or handled (through designated code), with SIGSTOP (pause) and SIGKILL (terminate immediately) as the two notable exceptions. Symbolic constants such as SIGKILL have integer values, in this case, 9.

Signals can arise in user interaction. For example, a user hits Ctrl+C from the command line to terminate a program started from the command-line; Ctrl+C generates a SIGTERM signal. SIGTERM for terminate, unlike SIGKILL, can be either blocked or handled. One process also can signal another, thereby making signals an IPC mechanism.

Consider how a multi-processing application such as the Nginx web server might be shut down gracefully from another process. The kill function:

int kill(pid_t pid, int signum); /* declaration */

can be used by one process to terminate another process or group of processes. If the first argument to function kill is greater than zero, this argument is treated as the pid (process ID) of the targeted process; if the argument is zero, the argument identifies the group of processes to which the signal sender belongs.

The second argument to kill is either a standard signal number (e.g., SIGTERM or SIGKILL) or 0, which makes the call to signal a query about whether the pid in the first argument is indeed valid. The graceful shutdown of a multi-processing application thus could be accomplished by sending a terminate signal-a call to the kill function with SIGTERM as the second argument-to the group of processes that make up the application. (The Nginx master process could terminate the worker processes with a call to kill and then exit itself.) The kill function, like so many library functions, houses power and flexibility in a simple invocation syntax.

Example 3. The graceful shutdown of a multi-processing system

 
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

void graceful(int signum) {
printf("\tChild confirming received signal: %i\n", signum);
puts("\tChild about to terminate gracefully...");
sleep(1);
puts("\tChild terminating now...");
_exit(0); /* fast-track notification of parent */
}

void set_handler() {
struct sigaction current;
sigemptyset(&current.sa_mask); /* clear the signal set */
current.sa_flags = 0; /* enables setting sa_handler, not sa_action */
current.sa_handler = graceful; /* specify a handler */
sigaction(SIGTERM, &current, NULL); /* register the handler */
}

void child_code() {
set_handler();

while (1) { /** loop until interrupted **/
sleep(1);
puts("\tChild just woke up, but going back to sleep.");
}
}

void parent_code(pid_t cpid) {
puts("Parent sleeping for a time...");
sleep(5);

/* Try to terminate child. */
if (-1 == kill(cpid, SIGTERM)) {
perror("kill");
exit(-1);
}
wait(NULL); /** wait for child to terminate **/
puts("My child terminated, about to exit myself...");
}

int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return -1; /* error */
}
if (0 == pid)
child_code();
else
parent_code(pid);
return 0; /* normal */
}

 

The shutdown program above simulates the graceful shutdown of a multi-processing system, in this case, a simple one consisting of a parent process and a single child process. The simulation works as follows:

Here is the output from a sample run:

 
% ./shutdown
Parent sleeping for a time...
Child just woke up, but going back to sleep.
Child just woke up, but going back to sleep.
Child just woke up, but going back to sleep.
Child just woke up, but going back to sleep.
Child confirming received signal: 15 ## SIGTERM is 15
Child about to terminate gracefully...
Child terminating now...
My child terminated, about to exit myself...
 

For the signal handling, the example uses the sigaction library function (POSIX recommended) rather than the legacy signal function, which has portability issues. Here are the code segments of chief interest:

Using signals for IPC is indeed a minimalist approach, but a tried-and-true one at that. IPC through signals clearly belongs in the IPC toolbox.

Marty Kalin - I'm an academic in computer science (College of Computing and Digital Media, DePaul University) with wide experience in software development, mostly in production planning and scheduling (steel industry) and product configuration (truck and bus manufacturing). Details on books and other publications are available at Marty Kalin's hompage

Unix socket magic

Here's another technique that helps you split an application into a privileged and a non-privileged part. In the fork example, we used a pipe for communication between the front-end and the backend. One drawback with this is that pipes are one-way only, so you'd need two of them if the back end is to talk to the front-end as well. Another problem with the fork approach in general is that the two processes cannot exchange resources such as open file descriptors easily. Imagine an application where the user selects one out of several serial ports, and wants to open it. You can design the application so that the front-end asks the back-end to open the serial device, but how do you transfer the open file to the front-end process?

This is where Unix domain sockets come in. This family of sockets is for local communication only, i.e. you cannot use them across a network. They do have some properties that make them unique; one of them is what's commonly called file descriptor passing. One process can attach a set of file descriptors to a message it sends through the socket, and if the peer is willing to receive them, these open files are attached to the recipient's file set.

Here's an outline of using a back end for opening serial ports:

int     backend_fd;

void init_backend(void)
{
        char    buffer[1024];
        int     pair[2], fd, n;
        pid_t   pid;

        if (socketpair(PF_UNIX, SOCK_DGRAM, 0, pair) < 0)
                fatal("socketpair failed");
        if ((pid = fork()) < 0)
                fatal("fork failed");
        if (pid != 0) {
                if (setuid(getuid()) < 0)
                        fatal("unable to drop privs");
                backend_fd = pair[0];
                close(pair[1]);
                return;
        }

        /* We're the backend */
        close(pair[0]);
        fd = pair[1];

        while ((n = recv(fd, buffer, sizeof(buffer)-1, 0)) > 0) {
                char            control[sizeof(struct cmsg)+10];
                struct msghdr   msg;
                struct cmsghdr  *cmsg;
                struct iovec    iov;
                int             tty;

                buffer[n] = '\0';
                if (strchr(buffer, '/') != NULL
                 || strncmp(buffer, "ttyS", 4))
                        reply(fd, "bad tty path");
                else
                if ((tty = lock_and_open_port(buffer)) < 0)
                        reply(fd, "tty already locked");
                else {
                        /* Response data */
                        iov.iov_base = "OK";
                        iov.iov_len  = 2;

                        /* compose the message */
                        memset(&msg, 0, sizeof(msg));
                        msg.msg_iov = &iov;
                        msg.msg_iovlen = 1;
                        msg.msg_control = control;
                        msg.msg_controllen = sizeof(control);

                        /* attach open tty fd */
                        cmsg = CMSG_FIRSTHDR(&msg);
                        cmsg->cmsg_level = SOL_SOCKET;
                        cmsg->cmsg_type = SCM_RIGHTS;
                        cmsg->cmsg_len = CMSG_LEN(sizeof(tty));
                        *(int *)CMSG_DATA(cmsg) = tty;

                        msg.msg_controllen = cmsg->cmsg_len;

                        if (sendmsg(fd, &msg, 0) < 0)
                                fatal("sendmsg failed");
                }
        }
        exit(0);
}

int open_tty(const char *name)
{
        char            data[1024], control[1024];
        struct msghdr   msg;
        struct cmsghdr  *cmsg;
        struct iovec    iov;

        if (write(backend_fd, name, strlen(name)) < 0)
                fatal("write failed");

        memset(&msg, 0, sizeof(msg));
        iov.iov_base   = data;
        iov.iov_len    = sizeof(data)-1;
        msg.msg_iov    = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = control;
        msg.msg_controllen = sizeof(control);

        if (recvmsg(backend_fd, &msg, 0) < 0)
                fatal("recvmsg failed");

        data[iov.iov_len] = '\0';
        if (strcmp(data, "OK")) {
                printf("failed to open %s: %s\n", name, data);
                return -1;
        }

        /* Loop over all control messages */
        cmsg = CMSG_FIRSTHDR(&msg);
        while (cmsg != NULL) {
                if (cmsg->cmsg_level == SOL_SOCKET
                 && cmsg->cmsg_type  == SCM_RIGHTS)
                        return *(int *) CMSG_DATA(cmsg);
                cmsg = CMSG_NXTHDR(&msg, cmsg);
        }

        printf("failed to open %s: bug!\n");
        return -1;
}

int
main(int argc, char **argv)
{
        init_backend();

        /* parse arguments, set up GUI etc  */

        /* now open tty */
        fd = open_tty("ttyS0");
}

The init_backend function, which is called as the very first thing in main(), creates a Unix socket pair and forks a worker process that continues with root privilege, while the main parent process that does all the GUI stuff drops all privilege before returning to main().

When the frontend comes around to opening a serial port, it sends the name of the device to the backend via the Unix socket. The backend performs some sanity checks on the name to make sure this is indeed a serial port and not /etc/shadow it's about to open, and tries to create a lock file. If any of this fails, it simply returns an error message to the front-end. If all this succeeds however, it sends the string OK as response, and sends the file descriptor of the open tty along.

This code, as well as the code that receives the open tty descriptor in open_tty looks quite complicated, and it really is. The reason for this is that the only way to do file descriptor passing is by using sendmsg and recvmsg, and a control message (struct cmsghdr) attached to it. I will not go into greater details here concerning these details; if this is all greek to you (as it was to me when I first tried to understand it), refer to the manual pages for sendmsg and recvmsg, as well as cmsg(3).

There's a second control message Unix sockets offer which is quite interesting in a security context. On Linux and BSD, Unix sockets allow you to obtain the Unix credentials of the process that sent you a particular message. These credentials come along as a control message at level SOL_SOCKET, type SCM_CREDENTIALS. They're passed as a struct containing uid, gid, and pid of the sender process.

This is quite useful for applications that are supposed to serve local users only, and need to know the identity of the user connecting to them.

The most convenient way to use this feature is for the server to set the SO_PASSCRED socket option. It can then retrieve the user credentials every time the client sends a message:

int
svc_make(const char *pathname)
{
        struct sockaddr_un sun;
        int             on = 1;

        /* create server socket */
        fd = socket(PF_UNIX, SOCK_STREAM, 0);

        /* bind it */
        memset(&sun, 0, sizeof(sun));
        sun.sun_family = AF_UNIX;
        strcpy(sun.sun_path, pathname);
        bind(fd, (struct sockaddr *) &sun, sizeof(sun));
        listen(fd, 10);

        /* turn on credentials passing */
        setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
        return fd;
}

int
svc_recv(int fd, char *buffer, size_t size, struct ucred *cred)
{
        char            control[1024];
        struct msghdr   msg;
        struct cmsghdr  *cmsg;
        struct iovec    iov;
        int             result;

        memset(&msg, 0, sizeof(msg));
        iov.iov_base = buffer;
        iov.iov_len = size;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = control;
        msg.msg_controllen = sizeof(control);

        if (recvmsg(fd, &msg, 0) < 0)
                return -1;

        result = -1;
        cmsg = CMSG_FIRSTHDR(&msg);
        while (cmsg != NULL) {
                if (cmsg->cmsg_level == SOL_SOCKET
                 && cmsg->cmsg_type  == SCM_CREDENTIALS) {
                        memcpy(cred, CMSG_DATA(cmsg), sizeof(*cred));
                        result = iov.iov_len;
                } else
                if (cmsg->cmsg_level == SOL_SOCKET
                 && cmsg->cmsg_type  == SCM_RIGHTS) {
                        dispose_fds((int *) CMSG_DATA(cmsg),
                                (cmsg->cmsg_len - CMSG_LEN(0))/sizeof(int));
                }
                cmsg = CMSG_NXTHDR(&msg, cmsg);
        }

        return result;
}

The svc_make routine creates a Unix stream socket, and sets its SO_PASSCRED option. The svc_recv function receives data from the client, and in addition extracts the sender's credentials. This gives the server the uid and gid of the process that sent the message. This information is reliable (well, it's as trustworthy as the integrity of your kernel).

There are two things to watch out for, however. The first is that the identity of the sender may change from message to message. If you don't want to deal with this kind of multiple personality disorder, you should at least verify that the uid/gid of the client remains the same, and balk if not.

The second issue is that a process receiving any control messages via Unix sockets can also be sent socket descriptors by a client. If you're not prepared to handle file descriptors passed by the client, an attacker can flood your server with open file descriptors until it reaches its resource limit; after that, it will be unable to accept new connections. This is why the sample code above calls the dispose_fds function when receiving file descriptors from the client.11.7

BSD Sockets: A Quick & Dirty Primer by Jim Frost

"The socket is the BSD method for accomplishing interprocess communication (IPC). What this means is a socket is used to allow one process to speak to another, very much like the telephone is used to allow one person to speak to another. "
``As you delve into the mysteries of UNIX, you find more and more things that are difficult to understand immediately. One of these things, at least for most people, is the BSD socket concept. This is a short tutorial that explains what they are, how they work, and gives sample code showing how to use them.'' --Jim Frost

Berkeley UNIX® System Calls and Interprocess Communication

The purpose of this paper is to discuss interprocess communication in the context of Berkeley UNIX. Special emphasis will be given to those system calls concerned with the creation, management, and use of sockets. --Lawrence Besaw

Recommended Links

Google matched content

Softpanorama Recommended

Top articles

Sites

Unix domain socket - Wikipedia, the free encyclopedia

Spencer's Socket Site Network Programming with Sockets --Links

Beej's Guide to Network Programming Sockets from A-Z with sample source.

Unix Sockets Tutorial - UNIX Tutorial- Learn UNIX

IP Socket Programming

UNIX Socket FAQ - Powered by vBulletin

Unix Sockets

Socket Programming Guide

UNIX Socket Programming in C -- Programming UNIX Sockets in C - Frequently Asked Questions Created by Vic Metcalfe, Andrew Gierth and other contributers May 21, 1998

Beej's Guide to Network Programming -- Using Internet Sockets Version 1.5.4 (17-May-1998) [http://www.ecst.csuchico.edu/~beej/guide/net]

BSD Sockets A Quick And Dirty Primer -- Jim Frost Software Tool & Die Copyright (c) 1988, 1994 Jim Frost All Rights Reserved Last changed November 4, 1994

Network Programmer's Guide and Reference -- big e-book

6 Linux Interprocess Communications

Sockets Tutorial

Sockets Tutorial -- Socketpairs -- Berkeley UNIX 4.4BSD provides a slight generalization of pipes. A pipe is a pair of connected sockets for one-way stream communication. One may obtain a pair of connected sockets for two-way stream communication by calling the routine socketpair(). The program in Figure 3 calls socketpair() to create such a connection. The program uses the link for communication in both directions. Since socketpairs are an extension of pipes, their use resembles that of pipes. Figure 4 illustrates the result of a fork following a call to socketpair().

(most of them from Davin's collection of unix programming links)



Etc

Society

Groupthink : Two Party System as Polyarchy : Corruption of Regulators : Bureaucracies : Understanding Micromanagers and Control Freaks : Toxic Managers :   Harvard Mafia : Diplomatic Communication : Surviving a Bad Performance Review : Insufficient Retirement Funds as Immanent Problem of Neoliberal Regime : PseudoScience : Who Rules America : Neoliberalism  : The Iron Law of Oligarchy : Libertarian Philosophy

Quotes

War and Peace : Skeptical Finance : John Kenneth Galbraith :Talleyrand : Oscar Wilde : Otto Von Bismarck : Keynes : George Carlin : Skeptics : Propaganda  : SE quotes : Language Design and Programming Quotes : Random IT-related quotesSomerset Maugham : Marcus Aurelius : Kurt Vonnegut : Eric Hoffer : Winston Churchill : Napoleon Bonaparte : Ambrose BierceBernard Shaw : Mark Twain Quotes

Bulletin:

Vol 25, No.12 (December, 2013) Rational Fools vs. Efficient Crooks The efficient markets hypothesis : Political Skeptic Bulletin, 2013 : Unemployment Bulletin, 2010 :  Vol 23, No.10 (October, 2011) An observation about corporate security departments : Slightly Skeptical Euromaydan Chronicles, June 2014 : Greenspan legacy bulletin, 2008 : Vol 25, No.10 (October, 2013) Cryptolocker Trojan (Win32/Crilock.A) : Vol 25, No.08 (August, 2013) Cloud providers as intelligence collection hubs : Financial Humor Bulletin, 2010 : Inequality Bulletin, 2009 : Financial Humor Bulletin, 2008 : Copyleft Problems Bulletin, 2004 : Financial Humor Bulletin, 2011 : Energy Bulletin, 2010 : Malware Protection Bulletin, 2010 : Vol 26, No.1 (January, 2013) Object-Oriented Cult : Political Skeptic Bulletin, 2011 : Vol 23, No.11 (November, 2011) Softpanorama classification of sysadmin horror stories : Vol 25, No.05 (May, 2013) Corporate bullshit as a communication method  : Vol 25, No.06 (June, 2013) A Note on the Relationship of Brooks Law and Conway Law

History:

Fifty glorious years (1950-2000): the triumph of the US computer engineering : Donald Knuth : TAoCP and its Influence of Computer Science : Richard Stallman : Linus Torvalds  : Larry Wall  : John K. Ousterhout : CTSS : Multix OS Unix History : Unix shell history : VI editor : History of pipes concept : Solaris : MS DOSProgramming Languages History : PL/1 : Simula 67 : C : History of GCC developmentScripting Languages : Perl history   : OS History : Mail : DNS : SSH : CPU Instruction Sets : SPARC systems 1987-2006 : Norton Commander : Norton Utilities : Norton Ghost : Frontpage history : Malware Defense History : GNU Screen : OSS early history

Classic books:

The Peter Principle : Parkinson Law : 1984 : The Mythical Man-MonthHow to Solve It by George Polya : The Art of Computer Programming : The Elements of Programming Style : The Unix Hater’s Handbook : The Jargon file : The True Believer : Programming Pearls : The Good Soldier Svejk : The Power Elite

Most popular humor pages:

Manifest of the Softpanorama IT Slacker Society : Ten Commandments of the IT Slackers Society : Computer Humor Collection : BSD Logo Story : The Cuckoo's Egg : IT Slang : C++ Humor : ARE YOU A BBS ADDICT? : The Perl Purity Test : Object oriented programmers of all nations : Financial Humor : Financial Humor Bulletin, 2008 : Financial Humor Bulletin, 2010 : The Most Comprehensive Collection of Editor-related Humor : Programming Language Humor : Goldman Sachs related humor : Greenspan humor : C Humor : Scripting Humor : Real Programmers Humor : Web Humor : GPL-related Humor : OFM Humor : Politically Incorrect Humor : IDS Humor : "Linux Sucks" Humor : Russian Musical Humor : Best Russian Programmer Humor : Microsoft plans to buy Catholic Church : Richard Stallman Related Humor : Admin Humor : Perl-related Humor : Linus Torvalds Related humor : PseudoScience Related Humor : Networking Humor : Shell Humor : Financial Humor Bulletin, 2011 : Financial Humor Bulletin, 2012 : Financial Humor Bulletin, 2013 : Java Humor : Software Engineering Humor : Sun Solaris Related Humor : Education Humor : IBM Humor : Assembler-related Humor : VIM Humor : Computer Viruses Humor : Bright tomorrow is rescheduled to a day after tomorrow : Classic Computer Humor

The Last but not Least Technology is dominated by two types of people: those who understand what they do not manage and those who manage what they do not understand ~Archibald Putt. Ph.D


Copyright © 1996-2021 by Softpanorama Society. www.softpanorama.org was initially created as a service to the (now defunct) UN Sustainable Development Networking Programme (SDNP) without any remuneration. This document is an industrial compilation designed and created exclusively for educational use and is distributed under the Softpanorama Content License. Original materials copyright belong to respective owners. Quotes are made for educational purposes only in compliance with the fair use doctrine.

FAIR USE NOTICE This site contains copyrighted material the use of which has not always been specifically authorized by the copyright owner. We are making such material available to advance understanding of computer science, IT technology, economic, scientific, and social issues. We believe this constitutes a 'fair use' of any such copyrighted material as provided by section 107 of the US Copyright Law according to which such material can be distributed without profit exclusively for research and educational purposes.

This is a Spartan WHYFF (We Help You For Free) site written by people for whom English is not a native language. Grammar and spelling errors should be expected. The site contain some broken links as it develops like a living tree...

You can use PayPal to to buy a cup of coffee for authors of this site

Disclaimer:

The statements, views and opinions presented on this web page are those of the author (or referenced source) and are not endorsed by, nor do they necessarily reflect, the opinions of the Softpanorama society. We do not warrant the correctness of the information provided or its fitness for any purpose. The site uses AdSense so you need to be aware of Google privacy policy. You you do not want to be tracked by Google please disable Javascript for this site. This site is perfectly usable without Javascript.

Last modified: July 30, 2019