[systemd-devel] How to get a useful peer address when doing accept(3, ...) on a systemd supplied listening socket

Mantas Mikulėnas grawity at gmail.com
Thu Oct 27 12:04:40 UTC 2022


On Thu, Oct 27, 2022 at 1:51 PM Klaus Ebbe Grue <grue at di.ku.dk> wrote:

> Hi systemd-devel,
>
> Sorry to bug you with another user question.
>
> I have a socket activated daemon, call it mydaemon, and I have trouble
> finding out who connects to it.
>
>
> mydaemon.socket contains:
>
>
>   [Socket]
>   ListenStream=9999
>
> When I connect using IPv4 using
>
>   nc -4 localhost 9999
>
> then mydaemon does
>
>   sockaddr_in6 peer;
>   socklen_t peer_size=sizeof(peer);
>   accept(3,(struct sockaddr *)&peer,sizeof(peer))
>
> Afterwards, peer.sin6_family is AF_INET6 and peer.sin6_addr contains some
> gibberish like a00:e5ae::
>

If you specify nothing for the listen address, systemd will assume the IPv6
address [::] as the default, and will create an AF_INET6 socket bound to
[::]:9999.

Due to Linux's default "bind both families" magic, it will actually be
bound to both [::]:9999 *and* 0.0.0.0:9999, so it will accept IPv4
connections – but you'll receive them in the form of AF_INET6 sockets, so
the peer address of your v4 client indeed has family AF_INET6 but contains
a "v6-mapped" IPv4 address such as [::ffff:10.0.229.174] aka
[::ffff:a00:e5ae].

The alternative would be to specify both ListenStream=[::]:9999 and
ListenStream=0.0.0.0:9999 (as well as BindIPv6Only=ipv6-only), which would
cause you to receive *two* socket FDs – one purely for IPv6 clients, the
other for IPv4 – that you'd have to put into poll() or some other loop for
accepting clients.

You can extract the IPv4 address by detecting the [::ffff:0:0/96] prefix
and stripping away the first 12 bytes. (There's also a magic option for
getsockopt() listed in ipv6(7) that can convert such a "v6-mapped" socket
to a "real" AF_INET socket, but it's rarely needed.)


>
> If I connect more than once, the gibberish changes from connection to
> connection.
>

I have a feeling it "changes" because you're trying to give the whole
struct sockaddr to inet_pton() instead of giving just the .sin6_addr field,
so your program is trying to interpret the *port number* (i.e. the
.sin6_port which precedes .sin_addr) as part of the address...

But please show your entire code, otherwise this is all just guessing.

Here's a working example that I've just tested with
`systemd-socket-activate --listen=9999`:
https://gist.github.com/grawity/63369273742f23b596d764cb6d45feb7


>
> If mydaemon creates the listening socket, I can easily get the peer
> address.
>
> I suspect that when systemd creates the listening socket then
> accept(3,...) returns a socket which is connected to a local socket created
> by systemd.
>
> QUESTION: Is that suspicion correct?
>

No, it isn't.

>

-- 
Mantas Mikulėnas
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.freedesktop.org/archives/systemd-devel/attachments/20221027/048f9483/attachment.htm>


More information about the systemd-devel mailing list