Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support passing unix fds #54

Merged
merged 6 commits into from
Oct 12, 2020
Merged

Conversation

michallowasrzechonek-silvair
Copy link
Contributor

Hi,

We've started working on #33. It's still very much in progress, but I wanted to give you heads up and get some feedback about the general approach.

On the other side, we'll be using https://git.kernel.org/pub/scm/libs/ell/ell.git/

@acrisci
Copy link
Member

acrisci commented Jun 1, 2020

Yeah looks ok. Just a few notes.

  • Try to void the monkey patching if possible.
  • We need to leave room to support this in the high level interface.
  • The spec seems to say fds can be passed at any time during the message, not just after the first byte and will definitely not be passed if the header is not present (if that affects performance).
  • You might need to add the NEGOTIATE_UNIX_FDS command to the auth protocol.

@michallowasrzechonek-silvair
Copy link
Contributor Author

Unfortunately, asyncio doesn't expose sendmsg/recvmsg coroutines :( What we could do is keep these as regular functions, at the cost of slightly ugly invocation.

As for passing fds at any point, I'm pretty sure that all existing implementations do that while sending the initial fixed size header. Allowing fds to be passed along the body is somewhat problematic, because recvmsg is atomic, i.e. it truncates messages if buffer is too small, and has a buffer size limit. And dbus-next parses messages on the fly, so we would need to refactor the whole unmarshalling part to use a in-memory buffer.

High level API and negotiation: ok.

@rafalgajda-silvair
Copy link
Contributor

Hi all,
Current version seams to be working and all tests are passing. Could you take a moment to review the code changes and see if I missed something?
Much obliged.


if self.sock:
try:
msg, ancdata, *_ = self.sock.recvmsg(1, socket.CMSG_LEN(16 * unix_fds.itemsize),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we should replace 1 with the size of fixed message header. Also, why 16? Let's at least extract magic number into a constant.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done

@@ -228,7 +250,8 @@ def _unmarshall(self):
sender = header_fields.get(HeaderField.SENDER.name)
signature = header_fields.get(HeaderField.SIGNATURE.name, '')
signature_tree = SignatureTree(signature)
unix_fds = header_fields.get(HeaderField.UNIX_FDS.name)
# TODO: check unix_fds against the header
Copy link
Contributor Author

Choose a reason for hiding this comment

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

?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure what to check here

ancdata = [(socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array("i", msg.unix_fds))] \
if msg.unix_fds else None

await self.sock_sendmsg(self._sock, buf[:1], ancdata=ancdata)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here, send the whole fixed-size header first, then the rest.

Copy link
Contributor

Choose a reason for hiding this comment

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

And done

@michallowasrzechonek-silvair
Copy link
Contributor Author

@acrisci Did you have a chance to look at this?

@acrisci
Copy link
Member

acrisci commented Jul 1, 2020

I'm getting a test failure on travis that I can reproduce locally.

unix_fds = array.array("i")

if self.sock:
try:
Copy link
Member

@acrisci acrisci Jul 1, 2020

Choose a reason for hiding this comment

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

This code path might run multiple times for a single unmarshalling of a message. That will happen in the case there was a blocking error. The previously read data will be read from a buffer cache instead of the socket.

One solution would be to cache the fds on the unmarshaller instance and then skip reading the fds from the socket in the case that the fd cache is present.

Copy link
Contributor

Choose a reason for hiding this comment

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

I added that cache to unmarshaller, please have a look if this is what you had in mind.

@acrisci
Copy link
Member

acrisci commented Jul 1, 2020

Use make format to run the formatter.

fds = []

def _replace(obj):
if getattr(obj, "fileno", False):
Copy link
Member

Choose a reason for hiding this comment

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

There's hasattr but better might be type(obj) is socket.

Copy link
Contributor

Choose a reason for hiding this comment

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

I changed this to use hasattr. If I'm not mistaken this doesn't have to be a socket but could for example be an open file.

@@ -135,7 +135,7 @@ def new_error(msg: 'Message', error_name: str, error_text: str) -> 'Message':
body=[error_text])

@staticmethod
def new_method_return(msg: 'Message', signature: str = '', body: List[Any] = []) -> 'Message':
def new_method_return(msg: 'Message', signature: str = '', body: List[Any] = [], fds: List[Any] = []) -> 'Message':
Copy link
Member

Choose a reason for hiding this comment

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

Be more specific than List[Any]. I think you only accept a union of socket and int.

Copy link
Contributor

Choose a reason for hiding this comment

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

This could be something else other than a socket so I changed it to List[Union[int, IO]]

if response != _AuthResponse.OK:
raise AuthError(f'authentication failed: {response.value}: {args}')
if response is _AuthResponse.OK:
return "NEGOTIATE_UNIX_FD"
Copy link
Member

Choose a reason for hiding this comment

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

This command may only be sent on transports that support Unix file descriptor passing.

Some people use http transport with this even though it's deprecated.

Copy link
Contributor

Choose a reason for hiding this comment

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

I reworked AuthResponse so if fds negotiation fails with an ERROR we just continue without them (like before).

@@ -222,6 +225,32 @@ def reply_handler(reply, err):

return future.result()

def sock_sendmsg(self, sock, *buffers, ancdata=None, flags=0):
Copy link
Member

Choose a reason for hiding this comment

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

This should be private.

Copy link
Contributor

Choose a reason for hiding this comment

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

Changed

@@ -232,3 +235,41 @@ def _marshall(self):
header_block.marshall()
header_block.align(8)
return header_block.buffer + body_block.buffer


def replace_fds(body_obj, children, replace_fn):
Copy link
Member

Choose a reason for hiding this comment

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

This should be private.

Copy link
Contributor

Choose a reason for hiding this comment

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

Changed

import socket

import pytest

Copy link
Member

Choose a reason for hiding this comment

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

Be sure these tests are good because someone might come along and do the glib implementation and need to modify this.

@acrisci
Copy link
Member

acrisci commented Aug 21, 2020

Are you guys still working on this? It seems like a good feature. Let me know if I can help.

@michallowasrzechonek-silvair
Copy link
Contributor Author

Hi,

It turned out that our target platform puts restrictions on SCM_RIGHTS, and we can't use it directly. We ended up with a workaround, passing path to an unix socket istead of a descriptor.

So no, we're not actively working on that feature, sorry :(

At least the PR is functional, we've tested it with ELL acting as a service side.

@rafalgajda-silvair
Copy link
Contributor

Hi,
I might get some spare time to finish this pull request. It would be great if You could have a look at it again and let me know if there is anything more to be fixed before merging.

@acrisci
Copy link
Member

acrisci commented Oct 11, 2020

Sorry for not getting to this sooner. There are just too many subtle issues here to do this in one PR. But what is here is basically good.

I am going to merge this, make sure it's turned off so it doesn't affect any existing code and mark the feature as experimental so we can make breaking changes for awhile. I'll tag you on any PRs I make in the future.

@acrisci acrisci merged commit bb0a247 into altdesktop:master Oct 12, 2020
@rafalgajda-silvair
Copy link
Contributor

Sure thing, I'm just glad this code will not get lost :D

@rafalgajda-silvair rafalgajda-silvair deleted the support_unix_fds branch October 12, 2020 07:14
@michallowasrzechonek-silvair michallowasrzechonek-silvair changed the title [WIP] Support passing unix fds Support passing unix fds Feb 24, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants