Skip to content

Support abstract UnixAddress#5617

Merged
dlang-bot merged 1 commit intodlang:masterfrom
epi:abstract-uds
Jul 22, 2017
Merged

Support abstract UnixAddress#5617
dlang-bot merged 1 commit intodlang:masterfrom
epi:abstract-uds

Conversation

@epi
Copy link
Contributor

@epi epi commented Jul 16, 2017

Lack of support for abstract UNIX domain sockets was discussed in forums [1], but I couldn't find a related issue in bugzilla.

According to [2]: "abstract: an abstract socket address is distinguished (from a pathname socket) by the fact that sun_path[0] is a null byte ('\0'). 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. (Null bytes in the name have no special significance.)"

For this reason, the previous implementation using strlen() was not sufficient, and additional class field containing the length of the address was required.

[1] http://forum.dlang.org/post/mmk50s$1qm$1@digitalmars.com
[2] UNIX(7)

@dlang-bot
Copy link
Contributor

Thanks for your pull request, @epi! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.

Some tips to help speed things up:

  • smaller, focused PRs are easier to review than big ones

  • try not to mix up refactoring or style changes with bug fixes or feature enhancements

  • provide helpful commit messages explaining the rationale behind each change

Bear in mind that large or tricky changes may require multiple rounds of review and revision.

Please see CONTRIBUTING.md for more information.

Bugzilla references

Your PR doesn't reference any Bugzilla issue.

If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog.

Copy link
Member

@CyberShadow CyberShadow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, however the implementation seems to be somewhat confused.

It seems like the change seems to go halfway to making UnixAddress just store the address as a char[] with a header, which also seems to align with the actual representation and usage of UnixAddress. This is not how typical C programs deal with UNIX addresses (and other types of addresses), as there the address types are forced to be self-contained value types, which is not the case for std.socket.

So, why not go all the way and make UnixAddress just manage a header of sockaddr_un.sun_path.offsetof bytes plus an arbitrarily long (and containing arbitrary bytes) path? Something very similar was already done for SocketSet, which is not limited to FD_SETSIZE like the C equivalent.

std/socket.d Outdated
sun.sun_path.ptr[0 .. path.length] = (cast(byte[]) path)[];
sun.sun_path.ptr[path.length] = 0;
_nameLen = cast(socklen_t) (sockaddr_un.init.sun_path.offsetof +
path.length + (sun.sun_path.ptr[0] ? 1 : 0));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't the null byte be already included in path, and thus, path.length?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For pathname addresses, the terminating null is of course not included in path, and the manual recommends following these rules:

  • 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 sockaddr_un).

For abstract addresses, the null byte is the initial one and it is included in the path. The length of the address in this case is sockaddr_un.init.sun_path.offsetof + path.length.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, so I understand that unlike regular UNIX sockets, abstract sockets' paths do not need to be null-terminated (and in fact doing so would change the address).

Please add a short comment describing the length calculation done here, as in your message above. A DDoc comment on the constructor mentioning how to create an abstract UNIX socket address would also be nice.

return (cast(const(char)*) sun.sun_path.ptr)[0 ..
_nameLen - sockaddr_un.init.sun_path.offsetof -
(sun.sun_path.ptr[0] ? 1 : 0)].idup;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why exclude the null byte from the result? It was present in the constructor, so excluding it here would be inconsistent. Also, without it, it seems like it's no longer possible to know whether a particular UnixAddress represents an abstract path or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The excluded byte is the null byte terminating a pathname address (added in the constructor to make it a C string).
For abstract address (sun.sun_path.ptr[0] == 0), nothing is excluded.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for the explanation. This should be added as a comment. I think it would be better to split the length calculation in multiple statements, so it's possible to explain each step.

A DDoc comment mentioning that filesystem paths are returned without the implicit null terminator but Linux abstract socket paths are returned as-is would be nice.

if (len > this.nameLen)
throw new SocketParameterException("Not enough socket address storage");
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the point of this method. The base Address class does not own the storage for the address data, so where the address itself is stored or how it deals with its storage/length is up to the descendant classes.

