This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Wrong usage of sockaddr_un struct for abstract namespace unix sockets
Type: behavior Stage:
Components: IO, Library (Lib) Versions: Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: anthonypjshaw, pitrou, soutys
Priority: normal Keywords:

Created on 2015-11-03 11:08 by soutys, last changed 2022-04-11 14:58 by admin.

Messages (8)
msg253981 - (view) Author: PrzemeK (soutys) Date: 2015-11-03 11:15
Case: http://stackoverflow.com/questions/33465094/connection-refused-when-using-abstract-namespace-unix-sockets

The code that does not work (python3 example):
...
sock_path = b"/var/tmp/sock.tmp"
server.bind(b"\0" + sock_path)
...
and strace shows:
connect(3, {sa_family=AF_LOCAL, sun_path=@"/var/tmp/sock.tmp"}, 20) = -1 ECONNREFUSED (Connection refused)

For C-written client strace shows:
connect(3, {sa_family=AF_LOCAL, sun_path=@"/var/tmp/sock.tmp"}, 110) = 0

The code that actually works (python3 example):
...
sun_path_len = 108
sock_path = b"/var/tmp/sock.tmp"
server.bind(b"\0" + sock_path + (b"\0" * (sun_path_len - len(sock_path) - 1)))
...
and strace shows:
bind(3, {sa_family=AF_LOCAL, sun_path=@"/var/tmp/sock.tmp"}, 110) = 0
110 it's the correst size of a struct.

There's no hint at https://docs.python.org/3/library/socket.html#socket-families for that.

Test files (servers and clients): https://gist.github.com/soutys/ffbe2e76a86835a9cc6b
msg253982 - (view) Author: PrzemeK (soutys) Date: 2015-11-03 11:23
Errata - 1st paragraph should be:

The code that does not work (python3 example):
...
sock_path = b"/var/tmp/sock.tmp"
server.bind(b"\0" + sock_path)
...
and strace shows:
bind(3, {sa_family=AF_LOCAL, sun_path=@"/var/tmp/sock.tmp"...}, 20) = 0
msg341637 - (view) Author: anthony shaw (anthonypjshaw) * (Python triager) Date: 2019-05-06 20:15
hi, which version of Python were you using to do this? Please could you provide the full code snippet to reproduce the issue.

The following example binds to the correct namespace

  from socket import *

  sock = socket(AF_UNIX, SOCK_STREAM)

  sock.bind("\0/var/tmp/sock.tmp")
msg341641 - (view) Author: Stefan Behnel (scoder) * (Python committer) Date: 2019-05-06 20:32
Looks like the issue was originally reported against Python 3.4.
msg341686 - (view) Author: PrzemeK (soutys) Date: 2019-05-07 06:49
Yep, it was 3.4 then... but I think problem still exists

tl;dr: For abstract sockets (only?) filling struct with zeros is 
meaningful.

long:
* Python (cli) -> Python (srv) = works
* C (cli) -> C (srv) = works
* C (cli) -> Python (srv) = does NOT work
* Python (cli) -> C (srv) = does NOT work (strace dumps below)

$ gcc --version
gcc (Ubuntu 7.4.0-1ubuntu1~18.04) 7.4.0

$ uname -a
Linux ajzus 4.15.0-48-generic #51-Ubuntu SMP Wed Apr 3 08:28:49 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

$ python3.7 --version
Python 3.7.1


// 1st console
$ gcc -g -Wall -Wextra -pedantic -o abs_srv abs_srv.c

$ strace ./abs_srv
...
socket(AF_UNIX, SOCK_STREAM, 0)         = 3
bind(3, {sa_family=AF_UNIX, sun_path=@"/var/tmp/sock.tmp\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 110) = 0
listen(3, 5)                            = 0
accept(3, NULL, NULL
// waiting


// 2nd console
$ ls /var/tmp/*.sock
ls: cannot access '/var/tmp/*.sock': No such file or directory

$ strace python3.7 abs_cli.py
...
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0) = 3
connect(3, {sa_family=AF_UNIX, sun_path=@"/var/tmp/sock.tmp"}, 20) = -1 ECONNREFUSED (Connection refused)
msg341688 - (view) Author: PrzemeK (soutys) Date: 2019-05-07 06:59
Gist: https://gist.github.com/soutys/ffbe2e76a86835a9cc6b

More:

Padding `sun_path` with zeros for python cli code:
 
...
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.connect("\0/var/tmp/sock.tmp" + "\0" * 90)
...

gives strace like:
...
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0) = 3
connect(3, {sa_family=AF_UNIX, sun_path=@"/var/tmp/sock.tmp\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 110) = 0
write(1, "Ready.\n", 7Ready.
)                 = 7
...

= works
msg341971 - (view) Author: anthony shaw (anthonypjshaw) * (Python triager) Date: 2019-05-09 07:49
thanks, your code example zero-pads the socket address, and looking at the socketmodule.c code it does some padding under certain circumstances.

https://github.com/python/cpython/blob/master/Modules/socketmodule.c#L1318-L1330

The Unix man page specify the same requirement you've noticed:

 When binding a socket to a pathname, a few rules should be observed
       for maximum portability and ease of coding:

       *  The pathname in sun_path should be null-terminated.

       *  The length of the pathname, including the terminating null byte,
          should not exceed the size of sun_path.

       *  The addrlen argument that describes the enclosing sockaddr_un
          structure should have a value of at least:

              offsetof(struct sockaddr_un, sun_path)+strlen(addr.sun_path)+1

          or, more simply, addrlen can be specified as sizeof(struct sock‐
          addr_un).
msg341972 - (view) Author: anthony shaw (anthonypjshaw) * (Python triager) Date: 2019-05-09 08:04
The existing tests in place add the null-termination bytes in the test string:

    def testLinuxAbstractNamespace(self):
        address = b"\x00python-test-hello\x00\xff"
        with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s1:
            s1.bind(address)
            s1.listen()
            with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s2:
                s2.connect(s1.getsockname())
                with s1.accept()[0] as s3:
                    self.assertEqual(s1.getsockname(), address)
                    self.assertEqual(s2.getpeername(), address)

So that answers your initial question, it does work, but you have to add the null termination bytes in the address string.

This isn't documented, so the documentation needs fixing.
History
Date User Action Args
2022-04-11 14:58:23adminsetgithub: 69727
2019-05-09 08:04:48anthonypjshawsetversions: + Python 3.8, - Python 2.7, Python 3.4, Python 3.7
2019-05-09 08:04:33anthonypjshawsetmessages: + msg341972
2019-05-09 07:49:58anthonypjshawsetmessages: + msg341971
2019-05-07 06:59:38soutyssetmessages: + msg341688
2019-05-07 06:49:15soutyssetmessages: + msg341686
versions: + Python 3.7
2019-05-06 20:33:14scodersetnosy: - scoder
2019-05-06 20:32:36scodersetnosy: pitrou, scoder, soutys, anthonypjshaw
messages: + msg341641
2019-05-06 20:15:06anthonypjshawsetnosy: + anthonypjshaw, scoder
messages: + msg341637
2015-11-06 23:46:00terry.reedysetnosy: + pitrou
2015-11-03 11:23:44soutyssetmessages: + msg253982
2015-11-03 11:15:21soutyssetmessages: + msg253981
2015-11-03 11:08:21soutyscreate