Automated naming of AF_UNIX local datagram sockets?
Asked Answered
E

4

7

I'm implementing a simple service using datagrams over unix local sockets (AF_UNIX address family, i.e. not UDP). The server is bound to a public address, and it receives requests just fine. Unfortunately, when it comes to answering back, sendto fails unless the client is bound too. (the common error is Transport endpoint is not connected).

Binding to some random name (filesystem-based or abstract) works. But I'd like to avoid that: who am I to guarantee the names I picked won't collide?

The unix sockets' stream mode documentation tell us that an abstract name will be assigned to them at connect time if they don't have one already. Is such a feature available for datagram oriented sockets?

Elspeth answered 18/9, 2008 at 17:18 Comment(0)
C
4

I assume that you are running Linux; I don't know if this advice applies to SunOS or any UNIX.

First, the answer: after the socket() and before the connect() or first sendto(), try adding this code:

struct sockaddr_un me;
me.sun_family = AF_UNIX;
int result = bind(fd, (void*)&me, sizeof(short));

Now, the explanation: the the unix(7) man page says this:

When a socket is connected and it doesn’t already have a local address a unique address in the abstract namespace will be generated automatically.

Sadly, the man page lies.

Examining the Linux source code, we see that unix_dgram_connect() only calls unix_autobind() if SOCK_PASSCRED is set in the socket flags. Since I don't know what SOCK_PASSCRED is, and it is now 1:00AM, I need to look for another solution.

Examining unix_bind, I notice that unix_bind calls unix_autobind if the passed-in size is equal to "sizeof(short)". Thus, the solution above.

Good luck, and good morning.

Rob

Coolie answered 20/9, 2008 at 6:15 Comment(1)
Great analysis, Rob! Thanks a lot! There seems to be different sources to the manpages: at work (some old SuSE), they explicitely restrict the autobind-on-connect effect to stream sockets. At home, I've got the same sentence as you. In the long run, I'll remember this as binding to an empty path.Elspeth
K
6

The unix(7) man page I referenced had this information about autobind UNIX sockets:

If a bind(2) call specifies addrlen as sizeof(sa_family_t), or the SO_PASSCRED socket option was specified for a socket that was not explicitly bound to an address, then the socket is autobound to an abstract address.

This is why the Linux kernel checks the address length is equal to sizeof(short) because sa_family_t is a short. The other unix(7) man page referenced by Rob's great answer says that client sockets are always autobound on connect, but because SOCK_DGRAM sockets are connectionless (despite calling connect on them) I believe this only applies to SOCK_STREAM sockets.

Also note that when supplying your own abstract namespace socket names, 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.

struct sockaddr_un me;
const char name[] = "\0myabstractsocket";
me.sun_family = AF_UNIX;
// size-1 because abstract socket names are not null terminated
memcpy(me.sun_path, name, sizeof(name) - 1);
int result = bind(fd, (void*)&me, sizeof(me.sun_family) + sizeof(name) - 1);

sendto() should likewise limit the address length, and not pass sizeof(sockaddr_un).

Kizer answered 15/12, 2011 at 16:58 Comment(0)
C
4

I assume that you are running Linux; I don't know if this advice applies to SunOS or any UNIX.

First, the answer: after the socket() and before the connect() or first sendto(), try adding this code:

struct sockaddr_un me;
me.sun_family = AF_UNIX;
int result = bind(fd, (void*)&me, sizeof(short));

Now, the explanation: the the unix(7) man page says this:

When a socket is connected and it doesn’t already have a local address a unique address in the abstract namespace will be generated automatically.

Sadly, the man page lies.

Examining the Linux source code, we see that unix_dgram_connect() only calls unix_autobind() if SOCK_PASSCRED is set in the socket flags. Since I don't know what SOCK_PASSCRED is, and it is now 1:00AM, I need to look for another solution.

Examining unix_bind, I notice that unix_bind calls unix_autobind if the passed-in size is equal to "sizeof(short)". Thus, the solution above.

Good luck, and good morning.

Rob

Coolie answered 20/9, 2008 at 6:15 Comment(1)
Great analysis, Rob! Thanks a lot! There seems to be different sources to the manpages: at work (some old SuSE), they explicitely restrict the autobind-on-connect effect to stream sockets. At home, I've got the same sentence as you. In the long run, I'll remember this as binding to an empty path.Elspeth
F
1

A bit of a late response, but for whomever finds this using google as I did. Rob Adam's answer helped me get the 'real' answer to this: simply use set (level SO_SOCKET, see man 7 unix) to set SO_PASSCRED to 1. No need for a silly bind.

I used this in PHP, but it doesn't have SO_PASSCRED defined (stupid PHP). It does still work, though, if you define it yourself. On my computer it has the value of 16, and I reckon that it will work quite portably.

Freehanded answered 16/8, 2009 at 12:35 Comment(1)
What's so silly about a bind? Naming sockets is done with it, it's not a huge stretch that the autonaming would be forced with it too. From a documentation (implying maintenance) point of view, SO_PASSCRED is probably better. For intuitiveness, though, SO_PASSCRED is the silly one.Elspeth
L
-1

I'm not so sure I understand your question completely, but here is a datagram implementation of an echo server I just wrote. You can see the server is responding to the client on the same IP/PORT it was sent from.

Here's the code

First, the server (listener)

from socket import *
import time
class Listener:
    def __init__(self, port):
        self.port = port
        self.buffer = 102400

    def listen(self):

        sock = socket(AF_INET, SOCK_DGRAM)
        sock.bind(('', self.port))

        while 1:
            data, addr = sock.recvfrom(self.buffer)
            print "Received: " + data
            print "sending to %s" % addr[0]
            print "sending data %s" % data
            time.sleep(0.25)
            #print addr # will tell you what IP address the request came from and port
            sock.sendto(data, (addr[0], addr[1]))
            print "sent"
        sock.close()

if __name__ == "__main__":
    l = Listener(1975)
    l.listen()

And now, the Client (sender) which receives the response from the Listener

from socket import *
from time import sleep
class Sender:
    def __init__(self, server):
       self.port = 1975
       self.server = server
       self.buffer = 102400

    def sendPacket(self, packet):
        sock = socket(AF_INET, SOCK_DGRAM)
        sock.settimeout(10.75)


        sock.sendto(packet, (self.server, int(self.port)))

        while 1:
            print "waiting for response"
            data, addr = sock.recvfrom(self.buffer)
            sock.close()
            return data



if __name__ == "__main__":
        s = Sender("127.0.0.1")
        response = s.sendPacket("Hello, world!")
        print response
Lecce answered 18/9, 2008 at 18:7 Comment(1)
The question was about unix sockets, not internet ones. (i.e., AF_UNIX address family, not AF_INET). Thanks for your answer, I'll edit the question and make that explicit.Elspeth

© 2022 - 2024 — McMap. All rights reserved.