Setting the length for an address of fixed length (e.g. IP addresses) is nonsensical, so I don't see why this should be part of the abstract Address interface.

Furthermore, it seems to break the expectation that invoking a property's setter with some value should either throw or cause invoking the getter right afterwards to return the same value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reasoning behind adding this setter was:
There are methods in Socket (remoteAddress, localAddress, receiveFrom), which do the following:

  1. call Socket.createAddress, getting an instance of one of Address' descendants, created with argument-less constructor.
  2. call Address.name and Address.nameLen getters on that instance to get the address and capacity of the buffer for the next call
  3. call one of getsockname, getpeername, recvfrom, to set the name in the buffer just obtained.
  4. read Address.nameLen again and compare with result of previous call to see if the buffer was large enough (receiveFrom doesn't do this step)

While the above procedure works well for fixed-length addresses, it doesn't provide a way to feed the actual length of the address back to the concrete Address. I had to add a way that doesn't change the public interface nor the behavior of the above methods for fixed-length addresses, and does not require adding a special case for UnixAddress in all of them.

The nameLen setter is protected and deliberately not documented with DDoc. The default implementation in base Address is intended to be sufficient for the typical case: fixed-length addresses.
Yes, there is one case where it breaks the expectation you mentioned: where the len argument passed is less than the fixed-length. I should add a check for this.

Another solution I had in mind was to remove the argument-less constructors, get the address from getsockname, getpeername and receiveFrom to a temporary buffer on the stack, and refactor createAddress to create concrete Addresses by passing that temporary buffer to the appropriate constructor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. So, abstract UNIX socket addresses require that the address length is not greater than the length of the actual abstract "path", because they allow embedded null bytes and there is no way to infer the address length like the other address types we support so far. For this, I think a regular method would be more appropriate (for the reasons I mentioned above).

Also, it would be great if you could summarize the above into comments, as all this may not be obvious to the next person looking at this code.

@CyberShadow
Copy link
Member

CyberShadow commented Jul 17, 2017

CyberShadow/DAutoTest — Pull request SHA mismatch

Gotcha.

If you look at the commit list on this PR, it says that the latest commit is 6f247e6.

However, if you try to fetch the PR branch (refs/pull/5617/head), the resulting commit is 84b727a.

It looks like 6f247e6 has the changelog entry, so it seems GitHub's caching is broken and is serving the wrong commit on the pull/ refs.

@CyberShadow
Copy link
Member

Sent a message to support. CC @wilzbach :)

@epi
Copy link
Contributor Author

epi commented Jul 17, 2017

As for the fail in DAutoTest, the reason could be that I added the changelog entry, committed with --amend, and then pushed with --force.

As for storing arbitrarily long arrays of bytes in UnixAddress, I don't see the benefit of that that could justify the added complexity. Implementations are limited to the static size of sockaddr_un anyway, e.g. Linux copies the address to a fixed-length buffer in kernel space, and rejects addresses longer than sizeof(sockaddr_un) [1][2]

[1] http://lxr.linux.no/linux+v4.10.1/include/uapi/linux/socket.h#L7
[2] http://lxr.linux.no/linux+v4.10.1/net/unix/af_unix.c#L218

@CyberShadow
Copy link
Member

CyberShadow commented Jul 17, 2017

Implementations are limited to the static size of sockaddr_un anyway, e.g. Linux copies the address to a fixed-length buffer in kernel space, and rejects addresses longer than sizeof(sockaddr_un) [1][2]

That is a great argument against that idea.

I'm still somewhat unsure about the inconsistency in behaviour, such as treatment of the terminating null byte, for abstract sockets. Perhaps introducing a new type would be more appropriate, what do you think? Sorry, retracting this for now, I need to have a closer look at this tomorrow.

@wilzbach
Copy link
Contributor

As for the fail in DAutoTest, the reason could be that I added the changelog entry, committed with --amend, and then pushed with --force.

That's quite a typical action and we are seeing this error message on other PRs , so I don't think this is related to anything you did (or did not) ;-)

@epi
Copy link
Contributor Author

epi commented Jul 17, 2017

