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

Add full'ish support for network socket get & set options #2513

Merged
merged 20 commits into from
Mar 6, 2018

Conversation

slfritchie
Copy link
Contributor

Hi, all. This is a "what do you think?" PR for adding runtime support for network socket getsockopt(2) and setsockopt(2) system calls and all (?) of the magic number constants that are required to use those system calls.

  • Add @pony_os_rcvbuf() and @pony_os_sndbuf(), in the likeness (I hope) of other existing runtime functions that call getsockopt(2) and setsockopt(2). These would be shorthand or convenience functions to use instead of the more general @pony_os_getsockopt() and pony_os_setsockopt().

  • Add functions to the Pony runtime for setsockopt(2) and getsockopt(2).

Constants for the level and option_name arguments to these
syscalls (2nd and 3rd args, respectively) can be accessed via
functions in the new primitive class OSSockOpt.
* NOTE: The usual (?) hack of using apply() to return these constant
values isn't used here. Please let me know if apply() really
is the preferred way to implement this. It seemed better to add one
primitive with 200+ functions instead of 200+ primitives.

I'm not sure which commonly-used (or rarely-used!) constants for
these syscalls that I've omitted. There are about 149 level
constants and about 88 option_name constants that I scraped out
of /usr/include header files for Linux, MacOS, and FreeBSD.
* I don't have a Windows development environment available, but it'd
be nifty to include any missing Windows constants also.

  • Also, make the fd field of the TCPConnection actor public so that
    it's accessible for use with setsockopt and getsockopt without
    worry of race conditions that a behavior would be vulnerable to.

