I'm trying to use UNIX sockets for inter-thread communication. The program is only intended to run on Linux. To avoid creating the socket files, I wanted to use "abstract" sockets, as documented in unix(7).
However, I don't seem to be able to connect to these sockets. Everything works if I'm using "pathname" sockets, though.
Here is the code (I haven't quoted any error handling, but it's done): thread#1:
int log_socket = socket(AF_LOCAL, SOCK_STREAM, 0);
struct sockaddr_un logaddr;
socklen_t sun_len = sizeof(struct sockaddr_un);
logaddr.sun_family = AF_UNIX;
logaddr.sun_path[0] = 0;
strcpy(logaddr.sun_path+1, "futurama");
bind(log_socket, &logaddr, sun_len);
listen(log_socket, 5);
accept(log_socket, &logaddr, &sun_len);
... // send - receive
thread#2:
struct sockaddr_un tolog;
int sock = socket(AF_LOCAL, SOCK_STREAM, 0);
tolog.sun_family = AF_UNIX;
tolog.sun_path[0] = 0;
strcpy(tolog.sun_path+1, "futurama");
connect(sock, (struct sockaddr*)&tolog, sizeof(struct sockaddr_un));
If all I do in the above code, is change the sun_path to not have leading \0, things work perfect.
strace output:
t1: socket(PF_FILE, SOCK_STREAM, 0) = 0
t1: bind(0, {sa_family=AF_FILE, path=@"futurama"}, 110)
t1: listen(0, 5)
t2: socket(PF_FILE, SOCK_STREAM, 0) = 1
t2: connect(1, {sa_family=AF_FILE, path=@"futurama"}, 110 <unfinished ...>
t2: <... connect resumed> ) = -1 ECONNREFUSED (Connection refused)
t1: accept(0, <unfinished ...>
I know that the connect comes before accept, that's not an issue (I tried making sure that accept() is called before connect(), same result. Also, things are fine if the socket is "pathname" anyway).
The traditional UNIX system calls are blocking. For example: accept() blocks the caller until a connection is present. If no messages space is available at the socket to hold the message to be transmitted, then send() normally blocks.
* abstract: an abstract socket address is distinguished (from a pathname socket) by the fact that sun_path[0] is a null byte ('\0'). The socket's address in this namespace is given by the additional bytes in sun_path that are covered by the specified length of the address structure.
Unix Domain Socket uses a local file on the device. It does not require network ports to be open, instead the Linux system controls who can have access to the file for communication. This is advantageous as you can assign permissions that suit the way you want to set up the system.
socket() creates an endpoint for communication and returns a file descriptor that refers to that endpoint. The file descriptor returned by a successful call will be the lowest-numbered file descriptor not currently open for the process.
While I was posting this question, and re-reading unix(7) man page, this wording caught my attention:
an abstract socket address is distinguished by the fact that sun_path[0] is a null byte (’\0’). All of the remaining bytes in sun_path define the "name" of the socket
So, if I bzero'ed the sun_path before filling in my name into it, things started to work. I figured that's not necessarily straight-forward. Additionally, as rightfully pointed out by @davmac and @StoneThrow, the number of those "remaining bytes" can be reduced by specifying only enough length of the socket address structure to cover the bytes you want to consider as your address. One way to do that is to use SUN_LEN
macro, however, the first byte of the sun_path
will have to be set to !0, as SUN_LEN
uses strlen
.
elaboration
If sun_path[0] is \0, The kernel uses the entirety of the remainder of sun_path as the name of the socket, whether it's \0-terminated or not, so all of that remainder counts. In my original code I would zero the first byte, and then strcpy() the socket name into the sun_path at position 1. Whatever gibberish that was in sun_path when the structure was allocated (especially likely to contain gibberish since it's allocated on the stack), and was included in the length of the socket structure (as passed to the syscalls), counted as the name of the socket, and was different in bind() and connect().
IMHO, strace should fix the way it displays abstract socket names, and display all the sun_path
bytes from 1 to whatever the structure length that was supplied, if sun_path[0]
is 0
The key of making sockets in abstract namespace work is providing the proper length to 'bind' and 'connect' commands. To avoid setting '\0' at the end of the address in sockaddr_un it should be copied with strncpy or alike.
It is already explained in Pawel's answer so I'm just going to give an example.
Server:
int main(int argc, char** argv)
{
//to remove warning for unused variables.
int dummy = argc;
dummy = (int)argv;
int fdServer = 0;
int fdClient = 0;
int iErr = 0;
int n = 0;
socklen_t addr_len = 0;
char buff[1024];
char resp[1024];
const char* const pcSocketName = "/tmp/test";
struct sockaddr_un serv_addr;
//set the structure with 'x' instead of 0 so that we're able
//to see the full socket name by 'cat /proc/net/unix'
//you may try playing with addr_len and see the actual name
//reported in /proc/net/unix
memset(&serv_addr, 'x', sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
serv_addr.sun_path[0] = '\0';
//sizeof(pcSocketName) returns the size of 'char*' this is why I use strlen
strncpy(serv_addr.sun_path+1, pcSocketName, strlen(pcSocketName));
fdServer = socket(PF_UNIX, SOCK_STREAM, 0);
if(-1 == fdServer) {
printf("socket() failed: [%d][%s]\n", errno, strerror(errno));
return(-1);
}
iErr = bind(fdServer, (struct sockaddr*)&serv_addr, offsetof(struct sockaddr_un, sun_path) + 1/*\0*/ + strlen(pcSocketName));
if(0 != iErr) {
printf("bind() failed: [%d][%s]\n", errno, strerror(errno));
return(-1);
}
iErr = listen(fdServer, 1);
if(0 != iErr) {
printf("listen() failed: [%d][%s]\n", errno, strerror(errno));
return(-1);
}
addr_len = sizeof(pcSocketName);
while(1) {
fdClient = accept(fdServer, (struct sockaddr*) &serv_addr, &addr_len);
if(0 >= fdClient) {
printf("accept() failed: [%d][%s]\n", errno, strerror(errno));
return(-1);
}
memset(resp, 0, sizeof(resp));
memset(buff, 0, sizeof(buff));
n = recv(fdClient, buff, sizeof(buff), 0);
if(0 > n) {
printf("recv() failed: [%d][%s]\n", errno, strerror(errno));
return(-1);
}
printf("[client]: %s\n", buff);
sprintf(resp, "echo >> %s", buff);
n = send(fdClient, resp, sizeof(resp), 0);
if(0 > n) {
printf("send() failed: [%d][%s]\n", errno, strerror(errno));
return(-1);
}
printf("[server]: %s\n", resp);
}
close(fdServer);
return(0);
}
Client:
int main(int argc, char** argv) {
//to remove warning for unused variables.
int dummy = argc;
dummy = (int)argv;
int fdClient = 0;
struct sockaddr_un serv_addr;
int iErr = 0;
const char* const pcSocketName = "/tmp/test";
char buff[1024];
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
serv_addr.sun_path[0] = '\0';
strncpy(serv_addr.sun_path+1, pcSocketName, strlen(pcSocketName));
fdClient = socket(PF_UNIX, SOCK_STREAM, 0);
if(-1 == fdClient) {
printf("socket() failed: [%d][%s]\n", errno, strerror(errno));
return(-1);
}
iErr = connect(fdClient, (struct sockaddr*) &serv_addr, offsetof(struct sockaddr_un, sun_path) + 1/*\0*/ + strlen(pcSocketName));
if(0 != iErr) {
printf("connect() failed: [%d][%s]\n", errno, strerror(errno));
return(-1);
}
memset(buff, 0, sizeof(buff));
sprintf(buff, "Hello from client!");
printf("[client]: %s\n", buff);
iErr = send(fdClient, buff, sizeof(buff), 0);
if(0 > iErr){
printf("write() failed: [%d][%s]\n", errno, strerror(errno));
return(-1);
}
iErr = recv(fdClient, buff, sizeof(buff), 0);
if(0 > iErr){
printf("read() failed: [%d][%s]\n", errno, strerror(errno));
return(-1);
}
printf("[server]: %s\n", buff);
return(0);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With