├── .cirrus.yml ├── .editorconfig ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── rustfmt.toml └── src ├── compat.rs ├── lib.rs ├── socket.rs ├── sys ├── mod.rs └── unix.rs └── tests.rs /.cirrus.yml: -------------------------------------------------------------------------------- 1 | test_task: 2 | matrix: 3 | - container: 4 | image: rust:latest 5 | - allow_failures: true 6 | container: 7 | image: rustlang/rust:nightly 8 | 9 | cargo_cache: 10 | folder: $CARGO_HOME/registry 11 | build_script: cargo build 12 | test_script: cargo test 13 | before_cache_script: rm -rf $CARGO_HOME/registry/index 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | end_of_line = lf 4 | insert_final_newline = true 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | target 9 | Cargo.lock 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "icmp" 3 | description = "ICMP socket" 4 | version = "0.3.0" 5 | authors = ["svartalf "] 6 | repository = "https://github.com/svartalf/rust-icmp" 7 | keywords = ["icmp", "socket"] 8 | license = "Apache-2.0 OR MIT" 9 | edition = "2018" 10 | 11 | [features] 12 | default = [] 13 | 14 | [dependencies] 15 | libc = "0.2.51" 16 | -------------------------------------------------------------------------------- /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 2019-NOW svartalf 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) 2019 svartalf 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 | # rust-icmp 2 | 3 | Rust ICMP socket. It is intended to provide similar functionality 4 | as a rust' `std::net::TcpStream` / `std::net::UdpSocket` structs. 5 | 6 | ## Compatibility 7 | 8 | Right now it is an early alpha (compatible only with the nightly rust) 9 | and supports only latest Linux platform. 10 | 11 | I will highly appreciate any pull requests and issues (emails are great too!) 12 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | version = "Two" 3 | wrap_comments = true 4 | comment_width = 120 5 | max_width = 120 6 | merge_imports = false 7 | newline_style = "Unix" 8 | struct_lit_single_line = false 9 | -------------------------------------------------------------------------------- /src/compat.rs: -------------------------------------------------------------------------------- 1 | //! Few copy-pasted things from the private Rust modules to mimic core behaviour 2 | //! See `std::sys_common` at https://github.com/rust-lang/rust/tree/master/src/libstd/sys/common 3 | use std::convert::From; 4 | use std::io; 5 | use std::mem; 6 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 7 | use std::time::Duration; 8 | use std::u32; 9 | 10 | use crate::sys::Socket; 11 | 12 | pub trait IsMinusOne { 13 | fn is_minus_one(&self) -> bool; 14 | } 15 | 16 | macro_rules! impl_is_minus_one { 17 | ($($t:ident)*) => ($(impl IsMinusOne for $t { 18 | fn is_minus_one(&self) -> bool { 19 | *self == -1 20 | } 21 | })*) 22 | } 23 | 24 | impl_is_minus_one! { i8 i16 i32 i64 isize } 25 | 26 | pub fn cvt(t: T) -> io::Result { 27 | if t.is_minus_one() { 28 | Err(io::Error::last_os_error()) 29 | } else { 30 | Ok(t) 31 | } 32 | } 33 | 34 | #[doc(hidden)] 35 | pub trait AsInner { 36 | fn as_inner(&self) -> &Inner; 37 | } 38 | 39 | #[doc(hidden)] 40 | pub trait FromInner { 41 | fn from_inner(inner: Inner) -> Self; 42 | } 43 | 44 | #[doc(hidden)] 45 | pub trait IntoInner { 46 | fn into_inner(self) -> Inner; 47 | } 48 | 49 | impl FromInner for IpAddr { 50 | fn from_inner(inner: libc::sockaddr) -> IpAddr { 51 | match inner.sa_family as i32 { 52 | libc::AF_INET => { 53 | // TODO: probably `ref` can be used here 54 | let addr: libc::sockaddr_in = 55 | unsafe { *(&inner as *const _ as *const libc::sockaddr_in) as libc::sockaddr_in }; 56 | IpAddr::V4(Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr))) 57 | } 58 | libc::AF_INET6 => { 59 | // TODO: probably `ref` can be used here 60 | let addr: libc::sockaddr_in6 = 61 | unsafe { *(&inner as *const _ as *const libc::sockaddr_in6) as libc::sockaddr_in6 }; 62 | IpAddr::V6(Ipv6Addr::from(addr.sin6_addr.s6_addr)) 63 | } 64 | _ => unreachable!(), 65 | } 66 | } 67 | } 68 | 69 | impl IntoInner for IpAddr { 70 | fn into_inner(self) -> libc::sockaddr { 71 | match self { 72 | IpAddr::V4(ref a) => { 73 | let ip: u32 = From::from(*a); 74 | 75 | let mut addr: libc::sockaddr_in = unsafe { mem::zeroed() }; 76 | addr.sin_family = libc::AF_INET as libc::sa_family_t; 77 | addr.sin_port = 0 as libc::in_port_t; 78 | addr.sin_addr = libc::in_addr { 79 | s_addr: ip.to_be() as libc::uint32_t, 80 | }; 81 | 82 | unsafe { *(&addr as *const _ as *const libc::sockaddr) as libc::sockaddr } 83 | } 84 | IpAddr::V6(ref a) => { 85 | let mut addr: libc::sockaddr_in6 = unsafe { mem::zeroed() }; 86 | addr.sin6_family = libc::AF_INET6 as libc::sa_family_t; 87 | addr.sin6_addr = unsafe { mem::zeroed() }; 88 | addr.sin6_addr.s6_addr = a.octets(); 89 | 90 | unsafe { *(&addr as *const _ as *const libc::sockaddr) as libc::sockaddr } 91 | } 92 | } 93 | } 94 | } 95 | 96 | pub fn setsockopt(sock: &Socket, opt: libc::c_int, val: libc::c_int, payload: T) -> io::Result<()> { 97 | unsafe { 98 | let payload = &payload as *const T as *const libc::c_void; 99 | cvt(libc::setsockopt( 100 | *sock.as_inner(), 101 | opt, 102 | val, 103 | payload, 104 | mem::size_of::() as libc::socklen_t, 105 | ))?; 106 | Ok(()) 107 | } 108 | } 109 | 110 | pub fn getsockopt(sock: &Socket, opt: libc::c_int, val: libc::c_int) -> io::Result { 111 | unsafe { 112 | let mut slot: T = mem::zeroed(); 113 | let mut len = mem::size_of::() as libc::socklen_t; 114 | cvt(libc::getsockopt( 115 | *sock.as_inner(), 116 | opt, 117 | val, 118 | &mut slot as *mut _ as *mut _, 119 | &mut len, 120 | ))?; 121 | assert_eq!(len as usize, mem::size_of::()); 122 | Ok(slot) 123 | } 124 | } 125 | 126 | /// Based on the rust' `std/sys/unix/net.rs` 127 | pub fn set_timeout(sock: &Socket, dur: Option, kind: libc::c_int) -> io::Result<()> { 128 | let timeout = match dur { 129 | Some(dur) => { 130 | if dur.as_secs() == 0 && dur.subsec_nanos() == 0 { 131 | return Err(io::Error::new( 132 | io::ErrorKind::InvalidInput, 133 | "cannot set a 0 duration timeout", 134 | )); 135 | } 136 | 137 | let secs = if dur.as_secs() > libc::time_t::max_value() as u64 { 138 | libc::time_t::max_value() 139 | } else { 140 | dur.as_secs() as libc::time_t 141 | }; 142 | let mut timeout = libc::timeval { 143 | tv_sec: secs, 144 | tv_usec: (dur.subsec_nanos() / 1000) as libc::suseconds_t, 145 | }; 146 | if timeout.tv_sec == 0 && timeout.tv_usec == 0 { 147 | timeout.tv_usec = 1; 148 | } 149 | timeout 150 | } 151 | None => libc::timeval { 152 | tv_sec: 0, 153 | tv_usec: 0, 154 | }, 155 | }; 156 | setsockopt(sock, libc::SOL_SOCKET, kind, timeout) 157 | } 158 | 159 | /// Based on the rust' `std/sys/unix/net.rs` 160 | pub fn timeout(sock: &Socket, kind: libc::c_int) -> io::Result> { 161 | let raw: libc::timeval = getsockopt(sock, libc::SOL_SOCKET, kind)?; 162 | if raw.tv_sec == 0 && raw.tv_usec == 0 { 163 | Ok(None) 164 | } else { 165 | let sec = raw.tv_sec as u64; 166 | let nsec = (raw.tv_usec as u32) * 1000; 167 | Ok(Some(Duration::new(sec, nsec))) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Raw ICMP socket 2 | 3 | #![deny(missing_docs)] 4 | 5 | mod compat; 6 | mod socket; 7 | 8 | #[cfg(unix)] 9 | #[path = "sys/unix.rs"] 10 | mod sys; 11 | 12 | #[cfg(windows)] 13 | #[path = "sys/mod.rs"] 14 | mod sys; 15 | 16 | pub use socket::IcmpSocket; 17 | 18 | #[cfg(test)] 19 | mod tests; 20 | -------------------------------------------------------------------------------- /src/socket.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | use std::net::IpAddr; 3 | use std::time::Duration; 4 | 5 | use crate::compat::{set_timeout, timeout, AsInner}; 6 | use crate::sys::Socket; 7 | 8 | /// An Internet Control Message Protocol socket. 9 | /// 10 | /// This is an implementation of a bound ICMP socket. This supports both IPv4 and 11 | /// IPv6 addresses, and there is no corresponding notion of a server because ICMP 12 | /// is a datagram protocol. 13 | /// 14 | /// 15 | /// 16 | /// ```rust 17 | /// use icmp; 18 | /// use std::net::{IpAddr, Ipv4Addr}; 19 | /// 20 | /// let localhost_v4 = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); 21 | /// let ping = icmp::IcmpSocket::connect(localhost_v4); 22 | /// let mut ping = ping.unwrap(); 23 | /// 24 | /// let payload: &[u8] = &[1, 2]; 25 | /// 26 | /// let result = ping.send(payload); 27 | /// assert_eq!(result.unwrap(), 2); 28 | /// ``` 29 | /// 30 | /// In case you received an error message, you need to enable the correct capabilites: 31 | /// ```bash 32 | /// cargo build 33 | /// sudo setcap cap_net_raw+ep ./target/debug/PROJECT_NAME 34 | /// cargo run 35 | /// ``` 36 | 37 | // 38 | pub struct IcmpSocket { 39 | inner: Socket, 40 | } 41 | 42 | impl IcmpSocket { 43 | /// Connect socket to `addr` 44 | pub fn connect(addr: IpAddr) -> Result { 45 | let inner = Socket::connect(addr)?; 46 | 47 | Ok(IcmpSocket { 48 | inner, 49 | }) 50 | } 51 | 52 | /// Receives data from the socket. On success, returns the number of bytes read. 53 | pub fn recv(&self, buf: &mut [u8]) -> Result { 54 | self.inner.recv(buf) 55 | } 56 | 57 | /// Receives data from the socket. On success, returns the number of bytes 58 | /// read and the address from whence the data came. 59 | pub fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpAddr)> { 60 | self.inner.recv_from(buf) 61 | } 62 | 63 | /// Sends data on the socket to the remote address to which it is connected. 64 | /// 65 | /// The `connect` method will connect this socket to a remote address. This 66 | /// method will fail if the socket is not connected. 67 | pub fn send(&mut self, buf: &[u8]) -> Result { 68 | self.inner.send(buf) 69 | } 70 | 71 | /// Sets the read timeout to the timeout specified. 72 | /// 73 | /// If the value specified is `None`, then `read` calls will block 74 | /// indefinitely. It is an error to pass the zero `Duration` to this 75 | /// method. 76 | /// 77 | /// # Note 78 | /// 79 | /// Platforms may return a different error code whenever a read times out as 80 | /// a result of setting this option. For example Unix typically returns an 81 | /// error of the kind `WouldBlock`, but Windows may return `TimedOut`. 82 | pub fn set_read_timeout(&self, dur: Option) -> Result<()> { 83 | set_timeout(self.as_inner(), dur, libc::SO_RCVTIMEO) 84 | } 85 | 86 | /// Sets the write timeout to the timeout specified. 87 | /// 88 | /// If the value specified is `None`, then `write` calls will block 89 | /// indefinitely. It is an error to pass the zero `Duration` to this 90 | /// method. 91 | /// 92 | /// # Note 93 | /// 94 | /// Platforms may return a different error code whenever a write times out 95 | /// as a result of setting this option. For example Unix typically returns 96 | /// an error of the kind `WouldBlock`, but Windows may return `TimedOut`. 97 | pub fn set_write_timeout(&self, dur: Option) -> Result<()> { 98 | set_timeout(self.as_inner(), dur, libc::SO_SNDTIMEO) 99 | } 100 | 101 | /// Returns the read timeout of this socket. 102 | /// 103 | /// If the timeout is `None`, then `read` calls will block indefinitely. 104 | pub fn read_timeout(&self) -> Result> { 105 | timeout(self.as_inner(), libc::SO_RCVTIMEO) 106 | } 107 | 108 | /// Returns the write timeout of this socket. 109 | /// 110 | /// If the timeout is `None`, then `write` calls will block indefinitely. 111 | pub fn write_timeout(&self) -> Result> { 112 | timeout(self.as_inner(), libc::SO_SNDTIMEO) 113 | } 114 | 115 | /// Sets the value for the `IP_TTL` option on this socket. 116 | /// 117 | /// This value sets the time-to-live field that is used in every packet sent 118 | /// from this socket. 119 | pub fn set_ttl(&self, ttl: u32) -> Result<()> { 120 | self.inner.set_ttl(ttl) 121 | } 122 | 123 | /// Gets the value of the `IP_TTL` option for this socket. 124 | /// 125 | /// For more information about this option, see [`set_ttl`][link]. 126 | /// 127 | /// [link]: #method.set_ttl 128 | pub fn ttl(&self) -> Result { 129 | self.inner.ttl() 130 | } 131 | 132 | /// Sets the value of the SO_BROADCAST option for this socket. 133 | /// 134 | /// When enabled, this socket is allowed to send packets to a broadcast address. 135 | pub fn set_broadcast(&self, broadcast: bool) -> Result<()> { 136 | self.inner.set_broadcast(broadcast) 137 | } 138 | 139 | /// Gets the value of the `SO_BROADCAST` option for this socket. 140 | /// 141 | /// For more information about this option, see 142 | /// [`set_broadcast`][link]. 143 | /// 144 | /// [link]: #method.set_broadcast 145 | pub fn broadcast(&self) -> Result { 146 | self.inner.broadcast() 147 | } 148 | 149 | /// Sets the QoS value of the `IP_TOS`/`IPV6_TCLASS` option for this socket. 150 | /// 151 | /// This value sets the TOS/DSCP field that is used in every packet sent 152 | /// from this socket. 153 | pub fn set_qos(&self, qos: u8) -> Result<()> { 154 | self.inner.set_qos(qos) 155 | } 156 | 157 | /// Gets the value of the `IP_TOS`/`IPV6_TCLASS` option for this socket. 158 | /// 159 | /// For more information about this option, see 160 | /// [`set_qos`][link]. 161 | /// 162 | /// [link]: #method.set_qos 163 | pub fn qos(&self) -> Result { 164 | self.inner.qos() 165 | } 166 | } 167 | 168 | impl AsInner for IcmpSocket { 169 | fn as_inner(&self) -> &Socket { 170 | &self.inner 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/sys/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/sys/unix.rs: -------------------------------------------------------------------------------- 1 | use std::io::{ErrorKind, Result}; 2 | use std::mem; 3 | use std::net::IpAddr; 4 | 5 | use crate::compat::{cvt, getsockopt, setsockopt, AsInner, FromInner, IntoInner}; 6 | 7 | // Following constants are not defined in libc (as for 0.2.17 version) 8 | const IPPROTO_ICMP: libc::c_int = 1; 9 | // Ipv4 10 | const IP_TOS: libc::c_int = 1; 11 | // Ipv6 12 | const IPV6_UNICAST_HOPS: libc::c_int = 16; 13 | const IPV6_TCLASS: libc::c_int = 67; 14 | 15 | #[cfg(target_os = "linux")] 16 | use libc::SOCK_CLOEXEC; 17 | #[cfg(not(target_os = "linux"))] 18 | const SOCK_CLOEXEC: libc::c_int = 0; 19 | 20 | pub struct Socket { 21 | fd: libc::c_int, 22 | family: libc::c_int, 23 | peer: libc::sockaddr, 24 | } 25 | 26 | impl Socket { 27 | pub fn connect(addr: IpAddr) -> Result { 28 | let family = match addr { 29 | IpAddr::V4(..) => libc::AF_INET, 30 | IpAddr::V6(..) => libc::AF_INET6, 31 | }; 32 | 33 | let fd = unsafe { cvt(libc::socket(family, libc::SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMP))? }; 34 | 35 | Ok(Socket { 36 | fd: fd, 37 | family: family, 38 | peer: addr.into_inner(), 39 | }) 40 | } 41 | 42 | pub fn recv(&self, buf: &mut [u8]) -> Result { 43 | let ret = unsafe { 44 | cvt(libc::recv( 45 | self.fd, 46 | buf.as_mut_ptr() as *mut libc::c_void, 47 | buf.len() as libc::size_t, 48 | 0, 49 | )) 50 | }; 51 | 52 | match ret { 53 | Ok(size) => Ok(size as usize), 54 | Err(ref err) if err.kind() == ErrorKind::Interrupted => Ok(0), 55 | Err(err) => Err(err), 56 | } 57 | } 58 | 59 | pub fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpAddr)> { 60 | let mut peer: libc::sockaddr = unsafe { mem::uninitialized() }; 61 | let ret = unsafe { 62 | cvt(libc::recvfrom( 63 | self.fd, 64 | buf.as_mut_ptr() as *mut libc::c_void, 65 | buf.len() as libc::size_t, 66 | 0, 67 | &mut peer, 68 | &mut (mem::size_of_val(&peer) as libc::socklen_t), 69 | )) 70 | }; 71 | 72 | match ret { 73 | Ok(size) => Ok((size as usize, IpAddr::from_inner(peer))), 74 | Err(ref err) if err.kind() == ErrorKind::Interrupted => Ok((0, IpAddr::from_inner(peer))), 75 | Err(err) => Err(err), 76 | } 77 | } 78 | 79 | pub fn send(&mut self, buf: &[u8]) -> Result { 80 | let ret = unsafe { 81 | cvt(libc::sendto( 82 | self.fd, 83 | buf.as_ptr() as *mut libc::c_void, 84 | buf.len() as libc::size_t, 85 | 0, 86 | &self.peer, 87 | mem::size_of_val(&self.peer) as libc::socklen_t, 88 | ))? 89 | }; 90 | 91 | Ok(ret as usize) 92 | } 93 | 94 | pub fn set_ttl(&self, ttl: u32) -> Result<()> { 95 | match self.family { 96 | libc::AF_INET => setsockopt(self, libc::IPPROTO_IP, libc::IP_TTL, ttl as libc::c_int), 97 | libc::AF_INET6 => setsockopt(self, libc::IPPROTO_IPV6, IPV6_UNICAST_HOPS, ttl as libc::c_int), 98 | _ => unreachable!(), 99 | } 100 | } 101 | 102 | pub fn ttl(&self) -> Result { 103 | match self.family { 104 | libc::AF_INET => getsockopt(self, libc::IPPROTO_IP, libc::IP_TTL), 105 | libc::AF_INET6 => getsockopt(self, libc::IPPROTO_IPV6, IPV6_UNICAST_HOPS), 106 | _ => unreachable!(), 107 | } 108 | } 109 | 110 | pub fn set_broadcast(&self, broadcast: bool) -> Result<()> { 111 | setsockopt(&self, libc::SOL_SOCKET, libc::SO_BROADCAST, broadcast as libc::c_int) 112 | } 113 | 114 | pub fn broadcast(&self) -> Result { 115 | let raw: libc::c_int = getsockopt(&self, libc::SOL_SOCKET, libc::SO_BROADCAST)?; 116 | Ok(raw != 0) 117 | } 118 | 119 | pub fn set_qos(&self, qos: u8) -> Result<()> { 120 | match self.family { 121 | libc::AF_INET => setsockopt(&self, libc::IPPROTO_IP, IP_TOS, qos as libc::c_int), 122 | libc::AF_INET6 => setsockopt(&self, libc::IPPROTO_IPV6, IPV6_TCLASS, qos as libc::c_int), 123 | _ => unreachable!(), 124 | } 125 | } 126 | 127 | pub fn qos(&self) -> Result { 128 | match self.family { 129 | libc::AF_INET => getsockopt(&self, libc::IPPROTO_IP, IP_TOS), 130 | libc::AF_INET6 => getsockopt(&self, libc::IPPROTO_IPV6, IPV6_TCLASS), 131 | _ => unreachable!(), 132 | } 133 | } 134 | } 135 | 136 | impl Drop for Socket { 137 | fn drop(&mut self) { 138 | let _ = unsafe { libc::close(self.fd) }; 139 | } 140 | } 141 | 142 | impl AsInner for Socket { 143 | fn as_inner(&self) -> &libc::c_int { 144 | &self.fd 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 2 | use std::time::Duration; 3 | 4 | use crate::IcmpSocket; 5 | 6 | macro_rules! t { 7 | ($e:expr) => { 8 | match $e { 9 | Ok(t) => t, 10 | Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), 11 | } 12 | }; 13 | } 14 | 15 | fn ipv4() -> IpAddr { 16 | IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)) 17 | } 18 | 19 | fn ipv6() -> IpAddr { 20 | IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)) 21 | } 22 | 23 | #[test] 24 | fn ttl_v4() { 25 | let ttl = 100; 26 | 27 | let socket = t!(IcmpSocket::connect(ipv4())); 28 | t!(socket.set_ttl(ttl)); 29 | 30 | assert_eq!(ttl, t!(socket.ttl())); 31 | } 32 | 33 | #[test] 34 | fn ttl_v6() { 35 | let ttl = 100; 36 | 37 | let socket = t!(IcmpSocket::connect(ipv6())); 38 | t!(socket.set_ttl(ttl)); 39 | 40 | assert_eq!(ttl, t!(socket.ttl())); 41 | } 42 | 43 | #[test] 44 | fn qos_v4() { 45 | let tos: u8 = 0x10; // IPTOS_LOWDELAY 46 | 47 | let socket = t!(IcmpSocket::connect(ipv4())); 48 | t!(socket.set_qos(tos)); 49 | 50 | assert_eq!(tos, t!(socket.qos())); 51 | } 52 | 53 | #[test] 54 | fn qos_v6() { 55 | let dscp = 46; 56 | 57 | let socket = t!(IcmpSocket::connect(ipv6())); 58 | t!(socket.set_qos(46)); 59 | 60 | assert_eq!(dscp, t!(socket.qos())); 61 | } 62 | 63 | #[test] 64 | fn read_timeout_v4() { 65 | let timeout = Duration::new(2, 0); 66 | let socket = t!(IcmpSocket::connect(ipv4())); 67 | 68 | t!(socket.set_read_timeout(Some(timeout))); 69 | assert_eq!(Some(timeout), t!(socket.read_timeout())); 70 | 71 | t!(socket.set_read_timeout(None)); 72 | assert_eq!(None, t!(socket.read_timeout())); 73 | } 74 | 75 | #[test] 76 | fn read_timeout_v6() { 77 | let timeout = Duration::new(2, 0); 78 | let socket = t!(IcmpSocket::connect(ipv6())); 79 | 80 | t!(socket.set_read_timeout(Some(timeout))); 81 | assert_eq!(Some(timeout), t!(socket.read_timeout())); 82 | 83 | t!(socket.set_read_timeout(None)); 84 | assert_eq!(None, t!(socket.read_timeout())); 85 | } 86 | 87 | #[test] 88 | fn write_timeout_v4() { 89 | let timeout = Duration::new(2, 0); 90 | let socket = t!(IcmpSocket::connect(ipv4())); 91 | 92 | t!(socket.set_write_timeout(Some(timeout))); 93 | assert_eq!(Some(timeout), t!(socket.write_timeout())); 94 | 95 | t!(socket.set_write_timeout(None)); 96 | assert_eq!(None, t!(socket.write_timeout())); 97 | } 98 | 99 | #[test] 100 | fn write_timeout_v6() { 101 | let timeout = Duration::new(2, 0); 102 | let socket = t!(IcmpSocket::connect(ipv6())); 103 | 104 | t!(socket.set_write_timeout(Some(timeout))); 105 | assert_eq!(Some(timeout), t!(socket.write_timeout())); 106 | 107 | t!(socket.set_write_timeout(None)); 108 | assert_eq!(None, t!(socket.write_timeout())); 109 | } 110 | 111 | #[test] 112 | fn broadcast_v4() { 113 | let socket = t!(IcmpSocket::connect(ipv4())); 114 | 115 | t!(socket.set_broadcast(true)); 116 | assert_eq!(true, t!(socket.broadcast())); 117 | 118 | t!(socket.set_broadcast(false)); 119 | assert_eq!(false, t!(socket.broadcast())); 120 | 121 | t!(socket.set_broadcast(true)); 122 | assert_eq!(true, t!(socket.broadcast())); 123 | } 124 | 125 | #[test] 126 | fn broadcast_v6() { 127 | let socket = t!(IcmpSocket::connect(ipv6())); 128 | 129 | t!(socket.set_broadcast(true)); 130 | assert_eq!(true, t!(socket.broadcast())); 131 | 132 | t!(socket.set_broadcast(false)); 133 | assert_eq!(false, t!(socket.broadcast())); 134 | 135 | t!(socket.set_broadcast(true)); 136 | assert_eq!(true, t!(socket.broadcast())); 137 | } 138 | --------------------------------------------------------------------------------