Retrieve PID from the packet in Unix domain socket – a complete use case for recvmsg()/sendmsg()

The original question was how to retrieve the PID of the packet (sender) in the Unix domain socket. As titled, the answer is recvmsg()/sendmsg(). However, the most useful information I could find online is Michael’s man7.org. People keep talking about send()/recv() or sendto()/recvfrom() but ignoring recvmsg()/sendmsg(), which we will talk about in the post. At the end, a working datagram Unix socket server/client will be provided. Thank Michael’s man7.org and his excellent book tlpi to make this happen.

0. Why recvmsg()/sendmsg()?

There are mainly 2 reasons why recvmsg()/sendmsg() was invented in my opinion. The 1st reason/target is to simplify the socket programming itself. Run “man sendmsg”, you will find sendmsg() has the least number of parameters (3) comparing to send() (4) and sendto()(7). Rather than buffer, length and etc., we only need to deal with one parameter, struct msghdr, except socket fd. You can imagine now where have a place to put all other parameters. It looks nice even though it does not mean the socket programming itself is easier. (Actually, manipulating struct msghdr is not trivial…)

So why do we need this struct msghdr? The second reason/target of recvmsg()/sendmsg() is to provide “extra” functionalities, which are called¬†ancillary. The struct cmsghdr is used to implement these “extra” functionalities. As you might have found already, the struct cmsghdr could be a member of the struct msghdr. One of these “extra” functionalities is to retrieve the credential information from the peer socket, which includes the PID, GID, UID. Yep, we do not need to hack the kernel to retrieve the PID of the packet sender:)

1. Unix domain socket server

Below is the implementation of an Unix domain socket server, support blocking/non-blocking recv and PID information extraction of the peer socket. Run “man cmsg” to find out how to manipulate struct cmsghdr. There is nothing more helpful I could tell here than reading the (damn) code yourself.

/*
 * unix_server.c
 * A UNIX socket server used to retrieve the creditial of peer socket.
 * NOTE: unix_server uses UDP.
 * Reference: man7.org/tlpi
 * Feb 16, 2015
 * root@davejingtian.org
 * http://davejingtian.org
 */
#define _GNU_SOURCE             /* To get SCM_CREDENTIALS definition from
                                   <sys/sockets.h> */
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */
#include <string.h>     /* Commonly used string-handling functions */

#define SOCK_PATH "/home/daveti/c/unix_server_sock"

struct my_data {
	int index;
	char msg[32];
};

static int non_blocking = 1;