I'll just note that my first try was with class AbstractUnixAddress : UnixAddress, but it ended up being even more confusing than the current one. I've seen various conventions elsewhere: netstat and Go use '@' prefix (instead of initial '\0') to distinguish an abstract address [1]. DBus expects the key "abstract", e.g. "unix:abstract=/tmp/dbus-OtHLWmCLPR" [2].
My feeling is that since '\0' is explicitly allowed in D strings, the current behavior follows the POLA for someone who has read UNIX(7). I agree that adding an example in DDoc would make this clear.

[1] http://golang-examples.tumblr.com/post/92025745979/pitfall-of-abstract-unix-domain-socket-address-in
[2] https://dbus.freedesktop.org/doc/dbus-specification.html#transports-unix-domain-sockets

Copy link
Member

@CyberShadow CyberShadow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanations; this looks pretty great, just needs some clarifications in the code itself. An example for creating abstract UNIX socket addresses in the documentation would also be nice.

if (len > this.nameLen)
throw new SocketParameterException("Not enough socket address storage");
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. So, abstract UNIX socket addresses require that the address length is not greater than the length of the actual abstract "path", because they allow embedded null bytes and there is no way to infer the address length like the other address types we support so far. For this, I think a regular method would be more appropriate (for the reasons I mentioned above).

Also, it would be great if you could summarize the above into comments, as all this may not be obvious to the next person looking at this code.

std/socket.d Outdated
sun.sun_path.ptr[0 .. path.length] = (cast(byte[]) path)[];
sun.sun_path.ptr[path.length] = 0;
_nameLen = cast(socklen_t) (sockaddr_un.init.sun_path.offsetof +
path.length + (sun.sun_path.ptr[0] ? 1 : 0));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, so I understand that unlike regular UNIX sockets, abstract sockets' paths do not need to be null-terminated (and in fact doing so would change the address).

Please add a short comment describing the length calculation done here, as in your message above. A DDoc comment on the constructor mentioning how to create an abstract UNIX socket address would also be nice.

return (cast(const(char)*) sun.sun_path.ptr)[0 ..
_nameLen - sockaddr_un.init.sun_path.offsetof -
(sun.sun_path.ptr[0] ? 1 : 0)].idup;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for the explanation. This should be added as a comment. I think it would be better to split the length calculation in multiple statements, so it's possible to explain each step.

A DDoc comment mentioning that filesystem paths are returned without the implicit null terminator but Linux abstract socket paths are returned as-is would be nice.

@epi
Copy link
Contributor Author

epi commented Jul 18, 2017

Thanks for the detailed review, I'll follow up with the requested changes shortly.

@epi
Copy link
Contributor Author

epi commented Jul 20, 2017

What's wrong with std.concurrency tests?
Also, I can't see my changelog entry when I build the preview according to these instructions: https://github.com/dlang/phobos/tree/master/changelog

@CyberShadow
Copy link
Member

What's wrong with std.concurrency tests?

Looks like a random failure.

Also, I can't see my changelog entry when I build the preview according to these instructions: https://github.com/dlang/phobos/tree/master/changelog

Works for me. You may need to rm ../dlang.org/changelog/2.076.0_pre.dd if you've built dlang.org before adding your changelog entry.

@wilzbach
Copy link
Contributor

You may need to rm ../dlang.org/changelog/2.076.0_pre.dd

That shouldn't be necessary - please let me know if the nuke helped.
Btw normally you can also preview the changelog at DAutoTest, but we are currently experiencing some troubles with GitHub (they have been notified and are working on their bugs).

@wilzbach
Copy link
Contributor

That shouldn't be necessary - please let me know if the nuke helped.

Okay it was necessary. Sorry. Fix -> dlang/dlang.org#1837

@epi
Copy link
Contributor Author

epi commented Jul 21, 2017

Thanks, I can now see my changelog entry.

@epi
Copy link
Contributor Author

epi commented Jul 21, 2017

Is there anything else I should improve in this PR?

@CyberShadow
Copy link
Member

I don't think so.

Thanks for your great work on this PR. Please send more! :)

@dlang-bot dlang-bot merged commit c29516a into dlang:master Jul 22, 2017
@epi epi deleted the abstract-uds branch July 23, 2017 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants