Thursday, January 10, 2013

Unix Domain Socket with Abstract Socket Address


The socket address is abstract when sun_path[0] is a null byte ('\0'). The rest of sun_path is used to name the socket in the abstract namespace which is independent of the file system. This feature is only supported in Linux and it is not portable.

With the abstract socket address, you don't need to maintain the socket file (e.g. unlink) in the file system. And there is no file permissions control over it.

Example of using the sockets:

server.cpp

#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int socket_fd;
    struct sockaddr_un server_address, client_address;
    int bytes_received, bytes_sent, integer_buffer;
    socklen_t address_length = sizeof(struct sockaddr_un);

    if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
    {
        perror("server: socket");
        return -1;
    }
   
    memset(&server_address, 0, sizeof(server_address));
    server_address.sun_family = AF_UNIX;
    strcpy(server_address.sun_path, "#UdsServer");
    server_address.sun_path[0] = 0;
   
    if (bind(socket_fd,
             (const struct sockaddr *) &server_address,
             address_length) < 0)
    {
        close(socket_fd);
        perror("server: bind");
        return -1;
    }

    for (;;)
    {
        bytes_received = recvfrom(socket_fd,
                                  &integer_buffer,
                                  sizeof(integer_buffer),
                                  0,
                                  (struct sockaddr *) &client_address,
                                  &address_length);

        if(bytes_received != sizeof(integer_buffer))
        {
            printf("Error: recvfrom - %d.\n", bytes_received);
        } else {
            printf("received: %d.\n", integer_buffer);

            integer_buffer += 10;

            bytes_sent = sendto(socket_fd,
                                &integer_buffer,
                                sizeof(integer_buffer),
                                0,
                                (struct sockaddr *) &client_address,
                                address_length);
        }
    }

    close(socket_fd);

    return 0;
}


client.cpp

#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int socket_fd;
    struct sockaddr_un server_address, client_address;
    int bytes_received, bytes_sent, integer_buffer;
    socklen_t address_length = sizeof(struct sockaddr_un);

    if((socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
    {
        perror("client: socket");
        return -1;
    }
         
    memset(&client_address, 0, sizeof(client_address));
    client_address.sun_family = AF_UNIX;
    strcpy(client_address.sun_path, "#UdsClient");
    client_address.sun_path[0] = 0;

    if(bind(socket_fd,
            (const struct sockaddr *) &client_address,
            address_length) < 0)
    {
        perror("client: bind");
        return -1;
    }

    memset(&server_address, 0, sizeof(server_address));
    server_address.sun_family = AF_UNIX;
    strcpy(server_address.sun_path, "#UdsServer");
    server_address.sun_path[0] = 0;

    integer_buffer = 1;

    for (;;)
    {
       bytes_sent = sendto(socket_fd,
                           &integer_buffer,
                           sizeof(integer_buffer),
                           0,
                           (struct sockaddr *) &server_address,
                           address_length);

       bytes_received = recvfrom(socket_fd,
                                 &integer_buffer,
                                 sizeof(integer_buffer),
                                 0,
                                 (struct sockaddr *) &server_address,
                                 &address_length);

       if (bytes_received != sizeof(integer_buffer))
       {
           printf("Error: recvfrom - %d.\n", bytes_received);
           return -1;
       }

       printf("received: %d\n", integer_buffer);

       integer_buffer += 1;

       sleep(10);
    }

    close(socket_fd);

    return 0;
}


Run them, then run the command
netstat -ax | grep DGRAM
and you can see how the sockets look like.

unix  2      [ ]         DGRAM                    9911     @UdsClient
unix  2      [ ]         DGRAM                    9888     @UdsServer

2 comments:

JayPare said...

I know this is an old post, but it still helped me a lot while using abstract UDS and I'm pretty sure a lot of people get here when Googling "Abstract UDS example".

I'm not sure, but I think your client socket is actually named "@UdsClient " (didn't count the number of " " but there should be (108 - strlen("#UdsClient")) space at the end of your socket name). Same goes for the server socket.

The thing is with abstract socket, null bytes in sun_path have no special significance, so they are not acting as a termination character.

They only way for bind function to know what's the length of sun_path when using abstract socket, is with its addrlen argument.

Since your addrlen is set to sizeof(struct sockaddr_un), bind thinks you sun_path length is 108 (sockaddr_un implementation of sun_path is char sun_path[108]) and creates a socket with "@UdsClient [...]".

I've tested your code on SLES12 which is representing all the null-byte character of UDS socket as @ in net stat command and I got the folloging:
unix 2 [ ] DGRAM 943601926 @UdsClient@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

I think the correct way of setting address_length for UDS Sockets is to set it to sizeof(sun_family) + that actual length of you path (strlen("#UdsClient") for example). This way, your socket will be name "@UdsClient", without the null byte at the end.

Doing this is outputting the following on SLES12:
unix 2 [ ] DGRAM 943679252 @UdsClient

BTW, I'm not pretending to be an expert in UDS and I learned a lot with this blog. Let me know what you think and feel free to reach me out if you want some more information.

JayPare said...

I know this is an old post, but it still helped me a lot while using abstract UDS and I'm pretty sure a lot of people get here when Googling "Abstract UDS example".

I'm not sure, but I think your client socket is actually named "@UdsClient " (didn't count the number of " " but there should be (108 - strlen("#UdsClient")) space at the end of your socket name). Same goes for the server socket.

The thing is with abstract socket, null bytes in sun_path have no special significance, so they are not acting as a termination character.

They only way for bind function to know what's the length of sun_path when using abstract socket, is with its addrlen argument.

Since your addrlen is set to sizeof(struct sockaddr_un), bind thinks you sun_path length is 108 (sockaddr_un implementation of sun_path is char sun_path[108]) and creates a socket with "@UdsClient [...]".

I've tested your code on SLES12 which is representing all the null-byte character of UDS socket as @ in net stat command and I got the folloging:
unix 2 [ ] DGRAM 943601926 @UdsClient@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

I think the correct way of setting address_length for UDS Sockets is to set it to sizeof(sun_family) + that actual length of you path (strlen("#UdsClient") for example). This way, your socket will be name "@UdsClient", without the null byte at the end.

Doing this is outputting the following on SLES12:
unix 2 [ ] DGRAM 943679252 @UdsClient

BTW, I'm not pretending to be an expert in UDS and I learned a lot with this blog. Let me know what you think and feel free to reach me out if you want some more information.

 
Get This <