├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── client.rs ├── server.rs └── spam-clients.sh └── src ├── lib.rs ├── unix.rs └── win.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | pull_request: 5 | env: 6 | CARGO_TERM_COLOR: always 7 | jobs: 8 | build: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, macOS-latest, windows-latest] 13 | steps: 14 | - run: rustc -vV 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: cargo build --verbose 18 | - name: Run tests 19 | run: cargo test --verbose 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .idea -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parity-tokio-ipc" 3 | version = "0.9.0" 4 | edition = "2021" 5 | authors = ["NikVolf "] 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/paritytech/parity-tokio-ipc" 9 | homepage = "https://github.com/paritytech/parity-tokio-ipc" 10 | description = """ 11 | Interprocess communication library for tokio. 12 | """ 13 | 14 | [dependencies] 15 | futures = "0.3" 16 | log = "0.4" 17 | rand = "0.7" 18 | tokio = { version = "1.7.0", features = ["net", "time"] } 19 | libc = "0.2.65" 20 | 21 | [target.'cfg(windows)'.dependencies] 22 | winapi = { version = "0.3", features = ["winbase", "winnt", "accctrl", "aclapi", "securitybaseapi", "minwinbase", "winbase"] } 23 | 24 | [dev-dependencies] 25 | tokio = { version = "1.7.0", features = ["io-util", "rt", "time", "macros"] } 26 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Nikolay Volf 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parity-tokio-ipc 2 | 3 | [![CI](https://github.com/paritytech/parity-tokio-ipc/actions/workflows/ci.yml/badge.svg)](https://github.com/paritytech/parity-tokio-ipc/actions/workflows/ci.yml) 4 | [![Documentation](https://docs.rs/parity-tokio-ipc/badge.svg)](https://docs.rs/parity-tokio-ipc) 5 | 6 | This crate abstracts interprocess transport for UNIX/Windows. 7 | 8 | It utilizes unix sockets on UNIX (via `tokio::net::UnixStream`) and named pipes on windows (via `tokio::net::windows::named_pipe` module). 9 | 10 | Endpoint is transport-agnostic interface for incoming connections: 11 | ```rust 12 | use parity_tokio_ipc::Endpoint; 13 | use futures::stream::StreamExt; 14 | 15 | // For testing purposes only - instead, use a path to an actual socket or a pipe 16 | let addr = parity_tokio_ipc::dummy_endpoint(); 17 | 18 | let server = async move { 19 | Endpoint::new(addr) 20 | .incoming() 21 | .expect("Couldn't set up server") 22 | .for_each(|conn| async { 23 | match conn { 24 | Ok(stream) => println!("Got connection!"), 25 | Err(e) => eprintln!("Error when receiving connection: {:?}", e), 26 | } 27 | }); 28 | }; 29 | 30 | let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); 31 | rt.block_on(server); 32 | ``` 33 | 34 | 35 | # License 36 | 37 | `parity-tokio-ipc` is primarily distributed under the terms of both the MIT 38 | license and the Apache License (Version 2.0), with portions covered by various 39 | BSD-like licenses. 40 | 41 | See LICENSE-APACHE, and LICENSE-MIT for details. 42 | -------------------------------------------------------------------------------- /examples/client.rs: -------------------------------------------------------------------------------- 1 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 2 | use parity_tokio_ipc::Endpoint; 3 | 4 | #[tokio::main(flavor = "current_thread")] 5 | async fn main() { 6 | let path = std::env::args().nth(1).expect("Run it with server path to connect as argument"); 7 | 8 | let mut client = Endpoint::connect(&path).await 9 | .expect("Failed to connect client."); 10 | 11 | loop { 12 | let mut buf = [0u8; 4]; 13 | println!("SEND: PING"); 14 | client.write_all(b"ping").await.expect("Unable to write message to client"); 15 | client.read_exact(&mut buf[..]).await.expect("Unable to read buffer"); 16 | if let Ok("pong") = std::str::from_utf8(&buf[..]) { 17 | println!("RECEIVED: PONG"); 18 | } else { 19 | break; 20 | } 21 | 22 | tokio::time::sleep(std::time::Duration::from_secs(2)).await; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/server.rs: -------------------------------------------------------------------------------- 1 | use futures::StreamExt as _; 2 | use tokio::io::{split, AsyncReadExt, AsyncWriteExt}; 3 | 4 | use parity_tokio_ipc::{Endpoint, SecurityAttributes}; 5 | 6 | async fn run_server(path: String) { 7 | let mut endpoint = Endpoint::new(path); 8 | endpoint.set_security_attributes(SecurityAttributes::allow_everyone_create().unwrap()); 9 | 10 | let incoming = endpoint.incoming().expect("failed to open new socket"); 11 | futures::pin_mut!(incoming); 12 | 13 | while let Some(result) = incoming.next().await 14 | { 15 | match result { 16 | Ok(stream) => { 17 | let (mut reader, mut writer) = split(stream); 18 | 19 | tokio::spawn(async move { 20 | loop { 21 | let mut buf = [0u8; 4]; 22 | let pong_buf = b"pong"; 23 | if let Err(_) = reader.read_exact(&mut buf).await { 24 | println!("Closing socket"); 25 | break; 26 | } 27 | if let Ok("ping") = std::str::from_utf8(&buf[..]) { 28 | println!("RECIEVED: PING"); 29 | writer.write_all(pong_buf).await.expect("unable to write to socket"); 30 | println!("SEND: PONG"); 31 | } 32 | } 33 | }); 34 | } 35 | _ => unreachable!("ideally") 36 | } 37 | }; 38 | } 39 | 40 | #[tokio::main(flavor = "current_thread")] 41 | async fn main() { 42 | let path = std::env::args().nth(1).expect("Run it with server path as argument"); 43 | run_server(path).await 44 | } -------------------------------------------------------------------------------- /examples/spam-clients.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Spawning 100 processes" 4 | for i in {1..100} ; 5 | do 6 | ( cargo run --example client -- /tmp/test.ipc & ) 7 | done -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Tokio IPC transport. Under the hood uses Unix Domain Sockets for Linux/Mac 2 | //! and Named Pipes for Windows. 3 | 4 | #![warn(missing_docs)] 5 | //#![deny(rust_2018_idioms)] 6 | 7 | // Use this directly once Rust 1.54 is stabilized; for some reason going 8 | // indirectly through a macro is okay. 9 | // See https://github.com/rust-lang/rust/issues/78835 10 | macro_rules! doc_comment { 11 | ($x:expr) => { 12 | #[doc = $x] 13 | extern {} 14 | }; 15 | } 16 | 17 | doc_comment!(include_str!("../README.md")); 18 | 19 | #[cfg(windows)] 20 | mod win; 21 | #[cfg(not(windows))] 22 | mod unix; 23 | 24 | /// Endpoint for IPC transport 25 | /// 26 | /// # Examples 27 | /// 28 | /// ```ignore 29 | /// use parity_tokio_ipc::{Endpoint, dummy_endpoint}; 30 | /// use futures::{future, Future, Stream, StreamExt}; 31 | /// use tokio::runtime::Runtime; 32 | /// 33 | /// fn main() { 34 | /// let mut runtime = Runtime::new().unwrap(); 35 | /// let mut endpoint = Endpoint::new(dummy_endpoint()); 36 | /// let server = endpoint.incoming() 37 | /// .expect("failed to open up a new pipe/socket") 38 | /// .for_each(|_stream| { 39 | /// println!("Connection received"); 40 | /// futures::future::ready(()) 41 | /// }); 42 | /// runtime.block_on(server) 43 | /// } 44 | ///``` 45 | #[cfg(windows)] 46 | pub use win::{SecurityAttributes, Endpoint, Connection}; 47 | #[cfg(unix)] 48 | pub use unix::{SecurityAttributes, Endpoint, Connection}; 49 | 50 | /// For testing/examples 51 | pub fn dummy_endpoint() -> String { 52 | let num: u64 = rand::Rng::gen(&mut rand::thread_rng()); 53 | if cfg!(windows) { 54 | format!(r"\\.\pipe\my-pipe-{}", num) 55 | } else { 56 | format!(r"/tmp/my-uds-{}", num) 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use futures::{channel::oneshot, StreamExt as _, FutureExt as _}; 63 | use std::time::Duration; 64 | use tokio::io::{split, AsyncReadExt, AsyncWriteExt}; 65 | 66 | use super::{dummy_endpoint, Endpoint, SecurityAttributes}; 67 | use std::path::Path; 68 | use futures::future::{Either, select, ready}; 69 | 70 | async fn run_server(path: String) { 71 | let path = path.to_owned(); 72 | let mut endpoint = Endpoint::new(path); 73 | 74 | endpoint.set_security_attributes( 75 | SecurityAttributes::empty() 76 | .set_mode(0o777) 77 | .unwrap() 78 | ); 79 | let incoming = endpoint.incoming().expect("failed to open up a new socket"); 80 | futures::pin_mut!(incoming); 81 | 82 | while let Some(result) = incoming.next().await { 83 | match result { 84 | Ok(stream) => { 85 | let (mut reader, mut writer) = split(stream); 86 | let mut buf = [0u8; 5]; 87 | reader.read_exact(&mut buf).await.expect("unable to read from socket"); 88 | writer.write_all(&buf[..]).await.expect("unable to write to socket"); 89 | } 90 | _ => unreachable!("ideally") 91 | } 92 | }; 93 | } 94 | 95 | #[tokio::test] 96 | async fn smoke_test() { 97 | let path = dummy_endpoint(); 98 | let (shutdown_tx, shutdown_rx) = oneshot::channel(); 99 | 100 | let server = select(Box::pin(run_server(path.clone())), shutdown_rx) 101 | .then(|either| { 102 | match either { 103 | Either::Right((_, server)) => { 104 | drop(server); 105 | } 106 | _ => unreachable!("also ideally") 107 | }; 108 | ready(()) 109 | }); 110 | tokio::spawn(server); 111 | 112 | tokio::time::sleep(Duration::from_secs(2)).await; 113 | 114 | println!("Connecting to client 0..."); 115 | let mut client_0 = Endpoint::connect(&path).await 116 | .expect("failed to open client_0"); 117 | tokio::time::sleep(Duration::from_secs(2)).await; 118 | println!("Connecting to client 1..."); 119 | let mut client_1 = Endpoint::connect(&path).await 120 | .expect("failed to open client_1"); 121 | let msg = b"hello"; 122 | 123 | let mut rx_buf = vec![0u8; msg.len()]; 124 | client_0.write_all(msg).await.expect("Unable to write message to client"); 125 | client_0.read_exact(&mut rx_buf).await.expect("Unable to read message from client"); 126 | 127 | let mut rx_buf2 = vec![0u8; msg.len()]; 128 | client_1.write_all(msg).await.expect("Unable to write message to client"); 129 | client_1.read_exact(&mut rx_buf2).await.expect("Unable to read message from client"); 130 | 131 | assert_eq!(rx_buf, msg); 132 | assert_eq!(rx_buf2, msg); 133 | 134 | // shutdown server 135 | if let Ok(()) = shutdown_tx.send(()) { 136 | // wait one second for the file to be deleted. 137 | tokio::time::sleep(Duration::from_secs(1)).await; 138 | let path = Path::new(&path); 139 | // assert that it has 140 | assert!(!path.exists()); 141 | } 142 | } 143 | 144 | #[tokio::test] 145 | async fn incoming_stream_is_static() { 146 | fn is_static(_: T) {} 147 | 148 | let path = dummy_endpoint(); 149 | let endpoint = Endpoint::new(path); 150 | is_static(endpoint.incoming()); 151 | } 152 | 153 | #[cfg(windows)] 154 | fn create_pipe_with_permissions(attr: SecurityAttributes) -> ::std::io::Result<()> { 155 | let path = dummy_endpoint(); 156 | 157 | let mut endpoint = Endpoint::new(path); 158 | endpoint.set_security_attributes(attr); 159 | endpoint.incoming().map(|_| ()) 160 | } 161 | 162 | #[cfg(windows)] 163 | #[tokio::test] 164 | async fn test_pipe_permissions() { 165 | create_pipe_with_permissions(SecurityAttributes::empty()) 166 | .expect("failed with no attributes"); 167 | create_pipe_with_permissions(SecurityAttributes::allow_everyone_create().unwrap()) 168 | .expect("failed with attributes for creating"); 169 | create_pipe_with_permissions(SecurityAttributes::empty().allow_everyone_connect().unwrap()) 170 | .expect("failed with attributes for connecting"); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/unix.rs: -------------------------------------------------------------------------------- 1 | use libc::chmod; 2 | use std::ffi::CString; 3 | use std::io::{self, Error}; 4 | use futures::Stream; 5 | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; 6 | use tokio::net::{UnixListener, UnixStream}; 7 | use std::path::Path; 8 | use std::pin::Pin; 9 | use std::task::{Context, Poll}; 10 | 11 | /// Socket permissions and ownership on UNIX 12 | pub struct SecurityAttributes { 13 | // read/write permissions for owner, group and others in unix octal. 14 | mode: Option 15 | } 16 | 17 | impl SecurityAttributes { 18 | /// New default security attributes. These only allow access by the 19 | /// process’s own user and the system administrator. 20 | pub fn empty() -> Self { 21 | SecurityAttributes { 22 | mode: Some(0o600) 23 | } 24 | } 25 | 26 | /// New security attributes that allow everyone to connect. 27 | pub fn allow_everyone_connect(mut self) -> io::Result { 28 | self.mode = Some(0o666); 29 | Ok(self) 30 | } 31 | 32 | /// Set a custom permission on the socket 33 | pub fn set_mode(mut self, mode: u16) -> io::Result { 34 | self.mode = Some(mode); 35 | Ok(self) 36 | } 37 | 38 | /// New security attributes that allow everyone to create. 39 | /// 40 | /// This does not work on unix, where it is equivalent to 41 | /// [`SecurityAttributes::allow_everyone_connect`]. 42 | pub fn allow_everyone_create() -> io::Result { 43 | Ok(SecurityAttributes { 44 | mode: None 45 | }) 46 | } 47 | 48 | /// called in unix, after server socket has been created 49 | /// will apply security attributes to the socket. 50 | fn apply_permissions(&self, path: &str) -> io::Result<()> { 51 | if let Some(mode) = self.mode { 52 | let path = CString::new(path)?; 53 | if unsafe { chmod(path.as_ptr(), mode.into()) } == -1 { 54 | return Err(Error::last_os_error()); 55 | } 56 | } 57 | 58 | Ok(()) 59 | } 60 | } 61 | 62 | /// Endpoint implementation for unix systems 63 | pub struct Endpoint { 64 | path: String, 65 | security_attributes: SecurityAttributes, 66 | } 67 | 68 | impl Endpoint { 69 | /// Stream of incoming connections 70 | pub fn incoming(self) -> io::Result> + 'static> { 71 | let listener = self.inner()?; 72 | // the call to bind in `inner()` creates the file 73 | // `apply_permission()` will set the file permissions. 74 | self.security_attributes.apply_permissions(&self.path)?; 75 | Ok(Incoming { 76 | path: self.path, 77 | listener, 78 | }) 79 | } 80 | 81 | /// Inner platform-dependant state of the endpoint 82 | fn inner(&self) -> io::Result { 83 | UnixListener::bind(&self.path) 84 | } 85 | 86 | /// Set security attributes for the connection 87 | pub fn set_security_attributes(&mut self, security_attributes: SecurityAttributes) { 88 | self.security_attributes = security_attributes; 89 | } 90 | 91 | /// Make new connection using the provided path and running event pool 92 | pub async fn connect>(path: P) -> io::Result { 93 | Ok(Connection::wrap(UnixStream::connect(path.as_ref()).await?)) 94 | } 95 | 96 | /// Returns the path of the endpoint. 97 | pub fn path(&self) -> &str { 98 | &self.path 99 | } 100 | 101 | /// New IPC endpoint at the given path 102 | pub fn new(path: String) -> Self { 103 | Endpoint { 104 | path, 105 | security_attributes: SecurityAttributes::empty(), 106 | } 107 | } 108 | } 109 | 110 | /// Stream of incoming connections. 111 | /// 112 | /// Removes the bound socket file when dropped. 113 | struct Incoming { 114 | path: String, 115 | listener: UnixListener, 116 | } 117 | 118 | impl Stream for Incoming { 119 | type Item = io::Result; 120 | 121 | fn poll_next( 122 | self: Pin<&mut Self>, 123 | cx: &mut Context<'_>, 124 | ) -> Poll> { 125 | let this = Pin::into_inner(self); 126 | match Pin::new(&mut this.listener).poll_accept(cx) { 127 | Poll::Pending => Poll::Pending, 128 | Poll::Ready(result) => Poll::Ready(Some(result.map(|(stream, _addr)| stream))), 129 | } 130 | } 131 | } 132 | 133 | impl Drop for Incoming { 134 | fn drop(&mut self) { 135 | use std::fs; 136 | if let Ok(()) = fs::remove_file(&self.path) { 137 | log::trace!("Removed socket file at: {}", self.path) 138 | } 139 | } 140 | } 141 | 142 | /// IPC connection. 143 | pub struct Connection { 144 | inner: UnixStream, 145 | } 146 | 147 | impl Connection { 148 | fn wrap(stream: UnixStream) -> Self { 149 | Self { inner: stream } 150 | } 151 | } 152 | 153 | impl AsyncRead for Connection { 154 | fn poll_read( 155 | self: Pin<&mut Self>, 156 | ctx: &mut Context<'_>, 157 | buf: &mut ReadBuf<'_>, 158 | ) -> Poll> { 159 | let this = Pin::into_inner(self); 160 | Pin::new(&mut this.inner).poll_read(ctx, buf) 161 | } 162 | } 163 | 164 | impl AsyncWrite for Connection { 165 | fn poll_write( 166 | self: Pin<&mut Self>, 167 | ctx: &mut Context<'_>, 168 | buf: &[u8], 169 | ) -> Poll> { 170 | let this = Pin::into_inner(self); 171 | Pin::new(&mut this.inner).poll_write(ctx, buf) 172 | } 173 | 174 | fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 175 | let this = Pin::into_inner(self); 176 | Pin::new(&mut this.inner).poll_flush(ctx) 177 | } 178 | 179 | fn poll_shutdown(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 180 | let this = Pin::into_inner(self); 181 | Pin::new(&mut this.inner).poll_shutdown(ctx) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/win.rs: -------------------------------------------------------------------------------- 1 | use winapi::shared::winerror::{ERROR_PIPE_BUSY, ERROR_SUCCESS}; 2 | use winapi::um::accctrl::*; 3 | use winapi::um::aclapi::*; 4 | use winapi::um::minwinbase::{LPTR, PSECURITY_ATTRIBUTES, SECURITY_ATTRIBUTES}; 5 | use winapi::um::securitybaseapi::*; 6 | use winapi::um::winbase::{LocalAlloc, LocalFree}; 7 | use winapi::um::winnt::*; 8 | 9 | use futures::Stream; 10 | use std::io; 11 | use std::marker; 12 | use std::mem; 13 | use std::path::Path; 14 | use std::pin::Pin; 15 | use std::ptr; 16 | use std::task::{Context, Poll}; 17 | use std::time::{Duration, Instant}; 18 | use tokio::io::{AsyncRead, AsyncWrite}; 19 | 20 | use tokio::net::windows::named_pipe; 21 | 22 | enum NamedPipe { 23 | Server(named_pipe::NamedPipeServer), 24 | Client(named_pipe::NamedPipeClient), 25 | } 26 | 27 | const PIPE_AVAILABILITY_TIMEOUT: Duration = Duration::from_secs(5); 28 | 29 | /// Endpoint implementation for windows 30 | pub struct Endpoint { 31 | path: String, 32 | security_attributes: SecurityAttributes, 33 | created_listener: bool, 34 | } 35 | 36 | impl Endpoint { 37 | /// Stream of incoming connections 38 | pub fn incoming( 39 | mut self, 40 | ) -> io::Result> + 'static> { 41 | let pipe = self.create_listener()?; 42 | 43 | let stream = 44 | futures::stream::try_unfold((pipe, self), |(listener, mut endpoint)| async move { 45 | let () = listener.connect().await?; 46 | 47 | let new_listener = endpoint.create_listener()?; 48 | 49 | let conn = Connection::wrap(NamedPipe::Server(listener)); 50 | 51 | Ok(Some((conn, (new_listener, endpoint)))) 52 | }); 53 | 54 | Ok(stream) 55 | } 56 | 57 | fn create_listener(&mut self) -> io::Result { 58 | let server = unsafe { 59 | named_pipe::ServerOptions::new() 60 | .first_pipe_instance(!self.created_listener) 61 | .reject_remote_clients(true) 62 | .access_inbound(true) 63 | .access_outbound(true) 64 | .in_buffer_size(65536) 65 | .out_buffer_size(65536) 66 | .create_with_security_attributes_raw( 67 | &self.path, 68 | self.security_attributes.as_ptr() as *mut libc::c_void, 69 | ) 70 | }?; 71 | self.created_listener = true; 72 | 73 | Ok(server) 74 | } 75 | 76 | /// Set security attributes for the connection 77 | pub fn set_security_attributes(&mut self, security_attributes: SecurityAttributes) { 78 | self.security_attributes = security_attributes; 79 | } 80 | 81 | /// Returns the path of the endpoint. 82 | pub fn path(&self) -> &str { 83 | &self.path 84 | } 85 | 86 | /// Make new connection using the provided path and running event pool. 87 | pub async fn connect>(path: P) -> io::Result { 88 | let path = path.as_ref(); 89 | 90 | // There is not async equivalent of waiting for a named pipe in Windows, 91 | // so we keep trying or sleeping for a bit, until we hit a timeout 92 | let attempt_start = Instant::now(); 93 | let client = loop { 94 | match named_pipe::ClientOptions::new() 95 | .read(true) 96 | .write(true) 97 | .open(path) 98 | { 99 | Ok(client) => break client, 100 | Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) => { 101 | if attempt_start.elapsed() < PIPE_AVAILABILITY_TIMEOUT { 102 | tokio::time::sleep(Duration::from_millis(50)).await; 103 | continue; 104 | } else { 105 | return Err(e); 106 | } 107 | } 108 | Err(e) => return Err(e), 109 | } 110 | }; 111 | 112 | Ok(Connection::wrap(NamedPipe::Client(client))) 113 | } 114 | 115 | /// New IPC endpoint at the given path 116 | pub fn new(path: String) -> Self { 117 | Endpoint { 118 | path, 119 | security_attributes: SecurityAttributes::empty(), 120 | created_listener: false, 121 | } 122 | } 123 | } 124 | 125 | /// IPC connection. 126 | pub struct Connection { 127 | inner: NamedPipe, 128 | } 129 | 130 | impl Connection { 131 | /// Wraps an existing named pipe 132 | fn wrap(pipe: NamedPipe) -> Self { 133 | Self { inner: pipe } 134 | } 135 | } 136 | 137 | impl AsyncRead for Connection { 138 | fn poll_read( 139 | self: Pin<&mut Self>, 140 | ctx: &mut Context<'_>, 141 | buf: &mut tokio::io::ReadBuf<'_>, 142 | ) -> Poll> { 143 | let this = Pin::into_inner(self); 144 | match this.inner { 145 | NamedPipe::Client(ref mut c) => Pin::new(c).poll_read(ctx, buf), 146 | NamedPipe::Server(ref mut s) => Pin::new(s).poll_read(ctx, buf), 147 | } 148 | } 149 | } 150 | 151 | impl AsyncWrite for Connection { 152 | fn poll_write( 153 | self: Pin<&mut Self>, 154 | ctx: &mut Context<'_>, 155 | buf: &[u8], 156 | ) -> Poll> { 157 | let this = Pin::into_inner(self); 158 | match this.inner { 159 | NamedPipe::Client(ref mut c) => Pin::new(c).poll_write(ctx, buf), 160 | NamedPipe::Server(ref mut s) => Pin::new(s).poll_write(ctx, buf), 161 | } 162 | } 163 | 164 | fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 165 | let this = Pin::into_inner(self); 166 | match this.inner { 167 | NamedPipe::Client(ref mut c) => Pin::new(c).poll_flush(ctx), 168 | NamedPipe::Server(ref mut s) => Pin::new(s).poll_flush(ctx), 169 | } 170 | } 171 | 172 | fn poll_shutdown(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 173 | let this = Pin::into_inner(self); 174 | match this.inner { 175 | NamedPipe::Client(ref mut c) => Pin::new(c).poll_shutdown(ctx), 176 | NamedPipe::Server(ref mut s) => Pin::new(s).poll_shutdown(ctx), 177 | } 178 | } 179 | } 180 | 181 | /// Security attributes. 182 | pub struct SecurityAttributes { 183 | attributes: Option, 184 | } 185 | 186 | pub const DEFAULT_SECURITY_ATTRIBUTES: SecurityAttributes = SecurityAttributes { 187 | attributes: Some(InnerAttributes { 188 | descriptor: SecurityDescriptor { 189 | descriptor_ptr: ptr::null_mut(), 190 | }, 191 | acl: Acl { 192 | acl_ptr: ptr::null_mut(), 193 | }, 194 | attrs: SECURITY_ATTRIBUTES { 195 | nLength: mem::size_of::() as u32, 196 | lpSecurityDescriptor: ptr::null_mut(), 197 | bInheritHandle: 0, 198 | }, 199 | }), 200 | }; 201 | 202 | impl SecurityAttributes { 203 | /// New default security attributes. 204 | pub fn empty() -> SecurityAttributes { 205 | DEFAULT_SECURITY_ATTRIBUTES 206 | } 207 | 208 | /// New default security attributes that allow everyone to connect. 209 | pub fn allow_everyone_connect(&self) -> io::Result { 210 | let attributes = Some(InnerAttributes::allow_everyone( 211 | GENERIC_READ | FILE_WRITE_DATA, 212 | )?); 213 | Ok(SecurityAttributes { attributes }) 214 | } 215 | 216 | /// Set a custom permission on the socket 217 | pub fn set_mode(self, _mode: u32) -> io::Result { 218 | // for now, does nothing. 219 | Ok(self) 220 | } 221 | 222 | /// New default security attributes that allow everyone to create. 223 | pub fn allow_everyone_create() -> io::Result { 224 | let attributes = Some(InnerAttributes::allow_everyone( 225 | GENERIC_READ | GENERIC_WRITE, 226 | )?); 227 | Ok(SecurityAttributes { attributes }) 228 | } 229 | 230 | /// Return raw handle of security attributes. 231 | pub(crate) unsafe fn as_ptr(&mut self) -> PSECURITY_ATTRIBUTES { 232 | match self.attributes.as_mut() { 233 | Some(attributes) => attributes.as_ptr(), 234 | None => ptr::null_mut(), 235 | } 236 | } 237 | } 238 | 239 | unsafe impl Send for SecurityAttributes {} 240 | 241 | struct Sid { 242 | sid_ptr: PSID, 243 | } 244 | 245 | impl Sid { 246 | fn everyone_sid() -> io::Result { 247 | let mut sid_ptr = ptr::null_mut(); 248 | let result = unsafe { 249 | #[allow(const_item_mutation)] 250 | AllocateAndInitializeSid( 251 | SECURITY_WORLD_SID_AUTHORITY.as_mut_ptr() as *mut _, 252 | 1, 253 | SECURITY_WORLD_RID, 254 | 0, 255 | 0, 256 | 0, 257 | 0, 258 | 0, 259 | 0, 260 | 0, 261 | &mut sid_ptr, 262 | ) 263 | }; 264 | if result == 0 { 265 | Err(io::Error::last_os_error()) 266 | } else { 267 | Ok(Sid { sid_ptr }) 268 | } 269 | } 270 | 271 | // Unsafe - the returned pointer is only valid for the lifetime of self. 272 | unsafe fn as_ptr(&self) -> PSID { 273 | self.sid_ptr 274 | } 275 | } 276 | 277 | impl Drop for Sid { 278 | fn drop(&mut self) { 279 | if !self.sid_ptr.is_null() { 280 | unsafe { 281 | FreeSid(self.sid_ptr); 282 | } 283 | } 284 | } 285 | } 286 | 287 | struct AceWithSid<'a> { 288 | explicit_access: EXPLICIT_ACCESS_W, 289 | _marker: marker::PhantomData<&'a Sid>, 290 | } 291 | 292 | impl<'a> AceWithSid<'a> { 293 | fn new(sid: &'a Sid, trustee_type: u32) -> AceWithSid<'a> { 294 | let mut explicit_access = unsafe { mem::zeroed::() }; 295 | explicit_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; 296 | explicit_access.Trustee.TrusteeType = trustee_type; 297 | explicit_access.Trustee.ptstrName = unsafe { sid.as_ptr() as *mut _ }; 298 | 299 | AceWithSid { 300 | explicit_access, 301 | _marker: marker::PhantomData, 302 | } 303 | } 304 | 305 | fn set_access_mode(&mut self, access_mode: u32) -> &mut Self { 306 | self.explicit_access.grfAccessMode = access_mode; 307 | self 308 | } 309 | 310 | fn set_access_permissions(&mut self, access_permissions: u32) -> &mut Self { 311 | self.explicit_access.grfAccessPermissions = access_permissions; 312 | self 313 | } 314 | 315 | fn allow_inheritance(&mut self, inheritance_flags: u32) -> &mut Self { 316 | self.explicit_access.grfInheritance = inheritance_flags; 317 | self 318 | } 319 | } 320 | 321 | struct Acl { 322 | acl_ptr: PACL, 323 | } 324 | 325 | impl Acl { 326 | fn empty() -> io::Result { 327 | Self::new(&mut []) 328 | } 329 | 330 | fn new(entries: &mut [AceWithSid<'_>]) -> io::Result { 331 | let mut acl_ptr = ptr::null_mut(); 332 | let result = unsafe { 333 | SetEntriesInAclW( 334 | entries.len() as u32, 335 | entries.as_mut_ptr() as *mut _, 336 | ptr::null_mut(), 337 | &mut acl_ptr, 338 | ) 339 | }; 340 | 341 | if result != ERROR_SUCCESS { 342 | return Err(io::Error::from_raw_os_error(result as i32)); 343 | } 344 | 345 | Ok(Acl { acl_ptr }) 346 | } 347 | 348 | unsafe fn as_ptr(&self) -> PACL { 349 | self.acl_ptr 350 | } 351 | } 352 | 353 | impl Drop for Acl { 354 | fn drop(&mut self) { 355 | if !self.acl_ptr.is_null() { 356 | unsafe { LocalFree(self.acl_ptr as *mut _) }; 357 | } 358 | } 359 | } 360 | 361 | struct SecurityDescriptor { 362 | descriptor_ptr: PSECURITY_DESCRIPTOR, 363 | } 364 | 365 | impl SecurityDescriptor { 366 | fn new() -> io::Result { 367 | let descriptor_ptr = unsafe { LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH) }; 368 | if descriptor_ptr.is_null() { 369 | return Err(io::Error::new( 370 | io::ErrorKind::Other, 371 | "Failed to allocate security descriptor", 372 | )); 373 | } 374 | 375 | if unsafe { 376 | InitializeSecurityDescriptor(descriptor_ptr, SECURITY_DESCRIPTOR_REVISION) == 0 377 | } { 378 | return Err(io::Error::last_os_error()); 379 | }; 380 | 381 | Ok(SecurityDescriptor { descriptor_ptr }) 382 | } 383 | 384 | fn set_dacl(&mut self, acl: &Acl) -> io::Result<()> { 385 | if unsafe { 386 | SetSecurityDescriptorDacl(self.descriptor_ptr, true as i32, acl.as_ptr(), false as i32) 387 | == 0 388 | } { 389 | return Err(io::Error::last_os_error()); 390 | } 391 | Ok(()) 392 | } 393 | 394 | unsafe fn as_ptr(&self) -> PSECURITY_DESCRIPTOR { 395 | self.descriptor_ptr 396 | } 397 | } 398 | 399 | impl Drop for SecurityDescriptor { 400 | fn drop(&mut self) { 401 | if !self.descriptor_ptr.is_null() { 402 | unsafe { LocalFree(self.descriptor_ptr) }; 403 | self.descriptor_ptr = ptr::null_mut(); 404 | } 405 | } 406 | } 407 | 408 | struct InnerAttributes { 409 | descriptor: SecurityDescriptor, 410 | acl: Acl, 411 | attrs: SECURITY_ATTRIBUTES, 412 | } 413 | 414 | impl InnerAttributes { 415 | fn empty() -> io::Result { 416 | let descriptor = SecurityDescriptor::new()?; 417 | let mut attrs = unsafe { mem::zeroed::() }; 418 | attrs.nLength = mem::size_of::() as u32; 419 | attrs.lpSecurityDescriptor = unsafe { descriptor.as_ptr() }; 420 | attrs.bInheritHandle = false as i32; 421 | 422 | let acl = Acl::empty().expect("this should never fail"); 423 | 424 | Ok(InnerAttributes { 425 | acl, 426 | descriptor, 427 | attrs, 428 | }) 429 | } 430 | 431 | fn allow_everyone(permissions: u32) -> io::Result { 432 | let mut attributes = Self::empty()?; 433 | let sid = Sid::everyone_sid()?; 434 | 435 | let mut everyone_ace = AceWithSid::new(&sid, TRUSTEE_IS_WELL_KNOWN_GROUP); 436 | everyone_ace 437 | .set_access_mode(SET_ACCESS) 438 | .set_access_permissions(permissions) 439 | .allow_inheritance(false as u32); 440 | 441 | let mut entries = vec![everyone_ace]; 442 | attributes.acl = Acl::new(&mut entries)?; 443 | attributes.descriptor.set_dacl(&attributes.acl)?; 444 | 445 | Ok(attributes) 446 | } 447 | 448 | unsafe fn as_ptr(&mut self) -> PSECURITY_ATTRIBUTES { 449 | &mut self.attrs as *mut _ 450 | } 451 | } 452 | 453 | #[cfg(test)] 454 | mod test { 455 | use super::SecurityAttributes; 456 | 457 | #[test] 458 | fn test_allow_everyone_everything() { 459 | SecurityAttributes::allow_everyone_create() 460 | .expect("failed to create security attributes that allow everyone to create a pipe"); 461 | } 462 | 463 | #[test] 464 | fn test_allow_eveyone_read_write() { 465 | SecurityAttributes::empty() 466 | .allow_everyone_connect() 467 | .expect("failed to create security attributes that allow everyone to read and write to/from a pipe"); 468 | } 469 | } 470 | --------------------------------------------------------------------------------