int main(int argc, char *argv[])
{
    struct msghdr msgh;
    struct iovec iov;
    struct ucred *ucredp, ucred;
    int lfd, sfd, optval, opt;
    ssize_t nr, ns;
    union {
        struct cmsghdr cmh;
        char   control[CMSG_SPACE(sizeof(struct ucred))];
                        /* Space large enough to hold a ucred structure */
    } control_un;
    struct cmsghdr *cmhp;
    socklen_t len;
    struct sockaddr_un addr;
    struct sockaddr_un client_addr;
    struct my_data data;


    /* Create socket bound to well-known address */
    if (remove(SOCK_PATH) == -1 && errno != ENOENT) {
	printf("Error: remove failed\n");
	return -1;
    }

    /* Build the UNIX socket using UDP */
    printf("Receiving via datagram socket\n");
    if (strlen(SOCK_PATH) >= sizeof(addr.sun_path)-1) {
	printf("Error: path length exceeds the max\n");
        return -1;
    }

    memset(&addr, 0x0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path)-1);

    sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sfd == -1) {
	printf("Error: socket failed [%s]\n", strerror(errno));
        return -1;
    }

    if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) {
	printf("Error: bind failed [%s]\n", strerror(errno));
        close(sfd);
        return -1;
    }

    /* We must set the SO_PASSCRED socket option in order to receive
       credentials */

    optval = 1;
    if (setsockopt(sfd, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) == -1) {
	printf("Error: setsockopt failed [%s]\n", strerror(errno));
	return -1;
    }

    /* Set 'control_un' to describe ancillary data that we want to receive */

    control_un.cmh.cmsg_len = CMSG_LEN(sizeof(struct ucred));
    control_un.cmh.cmsg_level = SOL_SOCKET;
    control_un.cmh.cmsg_type = SCM_CREDENTIALS;

    /* Set 'msgh' fields to describe 'control_un' */

    msgh.msg_control = control_un.control;
    msgh.msg_controllen = sizeof(control_un.control);

    /* Set fields of 'msgh' to point to buffer used to receive (real)
       data read by recvmsg() */

    msgh.msg_iov = &iov;
    msgh.msg_iovlen = 1;
    iov.iov_base = &data;
    iov.iov_len = sizeof(data);
    memset(&data, 0x0, sizeof(data));

    /* Save the client address for sendmsg */
    memset(&client_addr, 0x0, sizeof(client_addr));
    msgh.msg_name = (void *)&client_addr;
    msgh.msg_namelen = sizeof(client_addr);

    /* Receive real plus ancillary data */
    if (non_blocking) {
	/* NonBlocking I/O */
	do {
	   nr = recvmsg(sfd, &msgh, MSG_DONTWAIT);
	   if (nr <= 0) {
		printf("non-blocking wait again [%s]\n", strerror(errno));
		sleep(2);
	   } else {
		break;
	   }
	} while (nr <= 0);
    } else {
	/* Blocking I/O */
	nr = recvmsg(sfd, &msgh, 0);
	if (nr == -1) {
	    printf("Error: recvmsg failed [%s]\n", strerror(errno));
	    return -1;
	}
    }
    printf("recvmsg() returned %ld\n", (long)nr);

    /* Dump the client sock path */
    printf("Debug: client sock path [%s]\n", client_addr.sun_path);

    if (nr > 0)
        printf("Received data: index=[%d], msg=[%s]\n",
		data.index, data.msg);

    /* Extract credentials information from received ancillary data */

    cmhp = CMSG_FIRSTHDR(&msgh);
    if (cmhp == NULL || cmhp->cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
        printf("Error: bad cmsg header / message length\n");
	return -1;
    }
    if (cmhp->cmsg_level != SOL_SOCKET) {
        printf("Error: cmsg_level != SOL_SOCKET\n");
	return -1;
    }
    if (cmhp->cmsg_type != SCM_CREDENTIALS) {
        printf("Error: cmsg_type != SCM_CREDENTIALS\n");
	return -1;
    }

    ucredp = (struct ucred *) CMSG_DATA(cmhp);
    printf("Received credentials pid=[%ld], uid=[%ld], gid=[%ld]\n",
		(long) ucredp->pid, (long) ucredp->uid, (long) ucredp->gid);

    /* Send an ACK to the client */
    memset(&data, 0x0, sizeof(data));
    data.index = 888;
    snprintf(data.msg, 32, "%s", "ACK");
    msgh.msg_name = (void *)&client_addr;
    msgh.msg_namelen = sizeof(client_addr);
    msgh.msg_control = NULL;
    msgh.msg_controllen = 0;
    printf("Debug: msg.name [%p], msg.namelen [%d], path [%s]\n",
	msgh.msg_name, msgh.msg_namelen, client_addr.sun_path);
    ns = sendmsg(sfd, &msgh, 0);
    if (ns == -1) {
	printf("Error: sendmsg failed [%s]\n", strerror(errno));
	return -1;
    }
    printf("sendmsg() returned %ld\n", (long) ns);

    return 0;
}

2. Unix domain socket client

Below is the implementation of an Unix domain socket server, support connection/non-connection. Again, study how to manipulate struct msghdr and cmsghdr in this example.

/*
 * unix_client.c
 * A UNIX socket client used to unix_server.
 * NOTE: unix_server uses UDP.
 * Reference: man7.org/tlpi
 * Feb 16, 2015
 * root@davejingtian.org
 * http://davejingtian.org
 */
#define _GNU_SOURCE             /* To get SCM_CREDENTIALS definition from
                                   <sys/sockets.h> */
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */
#include <string.h>     /* Commonly used string-handling functions */

#define SERVER_SOCK_PATH "/home/daveti/c/unix_server_sock"
#define CLIENT_SOCK_PATH "/home/daveti/c/unix_client_sock"

struct my_data {
	int index;
	char msg[32];
};

static int non_connection = 1;

