| … | … | @@ -0,0 +1,3 @@ |
| 1 | node_modules/ | |
| 2 | build/ | |
| 3 | *.log |
| … | … | @@ -0,0 +1,13 @@ |
| 1 | cmake_minimum_required(VERSION 4.0) | |
| 2 | ||
| 3 | find_package(cmake-bare REQUIRED PATHS node_modules/cmake-bare) | |
| 4 | ||
| 5 | project(bare_abstract_socket C) | |
| 6 | ||
| 7 | add_bare_module(bare_abstract_socket) | |
| 8 | ||
| 9 | target_sources( | |
| 10 | ${bare_abstract_socket} | |
| 11 | PRIVATE | |
| 12 | binding.c | |
| 13 | ) |
| … | … | @@ -0,0 +1,201 @@ |
| 1 | Apache License | |
| 2 | Version 2.0, January 2004 | |
| 3 | http://www.apache.org/licenses/ | |
| 4 | ||
| 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
| 6 | ||
| 7 | 1. Definitions. | |
| 8 | ||
| 9 | "License" shall mean the terms and conditions for use, reproduction, | |
| 10 | and distribution as defined by Sections 1 through 9 of this document. | |
| 11 | ||
| 12 | "Licensor" shall mean the copyright owner or entity authorized by | |
| 13 | the copyright owner that is granting the License. | |
| 14 | ||
| 15 | "Legal Entity" shall mean the union of the acting entity and all | |
| 16 | other entities that control, are controlled by, or are under common | |
| 17 | control with that entity. For the purposes of this definition, | |
| 18 | "control" means (i) the power, direct or indirect, to cause the | |
| 19 | direction or management of such entity, whether by contract or | |
| 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
| 21 | outstanding shares, or (iii) beneficial ownership of such entity. | |
| 22 | ||
| 23 | "You" (or "Your") shall mean an individual or Legal Entity | |
| 24 | exercising permissions granted by this License. | |
| 25 | ||
| 26 | "Source" form shall mean the preferred form for making modifications, | |
| 27 | including but not limited to software source code, documentation | |
| 28 | source, and configuration files. | |
| 29 | ||
| 30 | "Object" form shall mean any form resulting from mechanical | |
| 31 | transformation or translation of a Source form, including but | |
| 32 | not limited to compiled object code, generated documentation, | |
| 33 | and conversions to other media types. | |
| 34 | ||
| 35 | "Work" shall mean the work of authorship, whether in Source or | |
| 36 | Object form, made available under the License, as indicated by a | |
| 37 | copyright notice that is included in or attached to the work | |
| 38 | (an example is provided in the Appendix below). | |
| 39 | ||
| 40 | "Derivative Works" shall mean any work, whether in Source or Object | |
| 41 | form, that is based on (or derived from) the Work and for which the | |
| 42 | editorial revisions, annotations, elaborations, or other modifications | |
| 43 | represent, as a whole, an original work of authorship. For the purposes | |
| 44 | of this License, Derivative Works shall not include works that remain | |
| 45 | separable from, or merely link (or bind by name) to the interfaces of, | |
| 46 | the Work and Derivative Works thereof. | |
| 47 | ||
| 48 | "Contribution" shall mean any work of authorship, including | |
| 49 | the original version of the Work and any modifications or additions | |
| 50 | to that Work or Derivative Works thereof, that is intentionally | |
| 51 | submitted to Licensor for inclusion in the Work by the copyright owner | |
| 52 | or by an individual or Legal Entity authorized to submit on behalf of | |
| 53 | the copyright owner. For the purposes of this definition, "submitted" | |
| 54 | means any form of electronic, verbal, or written communication sent | |
| 55 | to the Licensor or its representatives, including but not limited to | |
| 56 | communication on electronic mailing lists, source code control systems, | |
| 57 | and issue tracking systems that are managed by, or on behalf of, the | |
| 58 | Licensor for the purpose of discussing and improving the Work, but | |
| 59 | excluding communication that is conspicuously marked or otherwise | |
| 60 | designated in writing by the copyright owner as "Not a Contribution." | |
| 61 | ||
| 62 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
| 63 | on behalf of whom a Contribution has been received by Licensor and | |
| 64 | subsequently incorporated within the Work. | |
| 65 | ||
| 66 | 2. Grant of Copyright License. Subject to the terms and conditions of | |
| 67 | this License, each Contributor hereby grants to You a perpetual, | |
| 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 69 | copyright license to reproduce, prepare Derivative Works of, | |
| 70 | publicly display, publicly perform, sublicense, and distribute the | |
| 71 | Work and such Derivative Works in Source or Object form. | |
| 72 | ||
| 73 | 3. Grant of Patent License. Subject to the terms and conditions of | |
| 74 | this License, each Contributor hereby grants to You a perpetual, | |
| 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 76 | (except as stated in this section) patent license to make, have made, | |
| 77 | use, offer to sell, sell, import, and otherwise transfer the Work, | |
| 78 | where such license applies only to those patent claims licensable | |
| 79 | by such Contributor that are necessarily infringed by their | |
| 80 | Contribution(s) alone or by combination of their Contribution(s) | |
| 81 | with the Work to which such Contribution(s) was submitted. If You | |
| 82 | institute patent litigation against any entity (including a | |
| 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work | |
| 84 | or a Contribution incorporated within the Work constitutes direct | |
| 85 | or contributory patent infringement, then any patent licenses | |
| 86 | granted to You under this License for that Work shall terminate | |
| 87 | as of the date such litigation is filed. | |
| 88 | ||
| 89 | 4. Redistribution. You may reproduce and distribute copies of the | |
| 90 | Work or Derivative Works thereof in any medium, with or without | |
| 91 | modifications, and in Source or Object form, provided that You | |
| 92 | meet the following conditions: | |
| 93 | ||
| 94 | (a) You must give any other recipients of the Work or | |
| 95 | Derivative Works a copy of this License; and | |
| 96 | ||
| 97 | (b) You must cause any modified files to carry prominent notices | |
| 98 | stating that You changed the files; and | |
| 99 | ||
| 100 | (c) You must retain, in the Source form of any Derivative Works | |
| 101 | that You distribute, all copyright, patent, trademark, and | |
| 102 | attribution notices from the Source form of the Work, | |
| 103 | excluding those notices that do not pertain to any part of | |
| 104 | the Derivative Works; and | |
| 105 | ||
| 106 | (d) If the Work includes a "NOTICE" text file as part of its | |
| 107 | distribution, then any Derivative Works that You distribute must | |
| 108 | include a readable copy of the attribution notices contained | |
| 109 | within such NOTICE file, excluding those notices that do not | |
| 110 | pertain to any part of the Derivative Works, in at least one | |
| 111 | of the following places: within a NOTICE text file distributed | |
| 112 | as part of the Derivative Works; within the Source form or | |
| 113 | documentation, if provided along with the Derivative Works; or, | |
| 114 | within a display generated by the Derivative Works, if and | |
| 115 | wherever such third-party notices normally appear. The contents | |
| 116 | of the NOTICE file are for informational purposes only and | |
| 117 | do not modify the License. You may add Your own attribution | |
| 118 | notices within Derivative Works that You distribute, alongside | |
| 119 | or as an addendum to the NOTICE text from the Work, provided | |
| 120 | that such additional attribution notices cannot be construed | |
| 121 | as modifying the License. | |
| 122 | ||
| 123 | You may add Your own copyright statement to Your modifications and | |
| 124 | may provide additional or different license terms and conditions | |
| 125 | for use, reproduction, or distribution of Your modifications, or | |
| 126 | for any such Derivative Works as a whole, provided Your use, | |
| 127 | reproduction, and distribution of the Work otherwise complies with | |
| 128 | the conditions stated in this License. | |
| 129 | ||
| 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, | |
| 131 | any Contribution intentionally submitted for inclusion in the Work | |
| 132 | by You to the Licensor shall be under the terms and conditions of | |
| 133 | this License, without any additional terms or conditions. | |
| 134 | Notwithstanding the above, nothing herein shall supersede or modify | |
| 135 | the terms of any separate license agreement you may have executed | |
| 136 | with Licensor regarding such Contributions. | |
| 137 | ||
| 138 | 6. Trademarks. This License does not grant permission to use the trade | |
| 139 | names, trademarks, service marks, or product names of the Licensor, | |
| 140 | except as required for reasonable and customary use in describing the | |
| 141 | origin of the Work and reproducing the content of the NOTICE file. | |
| 142 | ||
| 143 | 7. Disclaimer of Warranty. Unless required by applicable law or | |
| 144 | agreed to in writing, Licensor provides the Work (and each | |
| 145 | Contributor provides its Contributions) on an "AS IS" BASIS, | |
| 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
| 147 | implied, including, without limitation, any warranties or conditions | |
| 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
| 149 | PARTICULAR PURPOSE. You are solely responsible for determining the | |
| 150 | appropriateness of using or redistributing the Work and assume any | |
| 151 | risks associated with Your exercise of permissions under this License. | |
| 152 | ||
| 153 | 8. Limitation of Liability. In no event and under no legal theory, | |
| 154 | whether in tort (including negligence), contract, or otherwise, | |
| 155 | unless required by applicable law (such as deliberate and grossly | |
| 156 | negligent acts) or agreed to in writing, shall any Contributor be | |
| 157 | liable to You for damages, including any direct, indirect, special, | |
| 158 | incidental, or consequential damages of any character arising as a | |
| 159 | result of this License or out of the use or inability to use the | |
| 160 | Work (including but not limited to damages for loss of goodwill, | |
| 161 | work stoppage, computer failure or malfunction, or any and all | |
| 162 | other commercial damages or losses), even if such Contributor | |
| 163 | has been advised of the possibility of such damages. | |
| 164 | ||
| 165 | 9. Accepting Warranty or Additional Liability. While redistributing | |
| 166 | the Work or Derivative Works thereof, You may choose to offer, | |
| 167 | and charge a fee for, acceptance of support, warranty, indemnity, | |
| 168 | or other liability obligations and/or rights consistent with this | |
| 169 | License. However, in accepting such obligations, You may act only | |
| 170 | on Your own behalf and on Your sole responsibility, not on behalf | |
| 171 | of any other Contributor, and only if You agree to indemnify, | |
| 172 | defend, and hold each Contributor harmless for any liability | |
| 173 | incurred by, or claims asserted against, such Contributor by reason | |
| 174 | of your accepting any such warranty or additional liability. | |
| 175 | ||
| 176 | END OF TERMS AND CONDITIONS | |
| 177 | ||
| 178 | APPENDIX: How to apply the Apache License to your work. | |
| 179 | ||
| 180 | To apply the Apache License to your work, attach the following | |
| 181 | boilerplate notice, with the fields enclosed by brackets "[]" | |
| 182 | replaced with your own identifying information. (Don't include | |
| 183 | the brackets!) The text should be enclosed in the appropriate | |
| 184 | comment syntax for the file format. We also recommend that a | |
| 185 | file or class name and description of purpose be included on the | |
| 186 | same "printed page" as the copyright notice for easier | |
| 187 | identification within third-party archives. | |
| 188 | ||
| 189 | Copyright [yyyy] [name of copyright owner] | |
| 190 | ||
| 191 | Licensed under the Apache License, Version 2.0 (the "License"); | |
| 192 | you may not use this file except in compliance with the License. | |
| 193 | You may obtain a copy of the License at | |
| 194 | ||
| 195 | http://www.apache.org/licenses/LICENSE-2.0 | |
| 196 | ||
| 197 | Unless required by applicable law or agreed to in writing, software | |
| 198 | distributed under the License is distributed on an "AS IS" BASIS, | |
| 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 200 | See the License for the specific language governing permissions and | |
| 201 | limitations under the License. |
| … | … | @@ -0,0 +1,121 @@ |
| 1 | # bare-abstract-socket | |
| 2 | ||
| 3 | Linux **abstract-namespace** `AF_UNIX` sockets for [Bare](https://github.com/holepunchto/bare). | |
| 4 | ||
| 5 | This module owns only the *socket setup* — it creates abstract-namespace sockets | |
| 6 | and hands raw file descriptors back to JavaScript. All stream I/O is done by | |
| 7 | stock, unmodified [`bare-pipe`](https://github.com/holepunchto/bare-pipe) via | |
| 8 | `new Pipe(fd)`. It composes with bare-pipe rather than patching or forking it. | |
| 9 | ||
| 10 | ## Why | |
| 11 | ||
| 12 | An **abstract-namespace** unix socket lives in a Linux-only address namespace | |
| 13 | instead of on the filesystem — its name begins with a NUL byte (`\0name`) and no | |
| 14 | path is created on disk. | |
| 15 | ||
| 16 | This matters on locked-down systems. On **Android**, for example, SELinux denies | |
| 17 | the `shell` domain permission to create a pathname socket (`bind(AF_UNIX, | |
| 18 | "/path.sock")` → `EACCES`), so a daemon that talks to its CLI over a pathname | |
| 19 | unix socket cannot run without root. Abstract-namespace sockets are permitted, so | |
| 20 | switching the transport to an abstract name restores no-root local IPC. | |
| 21 | ||
| 22 | `bare-pipe`'s public API can't express an abstract name: internally it measures | |
| 23 | the socket name with `strlen()`, and an abstract name *starts* with `\0`, so it | |
| 24 | truncates to empty. This module creates the socket itself with plain POSIX | |
| 25 | `sockaddr_un` (the address length distinguishes the bytes — no `strlen`), then | |
| 26 | lets bare-pipe adopt the resulting fd for reading and writing. | |
| 27 | ||
| 28 | ## Platform support | |
| 29 | ||
| 30 | **Linux only.** Abstract-namespace sockets are a Linux extension; this module has | |
| 31 | no meaning on other platforms. | |
| 32 | ||
| 33 | ## Install | |
| 34 | ||
| 35 | ```sh | |
| 36 | npm install bare-abstract-socket | |
| 37 | ``` | |
| 38 | ||
| 39 | Prebuilt binaries are shipped in `prebuilds/`, so no toolchain is required to | |
| 40 | consume the module. | |
| 41 | ||
| 42 | ## Usage | |
| 43 | ||
| 44 | ```js | |
| 45 | const { AbstractServer, connect } = require('bare-abstract-socket') | |
| 46 | ||
| 47 | // Server — echoes everything back. | |
| 48 | const server = new AbstractServer() | |
| 49 | server.on('connection', (socket) => socket.pipe(socket)) | |
| 50 | server.listen('my-service') // the abstract name, WITHOUT the leading NUL | |
| 51 | ||
| 52 | // Client. | |
| 53 | const socket = connect('my-service') | |
| 54 | socket.write(Buffer.from('hello')) | |
| 55 | socket.on('data', (data) => console.log(data.toString())) // "hello" | |
| 56 | ``` | |
| 57 | ||
| 58 | Both `server`'s connections and the value returned by `connect()` are ordinary | |
| 59 | [`bare-pipe`](https://github.com/holepunchto/bare-pipe) `Pipe` streams (duplex | |
| 60 | `bare-stream`s), so the full stream API is available. | |
| 61 | ||
| 62 | ## API | |
| 63 | ||
| 64 | ### `const server = new AbstractServer()` | |
| 65 | ||
| 66 | An `EventEmitter`. | |
| 67 | ||
| 68 | #### `server.listen(name[, backlog])` | |
| 69 | ||
| 70 | Bind an abstract socket named `name` and start accepting connections. `name` is | |
| 71 | the abstract name **without** the leading NUL byte — the binding adds it. | |
| 72 | `backlog` defaults to `511`. Emits `'listening'`. | |
| 73 | ||
| 74 | #### Event: `'connection'` (`socket`) | |
| 75 | ||
| 76 | Emitted for each accepted connection. `socket` is a `bare-pipe` `Pipe`. | |
| 77 | ||
| 78 | #### Event: `'error'` (`err`) | |
| 79 | ||
| 80 | Emitted if the accept poll fails. | |
| 81 | ||
| 82 | #### `server.close()` | |
| 83 | ||
| 84 | Stop polling and close the listening socket. Emits `'close'`. Does not close | |
| 85 | already-accepted connections. | |
| 86 | ||
| 87 | ### `connect(name)` | |
| 88 | ||
| 89 | Connect to the abstract socket named `name` and return a `bare-pipe` `Pipe` | |
| 90 | stream. Throws if the socket does not exist (`ECONNREFUSED`) or the name is too | |
| 91 | long (`ENAMETOOLONG`). | |
| 92 | ||
| 93 | ## Building from source | |
| 94 | ||
| 95 | Requires the Bare native toolchain (`clang` + `lld` + `llvm` + `ninja`): | |
| 96 | ||
| 97 | ```sh | |
| 98 | npm install | |
| 99 | npx bare-make generate | |
| 100 | npx bare-make build | |
| 101 | ``` | |
| 102 | ||
| 103 | The built addon lands in `build/`; copy it into `prebuilds/<platform>-<arch>/` | |
| 104 | to ship it. | |
| 105 | ||
| 106 | ## How it works | |
| 107 | ||
| 108 | - **Native (`binding.c`)** — `connect(name)` does `socket()` + `connect()` to an | |
| 109 | abstract `sockaddr_un` and returns the connected fd. `listen(name, backlog, | |
| 110 | ctx, onConnection)` does `socket()` + `bind()` + `listen()`, then registers a | |
| 111 | `uv_poll` on the listening fd; when it becomes readable it drains the | |
| 112 | `accept()` backlog, handing each connected fd to JavaScript. Abstract | |
| 113 | addressing is plain POSIX — the address length, not a NUL terminator, | |
| 114 | delimits the name. | |
| 115 | - **JavaScript (`index.js`)** — wraps each fd with stock `bare-pipe` | |
| 116 | (`new Pipe(fd)`) for all stream I/O. bare-pipe is an ordinary dependency and | |
| 117 | is never modified. | |
| 118 | ||
| 119 | ## License | |
| 120 | ||
| 121 | Apache-2.0 |
| … | … | @@ -0,0 +1,370 @@ |
| 1 | // bare-abstract-socket — Linux abstract-namespace AF_UNIX sockets for bare. | |
| 2 | // | |
| 3 | // This module owns ONLY the socket setup: it creates abstract-namespace sockets | |
| 4 | // (leading-NUL sun_path, which SELinux permits no-root where pathname sockets do | |
| 5 | // not) and hands raw fds back to JS. All stream I/O is done by upstream, stock | |
| 6 | // `bare-pipe` via `new Pipe(fd)` — we do not touch or fork it. | |
| 7 | // | |
| 8 | // connect(name) -> a connected client fd (int) | |
| 9 | // listen(name, backlog, ctx, onc) -> a server handle; onc(fd) per accepted conn | |
| 10 | // close(handle) -> stop polling + close the listening fd | |
| 11 | // | |
| 12 | // Server accept is integrated with the event loop via uv_poll on the listening | |
| 13 | // fd (readable -> accept() the backlog). Abstract addressing is plain POSIX | |
| 14 | // sockaddr_un, so none of libuv's name-length handling is involved. | |
| 15 | ||
| 16 | #include <assert.h> | |
| 17 | #include <bare.h> | |
| 18 | #include <errno.h> | |
| 19 | #include <fcntl.h> | |
| 20 | #include <js.h> | |
| 21 | #include <stdbool.h> | |
| 22 | #include <stddef.h> | |
| 23 | #include <string.h> | |
| 24 | #include <sys/socket.h> | |
| 25 | #include <sys/un.h> | |
| 26 | #include <unistd.h> | |
| 27 | #include <uv.h> | |
| 28 | ||
| 29 | typedef struct { | |
| 30 | uv_poll_t poll; | |
| 31 | int fd; | |
| 32 | ||
| 33 | js_env_t *env; | |
| 34 | js_ref_t *ctx; | |
| 35 | js_ref_t *on_connection; | |
| 36 | ||
| 37 | bool closing; | |
| 38 | ||
| 39 | js_deferred_teardown_t *teardown; | |
| 40 | } bare_abstract_server_t; | |
| 41 | ||
| 42 | // Fill an abstract-namespace sockaddr_un. Returns the address length to pass to | |
| 43 | // bind()/connect(), or -1 if the name is too long. The abstract name is a | |
| 44 | // leading NUL byte followed by `name_len` bytes (NOT NUL-terminated); the | |
| 45 | // address length is what distinguishes the bytes, so we must pass it exactly. | |
| 46 | static int | |
| 47 | bare_abstract__fill_addr(struct sockaddr_un *addr, const char *name, size_t name_len) { | |
| 48 | if (name_len + 1 > sizeof(addr->sun_path)) return -1; | |
| 49 | ||
| 50 | memset(addr, 0, sizeof(*addr)); | |
| 51 | addr->sun_family = AF_UNIX; | |
| 52 | addr->sun_path[0] = '\0'; | |
| 53 | memcpy(addr->sun_path + 1, name, name_len); | |
| 54 | ||
| 55 | return (int) (offsetof(struct sockaddr_un, sun_path) + 1 + name_len); | |
| 56 | } | |
| 57 | ||
| 58 | static int | |
| 59 | bare_abstract__set_nonblock(int fd) { | |
| 60 | int flags = fcntl(fd, F_GETFL, 0); | |
| 61 | if (flags < 0) return -1; | |
| 62 | return fcntl(fd, F_SETFL, flags | O_NONBLOCK); | |
| 63 | } | |
| 64 | ||
| 65 | // uv_poll callback: the listening fd is readable -> drain the accept backlog, | |
| 66 | // emitting each connected fd to JS. bare-pipe (via new Pipe(fd)) then does I/O. | |
| 67 | static void | |
| 68 | bare_abstract__on_poll(uv_poll_t *handle, int status, int events) { | |
| 69 | int err; | |
| 70 | ||
| 71 | bare_abstract_server_t *server = (bare_abstract_server_t *) handle->data; | |
| 72 | ||
| 73 | if (server->closing) return; | |
| 74 | ||
| 75 | js_env_t *env = server->env; | |
| 76 | ||
| 77 | js_handle_scope_t *scope; | |
| 78 | err = js_open_handle_scope(env, &scope); | |
| 79 | assert(err == 0); | |
| 80 | ||
| 81 | js_value_t *ctx; | |
| 82 | err = js_get_reference_value(env, server->ctx, &ctx); | |
| 83 | assert(err == 0); | |
| 84 | ||
| 85 | js_value_t *on_connection; | |
| 86 | err = js_get_reference_value(env, server->on_connection, &on_connection); | |
| 87 | assert(err == 0); | |
| 88 | ||
| 89 | if (status < 0) { | |
| 90 | js_value_t *argv[2]; | |
| 91 | ||
| 92 | err = js_create_string_utf8(env, (utf8_t *) uv_err_name(status), -1, &argv[0]); | |
| 93 | assert(err == 0); | |
| 94 | ||
| 95 | err = js_get_null(env, &argv[1]); | |
| 96 | assert(err == 0); | |
| 97 | ||
| 98 | js_call_function(env, ctx, on_connection, 2, argv, NULL); | |
| 99 | ||
| 100 | err = js_close_handle_scope(env, scope); | |
| 101 | assert(err == 0); | |
| 102 | ||
| 103 | return; | |
| 104 | } | |
| 105 | ||
| 106 | // Drain everything currently pending so we never miss a connection between | |
| 107 | // poll wakeups. | |
| 108 | while (!server->closing) { | |
| 109 | int fd = accept(server->fd, NULL, NULL); | |
| 110 | ||
| 111 | if (fd < 0) { | |
| 112 | if (errno == EAGAIN || errno == EWOULDBLOCK) break; | |
| 113 | if (errno == EINTR || errno == ECONNABORTED) continue; | |
| 114 | break; // other errors: stop draining this wakeup | |
| 115 | } | |
| 116 | ||
| 117 | bare_abstract__set_nonblock(fd); | |
| 118 | ||
| 119 | js_value_t *argv[2]; | |
| 120 | ||
| 121 | err = js_get_null(env, &argv[0]); | |
| 122 | assert(err == 0); | |
| 123 | ||
| 124 | err = js_create_uint32(env, (uint32_t) fd, &argv[1]); | |
| 125 | assert(err == 0); | |
| 126 | ||
| 127 | js_call_function(env, ctx, on_connection, 2, argv, NULL); | |
| 128 | } | |
| 129 | ||
| 130 | err = js_close_handle_scope(env, scope); | |
| 131 | assert(err == 0); | |
| 132 | } | |
| 133 | ||
| 134 | static void | |
| 135 | bare_abstract__on_close(uv_handle_t *handle) { | |
| 136 | int err; | |
| 137 | ||
| 138 | bare_abstract_server_t *server = (bare_abstract_server_t *) handle->data; | |
| 139 | ||
| 140 | js_env_t *env = server->env; | |
| 141 | ||
| 142 | js_deferred_teardown_t *teardown = server->teardown; | |
| 143 | ||
| 144 | err = js_delete_reference(env, server->on_connection); | |
| 145 | assert(err == 0); | |
| 146 | ||
| 147 | err = js_delete_reference(env, server->ctx); | |
| 148 | assert(err == 0); | |
| 149 | ||
| 150 | err = js_finish_deferred_teardown_callback(teardown); | |
| 151 | assert(err == 0); | |
| 152 | } | |
| 153 | ||
| 154 | static void | |
| 155 | bare_abstract__teardown(js_deferred_teardown_t *handle, void *data) { | |
| 156 | bare_abstract_server_t *server = (bare_abstract_server_t *) data; | |
| 157 | ||
| 158 | if (server->closing) return; | |
| 159 | ||
| 160 | server->closing = true; | |
| 161 | ||
| 162 | if (server->fd >= 0) { | |
| 163 | close(server->fd); | |
| 164 | server->fd = -1; | |
| 165 | } | |
| 166 | ||
| 167 | uv_close((uv_handle_t *) &server->poll, bare_abstract__on_close); | |
| 168 | } | |
| 169 | ||
| 170 | static js_value_t * | |
| 171 | bare_abstract_connect(js_env_t *env, js_callback_info_t *info) { | |
| 172 | int err; | |
| 173 | ||
| 174 | size_t argc = 1; | |
| 175 | js_value_t *argv[1]; | |
| 176 | ||
| 177 | err = js_get_callback_info(env, info, &argc, argv, NULL, NULL); | |
| 178 | assert(err == 0); | |
| 179 | ||
| 180 | assert(argc == 1); | |
| 181 | ||
| 182 | char name[sizeof(((struct sockaddr_un *) 0)->sun_path)]; | |
| 183 | size_t name_len; | |
| 184 | err = js_get_value_string_utf8(env, argv[0], (utf8_t *) name, sizeof(name), &name_len); | |
| 185 | assert(err == 0); | |
| 186 | ||
| 187 | struct sockaddr_un addr; | |
| 188 | int addrlen = bare_abstract__fill_addr(&addr, name, name_len); | |
| 189 | ||
| 190 | if (addrlen < 0) { | |
| 191 | err = js_throw_error(env, "ENAMETOOLONG", "abstract socket name too long"); | |
| 192 | assert(err == 0); | |
| 193 | return NULL; | |
| 194 | } | |
| 195 | ||
| 196 | int fd = socket(AF_UNIX, SOCK_STREAM, 0); | |
| 197 | ||
| 198 | if (fd < 0) { | |
| 199 | err = js_throw_error(env, uv_err_name(uv_translate_sys_error(errno)), strerror(errno)); | |
| 200 | assert(err == 0); | |
| 201 | return NULL; | |
| 202 | } | |
| 203 | ||
| 204 | // AF_UNIX connect completes (or is refused) immediately for a listening peer, | |
| 205 | // so a blocking connect never stalls the loop; switch to non-blocking after, | |
| 206 | // as uv_pipe_open (bare-pipe) expects. | |
| 207 | if (connect(fd, (struct sockaddr *) &addr, addrlen) < 0) { | |
| 208 | int e = errno; | |
| 209 | close(fd); | |
| 210 | err = js_throw_error(env, uv_err_name(uv_translate_sys_error(e)), strerror(e)); | |
| 211 | assert(err == 0); | |
| 212 | return NULL; | |
| 213 | } | |
| 214 | ||
| 215 | bare_abstract__set_nonblock(fd); | |
| 216 | ||
| 217 | js_value_t *result; | |
| 218 | err = js_create_uint32(env, (uint32_t) fd, &result); | |
| 219 | assert(err == 0); | |
| 220 | ||
| 221 | return result; | |
| 222 | } | |
| 223 | ||
| 224 | static js_value_t * | |
| 225 | bare_abstract_listen(js_env_t *env, js_callback_info_t *info) { | |
| 226 | int err; | |
| 227 | ||
| 228 | size_t argc = 4; | |
| 229 | js_value_t *argv[4]; | |
| 230 | ||
| 231 | err = js_get_callback_info(env, info, &argc, argv, NULL, NULL); | |
| 232 | assert(err == 0); | |
| 233 | ||
| 234 | assert(argc == 4); | |
| 235 | ||
| 236 | char name[sizeof(((struct sockaddr_un *) 0)->sun_path)]; | |
| 237 | size_t name_len; | |
| 238 | err = js_get_value_string_utf8(env, argv[0], (utf8_t *) name, sizeof(name), &name_len); | |
| 239 | assert(err == 0); | |
| 240 | ||
| 241 | uint32_t backlog; | |
| 242 | err = js_get_value_uint32(env, argv[1], &backlog); | |
| 243 | assert(err == 0); | |
| 244 | ||
| 245 | struct sockaddr_un addr; | |
| 246 | int addrlen = bare_abstract__fill_addr(&addr, name, name_len); | |
| 247 | ||
| 248 | if (addrlen < 0) { | |
| 249 | err = js_throw_error(env, "ENAMETOOLONG", "abstract socket name too long"); | |
| 250 | assert(err == 0); | |
| 251 | return NULL; | |
| 252 | } | |
| 253 | ||
| 254 | int fd = socket(AF_UNIX, SOCK_STREAM, 0); | |
| 255 | ||
| 256 | if (fd < 0) { | |
| 257 | err = js_throw_error(env, uv_err_name(uv_translate_sys_error(errno)), strerror(errno)); | |
| 258 | assert(err == 0); | |
| 259 | return NULL; | |
| 260 | } | |
| 261 | ||
| 262 | if (bind(fd, (struct sockaddr *) &addr, addrlen) < 0) { | |
| 263 | int e = errno; | |
| 264 | close(fd); | |
| 265 | err = js_throw_error(env, uv_err_name(uv_translate_sys_error(e)), strerror(e)); | |
| 266 | assert(err == 0); | |
| 267 | return NULL; | |
| 268 | } | |
| 269 | ||
| 270 | if (listen(fd, (int) backlog) < 0) { | |
| 271 | int e = errno; | |
| 272 | close(fd); | |
| 273 | err = js_throw_error(env, uv_err_name(uv_translate_sys_error(e)), strerror(e)); | |
| 274 | assert(err == 0); | |
| 275 | return NULL; | |
| 276 | } | |
| 277 | ||
| 278 | bare_abstract__set_nonblock(fd); | |
| 279 | ||
| 280 | js_value_t *handle; | |
| 281 | bare_abstract_server_t *server; | |
| 282 | err = js_create_arraybuffer(env, sizeof(bare_abstract_server_t), (void **) &server, &handle); | |
| 283 | assert(err == 0); | |
| 284 | ||
| 285 | uv_loop_t *loop; | |
| 286 | err = js_get_env_loop(env, &loop); | |
| 287 | assert(err == 0); | |
| 288 | ||
| 289 | server->fd = fd; | |
| 290 | server->env = env; | |
| 291 | server->closing = false; | |
| 292 | ||
| 293 | err = uv_poll_init(loop, &server->poll, fd); | |
| 294 | ||
| 295 | if (err < 0) { | |
| 296 | close(fd); | |
| 297 | err = js_throw_error(env, uv_err_name(err), uv_strerror(err)); | |
| 298 | assert(err == 0); | |
| 299 | return NULL; | |
| 300 | } | |
| 301 | ||
| 302 | server->poll.data = server; | |
| 303 | ||
| 304 | err = js_create_reference(env, argv[2], 1, &server->ctx); | |
| 305 | assert(err == 0); | |
| 306 | ||
| 307 | err = js_create_reference(env, argv[3], 1, &server->on_connection); | |
| 308 | assert(err == 0); | |
| 309 | ||
| 310 | err = js_add_deferred_teardown_callback(env, bare_abstract__teardown, (void *) server, &server->teardown); | |
| 311 | assert(err == 0); | |
| 312 | ||
| 313 | err = uv_poll_start(&server->poll, UV_READABLE, bare_abstract__on_poll); | |
| 314 | assert(err == 0); | |
| 315 | ||
| 316 | return handle; | |
| 317 | } | |
| 318 | ||
| 319 | static js_value_t * | |
| 320 | bare_abstract_close(js_env_t *env, js_callback_info_t *info) { | |
| 321 | int err; | |
| 322 | ||
| 323 | size_t argc = 1; | |
| 324 | js_value_t *argv[1]; | |
| 325 | ||
| 326 | err = js_get_callback_info(env, info, &argc, argv, NULL, NULL); | |
| 327 | assert(err == 0); | |
| 328 | ||
| 329 | assert(argc == 1); | |
| 330 | ||
| 331 | bare_abstract_server_t *server; | |
| 332 | err = js_get_arraybuffer_info(env, argv[0], (void **) &server, NULL); | |
| 333 | assert(err == 0); | |
| 334 | ||
| 335 | if (server->closing) return NULL; | |
| 336 | ||
| 337 | server->closing = true; | |
| 338 | ||
| 339 | if (server->fd >= 0) { | |
| 340 | close(server->fd); | |
| 341 | server->fd = -1; | |
| 342 | } | |
| 343 | ||
| 344 | uv_close((uv_handle_t *) &server->poll, bare_abstract__on_close); | |
| 345 | ||
| 346 | return NULL; | |
| 347 | } | |
| 348 | ||
| 349 | static js_value_t * | |
| 350 | bare_abstract_exports(js_env_t *env, js_value_t *exports) { | |
| 351 | int err; | |
| 352 | ||
| 353 | #define V(name, fn) \ | |
| 354 | { \ | |
| 355 | js_value_t *val; \ | |
| 356 | err = js_create_function(env, name, -1, fn, NULL, &val); \ | |
| 357 | assert(err == 0); \ | |
| 358 | err = js_set_named_property(env, exports, name, val); \ | |
| 359 | assert(err == 0); \ | |
| 360 | } | |
| 361 | ||
| 362 | V("connect", bare_abstract_connect) | |
| 363 | V("listen", bare_abstract_listen) | |
| 364 | V("close", bare_abstract_close) | |
| 365 | #undef V | |
| 366 | ||
| 367 | return exports; | |
| 368 | } | |
| 369 | ||
| 370 | BARE_MODULE(bare_abstract_socket, bare_abstract_exports) |
| … | … | @@ -0,0 +1 @@ |
| 1 | module.exports = require.addon() |
| … | … | @@ -0,0 +1,73 @@ |
| 1 | // bare-abstract-socket — abstract-namespace AF_UNIX sockets on Linux. | |
| 2 | // | |
| 3 | // We own only the socket setup (native binding: create the abstract socket, hand | |
| 4 | // back fds). Stock, unmodified `bare-pipe` does all stream I/O via `new Pipe(fd)`. | |
| 5 | // | |
| 6 | // const { AbstractServer, connect } = require('bare-abstract-socket') | |
| 7 | // | |
| 8 | // const server = new AbstractServer() | |
| 9 | // server.on('connection', (socket) => socket.pipe(socket)) // echo | |
| 10 | // server.listen('my-abstract-name') | |
| 11 | // | |
| 12 | // const socket = connect('my-abstract-name') // a bare-pipe stream | |
| 13 | // | |
| 14 | // The name is the abstract-namespace name WITHOUT the leading NUL — the binding | |
| 15 | // adds it. Linux only; abstract sockets are a Linux extension. | |
| 16 | ||
| 17 | const EventEmitter = require('bare-events') | |
| 18 | const Pipe = require('bare-pipe') | |
| 19 | const binding = require('./binding') | |
| 20 | ||
| 21 | const DEFAULT_BACKLOG = 511 | |
| 22 | ||
| 23 | class AbstractServer extends EventEmitter { | |
| 24 | constructor() { | |
| 25 | super() | |
| 26 | ||
| 27 | this._handle = null | |
| 28 | this._name = null | |
| 29 | this.listening = false | |
| 30 | } | |
| 31 | ||
| 32 | listen(name, backlog = DEFAULT_BACKLOG) { | |
| 33 | if (this._handle !== null) throw new Error('Server is already listening') | |
| 34 | ||
| 35 | // `this` is the ctx; _onConnection is invoked as (err, fd) per accepted conn. | |
| 36 | this._handle = binding.listen(name, backlog, this, this._onConnection) | |
| 37 | this._name = name | |
| 38 | this.listening = true | |
| 39 | ||
| 40 | queueMicrotask(() => this.emit('listening')) | |
| 41 | ||
| 42 | return this | |
| 43 | } | |
| 44 | ||
| 45 | // Called from native on each accepted connection (and on poll error). | |
| 46 | _onConnection(err, fd) { | |
| 47 | if (err) { | |
| 48 | this.emit('error', new Error('abstract socket poll failed: ' + err)) | |
| 49 | return | |
| 50 | } | |
| 51 | ||
| 52 | // Wrap the accepted fd with stock bare-pipe — it does the stream I/O. | |
| 53 | const socket = new Pipe(fd) | |
| 54 | this.emit('connection', socket) | |
| 55 | } | |
| 56 | ||
| 57 | close() { | |
| 58 | if (this._handle === null) return | |
| 59 | ||
| 60 | binding.close(this._handle) | |
| 61 | this._handle = null | |
| 62 | this.listening = false | |
| 63 | ||
| 64 | this.emit('close') | |
| 65 | } | |
| 66 | } | |
| 67 | ||
| 68 | function connect(name) { | |
| 69 | const fd = binding.connect(name) | |
| 70 | return new Pipe(fd) | |
| 71 | } | |
| 72 | ||
| 73 | module.exports = { AbstractServer, connect } |
| … | … | @@ -0,0 +1,31 @@ |
| 1 | { | |
| 2 | "name": "bare-abstract-socket", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "Linux abstract-namespace AF_UNIX sockets for bare — creates the fds, stock bare-pipe does the I/O", | |
| 5 | "exports": { | |
| 6 | "./package": "./package.json", | |
| 7 | ".": "./index.js" | |
| 8 | }, | |
| 9 | "files": [ | |
| 10 | "index.js", | |
| 11 | "binding.c", | |
| 12 | "binding.js", | |
| 13 | "CMakeLists.txt", | |
| 14 | "prebuilds" | |
| 15 | ], | |
| 16 | "addon": true, | |
| 17 | "scripts": { | |
| 18 | "test": "bare test.js" | |
| 19 | }, | |
| 20 | "license": "Apache-2.0", | |
| 21 | "engines": { | |
| 22 | "bare": ">=1.16.0" | |
| 23 | }, | |
| 24 | "dependencies": { | |
| 25 | "bare-events": "^2.0.0", | |
| 26 | "bare-pipe": "^4.2.2" | |
| 27 | }, | |
| 28 | "devDependencies": { | |
| 29 | "cmake-bare": "^1.1.6" | |
| 30 | } | |
| 31 | } |
| … | … | @@ -0,0 +1,45 @@ |
| 1 | // Isolated proof: abstract-namespace server + client over our companion, with | |
| 2 | // ALL stream I/O done by stock bare-pipe (new Pipe(fd)). Echo round-trip + a | |
| 3 | // check that no filesystem path was created (true abstract socket). | |
| 4 | const { AbstractServer, connect } = require('./index.js') | |
| 5 | ||
| 6 | const NAME = 'bare-abstract-companion-proof' | |
| 7 | ||
| 8 | let done = false | |
| 9 | function finish(code, msg) { | |
| 10 | if (done) return | |
| 11 | done = true | |
| 12 | if (msg) console.log(msg) | |
| 13 | try { server.close() } catch {} | |
| 14 | Bare.exit(code) | |
| 15 | } | |
| 16 | ||
| 17 | const server = new AbstractServer() | |
| 18 | ||
| 19 | server.on('error', (err) => finish(1, 'SERVER ERROR: ' + err.message)) | |
| 20 | ||
| 21 | server.on('connection', (socket) => { | |
| 22 | console.log('server: accepted a connection (fd wrapped by stock bare-pipe)') | |
| 23 | socket.on('data', (data) => { | |
| 24 | socket.write(Buffer.concat([Buffer.from('echo:'), data])) | |
| 25 | }) | |
| 26 | socket.on('error', () => {}) | |
| 27 | }) | |
| 28 | ||
| 29 | server.on('listening', () => { | |
| 30 | console.log('server: listening on abstract name "' + NAME + '"') | |
| 31 | const socket = connect(NAME) | |
| 32 | socket.on('error', (err) => finish(1, 'CLIENT ERROR: ' + err.message)) | |
| 33 | socket.on('data', (data) => { | |
| 34 | const s = data.toString() | |
| 35 | console.log('client: received "' + s + '"') | |
| 36 | if (s === 'echo:hello') finish(0, 'PASS: abstract round-trip works via stock bare-pipe I/O') | |
| 37 | else finish(1, 'FAIL: unexpected "' + s + '"') | |
| 38 | }) | |
| 39 | socket.write(Buffer.from('hello')) | |
| 40 | console.log('client: connected + sent "hello"') | |
| 41 | }) | |
| 42 | ||
| 43 | server.listen(NAME) | |
| 44 | ||
| 45 | setTimeout(() => finish(1, 'TIMEOUT — no round-trip'), 3000) |