0

I've followed the code in this answer to create a pair of programs which send and receive datagrams via a Unix socket.

What's awkward about this: On the side which creates the first socket (i.e. the "server"), I can't use calls to send, recv, read or write because there is no destination set (those calls fail with "Destination address required" error.

I've tried working around this by adding an initial call to recvfrom and using the address given back through there, but it never has the correct value (on OSX at least). It also doesn't work to use sendto since we don't know the client address.

The way which I have got it working is roughly following this process:

  1. Start server program, which:
    1. Calls socket and bind to create the server socket.
    2. It waits here.
  2. Start client program, which:
    1. Calls socket and bind to create the client socket.
    2. It knows the path to the server socket and calls connect.
    3. This side is now set up correctly.
  3. Server program:
    1. Accepts the path to the client socket via stdin
    2. Copies the path to a struct sockaddr_un and uses that to call connect (as in the linked answer).

This is pretty awkward! If I was doing this with SOCK_STREAM sockets, I could use listen and accept; the flow is much more straight-forward without the server needing to know the client's socket path.

Is there a more elegant way of getting these sockets connected?

arrtchiu
  • 1,076
  • 1
  • 10
  • 23
  • It sounds like you want a stream socket. Why are you using a datagram socket? If you use a stream socket, it will be very similar to TCP (`AF_INET`) except for the bind address. – Gil Hamilton Sep 07 '18 at 03:26
  • Agreed - though since the data being pushed through isn't stream-oriented, I'd need to encapsulate by adding a length prefix. I'd rather not do that since it requires modifying all read/write calls on both programs. Currently the programs assume 1 write = 1 read. – arrtchiu Sep 07 '18 at 03:54

3 Answers3

0

SOCK_DGRAM (UDP) sockets are "Connectionless", so you cannot "connect" the two sockets. They only send packets to the designated destination address and the client simply captures it. So you'll to first decide if you are going to use a SOCK_DGRAM (UDP) or SOCK_STREAM (TCP).

If you are using UDP sockets the client side socket need not connect, you simply sendto the destination address (Server in this case) after creating and binding.

So if you need a dedicated connected connection you are better off using TCP socket. Or if you are using this over the internet the closest thing you can find for UDP is Hole punching.

  • // "you cannot "connect" the two sockets " -> This is incorrect - you can, and once they are connected there are several advantages, such as being able to use `read`, `write`, `send` and `recv`. Additionally, in the case of UDP it makes some implementations aware of related ICMP messages https://stackoverflow.com/a/42022953/527419 // "you simply `sendto` the destination address" -> This is correct, however, without first calling `connect` from the "server"'s side, it doesn't know the address of the client. The server needs to discover this information somehow. – arrtchiu Sep 07 '18 at 06:50
  • @arrtchiu calling connect on connectionless type protocol such as UDP only sets its default `sendto` IP value to the specified one, so you dont have to call `sendto` everytime with the destination address. This doesnt mean that they build a secure [connection](http://man7.org/linux/man-pages/man2/connect.2.html) between the two sockets like TCP does. – Dunura Dulshan Sep 07 '18 at 07:31
  • `connect` requires destination IP. If the server does not know the address of the client, you cant call connet anyway. You dont need `connect` to receive packets from the client. As long server is in listening(`recvfrom`) connectonless packets send by client captured. – Dunura Dulshan Sep 07 '18 at 07:31
0

One way to solve the problem:

Your messages probably have common header. Add address information of sender to the header.

Then your server can respond to the correct client by using sendto.

Pseudo example:

void handle_my_message(const my_message_t *msg)
{
   struct sockaddr_un client_address = msg->header.sender;
   my_message_response_t response_msg;  

   ... handle the message and fill the response...

   // Send response message
   sendto(fd, &response_msg, sizeof(response_msg), 0,
          (struct sockaddr*)&client_address, sizeof(client_address));   
}

This way your server programs does not need to keep book of connections.

Instead of struct sockaddr_un in the header you maybe should use something smaller and more portable format, that can be converted to struct sockaddr_un.

SKi
  • 8,007
  • 2
  • 26
  • 57
0

You should also bind the client side socket to an address. If the client socket is bound (i.e. has its own name), then you don't need an out-of-band mechanism to communicate the client's address to the server. The OS sends it along with each datagram.

Sample code for client (in python because it's quick and easy to prototype -- should be easy to translate to the equivalent C):

#!/usr/bin/env python3
import os
import socket

server_addr = "/tmp/ux_server"
client_addr = "/tmp/ux_client"
if os.path.exists(client_addr):
    os.remove(client_addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.bind(client_addr)
for n in range(5):
    data = "Hello " + str(n)
    data = data.encode()
    print("Sent '{}' to {}".format(data, server_addr))
    sock.sendto(data, server_addr)
    data, addr = sock.recvfrom(16000)
    print("Got '{}' back from {}".format(data, addr))

Furthermore, you can execute a connect on the client side. Since it's a datagram socket, that doesn't actually create a connection between the two but it does fix the address of the server endpoint, relieving you of the need to provide the server address on every send (i.e. you can use simple send rather than sendto).

For completeness, here's the echo server corresponding to the above:

#!/usr/bin/env python3
import os
import socket

server_addr = "/tmp/ux_server"
if os.path.exists(server_addr):
    # Bind will fail if endpoint exists
    os.remove(server_addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.bind(server_addr)
while True:
    data, addr = sock.recvfrom(16000)
    print("Got '{}' from {}".format(data, addr))
    sock.sendto(data, addr)

EDIT

Hmm... I see now that you say you're already binding the client socket, and then connecting to the server side. But that means you simply need to have the server use recvfrom once initially to obtain the client's address. The OS will send the address along and you don't need to use an out-of-band mechanism.

The downside to connecting the socket is that if the client goes down, the server won't know that unless it attempts to send, but the client won't be able to reconnect because the server's socket is already connected. That's why datagram servers typically use recvfrom and sendto for all messages.

Updated server with initial recvfrom followed by connect:

#!/usr/bin/env python3
import os
import socket

server_addr = "/tmp/ux_server"
if os.path.exists(server_addr):
    # Bind will fail if endpoint exists
    os.remove(server_addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.bind(server_addr)
client_addr = None
while True:
    if client_addr:
        data = sock.recv(16000)
    else:
        data, client_addr = sock.recvfrom(16000)
        sock.connect(client_addr)
    print("Got '{}' from {}".format(data, client_addr))
    sock.send(data)

Updated client with connected socket.

#!/usr/bin/env python3
import os
import socket

server_addr = "/tmp/ux_server"
client_addr = "/tmp/ux_client"
if os.path.exists(client_addr):
    os.remove(client_addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.bind(client_addr)
sock.connect(server_addr)
for n in range(5):
    data = ("Hello " + str(n)).encode()
    print("Sent '{}'".format(data))
    sock.send(data)
    data = sock.recv(16000)
    print("Got '{}' back".format(data))
Gil Hamilton
  • 11,973
  • 28
  • 51