3

I'm using an abstract Unix socket for passing data between a C and Go program. The C program is creating the socket and the Go program connects to it. The issue is the Go program fails to connect to the socket, and I receive the following error message:

UDS connection failed: dial unixgram @uds-js: connect: connection refused

Here is the C program:

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

/* Buffer size for the receive socket */
#define BUFFER_SIZE 4096

/* The abstract Unix domain socket address name */
#define UDS_ADDRESS_NAME "#uds-js"

int main() {
    int socket_fd;
    int bytes_received;
    char buffer[BUFFER_SIZE];
    struct sockaddr_un server_address;
    struct sockaddr_un client_address;
    socklen_t address_length = sizeof(struct sockaddr_un);

    /* Create local unix socket */
    if ( ( socket_fd = socket ( AF_UNIX, SOCK_DGRAM, 0 ) ) < 0 ) {
        printf ( "socket error\n" );
        return 1;
    }

    /* Set an abstract socket address */
    memset( &server_address, 0, sizeof(server_address) );
    server_address.sun_family = AF_UNIX;
    strcpy( server_address.sun_path, UDS_ADDRESS_NAME );
    server_address.sun_path[0] = '\0';

    /* Bind socket to address */
    if ( bind ( socket_fd, (const struct sockaddr *) &server_address, address_length ) < 0 ) {
        close ( socket_fd );
        printf ( "socket bind error\n" );
        return 1;
    }
    bytes_received =
            recvfrom(
                socket_fd,
                &buffer,
                BUFFER_SIZE,
                0,
                (struct sockaddr *) &client_address,
                &address_length );
    printf ( "Received: %s\n", buffer );
    return 0;
}

And the Go program:


import (
    "fmt"
    "net"
    "os"
)

func main() {
    addr, _ := net.ResolveUnixAddr("unixgram", "@uds-js")
    udsSock, err := net.DialUnix("unixgram", nil, addr)
    if err != nil {
        fmt.Printf("UDS connection failed: %v\n", err)
        os.Exit(1)
    }
    defer udsSock.Close()
    if _, err := udsSock.Write([]byte("{\"test\":100}")); err != nil {
        fmt.Printf("Failed to send message on UDS: %v\n", err)
    }
}

In the C program I set the first byte in the socket name to a null byte, as to spec. From what I've gathered in Go the name needs to start with a @.

Running netstat I can see the socket was created:

$ netstat -ax | grep DGRAM
unix  2      [ ]         DGRAM                    12411992 @uds-js@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Why does the Go program fail to connect to the socket?

EDIT:

Changing the name to a pathname, /tmp/uds-js, and this does work as expected.

EDIT2:

I created a server in Go and a client in C for the abstract socket and the two C programs and the two Go programs work okay together. The issue seem to be when going from C to Go using abstract sockets.

10
  • Does it work if you make the socket not abstract (that is, so it has a real path on a filesystem)? That would dissect the problem into two smaller parts. Commented Jun 23, 2020 at 13:07
  • also its good practice to remove the socket path on starting: remove( UDS_ADDRESS_NAME ), because you wont be able to use it if it already exists. Dont know about the # at the beginning of the path either, does that work ? Commented Jun 23, 2020 at 13:23
  • 1
    @secretsquirrel, abstract sockets go away as soon as all references to them are lost; that's their upside compared to "classical" UD sockets. Commented Jun 23, 2020 at 13:38
  • oh yes youre right I've never used them before Commented Jun 23, 2020 at 13:48
  • 1
    You deleted server_address.sun_path[0] = '\0'; when you changed it to /tmp/uds-js, right? Commented Jun 23, 2020 at 14:10

1 Answer 1

5

The Go program is connecting to the wrong abstract socket (or, equivalently, the C program is binding to the wrong abstract socket).

Your bind() is made against an abstract namespace UNIX socket address of length sizeof(struct sockaddr_un). If I am reading the Go implementation correctly, however, your connect() is performed against a socket address of length nine: two bytes for sa_family, and seven bytes, in this case, for len(name).

Those are different sockets.

In the abstract namespace, NULLs aren't special, so "\0uds-js" is one valid socket address, and "\0uds-js\0\0\0\0\0\0\0\0..." is a different valid address.

strace both processes looking at the C program's bind() and the Go program's connect(). I expect you will see that they call those functions with different socklen_t arguments.

Sign up to request clarification or add additional context in comments.

2 Comments

That indeed was the issue. In the C program I changed the address length to 9 and it worked. Looking at netstat output the name now shows as @uds-js, no more trailing nulls.
@dangeroushobo it should be offsetof(struct sockaddr_un, sun_name[7])

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.