Question

I am trying to modify the original Michael Kerrisk's AF_UNIX SOCK_DGRAM sample client/server program. Please click on the following two links (client, server) for more info. The example code was originally published in his book The Linux Programming Interface, chapter 57. My goal is to replace the 'file socket' definition with 'abstract socket' definition, based on Kerrisk's code example.

Unfortunately, I am not able to establish communication between my version of the client and server. Of course, Kerrisk's version works with a flying colors.

It is obvious to me, that I am doing something fundamentally wrong... but... I could not figured out what it is.

=================== SERVER ===============================

Here is my version of the Kerrisk's code for 'server':

int main(int argc, char *argv[])
{
   struct sockaddr_un svaddr, claddr;
   int sfd, j;
   ssize_t numBytes;
   socklen_t len;
   char buf[BUF_SIZE];

   char *abstract_server;

   sfd = socket(AF_UNIX, SOCK_DGRAM, 0);       /* Create server socket */
   if (sfd == -1)
       errExit("socket");

   abstract_server = "viper_server";
   /* Construct well-known address and bind server socket to it */

   if (remove(abstract_server) == -1 && errno != ENOENT)
       errExit("remove-%s", abstract_server);  

   memset(&svaddr, 0, sizeof(struct sockaddr_un));
   svaddr.sun_family = AF_UNIX;
   strncpy(&svaddr.sun_path[1], abstract_server, strlen(abstract_server));

   if (bind(sfd, (struct sockaddr *) &svaddr, 
     sizeof(sa_family_t) + strlen(abstract_server) + 1) == -1)
       errExit("bind");

   /* Receive messages, convert to uppercase, and return to client */

   for (;;) {
       len = sizeof(struct sockaddr_un);
       numBytes = recvfrom(sfd, buf, BUF_SIZE, 0,
                           (struct sockaddr *) &claddr, &len);
       if (numBytes == -1)
           errExit("recvfrom");

       printf("Server received %ld bytes from %s\n", (long) numBytes,
               claddr.sun_path);

       for (j = 0; j < numBytes; j++)
       buf[j] = toupper((unsigned char) buf[j]);

       if (sendto(sfd, buf, numBytes, 0, (struct sockaddr *) &claddr, len) != numBytes)
           fatal("sendto");
     }
 }

========================= CLIENT ============================

This is my version of the Kerrisk's code for the 'client':

int main(int argc, char *argv[])
{
    struct sockaddr_un svaddr, claddr;
    int sfd, j;
    size_t msgLen;
    ssize_t numBytes;
    char resp[BUF_SIZE];

    char *abstract_client;
    char *abstract_server;

    if (argc < 2 || strcmp(argv[1], "--help") == 0)
        usageErr("%s msg...\n", argv[0]);

    /* Create client socket; bind to unique pathname (based on PID) */

    sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sfd == -1)
        errExit("socket");

    abstract_client = "viper_client";
    abstract_server = "viper_server";

    memset(&claddr, 0, sizeof(struct sockaddr_un));
    claddr.sun_family = AF_UNIX;
    strncpy(&claddr.sun_path[1], abstract_client, strlen(abstract_client)); 

    if (bind(sfd, (struct sockaddr *) &claddr, 
      sizeof(sa_family_t) + strlen(abstract_client) + 1) == -1)
        errExit("bind");

    /* Construct address of server */

    memset(&svaddr, 0, sizeof(struct sockaddr_un));
    svaddr.sun_family = AF_UNIX;
    strncpy(&svaddr.sun_path[1], abstract_server, strlen(abstract_server));

    /* Send messages to server; echo responses on stdout */

    for (j = 1; j < argc; j++) {
        msgLen = strlen(argv[j]);       /* May be longer than BUF_SIZE */

       /* code FIX */
       if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
                 (sizeof(sa_family_t) + strlen(abstract_server) + 1) ) != msgLen) 
              fatal("sendto");

        /* original - non working code - replaced with the code FIX above 
        if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
                sizeof(struct sockaddr_un)) != msgLen)
        {
            fatal("sendto");
        } */

        numBytes = recvfrom(sfd, resp, BUF_SIZE, 0, NULL, NULL);
        /* Or equivalently: numBytes = recv(sfd, resp, BUF_SIZE, 0);
                        or: numBytes = read(sfd, resp, BUF_SIZE); */
        if (numBytes == -1)
            errExit("recvfrom");
        printf("Response %d: %.*s\n", j, (int) numBytes, resp);
    }

    remove(claddr.sun_path);            /* Remove client socket pathname */
    exit(EXIT_SUCCESS);
}

