1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
# Native Client's IMC sockets interface
## Overview of interface
NaCl has four types of socket endpoint:
* SocketAddress: created by imc\_makeboundsock(); imc\_connect()able
* BoundSocket: created by imc\_makeboundsock(); imc\_accept()able
* ConnectedSocket: created by imc\_connect() or imc\_accept()
* Message-oriented: can send/receive messages with
imc\_sendmsg()/recvmsg().
* Can send any descriptor except ConnectedSocket or BoundSocket.
* Therefore cannot be shared between processes.
* DataOnlySocket: created by imc\_socketpair()
* Message-oriented: can send/receive messages with
imc\_sendmsg()/recvmsg().
* Can be shared between processes, but messages are not reliably separated
and may be corrupted if sent/received concurrently.
There are no byte stream sockets.
In addition, NaCl has these non-socket descriptor types:
* FileDesc: for read-only files fetched by the browser.
* SharedMemory: created with imc\_mem\_obj\_create().
(Note: The names used here are not necessarily how they appear in the code.)
## SocketAddress/BoundSocket pairs
SocketAddress and BoundSocket are NaCl's answer to Unix domain sockets that are
bound into the filesystem namespace.
With Unix domain sockets, a server process, A, will do the following to create
and listen on a socket bound into the file namespace: `fd0 = socket() bind(fd0,
"/tmp/xxx/sock") listen(fd0, queue_size) /* boring */
announce_to_other_process("/tmp/xxx/sock") while True: fd1 = accept(fd0)
handle_connection(fd1)
` Process A must pass the string "/tmp/xxx/sock" to another process, B, which
does: `fd2 = socket() connect(fd2, "/tmp/xxx/sock")
` Result: fd1 and fd2 are connected together.
In NaCl, the filename string is replaced with a pair of descriptors, created by
the system call imc\_makeboundsock(), of types SocketAddress and BoundSocket.
Server process A does: `sockaddr_fd, boundsock_fd = imc_makeboundsock()
send_to_other_process(sockaddr_fd) while True: fd1 = imc_accept(boundsock_fd)
handle_connection(fd1)
` Process B receives sockaddr\_fd and does: `fd2 = imc_connect(sockaddr_fd)
` Result: fd1 and fd2 are both ConnectedSockets and are connected together.
There is no built-in filesystem namespace in NaCl. Rather than passing the
filename of a Unix domain socket across processes, a process must pass a
SocketAddress descriptor.
SocketAddress is misleadingly named because it is a descriptor, not an address
(a string). It just happens that the way it is implemented, the descriptor wraps
a randomly-chosen string.
In NaCl, the primary means of sharing is to share SocketAddress descriptors,
because ConnectedSockets are not transferrable.
The reason ConnectedSockets are not transferable is that the current
implementation of descriptor-passing on Windows requires that the recipient
process does not change during an imc\_sendmsg() operation.
The call `imc_sendmsg(socket_fd, "data", [desc_handle])` (i.e. sending some data
and a single descriptor) is implemented as follows on Windows by NaCl's trusted
runtime:
1. Grab in-process lock for socket descriptor
2. Send message: "What is your process ID?"
3. Receive message: "My process ID is proc\_pid"
4. `proc_handle = OpenProcess(proc_pid)`
5. `new_handle_id = DuplicateHandle(proc_handle, desc_handle)`
* This copies our handle into the receiving process's handle table.
6. `SendMessage("header indicating 1 handle is being sent" + new_handle_id +
"data")`
* Sends the message data along with the ID of the handle in the receiving
process's handle table.
7. Release lock
Apparently this is the only way to transfer handles in Windows.
NaCl has to ensure that there can only be one recipient process, otherwise there
could be a race condition: the Windows handle could get transferred to one
process, while the handle ID is received by a different process.
(Note that steps 2/3 could be performed when the ConnectedSocket is first
created, but the current implementation does it on every imc\_sendmsg().)
I suspect there are ways to fix this race condition while allowing
ConnectedSockets to be transferable, but first I'd like to explore what
conventions might be layered on top of this interface to provide
object-capabilities that are sharable by default.
## How to use this as a capability system
The convention could be that all objects are SocketAddresses. ConnectedSockets
are created only temporarily, one per method call.
To make a method call: 1. imc\_connect() to the object, getting a stream S (a
ConnectedSocket, non-first-class). 1. Call imc\_sendmsg() to send the message
(data and capabilities), possibly multiple times, because IMC messages are
limited to (I believe) 128k. 1. Wait for a reply by calling imc\_recvmsg().
Again, multiple calls may be necessary to receive large reply messages. 1.
close() the stream.
If this pattern is going to be common, it could be combined into a single NaCl
system call.
One down side to using SocketAddresses this way is that the holder of a
BoundSocket cannot tell when all the references to the corresponding
SocketAddress have been dropped. (Unix domain sockets that are bound into the
filesystem have the same problem.) This means the system does not provide a way
to garbage collect objects.
Also, doing an imc\_connect() for every method call might be slow.
## DataOnlySocket
DataOnlySockets are created as connected pairs with imc\_socketpair(). Messages
sent across these sockets can contain only data, not descriptors. This means
they are not subject to the race condition above, and so transferring these
sockets between processes using imc\_sendmsg() is allowed.
Although DataOnlySockets are message-oriented, on Windows they are implemented
on top of byte stream sockets using headers to separate the messages. This is
not done in a multiprocess-safe way, so concurrent imc\_sendmsg()/imc\_recvmsg()
calls can corrupt the message headers so that header data is returned by
imc\_recvmsg(). This does not threaten the integrity of the system, however,
because it does not provide a way to acquire other processes' descriptors.
I expect that it will be desirable to add concurrently-usable byte stream
sockets to NaCl in order to support Unix programs that use pipes.
## Extracting address data from SocketAddress descriptors
The NaCl browser plugin allows Javascript code to perform some extra operations
on SocketAddress descriptors that are not directly available to untrusted NaCl
processes.
Javascript can extract the address string (pure data) from a SocketAddress
descriptor wrapper object (using its toString() method), and it can convert
address strings back to SocketAddress descriptor objects (using the
socketAddressFactory() method on the plugin object). Calling
socketAddressFactory() on an invalid socket address string will return a
SocketAddress descriptor on which imc\_connect() will fail.
One of the reasons for exposing socket address strings this way is that it
allows SocketAddresses to be shared with non-NaCl plugins that are statically
linked with NaCl's IMC library, specifically the O3D plugin.
Socket address strings are not scoped at all, so it is possible to create
cross-tab connections -- for example, by passing the socket address string to
the server and back to another web app. This probably allows connections to be
shared across browser instances as well.
## Better terms
The names SocketAddress and BoundSocket come from their implementation. It might
be better to find terms which describe their abstract interface.
* Instead of SocketAddress, perhaps ConnectEndpoint?
* Instead of BoundSocket, perhaps AcceptEndpoint?
|