timestamped-socket-0.2.2/.cargo_vcs_info.json0000644000000001360000000000100146370ustar { "git": { "sha1": "ef24b9adccc4e9d565021e7ecbe3430c172deb97" }, "path_in_vcs": "" }timestamped-socket-0.2.2/.github/dependabot.yml000064400000000000000000000002131046102023000176130ustar 00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: weekly open-pull-requests-limit: 10 timestamped-socket-0.2.2/.github/workflows/build.yaml000064400000000000000000000111061046102023000210060ustar 00000000000000name: checks permissions: contents: read on: push: branches: - main pull_request: schedule: - cron: '0 4 * * *' merge_group: branches: - main jobs: format: name: Format runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 with: persist-credentials: false - name: Install rust toolchain uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af with: toolchain: stable override: true default: true components: rustfmt - name: Check formatting uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 with: command: fmt args: --all --check build: name: Clippy & Test ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: # os: [ubuntu-latest, macos-latest] os: [ubuntu-latest] rust: - 1.66.0 target: - "" features: - "" steps: - name: Checkout sources uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 with: persist-credentials: false - name: Install ${{ matrix.rust }} toolchain uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af with: toolchain: ${{ matrix.rust }} override: true - name: cargo build run: cargo build ${{ matrix.features }} - name: cargo test run: cargo test env: RUST_BACKTRACE: 1 build-musl: name: Clippy & Test ubuntu-latest / MUSL runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 with: persist-credentials: false - name: Install rust toolchain uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af with: toolchain: stable override: true default: true components: clippy target: x86_64-unknown-linux-musl - name: cargo build run: cargo build ${{ matrix.features }} - name: cargo test run: cargo test env: RUST_BACKTRACE: 1 build-freebsd: name: Clippy & Test FreeBSD runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: test on freebsd uses: vmactions/freebsd-vm@12c207ac1ba13827f25726fe93f9c2e6f685f0f3 with: usesh: true mem: 4096 copyback: false prepare: | pkg install -y curl curl https://sh.rustup.rs -sSf --output rustup.sh sh rustup.sh -y --profile default --default-toolchain 1.66.0 # cannot use `--profile minimal` because of clippy echo "~~~~ rustc --version ~~~~" $HOME/.cargo/bin/rustc --version echo "~~~~ freebsd-version ~~~~" freebsd-version run: $HOME/.cargo/bin/cargo clippy -- -D warnings && $HOME/.cargo/bin/cargo build && $HOME/.cargo/bin/cargo test clippy-raspberry-pi: name: ClippyRaspberryPi runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 with: persist-credentials: false - name: Install rust toolchain uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af with: toolchain: stable override: true default: true components: clippy target: armv7-unknown-linux-gnueabihf # Use zig as our C compiler for convenient cross-compilation. We run into rustls having a dependency on `ring`. # This crate uses C and assembly code, and because of its build scripts, `cargo clippy` needs to be able to compile # that code for our target. - uses: goto-bus-stop/setup-zig@7ab2955eb728f5440978d5824358023be3a2802d with: version: 0.9.0 - name: Install cargo-zigbuild uses: taiki-e/install-action@3e71e7135de310b70bc22dccb4d275acde8e055a with: tool: cargo-zigbuild - name: Run clippy uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 env: TARGET_CC: "/home/runner/.cargo/bin/cargo-zigbuild zig cc -- -target arm-linux-gnueabihf -mcpu=generic+v7a+vfp3-d32+thumb2-neon -g" with: command: clippy args: --target armv7-unknown-linux-gnueabihf --workspace --all-targets -- -D warnings timestamped-socket-0.2.2/.gitignore000064400000000000000000000000241046102023000154130ustar 00000000000000/target /Cargo.lock timestamped-socket-0.2.2/COPYRIGHT000064400000000000000000000005161046102023000147240ustar 00000000000000Copyright (c) 2022-2023 Tweede Golf and Contributors Except as otherwise noted (below and/or in individual files), this project is licensed under the Apache License, Version 2.0 or or the MIT license or , at your option. timestamped-socket-0.2.2/Cargo.lock0000644000000254000000000000100126130ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" dependencies = [ "libc", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "gimli" version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" [[package]] name = "hermit-abi" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" [[package]] name = "libc" version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "lock_api" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "mio" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", "windows-sys", ] [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "object" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "pin-project-lite" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "proc-macro2" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags", ] [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "signal-hook-registry" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "smallvec" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "socket2" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys", ] [[package]] name = "syn" version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "timestamped-socket" version = "0.2.2" dependencies = [ "libc", "serde", "tokio", "tracing", ] [[package]] name = "tokio" version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys", ] [[package]] name = "tokio-macros" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing" version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" timestamped-socket-0.2.2/Cargo.toml0000644000000023470000000000100126430ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.66" name = "timestamped-socket" version = "0.2.2" publish = true description = "Implementation of async UDP and raw ethernet sockets with timestamping" homepage = "https://github.com/pendulum-project/timestamped-socket" readme = "README.md" license = "Apache-2.0 OR MIT" repository = "https://github.com/pendulum-project/timestamped-socket" [dependencies.libc] version = "0.2.145" [dependencies.serde] version = "1.0.145" features = ["derive"] optional = true [dependencies.tokio] version = "1.29.1" features = [ "net", "time", ] [dependencies.tracing] version = "0.1.37" features = [ "std", "log", ] default-features = false [dev-dependencies.tokio] version = "1.32.0" features = ["full"] [features] default = ["serde"] timestamped-socket-0.2.2/Cargo.toml.orig000064400000000000000000000014611046102023000163200ustar 00000000000000[package] name = "timestamped-socket" description = "Implementation of async UDP and raw ethernet sockets with timestamping" version = "0.2.2" edition = "2021" license = "Apache-2.0 OR MIT" repository = "https://github.com/pendulum-project/timestamped-socket" homepage = "https://github.com/pendulum-project/timestamped-socket" publish = true rust-version = "1.66" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] libc = "0.2.145" tokio = { version = "1.29.1", features = ["net", "time"] } tracing = { version = "0.1.37", default-features = false, features = ["std", "log"] } serde = { version = "1.0.145", features = ["derive"], optional = true } [dev-dependencies] tokio = { version = "1.32.0", features = ["full"] } [features] default = ["serde"] timestamped-socket-0.2.2/LICENSE-APACHE000064400000000000000000000251241046102023000153570ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2022 tweede golf b.v. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. timestamped-socket-0.2.2/LICENSE-MIT000064400000000000000000000020651046102023000150660ustar 00000000000000Copyright (c) 2022-2023 Tweede Golf and Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. timestamped-socket-0.2.2/README.md000064400000000000000000000003031046102023000147020ustar 00000000000000# Timestamped Socket This crate implements asynchronous UDP and raw ethernet sockets that support timestamping. This crate is part of the [pendulum project](https://github.com/pendulum-project). timestamped-socket-0.2.2/examples/logchanges.rs000064400000000000000000000003571046102023000177320ustar 00000000000000use timestamped_socket::interface::ChangeDetector; #[tokio::main] async fn main() { let mut detector = ChangeDetector::new().unwrap(); loop { detector.wait_for_change().await; println!("Change detected"); } } timestamped-socket-0.2.2/src/control_message.rs000064400000000000000000000165561046102023000177650ustar 00000000000000use std::marker::PhantomData; use crate::socket::Timestamp; pub(crate) const fn control_message_space() -> usize { // Safety: CMSG_SPACE is safe to call (unsafe { libc::CMSG_SPACE((std::mem::size_of::()) as _) }) as usize } pub(crate) enum MessageQueue { Normal, #[cfg(target_os = "linux")] Error, } // Invariants: // self.mhdr points to a valid libc::msghdr with a valid control // message region. // self.next_msg points to one of the control messages // in the region described by self.mhdr or is NULL // // These invariants are guaranteed from the safety conditions on // calling ControlMessageIterator::new, the fact that next preserves // these invariants and that the fields of ControlMessageIterator // are not modified outside these two functions. pub(crate) struct ControlMessageIterator<'a> { mhdr: libc::msghdr, next_msg: *const libc::cmsghdr, phantom: PhantomData<&'a [u8]>, } impl<'a> ControlMessageIterator<'a> { // Safety assumptions: // mhdr has a control and controllen field // that together describe a memory region // with lifetime 'a containing valid control // messages pub unsafe fn new(mhdr: libc::msghdr) -> Self { // Safety: // mhdr's control and controllen fields are valid and point // to valid control messages. let current_msg = unsafe { libc::CMSG_FIRSTHDR(&mhdr) }; // Invariant preservation: // The safety assumptions guaranteed by the caller ensure // that mhdr points to a valid region with valid control // messages. CMSG_FIRSTHDR is then guaranteed to either // return the pointer to the first valid control message // in that region, or NULL if the region is empty. Self { mhdr, next_msg: current_msg, phantom: PhantomData, } } } pub(crate) enum ControlMessage { Timestamping { software: Option, hardware: Option, }, #[cfg(target_os = "linux")] ReceiveError(libc::sock_extended_err), Other(libc::cmsghdr), } #[cfg(target_os = "linux")] const SCM_TIMESTAMP_NS: libc::c_int = libc::SCM_TIMESTAMPNS; #[cfg(target_os = "freebsd")] const SCM_TIMESTAMP_NS: libc::c_int = libc::SCM_REALTIME; #[cfg(target_os = "linux")] const PACKET_TX_TIMESTAMP: libc::c_int = 16; impl<'a> Iterator for ControlMessageIterator<'a> { type Item = ControlMessage; fn next(&mut self) -> Option { // Safety: // By the invariants, self.current_msg either points to a valid control message // or is NULL let current_msg = unsafe { self.next_msg.as_ref() }?; // Safety: // Invariants ensure that self.mhdr points to a valid libc::msghdr with a valid // control message region, and that self.next_msg either points to a // valid control message or is NULL. // The previous statement would have returned if self.next_msg were NULL, // therefore both passed pointers are valid for use with CMSG_NXTHDR // Invariant preservation: // CMSG_NXTHDR returns either a pointer to the next valid control message in the // control message region described by self.mhdr, or NULL self.next_msg = unsafe { libc::CMSG_NXTHDR(&self.mhdr, self.next_msg) }; Some(match (current_msg.cmsg_level, current_msg.cmsg_type) { #[cfg(target_os = "linux")] (libc::SOL_SOCKET, libc::SCM_TIMESTAMPING) => { // Safety: // current_msg was constructed from a pointer that pointed to a valid control message. // SO_TIMESTAMPING always has 3 timespecs in the data let cmsg_data = unsafe { libc::CMSG_DATA(current_msg) } as *const [libc::timespec; 3]; let [software, _, hardware] = unsafe { std::ptr::read_unaligned(cmsg_data) }; // Both 0 indicates it is not present let hardware = if hardware.tv_sec != 0 || hardware.tv_nsec != 0 { Some(Timestamp::from_timespec(hardware)) } else { None }; // Both 0 indicates it is not present let software = if software.tv_sec != 0 || software.tv_nsec != 0 { Some(Timestamp::from_timespec(software)) } else { None }; ControlMessage::Timestamping { software, hardware } } #[cfg(any(target_os = "linux", target_os = "freebsd"))] (libc::SOL_SOCKET, SCM_TIMESTAMP_NS) => { // Safety: // current_msg was constructed from a pointer that pointed to a valid control message. // SO_TIMESTAMPNS always has a timespec in the data let cmsg_data = unsafe { libc::CMSG_DATA(current_msg) } as *const libc::timespec; let timespec = unsafe { std::ptr::read_unaligned(cmsg_data) }; ControlMessage::Timestamping { software: Some(Timestamp::from_timespec(timespec)), hardware: None, } } (libc::SOL_SOCKET, libc::SCM_TIMESTAMP) => { // Safety: // current_msg was constructed from a pointer that pointed to a valid control message. // SO_TIMESTAMP always has a timeval in the data let cmsg_data = unsafe { libc::CMSG_DATA(current_msg) } as *const libc::timeval; let timeval = unsafe { std::ptr::read_unaligned(cmsg_data) }; ControlMessage::Timestamping { software: Some(Timestamp::from_timeval(timeval)), hardware: None, } } #[cfg(target_os = "linux")] (libc::SOL_IP, libc::IP_RECVERR) | (libc::SOL_IPV6, libc::IPV6_RECVERR) | (libc::SOL_PACKET, PACKET_TX_TIMESTAMP) => { // this is part of how timestamps are reported. // Safety: // current_msg was constructed from a pointer that pointed to a valid // control message. // IP*_RECVERR always has a sock_extended_err in the data let error = unsafe { let ptr = libc::CMSG_DATA(current_msg) as *const libc::sock_extended_err; std::ptr::read_unaligned(ptr) }; ControlMessage::ReceiveError(error) } _ => ControlMessage::Other(*current_msg), }) } } pub(crate) fn zeroed_sockaddr_storage() -> libc::sockaddr_storage { // a zeroed-out sockaddr storage is semantically valid, because a ss_family with // value 0 is libc::AF_UNSPEC. Hence the rest of the data does not come with // any constraints Safety: // the MaybeUninit is zeroed before assumed to be initialized unsafe { std::mem::MaybeUninit::zeroed().assume_init() } } pub(crate) fn empty_msghdr() -> libc::msghdr { // On `target_env = "musl"`, there are several private padding fields. // the position of these padding fields depends on the system endianness, // so keeping making them public does not really help. // // Safety: // // all fields are either integer or pointer types. For those types, 0 is a valid // value unsafe { std::mem::MaybeUninit::::zeroed().assume_init() } } timestamped-socket-0.2.2/src/interface/fallback.rs000064400000000000000000000006161046102023000202660ustar 00000000000000struct Private; pub struct ChangeDetector { _private: Private, } impl ChangeDetector { pub fn new() -> std::io::Result { Ok(Self { _private: Private }) } pub async fn wait_for_change(&mut self) { // No platform independent way, but checking every so often is fine for a fallback tokio::time::sleep(std::time::Duration::from_secs(60)).await; } } timestamped-socket-0.2.2/src/interface/freebsd.rs000064400000000000000000000065601046102023000201450ustar 00000000000000use std::{io::ErrorKind, os::fd::RawFd}; use libc::recv; use tokio::io::{unix::AsyncFd, Interest}; use crate::{cerr, control_message::zeroed_sockaddr_storage}; pub struct ChangeDetector { fd: AsyncFd, } impl ChangeDetector { const SOCKET_PATH: &'static [u8] = b"/var/run/devd.seqpacket.pipe"; pub fn new() -> std::io::Result { const _: () = assert!( std::mem::size_of::() >= std::mem::size_of::() ); const _: () = assert!( std::mem::align_of::() >= std::mem::align_of::() ); let mut address_buf = zeroed_sockaddr_storage(); // Safety: the above assertions guarantee that alignment and size are correct. // the resulting reference won't outlast the function, and result lives the entire // duration of the function let address = unsafe { &mut *(&mut address_buf as *mut libc::sockaddr_storage as *mut libc::sockaddr_un) }; address.sun_family = libc::AF_UNIX as _; for i in 0..Self::SOCKET_PATH.len() { address.sun_path[i] = Self::SOCKET_PATH[i] as _; } // Safety: calling socket is safe let fd = cerr(unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET, 0) })?; // Safety: address is valid for the duration of the call cerr(unsafe { libc::connect( fd, address as *mut _ as *mut _, std::mem::size_of_val(address) as _, ) })?; let nonblocking = 1 as libc::c_int; // Safety: nonblocking lives for the duration of the call, and is 4 bytes long as expected for FIONBIO cerr(unsafe { libc::ioctl(fd, libc::FIONBIO, &nonblocking) })?; Ok(ChangeDetector { fd: AsyncFd::new(fd)?, }) } fn empty(fd: i32) { loop { // Safety: buf is valid for the duration of the call, and it's length is passed as the len argument let mut buf = [0u8; 16]; match cerr(unsafe { recv( fd, &mut buf as *mut _ as *mut _, std::mem::size_of_val(&buf) as _, 0, ) as _ }) { Ok(_) => continue, Err(e) if e.kind() == ErrorKind::WouldBlock => break, Err(e) => { tracing::error!("Could not receive on change socket: {}", e); break; } } } } pub async fn wait_for_change(&mut self) { if let Err(e) = self .fd .async_io(Interest::READABLE, |fd| { // Safety: buf is valid for the duration of the call, and it's length is passed as the len argument let mut buf = [0u8; 16]; cerr(unsafe { recv( *fd, &mut buf as *mut _ as *mut _, std::mem::size_of_val(&buf) as _, 0, ) as _ })?; Self::empty(*fd); Ok(()) }) .await { tracing::error!("Could not receive on change socket: {}", e); } } } timestamped-socket-0.2.2/src/interface/linux.rs000064400000000000000000000065051046102023000176710ustar 00000000000000use std::{io::ErrorKind, os::fd::RawFd}; use libc::recv; use tokio::io::{unix::AsyncFd, Interest}; use crate::{cerr, control_message::zeroed_sockaddr_storage}; pub struct ChangeDetector { fd: AsyncFd, } impl ChangeDetector { pub fn new() -> std::io::Result { const _: () = assert!( std::mem::size_of::() >= std::mem::size_of::() ); const _: () = assert!( std::mem::align_of::() >= std::mem::align_of::() ); let mut address_buf = zeroed_sockaddr_storage(); // Safety: the above assertions guarantee that alignment and size are correct. // the resulting reference won't outlast the function, and result lives the entire // duration of the function let address = unsafe { &mut *(&mut address_buf as *mut libc::sockaddr_storage as *mut libc::sockaddr_nl) }; address.nl_family = libc::AF_NETLINK as _; address.nl_groups = (libc::RTMGRP_IPV4_IFADDR | libc::RTMGRP_IPV6_IFADDR | libc::RTMGRP_LINK) as _; // Safety: calling socket is safe let fd = cerr(unsafe { libc::socket(libc::AF_NETLINK, libc::SOCK_RAW, libc::NETLINK_ROUTE) })?; // Safety: address is valid for the duration of the call cerr(unsafe { libc::bind( fd, address as *mut _ as *mut _, std::mem::size_of_val(address) as _, ) })?; let nonblocking = 1 as libc::c_int; // Safety: nonblocking lives for the duration of the call, and is 4 bytes long as expected for FIONBIO cerr(unsafe { libc::ioctl(fd, libc::FIONBIO, &nonblocking) })?; Ok(ChangeDetector { fd: AsyncFd::new(fd)?, }) } fn empty(fd: i32) { loop { // Safety: buf is valid for the duration of the call, and it's length is passed as the len argument let mut buf = [0u8; 16]; match cerr(unsafe { recv( fd, &mut buf as *mut _ as *mut _, std::mem::size_of_val(&buf) as _, 0, ) as _ }) { Ok(_) => continue, Err(e) if e.kind() == ErrorKind::WouldBlock => break, Err(e) => { tracing::error!("Could not receive on change socket: {}", e); break; } } } } pub async fn wait_for_change(&mut self) { if let Err(e) = self .fd .async_io(Interest::READABLE, |fd| { // Safety: buf is valid for the duration of the call, and it's length is passed as the len argument let mut buf = [0u8; 16]; cerr(unsafe { recv( *fd, &mut buf as *mut _ as *mut _, std::mem::size_of_val(&buf) as _, 0, ) as _ })?; Self::empty(*fd); Ok(()) }) .await { tracing::error!("Could not receive on change socket: {}", e); } } } timestamped-socket-0.2.2/src/interface.rs000064400000000000000000000344231046102023000165320ustar 00000000000000use std::{ collections::HashMap, net::{IpAddr, SocketAddr}, str::FromStr, }; use super::cerr; #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "linux")] pub use linux::ChangeDetector; // NOTE: this detection logic is not sharable with macos! #[cfg(target_os = "freebsd")] mod freebsd; #[cfg(target_os = "freebsd")] pub use freebsd::ChangeDetector; #[cfg(not(any(target_os = "linux", target_os = "freebsd")))] mod fallback; #[cfg(not(any(target_os = "linux", target_os = "freebsd")))] pub use fallback::ChangeDetector; pub fn interfaces() -> std::io::Result> { let mut elements = HashMap::default(); for data in InterfaceIterator::new()? { let current: &mut InterfaceData = elements.entry(data.name).or_default(); current.socket_addrs.extend(data.socket_addr); assert!(!(current.mac.is_some() && data.mac.is_some())); current.mac = current.mac.or(data.mac); } Ok(elements) } #[derive(Default, Debug)] pub struct InterfaceData { socket_addrs: Vec, mac: Option<[u8; 6]>, } impl InterfaceData { pub fn has_ip_addr(&self, address: IpAddr) -> bool { self.socket_addrs .iter() .any(|socket_addr| socket_addr.ip() == address) } pub fn ips(&self) -> impl Iterator + '_ { self.socket_addrs.iter().map(|a| a.ip()) } pub fn mac(&self) -> Option<[u8; 6]> { self.mac } } // Invariants: // self.base always contains a pointer received from libc::getifaddrs that is not NULL. The region pointed to is never modified in rust code. // self.next always contains either a pointer pointing to a valid ifaddr received from libc::getifaddrs or null. // // These invariants are setup by InterfaceIterator::new and guaranteed by drop and next, which are the only places these pointers are used. struct InterfaceIterator { base: *mut libc::ifaddrs, next: *const libc::ifaddrs, } impl InterfaceIterator { pub fn new() -> std::io::Result { let mut addrs: *mut libc::ifaddrs = std::ptr::null_mut(); // Safety: // addrs lives for the duration of the call to getifaddrs. // // Invariant preservation: // we validate that the received address is not null, and // by the guarantees from getifaddrs points to a valid // ifaddr returned from getifaddrs unsafe { cerr(libc::getifaddrs(&mut addrs))?; assert!(!addrs.is_null()); Ok(Self { base: addrs, next: addrs, }) } } } impl Drop for InterfaceIterator { fn drop(&mut self) { // Safety: // By the invariants, self.base is guaranteed to point to a memory region allocated by getifaddrs unsafe { libc::freeifaddrs(self.base) }; } } struct InterfaceDataInternal { name: InterfaceName, mac: Option<[u8; 6]>, socket_addr: Option, } impl Iterator for InterfaceIterator { type Item = InterfaceDataInternal; fn next(&mut self) -> Option<::Item> { // Safety: // By the invariants, self.next is guaranteed to be a valid pointer to an ifaddrs struct or null. let ifaddr = unsafe { self.next.as_ref() }?; // Invariant preservation // By the guarantees given by getifaddrs, ifaddr.ifa_next is either null or points to a valid // ifaddr. self.next = ifaddr.ifa_next; // Safety: // getifaddrs guarantees that ifa_name is not null and points to a valid C string. let ifname = unsafe { std::ffi::CStr::from_ptr(ifaddr.ifa_name) }; let name = match std::str::from_utf8(ifname.to_bytes()) { Err(_) => unreachable!("interface names must be ascii"), Ok(name) => InterfaceName::from_str(name).expect("name from os"), }; // Safety: // getifaddrs guarantees that ifa_addr either points to a valid address or is NULL. let family = unsafe { ifaddr.ifa_addr.as_ref() }.map(|a| a.sa_family); #[allow(unused)] let mac: Option<[u8; 6]> = None; #[cfg(target_os = "linux")] // Safety: getifaddrs ensures that, if an address is present, it is valid. A valid address // of type AF_PACKET is always reinterpret castable to sockaddr_ll, and we know an address // is present since family is not None let mac = if family == Some(libc::AF_PACKET as _) { let sockaddr_ll: libc::sockaddr_ll = unsafe { std::ptr::read_unaligned(ifaddr.ifa_addr as *const _) }; Some([ sockaddr_ll.sll_addr[0], sockaddr_ll.sll_addr[1], sockaddr_ll.sll_addr[2], sockaddr_ll.sll_addr[3], sockaddr_ll.sll_addr[4], sockaddr_ll.sll_addr[5], ]) } else { None }; #[cfg(any(target_os = "freebsd", target_os = "macos"))] let mac = if family == Some(libc::AF_LINK as _) { // Safety: getifaddrs ensures that, if an address is present, it is valid. A valid address // of type AF_LINK is always reinterpret castable to sockaddr_ll, and we know an address // is present since family is not None let sockaddr_dl: libc::sockaddr_dl = unsafe { std::ptr::read_unaligned(ifaddr.ifa_addr as *const _) }; // From sys/net/if_types.h in freebsd: const IFT_ETHER: u8 = 0x6; if sockaddr_dl.sdl_type == IFT_ETHER && sockaddr_dl.sdl_nlen.saturating_add(6) as usize <= sockaddr_dl.sdl_data.len() { Some([ sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize] as u8, sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 1] as u8, sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 2] as u8, sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 3] as u8, sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 4] as u8, sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 5] as u8, ]) } else { None } } else { None }; // Safety: ifaddr.ifa_addr is always either NULL, or by the guarantees of getifaddrs, points to a valid address. let socket_addr = unsafe { sockaddr_to_socket_addr(ifaddr.ifa_addr) }; let data = InterfaceDataInternal { name, mac, socket_addr, }; Some(data) } } #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct InterfaceName { bytes: [u8; libc::IFNAMSIZ], } impl InterfaceName { #[cfg(all(test, target_os = "linux"))] pub const LOOPBACK: Self = Self { bytes: *b"lo\0\0\0\0\0\0\0\0\0\0\0\0\0\0", }; #[cfg(all(test, any(target_os = "freebsd", target_os = "macos")))] pub const LOOPBACK: Self = Self { bytes: *b"lo0\0\0\0\0\0\0\0\0\0\0\0\0\0", }; #[cfg(test)] pub const INVALID: Self = Self { bytes: *b"123412341234123\0", }; pub fn as_str(&self) -> &str { std::str::from_utf8(self.bytes.as_slice()) .unwrap_or_default() .trim_end_matches('\0') } pub fn as_cstr(&self) -> &std::ffi::CStr { // TODO: in rust 1.69.0, use // std::ffi::CStr::from_bytes_until_nul(&self.bytes[..]).unwrap() // it is an invariant of InterfaceName that the bytes are null-terminated let first_null = self.bytes.iter().position(|b| *b == 0).unwrap(); std::ffi::CStr::from_bytes_with_nul(&self.bytes[..=first_null]).unwrap() } pub fn to_ifr_name(self) -> [libc::c_char; libc::IFNAMSIZ] { let mut it = self.bytes.iter().copied(); [0; libc::IFNAMSIZ].map(|_| it.next().unwrap_or(0) as libc::c_char) } pub fn from_socket_addr(local_addr: SocketAddr) -> std::io::Result> { let matches_inferface = |interface: &InterfaceDataInternal| match interface.socket_addr { None => false, Some(address) => address.ip() == local_addr.ip(), }; match InterfaceIterator::new()?.find(matches_inferface) { Some(interface) => Ok(Some(interface.name)), None => Ok(None), } } pub fn get_index(&self) -> Option { // # SAFETY // // self lives for the duration of the call, and is null terminated. match unsafe { libc::if_nametoindex(self.as_cstr().as_ptr()) } { 0 => None, n => Some(n), } } } impl std::fmt::Debug for InterfaceName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("InterfaceName") .field(&self.as_str()) .finish() } } impl std::fmt::Display for InterfaceName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.as_str().fmt(f) } } impl std::str::FromStr for InterfaceName { type Err = (); fn from_str(s: &str) -> Result { let mut bytes = [0; libc::IFNAMSIZ]; // >= so that we always retain a NUL byte at the end if s.len() >= bytes.len() { return Err(()); } if s.is_empty() { // this causes problems down the line when giving the interface name to tokio return Err(()); } let mut it = s.bytes(); bytes = bytes.map(|_| it.next().unwrap_or_default()); Ok(Self { bytes }) } } #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for InterfaceName { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; FromStr::from_str(&s).map_err(|_| serde::de::Error::custom("invalid interface name")) } } /// Convert a libc::sockaddr to a rust std::net::SocketAddr /// /// # Safety /// /// This function assumes that sockaddr is either NULL or points to a valid address. unsafe fn sockaddr_to_socket_addr(sockaddr: *const libc::sockaddr) -> Option { // Most (but not all) of the fields in a socket addr are in network byte // ordering. As such, when doing conversions here, we should start from the // NATIVE byte representation, as this will actualy be the big-endian // representation of the underlying value regardless of platform. // Check for null pointers if sockaddr.is_null() { return None; } // Safety: by the previous check, sockaddr is not NULL and hence points to a valid address match unsafe { (*sockaddr).sa_family as libc::c_int } { libc::AF_INET => { // SAFETY: we cast from a libc::sockaddr (alignment 2) to a libc::sockaddr_in (alignment 4) // that means that the pointer is now potentially unaligned. We must used read_unaligned! // However, the rest of the cast is safe as a valid AF_INET address is always reinterpret castable // as a sockaddr_in let inaddr: libc::sockaddr_in = unsafe { std::ptr::read_unaligned(sockaddr as *const libc::sockaddr_in) }; let socketaddr = std::net::SocketAddrV4::new( std::net::Ipv4Addr::from(inaddr.sin_addr.s_addr.to_ne_bytes()), u16::from_be_bytes(inaddr.sin_port.to_ne_bytes()), ); Some(std::net::SocketAddr::V4(socketaddr)) } libc::AF_INET6 => { // SAFETY: we cast from a libc::sockaddr (alignment 2) to a libc::sockaddr_in6 (alignment 4) // that means that the pointer is now potentially unaligned. We must used read_unaligned! // However, the cast is safe as a valid AF_INET6 address is always reinterpret catable as a sockaddr_in6 let inaddr: libc::sockaddr_in6 = unsafe { std::ptr::read_unaligned(sockaddr as *const libc::sockaddr_in6) }; // Safety: // sin_addr lives for the duration fo the call and matches type let sin_addr = inaddr.sin6_addr.s6_addr; let segment_bytes: [u8; 16] = unsafe { std::ptr::read_unaligned(&sin_addr as *const _ as *const _) }; let socketaddr = std::net::SocketAddrV6::new( std::net::Ipv6Addr::from(segment_bytes), u16::from_be_bytes(inaddr.sin6_port.to_ne_bytes()), inaddr.sin6_flowinfo, /* NOTE: Despite network byte order, no conversion is needed (see https://github.com/rust-lang/rust/issues/101605) */ inaddr.sin6_scope_id, ); Some(std::net::SocketAddr::V6(socketaddr)) } _ => None, } } #[cfg(test)] mod tests { use std::net::Ipv4Addr; use super::*; #[test] fn interface_name_from_string() { assert!(InterfaceName::from_str("").is_err()); assert!(InterfaceName::from_str("a string that is too long").is_err()); let input = "enp0s31f6"; assert_eq!(InterfaceName::from_str(input).unwrap().as_str(), input); let ifr_name = (*b"enp0s31f6\0\0\0\0\0\0\0").map(|b| b as libc::c_char); assert_eq!( InterfaceName::from_str(input).unwrap().to_ifr_name(), ifr_name ); } #[test] fn test_mac_address_iterator() { let v: Vec<_> = InterfaceIterator::new() .unwrap() .filter_map(|d| d.mac) .collect(); assert!(!v.is_empty()); } #[test] fn test_interface_name_iterator() { let v: Vec<_> = InterfaceIterator::new().unwrap().map(|d| d.name).collect(); assert!(v.contains(&InterfaceName::LOOPBACK)); } #[test] fn test_socket_addr_iterator() { let v: Vec<_> = InterfaceIterator::new() .unwrap() .filter_map(|d| d.socket_addr) .collect(); let localhost_0 = SocketAddr::from((Ipv4Addr::LOCALHOST, 0)); assert!(v.contains(&localhost_0)); } #[test] fn interface_index_ipv4() { assert!(InterfaceName::LOOPBACK.get_index().is_some()); } #[test] fn interface_index_ipv6() { assert!(InterfaceName::LOOPBACK.get_index().is_some()); } #[test] fn interface_index_invalid() { assert!(InterfaceName::INVALID.get_index().is_none()); } } timestamped-socket-0.2.2/src/lib.rs000064400000000000000000000004751046102023000153400ustar 00000000000000mod control_message; pub mod interface; pub mod networkaddress; mod raw_socket; pub mod socket; /// Turn a C failure (-1 is returned) into a rust Result pub(crate) fn cerr(t: libc::c_int) -> std::io::Result { match t { -1 => Err(std::io::Error::last_os_error()), _ => Ok(t), } } timestamped-socket-0.2.2/src/networkaddress/linux.rs000064400000000000000000000233751046102023000207740ustar 00000000000000use std::{ net::{Ipv4Addr, SocketAddrV4, SocketAddrV6}, os::fd::RawFd, }; use crate::{ cerr, control_message::zeroed_sockaddr_storage, interface::InterfaceName, networkaddress::NetworkAddress, }; use super::{ sealed::{PrivateToken, SealedMC, SealedNA}, MulticastJoinable, }; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MacAddress([u8; 6]); impl From<[u8; 6]> for MacAddress { fn from(value: [u8; 6]) -> Self { MacAddress(value) } } impl AsRef<[u8]> for MacAddress { fn as_ref(&self) -> &[u8] { &self.0 } } impl MacAddress { pub const fn new(address: [u8; 6]) -> Self { MacAddress(address) } } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct EthernetAddress { protocol: u16, mac_address: MacAddress, if_index: libc::c_int, } impl EthernetAddress { pub const fn new(protocol: u16, mac_address: MacAddress, if_index: libc::c_int) -> Self { EthernetAddress { protocol, mac_address, if_index, } } pub const fn mac(&self) -> MacAddress { self.mac_address } pub const fn protocol(&self) -> u16 { self.protocol } pub const fn interface(&self) -> libc::c_int { self.if_index } } impl SealedNA for EthernetAddress {} impl NetworkAddress for EthernetAddress { fn to_sockaddr(&self, _token: PrivateToken) -> libc::sockaddr_storage { const _: () = assert!( std::mem::size_of::() >= std::mem::size_of::() ); const _: () = assert!( std::mem::align_of::() >= std::mem::align_of::() ); let mut result = zeroed_sockaddr_storage(); // Safety: the above assertions guarantee that alignment and size are correct. // the resulting reference won't outlast the function, and result lives the entire // duration of the function let out = unsafe { &mut (*(&mut result as *mut _ as *mut libc::sockaddr_ll)) }; out.sll_family = libc::AF_PACKET as _; out.sll_addr[..6].copy_from_slice(&self.mac_address.0); out.sll_halen = 6; out.sll_protocol = u16::from_ne_bytes(self.protocol.to_be_bytes()); out.sll_ifindex = self.if_index; result } fn from_sockaddr(addr: libc::sockaddr_storage, _token: PrivateToken) -> Option { const _: () = assert!( std::mem::size_of::() >= std::mem::size_of::() ); const _: () = assert!( std::mem::align_of::() >= std::mem::align_of::() ); if addr.ss_family != libc::AF_PACKET as _ { return None; } // Safety: the above assertions guarantee that alignment and size are correct // the resulting reference won't outlast the function, and addr lives the entire // duration of the function let input = unsafe { &(*(&addr as *const _ as *const libc::sockaddr_ll)) }; if input.sll_halen != 6 { return None; } Some(EthernetAddress::new( u16::from_be_bytes(input.sll_protocol.to_ne_bytes()), MacAddress::new(input.sll_addr[..6].try_into().unwrap()), input.sll_ifindex, )) } } impl SealedMC for EthernetAddress {} impl MulticastJoinable for EthernetAddress { fn join_multicast( &self, socket: RawFd, interface: InterfaceName, _token: PrivateToken, ) -> std::io::Result<()> { let request = libc::packet_mreq { mr_ifindex: interface .get_index() .ok_or(std::io::ErrorKind::InvalidInput)? as _, mr_type: libc::PACKET_MR_MULTICAST as _, mr_alen: 6, mr_address: [ self.mac_address.0[0], self.mac_address.0[1], self.mac_address.0[2], self.mac_address.0[3], self.mac_address.0[4], self.mac_address.0[5], 0, 0, ], }; // Safety: // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IPV6/IPV6_ADD_MEMBERSHIP cerr(unsafe { libc::setsockopt( socket, libc::SOL_PACKET, libc::PACKET_ADD_MEMBERSHIP, &request as *const _ as *const _, std::mem::size_of_val(&request) as _, ) })?; Ok(()) } fn leave_multicast( &self, socket: RawFd, interface: InterfaceName, _token: PrivateToken, ) -> std::io::Result<()> { let request = libc::packet_mreq { mr_ifindex: interface .get_index() .ok_or(std::io::ErrorKind::InvalidInput)? as _, mr_type: libc::PACKET_MR_MULTICAST as _, mr_alen: 6, mr_address: [ self.mac_address.0[0], self.mac_address.0[1], self.mac_address.0[2], self.mac_address.0[3], self.mac_address.0[4], self.mac_address.0[5], 0, 0, ], }; // Safety: // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IPV6/IPV6_ADD_MEMBERSHIP cerr(unsafe { libc::setsockopt( socket, libc::SOL_PACKET, libc::PACKET_DROP_MEMBERSHIP, &request as *const _ as *const _, std::mem::size_of_val(&request) as _, ) })?; Ok(()) } } impl SealedMC for SocketAddrV4 {} impl MulticastJoinable for SocketAddrV4 { fn join_multicast( &self, socket: RawFd, interface: InterfaceName, _token: PrivateToken, ) -> std::io::Result<()> { let request = libc::ip_mreqn { imr_multiaddr: libc::in_addr { s_addr: u32::from_ne_bytes(self.ip().octets()), }, imr_address: libc::in_addr { s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()), }, imr_ifindex: interface .get_index() .ok_or(std::io::ErrorKind::InvalidInput)? as _, }; // Safety: // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IP/IP_ADD_MEMBERSHIP cerr(unsafe { libc::setsockopt( socket, libc::IPPROTO_IP, libc::IP_ADD_MEMBERSHIP, &request as *const _ as *const _, std::mem::size_of_val(&request) as _, ) })?; Ok(()) } fn leave_multicast( &self, socket: RawFd, interface: InterfaceName, _token: PrivateToken, ) -> std::io::Result<()> { let request = libc::ip_mreqn { imr_multiaddr: libc::in_addr { s_addr: u32::from_ne_bytes(self.ip().octets()), }, imr_address: libc::in_addr { s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()), }, imr_ifindex: interface .get_index() .ok_or(std::io::ErrorKind::InvalidInput)? as _, }; // Safety: // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IP/IP_DROP_MEMBERSHIP cerr(unsafe { libc::setsockopt( socket, libc::IPPROTO_IP, libc::IP_DROP_MEMBERSHIP, &request as *const _ as *const _, std::mem::size_of_val(&request) as _, ) })?; Ok(()) } } impl SealedMC for SocketAddrV6 {} impl MulticastJoinable for SocketAddrV6 { fn join_multicast( &self, socket: RawFd, interface: InterfaceName, _token: PrivateToken, ) -> std::io::Result<()> { let request = libc::ipv6_mreq { ipv6mr_multiaddr: libc::in6_addr { s6_addr: self.ip().octets(), }, ipv6mr_interface: interface .get_index() .ok_or(std::io::ErrorKind::InvalidInput)? as _, }; // Safety: // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IPV6/IPV6_ADD_MEMBERSHIP cerr(unsafe { libc::setsockopt( socket, libc::IPPROTO_IPV6, libc::IPV6_ADD_MEMBERSHIP, &request as *const _ as *const _, std::mem::size_of_val(&request) as _, ) })?; Ok(()) } fn leave_multicast( &self, socket: RawFd, interface: InterfaceName, _token: PrivateToken, ) -> std::io::Result<()> { let request = libc::ipv6_mreq { ipv6mr_multiaddr: libc::in6_addr { s6_addr: self.ip().octets(), }, ipv6mr_interface: interface .get_index() .ok_or(std::io::ErrorKind::InvalidInput)? as _, }; // Safety: // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IPV6/IPV6_DROP_MEMBERSHIP cerr(unsafe { libc::setsockopt( socket, libc::IPPROTO_IPV6, libc::IPV6_DROP_MEMBERSHIP, &request as *const _ as *const _, std::mem::size_of_val(&request) as _, ) })?; Ok(()) } } timestamped-socket-0.2.2/src/networkaddress.rs000064400000000000000000000143231046102023000176260ustar 00000000000000use std::{ net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, os::fd::RawFd, }; use crate::{control_message::zeroed_sockaddr_storage, interface::InterfaceName}; use self::sealed::{PrivateToken, SealedMC, SealedNA}; #[cfg(target_os = "linux")] pub use self::linux::*; #[cfg(target_os = "linux")] mod linux; pub(crate) mod sealed { // Seal to ensure NetworkAddress can't be implemented outside our crate pub trait SealedNA {} // Seal to ensure MulticastJoinable can't be implemented outside our crate pub trait SealedMC {} // Token to ensure trait functions cannot be called outside our crate pub struct PrivateToken; } pub trait NetworkAddress: Sized + SealedNA { #[doc(hidden)] fn to_sockaddr(&self, _token: PrivateToken) -> libc::sockaddr_storage; #[doc(hidden)] fn from_sockaddr(addr: libc::sockaddr_storage, _token: PrivateToken) -> Option; } pub trait MulticastJoinable: NetworkAddress + SealedMC { #[doc(hidden)] fn join_multicast( &self, socket: RawFd, interface: InterfaceName, _token: PrivateToken, ) -> std::io::Result<()>; #[doc(hidden)] fn leave_multicast( &self, socket: RawFd, interface: InterfaceName, _token: PrivateToken, ) -> std::io::Result<()>; } impl SealedNA for SocketAddrV4 {} impl NetworkAddress for SocketAddrV4 { fn to_sockaddr(&self, _token: PrivateToken) -> libc::sockaddr_storage { const _: () = assert!( std::mem::size_of::() >= std::mem::size_of::() ); const _: () = assert!( std::mem::align_of::() >= std::mem::align_of::() ); let mut result = zeroed_sockaddr_storage(); // Safety: the above assertions guarantee that alignment and size are correct. // the resulting reference won't outlast the function, and result lives the entire // duration of the function let out = unsafe { &mut (*(&mut result as *mut _ as *mut libc::sockaddr_in)) }; out.sin_family = libc::AF_INET as _; out.sin_port = u16::from_ne_bytes(self.port().to_be_bytes()); out.sin_addr = libc::in_addr { s_addr: u32::from_ne_bytes(self.ip().octets()), }; result } fn from_sockaddr(addr: libc::sockaddr_storage, _token: PrivateToken) -> Option { const _: () = assert!( std::mem::size_of::() >= std::mem::size_of::() ); const _: () = assert!( std::mem::align_of::() >= std::mem::align_of::() ); if addr.ss_family != libc::AF_INET as _ { return None; } // Safety: the above assertions guarantee that alignment and size are correct // the resulting reference won't outlast the function, and addr lives the entire // duration of the function let input = unsafe { &(*(&addr as *const _ as *const libc::sockaddr_in)) }; Some(SocketAddrV4::new( Ipv4Addr::from(input.sin_addr.s_addr.to_ne_bytes()), u16::from_be_bytes(input.sin_port.to_ne_bytes()), )) } } impl SealedNA for SocketAddrV6 {} impl NetworkAddress for SocketAddrV6 { fn to_sockaddr(&self, _token: PrivateToken) -> libc::sockaddr_storage { const _: () = assert!( std::mem::size_of::() >= std::mem::size_of::() ); const _: () = assert!( std::mem::align_of::() >= std::mem::align_of::() ); let mut result = zeroed_sockaddr_storage(); // Safety: the above assertions guarantee that alignment and size are correct. // the resulting reference won't outlast the function, and result lives the entire // duration of the function let out = unsafe { &mut (*(&mut result as *mut _ as *mut libc::sockaddr_in6)) }; out.sin6_family = libc::AF_INET6 as _; out.sin6_port = u16::from_ne_bytes(self.port().to_be_bytes()); out.sin6_addr = libc::in6_addr { s6_addr: self.ip().octets(), }; out.sin6_flowinfo = self.flowinfo(); out.sin6_scope_id = self.scope_id(); result } fn from_sockaddr(addr: libc::sockaddr_storage, _token: PrivateToken) -> Option { const _: () = assert!( std::mem::size_of::() >= std::mem::size_of::() ); const _: () = assert!( std::mem::align_of::() >= std::mem::align_of::() ); if addr.ss_family != libc::AF_INET6 as _ { return None; } // Safety: the above assertions guarantee that alignment and size are correct // the resulting reference won't outlast the function, and addr lives the entire // duration of the function let input = unsafe { &(*(&addr as *const _ as *const libc::sockaddr_in6)) }; Some(SocketAddrV6::new( Ipv6Addr::from(input.sin6_addr.s6_addr), u16::from_be_bytes(input.sin6_port.to_ne_bytes()), input.sin6_flowinfo, input.sin6_scope_id, )) } } impl SealedNA for SocketAddr {} impl NetworkAddress for SocketAddr { fn to_sockaddr(&self, _token: PrivateToken) -> libc::sockaddr_storage { match self { SocketAddr::V4(addr) => addr.to_sockaddr(PrivateToken), SocketAddr::V6(addr) => addr.to_sockaddr(PrivateToken), } } fn from_sockaddr(addr: libc::sockaddr_storage, _token: PrivateToken) -> Option { match addr.ss_family as _ { libc::AF_INET => Some(SocketAddr::V4(SocketAddrV4::from_sockaddr( addr, PrivateToken, )?)), libc::AF_INET6 => Some(SocketAddr::V6(SocketAddrV6::from_sockaddr( addr, PrivateToken, )?)), _ => None, } } } timestamped-socket-0.2.2/src/raw_socket/freebsd.rs000064400000000000000000000035751046102023000203510ustar 00000000000000use crate::cerr; use super::RawSocket; impl RawSocket { pub(crate) fn so_timestamp(&self, options: u32) -> std::io::Result<()> { // Documentation on the timestamping calls: // // - linux: https://www.kernel.org/doc/Documentation/networking/timestamping.txt // - freebsd: https://man.freebsd.org/cgi/man.cgi?setsockopt // // SAFETY: // // - the socket is provided by (safe) rust, and will outlive the call // - method is guaranteed to be a valid "name" argument // - the options pointer outlives the call // - the `option_len` corresponds with the options pointer // // Only some bits are valid to set in `options`, but setting invalid bits is // perfectly safe // // > Setting other bit returns EINVAL and does not change the current state. unsafe { cerr(libc::setsockopt( self.fd, libc::SOL_SOCKET, libc::SO_TIMESTAMP, &options as *const _ as *const libc::c_void, std::mem::size_of_val(&options) as libc::socklen_t, )) }?; if options != 0 { let clock = libc::SO_TS_REALTIME as u32; // Safety: // // - The socket is proviided by (safe) rust, and will outlive the call // - method is guaranteed to be a valid "name" argument // - clock outlives the call // - option_len corresponds with the size of clock unsafe { cerr(libc::setsockopt( self.fd, libc::SOL_SOCKET, libc::SO_TS_CLOCK, &clock as *const _ as *const libc::c_void, std::mem::size_of_val(&clock) as libc::socklen_t, )) }?; } Ok(()) } } timestamped-socket-0.2.2/src/raw_socket/linux.rs000064400000000000000000000144551046102023000200750ustar 00000000000000use std::net::Ipv4Addr; use crate::{cerr, interface::InterfaceName}; use super::RawSocket; #[repr(C)] struct so_timestamping { flags: libc::c_int, bind_phc: libc::c_int, } impl RawSocket { pub(crate) fn so_timestamping(&self, options: u32, bind_phc: u32) -> std::io::Result<()> { // Documentation on the timestamping calls: // // - linux: https://www.kernel.org/doc/Documentation/networking/timestamping.txt // - freebsd: https://man.freebsd.org/cgi/man.cgi?setsockopt // // SAFETY: // // - the socket is provided by (safe) rust, and will outlive the call // - method is guaranteed to be a valid "name" argument // - the options pointer outlives the call // - the `option_len` corresponds with the options pointer // // Only some bits are valid to set in `options`, but setting invalid bits is // perfectly safe // // > Setting other bit returns EINVAL and does not change the current state. let tstamp_config = so_timestamping { flags: options as libc::c_int, bind_phc: bind_phc as libc::c_int, }; unsafe { cerr(libc::setsockopt( self.fd, libc::SOL_SOCKET, libc::SO_TIMESTAMPING, &tstamp_config as *const _ as *const libc::c_void, std::mem::size_of_val(&tstamp_config) as libc::socklen_t, )) }?; Ok(()) } pub(crate) fn driver_enable_hardware_timestamping( &self, interface: InterfaceName, rx_filter: libc::c_int, ) -> std::io::Result<()> { let mut tstamp_config = libc::hwtstamp_config { flags: 0, tx_type: libc::HWTSTAMP_TX_ON as _, rx_filter, }; let mut ifreq = libc::ifreq { ifr_name: interface.to_ifr_name(), ifr_ifru: libc::__c_anonymous_ifr_ifru { ifru_data: (&mut tstamp_config as *mut _) as *mut libc::c_char, }, }; // Safety: // ifreq lives for the duration of the call, ioctl is safe to call otherwise cerr(unsafe { libc::ioctl(self.fd, libc::SIOCSHWTSTAMP as _, &mut ifreq) })?; Ok(()) } pub(crate) fn bind_to_device(&self, interface_name: InterfaceName) -> std::io::Result<()> { let value = interface_name.as_str().as_bytes(); let len = value.len(); // Safety: // value lives for the duration of the call, and is of size len. // setsockopt is safe to call in all other regards unsafe { cerr(libc::setsockopt( self.fd, libc::SOL_SOCKET, libc::SO_BINDTODEVICE, value.as_ptr().cast(), len as libc::socklen_t, ))?; } Ok(()) } pub(crate) fn ip_multicast_if(&self, interface_name: InterfaceName) -> std::io::Result<()> { let request = libc::ip_mreqn { imr_multiaddr: libc::in_addr { s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()), }, imr_address: libc::in_addr { s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()), }, imr_ifindex: interface_name .get_index() .ok_or(std::io::ErrorKind::InvalidInput)? as _, }; // Safety: // request lives for the duration of the call, and we pass its size // as option_len. setsockopt is safe to call in all other regards cerr(unsafe { libc::setsockopt( self.fd, libc::IPPROTO_IP, libc::IP_MULTICAST_IF, &request as *const _ as *const _, std::mem::size_of_val(&request) as _, ) })?; Ok(()) } pub(crate) fn ipv6_multicast_if(&self, interface_name: InterfaceName) -> std::io::Result<()> { let index = interface_name .get_index() .ok_or(std::io::ErrorKind::InvalidInput)?; // Safety: // index lives for the duration of the call, and we pass its size // as option_len. setsockopt is safe to call in all other regards cerr(unsafe { libc::setsockopt( self.fd, libc::IPPROTO_IPV6, libc::IPV6_MULTICAST_IF, &index as *const _ as *const _, std::mem::size_of_val(&index) as _, ) })?; Ok(()) } pub(crate) fn ip_multicast_loop(&self, enabled: bool) -> std::io::Result<()> { let state: i32 = if enabled { 1 } else { 0 }; // Safety: // state lives for the duration of the call, and we pass its size // as option_len. setsockopt is safe to call in all other regards. cerr(unsafe { libc::setsockopt( self.fd, libc::IPPROTO_IP, libc::IP_MULTICAST_LOOP, &state as *const _ as *const _, std::mem::size_of_val(&state) as _, ) })?; Ok(()) } pub(crate) fn ipv6_multicast_loop(&self, enabled: bool) -> std::io::Result<()> { let state: i32 = if enabled { 1 } else { 0 }; // Safety: // state lives for the duration of the call, and we pass its size // as option_len. setsockopt is safe to call in all other regards. cerr(unsafe { libc::setsockopt( self.fd, libc::IPPROTO_IPV6, libc::IPV6_MULTICAST_LOOP, &state as *const _ as *const _, std::mem::size_of_val(&state) as _, ) })?; Ok(()) } pub(crate) fn ipv6_v6only(&self, enabled: bool) -> std::io::Result<()> { let state: i32 = if enabled { 1 } else { 0 }; // Safety: // state lives for the duration of the call, and we pass its size // as option_len. setsockopt is safe to call in all other regards. cerr(unsafe { libc::setsockopt( self.fd, libc::IPPROTO_IPV6, libc::IPV6_V6ONLY, &state as *const _ as *const _, std::mem::size_of_val(&state) as _, ) })?; Ok(()) } } timestamped-socket-0.2.2/src/raw_socket/macos.rs000064400000000000000000000022621046102023000200310ustar 00000000000000use crate::cerr; use super::RawSocket; impl RawSocket { pub(crate) fn so_timestamp(&self, options: u32) -> std::io::Result<()> { // Documentation on the timestamping calls: // // - linux: https://www.kernel.org/doc/Documentation/networking/timestamping.txt // - freebsd: https://man.freebsd.org/cgi/man.cgi?setsockopt // // SAFETY: // // - the socket is provided by (safe) rust, and will outlive the call // - method is guaranteed to be a valid "name" argument // - the options pointer outlives the call // - the `option_len` corresponds with the options pointer // // Only some bits are valid to set in `options`, but setting invalid bits is // perfectly safe // // > Setting other bit returns EINVAL and does not change the current state. unsafe { cerr(libc::setsockopt( self.fd, libc::SOL_SOCKET, libc::SO_TIMESTAMP, &options as *const _ as *const libc::c_void, std::mem::size_of_val(&options) as libc::socklen_t, )) }?; Ok(()) } } timestamped-socket-0.2.2/src/raw_socket.rs000064400000000000000000000204061046102023000167270ustar 00000000000000use std::{ io::IoSliceMut, os::fd::{AsRawFd, RawFd}, }; use libc::{c_void, sockaddr, sockaddr_storage}; use crate::{ cerr, control_message::{ empty_msghdr, zeroed_sockaddr_storage, ControlMessage, ControlMessageIterator, MessageQueue, }, }; #[cfg(target_os = "freebsd")] mod freebsd; #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "macos")] mod macos; // A struct providing safe wrappers around various socket api calls #[derive(Debug, Hash)] pub(crate) struct RawSocket { fd: RawFd, } impl AsRawFd for RawSocket { fn as_raw_fd(&self) -> RawFd { self.fd } } impl RawSocket { pub(crate) fn open( domain: libc::c_int, ty: libc::c_int, protocol: libc::c_int, ) -> std::io::Result { // Safety: libc::socket is always safe to call Ok(RawSocket { fd: cerr(unsafe { libc::socket(domain, ty, protocol) })?, }) } pub(crate) fn bind(&self, addr: sockaddr_storage) -> std::io::Result<()> { // Per posix, it may be invalid to specify a length larger than that of the family. let len = sockaddr_len(addr); // Safety: socket is valid for the duration of the call, addr lives for the duration of // the call and len is at most the length of addr. cerr(unsafe { libc::bind(self.fd, &addr as *const _ as *const _, len) })?; Ok(()) } pub(crate) fn connect(&self, addr: sockaddr_storage) -> std::io::Result<()> { // Per posix, it may be invalid to specify a length larger than that of the family. let len = sockaddr_len(addr); // Safety: socket is valid for the duration of the call, addr lives for the duration of // the call and len is at most the length of addr. cerr(unsafe { libc::connect(self.fd, &addr as *const _ as *const _, len) })?; Ok(()) } pub(crate) fn set_nonblocking(&self, nonblocking: bool) -> std::io::Result<()> { let nonblocking = nonblocking as libc::c_int; // Safety: nonblocking lives for the duration of the call, and is 4 bytes long as expected for FIONBIO cerr(unsafe { libc::ioctl(self.fd, libc::FIONBIO, &nonblocking) }).map(drop) } #[cfg(target_os = "linux")] pub(crate) fn reuse_addr(&self) -> std::io::Result<()> { let options = 1u32; // Safety: // // the pointer argument is valid, the size is accurate unsafe { cerr(libc::setsockopt( self.fd, libc::SOL_SOCKET, libc::SO_REUSEADDR, &options as *const _ as *const libc::c_void, std::mem::size_of_val(&options) as libc::socklen_t, ))?; } Ok(()) } pub(crate) fn receive_message<'a>( &self, packet_buf: &mut [u8], control_buf: &'a mut [u8], queue: MessageQueue, ) -> std::io::Result<( usize, impl Iterator + 'a, sockaddr_storage, )> { let mut buf_slice = IoSliceMut::new(packet_buf); let mut addr = zeroed_sockaddr_storage(); let mut mhdr = empty_msghdr(); mhdr.msg_control = control_buf.as_mut_ptr().cast::(); mhdr.msg_controllen = control_buf.len() as _; mhdr.msg_iov = (&mut buf_slice as *mut IoSliceMut).cast::(); mhdr.msg_iovlen = 1; mhdr.msg_flags = 0; mhdr.msg_name = (&mut addr as *mut libc::sockaddr_storage).cast::(); mhdr.msg_namelen = std::mem::size_of::() as u32; let receive_flags = match queue { MessageQueue::Normal => 0, #[cfg(target_os = "linux")] MessageQueue::Error => libc::MSG_ERRQUEUE, }; // Safety: // We have a mutable reference to the control buffer for the duration of the // call, and controllen is also set to it's length. // IoSliceMut is ABI compatible with iovec, and we only have 1 which matches // iovlen msg_name is initialized to point to an owned sockaddr_storage and // msg_namelen is the size of sockaddr_storage // If one of the buffers is too small, recvmsg cuts off data at appropriate // boundary let received_bytes = loop { match cerr(unsafe { libc::recvmsg(self.fd, &mut mhdr, receive_flags) } as _) { Err(e) if std::io::ErrorKind::Interrupted == e.kind() => { // retry when the recv was interrupted continue; } Err(e) => return Err(e), Ok(sent) => break sent as usize, } }; if mhdr.msg_flags & libc::MSG_TRUNC > 0 { tracing::info!( "truncated packet because it was larger than expected: {} bytes", packet_buf.len(), ); } if mhdr.msg_flags & libc::MSG_CTRUNC > 0 { tracing::info!("truncated control messages"); } // Clear out the fields for which we are giving up the reference mhdr.msg_iov = std::ptr::null_mut(); mhdr.msg_iovlen = 0; mhdr.msg_name = std::ptr::null_mut(); mhdr.msg_namelen = 0; // Safety: // recvmsg ensures that the control buffer contains // a set of valid control messages and that controllen is // the length these take up in the buffer. Ok(( received_bytes, unsafe { ControlMessageIterator::new(mhdr) }, addr, )) } pub(crate) fn send_to(&self, msg: &[u8], addr: sockaddr_storage) -> std::io::Result<()> { // Per posix, it may be invalid to specify a length larger than that of the family. let len = sockaddr_len(addr); // Safety: // the socket will outlive the call. // msg points to a block of memory of length msg.len() // addr points to a block of memory of length at least len // with flags=0, the other arguments don't matter for safety cerr(unsafe { libc::sendto( self.fd, msg as *const _ as *const c_void, msg.len(), 0, &addr as *const _ as *const sockaddr, len, ) as _ })?; Ok(()) } pub(crate) fn send(&self, msg: &[u8]) -> std::io::Result<()> { // Safety: // msg points to a block of memory of length msg.len() // with flags=0, the other arguments don't matter for safety cerr(unsafe { libc::send(self.fd, msg as *const _ as *const c_void, msg.len(), 0) as _ })?; Ok(()) } pub(crate) fn getsockname(&self) -> std::io::Result { let mut addr = zeroed_sockaddr_storage(); let mut addr_len: libc::socklen_t = std::mem::size_of_val(&addr) as _; // Safety: // the socket will outlive the call. // addr points to a block of memory of length addr_len // addr_len will outlive the call. cerr(unsafe { libc::getsockname( self.fd, &mut addr as *mut _ as *mut _, &mut addr_len as *mut _, ) })?; Ok(addr) } pub(crate) fn getpeername(&self) -> std::io::Result { let mut addr = zeroed_sockaddr_storage(); let mut addr_len: libc::socklen_t = std::mem::size_of_val(&addr) as _; // Safety: // the socket will outlive the call. // addr points to a block of memory of length addr_len // addr_len will outlive the call. cerr(unsafe { libc::getpeername( self.fd, &mut addr as *mut _ as *mut _, &mut addr_len as *mut _, ) })?; Ok(addr) } } fn sockaddr_len(addr: sockaddr_storage) -> u32 { let len: libc::socklen_t = std::mem::size_of_val(&addr) as _; len.min(match addr.ss_family as _ { libc::AF_INET => std::mem::size_of::() as _, libc::AF_INET6 => std::mem::size_of::() as _, _ => len, }) } impl Drop for RawSocket { fn drop(&mut self) { // Safety: close is always safe to call on a file descriptor unsafe { libc::close(self.fd) }; } } timestamped-socket-0.2.2/src/socket/fallback.rs000064400000000000000000000005421046102023000176140ustar 00000000000000use crate::raw_socket::RawSocket; use super::InterfaceTimestampMode; pub(super) fn configure_timestamping( _socket: &RawSocket, mode: InterfaceTimestampMode, _bind_phc: Option, ) -> std::io::Result<()> { match mode { InterfaceTimestampMode::None => Ok(()), _ => Err(std::io::ErrorKind::Unsupported.into()), } } timestamped-socket-0.2.2/src/socket/freebsd.rs000064400000000000000000000006511046102023000174700ustar 00000000000000use crate::raw_socket::RawSocket; use super::InterfaceTimestampMode; pub(super) fn configure_timestamping( socket: &RawSocket, mode: InterfaceTimestampMode, _bind_phc: Option, ) -> std::io::Result<()> { match mode { InterfaceTimestampMode::None => Ok(()), InterfaceTimestampMode::SoftwareRecv => socket.so_timestamp(1), _ => Err(std::io::ErrorKind::Unsupported.into()), } } timestamped-socket-0.2.2/src/socket/linux.rs000064400000000000000000000345421046102023000172230ustar 00000000000000use std::{ marker::PhantomData, net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, }; use tokio::io::{unix::AsyncFd, Interest}; use crate::{ control_message::{control_message_space, ControlMessage, MessageQueue}, interface::InterfaceName, networkaddress::{sealed::PrivateToken, EthernetAddress, MacAddress, NetworkAddress}, raw_socket::RawSocket, socket::select_timestamp, }; use super::{InterfaceTimestampMode, Open, Socket, Timestamp}; const SOF_TIMESTAMPING_BIND_PHC: libc::c_uint = 1 << 15; impl Socket { pub(super) async fn fetch_send_timestamp( &self, expected_counter: u32, ) -> std::io::Result> { use std::time::Duration; const TIMEOUT: Duration = Duration::from_millis(200); match tokio::time::timeout(TIMEOUT, self.fetch_send_timestamp_loop(expected_counter)).await { Ok(res_opt_timestamp) => res_opt_timestamp, Err(_timeout_elapsed) => Ok(None), } } pub(super) async fn fetch_send_timestamp_loop( &self, expected_counter: u32, ) -> std::io::Result> { let try_read = |_: &RawSocket| self.fetch_send_timestamp_try_read(expected_counter); loop { // the timestamp being available triggers the error interest match self.socket.async_io(Interest::ERROR, try_read).await? { Some(timestamp) => break Ok(Some(timestamp)), None => continue, } } } pub(super) fn fetch_send_timestamp_try_read( &self, expected_counter: u32, ) -> std::io::Result> { const CONTROL_SIZE: usize = control_message_space::<[libc::timespec; 3]>() + control_message_space::<(libc::sock_extended_err, libc::sockaddr_storage)>(); let mut control_buf = [0; CONTROL_SIZE]; // NOTE: this read could block! let (_, control_messages, _) = self.socket.get_ref().receive_message( &mut [], &mut control_buf, MessageQueue::Error, )?; let mut send_ts = None; for msg in control_messages { match msg { ControlMessage::Timestamping { software, hardware } => { send_ts = select_timestamp(self.timestamp_mode, software, hardware); } ControlMessage::ReceiveError(error) => { // the timestamping does not set a message; if there is a message, that means // something else is wrong, and we want to know about it. if error.ee_errno as libc::c_int != libc::ENOMSG { tracing::warn!( expected_counter, error.ee_data, "error message on the MSG_ERRQUEUE" ); } // Check that this message belongs to the send we are interested in if error.ee_data != expected_counter { tracing::debug!( error.ee_data, expected_counter, "Timestamp for unrelated packet" ); return Ok(None); } } ControlMessage::Other(msg) => { tracing::warn!( msg.cmsg_level, msg.cmsg_type, "unexpected message on the MSG_ERRQUEUE", ); } } } Ok(send_ts) } } pub(super) fn configure_timestamping( socket: &RawSocket, mode: InterfaceTimestampMode, bind_phc: Option, ) -> std::io::Result<()> { let options = match mode { InterfaceTimestampMode::HardwareAll | InterfaceTimestampMode::HardwarePTPAll => { libc::SOF_TIMESTAMPING_RAW_HARDWARE | libc::SOF_TIMESTAMPING_TX_SOFTWARE | libc::SOF_TIMESTAMPING_RX_HARDWARE | libc::SOF_TIMESTAMPING_TX_HARDWARE | libc::SOF_TIMESTAMPING_OPT_TSONLY | libc::SOF_TIMESTAMPING_OPT_ID | bind_phc .map(|_| SOF_TIMESTAMPING_BIND_PHC) .unwrap_or_default() } InterfaceTimestampMode::HardwareRecv | InterfaceTimestampMode::HardwarePTPRecv => { libc::SOF_TIMESTAMPING_RAW_HARDWARE | libc::SOF_TIMESTAMPING_RX_HARDWARE | bind_phc .map(|_| SOF_TIMESTAMPING_BIND_PHC) .unwrap_or_default() } InterfaceTimestampMode::SoftwareAll => { libc::SOF_TIMESTAMPING_SOFTWARE | libc::SOF_TIMESTAMPING_RX_SOFTWARE | libc::SOF_TIMESTAMPING_TX_SOFTWARE | libc::SOF_TIMESTAMPING_OPT_TSONLY | libc::SOF_TIMESTAMPING_OPT_ID } InterfaceTimestampMode::SoftwareRecv => { libc::SOF_TIMESTAMPING_SOFTWARE | libc::SOF_TIMESTAMPING_RX_SOFTWARE } InterfaceTimestampMode::None => return Ok(()), }; socket.so_timestamping(options, bind_phc.unwrap_or_default()) } pub fn open_interface_udp( interface: InterfaceName, port: u16, timestamping: InterfaceTimestampMode, bind_phc: Option, ) -> std::io::Result> { // Setup the socket let socket = RawSocket::open(libc::PF_INET6, libc::SOCK_DGRAM, libc::IPPROTO_UDP)?; socket.reuse_addr()?; socket.ipv6_v6only(false)?; socket.bind(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, port, 0, 0).to_sockaddr(PrivateToken))?; socket.bind_to_device(interface)?; socket.ipv6_multicast_if(interface)?; socket.ipv6_multicast_loop(false)?; configure_timestamping(&socket, timestamping, bind_phc)?; match timestamping { InterfaceTimestampMode::HardwareAll | InterfaceTimestampMode::HardwareRecv => { socket.driver_enable_hardware_timestamping(interface, libc::HWTSTAMP_FILTER_ALL as _)? } InterfaceTimestampMode::HardwarePTPAll | InterfaceTimestampMode::HardwarePTPRecv => socket .driver_enable_hardware_timestamping( interface, libc::HWTSTAMP_FILTER_PTP_V2_L4_EVENT as _, )?, InterfaceTimestampMode::None | InterfaceTimestampMode::SoftwareAll | InterfaceTimestampMode::SoftwareRecv => {} } socket.set_nonblocking(true)?; Ok(Socket { timestamp_mode: timestamping, socket: AsyncFd::new(socket)?, send_counter: 0, _addr: PhantomData, _state: PhantomData, }) } pub fn open_interface_udp4( interface: InterfaceName, port: u16, timestamping: InterfaceTimestampMode, bind_phc: Option, ) -> std::io::Result> { // Setup the socket let socket = RawSocket::open(libc::PF_INET, libc::SOCK_DGRAM, libc::IPPROTO_UDP)?; socket.reuse_addr()?; socket.bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port).to_sockaddr(PrivateToken))?; socket.bind_to_device(interface)?; socket.ip_multicast_if(interface)?; socket.ip_multicast_loop(false)?; configure_timestamping(&socket, timestamping, bind_phc)?; match timestamping { InterfaceTimestampMode::HardwareAll | InterfaceTimestampMode::HardwareRecv => { socket.driver_enable_hardware_timestamping(interface, libc::HWTSTAMP_FILTER_ALL as _)? } InterfaceTimestampMode::HardwarePTPAll | InterfaceTimestampMode::HardwarePTPRecv => socket .driver_enable_hardware_timestamping( interface, libc::HWTSTAMP_FILTER_PTP_V2_L4_EVENT as _, )?, InterfaceTimestampMode::None | InterfaceTimestampMode::SoftwareAll | InterfaceTimestampMode::SoftwareRecv => {} } socket.set_nonblocking(true)?; Ok(Socket { timestamp_mode: timestamping, socket: AsyncFd::new(socket)?, send_counter: 0, _addr: PhantomData, _state: PhantomData, }) } pub fn open_interface_udp6( interface: InterfaceName, port: u16, timestamping: InterfaceTimestampMode, bind_phc: Option, ) -> std::io::Result> { // Setup the socket let socket = RawSocket::open(libc::PF_INET6, libc::SOCK_DGRAM, libc::IPPROTO_UDP)?; socket.reuse_addr()?; socket.ipv6_v6only(true)?; socket.bind(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, port, 0, 0).to_sockaddr(PrivateToken))?; socket.bind_to_device(interface)?; socket.ipv6_multicast_if(interface)?; socket.ipv6_multicast_loop(false)?; configure_timestamping(&socket, timestamping, bind_phc)?; match timestamping { InterfaceTimestampMode::HardwareAll | InterfaceTimestampMode::HardwareRecv => { socket.driver_enable_hardware_timestamping(interface, libc::HWTSTAMP_FILTER_ALL as _)? } InterfaceTimestampMode::HardwarePTPAll | InterfaceTimestampMode::HardwarePTPRecv => socket .driver_enable_hardware_timestamping( interface, libc::HWTSTAMP_FILTER_PTP_V2_L4_EVENT as _, )?, InterfaceTimestampMode::None | InterfaceTimestampMode::SoftwareAll | InterfaceTimestampMode::SoftwareRecv => {} } socket.set_nonblocking(true)?; Ok(Socket { timestamp_mode: timestamping, socket: AsyncFd::new(socket)?, send_counter: 0, _addr: PhantomData, _state: PhantomData, }) } pub fn open_interface_ethernet( interface: InterfaceName, protocol: u16, timestamping: InterfaceTimestampMode, bind_phc: Option, ) -> std::io::Result> { let socket = RawSocket::open( libc::AF_PACKET, libc::SOCK_DGRAM, u16::from_ne_bytes(protocol.to_be_bytes()) as _, )?; socket.bind( EthernetAddress::new( u16::from_ne_bytes(protocol.to_le_bytes()), MacAddress::new([0; 6]), interface .get_index() .ok_or(std::io::ErrorKind::InvalidInput)? as _, ) .to_sockaddr(PrivateToken), )?; configure_timestamping(&socket, timestamping, bind_phc)?; match timestamping { InterfaceTimestampMode::HardwareAll | InterfaceTimestampMode::HardwareRecv => { socket.driver_enable_hardware_timestamping(interface, libc::HWTSTAMP_FILTER_ALL as _)? } InterfaceTimestampMode::HardwarePTPAll | InterfaceTimestampMode::HardwarePTPRecv => socket .driver_enable_hardware_timestamping( interface, libc::HWTSTAMP_FILTER_PTP_V2_L2_EVENT as _, )?, InterfaceTimestampMode::None | InterfaceTimestampMode::SoftwareAll | InterfaceTimestampMode::SoftwareRecv => {} } socket.set_nonblocking(true)?; Ok(Socket { timestamp_mode: timestamping, socket: AsyncFd::new(socket)?, send_counter: 0, _addr: PhantomData, _state: PhantomData, }) } #[cfg(test)] mod tests { use std::net::IpAddr; use crate::socket::{connect_address, open_ip, GeneralTimestampMode}; use super::*; #[tokio::test] async fn test_open_udp6() { use std::str::FromStr; let mut a = open_interface_udp6( InterfaceName::from_str("lo").unwrap(), 5123, super::InterfaceTimestampMode::None, None, ) .unwrap(); let mut b = connect_address( SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 5123), GeneralTimestampMode::None, ) .unwrap(); assert!(b.send(&[1, 2, 3]).await.is_ok()); let mut buf = [0; 4]; let recv_result = a.recv(&mut buf).await.unwrap(); assert_eq!(recv_result.bytes_read, 3); assert_eq!(&buf[0..3], &[1, 2, 3]); assert!(a.send_to(&[4, 5, 6], recv_result.remote_addr).await.is_ok()); let recv_result = b.recv(&mut buf).await.unwrap(); assert_eq!(recv_result.bytes_read, 3); assert_eq!(&buf[0..3], &[4, 5, 6]); } #[tokio::test] async fn test_open_udp4() { use std::str::FromStr; let mut a = open_interface_udp4( InterfaceName::from_str("lo").unwrap(), 5124, super::InterfaceTimestampMode::None, None, ) .unwrap(); let mut b = connect_address( SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5124), GeneralTimestampMode::None, ) .unwrap(); assert!(b.send(&[1, 2, 3]).await.is_ok()); let mut buf = [0; 4]; let recv_result = a.recv(&mut buf).await.unwrap(); assert_eq!(recv_result.bytes_read, 3); assert_eq!(&buf[0..3], &[1, 2, 3]); assert!(a.send_to(&[4, 5, 6], recv_result.remote_addr).await.is_ok()); let recv_result = b.recv(&mut buf).await.unwrap(); assert_eq!(recv_result.bytes_read, 3); assert_eq!(&buf[0..3], &[4, 5, 6]); } #[tokio::test] async fn test_software_timestamping() { use std::time::SystemTime; let a = open_ip( SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5126), GeneralTimestampMode::SoftwareAll, ) .unwrap(); let mut b = connect_address( SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5126), GeneralTimestampMode::SoftwareAll, ) .unwrap(); let before = SystemTime::now(); let send_ts = b.send(&[1, 2, 3]).await.unwrap().unwrap(); let after = SystemTime::now(); let mut buf = [0; 4]; let recv_result = a.recv(&mut buf).await.unwrap(); let recv_ts = recv_result.timestamp.unwrap(); let before = before .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(); let after = after .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(); assert!((send_ts.seconds - (before as i64)).abs() < 2); assert!((send_ts.seconds - (after as i64)).abs() < 2); let send_nanos = send_ts.seconds * 1_000_000_000 + (send_ts.nanos as i64); let recv_nanos = recv_ts.seconds * 1_000_000_000 + (recv_ts.nanos as i64); assert!((send_nanos - recv_nanos) < 1_000_000 * 10); } } timestamped-socket-0.2.2/src/socket/macos.rs000064400000000000000000000006511046102023000171600ustar 00000000000000use crate::raw_socket::RawSocket; use super::InterfaceTimestampMode; pub(super) fn configure_timestamping( socket: &RawSocket, mode: InterfaceTimestampMode, _bind_phc: Option, ) -> std::io::Result<()> { match mode { InterfaceTimestampMode::None => Ok(()), InterfaceTimestampMode::SoftwareRecv => socket.so_timestamp(1), _ => Err(std::io::ErrorKind::Unsupported.into()), } } timestamped-socket-0.2.2/src/socket.rs000064400000000000000000000250741046102023000160640ustar 00000000000000use std::{marker::PhantomData, net::SocketAddr, os::fd::AsRawFd}; use tokio::io::{unix::AsyncFd, Interest}; use crate::{ control_message::{control_message_space, ControlMessage, MessageQueue}, interface::InterfaceName, networkaddress::{sealed::PrivateToken, MulticastJoinable, NetworkAddress}, raw_socket::RawSocket, }; #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "macos")))] mod fallback; #[cfg(target_os = "freebsd")] mod freebsd; #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "macos")] mod macos; #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "macos")))] use self::fallback::*; #[cfg(target_os = "freebsd")] use self::freebsd::*; #[cfg(target_os = "linux")] pub use self::linux::*; #[cfg(target_os = "macos")] use self::macos::*; #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] pub struct Timestamp { pub seconds: i64, pub nanos: u32, } impl Timestamp { #[cfg_attr(target_os = "macos", allow(unused))] // macos does not do nanoseconds pub(crate) fn from_timespec(timespec: libc::timespec) -> Self { Self { seconds: timespec.tv_sec as _, nanos: timespec.tv_nsec as _, } } pub(crate) fn from_timeval(timeval: libc::timeval) -> Self { Self { seconds: timeval.tv_sec as _, nanos: (1000 * timeval.tv_usec) as _, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum GeneralTimestampMode { SoftwareAll, SoftwareRecv, #[default] None, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum InterfaceTimestampMode { HardwareAll, HardwareRecv, HardwarePTPAll, HardwarePTPRecv, SoftwareAll, SoftwareRecv, #[default] None, } impl From for InterfaceTimestampMode { fn from(value: GeneralTimestampMode) -> Self { match value { GeneralTimestampMode::SoftwareAll => InterfaceTimestampMode::SoftwareAll, GeneralTimestampMode::SoftwareRecv => InterfaceTimestampMode::SoftwareRecv, GeneralTimestampMode::None => InterfaceTimestampMode::None, } } } fn select_timestamp( mode: InterfaceTimestampMode, software: Option, hardware: Option, ) -> Option { use InterfaceTimestampMode::*; match mode { SoftwareAll | SoftwareRecv => software, HardwareAll | HardwareRecv | HardwarePTPAll | HardwarePTPRecv => hardware, None => Option::None, } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct RecvResult { pub bytes_read: usize, pub remote_addr: A, pub timestamp: Option, } #[derive(Debug)] pub struct Socket { timestamp_mode: InterfaceTimestampMode, socket: AsyncFd, #[cfg(target_os = "linux")] send_counter: u32, _addr: PhantomData, _state: PhantomData, } pub struct Open; pub struct Connected; impl Socket { pub fn local_addr(&self) -> std::io::Result { let addr = self.socket.get_ref().getsockname()?; A::from_sockaddr(addr, PrivateToken).ok_or_else(|| std::io::ErrorKind::Other.into()) } pub fn peer_addr(&self) -> std::io::Result { let addr = self.socket.get_ref().getpeername()?; A::from_sockaddr(addr, PrivateToken).ok_or_else(|| std::io::ErrorKind::Other.into()) } pub async fn recv(&self, buf: &mut [u8]) -> std::io::Result> { self.socket .async_io(Interest::READABLE, |socket| { let mut control_buf = [0; control_message_space::<[libc::timespec; 3]>()]; // loops for when we receive an interrupt during the recv let (bytes_read, control_messages, remote_address) = socket.receive_message(buf, &mut control_buf, MessageQueue::Normal)?; let mut timestamp = None; // Loops through the control messages, but we should only get a single message // in practice for msg in control_messages { match msg { ControlMessage::Timestamping { software, hardware } => { tracing::trace!("Timestamps: {:?} {:?}", software, hardware); timestamp = select_timestamp(self.timestamp_mode, software, hardware); } #[cfg(target_os = "linux")] ControlMessage::ReceiveError(error) => { tracing::warn!( "unexpected error control message on receive: {}", error.ee_errno ); } ControlMessage::Other(msg) => { tracing::debug!( "unexpected control message on receive: {} {}", msg.cmsg_level, msg.cmsg_type, ); } } } let remote_addr = A::from_sockaddr(remote_address, PrivateToken) .ok_or(std::io::ErrorKind::Other)?; Ok(RecvResult { bytes_read, remote_addr, timestamp, }) }) .await } } impl Socket { pub async fn send_to(&mut self, buf: &[u8], addr: A) -> std::io::Result> { let addr = addr.to_sockaddr(PrivateToken); self.socket .async_io(Interest::WRITABLE, |socket| socket.send_to(buf, addr)) .await?; if matches!( self.timestamp_mode, InterfaceTimestampMode::HardwarePTPAll | InterfaceTimestampMode::SoftwareAll ) { #[cfg(target_os = "linux")] { let expected_counter = self.send_counter; self.send_counter = self.send_counter.wrapping_add(1); self.fetch_send_timestamp(expected_counter).await } #[cfg(not(target_os = "linux"))] { unreachable!("Should not be able to create send timestamping sockets on platforms other than linux") } } else { Ok(None) } } pub fn connect(self, addr: A) -> std::io::Result> { let addr = addr.to_sockaddr(PrivateToken); self.socket.get_ref().connect(addr)?; Ok(Socket { timestamp_mode: self.timestamp_mode, socket: self.socket, #[cfg(target_os = "linux")] send_counter: self.send_counter, _addr: PhantomData, _state: PhantomData, }) } } impl Socket { pub async fn send(&mut self, buf: &[u8]) -> std::io::Result> { self.socket .async_io(Interest::WRITABLE, |socket| socket.send(buf)) .await?; if matches!( self.timestamp_mode, InterfaceTimestampMode::HardwarePTPAll | InterfaceTimestampMode::SoftwareAll ) { #[cfg(target_os = "linux")] { let expected_counter = self.send_counter; self.send_counter = self.send_counter.wrapping_add(1); self.fetch_send_timestamp(expected_counter).await } #[cfg(not(target_os = "linux"))] { unreachable!("Should not be able to create send timestamping sockets on platforms other than linux") } } else { Ok(None) } } } impl Socket { pub fn join_multicast(&self, addr: A, interface: InterfaceName) -> std::io::Result<()> { addr.join_multicast(self.socket.get_ref().as_raw_fd(), interface, PrivateToken) } pub fn leave_multicast(&self, addr: A, interface: InterfaceName) -> std::io::Result<()> { addr.leave_multicast(self.socket.get_ref().as_raw_fd(), interface, PrivateToken) } } pub fn open_ip( addr: SocketAddr, timestamping: GeneralTimestampMode, ) -> std::io::Result> { // Setup the socket let socket = match addr { SocketAddr::V4(_) => RawSocket::open(libc::PF_INET, libc::SOCK_DGRAM, libc::IPPROTO_UDP), SocketAddr::V6(_) => RawSocket::open(libc::PF_INET6, libc::SOCK_DGRAM, libc::IPPROTO_UDP), }?; socket.bind(addr.to_sockaddr(PrivateToken))?; socket.set_nonblocking(true)?; configure_timestamping(&socket, timestamping.into(), None)?; Ok(Socket { timestamp_mode: timestamping.into(), socket: AsyncFd::new(socket)?, #[cfg(target_os = "linux")] send_counter: 0, _addr: PhantomData, _state: PhantomData, }) } pub fn connect_address( addr: SocketAddr, timestamping: GeneralTimestampMode, ) -> std::io::Result> { // Setup the socket let socket = match addr { SocketAddr::V4(_) => RawSocket::open(libc::PF_INET, libc::SOCK_DGRAM, libc::IPPROTO_UDP), SocketAddr::V6(_) => RawSocket::open(libc::PF_INET6, libc::SOCK_DGRAM, libc::IPPROTO_UDP), }?; socket.connect(addr.to_sockaddr(PrivateToken))?; socket.set_nonblocking(true)?; configure_timestamping(&socket, timestamping.into(), None)?; Ok(Socket { timestamp_mode: timestamping.into(), socket: AsyncFd::new(socket)?, #[cfg(target_os = "linux")] send_counter: 0, _addr: PhantomData, _state: PhantomData, }) } #[cfg(test)] mod tests { use super::*; use std::net::{IpAddr, Ipv4Addr}; #[tokio::test] async fn test_open_ip() { let mut a = open_ip( SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5125), GeneralTimestampMode::None, ) .unwrap(); let mut b = connect_address( SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5125), GeneralTimestampMode::None, ) .unwrap(); assert!(b.send(&[1, 2, 3]).await.is_ok()); let mut buf = [0; 4]; let recv_result = a.recv(&mut buf).await.unwrap(); assert_eq!(recv_result.bytes_read, 3); assert_eq!(&buf[0..3], &[1, 2, 3]); assert!(a.send_to(&[4, 5, 6], recv_result.remote_addr).await.is_ok()); let recv_result = b.recv(&mut buf).await.unwrap(); assert_eq!(recv_result.bytes_read, 3); assert_eq!(&buf[0..3], &[4, 5, 6]); } }