Operating system file descriptor for this socket, -1 if
not open or connected.
""" */
var fd: U32 = -1
Copy link
Member

@SeanTAllen SeanTAllen Jan 24, 2018

Choose a reason for hiding this comment

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

why expose this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's bullet point 3 in the description, sorry it isn't clearer. Is there a better way to allow TCPConnectionNotify.connected() or TCPListenNotify.connected() or .listening() to access the socket's file descriptor?

@slfritchie
Copy link
Contributor Author

slfritchie commented Jan 24, 2018

Hm, the following CircleCI error doesn't happen on OS X ... I'll look into it.

**** FAILED: net/http/_HTTPConnection._new_conn
listening on: 127.0.0.1:36274
received service: [36274]
URL: http://localhost:36274
url.string()=http://localhost:36274/
Test timed out without completing
Action never completed: client handler apply called 1
Action never completed: client handler apply called 2
Action never completed: server writing reponse 1
Action never completed: server writing reponse 2

Update: I can't get make test-ci config=release to fail on an Ubuntu Xenial VM. However, it does fail using the Docker image that Circle-CI is using. I'll continue looking into that unit test failure.

HOWEVER, there's another problem: ./stdlib --sequential does not exit promptly after the **** FAILED: 1 test, listed below: message. It seems like the runtime isn't detecting quiescence. That is an artifact of this specific unit test failure, I hope?

@slfritchie
Copy link
Contributor Author

Summary from this week's Pony Sync meeting:

  • I'll undo the _fd public'ification and instead see what adding functions to TCPListenNotify and TCPConnectionNotify feels like.
  • The current file descriptor-centric API has a lot of history behind it but also isn't a superb reason to do bad things.
  • The only "better" way to get those C preprocessor constants out of the compiler toolchain is to embed a "fraction" of LLVM into the Pony compiler. @sylvanc didn't recommend it. ^_^

@slfritchie slfritchie force-pushed the socket-sndbuf-rcvbuf branch 2 times, most recently from da13036 to cb141db Compare January 26, 2018 00:32
@slfritchie
Copy link
Contributor Author

All the CI checks are green/positive, hooray.

I changed 4 of the C setsockopt functions and made purely Pony variations, then added a not-quite-noop proposal for deprecating them. If there's an API deprecation policy already in place, please point me to it and I'll adjust accordingly.

(I noticed while making those changes that there's a likely cut-and-paste error in pony_os_multicast_ttl() using the wrong constant. This PR fixes both the C and Pony version.)

Last Wednesday's comments during the Pony Sync were helpful. I think another round of review would probably be good. Comments?

@@ -955,3 +961,6 @@ actor TCPConnection
_throttled = false
_notify.unthrottled(this)
end

fun get_fd(): U32 =>
Copy link
Member

Choose a reason for hiding this comment

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

I still believe that the fd should not be exposed in this way - it's not really in line with the capability security model.

As I mentioned in the sync call, I'd prefer to see the "set socket option" and "get socket option" methods be moved here to TCPConnection, so that the fd can remain hidden.

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 problem I ran into is that addressof can only be used for arguments to an FFI call. Is there a way to permit a pointer to be passed through the TCPConnection object boundary? I couldn't find a way, but I'm still new at that game, so I may have overlooked something.

Many useful getsockopt(2) and setsockopt(2) calls involve integers at 4 bytes. Creating a kludge where those things are copied across TCPConnection object boundary is a hack that I know how to do, using a Reader and Writer to . For any more complicated ones, such as IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP at https://github.com/ponylang/ponyc/blob/master/src/libponyrt/lang/socket.c#L1232-L1242 I don't know how to do in pure Pony strictly inside of TCPConnection without also resorting to Reader and Writer.

Copy link
Member

Choose a reason for hiding this comment

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

To be clear about what I'm envisioning:

  • OSSocket goes away
    • option_size_changed_error gets moved probably to OSSockOpt
    • the rest of the functions in OSSocket get moved to be methods of TCPConnection, with the fd taken from the field instead of passed as an argument.

I don't think the above plan would run into any issues with addressof?

Copy link
Member

Choose a reason for hiding this comment

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

To be clear, the capability security issue I have with exposing the fd is that it is immutable in theory (a val reference in the type system) that in practice lets you do mutation on the socket. This is problematic for trying to enforce capability restrictions through the type system.

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've cooked up an example at http://playground.ponylang.org/?gist=59fb7dfdf9afbaa7798ad9b41b439735 of the compiler error that I don't know how to work around:

0.21.3 [release]
compiled with: llvm 3.9.1 -- cc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609
Error:
main.pony:11:7: the addressof operator can only be used for FFI arguments
      addressof optval,
      ^
Error:
main.pony:12:7: the addressof operator can only be used for FFI arguments
      addressof optlen)
      ^

Copy link
Member

@jemc jemc Jan 31, 2018

Choose a reason for hiding this comment

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

Okay, the part I was missing was the part where you were still expecting some use cases to require the FFI interface instead of using OSSocket.

I took another look at your playground link. I still don't think the addressof is necessary to pass to the pony method - the same can be emulated by taking it as a normal argument and returning it in the return value. Or in cases like the one you had with an I32 option, there's no reason to pass one in, so you can just return it.

I noticed that you were assuming getsockopt would be a behaviour, but I don't think it should be - it should be a method that requires synchronous access to the TCPConnection, which only TCPConnectionNotify has. So I changed this in your playground link too.

http://playground.ponylang.org/?gist=5456427114b53e02c3f25022ae2c0027

EDIT: fixed the link.

Copy link
Member

Choose a reason for hiding this comment

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

I'd need to cut-and-paste those functions into udp_socket.pony

If you like, you could keep the FFI-wrapping implementations in a private primitive, which both TCPConnection and UDPSocket would have thin wrapper methods for, where they just pass their _fd in as the first argument.

Larger ones are pretty common, for example, passing pointers to (larger than 32 bit) C structures, such as the C code needed for IP_ADD_MEMBERSHIP, mentioned earlier today.

These can be wrapped as well - they just need a wrapper method that accounts for having the right signature.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After a chat with @SeanTAllen where neither of us are 100% clear of what you mean by a wrapper method, I'm wondering if pestering you later this week via chat might be possible?

One way I could interpret wrapper methods for right/proper signatures would be one wrapper per data size returned by the kernel. One example would be Linux's getsockopt( tcp_work_socket, SOL_IP, TCP_INFO, ...) where the value stored in arg4's pointer is a struct tcp_info, https://github.com/torvalds/linux/blob/2a17178/include/uapi/linux/tcp.h#L168-L227 for its value in master branch of Linus's tree today. Is the kind of wrapper that you're referring handle each of the members of that structure?

Copy link
Member

@jemc jemc Feb 6, 2018

Choose a reason for hiding this comment

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

Maybe we should chat about it during or after the sync call on Wednesday.

However, to illustrate my idea, I created a playground link that shows example method signatures I'd expect to see: https://playground.ponylang.org/?gist=691db79876e8dea099be6f4f509f4c73

Your "one wrapper per data size" assumption was close, but it should actually be "one wrapper per Pony type".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, that's about what I thought you were suggesting. This feels like a case of a 80-20 or 90-10 rule. Lots of options are a single word, and I'd initially created helpers for that case.

But the remaining cases, there are many dozens hiding in the magic constants that I added to pony_os_sockopt_option, and likely more for anyone experimenting with network protocols in a custom kernel/kernel module(*). Even the struct tcp_info case that we've been discussing in the last day could likely change when someone decides to add support for a new RFC and adds even more members to that structure.

(*) That person probably also would not mind (too much) putting the magic constants into their Pony code rather than discovering them in a platform-independent way via @pony_os_sockopt_option.

Constants for the `level` and `option_name` arguments to these
syscalls (2nd and 3rd args, respectively) can be accessed via
functions in the new primitive class `SockOpt`.
NOTE: The usual (?) hack of using `apply()` to return these constant
      values isn't used here.

I'm not sure which commonly-used (or rarely-used!) constants for
these syscalls that I've omitted.  There are about 149 `level`
constants and about 88 `option_name` constants that I scraped out
of `/usr/include` header files for Linux, MacOS, and FreeBSD.
I don't have a Windows development environment available, but it'd
be nifty to include any missing Windows constants also.

Also:

Add pony_os_rcvbuf() and pony_os_sndbuf()

More verbose get/setsockopt for under_pressure example
@slfritchie slfritchie force-pushed the socket-sndbuf-rcvbuf branch from 14dd6d8 to d444e3f Compare February 8, 2018 23:11
@slfritchie slfritchie added enhancement: 4 - in progress do not merge This PR should not be merged at this time changelog - added Automatically add "Added" CHANGELOG entry on merge labels Feb 8, 2018
@slfritchie
Copy link
Contributor Author

Boooo, the net/Broadcast appears to have a race condition. I'm investigating it now.

@slfritchie
Copy link
Contributor Author

I've changed the API substantially, thanks to the great review by @sylvanc @SeanTAllen @jemc and @kulibali from yesterday's sync meeting. My apologies if I've left anyone out. I hope that CI finds only small problems in this go-around.

Items to look at for another round of review could/ought to include?

  • What have I broken on Windows platforms that aren't tested by CI?
  • The API that I've implemented now is a bit different than was was discussed yesterday. Most of the change was to make it easier to write the docstrings for the new functions. A lot of documentation was plain ugly in most places where an optional argument was present.
  • What ought to be changed in the documentation?
  • How horrible is the new C function api_deprecated()? Should it do anything more than be a no-op placeholder?

Copy link
Member

@Praetonus Praetonus left a comment

Choose a reason for hiding this comment

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

The endianness stuff isn't exactly right. The current checks in target_is_big/littleendian return the endianness of the host platform, which isn't correct in a cross-compilation context. The functions should mirror the other target_is_* functions and use the LLVM API for target triples. The function to use here is llvm::Triple::isLittleEndian().

In addition, endianness should be covered in buildflagset.c in the same way OSes & co are covered, with mutually exclusive groups. The Platform primitive in the standard library should also get new functions for endianness, with the new associated compiler intrinsics that should be defined in genprim.c.

@slfritchie
Copy link
Contributor Author

Hi, @Praetonus, thanks for pointing me at the right places for adding new bits to the compiler and the Platform primitive. The three commits up to b2d7443 try to address your review change requests, though I'm not at all sure that I've done the right thing. There was a lot of "copy what's done with other flags tables then change the variable names". Please holler if I've missed or abused anything.

"""
_OSSocket.getsockopt_u32(_fd, level, option_name)