int main(int argc, char *argv[])
{
    struct msghdr msgh;
    struct iovec iov;
    int sfd, opt;
    ssize_t ns, nr;
    union {
        struct cmsghdr cmh;
        char   control[CMSG_SPACE(sizeof(struct ucred))];
                        /* Space large enough to hold a ucred structure */
    } control_un;
    struct cmsghdr *cmhp;
    struct sockaddr_un addr;
    struct sockaddr_un server_addr;
    struct my_data data;

    /* Create socket bound to well-known address */
    if (remove(CLIENT_SOCK_PATH) == -1 && errno != ENOENT) {
        printf("Error: remove failed\n");
        return -1;
    }

    /* Build the UNIX socket using UDP */
    if (strlen(CLIENT_SOCK_PATH) >= sizeof(addr.sun_path)-1) {
        printf("Error: path length exceeds the max\n");
        return -1;
    }

    memset(&addr, 0x0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, CLIENT_SOCK_PATH, sizeof(addr.sun_path)-1);

    sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sfd == -1) {
        printf("Error: socket failed [%s]\n", strerror(errno));
        return -1;
    }

    if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) {
        printf("Error: bind failed [%s]\n", strerror(errno));
        close(sfd);
        return -1;
    }


    /* Build the data */
    memset(&data, 0x0, sizeof(data));
    data.index = 777;
    snprintf(data.msg, 32, "%s", "This is not Boeing 777!");

    /* On Linux, we must transmit at least 1 byte of real data in
       order to send ancillary data */

    msgh.msg_iov = &iov;
    msgh.msg_iovlen = 1;
    iov.iov_base = &data;
    iov.iov_len = sizeof(data);

    if (!non_connection) {
	/* Don't need to specify destination address, because we use
	connect() below */
	msgh.msg_name = NULL;
	msgh.msg_namelen = 0;
    }

    /* Don't construct an explicit credentials structure. (It
       is not necessary to do so, if we just want the receiver to
       receive our real credentials.) */

    printf("Not explicitly sending a credentials structure\n");
    msgh.msg_control = NULL;
    msgh.msg_controllen = 0;

    /* Set the server address */
    memset(&server_addr, 0x0, sizeof(struct sockaddr_un));
    server_addr.sun_family = AF_UNIX;
    strncpy(server_addr.sun_path, SERVER_SOCK_PATH, sizeof(server_addr.sun_path)-1);

    if (non_connection) {
	printf("Info: no connection\n");
	msgh.msg_name = (void *)&server_addr;
	msgh.msg_namelen = sizeof(server_addr);
    } else {
	/* Connect the socket */
	if (connect(sfd, (struct sockaddr *) &server_addr,
                sizeof(struct sockaddr_un)) == -1) {
	    printf("Error: connect failed [%s]\n", strerror(errno));
	    close(sfd);
	    return -1;
	}
    }

    /* Send the msg */
    ns = sendmsg(sfd, &msgh, 0);
    if (ns == -1) {
	printf("Error: sendmsg failed [%s]\n", strerror(errno));
	return -1;
    }
    printf("sendmsg() returned %ld\n", (long) ns);

    /* Get PID before exit */
    printf("PID=[%d]\n", getpid());

    /* Wait for the ACK from the server */
    //msgh.msg_name = (void *)server_addr;
    //msgh.msg_namelen = sizeof(server_addr);
    nr = recvmsg(sfd, &msgh, 0);
    if (nr == -1) {
	printf("Error: recvmsg failed [%s]\n", strerror(errno));
	return -1;
    }
    printf("recvmsg() returned %ld\n", (long)nr);

    if (nr > 0)
	printf("Received data: index=[%d], msg=[%s]\n",
		data.index, data.msg);

    return 0;
}

3. Testing

[daveti@daveti c]$ ./unix_server
Receiving via datagram socket
non-blocking wait again [Resource temporarily unavailable]
non-blocking wait again [Resource temporarily unavailable]
non-blocking wait again [Resource temporarily unavailable]
non-blocking wait again [Resource temporarily unavailable]
recvmsg() returned 36
Debug: client sock path [/home/daveti/c/unix_client_sock]
Received data: index=[777], msg=[This is not Boeing 777!]
Received credentials pid=[19206], uid=[1000], gid=[1000]
Debug: msg.name [0x7fff984684b0], msg.namelen [110], path [/home/daveti/c/unix_client_sock]
sendmsg() returned 36

[daveti@daveti c]$ ./unix_client
Not explicitly sending a credentials structure
Info: no connection
sendmsg() returned 36
PID=[19206]
recvmsg() returned 36
Received data: index=[888], msg=[ACK]

4. What else?

Have you heard of recvmmsg()/sendmmsg()? Run your man to figure out what they are. In a word, a new struct mmsghdr is used in these functions to control multiple msg sending/recving the same time.

5. Reference:

[1]The Linux Programming Interface (tlpi)
[2]http://man7.org/tlpi/code/online/dist/sockets/scm_cred_recv.c.html

About daveti

Interested in kernel hacking, compilers, machine learning and guitars.
This entry was posted in OS, Programming and tagged , , , , , , . Bookmark the permalink.

One Response to Retrieve PID from the packet in Unix domain socket – a complete use case for recvmsg()/sendmsg()

  1. Pingback: Linux kernel hacking – support SO_PEERCRED for local TCP socket connections | davejingtian.org

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s