mozim-0.2.4/.cargo/config.toml000064400000000000000000000000651046102023000142760ustar 00000000000000[target.x86_64-unknown-linux-gnu] runner = 'sudo -E' mozim-0.2.4/.cargo_vcs_info.json0000644000000001360000000000100121720ustar { "git": { "sha1": "a48b393c046be77351b2efaabe8f99f80871a431" }, "path_in_vcs": "" }mozim-0.2.4/.github/workflows/build.yml000064400000000000000000000013321046102023000162000ustar 00000000000000name: Build on: pull_request: types: [opened, synchronize, reopened] push: branches: - main jobs: build: name: Build runs-on: ubuntu-latest strategy: fail-fast: true matrix: include: - rust_target: "i686-unknown-linux-gnu" - rust_target: "x86_64-unknown-linux-gnu" - rust_target: "loongarch64-unknown-linux-gnu" steps: - uses: actions/checkout@v3 - name: Install Rust Stable run: | rustup override set stable rustup update stable rustup target add ${{ matrix.rust_target }} - name: Build test for ${{ matrix.rust_target }} run: cargo build --target ${{ matrix.rust_target }} mozim-0.2.4/.github/workflows/main.yaml000064400000000000000000000022251046102023000161700ustar 00000000000000name: CI on: pull_request: types: [opened, synchronize, reopened] jobs: rust_lint: strategy: fail-fast: true matrix: include: - rust_version: "stable" - rust_version: "nightly" runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust ${{ matrix.rust_version }} run: | rustup override set ${{ matrix.rust_version }} rustup update ${{ matrix.rust_version }} rustup component add rustfmt clippy - name: Check fmt if: matrix.rust_version == 'stable' run: cargo fmt -- --check - name: Check clippy if: matrix.rust_version == 'nightly' run: cargo clippy -- -D warnings rust_integ: runs-on: ubuntu-latest steps: - name: Install packages run: sudo apt-get -y install dnsmasq - uses: actions/checkout@v3 - name: Install Rust stable run: rustup default stable - name: Run test env: # Needed for the `link::test::create_get_delete_w` test to pass. CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: "sudo -E" run: cargo test -- --test-threads=1 --show-output mozim-0.2.4/.gitignore000064400000000000000000000000631046102023000127510ustar 00000000000000/target *.swp Cargo.lock tags vendor/ *.orig *.rej mozim-0.2.4/.rustfmt.toml000064400000000000000000000001141046102023000134350ustar 00000000000000max_width = 80 reorder_imports = true edition = "2021" wrap_comments = true mozim-0.2.4/CHANGELOG000064400000000000000000000017661046102023000122060ustar 00000000000000# Changelog ## [0.2.4] - 2024-07-10 ### Breaking changes - N/A ### New features - N/A ### Bug fixes - Apply BPF filter right after raw socket been created. (e34bc25) - Ignore invalid DHCP message during discovery and request stage. (fed292c) ## [0.2.3] - 2024-02-19 ### Breaking changes - N/A ### New features - Support LoongArch. (c6d8e32) ### Bug fixes - N/A ## [0.2.2] - 2023-03-24 ### Breaking changes - N/A ### New features - N/A ### Bug fixes - Fix DHCP Server Identifier(54) option. (f267f10) ## [0.2.1] - 2023-02-17 ### Breaking changes - N/A ### New features - N/A ### Bug fixes - Fix build failuer on i686. (7e877da) - Improve ASYNC API performance. (341dfba) ## [0.2.0] - 2023-02-12 ### Breaking changes - Change the `DhcpV4Config::new()` to return `DhcpV4Config` instead of result. (ab07930) ### New features - Add async API. (05c04d9) ### Bug fixes - N/A ## [0.1.0] - 2022-12-01 ### Breaking changes - N/A ### New features - Initial release ### Bug fixes - N/A mozim-0.2.4/Cargo.lock0000644000000726010000000000100101530ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anstream" version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "anyhow" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-trait" version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", "syn 2.0.70", ] [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "colorchoice" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "data-encoding" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "dhcproto" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcee045385d5f7819022821f41209b9945d17550760b0b2349aaef4ecfa14bc3" dependencies = [ "dhcproto-macros", "hex", "ipnet", "rand", "thiserror", "trust-dns-proto", "url", ] [[package]] name = "dhcproto-macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7993efb860416547839c115490d4951c6d0f8ec04a3594d9dd99d50ed7ec170" [[package]] name = "enum-as-inner" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ "heck", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "env_filter" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ "anstream", "anstyle", "env_filter", "humantime", "log", ] [[package]] name = "etherparse" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "827292ea592108849932ad8e30218f8b1f21c0dfd0696698a18b5d0aed62d990" dependencies = [ "arrayvec", ] [[package]] name = "ethtool" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d8e04a35517dc77748dc04bf38152799382d3d8f85cb07cb579bb7f4d8d3b5a" dependencies = [ "anyhow", "byteorder", "futures", "genetlink", "log", "netlink-packet-core", "netlink-packet-generic", "netlink-packet-utils", "netlink-proto", "netlink-sys", "thiserror", "tokio", ] [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", "syn 2.0.70", ] [[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "genetlink" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f890076c1faa1298bf747ce3694a8d9e0d2cc4b06fe293f12dd95742bfd079f" dependencies = [ "futures", "log", "netlink-packet-core", "netlink-packet-generic", "netlink-packet-utils", "netlink-proto", "thiserror", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "idna" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "idna" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is_terminal_polyfill" version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "matches" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", "windows-sys 0.48.0", ] [[package]] name = "mozim" version = "0.2.4" dependencies = [ "byteorder", "dhcproto", "env_logger", "etherparse", "futures", "libc", "log", "nispor", "nix", "rand", "tokio", ] [[package]] name = "mptcp-pm" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eafa8fc63dce407b75e336f9a22f18cf5510a3a5c3a5d83262688eb5cca42d5" dependencies = [ "anyhow", "byteorder", "futures", "genetlink", "log", "netlink-packet-core", "netlink-packet-generic", "netlink-packet-utils", "netlink-proto", "netlink-sys", "thiserror", "tokio", ] [[package]] name = "netlink-packet-core" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" dependencies = [ "anyhow", "byteorder", "netlink-packet-utils", ] [[package]] name = "netlink-packet-generic" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd7eb8ad331c84c6b8cb7f685b448133e5ad82e1ffd5acafac374af4a5a308b" dependencies = [ "anyhow", "byteorder", "netlink-packet-core", "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74c171cd77b4ee8c7708da746ce392440cb7bcf618d122ec9ecc607b12938bf4" dependencies = [ "anyhow", "byteorder", "libc", "log", "netlink-packet-core", "netlink-packet-utils", ] [[package]] name = "netlink-packet-utils" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" dependencies = [ "anyhow", "byteorder", "paste", "thiserror", ] [[package]] name = "netlink-proto" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b33524dc0968bfad349684447bfce6db937a9ac3332a1fe60c0c5a5ce63f21" dependencies = [ "bytes", "futures", "log", "netlink-packet-core", "netlink-sys", "thiserror", "tokio", ] [[package]] name = "netlink-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" dependencies = [ "bytes", "futures", "libc", "log", "tokio", ] [[package]] name = "nispor" version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cd23dbe46d34178505027dc6b39446d3ce4e726fbeb90f397f1caa1f3bcdd4e" dependencies = [ "ethtool", "futures", "libc", "log", "mptcp-pm", "netlink-packet-route", "netlink-packet-utils", "netlink-sys", "rtnetlink", "serde", "serde_json", "tokio", ] [[package]] name = "nix" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ "bitflags", "cfg-if", "libc", ] [[package]] name = "object" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "regex" version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rtnetlink" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b684475344d8df1859ddb2d395dd3dac4f8f3422a1aa0725993cb375fc5caba5" dependencies = [ "futures", "log", "netlink-packet-core", "netlink-packet-route", "netlink-packet-utils", "netlink-proto", "netlink-sys", "nix", "thiserror", "tokio", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", "syn 2.0.70", ] [[package]] name = "serde_json" version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", "syn 2.0.70", ] [[package]] name = "tinyvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", "socket2", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", "syn 2.0.70", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", "syn 2.0.70", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "trust-dns-proto" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" dependencies = [ "async-trait", "cfg-if", "data-encoding", "enum-as-inner", "futures-channel", "futures-io", "futures-util", "idna 0.2.3", "ipnet", "lazy_static", "rand", "smallvec", "thiserror", "tinyvec", "tracing", "url", ] [[package]] name = "unicode-bidi" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "url" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna 0.5.0", "percent-encoding", ] [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[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 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" mozim-0.2.4/Cargo.toml0000644000000027050000000000100101740ustar # 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" name = "mozim" version = "0.2.4" build = false autobins = false autoexamples = false autotests = false autobenches = false description = "DHCP Client Library" homepage = "https://github.com/nispor/mozim" readme = "README.md" license = "Apache-2.0" [lib] name = "mozim" path = "src/lib.rs" [[example]] name = "mozim_async" path = "examples/mozim_async.rs" [dependencies.byteorder] version = "1.4.3" [dependencies.dhcproto] version = "0.9.0" [dependencies.etherparse] version = "0.13.0" [dependencies.futures] version = "0.3" features = ["std"] default-features = false [dependencies.libc] version = "0.2.132" [dependencies.log] version = "0.4.17" [dependencies.nispor] version = "1.2.17" [dependencies.nix] version = "0.27.0" features = [ "poll", "time", "event", ] [dependencies.rand] version = "0.8.5" default-features = false [dev-dependencies.env_logger] version = "0.11.0" [dev-dependencies.tokio] version = "1.19" features = [ "macros", "rt", ] mozim-0.2.4/Cargo.toml.orig0000644000000011670000000000100111340ustar [package] name = "mozim" version = "0.2.4" description = "DHCP Client Library" license = "Apache-2.0" homepage = "https://github.com/nispor/mozim" edition = "2021" [lib] name = "mozim" path = "src/lib.rs" [dependencies] rand = { version = "0.8.5", default-features = false } libc = "0.2.132" byteorder = "1.4.3" dhcproto = "0.9.0" log = "0.4.17" etherparse = "0.13.0" nix = { version = "0.27.0", features = ["poll", "time", "event"] } nispor = "1.2.17" futures = { version = "0.3", default-features = false, features = ["std"] } [dev-dependencies] tokio = { version = "1.19", features = ["macros", "rt"] } env_logger = "0.11.0" mozim-0.2.4/Cargo.toml.orig000064400000000000000000000011671046102023000136560ustar 00000000000000[package] name = "mozim" version = "0.2.4" description = "DHCP Client Library" license = "Apache-2.0" homepage = "https://github.com/nispor/mozim" edition = "2021" [lib] name = "mozim" path = "src/lib.rs" [dependencies] rand = { version = "0.8.5", default-features = false } libc = "0.2.132" byteorder = "1.4.3" dhcproto = "0.9.0" log = "0.4.17" etherparse = "0.13.0" nix = { version = "0.27.0", features = ["poll", "time", "event"] } nispor = "1.2.17" futures = { version = "0.3", default-features = false, features = ["std"] } [dev-dependencies] tokio = { version = "1.19", features = ["macros", "rt"] } env_logger = "0.11.0" mozim-0.2.4/LICENSE000064400000000000000000000261351046102023000117760ustar 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 [yyyy] [name of copyright owner] 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. mozim-0.2.4/README.md000064400000000000000000000012711046102023000122420ustar 00000000000000# Mozim -- DHCP Client Library Still doing code sign, no real work this project can do yet. Check again in 2022. DONE: * raw socket with BPF applied and accepting all mac address. * DHCP discovery and request. * Renew, rebind. * DHCP IP apply via cli tool `mzc`. * Route * Timeout and retry TODO: * Verify XID. * Handle vendor difference: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/848 * Support multiple DHCP servers with `DHCPNAK` reply. * Support DHCPNAK # Try out ```bash # Below script will create veth eth1/eth1.ep. # The `eth1.ep` is DHCP server interface running dnsmasq in `mozim` network # namespace. sudo ./utils/test_env_mozim & cargo run ``` mozim-0.2.4/doc/dhcp_discovery_with_lease.pcapng000064400000000000000000000034701046102023000201360ustar 00000000000000 ¬M<+ÿÿÿÿÿÿÿÿ6Intel(R) Core(TM) i7-8665U CPU @ 1.90GHz (with SSE4.2)Linux 5.10.79-1-lts4Dumpcap (Wireshark) 3.4.10 (Git commit 733b3a137c2b)¬@eth1  Linux 5.10.79-1-lts@x ºywžKVVÿÿÿÿÿÿ6//xEH€9–ÿÿÿÿDC4Óè,Êz6//xc‚Sc52À 7 =6//xÿxx º>פKVV6//xÂ‡ÙÆ7¥EÀH>]@´zÀÀ CD4‡S,ÊzÀ À6//xc‚Sc56À3x:<;iÿÿÿÀÿÀÀÿxx ºN+©KVVÿÿÿÿÿÿ6//xEH€9–ÿÿÿÿDC4Ëï,Êz6//xc‚Sc56À2À 7 =6//xÿxx º}¬LVV6//xÂ‡ÙÆ7¥EÀH>^@´yÀÀ CD4‡S,ÊzÀ À6//xc‚Sc56À3x:<;iÿÿÿÀÿÀÀÿxlkÑ–ðgCounters provided by dumpcapkÑéjNgkÑÏŒðglmozim-0.2.4/doc/dhcp_discovery_without_lease.pcapng000064400000000000000000000034701046102023000206660ustar 00000000000000 ¬M<+ÿÿÿÿÿÿÿÿ6Intel(R) Core(TM) i7-8665U CPU @ 1.90GHz (with SSE4.2)Linux 5.10.79-1-lts4Dumpcap (Wireshark) 3.4.10 (Git commit 733b3a137c2b)¬@eth1  Linux 5.10.79-1-lts@xO ºoRVVÿÿÿÿÿÿ6//xEH€9–ÿÿÿÿDC49ò“Q6//xc‚Sc57 =6//xÿxxO ºÛp’RVV6//xÂ‡ÙÆ7¥EÀHü­@ö)ÀÀ CD4‡Sò“QÀ À6//xc‚Sc56À3x:<;iÿÿÿÀÿÀÀÿxxO ºÁ–RVVÿÿÿÿÿÿ6//xEH€9–ÿÿÿÿDC4íJò“Q6//xc‚Sc56À2À 7 =6//xÿxxO º„ÌSVV6//xÂ‡ÙÆ7¥EÀHü®@ö(ÀÀ CD4‡Sò“QÀ À6//xc‚Sc56À3x:<;iÿÿÿÀÿÀÀÿxlkÑED–™Counters provided by dumpcapkÑÐ&™kÑ¡C–™lmozim-0.2.4/examples/mozim_async.rs000064400000000000000000000016151046102023000155010ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use futures::stream::StreamExt; use mozim::{DhcpV4ClientAsync, DhcpV4Config}; const TEST_NIC: &str = "dhcpcli"; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box> { enable_log(); let mut config = DhcpV4Config::new(TEST_NIC); config.set_host_name("mozim-test"); config.use_host_name_as_client_id(); config.set_timeout(60); let mut cli = DhcpV4ClientAsync::init(config, None).unwrap(); while let Some(Ok(lease)) = cli.next().await { // You need to code to apply the IP address in lease to this NIC, so // follow up renew can work. println!("Got lease {lease:?}"); } Ok(()) } fn enable_log() { env_logger::Builder::new() .filter(Some("nispor"), log::LevelFilter::Debug) .filter(Some("mozim"), log::LevelFilter::Debug) .init(); } mozim-0.2.4/src/bpf.rs000064400000000000000000000062531046102023000126740ustar 00000000000000use crate::{DhcpError, ErrorKind}; const DHCP_BPF_LEN: u16 = 11; // libc are setting these constant as u32 which make our life worse // as libc::sock_filter code is u16. const BPF_B: u16 = 0x10; const BPF_H: u16 = 0x08; const BPF_ABS: u16 = 0x20; const BPF_IND: u16 = 0x40; const BPF_MSH: u16 = 0xa0; const BPF_JEQ: u16 = 0x10; const BPF_JSET: u16 = 0x40; const BPF_K: u16 = 0x00; const BPF_LD: u16 = 0x00; const BPF_LDX: u16 = 0x01; const BPF_JMP: u16 = 0x05; const BPF_RET: u16 = 0x06; const ETHERTYPE_IP: u32 = 0x0800; const IPPROTO_UDP: u32 = 17; const DHCPV4_DST_PORT: u32 = 68; const ETHER_TYPE_POS: u32 = 12; const IP_PROTO_POS: u32 = 23; const IP_FRAGMENT_POS: u32 = 20; const IP_HEADER_LEN_POS: u32 = 14; const ETHER_HEADER_LEN: u32 = 14; const DST_PORT_IN_IP_POS: u32 = 2; const BPF_FILTER_RAW: [(u16, u8, u8, u32); DHCP_BPF_LEN as usize] = [ // Load protocol type to A (BPF_LD | BPF_H | BPF_ABS, 0, 0, ETHER_TYPE_POS), // Move on if ETHERTYPE_IP, otherwise drop package (BPF_JMP | BPF_JEQ | BPF_K, 0, 8, ETHERTYPE_IP), // Load IPv4 protocol type to A (BPF_LD | BPF_B | BPF_ABS, 0, 0, IP_PROTO_POS), // Move on if UDP, otherwise drop package (BPF_JMP | BPF_JEQ | BPF_K, 0, 6, IPPROTO_UDP), // Load IPv4 flag and fragment offset (BPF_LD | BPF_H | BPF_ABS, 0, 0, IP_FRAGMENT_POS), // Drop package which has MF(more fragment) set is 1 or is fragment (BPF_JMP | BPF_JSET | BPF_K, 4, 0, 0x1fff), // Store IP header length to X (BPF_LDX | BPF_B | BPF_MSH, 0, 0, IP_HEADER_LEN_POS), // Load UDP destination port number to A ( BPF_LD | BPF_H | BPF_IND, 0, 0, ETHER_HEADER_LEN + DST_PORT_IN_IP_POS, ), // Check whether destination port is DHCPV4_DST_PORT (BPF_JMP | BPF_JEQ | BPF_K, 0, 1, DHCPV4_DST_PORT), // Accept this package (BPF_RET, 0, 0, u32::MAX), // Drop this package (BPF_RET, 0, 0, 0x00000000), ]; pub(crate) fn apply_dhcp_bpf(fd: libc::c_int) -> Result<(), DhcpError> { let mut raw_filters = [libc::sock_filter { code: 0, jt: 0, jf: 0, k: 0, }; DHCP_BPF_LEN as usize]; for (i, (code, jt, jf, k)) in BPF_FILTER_RAW.iter().enumerate() { raw_filters[i].code = *code; raw_filters[i].jt = *jt; raw_filters[i].jf = *jf; raw_filters[i].k = *k; log::debug!( "Registering BPF filter {:#04x}, {}, {}, {:#010x}", code, jt, jf, k ); } let bpf_filter = libc::sock_fprog { len: DHCP_BPF_LEN, filter: raw_filters.as_ptr() as *mut _, }; let rc = unsafe { libc::setsockopt( fd, libc::SOL_SOCKET, libc::SO_ATTACH_FILTER, (&bpf_filter as *const _) as *const libc::c_void, std::mem::size_of::() as libc::socklen_t, ) }; if rc != 0 { let e = DhcpError::new( ErrorKind::Bug, format!( "Failed to apply socket BPF filter, error: {:?}", nix::errno::Errno::last() ), ); log::error!("{}", e); Err(e) } else { Ok(()) } } mozim-0.2.4/src/client.rs000064400000000000000000000434711046102023000134060ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use std::os::unix::io::{AsRawFd, RawFd}; use rand::Rng; use crate::{ event::{DhcpEventPool, DhcpV4Event}, socket::{DhcpRawSocket, DhcpSocket, DhcpUdpSocket}, time::{gen_dhcp_request_delay, gen_renew_rebind_times}, DhcpError, DhcpV4Config, DhcpV4Lease, DhcpV4Message, DhcpV4MessageType, ErrorKind, }; // RFC 2131 suggests four times(60 seconds) retry before fallback to // discovery phase const MAX_REQUEST_RETRY_COUNT: u32 = 4; const NOT_RETRY: bool = false; const IS_RETRY: bool = true; #[derive(Debug, PartialEq, Clone, Copy)] enum DhcpV4Phase { Done, Discovery, Request, Renew, Rebind, } impl Default for DhcpV4Phase { fn default() -> Self { Self::Discovery } } #[derive(Debug)] pub struct DhcpV4Client { config: DhcpV4Config, event_pool: DhcpEventPool, lease: Option, phase: DhcpV4Phase, raw_socket: Option, retry_count: u32, udp_socket: Option, xid: u32, } impl AsRawFd for DhcpV4Client { fn as_raw_fd(&self) -> RawFd { self.event_pool.epoll.as_raw_fd() } } impl DhcpV4Client { pub fn init( mut config: DhcpV4Config, lease: Option, ) -> Result { config.init()?; let mut event_pool = DhcpEventPool::new()?; event_pool.add_timer(config.timeout, DhcpV4Event::Timeout)?; let raw_socket = DhcpRawSocket::new(&config)?; event_pool .add_socket(raw_socket.as_raw_fd(), DhcpV4Event::RawPackageIn)?; let xid: u32 = rand::thread_rng().gen(); let (dhcp_msg, phase) = if let Some(lease) = &lease { event_pool.add_timer( gen_dhcp_request_delay(0), DhcpV4Event::RequestTimeout, )?; let mut dhcp_msg = DhcpV4Message::new(&config, DhcpV4MessageType::Request, xid); dhcp_msg.load_lease(lease.clone()); (dhcp_msg, DhcpV4Phase::Request) } else { event_pool.add_timer( gen_dhcp_request_delay(0), DhcpV4Event::DiscoveryTimeout, )?; ( DhcpV4Message::new(&config, DhcpV4MessageType::Discovery, xid), DhcpV4Phase::Discovery, ) }; raw_socket.send(&dhcp_msg.to_eth_pkg_broadcast()?)?; Ok(Self { config, event_pool, lease, phase, xid, raw_socket: Some(raw_socket), retry_count: 0, udp_socket: None, }) } fn clean_up(&mut self) { self.lease = None; self.retry_count = 0; self.phase = DhcpV4Phase::Done; self.event_pool.remove_all_event(); self.raw_socket = None; self.udp_socket = None; } pub fn poll(&self, wait_time: u32) -> Result, DhcpError> { self.event_pool.poll(wait_time) } fn gen_discovery_pkg(&self) -> DhcpV4Message { DhcpV4Message::new(&self.config, DhcpV4MessageType::Discovery, self.xid) } fn gen_request_pkg(&self, lease: &DhcpV4Lease) -> DhcpV4Message { let mut dhcp_msg = DhcpV4Message::new( &self.config, DhcpV4MessageType::Request, self.xid, ); dhcp_msg.load_lease(lease.clone()); dhcp_msg } fn process_discovery(&mut self) -> Result, DhcpError> { let socket = if let Some(s) = self.raw_socket.as_ref() { s } else { self.clean_up(); let e = DhcpError::new( ErrorKind::Bug, "process_discovery(): No Raw socket".to_string(), ); log::error!("{}", e); return Err(e); }; let lease = match recv_dhcp_msg(socket, DhcpV4MessageType::Offer, self.xid) { Ok(Some(l)) => l, Ok(None) => return Ok(None), Err(e) => { log::info!("Ignoring invalid DHCP package: {e}"); return Ok(None); } }; self.phase = DhcpV4Phase::Request; socket.send(&self.gen_request_pkg(&lease).to_eth_pkg_broadcast()?)?; Ok(None) } fn set_renew_rebind_timer( &mut self, lease: &DhcpV4Lease, ) -> Result<(), DhcpError> { let t = gen_renew_rebind_times(lease.t1, lease.t2, lease.lease_time); self.event_pool.add_timer(t[0], DhcpV4Event::Renew)?; self.event_pool.add_timer(t[1], DhcpV4Event::RenewRetry)?; self.event_pool.add_timer(t[2], DhcpV4Event::Rebind)?; self.event_pool.add_timer(t[3], DhcpV4Event::RebindRetry)?; self.event_pool .add_timer(lease.lease_time, DhcpV4Event::LeaseExpired)?; Ok(()) } fn process_request(&mut self) -> Result, DhcpError> { let socket = if let Some(s) = self.raw_socket.as_ref() { s } else { self.clean_up(); let e = DhcpError::new( ErrorKind::Bug, "process_request(): No Raw socket".to_string(), ); log::error!("{}", e); return Err(e); }; let lease = match recv_dhcp_msg(socket, DhcpV4MessageType::Ack, self.xid) { Ok(Some(l)) => l, Ok(None) => return Ok(None), Err(e) => { log::info!("Ignoring invalid DHCP package: {e}"); return Ok(None); } }; self.clean_up(); self.lease = Some(lease.clone()); self.set_renew_rebind_timer(&lease)?; Ok(Some(lease)) } // RFC 2131 suggests four times(60 seconds) retry before fallback to // discovery phase fn process_request_timeout( &mut self, ) -> Result, DhcpError> { self.event_pool.del_timer(DhcpV4Event::RequestTimeout)?; if self.retry_count >= MAX_REQUEST_RETRY_COUNT { self.retry_count = 0; self.phase = DhcpV4Phase::Discovery; self.event_pool.add_timer( gen_dhcp_request_delay(self.retry_count), DhcpV4Event::DiscoveryTimeout, )?; if let Some(raw_socket) = &self.raw_socket { raw_socket .send(&self.gen_discovery_pkg().to_eth_pkg_broadcast()?)?; Ok(None) } else { self.clean_up(); let e = DhcpError::new(ErrorKind::Bug, "No RAW socket".to_string()); log::error!("{}", e); Err(e) } } else { self.retry_count += 1; self.event_pool.add_timer( gen_dhcp_request_delay(self.retry_count), DhcpV4Event::RequestTimeout, )?; if let Some(raw_socket) = &self.raw_socket { if let Some(lease) = &self.lease { raw_socket.send( &self.gen_request_pkg(lease).to_eth_pkg_broadcast()?, )?; Ok(None) } else { self.clean_up(); let e = DhcpError::new( ErrorKind::Bug, "No lease in request timeout process".to_string(), ); log::error!("{}", e); Err(e) } } else { let e = DhcpError::new(ErrorKind::Bug, "No RAW socket".to_string()); log::error!("{}", e); Err(e) } } } fn process_discovery_timeout( &mut self, ) -> Result, DhcpError> { self.event_pool.del_timer(DhcpV4Event::RequestTimeout)?; self.retry_count += 1; self.event_pool.add_timer( gen_dhcp_request_delay(self.retry_count), DhcpV4Event::DiscoveryTimeout, )?; if let Some(raw_socket) = &self.raw_socket { raw_socket .send(&self.gen_discovery_pkg().to_eth_pkg_broadcast()?)?; Ok(None) } else { self.clean_up(); let e = DhcpError::new(ErrorKind::Bug, "No RAW socket".to_string()); log::error!("{}", e); Err(e) } } fn process_timeout(&mut self) -> Result, DhcpError> { self.clean_up(); let e = DhcpError::new(ErrorKind::Timeout, "Timeout".to_string()); log::error!("{}", e); Err(e) } // Unicast to DHCP server requesting lease extension, with ciaddr field // and empty server identifier. fn process_renew( &mut self, is_retry: bool, ) -> Result, DhcpError> { if is_retry { self.event_pool.del_timer(DhcpV4Event::RenewRetry)?; } else { self.event_pool.del_timer(DhcpV4Event::Renew)?; } // The renew require unicast to DHCP server which hard(need // ARP) to do in raw socket for proxy mode. // TODO: For now, we just skip renew stage and let the lease // been refreshed in rebind stage. if self.config.is_proxy { log::debug!("Proxy mode has no renew support yet, ignoring"); return Ok(None); } let lease = if let Some(l) = self.lease.as_ref() { l } else { self.clean_up(); let e = DhcpError::new( ErrorKind::Bug, "process_renew(): No lease".to_string(), ); log::error!("{}", e); return Err(e); }; let udp_socket = DhcpUdpSocket::new( self.config.iface_name.as_str(), &lease.yiaddr, &lease.siaddr, self.config.socket_timeout, )?; let mut dhcp_msg = DhcpV4Message::new( &self.config, DhcpV4MessageType::Request, self.xid, ); dhcp_msg.load_lease(lease.clone()); dhcp_msg.renew_or_rebind(true); udp_socket.send(&dhcp_msg.to_dhcp_pkg()?)?; self.event_pool .add_socket(udp_socket.as_raw_fd(), DhcpV4Event::UdpPackageIn)?; self.udp_socket = Some(udp_socket); self.phase = DhcpV4Phase::Renew; self.retry_count = u32::from(is_retry); Ok(None) } fn process_renew_recv(&mut self) -> Result, DhcpError> { let socket = if let Some(s) = self.udp_socket.as_ref() { s } else { self.clean_up(); let e = DhcpError::new( ErrorKind::Bug, "process_renew_recv(): No UDP socket".to_string(), ); log::error!("{}", e); return Err(e); }; match recv_dhcp_msg(socket, DhcpV4MessageType::Ack, self.xid) { Ok(Some(lease)) => { self.clean_up(); self.lease = Some(lease.clone()); self.set_renew_rebind_timer(&lease)?; Ok(Some(lease)) } Ok(None) => Ok(None), Err(e) => { if self.retry_count == 0 { log::warn!("DHCP renew failed: {}, will try", e); } else { log::warn!("DHCP renew failed twice: {}, will rebind", e); } Ok(None) } } } // Broadcast to all DHCP servers requesting lease extension with ciaddr. fn process_rebind( &mut self, is_retry: bool, ) -> Result, DhcpError> { if is_retry { self.event_pool.del_timer(DhcpV4Event::RebindRetry)?; } else { self.event_pool.del_timer(DhcpV4Event::Rebind)?; } let lease = if let Some(l) = self.lease.as_ref() { l } else { self.clean_up(); let e = DhcpError::new( ErrorKind::Bug, "process_rebind(): no lease".to_string(), ); log::error!("{}", e); return Err(e); }; let raw_socket = DhcpRawSocket::new(&self.config)?; let mut dhcp_msg = DhcpV4Message::new( &self.config, DhcpV4MessageType::Request, self.xid, ); dhcp_msg.load_lease(lease.clone()); dhcp_msg.renew_or_rebind(true); raw_socket.send(&dhcp_msg.to_eth_pkg_broadcast()?)?; self.event_pool .add_socket(raw_socket.as_raw_fd(), DhcpV4Event::RawPackageIn)?; self.raw_socket = Some(raw_socket); self.phase = DhcpV4Phase::Rebind; self.retry_count = u32::from(is_retry); Ok(None) } fn process_rebind_recv( &mut self, ) -> Result, DhcpError> { let socket = if let Some(s) = self.raw_socket.as_ref() { s } else { self.clean_up(); let e = DhcpError::new( ErrorKind::Bug, "process_rebind_recv(): No RAW socket".to_string(), ); log::error!("{}", e); return Err(e); }; match recv_dhcp_msg(socket, DhcpV4MessageType::Ack, self.xid) { Ok(Some(lease)) => { self.clean_up(); self.lease = Some(lease.clone()); self.set_renew_rebind_timer(&lease)?; Ok(Some(lease)) } Ok(None) => Ok(None), Err(e) => { if self.retry_count == 0 { log::warn!("DHCP rebind failed: {}, will try", e); } else { log::warn!( "DHCP rebind failed twice: {}, will request new lease", e ); } Ok(None) } } } // Instead raise error to user, we should try the whole DHCP discovery // again with specific timeout fn process_lease_expired( &mut self, ) -> Result, DhcpError> { self.clean_up(); self.event_pool .add_timer(self.config.timeout, DhcpV4Event::Timeout)?; let raw_socket = DhcpRawSocket::new(&self.config)?; self.event_pool .add_socket(raw_socket.as_raw_fd(), DhcpV4Event::RawPackageIn)?; self.event_pool.add_timer( gen_dhcp_request_delay(0), DhcpV4Event::DiscoveryTimeout, )?; let dhcp_msg = DhcpV4Message::new( &self.config, DhcpV4MessageType::Discovery, self.xid, ); raw_socket.send(&dhcp_msg.to_eth_pkg_broadcast()?)?; self.raw_socket = Some(raw_socket); self.phase = DhcpV4Phase::Discovery; Ok(None) } pub fn process( &mut self, event: DhcpV4Event, ) -> Result, DhcpError> { log::debug!("Processing event {:?}", event); match event { DhcpV4Event::RawPackageIn => match self.phase { DhcpV4Phase::Discovery => self.process_discovery(), DhcpV4Phase::Request => self.process_request(), DhcpV4Phase::Rebind => self.process_rebind_recv(), _ => todo!(), }, DhcpV4Event::UdpPackageIn => match self.phase { DhcpV4Phase::Renew => self.process_renew_recv(), _ => todo!(), }, DhcpV4Event::RequestTimeout => self.process_request_timeout(), DhcpV4Event::DiscoveryTimeout => self.process_discovery_timeout(), DhcpV4Event::Timeout => self.process_timeout(), DhcpV4Event::Renew => self.process_renew(NOT_RETRY), DhcpV4Event::RenewRetry => self.process_renew(IS_RETRY), DhcpV4Event::Rebind => self.process_rebind(NOT_RETRY), DhcpV4Event::RebindRetry => self.process_rebind(IS_RETRY), DhcpV4Event::LeaseExpired => self.process_lease_expired(), } } pub fn release(&mut self, lease: &DhcpV4Lease) -> Result<(), DhcpError> { let mut dhcp_msg = DhcpV4Message::new( &self.config, DhcpV4MessageType::Release, self.xid, ); dhcp_msg.load_lease(lease.clone()); if self.config.is_proxy { let raw_socket = DhcpRawSocket::new(&self.config)?; raw_socket.send(&dhcp_msg.to_proxy_eth_pkg_unicast()?)?; } else { let udp_socket = DhcpUdpSocket::new( self.config.iface_name.as_str(), &lease.yiaddr, &lease.siaddr, self.config.socket_timeout, )?; udp_socket.send(&dhcp_msg.to_dhcp_pkg()?)?; } self.clean_up(); Ok(()) } } fn recv_dhcp_msg( socket: &impl DhcpSocket, expected: DhcpV4MessageType, xid: u32, ) -> Result, DhcpError> { let buffer: Vec = socket.recv()?; let reply_dhcp_msg = if socket.is_raw() { DhcpV4Message::from_eth_pkg(&buffer)? } else { DhcpV4Message::from_dhcp_pkg(&buffer)? }; if reply_dhcp_msg.xid != xid { log::debug!( "Dropping DHCP message due to xid miss-match. \ Expecting {}, got {}", xid, reply_dhcp_msg.xid ); return Ok(None); } if reply_dhcp_msg.msg_type != expected { log::debug!( "Dropping DHCP message due to type miss-match. Expecting {}, got {}", expected, reply_dhcp_msg.msg_type ); return Ok(None); } if let Some(lease) = reply_dhcp_msg.lease { Ok(Some(lease)) } else { log::debug!( "No lease found in the reply from DHCP server {:?}", reply_dhcp_msg ); Ok(None) } } mozim-0.2.4/src/client_async.rs000064400000000000000000000114521046102023000145750ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use std::os::fd::BorrowedFd; use std::os::unix::io::{AsRawFd, RawFd}; use std::pin::Pin; use std::sync::{Arc, Mutex}; use futures::{ task::{Context, Poll, Waker}, Stream, }; use nix::poll::{PollFd, PollFlags}; use crate::{DhcpError, DhcpV4Client, DhcpV4Config, DhcpV4Lease, ErrorKind}; const POLL_TIMEOUT: libc::c_int = 1000; // milliseconds #[derive(Debug)] struct ShareState { waker: Option, } #[derive(Debug)] pub struct DhcpV4ClientAsync { client: DhcpV4Client, share_state: Arc>, } impl Stream for DhcpV4ClientAsync { type Item = Result; fn poll_next( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { // Poll without wait match self.client.poll(0) { Ok(events) => { for event in events { match self.client.process(event) { Ok(Some(lease)) => { return Poll::Ready(Some(Ok(lease))); } Ok(None) => (), Err(e) => { return Poll::Ready(Some(Err(e))); } } } } Err(e) => { log::error!("DHCP client poll error: {e}"); return Poll::Ready(Some(Err(e))); } } let mut share_state = match self.share_state.lock() { Ok(s) => s, Err(e) => { return Poll::Ready(Some(Err(DhcpError::new( ErrorKind::Bug, format!( "BUG: DhcpV4ClientAsync::poll_next() \ Failed to acquire lock on share_state {e}", ), )))); } }; if share_state.waker.is_none() { share_state.waker = Some(cx.waker().clone()); drop(share_state); let fd = self.client.as_raw_fd(); let share_state = self.share_state.clone(); std::thread::spawn(move || poll_thread(fd, share_state)); } else { share_state.waker = Some(cx.waker().clone()); drop(share_state); } Poll::Pending } } impl DhcpV4ClientAsync { pub fn init( config: DhcpV4Config, lease: Option, ) -> Result { Ok(Self { client: DhcpV4Client::init(config, lease)?, share_state: Arc::new(Mutex::new(ShareState { waker: None })), }) } } impl std::ops::Drop for DhcpV4ClientAsync { fn drop(&mut self) { if let Ok(mut s) = self.share_state.lock() { // Signal `poll_thread()` to quit s.waker = None; } } } // This function will be invoked in a thread to notify the async executor // via `Waker::wake()`. Will quit when `poll()` failed (except EAGAIN). fn poll_thread(fd: RawFd, share_state: Arc>) { let fd = unsafe { BorrowedFd::borrow_raw(fd) }; let mut poll_fds = [PollFd::new( &fd, PollFlags::POLLIN | PollFlags::POLLOUT | PollFlags::POLLHUP | PollFlags::POLLERR, )]; loop { if share_state.lock().map(|s| s.waker.is_none()).ok() == Some(true) { std::thread::sleep(std::time::Duration::from_millis( POLL_TIMEOUT as u64, )); } else { match nix::poll::poll(&mut poll_fds, POLL_TIMEOUT) { // Timeout, let's check whether waker is None(DHCP client quit); Ok(0) => { continue; } Ok(_) => match share_state.lock() { Ok(mut s) => { if let Some(waker) = s.waker.take() { log::debug!("poll_thread got event"); waker.wake(); } else { log::debug!( "poll_thread got event but Waker is None" ); } } Err(e) => { log::error!( "BUG: poll_thread() Failed to acquire lock: {e}" ); return; } }, Err(e) => { if e == nix::errno::Errno::EAGAIN { continue; } else { log::error!( "BUG: poll_thread() got error from poll(): {e}" ); return; } } } } } } mozim-0.2.4/src/config.rs000064400000000000000000000107061046102023000133700ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use crate::{mac::mac_str_to_u8_array, DhcpError, ErrorKind}; use nispor::{NetState, NetStateFilter, NetStateIfaceFilter}; // https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml#arp-parameters-2 const ARP_HW_TYPE_ETHERNET: u8 = 1; const DEFAULT_TIMEOUT: u32 = 120; const DEFAULT_SOCKET_TIMEOUT: u32 = 5; #[derive(Debug, PartialEq, Eq, Clone)] pub struct DhcpV4Config { pub(crate) iface_name: String, pub(crate) iface_index: u32, pub(crate) src_mac: String, pub(crate) client_id: Vec, pub(crate) host_name: String, // TODO: Support allow list and deny list for DHCP servers. pub(crate) use_host_name_as_client_id: bool, pub(crate) timeout: u32, pub(crate) socket_timeout: u32, pub(crate) is_proxy: bool, } impl Default for DhcpV4Config { fn default() -> Self { Self { iface_name: String::new(), iface_index: 0, src_mac: String::new(), client_id: Vec::new(), host_name: String::new(), use_host_name_as_client_id: false, timeout: DEFAULT_TIMEOUT, socket_timeout: DEFAULT_SOCKET_TIMEOUT, is_proxy: false, } } } impl DhcpV4Config { pub fn new(iface_name: &str) -> Self { Self { iface_name: iface_name.to_string(), ..Default::default() } } // Check whether interface exists and resolve iface_index and MAC pub(crate) fn init(&mut self) -> Result<(), DhcpError> { // We use thread to invoke nispor which has `tokio::block_on` which // stop our async usage let iface_name = self.iface_name.clone(); let np_iface = match std::thread::spawn(move || { get_nispor_iface(iface_name.as_str()) }) .join() { Ok(n) => n?, Err(e) => { return Err(DhcpError::new( ErrorKind::Bug, format!("Failed to invoke nispor thread: {e:?}"), )); } }; self.iface_index = np_iface.index; if !self.is_proxy { self.src_mac = np_iface.mac_address; } Ok(()) } pub fn new_proxy(out_iface_name: &str, proxy_mac: &str) -> Self { Self { iface_name: out_iface_name.to_string(), src_mac: proxy_mac.to_string(), is_proxy: true, ..Default::default() } } // Set timeout in seconds pub fn set_timeout(&mut self, timeout: u32) -> &mut Self { self.timeout = timeout; self } pub fn set_host_name(&mut self, host_name: &str) -> &mut Self { self.host_name = host_name.to_string(); self } pub fn use_mac_as_client_id(&mut self) -> &mut Self { self.client_id = vec![ARP_HW_TYPE_ETHERNET]; self.use_host_name_as_client_id = false; self.client_id .append(&mut mac_str_to_u8_array(&self.src_mac)); self } pub fn use_host_name_as_client_id(&mut self) -> &mut Self { // RFC 2132: 9.14. Client-identifier // Type 0 is used when not using hardware address self.client_id = vec![0]; // The RFC never mentioned the NULL terminator for string. // TODO: Need to check with dnsmasq implementation self.client_id.extend_from_slice(self.host_name.as_bytes()); self.use_host_name_as_client_id = true; self } } fn get_nispor_iface(iface_name: &str) -> Result { if iface_name.is_empty() { let e = DhcpError::new( ErrorKind::InvalidArgument, "Interface name not defined".to_string(), ); log::error!("{}", e); return Err(e); } let mut filter = NetStateFilter::minimum(); let mut iface_filter = NetStateIfaceFilter::minimum(); iface_filter.iface_name = Some(iface_name.to_string()); filter.iface = Some(iface_filter); let net_state = match NetState::retrieve_with_filter(&filter) { Ok(s) => s, Err(e) => { return Err(DhcpError::new( ErrorKind::Bug, format!("Faild to retrieve network state: {e}"), )) } }; if let Some(iface) = net_state.ifaces.get(iface_name) { Ok(iface.clone()) } else { Err(DhcpError::new( ErrorKind::InvalidArgument, format!("Interface {iface_name} not found"), )) } } mozim-0.2.4/src/error.rs000064400000000000000000000033211046102023000132470ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum ErrorKind { Timeout, InvalidArgument, InvalidDhcpServerReply, NoLease, Bug, LeaseExpired, } #[derive(Debug, PartialEq, Eq, Clone)] pub struct DhcpError { kind: ErrorKind, msg: String, } impl DhcpError { pub fn new(kind: ErrorKind, msg: String) -> Self { Self { kind, msg } } pub fn kind(&self) -> ErrorKind { self.kind } pub fn msg(&self) -> &str { self.msg.as_str() } } impl std::fmt::Display for ErrorKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{self:?}") } } impl std::fmt::Display for DhcpError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}: {}", self.kind, self.msg) } } impl From for DhcpError { fn from(e: std::io::Error) -> Self { Self::new(ErrorKind::Bug, format!("IO error: {e}")) } } impl From for DhcpError { fn from(e: std::ffi::NulError) -> Self { Self::new(ErrorKind::Bug, format!("CString error: {e}")) } } impl From for DhcpError { fn from(e: dhcproto::v4::EncodeError) -> Self { Self::new(ErrorKind::Bug, format!("DHCP protocol error: {e}")) } } impl From for DhcpError { fn from(e: etherparse::WriteError) -> Self { Self::new(ErrorKind::Bug, format!("etherparse protocol error: {e}")) } } impl From for DhcpError { fn from(e: std::net::AddrParseError) -> Self { Self::new(ErrorKind::Bug, format!("IPv4 address parse error: {e}")) } } mozim-0.2.4/src/event.rs000064400000000000000000000173531046102023000132510ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use std::collections::HashMap; use std::os::fd::BorrowedFd; use std::os::unix::io::{AsRawFd, RawFd}; use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags}; use crate::{time::DhcpTimerFd, DhcpError, ErrorKind}; const EVENT_BUFFER_COUNT: usize = 64; #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum DhcpV4Event { RawPackageIn = 1, UdpPackageIn, DiscoveryTimeout, RequestTimeout, Timeout, Renew, RenewRetry, Rebind, RebindRetry, LeaseExpired, } impl TryFrom for DhcpV4Event { type Error = DhcpError; fn try_from(v: u64) -> Result { match v { x if x == Self::RawPackageIn as u64 => Ok(Self::RawPackageIn), x if x == Self::UdpPackageIn as u64 => Ok(Self::UdpPackageIn), x if x == Self::DiscoveryTimeout as u64 => { Ok(Self::DiscoveryTimeout) } x if x == Self::RequestTimeout as u64 => Ok(Self::RequestTimeout), x if x == Self::Timeout as u64 => Ok(Self::Timeout), x if x == Self::Renew as u64 => Ok(Self::Renew), x if x == Self::RenewRetry as u64 => Ok(Self::RenewRetry), x if x == Self::Rebind as u64 => Ok(Self::Rebind), x if x == Self::RebindRetry as u64 => Ok(Self::RebindRetry), x if x == Self::LeaseExpired as u64 => Ok(Self::LeaseExpired), _ => { let e = DhcpError::new( ErrorKind::Bug, format!("Got unexpected event ID {v}"), ); log::error!("{}", e); Err(e) } } } } impl std::fmt::Display for DhcpV4Event { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { Self::RawPackageIn => "RawPackageIn", Self::UdpPackageIn => "UdpPackageIn", Self::DiscoveryTimeout => "DiscoveryTimeout", Self::RequestTimeout => "RequestTimeout", Self::Timeout => "Timeout", Self::Renew => "Renew", Self::RenewRetry => "RenewRetry", Self::Rebind => "Rebind", Self::RebindRetry => "RebindRetry", Self::LeaseExpired => "LeaseExpired", } ) } } #[derive(Debug)] pub(crate) struct DhcpEventPool { timer_fds: HashMap, socket_fds: HashMap, pub(crate) epoll: DhcpEpoll, } impl Drop for DhcpEventPool { fn drop(&mut self) { self.remove_all_event(); if self.epoll.as_raw_fd() >= 0 { unsafe { libc::close(self.epoll.as_raw_fd() as libc::c_int); } } } } impl DhcpEventPool { pub(crate) fn remove_all_event(&mut self) { for (_, timer_fd) in self.timer_fds.drain() { self.epoll.del_fd(timer_fd.as_raw_fd()).ok(); } for (_, fd) in self.socket_fds.drain() { self.epoll.del_fd(fd).ok(); } } pub(crate) fn new() -> Result { Ok(Self { timer_fds: HashMap::new(), socket_fds: HashMap::new(), epoll: DhcpEpoll::new()?, }) } pub(crate) fn add_socket( &mut self, fd: RawFd, event: DhcpV4Event, ) -> Result<(), DhcpError> { log::debug!("Adding socket {} with event {} to event pool", fd, event); self.socket_fds.insert(event, fd); self.epoll.add_fd(fd, event) } pub(crate) fn add_timer( &mut self, timeout: u32, event: DhcpV4Event, ) -> Result<(), DhcpError> { log::debug!( "Adding timer {} seconds with event {} to event pool", timeout, event ); let timer_fd = DhcpTimerFd::new(timeout)?; self.epoll.add_fd(timer_fd.as_raw_fd(), event)?; self.timer_fds.insert(event, timer_fd); Ok(()) } pub(crate) fn del_timer( &mut self, event: DhcpV4Event, ) -> Result<(), DhcpError> { if let Some(timer_fd) = self.timer_fds.remove(&event) { self.epoll.del_fd(timer_fd.as_raw_fd())?; } Ok(()) } pub(crate) fn poll( &self, wait_time: u32, ) -> Result, DhcpError> { match isize::try_from(wait_time) { Ok(i) => self.epoll.poll(i), Err(_) => Err(DhcpError::new( ErrorKind::InvalidArgument, format!( "Invalid timeout, should be in the range of \ 0 - {}", isize::MAX ), )), } } } #[derive(Debug)] pub(crate) struct DhcpEpoll { fd: Epoll, } impl AsRawFd for DhcpEpoll { fn as_raw_fd(&self) -> RawFd { self.fd.0.as_raw_fd() } } impl DhcpEpoll { fn new() -> Result { Ok(Self { fd: Epoll::new(EpollCreateFlags::empty()).map_err(|e| { let e = DhcpError::new( ErrorKind::Bug, format!("Failed to create Epoll: {e}"), ); log::error!("{e}"); e })?, }) } fn add_fd(&self, fd: RawFd, event: DhcpV4Event) -> Result<(), DhcpError> { let fd = unsafe { BorrowedFd::borrow_raw(fd) }; log::debug!( "Adding fd {} to Epoll {}, event {}", fd.as_raw_fd(), self.fd.0.as_raw_fd(), event ); let event = EpollEvent::new(EpollFlags::EPOLLIN, event as u64); self.fd.add(fd, event).map_err(|e| { let e = DhcpError::new( ErrorKind::Bug, format!( "Failed to add fd {} with event {} to epoll {}: {e}", fd.as_raw_fd(), event.data(), self.fd.0.as_raw_fd() ), ); log::error!("{}", e); e }) } fn del_fd(&self, fd: RawFd) -> Result<(), DhcpError> { let fd = unsafe { BorrowedFd::borrow_raw(fd) }; log::debug!( "Removing fd {} from Epoll {}", fd.as_raw_fd(), self.fd.0.as_raw_fd() ); self.fd.delete(fd).map_err(|e| { let e = DhcpError::new( ErrorKind::Bug, format!( "Failed to delete fd {} from epoll {}: {e}", fd.as_raw_fd(), self.fd.0.as_raw_fd(), ), ); log::error!("{}", e); e }) } fn poll(&self, wait_time: isize) -> Result, DhcpError> { let mut events: [EpollEvent; EVENT_BUFFER_COUNT] = [EpollEvent::empty(); EVENT_BUFFER_COUNT]; loop { match self.fd.wait(&mut events, 1000 * wait_time) { Ok(c) => { let mut ret = Vec::new(); for i in &events[..c] { ret.push(DhcpV4Event::try_from(i.data())?); } return Ok(ret); } Err(e) => match e { nix::errno::Errno::EINTR | nix::errno::Errno::EAGAIN => { // retry continue; } _ => { let e = DhcpError::new( ErrorKind::Bug, format!("Failed on epoll_wait(): {e}"), ); return Err(e); } }, } } } } mozim-0.2.4/src/integ_tests/dhcpv4.rs000064400000000000000000000021531046102023000156400ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use crate::{DhcpV4Client, DhcpV4Config, DhcpV4Lease}; use super::env::{DhcpServerEnv, FOO1_STATIC_IP, TEST_NIC_CLI}; const POLL_WAIT_TIME: u32 = 5; #[test] fn test_dhcpv4_manual_client_id() { let _srv = DhcpServerEnv::start(); let mut config = DhcpV4Config::new(TEST_NIC_CLI); config.set_host_name("foo1"); config.use_host_name_as_client_id(); let mut cli = DhcpV4Client::init(config, None).unwrap(); let lease = get_lease(&mut cli); assert!(lease.is_some()); if let Some(lease) = lease { assert_eq!(lease.host_name.as_ref(), Some(&"foo1".to_string())); assert_eq!(lease.yiaddr, FOO1_STATIC_IP,); } } fn get_lease(cli: &mut DhcpV4Client) -> Option { while let Ok(events) = cli.poll(POLL_WAIT_TIME) { for event in events { match cli.process(event) { Ok(Some(lease)) => { return Some(lease); } Ok(None) => (), Err(_) => { return None; } } } } None } mozim-0.2.4/src/integ_tests/dhcpv4_async.rs000064400000000000000000000016441046102023000170410ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use futures::StreamExt; use crate::{DhcpV4ClientAsync, DhcpV4Config, DhcpV4Lease}; use super::env::{DhcpServerEnv, FOO1_STATIC_IP, TEST_NIC_CLI}; #[test] fn test_dhcpv4_async() { let _srv = DhcpServerEnv::start(); let mut config = DhcpV4Config::new(TEST_NIC_CLI); config.set_host_name("foo1"); config.use_host_name_as_client_id(); let mut cli = DhcpV4ClientAsync::init(config, None).unwrap(); let rt = tokio::runtime::Builder::new_current_thread() .enable_io() .build() .unwrap(); let lease = rt.block_on(get_lease(&mut cli)); assert!(lease.is_some()); if let Some(lease) = lease { assert_eq!(lease.host_name.as_ref(), Some(&"foo1".to_string())); assert_eq!(lease.yiaddr, FOO1_STATIC_IP,); } } async fn get_lease(cli: &mut DhcpV4ClientAsync) -> Option { cli.next().await.unwrap().ok() } mozim-0.2.4/src/integ_tests/dhcpv4_proxy.rs000064400000000000000000000020341046102023000170770ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use crate::{DhcpV4Client, DhcpV4Config, DhcpV4Lease}; use super::env::{ DhcpServerEnv, TEST_NIC_CLI, TEST_PROXY_IP1, TEST_PROXY_MAC1, }; const POLL_WAIT_TIME: u32 = 5; #[test] fn test_dhcpv4_proxy() { let _srv = DhcpServerEnv::start(); let config = DhcpV4Config::new_proxy(TEST_NIC_CLI, TEST_PROXY_MAC1); let mut cli = DhcpV4Client::init(config, None).unwrap(); let lease = get_lease(&mut cli); assert!(lease.is_some()); if let Some(lease) = lease { assert_eq!(lease.yiaddr, TEST_PROXY_IP1); cli.release(&lease).unwrap(); } } fn get_lease(cli: &mut DhcpV4Client) -> Option { while let Ok(events) = cli.poll(POLL_WAIT_TIME) { for event in events { match cli.process(event) { Ok(Some(lease)) => { return Some(lease); } Ok(None) => (), Err(_) => { return None; } } } } None } mozim-0.2.4/src/integ_tests/env.rs000064400000000000000000000071341046102023000152440ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use std::process::{Child, Command}; const TEST_DHCPD_NETNS: &str = "mozim_test"; pub(crate) const TEST_NIC_CLI: &str = "dhcpcli"; pub(crate) const TEST_PROXY_MAC1: &str = "00:11:22:33:44:55"; const TEST_NIC_SRV: &str = "dhcpsrv"; const TEST_DHCP_SRV_IP: &str = "192.0.2.1"; pub(crate) const FOO1_STATIC_IP: std::net::Ipv4Addr = std::net::Ipv4Addr::new(192, 0, 2, 99); pub(crate) const TEST_PROXY_IP1: std::net::Ipv4Addr = std::net::Ipv4Addr::new(192, 0, 2, 51); const DNSMASQ_OPTS: &str = r#" --log-dhcp --keep-in-foreground --no-daemon --conf-file=/dev/null --dhcp-leasefile=/tmp/mozim_test_dhcpd_lease --no-hosts --dhcp-host=foo1,192.0.2.99 --dhcp-host=00:11:22:33:44:55,192.0.2.51 --dhcp-option=option:dns-server,8.8.8.8,1.1.1.1 --dhcp-option=option:mtu,1492 --dhcp-option=option:domain-name,example.com --dhcp-option=option:ntp-server,192.0.2.1 --keep-in-foreground --bind-interfaces --except-interface=lo --clear-on-reload --listen-address=192.0.2.1 --dhcp-range=192.0.2.2,192.0.2.50,60 --no-ping "#; #[derive(Debug)] pub(crate) struct DhcpServerEnv { daemon: Child, } impl DhcpServerEnv { pub(crate) fn start() -> Self { create_test_net_namespace(); create_test_veth_nics(); let daemon = start_dhcp_server(); Self { daemon } } } impl Drop for DhcpServerEnv { fn drop(&mut self) { stop_dhcp_server(&mut self.daemon); remove_test_veth_nics(); remove_test_net_namespace(); } } fn create_test_net_namespace() { run_cmd(&format!("ip netns add {TEST_DHCPD_NETNS}")); } fn remove_test_net_namespace() { run_cmd_ignore_failure(&format!("ip netns del {TEST_DHCPD_NETNS}")); } fn create_test_veth_nics() { run_cmd(&format!( "ip link add {TEST_NIC_CLI} type veth peer name {TEST_NIC_SRV}" )); run_cmd(&format!("ip link set {TEST_NIC_CLI} up")); run_cmd(&format!( "ip link set {TEST_NIC_SRV} netns {TEST_DHCPD_NETNS}" )); run_cmd(&format!( "ip netns exec {TEST_DHCPD_NETNS} ip link set {TEST_NIC_SRV} up", )); run_cmd(&format!( "ip netns exec {TEST_DHCPD_NETNS} ip addr add {TEST_DHCP_SRV_IP}/24 dev {TEST_NIC_SRV}", )); } fn remove_test_veth_nics() { run_cmd_ignore_failure(&format!("ip link del {TEST_NIC_CLI}")); } fn start_dhcp_server() -> Child { let cmd = format!( "ip netns exec {} dnsmasq {}", TEST_DHCPD_NETNS, DNSMASQ_OPTS.replace('\n', " ") ); let cmds: Vec<&str> = cmd.split(' ').collect(); let mut child = Command::new(cmds[0]) .args(&cmds[1..]) .spawn() .expect("Failed to start DHCP server"); std::thread::sleep(std::time::Duration::from_secs(1)); if let Ok(Some(ret)) = child.try_wait() { panic!("Failed to start DHCP server {ret:?}"); } child } fn stop_dhcp_server(daemon: &mut Child) { daemon.kill().expect("Failed to stop DHCP server") } fn run_cmd(cmd: &str) -> String { let cmds: Vec<&str> = cmd.split(' ').collect(); String::from_utf8( Command::new(cmds[0]) .args(&cmds[1..]) .output() .unwrap_or_else(|_| panic!("failed to execute command {cmd}")) .stdout, ) .expect("Failed to convert file command output to String") } fn run_cmd_ignore_failure(cmd: &str) -> String { let cmds: Vec<&str> = cmd.split(' ').collect(); match Command::new(cmds[0]).args(&cmds[1..]).output() { Ok(o) => String::from_utf8(o.stdout).unwrap_or_default(), Err(e) => { eprintln!("Failed to execute command {cmd}: {e}"); "".to_string() } } } mozim-0.2.4/src/integ_tests/mod.rs000064400000000000000000000002111046102023000152200ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 #[cfg(test)] mod dhcpv4; #[cfg(test)] mod dhcpv4_async; #[cfg(test)] mod dhcpv4_proxy; mod env; mozim-0.2.4/src/lease.rs000064400000000000000000000064711046102023000132200ustar 00000000000000use std::net::Ipv4Addr; use dhcproto::{v4, v4::DhcpOption}; use crate::DhcpError; #[derive(Debug, PartialEq, Eq, Clone)] pub struct DhcpV4Lease { // Required for sending DHCPRELEASE in proxy mode pub(crate) srv_mac: [u8; 6], pub siaddr: Ipv4Addr, pub yiaddr: Ipv4Addr, pub t1: u32, pub t2: u32, pub lease_time: u32, pub srv_id: Ipv4Addr, pub subnet_mask: Ipv4Addr, pub broadcast_addr: Option, pub dns_srvs: Option>, pub gateways: Option>, pub ntp_srvs: Option>, pub mtu: Option, pub host_name: Option, pub domain_name: Option, // TODO: We should save the unsupported DHCP options for external parser. //pub other_dhcp_opts: Vec, } impl Default for DhcpV4Lease { fn default() -> Self { Self { srv_mac: [u8::MAX; 6], siaddr: Ipv4Addr::new(0, 0, 0, 0), yiaddr: Ipv4Addr::new(0, 0, 0, 0), t1: 0, t2: 0, lease_time: 0, srv_id: Ipv4Addr::new(0, 0, 0, 0), subnet_mask: Ipv4Addr::new(0, 0, 0, 0), broadcast_addr: None, dns_srvs: None, gateways: None, ntp_srvs: None, mtu: None, host_name: None, domain_name: None, } } } impl std::convert::TryFrom<&v4::Message> for DhcpV4Lease { type Error = DhcpError; fn try_from(v4_dhcp_msg: &v4::Message) -> Result { let mut ret = Self { siaddr: v4_dhcp_msg.siaddr(), yiaddr: v4_dhcp_msg.yiaddr(), ..Default::default() }; for (_, dhcp_opt) in v4_dhcp_msg.opts().iter() { match dhcp_opt { DhcpOption::MessageType(_) => (), DhcpOption::Renewal(v) => { ret.t1 = *v; } DhcpOption::Rebinding(v) => { ret.t2 = *v; } DhcpOption::InterfaceMtu(v) => { ret.mtu = Some(*v); } DhcpOption::ServerIdentifier(v) => { ret.srv_id = *v; } DhcpOption::AddressLeaseTime(v) => { ret.lease_time = *v; } DhcpOption::SubnetMask(v) => { ret.subnet_mask = *v; } DhcpOption::BroadcastAddr(v) => { ret.broadcast_addr = Some(*v); } DhcpOption::DomainNameServer(v) => { ret.dns_srvs = Some(v.clone()); } DhcpOption::Router(v) => { ret.gateways = Some(v.clone()); } DhcpOption::NTPServers(v) => { ret.ntp_srvs = Some(v.clone()); } DhcpOption::Hostname(v) => { ret.host_name = Some(v.to_string()); } DhcpOption::DomainName(v) => { ret.domain_name = Some(v.to_string()); } v => { log::debug!("Unsupported DHCP opt {:?}", v); } } } // TODO: Validate T1 < T2 < lease_time. Ok(ret) } } mozim-0.2.4/src/lib.rs000064400000000000000000000007721046102023000126730ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 mod bpf; mod client; mod client_async; mod config; mod error; mod event; mod lease; mod mac; mod msg; mod proiscuous; mod socket; mod time; #[cfg(test)] mod integ_tests; pub use crate::client::DhcpV4Client; pub use crate::client_async::DhcpV4ClientAsync; pub use crate::config::DhcpV4Config; pub use crate::error::{DhcpError, ErrorKind}; pub use crate::event::DhcpV4Event; pub use crate::lease::DhcpV4Lease; pub use crate::msg::{DhcpV4Message, DhcpV4MessageType}; mozim-0.2.4/src/mac.rs000064400000000000000000000022621046102023000126610ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use log::error; use crate::{DhcpError, ErrorKind}; pub(crate) const BROADCAST_MAC_ADDRESS: [u8; 6] = [u8::MAX; 6]; pub(crate) fn mac_str_to_u8_array(mac: &str) -> Vec { let mut mac_bytes = Vec::new(); for item in mac.split(':') { match u8::from_str_radix(item, 16) { Ok(i) => mac_bytes.push(i), Err(e) => { error!( "Failed to convert to MAC address to bytes {:?}: {}", mac, e ); return Vec::new(); } } } mac_bytes } pub(crate) fn mac_address_to_eth_mac_bytes( mac_address: &str, ) -> Result<[u8; libc::ETH_ALEN as usize], DhcpError> { let mut ret = [0u8; libc::ETH_ALEN as usize]; let mac_bytes = mac_str_to_u8_array(mac_address); if mac_bytes.len() > libc::ETH_ALEN as usize { Err(DhcpError::new( ErrorKind::Bug, format!( "MAC address {} exceeded the max length {}", mac_address, libc::ETH_ALEN ), )) } else { ret.clone_from_slice(&mac_bytes); Ok(ret) } } mozim-0.2.4/src/msg.rs000064400000000000000000000246251046102023000127160ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use std::net::Ipv4Addr; use dhcproto::{v4, Decodable, Decoder, Encodable}; use crate::{ mac::{ mac_address_to_eth_mac_bytes, mac_str_to_u8_array, BROADCAST_MAC_ADDRESS, }, DhcpError, DhcpV4Config, DhcpV4Lease, ErrorKind, }; const DEFAULT_TTL: u8 = 128; #[derive(Debug, PartialEq, Eq, Clone)] pub enum DhcpV4MessageType { Discovery, Offer, Request, Ack, Nack, Decline, Release, Inform, Unknown, } impl Default for DhcpV4MessageType { fn default() -> Self { Self::Unknown } } impl std::fmt::Display for DhcpV4MessageType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{:?}", match self { Self::Discovery => "discovery", Self::Offer => "offer", Self::Request => "request", Self::Ack => "ack", Self::Nack => "nack", Self::Decline => "decline", Self::Release => "release", Self::Inform => "inform", Self::Unknown => "unknown", } ) } } #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct DhcpV4Message { pub msg_type: DhcpV4MessageType, pub lease: Option, pub config: DhcpV4Config, renew_or_rebind: bool, pub(crate) xid: u32, } impl DhcpV4Message { pub fn new( config: &DhcpV4Config, msg_type: DhcpV4MessageType, xid: u32, ) -> Self { Self { msg_type, config: config.clone(), lease: None, renew_or_rebind: false, xid, } } pub fn load_lease(&mut self, lease: DhcpV4Lease) -> &mut Self { self.lease = Some(lease); self } pub(crate) fn renew_or_rebind(&mut self, value: bool) -> &mut Self { self.renew_or_rebind = value; self } pub(crate) fn to_dhcp_pkg(&self) -> Result, DhcpError> { let mut dhcp_msg = v4::Message::default(); dhcp_msg.set_flags(v4::Flags::default()); dhcp_msg.set_xid(self.xid); if !self.config.host_name.is_empty() { dhcp_msg.set_sname_str(self.config.host_name.clone()); } if !self.config.src_mac.is_empty() { dhcp_msg .set_chaddr(&mac_str_to_u8_array(self.config.src_mac.as_str())); } if self.msg_type == DhcpV4MessageType::Discovery { dhcp_msg .opts_mut() .insert(v4::DhcpOption::MessageType(v4::MessageType::Discover)); dhcp_msg .opts_mut() .insert(v4::DhcpOption::ParameterRequestList(vec![ v4::OptionCode::Hostname, v4::OptionCode::SubnetMask, v4::OptionCode::Router, v4::OptionCode::DomainNameServer, v4::OptionCode::DomainName, v4::OptionCode::InterfaceMtu, v4::OptionCode::NTPServers, ])); } else if self.msg_type == DhcpV4MessageType::Request { dhcp_msg .opts_mut() .insert(v4::DhcpOption::MessageType(v4::MessageType::Request)); if let Some(lease) = self.lease.as_ref() { if self.renew_or_rebind { dhcp_msg.set_ciaddr(lease.yiaddr); } else { if lease.srv_id != Ipv4Addr::new(0, 0, 0, 0) { dhcp_msg.opts_mut().insert( v4::DhcpOption::ServerIdentifier(lease.srv_id), ); } else { dhcp_msg.opts_mut().insert( v4::DhcpOption::ServerIdentifier(lease.siaddr), ); } dhcp_msg.opts_mut().insert( v4::DhcpOption::RequestedIpAddress(lease.yiaddr), ); } } else { let e = DhcpError::new( ErrorKind::InvalidArgument, "No DHCP lease found for DHCP request, \ please run DhcpV4Message::load_lease() first" .to_string(), ); log::error!("{}", e); return Err(e); } dhcp_msg .opts_mut() .insert(v4::DhcpOption::ParameterRequestList(vec![ v4::OptionCode::Hostname, v4::OptionCode::SubnetMask, v4::OptionCode::Router, v4::OptionCode::DomainNameServer, v4::OptionCode::DomainName, v4::OptionCode::InterfaceMtu, v4::OptionCode::NTPServers, ])); } else if self.msg_type == DhcpV4MessageType::Release { if let Some(lease) = self.lease.as_ref() { dhcp_msg.set_ciaddr(lease.yiaddr); dhcp_msg.opts_mut().insert(v4::DhcpOption::MessageType( v4::MessageType::Release, )); if lease.srv_id != Ipv4Addr::new(0, 0, 0, 0) { dhcp_msg .opts_mut() .insert(v4::DhcpOption::ServerIdentifier(lease.srv_id)); } else { dhcp_msg .opts_mut() .insert(v4::DhcpOption::ServerIdentifier(lease.siaddr)); } } else { return Err(DhcpError::new( ErrorKind::Bug, format!("Got no lease for RELEASE message: {self:?}"), )); } } else { let e = DhcpError::new( ErrorKind::InvalidArgument, format!("Unsupported DHCP message type {:?}", self.msg_type), ); log::error!("{}", e); return Err(e); } dhcp_msg.opts_mut().insert(v4::DhcpOption::ClientIdentifier( self.config.client_id.clone(), )); if self.config.use_host_name_as_client_id { dhcp_msg.opts_mut().insert(v4::DhcpOption::Hostname( self.config.host_name.clone(), )); } log::debug!("DHCP message {:?}", dhcp_msg); let mut dhcp_msg_buff = Vec::new(); let mut e = v4::Encoder::new(&mut dhcp_msg_buff); dhcp_msg.encode(&mut e)?; Ok(dhcp_msg_buff) } pub(crate) fn from_dhcp_pkg(payload: &[u8]) -> Result { let v4_dhcp_msg = v4::Message::decode(&mut Decoder::new(payload)) .map_err(|decode_error| { let e = DhcpError::new( ErrorKind::InvalidDhcpServerReply, format!( "Failed to parse DHCP message from payload of pkg \ {payload:?}: {decode_error}" ), ); log::error!("{}", e); e })?; let msg_type = match v4_dhcp_msg.opts().get(v4::OptionCode::MessageType) { Some(v4::DhcpOption::MessageType(v4::MessageType::Offer)) => { DhcpV4MessageType::Offer } Some(v4::DhcpOption::MessageType(v4::MessageType::Ack)) => { DhcpV4MessageType::Ack } Some(t) => { log::debug!("Unknown dhcp message type {:?}", t); DhcpV4MessageType::Unknown } None => { log::debug!("Got no dhcp message type"); DhcpV4MessageType::Unknown } }; let ret = Self { lease: Some(DhcpV4Lease::try_from(&v4_dhcp_msg)?), msg_type, xid: v4_dhcp_msg.xid(), ..Default::default() }; log::debug!("Got reply DHCP message {:?}", ret); Ok(ret) } pub(crate) fn to_eth_pkg_broadcast(&self) -> Result, DhcpError> { let dhcp_msg_buff = self.to_dhcp_pkg()?; gen_eth_pkg( &mac_address_to_eth_mac_bytes(&self.config.src_mac)?, &BROADCAST_MAC_ADDRESS, &Ipv4Addr::new(0, 0, 0, 0), &Ipv4Addr::new(255, 255, 255, 255), dhcproto::v4::CLIENT_PORT, dhcproto::v4::SERVER_PORT, &dhcp_msg_buff, ) } pub(crate) fn to_proxy_eth_pkg_unicast( &self, ) -> Result, DhcpError> { if let Some(lease) = self.lease.as_ref() { let dhcp_msg_buff = self.to_dhcp_pkg()?; gen_eth_pkg( &mac_address_to_eth_mac_bytes(&self.config.src_mac)?, &lease.srv_mac, &lease.yiaddr, &lease.siaddr, dhcproto::v4::CLIENT_PORT, dhcproto::v4::SERVER_PORT, &dhcp_msg_buff, ) } else { Err(DhcpError::new( ErrorKind::Bug, "No lease found for `to_proxy_eth_pkg_unicast()`".to_string(), )) } } pub(crate) fn from_eth_pkg(data: &[u8]) -> Result { let pkg = match etherparse::SlicedPacket::from_ethernet(data) { Err(error) => { let e = DhcpError::new( ErrorKind::InvalidDhcpServerReply, format!( "Failed to parse ethernet package to Dhcpv4Offer: {error}" ), ); log::error!("{}", e); return Err(e); } Ok(v) => v, }; let mut ret = Self::from_dhcp_pkg(pkg.payload)?; if let Some(eth_header) = pkg.link.map(|l| l.to_header()) { if let Some(lease) = ret.lease.as_mut() { lease.srv_mac = eth_header.source; } } Ok(ret) } } fn gen_eth_pkg( src_mac: &[u8; 6], dst_mac: &[u8; 6], src_ip: &Ipv4Addr, dst_ip: &Ipv4Addr, src_port: u16, dst_port: u16, payload: &[u8], ) -> Result, DhcpError> { let builder = etherparse::PacketBuilder::ethernet2(*src_mac, *dst_mac) .ipv4(src_ip.octets(), dst_ip.octets(), DEFAULT_TTL) .udp(src_port, dst_port); let mut pkg = Vec::::with_capacity(builder.size(payload.len())); builder.write(&mut pkg, payload)?; Ok(pkg) } mozim-0.2.4/src/proiscuous.rs000064400000000000000000000016431046102023000143360ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use crate::{DhcpError, ErrorKind}; pub(crate) fn enable_promiscuous_mode( fd: libc::c_int, iface_index: libc::c_int, ) -> Result<(), DhcpError> { let mreq = libc::packet_mreq { mr_ifindex: iface_index, mr_type: libc::PACKET_MR_PROMISC as libc::c_ushort, mr_alen: 0, mr_address: [0; 8], }; unsafe { let rc = libc::setsockopt( fd, libc::SOL_PACKET, libc::PACKET_ADD_MEMBERSHIP, (&mreq as *const libc::packet_mreq) as *const libc::c_void, std::mem::size_of::() as libc::socklen_t, ); if rc != 0 { return Err(DhcpError::new( ErrorKind::Bug, format!( "Failed to set socket to promiscuous mode with error: {rc}" ), )); } } Ok(()) } mozim-0.2.4/src/socket.rs000064400000000000000000000254011046102023000134110ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use std::ffi::CString; use std::net::{Ipv4Addr, UdpSocket}; use std::os::unix::io::AsRawFd; use std::os::unix::io::RawFd; use nix::errno::Errno; use crate::{ bpf::apply_dhcp_bpf, mac::{mac_address_to_eth_mac_bytes, BROADCAST_MAC_ADDRESS}, proiscuous::enable_promiscuous_mode, DhcpError, DhcpV4Config, ErrorKind, }; const PACKET_HOST: u8 = 0; // a packet addressed to the local host pub(crate) trait DhcpSocket { fn recv(&self) -> Result, DhcpError>; fn send(&self, eth_pkg: &[u8]) -> Result<(), DhcpError>; fn is_raw(&self) -> bool; } #[derive(Debug, PartialEq, Clone, Default)] pub(crate) struct DhcpRawSocket { config: DhcpV4Config, raw_fd: libc::c_int, } impl std::os::unix::io::AsRawFd for DhcpRawSocket { fn as_raw_fd(&self) -> std::os::unix::io::RawFd { self.raw_fd as std::os::unix::io::RawFd } } impl Drop for DhcpRawSocket { fn drop(&mut self) { if self.raw_fd >= 0 { unsafe { libc::close(self.raw_fd); } } } } impl DhcpRawSocket { pub(crate) fn new(config: &DhcpV4Config) -> Result { let iface_index = config.iface_index as libc::c_int; let eth_protocol = libc::ETH_P_ALL; let raw_fd = create_raw_socket(eth_protocol)?; apply_dhcp_bpf(raw_fd)?; bind_raw_socket(raw_fd, eth_protocol, iface_index, &config.src_mac)?; if config.is_proxy { enable_promiscuous_mode(raw_fd, iface_index)?; } set_socket_timeout(raw_fd, config.socket_timeout)?; log::debug!("Raw socket created {}", raw_fd); Ok(DhcpRawSocket { raw_fd, config: config.clone(), }) } } impl DhcpSocket for DhcpRawSocket { fn is_raw(&self) -> bool { true } fn send(&self, eth_pkg: &[u8]) -> Result<(), DhcpError> { if self.raw_fd < 0 { let e = DhcpError::new( ErrorKind::Bug, "Please run DhcpSocket::open_raw() first".to_string(), ); log::error!("{}", e); return Err(e); } let mut dst_addr: libc::sockaddr_ll = unsafe { std::mem::zeroed() }; dst_addr.sll_halen = libc::ETH_ALEN as u8; dst_addr.sll_addr[..libc::ETH_ALEN as usize] .clone_from_slice(&BROADCAST_MAC_ADDRESS); dst_addr.sll_ifindex = self.config.iface_index as i32; let addr_buffer_size: libc::socklen_t = std::mem::size_of::() as libc::socklen_t; let addr_ptr = unsafe { std::mem::transmute::<*mut libc::sockaddr_ll, *mut libc::sockaddr>( &mut dst_addr, ) }; unsafe { log::debug!("Sending raw ethernet package: {:?}", eth_pkg); let sent_bytes = libc::sendto( self.raw_fd, eth_pkg.as_ptr() as *mut libc::c_void, eth_pkg.len(), 0, // flags addr_ptr, addr_buffer_size, ); log::debug!("Raw socket sent: {} bytes", sent_bytes); if sent_bytes <= 0 { let e = DhcpError::new( ErrorKind::Bug, format!( "Failed to send data to socket {}: {}, data: {:?}", self.raw_fd, Errno::last(), eth_pkg, ), ); log::error!("{}", e); return Err(e); } } Ok(()) } fn recv(&self) -> Result, DhcpError> { let mut src_addr: libc::sockaddr_ll = unsafe { std::mem::zeroed() }; // TODO: Add support of `Maximum DHCP Message Size` option let mut buffer = [0u8; 1500]; let mut addr_buffer_size: libc::socklen_t = std::mem::size_of::() as libc::socklen_t; let addr_ptr = unsafe { std::mem::transmute::<*mut libc::sockaddr_ll, *mut libc::sockaddr>( &mut src_addr, ) }; unsafe { log::debug!("Raw socket receiving"); let rc = libc::recvfrom( self.raw_fd, buffer.as_mut_ptr() as *mut libc::c_void, buffer.len(), 0, // flags addr_ptr, &mut addr_buffer_size, ); if rc <= 0 { let errno = Errno::last(); let e = if errno == Errno::EAGAIN { DhcpError::new( ErrorKind::Timeout, "Timeout on receiving data from socket".to_string(), ) } else { DhcpError::new( ErrorKind::Bug, format!( "Failed to recv from socket {}: {}", self.raw_fd, errno ), ) }; log::error!("{}", e); return Err(e); } log::debug!("Raw socket received {:?}", &buffer[..rc as usize]); } Ok(buffer.to_vec()) } } fn create_raw_socket( eth_protocol: libc::c_int, ) -> Result { unsafe { match libc::socket( libc::AF_PACKET, libc::SOCK_RAW, eth_protocol.to_be() as libc::c_int, ) { -1 => Err(DhcpError::new( ErrorKind::Bug, "libc::socket() failed with -1".to_string(), )), fd => Ok(fd), } } } fn bind_raw_socket( fd: libc::c_int, eth_protocol: libc::c_int, iface_index: libc::c_int, mac_address: &str, ) -> Result<(), DhcpError> { let mut sll_addr: [libc::c_uchar; 8] = [0; 8]; sll_addr[..libc::ETH_ALEN as usize] .clone_from_slice(&mac_address_to_eth_mac_bytes(mac_address)?); let mut socket_addr = libc::sockaddr_ll { sll_family: libc::AF_PACKET as libc::c_ushort, sll_protocol: (eth_protocol as libc::c_ushort).to_be(), sll_ifindex: iface_index, sll_hatype: libc::ARPHRD_ETHER as libc::c_ushort, sll_pkttype: PACKET_HOST as libc::c_uchar, sll_halen: libc::ETH_ALEN as libc::c_uchar, sll_addr, }; unsafe { let addr_ptr = std::mem::transmute::< *mut libc::sockaddr_ll, *mut libc::sockaddr, >(&mut socket_addr); match libc::bind( fd, addr_ptr, std::mem::size_of::() as libc::socklen_t, ) { 0 => Ok(()), rc => { libc::close(fd); Err(DhcpError::new( ErrorKind::Bug, format!("Failed to bind socket: {rc}"), )) } } } } #[derive(Debug)] pub(crate) struct DhcpUdpSocket { socket: UdpSocket, } impl std::os::unix::io::AsRawFd for DhcpUdpSocket { fn as_raw_fd(&self) -> RawFd { self.socket.as_raw_fd() } } impl DhcpUdpSocket { pub(crate) fn new( iface_name: &str, src_ip: &Ipv4Addr, dst_ip: &Ipv4Addr, socket_timeout: u32, ) -> Result { let socket = UdpSocket::bind(format!( "{}:{}", src_ip, 0 // Use random source port ))?; log::debug!("UDP socket bind to {:?}", socket); bind_socket_to_iface(socket.as_raw_fd(), iface_name)?; socket.set_read_timeout(Some(std::time::Duration::from_secs( socket_timeout.into(), )))?; socket.set_write_timeout(Some(std::time::Duration::from_secs( socket_timeout.into(), )))?; socket.connect(format!("{}:{}", dst_ip, dhcproto::v4::SERVER_PORT))?; Ok(Self { socket }) } } impl DhcpSocket for DhcpUdpSocket { fn is_raw(&self) -> bool { false } fn send(&self, pkg: &[u8]) -> Result<(), DhcpError> { self.socket.send(pkg)?; Ok(()) } fn recv(&self) -> Result, DhcpError> { // TODO: Add support of `Maximum DHCP Message Size` option let mut buffer = [0u8; 1500]; self.socket.recv(&mut buffer)?; Ok(buffer.to_vec()) } } fn set_socket_timeout(fd: libc::c_int, timeout: u32) -> Result<(), DhcpError> { // suppress clippy warning when compiling on 64bit system, but this // `try_into()` is require on i686 system. #[allow(clippy::unnecessary_fallible_conversions)] let tv_sec: libc::time_t = match timeout.try_into() { Ok(t) => t, Err(e) => { return Err(DhcpError::new( ErrorKind::InvalidArgument, format!("Invalid timeout value {timeout}, error: {e}"), )); } }; let tmo = libc::timeval { tv_sec, tv_usec: 0 }; unsafe { let rc = libc::setsockopt( fd, libc::SOL_SOCKET, libc::SO_SNDTIMEO, (&tmo as *const libc::timeval) as *const libc::c_void, std::mem::size_of::() as libc::socklen_t, ); if rc < 0 { return Err(DhcpError::new( ErrorKind::Bug, format!( "Failed to set the send timeout SO_SNDTIMEO to \ socket {fd}: {rc}" ), )); } let rc = libc::setsockopt( fd, libc::SOL_SOCKET, libc::SO_RCVTIMEO, (&tmo as *const libc::timeval) as *const libc::c_void, std::mem::size_of::() as libc::socklen_t, ); if rc < 0 { let e = DhcpError::new( ErrorKind::Bug, format!( "Failed to set the recv timeout SO_RCVTIMEO to \ socket {fd}: {rc}" ), ); log::error!("{}", e); return Err(e); } } Ok(()) } fn bind_socket_to_iface(fd: RawFd, iface_name: &str) -> Result<(), DhcpError> { let iface_name_cstr = CString::new(iface_name)?; unsafe { let rc = libc::setsockopt( fd, libc::SOL_SOCKET, libc::SO_BINDTODEVICE, iface_name_cstr.as_ptr() as *const libc::c_void, std::mem::size_of::() as libc::socklen_t, ); if rc != 0 { let e = DhcpError::new( ErrorKind::Bug, format!( "Failed to bind socket to interface {} with error: {}", iface_name, Errno::last(), ), ); log::error!("{}", e); return Err(e); } } Ok(()) } mozim-0.2.4/src/time.rs000064400000000000000000000044261046102023000130630ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 use std::os::fd::AsFd; use std::os::unix::io::{AsRawFd, RawFd}; use std::time::Duration; use nix::sys::time::{TimeSpec, TimeValLike}; use nix::sys::timerfd::{ ClockId::CLOCK_BOOTTIME, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags, }; use rand::Rng; use crate::{DhcpError, ErrorKind}; #[derive(Debug)] pub(crate) struct DhcpTimerFd { pub(crate) fd: TimerFd, } impl AsRawFd for DhcpTimerFd { fn as_raw_fd(&self) -> RawFd { self.fd.as_fd().as_raw_fd() } } impl DhcpTimerFd { pub(crate) fn new(time: u32) -> Result { let fd = TimerFd::new(CLOCK_BOOTTIME, TimerFlags::empty()).map_err(|e| { let e = DhcpError::new( ErrorKind::Bug, format!("Failed to create timerfd {e}"), ); log::error!("{}", e); e })?; fd.set( Expiration::OneShot(TimeSpec::seconds(time.into())), TimerSetTimeFlags::empty(), ) .map_err(|e| { let e = DhcpError::new( ErrorKind::Bug, format!("Failed to set timerfd {e}"), ); log::error!("{}", e); e })?; log::debug!("TimerFd created {:?} with {} seconds", fd, time); Ok(Self { fd }) } } // The T1/T2 randomization is done by server side according to RFC 2131: // Times T1 and T2 SHOULD be chosen with some random "fuzz" around a fixed // value, to avoid synchronization of client reacquisition. pub(crate) fn gen_renew_rebind_times(t1: u32, t2: u32, lease: u32) -> [u32; 4] { [t1, t1 + (t2 - t1) / 2, t2, t2 + (lease - t2) / 2] } // RFC 2131, section 4.1 "Constructing and sending DHCP messages" has // retransmission guideline. // It should be starting with 4 seconds and double of previous delay, up to 64 // seconds. Delay should be randomized from range -1 to 1; pub(crate) fn gen_dhcp_request_delay(retry_count: u32) -> u32 { let mut base = 2u64.pow(retry_count + 2) - 1; if base > 62 { base = 62; } let ms: u64 = rand::thread_rng().gen_range(0..2000); (Duration::from_secs(base) + Duration::from_millis(ms)) .as_secs() .try_into() .unwrap_or(u32::MAX) } mozim-0.2.4/utils/test_env_mozim000075500000000000000000000026501046102023000151150ustar 00000000000000#!/bin/bash -x PID_FILE="/tmp/test_dnsmasq.pid" LEASE_FILE="/tmp/test_dnsmasq.lease" IPV4_BLOCK="192.0.2" IPV6_BLOCK="2001:db8:a::" DHCP_SRV_IP="${IPV4_BLOCK}.1" DHCP_SRV_IP6="${IPV6_BLOCK}1" if [ -e $PID_FILE ];then sudo kill `cat $PID_FILE` fi if [ "CHK$1" == "CHKrm" ]; then sudo ip link del dhcpcli sudo ip netns del mozim exit fi sudo ip netns add mozim sudo ip link add dhcpcli type veth peer name dhcpsrv sudo ip link set dhcpcli up sudo ip link set dhcpsrv netns mozim sudo ip netns exec mozim ip link set dhcpsrv up sudo ip netns exec mozim ip addr add ${DHCP_SRV_IP}/24 dev dhcpsrv sudo ip netns exec mozim ip -6 addr add ${DHCP_SRV_IP6}/64 dev dhcpsrv sudo rm $LEASE_FILE -f sudo ip netns exec mozim dnsmasq \ --log-dhcp \ --keep-in-foreground \ --no-daemon \ --conf-file=/dev/null \ --dhcp-leasefile=$LEASE_FILE \ --no-hosts \ --dhcp-host=dummy-host,${IPV4_BLOCK}.99 \ --dhcp-option=option:dns-server,8.8.8.8,1.1.1.1 \ --dhcp-option=option:mtu,1492 \ --dhcp-option=option:domain-name,example.com\ --dhcp-option=option:ntp-server,${DHCP_SRV_IP} \ --keep-in-foreground \ --clear-on-reload \ --interface=dhcpsrv \ --enable-ra \ --dhcp-range=${IPV6_BLOCK}2,${IPV6_BLOCK}fff,ra-names,slaac,64,2m \ --dhcp-range=${IPV4_BLOCK}.2,${IPV4_BLOCK}.50,2m \ --no-ping if [ "CHK$1" == "CHK" ];then sudo ip link del dhcpcli sudo ip netns del mozim fi