fun setsockopt(level: I32, option_name: I32, option: Array[U8]): U32 =>
Copy link
Contributor

Choose a reason for hiding this comment

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

should all those methods setting sock opts be fun ref instead of fun box as they kind of mutate the connection (although it is an actor)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, yes. And that was also mentioned back in a Sync meeting, sorry, I'll fix it up shortly.

@Praetonus
Copy link
Member

The compiler changes to endianness look good to me.

@Praetonus Praetonus dismissed their stale review February 13, 2018 15:45

The requested changes have been implemented.


In case of system call failure, this function returns the 2-tuple:
1. The value of `errno`.
2. An undefined value that must be ignored.
Copy link
Contributor

Choose a reason for hiding this comment

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

A usage example would be nice.

Also the information that the array should be pre allocated the expected result size is missing.

@slfritchie
Copy link
Contributor Author

I've added the examples requested by @mfelsche. AFAIK, this PR is mergeable. If someone has a different opinion, please shout.

However, @dipinhora made an good suggestion to be last night to change the API of getsockopt. I'm going to try it out today. If it's quick, I'll add it to this PR. It will make the example code I added less verbose by making the use of Reader less cumbersome.

@jemc
Copy link
Member

jemc commented Mar 1, 2018

Yep, should be mergeable.

@slfritchie
Copy link
Contributor Author