How I am launching the test:

> ./server &
> ./client  aa bb cc
> result:  ERROR: sendto

Any help | suggestion | solution | RTFM is very welcome

Igor

P.S. Since my reputation is less than 10, I can post ONLY 2 links (see Kerrisk's client URL, Kerrrisk's server URL at the top of this post). So, I did a "cut&paste" the Kerrisk's C-code snippet for "How to use abstract sockets". It was published in his book on page 1176, Listing 57-8.

int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_un addr;
    char *str;

    memset(&addr, 0, sizeof(struct sockaddr_un));  /* Clear address structure */
    addr.sun_family = AF_UNIX;                     /* UNIX domain address */

    /* addr.sun_path[0] has already been set to 0 by memset() */

    str = "xyz";        /* Abstract name is "\0abc" */
    strncpy(&addr.sun_path[1], str, strlen(str));

    // In early printings of the book, the above two lines were instead:
    //
    // strncpy(&addr.sun_path[1], "xyz", sizeof(addr.sun_path) - 2);
    //            /* Abstract name is "xyz" followed by null bytes */

    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd == -1)
        errExit("socket");

    if (bind(sockfd, (struct sockaddr *) &addr,
            sizeof(sa_family_t) + strlen(str) + 1) == -1)
        errExit("bind");

    // In early printings of the book, the final part of the bind() call
    // above was instead:
    //        sizeof(struct sockaddr_un)) == -1)

    sleep(60);

    exit(EXIT_SUCCESS);
}
Was it helpful?

Solution 2

Your server binds to a different address than the client uses in sendto(). This is because you calculate the length of the svaddr differently in the client and server.

server: sizeof(sa_family_t) + strlen(abstract_server) + 1)

client: sizeof(struct sockaddr_un)

The unix socket address is the whole given length of the path described in struct sockaddr_un.sun_path. After changing the size calculations to match the client and server work.

EDIT s/abstract_client/abstract_server/ Seems I had the same copy&paste error as the other answer.

OTHER TIPS

if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
            sizeof(struct sockaddr_un)) != msgLen)

The last arg is invalid, the sockaddr_un address length should be

sizeof(sa_family_t) + strlen(abstract_client) + 1;

BTW, you should print out the "errno" value to precisely locate what happened when errorly returned from the glibc API. In the above case, the "errno" value is "111", indicating the server address cannot be reached.


OK, After having read your post once more, I think I got what you mean.

When you use the no abstract unix sockets(sun_path[0] != 0), the server name len is calculated by using strlen(sun_path) in the kernel. So the last arg is not really used.

net/unix/af_unix.c:unix_mkname

if (sunaddr->sun_path[0]) {
    /* ... */
    ((char *)sunaddr)[len] = 0;
    len = strlen(sunaddr->sun_path)+1+sizeof(short);
    return len;
}  

But when you use the abstract unix sockets(sun_path[0] == 0), the server name len is the last arg that your "bind" or "sendto".

net/unix/af_unix.c:unix_mkname:

*hashp = unix_hash_fold(csum_partial(sunaddr, len, 0));

So when you bind the socket to the address with length,

if (bind(sfd, (struct sockaddr *) &svaddr,
    sizeof(sa_family_t) + strlen(abstract_server) + 1) == -1)

Kernel think the server name is an array with that length.

When you try to send to an address with

if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
            sizeof((struct sockaddr)) != msgLen)

Kernel think the server name is an array with that length of the last arg.

Because,

sizeof((struct sockaddr)) != sizeof(sa_family_t) + strlen(abstract_server) + 1

Kernel will not think the address is the same, so sendto returns with errno 111.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top