7

I'm writing an application that listens for UDP packets over a unix domain socket. Consider the following code block.

int sockfd;
struct sockaddr_un servaddr;

sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0);

if(sockfd < 0)
{
    perror("socket() failed");
}

unlink(port);

bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, port);    

if(bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
    perror("bind() failed");
    close(sockfd);
}

int n;
struct sockaddr_un cliaddr;
socklen_t len = sizeof(cliaddr);
discovery_msgs client_message;


bzero(&client_message, sizeof(client_message));
// Wait for a message to be received
n = recvfrom(sock_fd, &client_message, sizeof(client_message), 0, 
    (struct sockaddr *) &cliaddr, &len);

// At this point n = 560, client_message is filled with the expected data 
//len = 0 and cliaddr has no information about the client that sent the data

Now the type of client_message isn't really important, I'm receiving a UDP packet and client_message contains all of the data I expect. The problem begins when I look at cliaddr and len after calling recvfrom. cliaddr is not modified by recvfrom like it normally is with normal network TCP/UDP and len is set to 0 after the call(which means recvfrom wrote no data to &cliaddr). I need the information in cliaddr to be populated with the unix domain path so I can send a response.

What am I doing wrong?

user2278457
  • 315
  • 2
  • 9
  • You could try some error checking. What are the values returned by `socket(),` `bind(),` and `recvfrom()?` – user207421 Nov 03 '14 at 01:41
  • As mentioned in the question, all error checking was done(including socket(), bind(), and recvfrom()) and all calls successfully returned. n = 560 and client_message is populated with expected data from the client's packet. – user2278457 Nov 03 '14 at 01:45
  • You check cliaddr, len immediately after the recvfrom() returns? You have a breakpoint on the next line and a no optimization build? Just wondering if your debugger is telling porkies.. – Martin James Nov 03 '14 at 02:00
  • Correct, I was seeing errors when trying to reuse `cliaddr` to send a response which first tipped me off. Optimization is off, breakpoint is immediately after the call to `recvfrom()`. – user2278457 Nov 03 '14 at 02:06
  • OK, I've got nuthin.. :(( – Martin James Nov 03 '14 at 02:17
  • Note that POSIX [`recvfrom()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html) says: _Not all protocols provide the source address for messages._ Maybe it is reasonable to deduce that Unix domain sockets do not provide a source address; it seems reasonable to me, though I've not found the standard words that formalize that. – Jonathan Leffler Nov 03 '14 at 02:19
  • Is it possible that you need to bind your socket to a port(path) with unix domain if you want your address to be attached with the sent message? I dont see that happening in the Stevens Unix Programming book examples. – user2278457 Nov 03 '14 at 02:39
  • Hmm, maybe. Every time I've ever used UDP I've been able to get the source address. This problem annoys me. It's a good question, the OP has done some debugging AND I CAN'T SEE ANYTHING WRONG!! – Martin James Nov 03 '14 at 02:40
  • scratch that, it looks like the client is binding in the stevens book and it states the exact reason is to send a reply address. Doh – user2278457 Nov 03 '14 at 02:42
  • @user2278457 I've been grasping at straws with this and I've now run out of straw. – Martin James Nov 03 '14 at 02:43
  • I guess you could check the incoming datagram with wireshark and see if it's well-formed with a source address. – Martin James Nov 03 '14 at 02:44
  • Okay, it seemingly started working, the solution is indeed binding the socket on the client side otherwise the transient port created for sending the UDP packet immediately disappears after sendto(). See Stevens Network Programming page 419 or see this for an example: http://www.libpix.org/unp/unixdgcli01_8c_source.html – user2278457 Nov 03 '14 at 03:02

1 Answers1

10

The solution is binding the socket on the client side when using Unix domain sockets. Otherwise the transient pathname created for sending the UDP packet immediately disappears after sendto(), which explains why the client's address information is not available on the server side.

See Stevens Network Programming page 419 or see this for an example client implementation that solves this issue: libpix.org/unp/unixdgcli01_8c_source.html

#include    "unp.h"

int
main(int argc, char **argv)
{
    int                 sockfd;
    struct sockaddr_un  cliaddr, servaddr;

    sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0);

    bzero(&cliaddr, sizeof(cliaddr));       /* bind an address for us */
    cliaddr.sun_family = AF_LOCAL;
    strcpy(cliaddr.sun_path, tmpnam(NULL));

    Bind(sockfd, (SA *) &cliaddr, sizeof(cliaddr));

    bzero(&servaddr, sizeof(servaddr)); /* fill in server's address */
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXDG_PATH);

    dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr));

    exit(0);
}

Note: unp.h defines Bind() which is simply bind() with some error checking(commonly used throughout Stevens Network Programming). In the same manner, (SA *) is the equivalent to (struct sockaddr *).

user207421
  • 305,947
  • 44
  • 307
  • 483
user2278457
  • 315
  • 2
  • 9
  • I use same way in my program. But what server should do to close peer fd if using sock_dgram unix socket. Is it not necessary like udp? – robert Feb 25 '18 at 10:18