I'd forgotten that I was going to remove the api_deprecated() C function that I added in the PR. I'll remove it now and use the new push as an excuse to tickle the Appveyor CI into retrying whatever timeout happened at https://ci.appveyor.com/project/ponylang/ponyc/build/ponyc-0.21.3-4082.10c6d82/job/1xyurgbwhjx9o6at.

@slfritchie slfritchie force-pushed the socket-sndbuf-rcvbuf branch from 3a7ac8e to 3602a9f Compare March 5, 2018 22:17
@slfritchie slfritchie removed the do not merge This PR should not be merged at this time label Mar 6, 2018
@slfritchie slfritchie merged commit 91543f7 into ponylang:master Mar 6, 2018
ponylang-main added a commit that referenced this pull request Mar 6, 2018
dipinhora pushed a commit to dipinhora/ponyc that referenced this pull request Jun 5, 2018
)

Add Pony runtime support for setsockopt(2) and getsockopt(2) system calls

Constants for the `level` and `option_name` arguments to these
syscalls (2nd and 3rd args, respectively) can be accessed via
functions in the new primitive class `OSSockOpt`.  The constants
provided are extracted from:

*   macOS Sierra 10.12.6
*   Ubuntu Linux Xenial/16.04 LTS + kernel 4.4.0-109-generic
*   FreeBSD 11.1-RELEASE
*   Windows Winsock function reference for getsockopt & setsockopt:
    *     https://msdn.microsoft.com/en-us/library/windows/desktop/ms738544(v=vs.85).aspx
    *     https://msdn.microsoft.com/en-us/library/windows/desktop/ms740476(v=vs.85).aspx

Also, this PR adds to the compiler new `ifdef` symbols called
`bigendian` and `littleendian` for checking the endianness of
the compilation target's CPU.
dipinhora pushed a commit to dipinhora/ponyc that referenced this pull request Jun 5, 2018
ergl added a commit that referenced this pull request Apr 10, 2021
The example mentions the `@pony_os_setsockopt` function, which was
originally added in PR #2513, but eventually removed before the PR got
merged.
ergl added a commit that referenced this pull request Apr 10, 2021
The example mentions the `@pony_os_setsockopt` function, which was
originally added in PR #2513, but eventually removed before the PR got
merged.
SeanTAllen pushed a commit that referenced this pull request Apr 10, 2021
The example mentions the `@pony_os_setsockopt` function, which was
originally added in PR #2513, but eventually removed before the PR got
merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog - added Automatically add "Added" CHANGELOG entry on merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants