├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── Cargo.toml ├── Cross.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── get_sockets_info_benchmark.rs ├── build.rs └── src ├── integrations ├── linux │ ├── api.rs │ ├── ext │ │ ├── mod.rs │ │ └── tcp_state_ext.rs │ ├── mod.rs │ ├── netlink_iterator.rs │ └── procfs.rs ├── mod.rs ├── osx │ ├── api.rs │ ├── ext │ │ ├── mod.rs │ │ └── tcp_state_ext.rs │ ├── mod.rs │ └── netstat.rs ├── shared_api.rs └── windows │ ├── api.rs │ ├── ext │ ├── mod.rs │ └── tcp_state_ext.rs │ ├── ffi │ ├── enums.rs │ ├── iphlpapi.rs │ ├── mod.rs │ ├── structs.rs │ ├── structs_extended.rs │ └── types.rs │ ├── mod.rs │ ├── socket_table.rs │ ├── socket_table_extended.rs │ └── socket_table_iterator.rs ├── lib.rs └── types ├── address_family.rs ├── error.rs ├── mod.rs ├── protocol.rs ├── socket_info.rs └── tcp_state.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | strategy: 10 | matrix: 11 | os: 12 | - ubuntu-latest 13 | - macos-latest 14 | - windows-latest 15 | runs-on: ${{ matrix.os }} 16 | env: 17 | RUST_BACKTRACE: 1 18 | steps: 19 | - uses: actions/checkout@v1 20 | with: 21 | submodules: true 22 | 23 | - uses: actions-rs/toolchain@v1 24 | with: 25 | profile: minimal 26 | toolchain: "1.82.0" 27 | default: true 28 | 29 | - run: cargo test 30 | 31 | - name: run cross build for Andriod (on Linux) 32 | run: | 33 | cargo install cross 34 | cross build --target aarch64-linux-android 35 | if: matrix.os == 'ubuntu-latest' 36 | 37 | - name: run cross build for iOS (on macOS) 38 | run: | 39 | cargo install cross 40 | rustup target add aarch64-apple-ios 41 | cross build --target aarch64-apple-ios 42 | if: matrix.os == 'macos-latest' 43 | 44 | - name: setup zig for cargo-zigbuild (on macOS) 45 | uses: mlugg/setup-zig@v1 46 | if: matrix.os == 'macos-latest' 47 | 48 | - name: run cargo-zigbuild for Linux (on macOS) 49 | run: | 50 | cargo install --locked cargo-zigbuild 51 | rustup target add aarch64-unknown-linux-gnu 52 | cargo zigbuild --target aarch64-unknown-linux-gnu 53 | if: matrix.os == 'macos-latest' 54 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | with: 12 | submodules: true 13 | 14 | - name: Publish to crates.io 15 | run: cargo publish --token "${CARGO_TOKEN}" --no-verify 16 | env: 17 | CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | cargo_home 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "netstat2" 3 | version = "0.11.1" 4 | authors = ["Ohad Ravid ", "ivxvm "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/ohadravid/netstat2-rs" 9 | documentation = "https://docs.rs/netstat2" 10 | categories = ["network-programming", "os"] 11 | keywords = ["network", "socket"] 12 | description = """ 13 | Cross-platform library to retrieve network sockets information. 14 | """ 15 | 16 | [dependencies] 17 | bitflags = "2" 18 | thiserror = "2" 19 | 20 | [dev-dependencies] 21 | criterion = { version = "0.4" } 22 | 23 | [[bench]] 24 | name = "get_sockets_info_benchmark" 25 | harness = false 26 | 27 | [target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "linux", target_os = "android"))'.build-dependencies] 28 | bindgen = "0.71" 29 | 30 | [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] 31 | num-derive = "0.3" 32 | num-traits = "0.2.8" 33 | byteorder = "1.3.2" 34 | 35 | [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] 36 | netlink-sys = "0.8" 37 | netlink-packet-core = "0.7" 38 | netlink-packet-utils = "0.5" 39 | netlink-packet-sock-diag = "0.4" -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [build.env] 2 | passthrough = [ 3 | "RUST_BACKTRACE", 4 | "RUST_LOG", 5 | "RUST_DEBUG", 6 | "CI", 7 | ] -------------------------------------------------------------------------------- /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 2018-NOW ivxvm 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) 2018 ivxvm 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | netstat2 2 | ======= 3 | [![Latest version](https://img.shields.io/crates/v/netstat2.svg)](https://crates.io/crates/netstat2) 4 | [![Documentation](https://docs.rs/netstat2/badge.svg)](https://docs.rs/netstat2) 5 | ![License](https://img.shields.io/crates/l/netstat2.svg) 6 | 7 | Cross-platform library to retrieve network sockets information. 8 | Aims to be optimal by using low-level OS APIs instead of command line utilities. 9 | Provides unified interface and returns data structures which may have additional fields depending on platform. 10 | 11 | ```toml 12 | # Cargo.toml 13 | [dependencies] 14 | netstat2 = "0.11" 15 | ``` 16 | 17 | This is a fork based on the [netstat](https://crates.io/crates/netstat) crate by [ivxvm](https://github.com/ivxvm). 18 | 19 | ## Example 20 | 21 | ```rust 22 | use netstat2::{get_sockets_info, AddressFamilyFlags, ProtocolFlags, ProtocolSocketInfo}; 23 | 24 | fn main() -> Result<(), Box> { 25 | let af_flags = AddressFamilyFlags::IPV4 | AddressFamilyFlags::IPV6; 26 | let proto_flags = ProtocolFlags::TCP | ProtocolFlags::UDP; 27 | let sockets_info = get_sockets_info(af_flags, proto_flags)?; 28 | 29 | for si in sockets_info { 30 | match si.protocol_socket_info { 31 | ProtocolSocketInfo::Tcp(tcp_si) => println!( 32 | "TCP {}:{} -> {}:{} {:?} - {}", 33 | tcp_si.local_addr, 34 | tcp_si.local_port, 35 | tcp_si.remote_addr, 36 | tcp_si.remote_port, 37 | si.associated_pids, 38 | tcp_si.state 39 | ), 40 | ProtocolSocketInfo::Udp(udp_si) => println!( 41 | "UDP {}:{} -> *:* {:?}", 42 | udp_si.local_addr, udp_si.local_port, si.associated_pids 43 | ), 44 | } 45 | } 46 | 47 | Ok(()) 48 | } 49 | ``` 50 | 51 | ## Details 52 | 53 | - On Windows, this library library uses [GetExtendedTcpTable](https://docs.microsoft.com/en-us/windows/desktop/api/iphlpapi/nf-iphlpapi-getextendedtcptable) & [GetExtendedUdpTable](https://docs.microsoft.com/en-us/windows/desktop/api/iphlpapi/nf-iphlpapi-getextendedudptable) (iphlpapi), 54 | with an option to use the older [GetTcpTable](https://docs.microsoft.com/en-us/windows/desktop/api/iphlpapi/nf-iphlpapi-gettcptable) & [GeUdpTable](https://docs.microsoft.com/en-us/windows/desktop/api/iphlpapi/nf-iphlpapi-getudptable). 55 | - On Linux and Android, it uses [NETLINK_INET_DIAG](http://manpages.ubuntu.com/manpages/bionic/en/man7/sock_diag.7.html) protocol and performs pid lookup by traversing `procfs`. 56 | - On macOS and iOS, it uses [proc_pidfdinfo](https://opensource.apple.com/source/xnu/xnu-1504.7.4/bsd/kern/proc_info.c.auto.html). 57 | 58 | ## License 59 | 60 | Licensed under either of: 61 | 62 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 63 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 64 | 65 | ## Contribution 66 | 67 | Unless you explicitly state otherwise, any contribution intentionally submitted 68 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 69 | additional terms or conditions. 70 | -------------------------------------------------------------------------------- /benches/get_sockets_info_benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use netstat2::*; 3 | 4 | fn criterion_benchmark(c: &mut Criterion) { 5 | c.bench_function("get_sockets_info", |b| b.iter(|| { 6 | let af_flags = AddressFamilyFlags::IPV4 | AddressFamilyFlags::IPV6; 7 | let proto_flags = ProtocolFlags::TCP | ProtocolFlags::UDP; 8 | let _result = get_sockets_info(black_box(af_flags), proto_flags).unwrap(); 9 | })); 10 | } 11 | 12 | criterion_group!(benches, criterion_benchmark); 13 | criterion_main!(benches); -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::Path; 3 | 4 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "linux", target_os = "android"))] 5 | fn main() { 6 | let os = env::var("CARGO_CFG_TARGET_OS").unwrap(); 7 | 8 | match os.as_str() { 9 | "macos" | "ios" => { 10 | let bindings = bindgen::builder() 11 | .header_contents("libproc_rs.h", "#include ") 12 | .layout_tests(false) 13 | .clang_args(&[ 14 | "-x", 15 | "c++", 16 | "-I", 17 | "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/", 18 | ]) 19 | .generate() 20 | .expect("Failed to build libproc bindings"); 21 | 22 | let output_path = 23 | Path::new(&env::var("OUT_DIR").expect("OUT_DIR env var was not defined")) 24 | .join("libproc_bindings.rs"); 25 | 26 | bindings 27 | .write_to_file(output_path) 28 | .expect("Failed to write libproc bindings"); 29 | } 30 | _ => {} 31 | } 32 | } 33 | 34 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "linux", target_os = "android")))] 35 | fn main() {} -------------------------------------------------------------------------------- /src/integrations/linux/api.rs: -------------------------------------------------------------------------------- 1 | use crate::integrations::linux::netlink_iterator::*; 2 | use crate::integrations::linux::procfs::*; 3 | use crate::types::error::Error; 4 | use crate::types::*; 5 | use netlink_packet_sock_diag::{AF_INET, AF_INET6, IPPROTO_TCP, IPPROTO_UDP}; 6 | 7 | /// Iterate through sockets information. 8 | pub fn iterate_sockets_info( 9 | af_flags: AddressFamilyFlags, 10 | proto_flags: ProtocolFlags, 11 | ) -> Result>, Error> { 12 | Ok(attach_pids(iterate_sockets_info_without_pids( 13 | af_flags, 14 | proto_flags, 15 | )?)) 16 | } 17 | 18 | /// Iterate through sockets information without attaching PID. 19 | pub fn iterate_sockets_info_without_pids( 20 | af_flags: AddressFamilyFlags, 21 | proto_flags: ProtocolFlags, 22 | ) -> Result>, Error> { 23 | let ipv4 = af_flags.contains(AddressFamilyFlags::IPV4); 24 | let ipv6 = af_flags.contains(AddressFamilyFlags::IPV6); 25 | let tcp = proto_flags.contains(ProtocolFlags::TCP); 26 | let udp = proto_flags.contains(ProtocolFlags::UDP); 27 | let mut iterators = Vec::with_capacity(4); 28 | if ipv4 { 29 | if tcp { 30 | iterators.push(NetlinkIterator::new(AF_INET as u8, IPPROTO_TCP as u8)?); 31 | } 32 | if udp { 33 | iterators.push(NetlinkIterator::new(AF_INET as u8, IPPROTO_UDP as u8)?); 34 | } 35 | } 36 | if ipv6 { 37 | if tcp { 38 | iterators.push(NetlinkIterator::new(AF_INET6 as u8, IPPROTO_TCP as u8)?); 39 | } 40 | if udp { 41 | iterators.push(NetlinkIterator::new(AF_INET6 as u8, IPPROTO_UDP as u8)?); 42 | } 43 | } 44 | Ok(iterators.into_iter().flatten()) 45 | } 46 | 47 | fn attach_pids( 48 | sockets_info: impl Iterator>, 49 | ) -> impl Iterator> { 50 | let mut pids_by_inode = build_hash_of_pids_by_inode(); 51 | sockets_info.map(move |r| { 52 | r.map(|socket_info| SocketInfo { 53 | associated_pids: pids_by_inode 54 | .remove(&socket_info.inode) 55 | .unwrap_or_default() 56 | .iter() 57 | .map(|x| *x) 58 | .collect(), 59 | ..socket_info 60 | }) 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /src/integrations/linux/ext/mod.rs: -------------------------------------------------------------------------------- 1 | mod tcp_state_ext; 2 | 3 | pub use self::tcp_state_ext::*; 4 | -------------------------------------------------------------------------------- /src/integrations/linux/ext/tcp_state_ext.rs: -------------------------------------------------------------------------------- 1 | use crate::types::TcpState; 2 | 3 | impl From for TcpState { 4 | fn from(tcp_state: u8) -> TcpState { 5 | match tcp_state { 6 | 1 => TcpState::Established, 7 | 2 => TcpState::SynSent, 8 | 3 => TcpState::SynReceived, 9 | 4 => TcpState::FinWait1, 10 | 5 => TcpState::FinWait2, 11 | 6 => TcpState::TimeWait, 12 | 7 => TcpState::Closed, 13 | 8 => TcpState::CloseWait, 14 | 9 => TcpState::LastAck, 15 | 10 => TcpState::Listen, 16 | 11 => TcpState::Closing, 17 | _ => TcpState::Unknown, 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/integrations/linux/mod.rs: -------------------------------------------------------------------------------- 1 | mod api; 2 | mod ext; 3 | mod netlink_iterator; 4 | mod procfs; 5 | 6 | pub use self::api::*; 7 | -------------------------------------------------------------------------------- /src/integrations/linux/netlink_iterator.rs: -------------------------------------------------------------------------------- 1 | //! This implementation is based on the `examples/dump_ipv4.rs` from `https://github.com/rust-netlink/netlink-packet-sock-diag`. 2 | use crate::types::error::*; 3 | use crate::types::*; 4 | use std; 5 | use std::io; 6 | use std::mem::size_of; 7 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 8 | 9 | use netlink_packet_core::{ 10 | NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST, 11 | }; 12 | use netlink_packet_sock_diag::inet::InetResponse; 13 | use netlink_packet_sock_diag::{ 14 | constants::*, 15 | inet::{ExtensionFlags, InetRequest, SocketId, StateFlags}, 16 | SockDiagMessage, 17 | }; 18 | use netlink_sys::{protocols::NETLINK_SOCK_DIAG, Socket, SocketAddr}; 19 | 20 | const SOCKET_BUFFER_SIZE: usize = 8192; 21 | 22 | pub struct NetlinkIterator { 23 | protocol: u8, 24 | recv_buf: [u8; SOCKET_BUFFER_SIZE], 25 | socket: Socket, 26 | offset: usize, 27 | size: usize, 28 | is_done: bool, 29 | } 30 | 31 | impl NetlinkIterator { 32 | pub fn new(family: u8, protocol: u8) -> Result { 33 | let mut socket = Socket::new(NETLINK_SOCK_DIAG)?; 34 | let _port_number = socket.bind_auto()?.port_number(); 35 | socket.connect(&SocketAddr::new(0, 0))?; 36 | 37 | let mut nl_hdr = NetlinkHeader::default(); 38 | nl_hdr.flags = NLM_F_REQUEST | NLM_F_DUMP; 39 | let mut packet = NetlinkMessage::new( 40 | nl_hdr, 41 | SockDiagMessage::InetRequest(InetRequest { 42 | family, 43 | protocol, 44 | extensions: ExtensionFlags::empty(), 45 | states: StateFlags::all(), 46 | socket_id: SocketId::new_v4(), 47 | }) 48 | .into(), 49 | ); 50 | 51 | packet.finalize(); 52 | 53 | let mut buf = vec![0; packet.buffer_len()]; 54 | packet.serialize(&mut buf[..]); 55 | socket.send(&buf[..], 0)?; 56 | 57 | Ok(NetlinkIterator { 58 | protocol, 59 | socket, 60 | recv_buf: [0u8; SOCKET_BUFFER_SIZE as usize], 61 | offset: 0, 62 | size: 0, 63 | is_done: false, 64 | }) 65 | } 66 | 67 | fn try_read_next_packet(&mut self) -> Result, Error> { 68 | if self.is_done { 69 | return Ok(None); 70 | } 71 | 72 | loop { 73 | if self.offset >= self.size { 74 | self.size = self.socket.recv(&mut &mut self.recv_buf[..], 0)?; 75 | self.offset = 0; 76 | } 77 | 78 | let bytes = &self.recv_buf[self.offset..self.size]; 79 | 80 | let rx_packet: NetlinkMessage = 81 | match NetlinkMessage::deserialize(bytes) { 82 | Ok(rx_packet) => rx_packet, 83 | Err(e) => { 84 | // Avoid endless loop in case of a deserialization failure. 85 | self.is_done = true; 86 | return Err(Error::from(e)); 87 | } 88 | }; 89 | self.offset += rx_packet.header.length as usize; 90 | 91 | match rx_packet.payload { 92 | NetlinkPayload::Noop => {} 93 | NetlinkPayload::InnerMessage(SockDiagMessage::InetResponse(response)) => { 94 | return Ok(Some(parse_diag_msg(&response, self.protocol)?)); 95 | } 96 | NetlinkPayload::Done(_) => { 97 | self.is_done = true; 98 | return Ok(None); 99 | } 100 | _ => return Ok(None), 101 | } 102 | } 103 | } 104 | } 105 | 106 | impl Iterator for NetlinkIterator { 107 | type Item = Result; 108 | 109 | fn next(&mut self) -> Option { 110 | self.try_read_next_packet().transpose() 111 | } 112 | } 113 | 114 | fn parse_diag_msg(diag_msg: &InetResponse, protocol: u8) -> Result { 115 | let src_port = diag_msg.header.socket_id.source_port; 116 | let dst_port = diag_msg.header.socket_id.destination_port; 117 | let src_ip = diag_msg.header.socket_id.source_address; 118 | let dst_ip = diag_msg.header.socket_id.destination_address; 119 | 120 | let sock_info = match protocol { 121 | IPPROTO_TCP => SocketInfo { 122 | protocol_socket_info: ProtocolSocketInfo::Tcp(TcpSocketInfo { 123 | local_addr: src_ip, 124 | local_port: src_port, 125 | remote_addr: dst_ip, 126 | remote_port: dst_port, 127 | state: TcpState::from(diag_msg.header.state), 128 | }), 129 | associated_pids: vec![], 130 | inode: diag_msg.header.inode, 131 | uid: diag_msg.header.uid, 132 | }, 133 | IPPROTO_UDP => SocketInfo { 134 | protocol_socket_info: ProtocolSocketInfo::Udp(UdpSocketInfo { 135 | local_addr: src_ip, 136 | local_port: src_port, 137 | }), 138 | associated_pids: vec![], 139 | inode: diag_msg.header.inode, 140 | uid: diag_msg.header.uid, 141 | }, 142 | _ => return Err(Error::UnknownProtocol(protocol)), 143 | }; 144 | 145 | Ok(sock_info) 146 | } 147 | -------------------------------------------------------------------------------- /src/integrations/linux/procfs.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::collections::HashSet; 3 | use std::fs::{read_dir, read_link}; 4 | 5 | pub fn build_hash_of_pids_by_inode() -> HashMap> { 6 | let pids = read_dir("/proc/") 7 | .expect("Can't read /proc/") 8 | .filter_map(|d| d.ok()?.file_name().to_str()?.parse::().ok()); 9 | let mut pid_by_inode = HashMap::new(); 10 | for pid in pids { 11 | if let Result::Ok(fds) = read_dir(format!("/proc/{}/fd", pid)) { 12 | let inodes = fds.filter_map(|fd| { 13 | let fd_file_name = fd.ok()?.file_name(); 14 | let fd_str = fd_file_name.to_str()?; 15 | let path_buf = read_link(format!("/proc/{}/fd/{}", pid, fd_str)).ok()?; 16 | let link_str = path_buf.to_str()?; 17 | if link_str.starts_with("socket:[") { 18 | let inode_str = &link_str[8..link_str.len() - 1]; 19 | inode_str.parse::().ok() 20 | } else { 21 | Option::None 22 | } 23 | }); 24 | for inode in inodes { 25 | pid_by_inode 26 | .entry(inode) 27 | .and_modify(|v: &mut HashSet| { 28 | v.insert(pid); 29 | }) 30 | .or_insert_with(|| { 31 | let mut s = HashSet::new(); 32 | s.insert(pid); 33 | s 34 | }); 35 | } 36 | } 37 | } 38 | pid_by_inode 39 | } 40 | -------------------------------------------------------------------------------- /src/integrations/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(target_os = "linux", target_os = "android"))] 2 | mod linux; 3 | #[cfg(any(target_os = "macos", target_os = "ios"))] 4 | mod osx; 5 | #[cfg(target_os = "windows")] 6 | mod windows; 7 | 8 | mod shared_api; 9 | pub use self::shared_api::*; 10 | 11 | #[cfg(any(target_os = "linux", target_os = "android"))] 12 | pub use self::linux::*; 13 | #[cfg(any(target_os = "macos", target_os = "ios"))] 14 | pub use self::osx::*; 15 | #[cfg(target_os = "windows")] 16 | pub use self::windows::*; 17 | -------------------------------------------------------------------------------- /src/integrations/osx/api.rs: -------------------------------------------------------------------------------- 1 | use crate::integrations::osx::netstat::*; 2 | use crate::types::error::Error; 3 | use crate::types::*; 4 | 5 | /// Iterate through sockets information. 6 | pub fn iterate_sockets_info( 7 | af_flags: AddressFamilyFlags, 8 | proto_flags: ProtocolFlags, 9 | ) -> Result>, Error> { 10 | iterate_netstat_info(af_flags, proto_flags) 11 | } 12 | -------------------------------------------------------------------------------- /src/integrations/osx/ext/mod.rs: -------------------------------------------------------------------------------- 1 | mod tcp_state_ext; 2 | 3 | pub use self::tcp_state_ext::*; 4 | -------------------------------------------------------------------------------- /src/integrations/osx/ext/tcp_state_ext.rs: -------------------------------------------------------------------------------- 1 | use crate::types::TcpState; 2 | 3 | impl<'a> From<&'a str> for TcpState { 4 | fn from(tcp_state: &'a str) -> TcpState { 5 | match tcp_state { 6 | "CLOSED" => TcpState::Closed, 7 | "LISTEN" => TcpState::Listen, 8 | "SYN_SENT" => TcpState::SynSent, 9 | "SYN_RCVD" => TcpState::SynReceived, 10 | "ESTABLISHED" => TcpState::Established, 11 | "FIN_WAIT_1" => TcpState::FinWait1, 12 | "FIN_WAIT_2" => TcpState::FinWait2, 13 | "CLOSE_WAIT" => TcpState::CloseWait, 14 | "CLOSING" => TcpState::Closing, 15 | "LAST_ACK" => TcpState::LastAck, 16 | "TIME_WAIT" => TcpState::TimeWait, 17 | _ => TcpState::Unknown, 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/integrations/osx/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(non_upper_case_globals)] 2 | #[allow(non_camel_case_types)] 3 | #[allow(non_snake_case)] 4 | #[allow(unused)] 5 | mod libproc_bindings { 6 | include!(concat!(env!("OUT_DIR"), "/libproc_bindings.rs")); 7 | } 8 | 9 | mod api; 10 | mod ext; 11 | mod netstat; 12 | 13 | pub use self::api::*; 14 | -------------------------------------------------------------------------------- /src/integrations/osx/netstat.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use std::fmt::{self, Display}; 4 | use std::mem::MaybeUninit; 5 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 6 | use std::os::raw::{c_int, c_uint, c_void}; 7 | use std::ptr; 8 | use std::{io, mem}; 9 | 10 | use byteorder::{ByteOrder, NetworkEndian}; 11 | use num_derive::FromPrimitive; 12 | use num_traits::FromPrimitive; 13 | 14 | use crate::integrations::osx::libproc_bindings::*; 15 | use crate::types::error::Error; 16 | use crate::types::{AddressFamilyFlags, ProtocolFlags}; 17 | use crate::{ProtocolSocketInfo, SocketInfo, TcpSocketInfo, TcpState, UdpSocketInfo}; 18 | 19 | pub type PID = c_int; 20 | 21 | #[repr(C)] 22 | #[derive(Debug, Copy, Clone)] 23 | pub struct ProcFDInfo { 24 | pub proc_fd: i32, 25 | pub proc_fdtype: ProcFDType, 26 | } 27 | 28 | impl Default for ProcFDInfo { 29 | fn default() -> Self { 30 | ProcFDInfo { 31 | proc_fd: 0, 32 | // Atalk == 0 33 | proc_fdtype: ProcFDType::Atalk, 34 | } 35 | } 36 | } 37 | 38 | #[repr(C)] 39 | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, FromPrimitive)] 40 | pub enum ProcType { 41 | ProcAllPIDS = 1, 42 | ProcPGRPOnly = 2, 43 | ProcTTYOnly = 3, 44 | ProcUIDOnly = 4, 45 | ProcRUIDOnly = 5, 46 | ProcPPIDOnly = 6, 47 | } 48 | 49 | #[repr(C)] 50 | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, FromPrimitive)] 51 | pub enum ProcFDType { 52 | Atalk = 0, 53 | Vnode = 1, 54 | Socket = 2, 55 | PSHM = 3, 56 | PSEM = 4, 57 | Kqueue = 5, 58 | Pipe = 6, 59 | FsEvents = 7, 60 | NetPolicy = 9, 61 | } 62 | 63 | // Adapter from proc_info.h 64 | #[repr(C)] 65 | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, FromPrimitive)] 66 | pub enum SockInfo { 67 | Generic = 0, 68 | In = 1, 69 | Tcp = 2, 70 | Un = 3, 71 | Ndrv = 4, 72 | Kern_event = 5, 73 | Kern_ctl = 6, 74 | } 75 | 76 | #[repr(C)] 77 | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, FromPrimitive)] 78 | pub enum SocketFamily { 79 | AF_UNSPEC = 0, 80 | /* unspecified */ 81 | AF_UNIX = 1, 82 | /* local to host (pipes) */ 83 | AF_INET = 2, 84 | /* internetwork: UDP, TCP, etc. */ 85 | AF_IMPLINK = 3, 86 | /* arpanet imp addresses */ 87 | AF_PUP = 4, 88 | /* pup protocols: e.g. BSP */ 89 | AF_CHAOS = 5, 90 | /* mit CHAOS protocols */ 91 | AF_NS = 6, 92 | /* XEROX NS protocols */ 93 | AF_ISO = 7, 94 | /* ISO protocols */ 95 | AF_ECMA = 8, 96 | /* European computer manufacturers */ 97 | AF_DATAKIT = 9, 98 | /* datakit protocols */ 99 | AF_CCITT = 10, 100 | /* CCITT protocols, X.25 etc */ 101 | AF_SNA = 11, 102 | /* IBM SNA */ 103 | AF_DECnet = 12, 104 | /* DECnet */ 105 | AF_DLI = 13, 106 | /* DEC Direct data link interface */ 107 | AF_LAT = 14, 108 | /* LAT */ 109 | AF_HYLINK = 15, 110 | /* NSC Hyperchannel */ 111 | AF_APPLETALK = 16, 112 | /* Apple Talk */ 113 | AF_ROUTE = 17, 114 | /* Internal Routing Protocol */ 115 | AF_LINK = 18, 116 | /* Link layer interface */ 117 | pseudo_AF_XTP = 19, 118 | /* eXpress Transfer Protocol (no AF) */ 119 | AF_COIP = 20, 120 | /* connection-oriented IP, aka ST II */ 121 | AF_CNT = 21, 122 | /* Computer Network Technology */ 123 | pseudo_AF_RTIP = 22, 124 | /* Help Identify RTIP packets */ 125 | AF_IPX = 23, 126 | /* Novell Internet Protocol */ 127 | AF_SIP = 24, 128 | /* Simple Internet Protocol */ 129 | pseudo_AF_PIP = 25, 130 | /* Help Identify PIP packets */ 131 | AF_NDRV = 27, 132 | /* Network Driver 'raw' access */ 133 | AF_ISDN = 28, 134 | /* Integrated Services Digital Network */ 135 | pseudo_AF_KEY = 29, 136 | /* Internal key-management function */ 137 | AF_INET6 = 30, 138 | /* IPv6 */ 139 | AF_NATM = 31, 140 | /* native ATM access */ 141 | AF_SYSTEM = 32, 142 | /* Kernel event messages */ 143 | AF_NETBIOS = 33, 144 | /* NetBIOS */ 145 | AF_PPP = 34, 146 | /* PPP communication protocol */ 147 | pseudo_AF_HDRCMPLT = 35, 148 | /* Used by BPF to not rewrite headers output routine */ 149 | AF_RESERVED_36 = 36, 150 | /* Reserved for internal usage */ 151 | AF_IEEE80211 = 37, 152 | /* IEEE 802.11 protocol */ 153 | AF_UTUN = 38, 154 | AF_MAX = 40, 155 | } 156 | 157 | #[repr(C)] 158 | #[derive(Debug, Copy, Clone)] 159 | pub enum PidInfoFlavor { 160 | // list of struct proc_fdinfo 161 | ListFDs = PROC_PIDLISTFDS as isize, 162 | // struct proc_taskallinfo 163 | TaskAllInfo = PROC_PIDTASKALLINFO as isize, 164 | TBSDInfo = 3, 165 | TaskInfo = 4, 166 | ThreadInfo = 5, 167 | ListThreads = 6, 168 | RegionInfo = 7, 169 | RegionPathInfo = 8, 170 | VNodePathInfo = 9, 171 | ThreadPathInfo = 10, 172 | PathInfo = 11, 173 | WorkQueueInfo = 12, 174 | } 175 | 176 | #[repr(C)] 177 | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, FromPrimitive)] 178 | pub enum TCPSocketState { 179 | CLOSED = 0, 180 | /* closed */ 181 | LISTEN = 1, 182 | /* listening for connection */ 183 | SYN_SENT = 2, 184 | /* active, have sent syn */ 185 | SYN_RECEIVED = 3, 186 | /* have send and received syn */ 187 | ESTABLISHED = 4, 188 | /* established */ 189 | CLOSE_WAIT = 5, 190 | /* rcvd fin, waiting for close */ 191 | FIN_WAIT_1 = 6, 192 | /* have closed, sent fin */ 193 | CLOSING = 7, 194 | /* closed xchd FIN; await FIN ACK */ 195 | LAST_ACK = 8, 196 | /* had fin and close; await FIN ACK */ 197 | FIN_WAIT_2 = 9, 198 | /* have closed, fin is acked */ 199 | TIME_WAIT = 10, 200 | /* in 2*msl quiet wait after close */ 201 | } 202 | 203 | impl From for TcpState { 204 | fn from(s: TCPSocketState) -> Self { 205 | match s { 206 | TCPSocketState::CLOSED => TcpState::Closed, 207 | TCPSocketState::LISTEN => TcpState::Listen, 208 | TCPSocketState::SYN_SENT => TcpState::SynSent, 209 | TCPSocketState::SYN_RECEIVED => TcpState::SynReceived, 210 | TCPSocketState::ESTABLISHED => TcpState::Established, 211 | TCPSocketState::CLOSE_WAIT => TcpState::CloseWait, 212 | TCPSocketState::FIN_WAIT_1 => TcpState::FinWait1, 213 | TCPSocketState::CLOSING => TcpState::Closing, 214 | TCPSocketState::LAST_ACK => TcpState::LastAck, 215 | TCPSocketState::FIN_WAIT_2 => TcpState::FinWait2, 216 | TCPSocketState::TIME_WAIT => TcpState::TimeWait, 217 | } 218 | } 219 | } 220 | 221 | impl ProcFDInfo { 222 | fn try_from_proc_fdinfo(other: proc_fdinfo) -> Result { 223 | Ok(ProcFDInfo { 224 | proc_fd: other.proc_fd, 225 | proc_fdtype: ProcFDType::from_i32(other.proc_fdtype as i32) 226 | .ok_or_else(|| Error::NotAValidFDType(other.proc_fdtype))?, 227 | }) 228 | } 229 | } 230 | 231 | // TODO: This can be extended to hold different kinds of FDInformation (tasks, thread, etc..) 232 | pub enum FDInformation { 233 | SocketInfo(socket_fdinfo), 234 | 235 | #[doc(hidden)] 236 | __Nonexhaustive, 237 | } 238 | 239 | pub fn list_pids(proc_types: ProcType) -> Result, Error> { 240 | let number_of_pids; 241 | 242 | unsafe { 243 | number_of_pids = proc_listpids(proc_types as c_uint, 0, ptr::null_mut(), 0); 244 | } 245 | 246 | if number_of_pids < 0 { 247 | return Err(Error::FailedToListProcesses(io::Error::from_raw_os_error( 248 | number_of_pids, 249 | ))); 250 | } 251 | 252 | let mut pids: Vec = Vec::new(); 253 | pids.resize_with(number_of_pids as usize, Default::default); 254 | 255 | let return_code = unsafe { 256 | proc_listpids( 257 | proc_types as c_uint, 258 | 0, 259 | pids.as_mut_ptr() as *mut c_void, 260 | (pids.len() * mem::size_of::()) as i32, 261 | ) 262 | }; 263 | 264 | if return_code <= 0 { 265 | return Err(Error::FailedToListProcesses(io::Error::from_raw_os_error( 266 | return_code, 267 | ))); 268 | } 269 | 270 | // Sometimes the OS returns excessive zero elements, so we truncate them. 271 | Ok(pids.into_iter().filter(|f| *f > 0).collect()) 272 | } 273 | 274 | pub fn list_all_fds_for_pid(pid: PID) -> Result, Error> { 275 | // We need to call proc_pidinfo twice, one time to get needed buffer size. 276 | // A second time to actually populate buffer. 277 | let buffer_size = unsafe { 278 | proc_pidinfo( 279 | pid as c_int, 280 | PROC_PIDLISTFDS as c_int, 281 | 0, 282 | ptr::null_mut(), 283 | 0, 284 | ) 285 | }; 286 | 287 | if buffer_size <= 0 { 288 | return Err(Error::FailedToListProcesses(io::Error::from_raw_os_error( 289 | buffer_size, 290 | ))); 291 | } 292 | 293 | let number_of_fds = buffer_size as usize / mem::size_of::(); 294 | 295 | let mut fds: Vec = Vec::new(); 296 | fds.resize_with(number_of_fds as usize, || proc_fdinfo { 297 | proc_fd: 0, 298 | proc_fdtype: 0, 299 | }); 300 | 301 | let return_code = unsafe { 302 | proc_pidinfo( 303 | pid as c_int, 304 | PROC_PIDLISTFDS as c_int, 305 | 0, 306 | fds.as_mut_ptr() as *mut c_void, 307 | buffer_size, 308 | ) 309 | }; 310 | 311 | if return_code <= 0 { 312 | Err(Error::FailedToListProcesses(io::Error::from_raw_os_error( 313 | return_code, 314 | ))) 315 | } else { 316 | Ok(fds 317 | .into_iter() 318 | .map(|fd| ProcFDInfo::try_from_proc_fdinfo(fd).unwrap_or_default()) 319 | .collect()) 320 | } 321 | } 322 | 323 | pub fn get_fd_information(pid: PID, fd: ProcFDInfo) -> Result { 324 | match fd.proc_fdtype { 325 | ProcFDType::Socket => { 326 | let mut sinfo: MaybeUninit = MaybeUninit::uninit(); 327 | 328 | let return_code = unsafe { 329 | proc_pidfdinfo( 330 | pid, 331 | fd.proc_fd, 332 | PROC_PIDFDSOCKETINFO as i32, 333 | sinfo.as_mut_ptr() as *mut c_void, 334 | mem::size_of::() as i32, 335 | ) 336 | }; 337 | 338 | if return_code < 0 { 339 | Err(Error::FailedToQueryFileDescriptors( 340 | io::Error::from_raw_os_error(return_code), 341 | )) 342 | } else { 343 | Ok(FDInformation::SocketInfo(unsafe { sinfo.assume_init() })) 344 | } 345 | } 346 | _ => Err(Error::UnsupportedFileDescriptor), 347 | } 348 | } 349 | 350 | fn get_local_addr(family: SocketFamily, saddr: in_sockinfo) -> Result { 351 | // Unsafe because of union access, but we check the type of address before accessing. 352 | match family { 353 | SocketFamily::AF_INET => { 354 | let addr = unsafe { saddr.insi_laddr.ina_46.i46a_addr4.s_addr }; 355 | Ok(IpAddr::V4(Ipv4Addr::from(u32::from_be(addr)))) 356 | } 357 | SocketFamily::AF_INET6 => { 358 | let addr = unsafe { &saddr.insi_laddr.ina_6.__u6_addr.__u6_addr8 }; 359 | let mut ipv6_addr = [0_u16; 8]; 360 | NetworkEndian::read_u16_into(addr, &mut ipv6_addr); 361 | Ok(IpAddr::V6(Ipv6Addr::from(ipv6_addr))) 362 | } 363 | _ => Err(Error::UnsupportedSocketFamily(family as u32)), 364 | } 365 | } 366 | 367 | fn get_remote_addr(family: SocketFamily, saddr: in_sockinfo) -> Result { 368 | // Unsafe because of union access, but we check the type of address before accessing. 369 | match family { 370 | SocketFamily::AF_INET => { 371 | let addr = unsafe { saddr.insi_faddr.ina_46.i46a_addr4.s_addr }; 372 | Ok(IpAddr::V4(Ipv4Addr::from(u32::from_be(addr)))) 373 | } 374 | SocketFamily::AF_INET6 => { 375 | let addr = unsafe { &saddr.insi_faddr.ina_6.__u6_addr.__u6_addr8 }; 376 | let mut ipv6_addr = [0_u16; 8]; 377 | NetworkEndian::read_u16_into(addr, &mut ipv6_addr); 378 | Ok(IpAddr::V6(Ipv6Addr::from(ipv6_addr))) 379 | } 380 | _ => Err(Error::UnsupportedSocketFamily(family as u32)), 381 | } 382 | } 383 | 384 | fn parse_tcp_socket_info(pid: PID, fd: ProcFDInfo, sinfo: socket_fdinfo) -> Option { 385 | let sock_info = sinfo.psi; 386 | let family = match SocketFamily::from_i32(sock_info.soi_family) { 387 | Some(family) => family, 388 | None => return None, 389 | }; 390 | let socket_kind = SockInfo::from_i32(sock_info.soi_kind)?; 391 | 392 | // Access to union field in unsafe, but we already checked that this is a TCP connection. 393 | assert!(socket_kind == SockInfo::Tcp); 394 | let tcp_in = unsafe { sock_info.soi_proto.pri_tcp }; 395 | 396 | let tcp_sockaddr_in = tcp_in.tcpsi_ini; 397 | 398 | let connection_state = TCPSocketState::from_i32(tcp_in.tcpsi_state)?; 399 | let remote_address = get_remote_addr(family, tcp_sockaddr_in).ok()?; 400 | let local_address = get_local_addr(family, tcp_sockaddr_in).ok()?; 401 | 402 | let lport_bytes: [u8; 4] = i32::to_le_bytes(tcp_sockaddr_in.insi_lport); 403 | let fport_bytes: [u8; 4] = i32::to_le_bytes(tcp_sockaddr_in.insi_fport); 404 | 405 | let socket_info = TcpSocketInfo { 406 | local_addr: local_address, 407 | local_port: NetworkEndian::read_u16(&lport_bytes), 408 | remote_addr: remote_address, 409 | remote_port: NetworkEndian::read_u16(&fport_bytes), 410 | state: connection_state.into(), 411 | }; 412 | 413 | Some(socket_info) 414 | } 415 | 416 | fn parse_udp_socket_info(pid: PID, fd: ProcFDInfo, sinfo: socket_fdinfo) -> Option { 417 | let sock_info = sinfo.psi; 418 | let family = match SocketFamily::from_i32(sock_info.soi_family) { 419 | Some(family) => family, 420 | None => return None, 421 | }; 422 | let socket_kind = SockInfo::from_i32(sock_info.soi_kind)?; 423 | 424 | // Access to union field in unsafe, but we already checked that this is a In connection. 425 | assert!(socket_kind == SockInfo::In); 426 | let in_socket_info = unsafe { sock_info.soi_proto.pri_in }; 427 | 428 | let local_address = get_local_addr(family, in_socket_info).ok()?; 429 | 430 | let lport_bytes: [u8; 4] = i32::to_le_bytes(in_socket_info.insi_lport); 431 | 432 | let sock_info = UdpSocketInfo { 433 | local_addr: local_address, 434 | local_port: NetworkEndian::read_u16(&lport_bytes), 435 | }; 436 | 437 | Some(sock_info) 438 | } 439 | 440 | pub fn iterate_netstat_info( 441 | af_flags: AddressFamilyFlags, 442 | proto_flags: ProtocolFlags, 443 | ) -> Result>, Error> { 444 | let ipv4 = af_flags.contains(AddressFamilyFlags::IPV4); 445 | let ipv6 = af_flags.contains(AddressFamilyFlags::IPV6); 446 | let tcp = proto_flags.contains(ProtocolFlags::TCP); 447 | let udp = proto_flags.contains(ProtocolFlags::UDP); 448 | 449 | let pids = list_pids(ProcType::ProcAllPIDS)?; 450 | 451 | let mut results = vec![]; 452 | 453 | for pid in pids { 454 | // This will fail on PermissionDenied if we are not sufficiently privileged. 455 | // We do not return on a specific pid failure, 456 | // since some of them may fail randomly (unexpectedly closed etc..) 457 | let fds = match list_all_fds_for_pid(pid) { 458 | Ok(fds) => fds, 459 | Err(e) => { 460 | continue; 461 | } 462 | }; 463 | 464 | for fd in fds { 465 | if fd.proc_fdtype == ProcFDType::Socket { 466 | let fd_information = match get_fd_information(pid, fd) { 467 | Ok(fd_information) => fd_information, 468 | Err(e) => { 469 | results.push(Err(e)); 470 | continue; 471 | } 472 | }; 473 | 474 | match fd_information { 475 | FDInformation::SocketInfo(sinfo) => { 476 | if ipv4 && sinfo.psi.soi_family == AF_INET as i32 477 | || ipv6 && sinfo.psi.soi_family == AF_INET6 as i32 478 | { 479 | if tcp && sinfo.psi.soi_protocol == IPPROTO_TCP as i32 { 480 | if let Some(row) = parse_tcp_socket_info(pid, fd, sinfo) { 481 | results.push(Ok(SocketInfo { 482 | protocol_socket_info: ProtocolSocketInfo::Tcp(row), 483 | associated_pids: vec![pid as u32], 484 | })); 485 | } 486 | } else if udp && sinfo.psi.soi_protocol == IPPROTO_UDP as i32 { 487 | if let Some(row) = parse_udp_socket_info(pid, fd, sinfo) { 488 | results.push(Ok(SocketInfo { 489 | protocol_socket_info: ProtocolSocketInfo::Udp(row), 490 | associated_pids: vec![pid as u32], 491 | })); 492 | } 493 | } 494 | } 495 | } 496 | _ => {} 497 | } 498 | } 499 | } 500 | } 501 | 502 | Ok(results.into_iter()) 503 | } 504 | 505 | #[cfg(test)] 506 | mod tests { 507 | use super::*; 508 | 509 | #[test] 510 | fn test_list_pids() { 511 | println!("{:#?}", list_pids(ProcType::ProcAllPIDS).unwrap()); 512 | assert!(list_pids(ProcType::ProcAllPIDS).unwrap().len() > 5); 513 | } 514 | 515 | #[test] 516 | fn test_list_fds_for_pid() { 517 | let pids = list_pids(ProcType::ProcAllPIDS).unwrap(); 518 | for pid in pids.iter().take(100) { 519 | if let Ok(fds) = list_all_fds_for_pid(*pid) { 520 | println!("{} {:#?}", pid, fds); 521 | assert!(!fds.is_empty()); 522 | } 523 | } 524 | } 525 | 526 | #[test] 527 | fn test_netstat() { 528 | let ns: Vec<_> = iterate_netstat_info(AddressFamilyFlags::all(), ProtocolFlags::all()) 529 | .unwrap() 530 | .collect(); 531 | println!("{:#?}", ns); 532 | assert!(!ns.is_empty()); 533 | } 534 | } 535 | -------------------------------------------------------------------------------- /src/integrations/shared_api.rs: -------------------------------------------------------------------------------- 1 | use crate::integrations::*; 2 | use crate::types::error::Error; 3 | use crate::types::*; 4 | 5 | /// Retrieve sockets information as a vector. 6 | /// Short-circuits on any error along the way. 7 | pub fn get_sockets_info( 8 | af_flags: AddressFamilyFlags, 9 | proto_flags: ProtocolFlags, 10 | ) -> Result, Error> { 11 | iterate_sockets_info(af_flags, proto_flags)?.collect() 12 | } 13 | -------------------------------------------------------------------------------- /src/integrations/windows/api.rs: -------------------------------------------------------------------------------- 1 | use crate::integrations::windows::ffi::*; 2 | use crate::integrations::windows::socket_table_iterator::SocketTableIterator; 3 | use crate::types::error::*; 4 | use crate::types::*; 5 | 6 | /// Iterate through sockets information. 7 | pub fn iterate_sockets_info( 8 | af_flags: AddressFamilyFlags, 9 | proto_flags: ProtocolFlags, 10 | ) -> Result>, Error> { 11 | let ipv4 = af_flags.contains(AddressFamilyFlags::IPV4); 12 | let ipv6 = af_flags.contains(AddressFamilyFlags::IPV6); 13 | let tcp = proto_flags.contains(ProtocolFlags::TCP); 14 | let udp = proto_flags.contains(ProtocolFlags::UDP); 15 | let mut iterators = Vec::with_capacity(4); 16 | if ipv4 { 17 | if tcp { 18 | iterators.push(SocketTableIterator::new::()?); 19 | } 20 | if udp { 21 | iterators.push(SocketTableIterator::new::()?); 22 | } 23 | } 24 | if ipv6 { 25 | if tcp { 26 | iterators.push(SocketTableIterator::new::()?); 27 | } 28 | if udp { 29 | iterators.push(SocketTableIterator::new::()?); 30 | } 31 | } 32 | 33 | Ok(iterators.into_iter().flatten()) 34 | } 35 | 36 | /// Iterate through sockets information. Works on older versions of Windows (like XP and 2003). 37 | pub fn iterate_sockets_info_without_pids( 38 | proto_flags: ProtocolFlags, 39 | ) -> Result>, Error> { 40 | let tcp = proto_flags.contains(ProtocolFlags::TCP); 41 | let udp = proto_flags.contains(ProtocolFlags::UDP); 42 | 43 | let mut iterators = Vec::with_capacity(4); 44 | if tcp { 45 | iterators.push(SocketTableIterator::new::()?); 46 | } 47 | if udp { 48 | iterators.push(SocketTableIterator::new::()?); 49 | } 50 | 51 | Ok(iterators.into_iter().flatten()) 52 | } 53 | -------------------------------------------------------------------------------- /src/integrations/windows/ext/mod.rs: -------------------------------------------------------------------------------- 1 | mod tcp_state_ext; 2 | 3 | pub use self::tcp_state_ext::*; 4 | -------------------------------------------------------------------------------- /src/integrations/windows/ext/tcp_state_ext.rs: -------------------------------------------------------------------------------- 1 | use crate::integrations::windows::ffi; 2 | use crate::types::TcpState; 3 | 4 | impl From for TcpState { 5 | fn from(tcp_state: ffi::DWORD) -> TcpState { 6 | match tcp_state { 7 | ffi::MIB_TCP_STATE_CLOSED => TcpState::Closed, 8 | ffi::MIB_TCP_STATE_LISTEN => TcpState::Listen, 9 | ffi::MIB_TCP_STATE_SYN_SENT => TcpState::SynSent, 10 | ffi::MIB_TCP_STATE_SYN_RCVD => TcpState::SynReceived, 11 | ffi::MIB_TCP_STATE_ESTAB => TcpState::Established, 12 | ffi::MIB_TCP_STATE_FIN_WAIT1 => TcpState::FinWait1, 13 | ffi::MIB_TCP_STATE_FIN_WAIT2 => TcpState::FinWait2, 14 | ffi::MIB_TCP_STATE_CLOSE_WAIT => TcpState::CloseWait, 15 | ffi::MIB_TCP_STATE_CLOSING => TcpState::Closing, 16 | ffi::MIB_TCP_STATE_LAST_ACK => TcpState::LastAck, 17 | ffi::MIB_TCP_STATE_TIME_WAIT => TcpState::TimeWait, 18 | ffi::MIB_TCP_STATE_DELETE_TCB => TcpState::DeleteTcb, 19 | _ => TcpState::Unknown, 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/integrations/windows/ffi/enums.rs: -------------------------------------------------------------------------------- 1 | use crate::integrations::windows::ffi::*; 2 | 3 | pub type BOOL = std::os::raw::c_int; 4 | pub const FALSE: BOOL = 0; 5 | 6 | pub type TCP_TABLE_CLASS = DWORD; 7 | pub const TCP_TABLE_OWNER_PID_ALL: TCP_TABLE_CLASS = 5; 8 | 9 | pub type MIB_TCP_STATE = DWORD; 10 | pub const MIB_TCP_STATE_CLOSED: MIB_TCP_STATE = 1; 11 | pub const MIB_TCP_STATE_LISTEN: MIB_TCP_STATE = 2; 12 | pub const MIB_TCP_STATE_SYN_SENT: MIB_TCP_STATE = 3; 13 | pub const MIB_TCP_STATE_SYN_RCVD: MIB_TCP_STATE = 4; 14 | pub const MIB_TCP_STATE_ESTAB: MIB_TCP_STATE = 5; 15 | pub const MIB_TCP_STATE_FIN_WAIT1: MIB_TCP_STATE = 6; 16 | pub const MIB_TCP_STATE_FIN_WAIT2: MIB_TCP_STATE = 7; 17 | pub const MIB_TCP_STATE_CLOSE_WAIT: MIB_TCP_STATE = 8; 18 | pub const MIB_TCP_STATE_CLOSING: MIB_TCP_STATE = 9; 19 | pub const MIB_TCP_STATE_LAST_ACK: MIB_TCP_STATE = 10; 20 | pub const MIB_TCP_STATE_TIME_WAIT: MIB_TCP_STATE = 11; 21 | pub const MIB_TCP_STATE_DELETE_TCB: MIB_TCP_STATE = 12; 22 | 23 | pub type UDP_TABLE_CLASS = DWORD; 24 | pub const UDP_TABLE_OWNER_PID: UDP_TABLE_CLASS = 1; 25 | 26 | pub type ERROR_CODE = DWORD; 27 | pub const NO_ERROR: ERROR_CODE = 0; 28 | pub const ERROR_INSUFFICIENT_BUFFER: ERROR_CODE = 0x7A; 29 | 30 | pub const AF_INET: ULONG = 2; 31 | pub const AF_INET6: ULONG = 23; 32 | -------------------------------------------------------------------------------- /src/integrations/windows/ffi/iphlpapi.rs: -------------------------------------------------------------------------------- 1 | use crate::integrations::windows::ffi::*; 2 | 3 | #[link(name = "iphlpapi")] 4 | extern "system" { 5 | pub fn GetExtendedTcpTable( 6 | pTcpTable: PVOID, 7 | pdwSize: PDWORD, 8 | bOrder: BOOL, 9 | ulAf: ULONG, 10 | TableClass: TCP_TABLE_CLASS, 11 | Reserved: ULONG, 12 | ) -> DWORD; 13 | pub fn GetExtendedUdpTable( 14 | pUdpTable: PVOID, 15 | pdwSize: PDWORD, 16 | bOrder: BOOL, 17 | ulAf: ULONG, 18 | TableClass: UDP_TABLE_CLASS, 19 | Reserved: ULONG, 20 | ) -> DWORD; 21 | pub fn GetTcpTable(pTcpTable: PVOID, pdwSize: PDWORD, bOrder: BOOL) -> DWORD; 22 | pub fn GetUdpTable(pUdpTable: PVOID, pdwSize: PDWORD, bOrder: BOOL) -> DWORD; 23 | } 24 | -------------------------------------------------------------------------------- /src/integrations/windows/ffi/mod.rs: -------------------------------------------------------------------------------- 1 | mod enums; 2 | mod iphlpapi; 3 | mod structs; 4 | mod structs_extended; 5 | mod types; 6 | 7 | pub use self::enums::*; 8 | pub use self::iphlpapi::*; 9 | pub use self::structs::*; 10 | pub use self::structs_extended::*; 11 | pub use self::types::*; 12 | -------------------------------------------------------------------------------- /src/integrations/windows/ffi/structs.rs: -------------------------------------------------------------------------------- 1 | use crate::integrations::windows::ffi::*; 2 | 3 | #[derive(Copy, Clone, Debug)] 4 | #[repr(C)] 5 | pub struct MIB_UDPTABLE { 6 | pub rows_count: DWORD, 7 | pub rows: [MIB_UDPROW; 1], 8 | } 9 | 10 | #[derive(Copy, Clone, Debug)] 11 | #[repr(C)] 12 | pub struct MIB_UDPROW { 13 | pub local_addr: DWORD, 14 | pub local_port: DWORD, 15 | } 16 | 17 | #[derive(Copy, Clone, Debug)] 18 | #[repr(C)] 19 | pub struct MIB_TCPTABLE { 20 | pub rows_count: DWORD, 21 | pub rows: [MIB_TCPROW; 1], 22 | } 23 | 24 | #[derive(Copy, Clone, Debug)] 25 | #[repr(C)] 26 | pub struct MIB_TCPROW { 27 | pub state: DWORD, 28 | pub local_addr: DWORD, 29 | pub local_port: DWORD, 30 | pub remote_addr: DWORD, 31 | pub remote_port: DWORD, 32 | } 33 | -------------------------------------------------------------------------------- /src/integrations/windows/ffi/structs_extended.rs: -------------------------------------------------------------------------------- 1 | use crate::integrations::windows::ffi::*; 2 | 3 | #[derive(Copy, Clone, Debug)] 4 | #[repr(C)] 5 | pub struct MIB_UDPTABLE_OWNER_PID { 6 | pub rows_count: DWORD, 7 | pub rows: [MIB_UDPROW_OWNER_PID; 1], 8 | } 9 | 10 | #[derive(Copy, Clone, Debug)] 11 | #[repr(C)] 12 | pub struct MIB_UDP6TABLE_OWNER_PID { 13 | pub rows_count: DWORD, 14 | pub rows: [MIB_UDP6ROW_OWNER_PID; 1], 15 | } 16 | 17 | #[derive(Copy, Clone, Debug)] 18 | #[repr(C)] 19 | pub struct MIB_UDPROW_OWNER_PID { 20 | pub local_addr: DWORD, 21 | pub local_port: DWORD, 22 | pub owning_pid: DWORD, 23 | } 24 | 25 | #[derive(Copy, Clone, Debug)] 26 | #[repr(C)] 27 | pub struct MIB_UDP6ROW_OWNER_PID { 28 | pub local_addr: [UCHAR; 16], 29 | pub local_scope_id: DWORD, 30 | pub local_port: DWORD, 31 | pub owning_pid: DWORD, 32 | } 33 | 34 | #[derive(Copy, Clone, Debug)] 35 | #[repr(C)] 36 | pub struct MIB_TCPTABLE_OWNER_PID { 37 | pub rows_count: DWORD, 38 | pub rows: [MIB_TCPROW_OWNER_PID; 1], 39 | } 40 | 41 | #[derive(Copy, Clone, Debug)] 42 | #[repr(C)] 43 | pub struct MIB_TCP6TABLE_OWNER_PID { 44 | pub rows_count: DWORD, 45 | pub rows: [MIB_TCP6ROW_OWNER_PID; 1], 46 | } 47 | 48 | #[derive(Copy, Clone, Debug)] 49 | #[repr(C)] 50 | pub struct MIB_TCPROW_OWNER_PID { 51 | pub state: DWORD, 52 | pub local_addr: DWORD, 53 | pub local_port: DWORD, 54 | pub remote_addr: DWORD, 55 | pub remote_port: DWORD, 56 | pub owning_pid: DWORD, 57 | } 58 | 59 | #[derive(Copy, Clone, Debug)] 60 | #[repr(C)] 61 | pub struct MIB_TCP6ROW_OWNER_PID { 62 | pub local_addr: [UCHAR; 16], 63 | pub local_scope_id: DWORD, 64 | pub local_port: DWORD, 65 | pub remote_addr: [UCHAR; 16], 66 | pub remote_scope_id: DWORD, 67 | pub remote_port: DWORD, 68 | pub state: DWORD, 69 | pub owning_pid: DWORD, 70 | } 71 | -------------------------------------------------------------------------------- /src/integrations/windows/ffi/types.rs: -------------------------------------------------------------------------------- 1 | pub type PVOID = *mut std::os::raw::c_void; 2 | pub type DWORD = std::os::raw::c_ulong; 3 | pub type PDWORD = *mut DWORD; 4 | pub type ULONG = std::os::raw::c_ulong; 5 | pub type UCHAR = std::os::raw::c_uchar; 6 | -------------------------------------------------------------------------------- /src/integrations/windows/mod.rs: -------------------------------------------------------------------------------- 1 | mod api; 2 | mod ext; 3 | mod ffi; 4 | mod socket_table; 5 | mod socket_table_extended; 6 | mod socket_table_iterator; 7 | 8 | pub use self::api::*; 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | use crate::integrations::windows::ffi::*; 13 | use crate::integrations::windows::socket_table_iterator::SocketTableIterator; 14 | 15 | #[test] 16 | fn test_iterate_over_all_supported_tables() { 17 | let table: Vec<_> = SocketTableIterator::new::() 18 | .unwrap() 19 | .collect(); 20 | assert!(!table.is_empty()); 21 | 22 | let table: Vec<_> = SocketTableIterator::new::() 23 | .unwrap() 24 | .collect(); 25 | assert!(!table.is_empty()); 26 | 27 | let table: Vec<_> = SocketTableIterator::new::() 28 | .unwrap() 29 | .collect(); 30 | assert!(!table.is_empty()); 31 | 32 | let table: Vec<_> = SocketTableIterator::new::() 33 | .unwrap() 34 | .collect(); 35 | assert!(!table.is_empty()); 36 | 37 | // Old API versions. 38 | let table: Vec<_> = SocketTableIterator::new::() 39 | .unwrap() 40 | .collect(); 41 | assert!(!table.is_empty()); 42 | 43 | let table: Vec<_> = SocketTableIterator::new::() 44 | .unwrap() 45 | .collect(); 46 | assert!(!table.is_empty()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/integrations/windows/socket_table.rs: -------------------------------------------------------------------------------- 1 | use crate::integrations::windows::ffi::*; 2 | use crate::integrations::windows::socket_table_extended::SocketTable; 3 | use crate::types::error::*; 4 | use crate::types::*; 5 | use std::net::{IpAddr, Ipv4Addr}; 6 | 7 | impl SocketTable for MIB_TCPTABLE { 8 | fn get_table() -> Result, Error> { 9 | get_tcp_table(AF_INET) 10 | } 11 | fn get_rows_count(table: &[u8]) -> usize { 12 | let table = unsafe { &*(table.as_ptr() as *const MIB_TCPTABLE) }; 13 | table.rows_count as usize 14 | } 15 | fn get_socket_info(table: &[u8], index: usize) -> SocketInfo { 16 | let table = unsafe { &*(table.as_ptr() as *const MIB_TCPTABLE) }; 17 | let rows_ptr = &table.rows[0] as *const MIB_TCPROW; 18 | let row = unsafe { &*rows_ptr.add(index) }; 19 | SocketInfo { 20 | protocol_socket_info: ProtocolSocketInfo::Tcp(TcpSocketInfo { 21 | local_addr: IpAddr::V4(Ipv4Addr::from(u32::from_be(row.local_addr))), 22 | local_port: u16::from_be(row.local_port as u16), 23 | remote_addr: IpAddr::V4(Ipv4Addr::from(u32::from_be(row.remote_addr))), 24 | remote_port: u16::from_be(row.remote_port as u16), 25 | state: TcpState::from(row.state), 26 | }), 27 | associated_pids: vec![], 28 | } 29 | } 30 | } 31 | 32 | impl SocketTable for MIB_UDPTABLE { 33 | fn get_table() -> Result, Error> { 34 | get_udp_table(AF_INET) 35 | } 36 | fn get_rows_count(table: &[u8]) -> usize { 37 | let table = unsafe { &*(table.as_ptr() as *const MIB_UDPTABLE) }; 38 | table.rows_count as usize 39 | } 40 | fn get_socket_info(table: &[u8], index: usize) -> SocketInfo { 41 | let table = unsafe { &*(table.as_ptr() as *const MIB_UDPTABLE) }; 42 | let rows_ptr = &table.rows[0] as *const MIB_UDPROW; 43 | let row = unsafe { &*rows_ptr.add(index) }; 44 | SocketInfo { 45 | protocol_socket_info: ProtocolSocketInfo::Udp(UdpSocketInfo { 46 | local_addr: IpAddr::V4(Ipv4Addr::from(u32::from_be(row.local_addr))), 47 | local_port: u16::from_be(row.local_port as u16), 48 | }), 49 | associated_pids: vec![], 50 | } 51 | } 52 | } 53 | 54 | fn get_tcp_table(_address_family: ULONG) -> Result, Error> { 55 | let mut table_size: DWORD = 0; 56 | let mut err_code = unsafe { GetTcpTable(std::ptr::null_mut(), &mut table_size, FALSE) }; 57 | let mut table = Vec::::new(); 58 | let mut iterations = 0; 59 | while err_code == ERROR_INSUFFICIENT_BUFFER { 60 | table = Vec::::with_capacity(table_size as usize); 61 | err_code = unsafe { GetTcpTable(table.as_mut_ptr() as PVOID, &mut table_size, FALSE) }; 62 | iterations += 1; 63 | if iterations > 100 { 64 | return Result::Err(Error::FailedToAllocateBuffer); 65 | } 66 | } 67 | if err_code == NO_ERROR { 68 | Ok(table) 69 | } else { 70 | Err(Error::FailedToGetTcpTable(err_code as i32)) 71 | } 72 | } 73 | 74 | fn get_udp_table(_address_family: ULONG) -> Result, Error> { 75 | let mut table_size: DWORD = 0; 76 | let mut err_code = unsafe { GetUdpTable(std::ptr::null_mut(), &mut table_size, FALSE) }; 77 | let mut table = Vec::::new(); 78 | let mut iterations = 0; 79 | while err_code == ERROR_INSUFFICIENT_BUFFER { 80 | table = Vec::::with_capacity(table_size as usize); 81 | err_code = unsafe { GetUdpTable(table.as_mut_ptr() as PVOID, &mut table_size, FALSE) }; 82 | iterations += 1; 83 | if iterations > 100 { 84 | return Result::Err(Error::FailedToAllocateBuffer); 85 | } 86 | } 87 | if err_code == NO_ERROR { 88 | Ok(table) 89 | } else { 90 | Err(Error::FailedToGetUdpTable(err_code as i32)) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/integrations/windows/socket_table_extended.rs: -------------------------------------------------------------------------------- 1 | use crate::integrations::windows::ffi::*; 2 | use crate::types::error::*; 3 | use crate::types::*; 4 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 5 | 6 | pub trait SocketTable { 7 | fn get_table() -> Result, Error>; 8 | fn get_rows_count(table: &[u8]) -> usize; 9 | fn get_socket_info(table: &[u8], index: usize) -> SocketInfo; 10 | } 11 | 12 | impl SocketTable for MIB_TCPTABLE_OWNER_PID { 13 | fn get_table() -> Result, Error> { 14 | get_extended_tcp_table(AF_INET) 15 | } 16 | fn get_rows_count(table: &[u8]) -> usize { 17 | let table = unsafe { &*(table.as_ptr() as *const MIB_TCPTABLE_OWNER_PID) }; 18 | table.rows_count as usize 19 | } 20 | fn get_socket_info(table: &[u8], index: usize) -> SocketInfo { 21 | let table = unsafe { &*(table.as_ptr() as *const MIB_TCPTABLE_OWNER_PID) }; 22 | let rows_ptr = &table.rows[0] as *const MIB_TCPROW_OWNER_PID; 23 | let row = unsafe { &*rows_ptr.add(index) }; 24 | SocketInfo { 25 | protocol_socket_info: ProtocolSocketInfo::Tcp(TcpSocketInfo { 26 | local_addr: IpAddr::V4(Ipv4Addr::from(u32::from_be(row.local_addr))), 27 | local_port: u16::from_be(row.local_port as u16), 28 | remote_addr: IpAddr::V4(Ipv4Addr::from(u32::from_be(row.remote_addr))), 29 | remote_port: u16::from_be(row.remote_port as u16), 30 | state: TcpState::from(row.state), 31 | }), 32 | associated_pids: vec![row.owning_pid], 33 | } 34 | } 35 | } 36 | 37 | impl SocketTable for MIB_TCP6TABLE_OWNER_PID { 38 | fn get_table() -> Result, Error> { 39 | get_extended_tcp_table(AF_INET6) 40 | } 41 | fn get_rows_count(table: &[u8]) -> usize { 42 | let table = unsafe { &*(table.as_ptr() as *const MIB_TCP6TABLE_OWNER_PID) }; 43 | table.rows_count as usize 44 | } 45 | fn get_socket_info(table: &[u8], index: usize) -> SocketInfo { 46 | let table = unsafe { &*(table.as_ptr() as *const MIB_TCP6TABLE_OWNER_PID) }; 47 | let rows_ptr = &table.rows[0] as *const MIB_TCP6ROW_OWNER_PID; 48 | let row = unsafe { &*rows_ptr.add(index) }; 49 | SocketInfo { 50 | protocol_socket_info: ProtocolSocketInfo::Tcp(TcpSocketInfo { 51 | local_addr: IpAddr::V6(Ipv6Addr::from(row.local_addr)), 52 | // local_scope: Option::Some(row.local_scope_id), 53 | local_port: u16::from_be(row.local_port as u16), 54 | remote_addr: IpAddr::V6(Ipv6Addr::from(row.remote_addr)), 55 | // remote_scope: Option::Some(row.remote_scope_id), 56 | remote_port: u16::from_be(row.remote_port as u16), 57 | state: TcpState::from(row.state), 58 | }), 59 | associated_pids: vec![row.owning_pid], 60 | } 61 | } 62 | } 63 | 64 | impl SocketTable for MIB_UDPTABLE_OWNER_PID { 65 | fn get_table() -> Result, Error> { 66 | get_extended_udp_table(AF_INET) 67 | } 68 | fn get_rows_count(table: &[u8]) -> usize { 69 | let table = unsafe { &*(table.as_ptr() as *const MIB_UDPTABLE_OWNER_PID) }; 70 | table.rows_count as usize 71 | } 72 | fn get_socket_info(table: &[u8], index: usize) -> SocketInfo { 73 | let table = unsafe { &*(table.as_ptr() as *const MIB_UDPTABLE_OWNER_PID) }; 74 | let rows_ptr = &table.rows[0] as *const MIB_UDPROW_OWNER_PID; 75 | let row = unsafe { &*rows_ptr.add(index) }; 76 | SocketInfo { 77 | protocol_socket_info: ProtocolSocketInfo::Udp(UdpSocketInfo { 78 | local_addr: IpAddr::V4(Ipv4Addr::from(u32::from_be(row.local_addr))), 79 | local_port: u16::from_be(row.local_port as u16), 80 | }), 81 | associated_pids: vec![row.owning_pid], 82 | } 83 | } 84 | } 85 | 86 | impl SocketTable for MIB_UDP6TABLE_OWNER_PID { 87 | fn get_table() -> Result, Error> { 88 | get_extended_udp_table(AF_INET6) 89 | } 90 | fn get_rows_count(table: &[u8]) -> usize { 91 | let table = unsafe { &*(table.as_ptr() as *const MIB_UDP6TABLE_OWNER_PID) }; 92 | table.rows_count as usize 93 | } 94 | fn get_socket_info(table: &[u8], index: usize) -> SocketInfo { 95 | let table = unsafe { &*(table.as_ptr() as *const MIB_UDP6TABLE_OWNER_PID) }; 96 | let rows_ptr = &table.rows[0] as *const MIB_UDP6ROW_OWNER_PID; 97 | let row = unsafe { &*rows_ptr.add(index) }; 98 | SocketInfo { 99 | protocol_socket_info: ProtocolSocketInfo::Udp(UdpSocketInfo { 100 | local_addr: IpAddr::V6(Ipv6Addr::from(row.local_addr)), 101 | // local_scope: Option::Some(row.local_scope_id), 102 | local_port: u16::from_be(row.local_port as u16), 103 | }), 104 | associated_pids: vec![row.owning_pid], 105 | } 106 | } 107 | } 108 | 109 | fn get_extended_tcp_table(address_family: ULONG) -> Result, Error> { 110 | let mut table_size: DWORD = 0; 111 | let mut err_code = unsafe { 112 | GetExtendedTcpTable( 113 | std::ptr::null_mut(), 114 | &mut table_size, 115 | FALSE, 116 | address_family, 117 | TCP_TABLE_OWNER_PID_ALL, 118 | 0, 119 | ) 120 | }; 121 | let mut table = Vec::::new(); 122 | let mut iterations = 0; 123 | while err_code == ERROR_INSUFFICIENT_BUFFER { 124 | table = Vec::::with_capacity(table_size as usize); 125 | err_code = unsafe { 126 | GetExtendedTcpTable( 127 | table.as_mut_ptr() as PVOID, 128 | &mut table_size, 129 | FALSE, 130 | address_family, 131 | TCP_TABLE_OWNER_PID_ALL, 132 | 0, 133 | ) 134 | }; 135 | iterations += 1; 136 | if iterations > 100 { 137 | return Result::Err(Error::FailedToAllocateBuffer); 138 | } 139 | } 140 | if err_code == NO_ERROR { 141 | Ok(table) 142 | } else { 143 | Err(Error::FailedToGetTcpTable(err_code as i32)) 144 | } 145 | } 146 | 147 | fn get_extended_udp_table(address_family: ULONG) -> Result, Error> { 148 | let mut table_size: DWORD = 0; 149 | let mut err_code = unsafe { 150 | GetExtendedUdpTable( 151 | std::ptr::null_mut(), 152 | &mut table_size, 153 | FALSE, 154 | address_family, 155 | UDP_TABLE_OWNER_PID, 156 | 0, 157 | ) 158 | }; 159 | let mut table = Vec::::new(); 160 | let mut iterations = 0; 161 | while err_code == ERROR_INSUFFICIENT_BUFFER { 162 | table = Vec::::with_capacity(table_size as usize); 163 | err_code = unsafe { 164 | GetExtendedUdpTable( 165 | table.as_mut_ptr() as PVOID, 166 | &mut table_size, 167 | FALSE, 168 | address_family, 169 | UDP_TABLE_OWNER_PID, 170 | 0, 171 | ) 172 | }; 173 | iterations += 1; 174 | if iterations > 100 { 175 | return Result::Err(Error::FailedToAllocateBuffer); 176 | } 177 | } 178 | if err_code == NO_ERROR { 179 | Ok(table) 180 | } else { 181 | Err(Error::FailedToGetUdpTable(err_code as i32)) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/integrations/windows/socket_table_iterator.rs: -------------------------------------------------------------------------------- 1 | use crate::integrations::windows::socket_table_extended::SocketTable; 2 | use crate::types::error::*; 3 | use crate::types::*; 4 | 5 | pub struct SocketTableIterator { 6 | table: Vec, 7 | rows_count: usize, 8 | current_row_index: usize, 9 | info_getter: fn(&[u8], usize) -> SocketInfo, 10 | } 11 | 12 | impl SocketTableIterator { 13 | pub fn new() -> Result { 14 | let table = Table::get_table()?; 15 | Ok(SocketTableIterator { 16 | rows_count: Table::get_rows_count(&table), 17 | info_getter: Table::get_socket_info, 18 | current_row_index: 0, 19 | table, 20 | }) 21 | } 22 | } 23 | 24 | impl Iterator for SocketTableIterator { 25 | type Item = Result; 26 | fn next(&mut self) -> Option { 27 | if self.current_row_index == self.rows_count { 28 | None 29 | } else { 30 | let socket_info = (self.info_getter)(&self.table, self.current_row_index); 31 | self.current_row_index += 1; 32 | Some(Ok(socket_info)) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Cross-platform library to retrieve network sockets information. 2 | //! Tries to be optimal by using low-level OS APIs instead of command line utilities. 3 | //! Provides unified interface and returns data structures which may have additional fields depending on platform. 4 | //! 5 | //! # Example 6 | //! 7 | //! ```rust 8 | //! use netstat2::*; 9 | //! 10 | //! # fn main() -> Result<(), netstat2::error::Error> { 11 | //! let af_flags = AddressFamilyFlags::IPV4 | AddressFamilyFlags::IPV6; 12 | //! let proto_flags = ProtocolFlags::TCP | ProtocolFlags::UDP; 13 | //! let sockets_info = get_sockets_info(af_flags, proto_flags)?; 14 | //! 15 | //! for si in sockets_info { 16 | //! match si.protocol_socket_info { 17 | //! ProtocolSocketInfo::Tcp(tcp_si) => println!( 18 | //! "TCP {}:{} -> {}:{} {:?} - {}", 19 | //! tcp_si.local_addr, 20 | //! tcp_si.local_port, 21 | //! tcp_si.remote_addr, 22 | //! tcp_si.remote_port, 23 | //! si.associated_pids, 24 | //! tcp_si.state 25 | //! ), 26 | //! ProtocolSocketInfo::Udp(udp_si) => println!( 27 | //! "UDP {}:{} -> *:* {:?}", 28 | //! udp_si.local_addr, udp_si.local_port, si.associated_pids 29 | //! ), 30 | //! } 31 | //! } 32 | //! # Ok(()) 33 | //! # } 34 | //! ``` 35 | #![allow(non_camel_case_types)] 36 | #![allow(unused_imports)] 37 | 38 | #[macro_use] 39 | extern crate bitflags; 40 | 41 | mod integrations; 42 | mod types; 43 | 44 | pub use crate::integrations::*; 45 | pub use crate::types::*; 46 | 47 | #[doc = include_str!("../README.md")] 48 | #[cfg(doctest)] 49 | pub struct ReadmeDoctests; 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, TcpListener, TcpStream, UdpSocket}; 55 | use std::process; 56 | 57 | #[test] 58 | fn listening_tcp_socket_is_found() { 59 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 60 | 61 | let open_port = listener.local_addr().unwrap().port(); 62 | let pid = process::id(); 63 | 64 | let af_flags = AddressFamilyFlags::all(); 65 | let proto_flags = ProtocolFlags::TCP; 66 | 67 | let sock_info = get_sockets_info(af_flags, proto_flags).unwrap(); 68 | 69 | assert!(!sock_info.is_empty()); 70 | 71 | let sock = sock_info 72 | .into_iter() 73 | .find(|s| { 74 | s.associated_pids.contains(&pid) 75 | && matches!(s.protocol_socket_info, 76 | ProtocolSocketInfo::Tcp(TcpSocketInfo { 77 | local_port, 78 | local_addr: IpAddr::V4(Ipv4Addr::LOCALHOST), 79 | state: TcpState::Listen, 80 | .. 81 | }) if local_port == open_port) 82 | }) 83 | .unwrap(); 84 | 85 | assert_eq!( 86 | sock.protocol_socket_info, 87 | ProtocolSocketInfo::Tcp(TcpSocketInfo { 88 | local_addr: IpAddr::V4(Ipv4Addr::LOCALHOST), 89 | local_port: open_port, 90 | remote_addr: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 91 | remote_port: 0, 92 | state: TcpState::Listen, 93 | }) 94 | ); 95 | assert_eq!(sock.associated_pids, vec![pid]); 96 | } 97 | 98 | #[test] 99 | fn listening_tcp_socket_ipv6_is_found() { 100 | let listener = TcpListener::bind("::1:0").unwrap(); 101 | 102 | let open_port = listener.local_addr().unwrap().port(); 103 | let pid = process::id(); 104 | 105 | let af_flags = AddressFamilyFlags::all(); 106 | let proto_flags = ProtocolFlags::TCP; 107 | 108 | let sock_info = get_sockets_info(af_flags, proto_flags).unwrap(); 109 | 110 | assert!(!sock_info.is_empty()); 111 | 112 | let sock = sock_info 113 | .into_iter() 114 | .find(|s| { 115 | s.associated_pids.contains(&pid) 116 | && matches!(s.protocol_socket_info, 117 | ProtocolSocketInfo::Tcp(TcpSocketInfo { 118 | local_port, 119 | local_addr: IpAddr::V6(Ipv6Addr::LOCALHOST), 120 | state: TcpState::Listen, 121 | .. 122 | }) if local_port == open_port) 123 | }) 124 | .unwrap(); 125 | 126 | assert_eq!( 127 | sock.protocol_socket_info, 128 | ProtocolSocketInfo::Tcp(TcpSocketInfo { 129 | local_addr: IpAddr::V6(Ipv6Addr::LOCALHOST), 130 | local_port: open_port, 131 | remote_addr: IpAddr::V6(Ipv6Addr::UNSPECIFIED), 132 | remote_port: 0, 133 | state: TcpState::Listen, 134 | }) 135 | ); 136 | assert_eq!(sock.associated_pids, vec![pid]); 137 | } 138 | 139 | #[test] 140 | fn listening_udp_socket_is_found() { 141 | let listener = UdpSocket::bind("127.0.0.1:0").unwrap(); 142 | 143 | let open_port = listener.local_addr().unwrap().port(); 144 | let pid = process::id(); 145 | 146 | let af_flags = AddressFamilyFlags::all(); 147 | let proto_flags = ProtocolFlags::UDP; 148 | 149 | let sock_info = get_sockets_info(af_flags, proto_flags).unwrap(); 150 | 151 | assert!(!sock_info.is_empty()); 152 | 153 | let sock = sock_info 154 | .into_iter() 155 | .find(|s| { 156 | s.associated_pids.contains(&pid) 157 | && matches!(s.protocol_socket_info, 158 | ProtocolSocketInfo::Udp(UdpSocketInfo { 159 | local_port, 160 | local_addr: IpAddr::V4(Ipv4Addr::LOCALHOST), 161 | .. 162 | }) if local_port == open_port) 163 | }) 164 | .unwrap(); 165 | 166 | assert_eq!( 167 | sock.protocol_socket_info, 168 | ProtocolSocketInfo::Udp(UdpSocketInfo { 169 | local_addr: IpAddr::V4(Ipv4Addr::LOCALHOST), 170 | local_port: open_port, 171 | }) 172 | ); 173 | assert_eq!(sock.associated_pids, vec![pid]); 174 | } 175 | 176 | #[test] 177 | fn listening_all_tcp_socket_is_found() { 178 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 179 | let _ipv6_listener = TcpListener::bind("::1:0").unwrap(); 180 | let _udp_listener = UdpSocket::bind("127.0.0.1:0").unwrap(); 181 | 182 | let open_port = listener.local_addr().unwrap().port(); 183 | 184 | let connection = TcpStream::connect(listener.local_addr().unwrap()).unwrap(); 185 | let pid = process::id(); 186 | 187 | let af_flags = AddressFamilyFlags::all(); 188 | let proto_flags = ProtocolFlags::TCP | ProtocolFlags::UDP; 189 | 190 | let sock_info = get_sockets_info(af_flags, proto_flags).unwrap(); 191 | 192 | assert!(!sock_info.is_empty()); 193 | 194 | let sock = sock_info 195 | .iter() 196 | .find(|s| { 197 | s.associated_pids.contains(&pid) 198 | && matches!(s.protocol_socket_info, 199 | ProtocolSocketInfo::Tcp(TcpSocketInfo { 200 | local_port, 201 | local_addr: IpAddr::V4(Ipv4Addr::LOCALHOST), 202 | state: TcpState::Listen, 203 | .. 204 | }) if local_port == open_port) 205 | }) 206 | .unwrap(); 207 | 208 | assert_eq!( 209 | sock.protocol_socket_info, 210 | ProtocolSocketInfo::Tcp(TcpSocketInfo { 211 | local_addr: IpAddr::V4(Ipv4Addr::LOCALHOST), 212 | local_port: open_port, 213 | remote_addr: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 214 | remote_port: 0, 215 | state: TcpState::Listen, 216 | }) 217 | ); 218 | assert_eq!(sock.associated_pids, vec![pid]); 219 | 220 | let sock = sock_info 221 | .iter() 222 | .find(|s| { 223 | s.associated_pids.contains(&pid) 224 | && matches!(s.protocol_socket_info, 225 | ProtocolSocketInfo::Tcp(TcpSocketInfo { 226 | remote_port, 227 | local_addr: IpAddr::V4(Ipv4Addr::LOCALHOST), 228 | state: TcpState::Established, 229 | .. 230 | }) if remote_port == open_port) 231 | }) 232 | .unwrap(); 233 | 234 | assert_eq!( 235 | sock.protocol_socket_info, 236 | ProtocolSocketInfo::Tcp(TcpSocketInfo { 237 | local_addr: IpAddr::V4(Ipv4Addr::LOCALHOST), 238 | local_port: connection.local_addr().unwrap().port(), 239 | remote_addr: IpAddr::V4(Ipv4Addr::LOCALHOST), 240 | remote_port: open_port, 241 | state: TcpState::Established, 242 | }) 243 | ); 244 | assert_eq!(sock.associated_pids, vec![pid]); 245 | } 246 | 247 | #[test] 248 | fn listening_tcp_socket_is_found_with_many_connections() { 249 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 250 | 251 | let open_port = listener.local_addr().unwrap().port(); 252 | 253 | let sessions: Vec<_> = (0..128) 254 | .map(|_| TcpStream::connect(listener.local_addr().unwrap()).unwrap()) 255 | .collect(); 256 | 257 | let pid = process::id(); 258 | 259 | let af_flags = AddressFamilyFlags::all(); 260 | let proto_flags = ProtocolFlags::TCP; 261 | 262 | let sock_info = get_sockets_info(af_flags, proto_flags).unwrap(); 263 | 264 | assert!(!sock_info.is_empty()); 265 | 266 | assert!(sock_info.len() >= sessions.len() + 1); 267 | 268 | let sock = sock_info 269 | .into_iter() 270 | .find(|s| { 271 | s.associated_pids.contains(&pid) 272 | && matches!(s.protocol_socket_info, 273 | ProtocolSocketInfo::Tcp(TcpSocketInfo { 274 | local_port, 275 | local_addr: IpAddr::V4(Ipv4Addr::LOCALHOST), 276 | state: TcpState::Listen, 277 | .. 278 | }) if local_port == open_port) 279 | }) 280 | .unwrap(); 281 | 282 | assert_eq!( 283 | sock.protocol_socket_info, 284 | ProtocolSocketInfo::Tcp(TcpSocketInfo { 285 | local_addr: IpAddr::V4(Ipv4Addr::LOCALHOST), 286 | local_port: open_port, 287 | remote_addr: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 288 | remote_port: 0, 289 | state: TcpState::Listen, 290 | }) 291 | ); 292 | assert_eq!(sock.associated_pids, vec![pid]); 293 | } 294 | 295 | #[test] 296 | fn result_is_ok_for_any_flags() { 297 | let af_flags_combs = (0..AddressFamilyFlags::all().bits() + 1) 298 | .filter_map(AddressFamilyFlags::from_bits) 299 | .collect::>(); 300 | let proto_flags_combs = (0..ProtocolFlags::all().bits() + 1) 301 | .filter_map(ProtocolFlags::from_bits) 302 | .collect::>(); 303 | for af_flags in af_flags_combs.iter() { 304 | for proto_flags in proto_flags_combs.iter() { 305 | assert!(get_sockets_info(*af_flags, *proto_flags).is_ok()); 306 | } 307 | } 308 | } 309 | 310 | #[test] 311 | fn result_is_empty_for_empty_flags() { 312 | let sockets_info = 313 | get_sockets_info(AddressFamilyFlags::empty(), ProtocolFlags::empty()).unwrap(); 314 | assert!(sockets_info.is_empty()); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/types/address_family.rs: -------------------------------------------------------------------------------- 1 | bitflags! { 2 | /// Set of address families. 3 | #[derive(Debug, Clone, Copy, PartialEq)] 4 | pub struct AddressFamilyFlags: u8 { 5 | const IPV4 = 0b00000001; 6 | const IPV6 = 0b00000010; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/types/error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum Error { 6 | #[error("Failed to call ffi")] 7 | OsError(#[from] io::Error), 8 | 9 | #[error("Unsupported SocketFamily {0}")] 10 | UnsupportedSocketFamily(u32), 11 | 12 | #[error("Failed to list processes")] 13 | FailedToListProcesses(io::Error), 14 | 15 | #[error("Not a valid socket")] 16 | NotAValidSocket, 17 | 18 | #[error("{0} is not a valid proc_fdtype")] 19 | NotAValidFDType(u32), 20 | 21 | #[error("Failed to query file descriptors")] 22 | FailedToQueryFileDescriptors(io::Error), 23 | 24 | #[error("Unsupported file descriptor")] 25 | UnsupportedFileDescriptor, 26 | 27 | #[error("Failed to allocate buffer")] 28 | FailedToAllocateBuffer, 29 | 30 | #[error("Failed to get UDP table")] 31 | FailedToGetTcpTable(i32), 32 | 33 | #[error("Failed to get UDP table")] 34 | FailedToGetUdpTable(i32), 35 | 36 | #[error("NetLink Error")] 37 | NetLinkError, 38 | 39 | #[cfg(any(target_os = "linux", target_os = "android"))] 40 | #[error("NetLink Error")] 41 | NetLinkPacketError(netlink_packet_core::error::ErrorMessage), 42 | 43 | #[cfg(any(target_os = "linux", target_os = "android"))] 44 | #[error("NetLink Decode Error")] 45 | NetLinkPacketDecodeError(#[from] netlink_packet_utils::errors::DecodeError), 46 | 47 | #[error("Found unknown protocol {0}")] 48 | UnknownProtocol(u8), 49 | } 50 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod address_family; 2 | pub mod error; 3 | mod protocol; 4 | mod socket_info; 5 | mod tcp_state; 6 | 7 | pub use self::address_family::*; 8 | pub use self::protocol::*; 9 | pub use self::socket_info::*; 10 | pub use self::tcp_state::*; 11 | -------------------------------------------------------------------------------- /src/types/protocol.rs: -------------------------------------------------------------------------------- 1 | bitflags! { 2 | /// Set of protocols. 3 | #[derive(Debug, Clone, Copy, PartialEq)] 4 | pub struct ProtocolFlags: u8 { 5 | const TCP = 0b00000001; 6 | const UDP = 0b00000010; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/types/socket_info.rs: -------------------------------------------------------------------------------- 1 | use crate::types::tcp_state::TcpState; 2 | use std::net::IpAddr; 3 | 4 | /// General socket information. 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub struct SocketInfo { 7 | /// Protocol-specific socket information. 8 | pub protocol_socket_info: ProtocolSocketInfo, 9 | /// Identifiers of processes associated with this socket. 10 | pub associated_pids: Vec, 11 | #[cfg(any(target_os = "linux", target_os = "android"))] 12 | /// Inode number of this socket. 13 | pub inode: u32, 14 | #[cfg(any(target_os = "linux", target_os = "android"))] 15 | /// Owner UID of this socket. 16 | pub uid: u32, 17 | } 18 | 19 | /// Protocol-specific socket information. 20 | #[derive(Clone, Debug, PartialEq)] 21 | pub enum ProtocolSocketInfo { 22 | /// TCP-specific socket information. 23 | Tcp(TcpSocketInfo), 24 | /// UDP-specific socket information. 25 | Udp(UdpSocketInfo), 26 | } 27 | 28 | /// TCP-specific socket information. 29 | #[derive(Clone, Debug, PartialEq)] 30 | pub struct TcpSocketInfo { 31 | pub local_addr: IpAddr, 32 | pub local_port: u16, 33 | pub remote_addr: IpAddr, 34 | pub remote_port: u16, 35 | pub state: TcpState, 36 | } 37 | 38 | /// UDP-specific socket information. 39 | #[derive(Clone, Debug, PartialEq)] 40 | pub struct UdpSocketInfo { 41 | pub local_addr: IpAddr, 42 | pub local_port: u16, 43 | } 44 | 45 | impl SocketInfo { 46 | /// Local address of this socket. 47 | pub fn local_addr(&self) -> IpAddr { 48 | match &self.protocol_socket_info { 49 | ProtocolSocketInfo::Tcp(s) => s.local_addr, 50 | ProtocolSocketInfo::Udp(s) => s.local_addr, 51 | } 52 | } 53 | 54 | /// Local port of this socket. 55 | pub fn local_port(&self) -> u16 { 56 | match &self.protocol_socket_info { 57 | ProtocolSocketInfo::Tcp(s) => s.local_port, 58 | ProtocolSocketInfo::Udp(s) => s.local_port, 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/types/tcp_state.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// State of TCP connection. 4 | #[derive(Copy, Clone, Debug, PartialEq)] 5 | pub enum TcpState { 6 | Closed, 7 | Listen, 8 | SynSent, 9 | SynReceived, 10 | Established, 11 | FinWait1, 12 | FinWait2, 13 | CloseWait, 14 | Closing, 15 | LastAck, 16 | TimeWait, 17 | DeleteTcb, 18 | Unknown, 19 | } 20 | 21 | impl fmt::Display for TcpState { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 23 | write!( 24 | f, 25 | "{}", 26 | match self { 27 | TcpState::Closed => "CLOSED", 28 | TcpState::Listen => "LISTEN", 29 | TcpState::SynSent => "SYN_SENT", 30 | TcpState::SynReceived => "SYN_RCVD", 31 | TcpState::Established => "ESTABLISHED", 32 | TcpState::FinWait1 => "FIN_WAIT_1", 33 | TcpState::FinWait2 => "FIN_WAIT_2", 34 | TcpState::CloseWait => "CLOSE_WAIT", 35 | TcpState::Closing => "CLOSING", 36 | TcpState::LastAck => "LAST_ACK", 37 | TcpState::TimeWait => "TIME_WAIT", 38 | TcpState::DeleteTcb => "DELETE_TCB", 39 | TcpState::Unknown => "__UNKNOWN", 40 | } 41 | ) 42 | } 43 | } 44 | --------------------------------------------------------------------------------