neli-0.7.4/.cargo_vcs_info.json0000644000000001361046102023000120150ustar { "git": { "sha1": "1f3498207671ccc80f391b56db0648cbd754b8a5" }, "path_in_vcs": "" }neli-0.7.4/Cargo.lock0000644000000410661046102023000077770ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anstream" version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", "windows-sys 0.61.2", ] [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "darling" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn", ] [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", "syn", ] [[package]] name = "derive_builder" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ "darling", "proc-macro2", "quote", "syn", ] [[package]] name = "derive_builder_macro" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", "syn", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "env_filter" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", "jiff", "log", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getset" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", "syn", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "jiff" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", "serde_core", ] [[package]] name = "jiff-static" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" dependencies = [ "proc-macro2", "quote", "syn", ] [[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.178" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mio" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", "windows-sys 0.61.2", ] [[package]] name = "neli" version = "0.7.4" dependencies = [ "bitflags", "byteorder", "derive_builder", "env_logger", "getset", "lazy_static", "libc", "log", "neli-proc-macros", "parking_lot", "tokio", ] [[package]] name = "neli-proc-macros" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d8d08c6e98f20a62417478ebf7be8e1425ec9acecc6f63e22da633f6b71609" dependencies = [ "either", "proc-macro2", "quote", "serde", "syn", ] [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] [[package]] name = "proc-macro-error-attr2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "proc-macro-error2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", "syn", ] [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", "windows-sys 0.60.2", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tokio" version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", "socket2", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[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.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "windows-targets" version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" neli-0.7.4/Cargo.toml0000644000000056231046102023000100210ustar # 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 = "neli" version = "0.7.4" authors = ["John Baublitz "] build = false include = [ "**/*.rs", "Cargo.toml", "LICENSE", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Type safe netlink library written in Rust" readme = "README.md" keywords = ["netlink"] license = "BSD-3-Clause" repository = "https://github.com/jbaublitz/neli" [package.metadata.docs.rs] all-features = true [features] async = [ "parking_lot", "tokio", ] default = ["sync"] netfilter = [] sync = ["parking_lot"] [lib] name = "neli" path = "src/lib.rs" [[example]] name = "create-nested-attributes" path = "examples/create-nested-attributes.rs" [[example]] name = "ctrl-list" path = "examples/ctrl-list.rs" [[example]] name = "ctrl-list-multiple" path = "examples/ctrl-list-multiple.rs" [[example]] name = "custom_generic_nl_family_custom_types" path = "examples/custom_generic_nl_family_custom_types.rs" [[example]] name = "error_packet" path = "examples/error_packet.rs" [[example]] name = "extack" path = "examples/extack.rs" [[example]] name = "genl_stream" path = "examples/genl_stream.rs" [[example]] name = "getips" path = "examples/getips.rs" [[example]] name = "getlink" path = "examples/getlink.rs" [[example]] name = "getlink-err" path = "examples/getlink-err.rs" [[example]] name = "getvlans" path = "examples/getvlans.rs" [[example]] name = "lookup_id" path = "examples/lookup_id.rs" [[example]] name = "neli" path = "examples/neli.rs" [[example]] name = "newvlan" path = "examples/newvlan.rs" [[example]] name = "nl80211" path = "examples/nl80211.rs" [[example]] name = "procmon" path = "examples/procmon.rs" [[example]] name = "route-list" path = "examples/route-list.rs" [dependencies.bitflags] version = "2.4" [dependencies.byteorder] version = "1.3" [dependencies.derive_builder] version = "0.20" [dependencies.getset] version = "0.1.2" [dependencies.libc] version = "0.2.174" [dependencies.log] version = "0.4" [dependencies.neli-proc-macros] version = "0.2.0" [dependencies.parking_lot] version = "0.12.1" optional = true [dependencies.tokio] version = "1" features = [ "io-util", "net", "sync", "rt", "macros", ] optional = true [dev-dependencies.env_logger] version = "0.11.0" [dev-dependencies.lazy_static] version = "1.4.0" [dev-dependencies.tokio] version = "1" features = [ "macros", "rt-multi-thread", ] neli-0.7.4/Cargo.toml.orig000064400000000000000000000020131046102023000134460ustar 00000000000000[package] name = "neli" version = "0.7.4" edition = "2021" authors = ["John Baublitz "] description = "Type safe netlink library written in Rust" license = "BSD-3-Clause" repository = "https://github.com/jbaublitz/neli" keywords = ["netlink"] include = [ "**/*.rs", "Cargo.toml", "LICENSE", ] [package.metadata.docs.rs] all-features = true [lib] name = "neli" path = "src/lib.rs" [dependencies] bitflags = "2.4" byteorder = "1.3" derive_builder = "0.20" getset = "0.1.2" libc = "0.2.174" log = "0.4" [dependencies.neli-proc-macros] version = "0.2.0" path = "neli-proc-macros" [dependencies.parking_lot] version = "0.12.1" optional = true [dependencies.tokio] version = "1" features = ["io-util", "net", "sync", "rt", "macros"] optional = true [dev-dependencies] env_logger = "0.11.0" lazy_static = "1.4.0" [dev-dependencies.tokio] version = "1" features = ["macros", "rt-multi-thread"] [features] default = ["sync"] sync = ["parking_lot"] async = ["parking_lot", "tokio"] netfilter = [] neli-0.7.4/LICENSE000064400000000000000000000027511046102023000115750ustar 00000000000000BSD 3-Clause License Copyright (c) 2017, John Baublitz All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. neli-0.7.4/README.md000064400000000000000000000122301046102023000120400ustar 00000000000000[![Latest Version](https://img.shields.io/crates/v/neli.svg)](https://crates.io/crates/neli) [![Documentation](https://docs.rs/neli/badge.svg)](https://docs.rs/neli) # neli Type safe netlink library for Rust As of version 0.4.0, completeness of autogenerated documentation and examples will be a focus. Please open issues if something is missing or unclear! ## API documentation API documentation can be found [here](https://docs.rs/neli/) ## Goals This library aims to cover as many of the netlink subsystems as possible and provide ways to extend neli for anything that is not within the scope of support for this library. This is also a pure Rust implementation and aims to make use of idiomatic Rust features. ## Examples using `neli` Examples of working code exist in the `examples/` subdirectory on Github. Run `cargo build --examples` to build the examples. Workflows usually follow a pattern of socket creation, and then either sending and receiving messages in request/response formats: ```rust use std::error::Error; use neli::{ consts::{genl::*, nl::*, socket::*}, err::RouterError, genl::{Genlmsghdr, GenlmsghdrBuilder, Nlattr}, nl::{NlmsghdrBuilder, NlPayload}, router::synchronous::NlRouter, types::{Buffer, GenlBuffer}, utils::Groups, }; const GENL_VERSION: u8 = 1; fn request_response() -> Result<(), Box> { let (socket, _) = NlRouter::connect( NlFamily::Generic, None, Groups::empty(), )?; let recv = socket.send::<_, _, NlTypeWrapper, Genlmsghdr>( GenlId::Ctrl, NlmF::DUMP, NlPayload::Payload( GenlmsghdrBuilder::<_, CtrlAttr, _>::default() .cmd(CtrlCmd::Getfamily) .version(GENL_VERSION) .build()? ), )?; for msg in recv { let msg = msg?; // Do things with response here... } Ok(()) } ``` or a subscriptions to a stream of event notifications from netlink: ```rust use std::error::Error; use neli::{ consts::{genl::*, nl::*, socket::*}, err::RouterError, genl::Genlmsghdr, router::synchronous::NlRouter, utils::Groups, }; fn subscribe_to_mcast() -> Result<(), Box> { let (s, multicast) = NlRouter::connect( NlFamily::Generic, None, Groups::empty(), )?; let id = s.resolve_nl_mcast_group( "my_family_name", "my_multicast_group_name", )?; s.add_mcast_membership(Groups::new_groups(&[id]))?; for next in multicast { // Do stuff here with parsed packets... // like printing a debug representation of them: println!("{:?}", next?); } Ok(()) } ``` ## Contributing Your contribution will be licensed under neli's [license](LICENSE). I want to keep this aspect as simple as possible so please read over the license file prior to contributing to make sure that you feel comfortable with your contributions being released under the BSD 3-Clause License. Please add tests for new features wherever possible. I may request this prior to merge if I see it is possible and missing. Please document new features not just at a lower level but also with `//!` comments at the module for high level documentation and overview of the feature. Before submitting PRs, take a look at the module's documentation that you are changing. I am currently in the process of adding a "Design decision" section to each module. If you are wondering why I did something the way I did, it should be there. That way, if you have a better way to do it, please let me know! I'm always happy to learn. My hope is that this will also clarify some questions beforehand about why I did things the way I did and will make your life as a contributer easier. ### Git hooks I've provided git hooks to simulate a subset of what CI is running. If you'd like to enable these, they'll probably catch some errors before having to look at CI. If you'd like to enable these, run `git config core.hookspath .githooks`. Your commits will be checked before the commit is created. ### PR target branch Steps for a PR: * For bug fixes and improvements that are not breaking changes, please target `main`. * For breaking changes, please target the branch for the next version release - this will look like `v[NEXT_VERSION]-dev` * Please include a brief description of your change in the CHANGELOG file * Once a PR has been reviewed and approved, please rebase onto the target branch ### Support matrix Currently support is defined as follows: * Edition updates will only be updated as part of breaking changes * Testing is done on nightly and stable and as such, both channels are considered supported * Problems found on older versions that cannot be reproduced on nightly or stable will be considered low-priority * The most recent breaking change release track will receive feature updates and bug fixes * The past breaking change release track is elligible for bug fixes * All release tracks older than this are considered out of scope for support and problems with these releases will be considered low-priority If you would like to propose a revision to these support guidelines, please open an issue. neli-0.7.4/examples/create-nested-attributes.rs000064400000000000000000000041721046102023000176620ustar 00000000000000extern crate neli; use std::{error::Error, io::Cursor}; use neli::{ consts::{genl::*, nl::*}, genl::{AttrTypeBuilder, GenlmsghdrBuilder, NlattrBuilder}, nl::{NlPayload, NlmsghdrBuilder}, types::GenlBuffer, Size, ToBytes, }; pub fn main() -> Result<(), Box> { let attrs = vec![ NlattrBuilder::default() .nla_type(AttrTypeBuilder::default().nla_type(0).build()?) .nla_payload(Vec::::new()) .build()? .nest( &NlattrBuilder::default() .nla_type(AttrTypeBuilder::default().nla_type(1).build()?) .nla_payload("this is a string") .build()?, )? .nest( &NlattrBuilder::default() .nla_type(AttrTypeBuilder::default().nla_type(1).build()?) .nla_payload(0) .build()?, )?, NlattrBuilder::default() .nla_type(AttrTypeBuilder::default().nla_type(2).build()?) .nla_payload(Vec::::new()) .build()? .nest( &NlattrBuilder::default() .nla_type(AttrTypeBuilder::default().nla_type(1).build()?) .nla_payload("this is also a string") .build()?, )? .nest( &NlattrBuilder::default() .nla_type(AttrTypeBuilder::default().nla_type(2).build()?) .nla_payload(5) .build()?, )?, ] .into_iter() .collect::>(); let genlmsg = GenlmsghdrBuilder::default() .cmd(CtrlCmd::Getfamily) .version(2) .attrs(attrs) .build()?; let nlmsg = NlmsghdrBuilder::default() .nl_type(Nlmsg::Noop) .nl_flags(NlmF::REQUEST) .nl_payload(NlPayload::Payload(genlmsg)) .build()?; let mut buffer = Cursor::new(vec![0; nlmsg.padded_size()]); nlmsg.to_bytes(&mut buffer)?; println!( "Serialized heterogeneous attributes: {:?}", buffer.into_inner() ); Ok(()) } neli-0.7.4/examples/ctrl-list-multiple.rs000064400000000000000000000076741046102023000165330ustar 00000000000000use std::{env, error::Error}; #[cfg(feature = "async")] use tokio::runtime::Runtime; #[cfg(feature = "async")] use neli::router::asynchronous::NlRouter; #[cfg(not(feature = "async"))] use neli::router::synchronous::NlRouter; use neli::{ attr::Attribute, consts::{genl::*, nl::*, socket::*}, genl::{Genlmsghdr, GenlmsghdrBuilder}, nl::NlPayload, types::{Buffer, GenlBuffer}, utils::Groups, }; const GENL_VERSION: u8 = 2; // This example attempts to mimic the "genl ctrl list" command. For simplicity, it only outputs // the name and identifier of each generic netlink family. #[cfg(feature = "async")] fn main() -> Result<(), Box> { env_logger::init(); let num_reps = env::args() .nth(1) .ok_or("Number of loop repetitions required")? .parse::()?; Runtime::new()?.block_on(async { for _ in 0..num_reps { let (socket, _) = NlRouter::connect(NlFamily::Generic, None, Groups::empty()).await?; let mut recv = socket .send::<_, _, NlTypeWrapper, Genlmsghdr>( GenlId::Ctrl, NlmF::DUMP, NlPayload::Payload( GenlmsghdrBuilder::default() .cmd(CtrlCmd::Getfamily) .version(GENL_VERSION) .attrs(GenlBuffer::::new()) .build()?, ), ) .await?; while let Some(response_result) = recv .next::>() .await { let response = response_result?; if let Some(p) = response.get_payload() { let handle = p.attrs().get_attr_handle(); for attr in handle.iter() { match attr.nla_type().nla_type() { CtrlAttr::FamilyName => { println!("{}", attr.get_payload_as_with_len::()?); } CtrlAttr::FamilyId => { println!("\tID: 0x{:x}", attr.get_payload_as::()?); } _ => (), } } } } } Result::<_, Box>::Ok(()) })?; Ok(()) } #[cfg(not(feature = "async"))] fn main() -> Result<(), Box> { env_logger::init(); let num_reps = env::args() .nth(1) .ok_or_else(|| "Number of loop repetitions required")? .parse::()?; for _ in 0..num_reps { let (socket, _) = NlRouter::connect(NlFamily::Generic, None, Groups::empty())?; let recv = socket.send::<_, _, NlTypeWrapper, Genlmsghdr>( GenlId::Ctrl, NlmF::DUMP, NlPayload::Payload( GenlmsghdrBuilder::default() .cmd(CtrlCmd::Getfamily) .version(GENL_VERSION) .attrs(GenlBuffer::::new()) .build()?, ), )?; for response_result in recv { let response = response_result?; if let Some(p) = response.get_payload() { let handle = p.attrs().get_attr_handle(); for attr in handle.iter() { match attr.nla_type().nla_type() { CtrlAttr::FamilyName => { println!("{}", attr.get_payload_as_with_len::()?); } CtrlAttr::FamilyId => { println!("\tID: 0x{:x}", attr.get_payload_as::()?); } _ => (), } } } } } Ok(()) } neli-0.7.4/examples/ctrl-list.rs000064400000000000000000000031301046102023000146610ustar 00000000000000use std::error::Error; use neli::{ attr::Attribute, consts::{genl::*, nl::*, socket::*}, genl::{Genlmsghdr, GenlmsghdrBuilder}, nl::NlPayload, router::synchronous::NlRouter, types::{Buffer, GenlBuffer}, utils::Groups, }; const GENL_VERSION: u8 = 2; // This example attempts to mimic the "genl ctrl list" command. For simplicity, it only outputs // the name and identifier of each generic netlink family. fn main() -> Result<(), Box> { env_logger::init(); let (socket, _) = NlRouter::connect(NlFamily::Generic, None, Groups::empty())?; let recv = socket.send::<_, _, NlTypeWrapper, Genlmsghdr>( GenlId::Ctrl, NlmF::DUMP, NlPayload::Payload( GenlmsghdrBuilder::default() .cmd(CtrlCmd::Getfamily) .version(GENL_VERSION) .attrs(GenlBuffer::::new()) .build()?, ), )?; for response_result in recv { let response = response_result?; if let Some(p) = response.get_payload() { let handle = p.attrs().get_attr_handle(); for attr in handle.iter() { match attr.nla_type().nla_type() { CtrlAttr::FamilyName => { println!("{}", attr.get_payload_as_with_len::()?); } CtrlAttr::FamilyId => { println!("\tID: 0x{:x}", attr.get_payload_as::()?); } _ => (), } } } } Ok(()) } neli-0.7.4/examples/custom_generic_nl_family_custom_types.rs000064400000000000000000000130561046102023000226320ustar 00000000000000//! Userland component written in Rust, that uses neli to talk to a custom Netlink //! family via Generic Netlink. The family is called "gnl_foobar_xmpl" and the //! kernel module must be loaded first. Otherwise the family doesn't exist. //! //! A working kernel module implementation with which you can use this binary //! can be found here: https://github.com/phip1611/generic-netlink-user-kernel-rust //! //! Output might look like this (if the kernel module is loaded) //! ``` //! Generic family number is 35 //! Send to kernel: 'Some data that has `Nl` trait implemented, like &str' //! Received from kernel: 'Some data that has `Nl` trait implemented, like &str' //! ``` use std::iter::once; use neli::{ consts::{nl::NlmF, socket::NlFamily}, genl::{AttrTypeBuilder, Genlmsghdr, GenlmsghdrBuilder, NlattrBuilder}, neli_enum, nl::{NlPayload, Nlmsghdr}, router::synchronous::NlRouter, types::GenlBuffer, utils::Groups, }; /// Name of the Netlink family registered via Generic Netlink const FAMILY_NAME: &str = "gnl_foobar_xmpl"; /// Data we want to send to kernel. const ECHO_MSG: &str = "Some data that has `Nl` trait implemented, like &str"; // Implements the necessary trait for the "neli" lib on an enum called "NlFoobarXmplOperation". // NlFoobarXmplOperation corresponds to "enum NlFoobarXmplCommand" in kernel module C code. // Describes what callback function shall be invoked in the linux kernel module. // This is for the "cmd" field in Generic Netlink header. #[neli_enum(serialized_type = "u8")] pub enum NlFoobarXmplOperation { Unspec = 0, Echo = 1, } impl neli::consts::genl::Cmd for NlFoobarXmplOperation {} // Implements the necessary trait for the "neli" lib on an enum called "NlFoobarXmplAttribute". // NlFoobarXmplAttribute corresponds to "enum NlFoobarXmplAttribute" in kernel module C code. // Describes the value type to data mappings inside the generic netlink packet payload. // This is for the Netlink Attributes (the actual payload) we want to send. #[neli_enum(serialized_type = "u16")] pub enum NlFoobarXmplAttribute { Unspec = 0, Msg = 1, } impl neli::consts::genl::NlAttrType for NlFoobarXmplAttribute {} fn main() { let (sock, _) = NlRouter::connect( NlFamily::Generic, // 0 is pid of kernel -> socket is connected to kernel Some(0), Groups::empty(), ) .unwrap(); let res = sock.resolve_genl_family(FAMILY_NAME); let family_id = match res { Ok(id) => id, Err(e) => { eprintln!( "The Netlink family '{FAMILY_NAME}' can't be found. Is the kernel module loaded yet? neli-error='{e}'" ); // Exit without error in order for Continuous Integration and automatic testing not to fail. // This is because in testing/build scenarios we do not have a Kernel module which we can load. return; } }; println!("Generic family number is {family_id}"); // We want to send an Echo command // 1) prepare NlFoobarXmpl Attribute let attrs = once( NlattrBuilder::default() .nla_type( AttrTypeBuilder::default() // the type of the attribute. This is an u16 and corresponds // to an enum on the receiving side .nla_type(NlFoobarXmplAttribute::Msg) .build() .unwrap(), ) .nla_payload(ECHO_MSG) .build() .unwrap(), ) .collect::>(); // 2) prepare Generic Netlink Header. The Generic Netlink Header contains the // attributes (actual data) as payload. let gnmsghdr = GenlmsghdrBuilder::default() .cmd(NlFoobarXmplOperation::Echo) // You can evolve your application over time using different versions or ignore it. // Application specific; receiver can check this value and to specific logic .version(1) // actual payload .attrs(attrs) .build() .unwrap(); println!("Send to kernel: '{ECHO_MSG}'"); // Send data let mut recv = sock .send::<_, _, u16, Genlmsghdr>( family_id, // You can use flags in an application specific way (e.g. ACK flag). It is up to you // if you check against flags in your Kernel module. It is required to add NLM_F_REQUEST, // otherwise the Kernel doesn't route the packet to the right Netlink callback handler // in your Kernel module. This might result in a deadlock on the socket if an expected // reply you are waiting for in a blocking way is never received. // Kernel reference: https://elixir.bootlin.com/linux/v5.10.16/source/net/netlink/af_netlink.c#L2487 // // NlRouter automatically adds the REQUEST flag. NlmF::empty(), NlPayload::Payload(gnmsghdr), ) .expect("Send must work"); // receive echo'ed message let res: Nlmsghdr> = recv.next().expect("Should receive a message").unwrap(); /* USELESS, just note: this is always the case. Otherwise neli would have returned Error if res.nl_type == family_id { println!("Received successful reply!"); }*/ let attr_handle = res.get_payload().unwrap().attrs().get_attr_handle(); let received = attr_handle .get_attr_payload_as_with_len::(NlFoobarXmplAttribute::Msg) .unwrap(); println!("Received from kernel: '{received}'"); } neli-0.7.4/examples/error_packet.rs000064400000000000000000000014001046102023000154220ustar 00000000000000use std::error::Error; use neli::{ consts::socket::NlFamily, err::RouterError, router::synchronous::NlRouter, utils::Groups, }; fn main() -> Result<(), Box> { env_logger::init(); // Create a socket and connect to generic netlink. let (sock, _) = NlRouter::connect(NlFamily::Generic, None, Groups::empty())?; // Attempt to resolve a multicast group that should not exist. let error = sock.resolve_nl_mcast_group("not_a", "group"); match error { Ok(_) => panic!("Should not succeed"), Err(RouterError::Nlmsgerr(e)) => { // Should be packet that caused error println!("{e:?}"); } Err(e) => panic!("Should not return any error other than NlError: {e}"), }; Ok(()) } neli-0.7.4/examples/extack.rs000064400000000000000000000067621046102023000142410ustar 00000000000000use std::iter::once; use neli::{ consts::{ nl::{GenlId, NlmF}, socket::NlFamily, }, err::RouterError, genl::{AttrTypeBuilder, Genlmsghdr, GenlmsghdrBuilder, NlattrBuilder, NoUserHeader}, nl::{NlPayload, Nlmsghdr}, router::synchronous::NlRouter, types::GenlBuffer, utils::Groups, }; #[neli::neli_enum(serialized_type = "u8")] pub enum Nl80211Command { GetInterface = 5, /* Others elided */ } impl neli::consts::genl::Cmd for Nl80211Command {} #[neli::neli_enum(serialized_type = "u16")] pub enum Nl80211Attribute { Mac = 6, /* Attributes Elided */ } impl neli::consts::genl::NlAttrType for Nl80211Attribute {} #[neli::neli_enum(serialized_type = "u16")] pub enum ExtAckAttr { Unused = 0, Msg = 1, Offs = 2, Cookie = 3, Policy = 4, } impl neli::consts::genl::NlAttrType for ExtAckAttr {} fn main() -> Result<(), Box> { env_logger::init(); let (sock, _) = NlRouter::connect( NlFamily::Generic, /* family */ None, /* pid */ Groups::empty(), /* groups */ )?; sock.enable_ext_ack(true)?; let family_id = sock.resolve_genl_family("nl80211")?; let attrs = once( NlattrBuilder::default() .nla_type( AttrTypeBuilder::default() .nla_type(/* Attribute */ Nl80211Attribute::Mac) .build() .unwrap(), ) .nla_payload( /* Value */ vec![0_u8], /* NOTE: Deliberately wrong length */ ) .build() .unwrap(), ) .collect::>(); let mut recv = sock.send::<_, _, GenlId, Genlmsghdr>( family_id, NlmF::ACK, NlPayload::Payload( GenlmsghdrBuilder::::default() .cmd(Nl80211Command::GetInterface) .version(1) .attrs(attrs) .build()?, ), )?; let data: Option>, _>> = recv.next(); match data { Some(Ok(msgs)) => { println!("msgs: {msgs:?}"); } Some(Err(RouterError::Nlmsgerr(e))) => { println!("msg err: {e:?}"); println!( "unix error: {:?}", std::io::Error::from_raw_os_error(-e.error()) ); for attr in e.ext_ack().iter() { match ExtAckAttr::from(u16::from(attr.nla_type())) { ExtAckAttr::Msg => { println!( "Msg={:?}", String::from_utf8(attr.nla_payload().as_ref().to_vec()) ); } ExtAckAttr::Offs => { println!("Offs={:?}", attr.nla_payload().as_ref()); } ExtAckAttr::Cookie => { println!("Cookie={:?}", attr.nla_payload().as_ref()); } ExtAckAttr::Policy => { println!("Policy={:?}", attr.nla_payload().as_ref()); } _ => println!("attr: {attr:?}"), } } } Some(Err(e)) => { println!("err: {e:#?}"); } None => { println!("No messages received"); } } Ok(()) } neli-0.7.4/examples/genl_stream.rs000064400000000000000000000043301046102023000152470ustar 00000000000000use std::{env, error::Error}; use neli::consts::socket::NlFamily; #[cfg(not(feature = "async"))] use neli::router::synchronous::NlRouter; #[cfg(feature = "async")] use neli::{genl::Genlmsghdr, router::asynchronous::NlRouter}; #[cfg(feature = "async")] fn debug_stream() -> Result<(), Box> { use neli::utils::Groups; let mut args = env::args(); let _ = args.next(); let first_arg = args.next(); let second_arg = args.next(); let (family_name, mc_group_name) = match (first_arg, second_arg) { (Some(fam_name), Some(mc_name)) => (fam_name, mc_name), (_, _) => { println!("USAGE: genl_stream FAMILY_NAME MULTICAST_GROUP_NAME"); std::process::exit(1) } }; let runtime = ::tokio::runtime::Runtime::new().unwrap(); runtime.block_on(async { let (s, mut multicast) = NlRouter::connect(NlFamily::Generic, None, Groups::empty()).await?; let id = s .resolve_nl_mcast_group(&family_name, &mc_group_name) .await?; s.add_mcast_membership(Groups::new_groups(&[id]))?; while let Some(Ok(msg)) = multicast.next::>().await { println!("{msg:?}"); } Ok(()) }) } #[cfg(not(feature = "async"))] fn debug_stream() -> Result<(), Box> { use neli::utils::Groups; let mut args = env::args(); let _ = args.next(); let first_arg = args.next(); let second_arg = args.next(); let (family_name, mc_group_name) = match (first_arg, second_arg) { (Some(fam_name), Some(mc_name)) => (fam_name, mc_name), (_, _) => { println!("USAGE: genl_stream FAMILY_NAME MULTICAST_GROUP_NAME"); std::process::exit(1) } }; let (s, mc_recv) = NlRouter::connect(NlFamily::Generic, None, Groups::empty())?; let id = s.resolve_nl_mcast_group(&family_name, &mc_group_name)?; s.add_mcast_membership(Groups::new_groups(&[id]))?; for next in mc_recv { println!("{:?}", next?); } Ok(()) } pub fn main() -> Result<(), Box> { env_logger::init(); #[cfg(feature = "async")] debug_stream()?; #[cfg(not(feature = "async"))] debug_stream()?; Ok(()) } neli-0.7.4/examples/getips.rs000064400000000000000000000032321046102023000142420ustar 00000000000000use std::net::Ipv4Addr; use neli::{ attr::Attribute, consts::{ nl::NlmF, rtnl::{Ifa, RtAddrFamily, RtScope, Rtm}, socket::NlFamily, }, err::MsgError, nl::{NlPayload, Nlmsghdr}, router::synchronous::NlRouter, rtnl::{Ifaddrmsg, IfaddrmsgBuilder}, utils::Groups, }; fn main() -> Result<(), Box> { env_logger::init(); let (rtnl, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty())?; rtnl.enable_strict_checking(true)?; let ifaddrmsg = IfaddrmsgBuilder::default() .ifa_family(RtAddrFamily::Inet) .ifa_prefixlen(0) .ifa_scope(RtScope::Universe) .ifa_index(0) .build()?; let recv = rtnl.send(Rtm::Getaddr, NlmF::ROOT, NlPayload::Payload(ifaddrmsg))?; let mut addrs = Vec::::with_capacity(1); for response in recv { let header: Nlmsghdr = response?; if let NlPayload::Payload(p) = header.nl_payload() { if header.nl_type() != &Rtm::Newaddr { return Err(Box::new(MsgError::new( "Netlink error retrieving IP address", ))); } if p.ifa_scope() != &RtScope::Universe { continue; } for rtattr in p.rtattrs().iter() { if rtattr.rta_type() == &Ifa::Local { addrs.push(Ipv4Addr::from(u32::from_be( rtattr.get_payload_as::()?, ))); } } } } println!("Local IPv4 addresses:"); for addr in addrs { println!("{addr}"); } Ok(()) } neli-0.7.4/examples/getlink-err.rs000064400000000000000000000022121046102023000151670ustar 00000000000000use neli::{ consts::{ nl::{NlmF, NlmsgerrAttr}, rtnl::Arphrd, rtnl::{RtAddrFamily, Rtm}, socket::NlFamily, }, nl::NlPayload, router::synchronous::NlRouter, rtnl::{Ifinfomsg, IfinfomsgBuilder}, utils::Groups, }; fn main() -> Result<(), Box> { env_logger::init(); let (rtnl, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty())?; rtnl.enable_ext_ack(true)?; rtnl.enable_strict_checking(true)?; let ifinfomsg = IfinfomsgBuilder::default() .ifi_family(RtAddrFamily::Inet) .ifi_type(Arphrd::None) .build()?; let recv = rtnl.send::<_, _, Rtm, Ifinfomsg>( Rtm::Getlink, NlmF::DUMP | NlmF::ACK, NlPayload::Payload(ifinfomsg), )?; for response in recv { if let NlPayload::DumpExtAck(ack) = response?.nl_payload() { println!("{ack:?}"); println!( "MSG: {}", ack.ext_ack() .get_attr_handle() .get_attr_payload_as_with_len::(NlmsgerrAttr::Msg)? ); } } Ok(()) } neli-0.7.4/examples/getlink.rs000064400000000000000000000020561046102023000144070ustar 00000000000000use neli::{ consts::{ nl::NlmF, rtnl::{Ifla, RtAddrFamily, Rtm}, socket::NlFamily, }, nl::NlPayload, router::synchronous::NlRouter, rtnl::{Ifinfomsg, IfinfomsgBuilder}, utils::Groups, }; fn main() -> Result<(), Box> { env_logger::init(); let (rtnl, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty())?; rtnl.enable_ext_ack(true)?; rtnl.enable_strict_checking(true)?; let ifinfomsg = IfinfomsgBuilder::default() .ifi_family(RtAddrFamily::Inet) .build()?; let recv = rtnl.send::<_, _, Rtm, Ifinfomsg>( Rtm::Getlink, NlmF::DUMP | NlmF::ACK, NlPayload::Payload(ifinfomsg), )?; for response in recv { if let Some(payload) = response?.get_payload() { println!( "{:?}", payload .rtattrs() .get_attr_handle() .get_attr_payload_as_with_len::(Ifla::Ifname)?, ) } } Ok(()) } neli-0.7.4/examples/getvlans.rs000064400000000000000000000043531046102023000145770ustar 00000000000000use neli::{ consts::{ nl::NlmF, rtnl::{Ifla, IflaInfo, IflaVlan, RtAddrFamily, Rtm}, socket::NlFamily, }, nl::NlPayload, router::synchronous::NlRouter, rtnl::{Ifinfomsg, IfinfomsgBuilder}, utils::Groups, }; use std::collections::HashMap; fn main() -> Result<(), Box> { env_logger::init(); let (rtnl, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty())?; rtnl.enable_ext_ack(true)?; rtnl.enable_strict_checking(true)?; let ifinfomsg = IfinfomsgBuilder::default() .ifi_family(RtAddrFamily::Netlink) .build()?; let recv = rtnl.send::<_, _, Rtm, Ifinfomsg>( Rtm::Getlink, NlmF::DUMP | NlmF::ACK, NlPayload::Payload(ifinfomsg), )?; // vlan index => (if index) let mut vlans = HashMap::new(); // if index => (if name, Option) let mut ifaces = HashMap::new(); for response in recv { if let Some(payload) = response?.get_payload() { let attrs = payload.rtattrs().get_attr_handle(); let index = *payload.ifi_index(); let name = attrs.get_attr_payload_as_with_len::(Ifla::Ifname)?; let link = attrs.get_attr_payload_as::(Ifla::Link).ok(); ifaces.insert(index, (name, link)); if let Some(vlan_id) = get_vlan_id(payload) { vlans.insert(vlan_id, index); } } } for (vlan_id, vlan_index) in &vlans { if let Some((name, link)) = ifaces.get(vlan_index) { if let Some((link, _)) = link.as_ref().and_then(|link| ifaces.get(link)) { println!("- vlan: {vlan_id}, name: {name}, link: {link}"); } } } Ok(()) } fn get_vlan_id(payload: &Ifinfomsg) -> Option { let attrs = payload.rtattrs().get_attr_handle(); let info = attrs .get_nested_attributes::(Ifla::Linkinfo) .ok()?; let kind: &str = info .get_attr_payload_as_with_len_borrowed(IflaInfo::Kind) .ok()?; if kind != "vlan\0" { return None; } let data = info .get_nested_attributes::(IflaInfo::Data) .ok()?; data.get_attr_payload_as(IflaVlan::Id).ok() } neli-0.7.4/examples/lookup_id.rs000064400000000000000000000011371046102023000147360ustar 00000000000000use std::{env, error::Error}; use neli::{consts::socket::NlFamily, err::MsgError, router::synchronous::NlRouter, utils::Groups}; fn main() -> Result<(), Box> { env_logger::init(); let (sock, _) = NlRouter::connect(NlFamily::Generic, None, Groups::empty())?; let id = env::args() .nth(1) .ok_or_else(|| MsgError::new("Integer argument required")) .and_then(|arg| arg.parse::().map_err(|e| MsgError::new(e.to_string())))?; let (fam, grp) = sock.lookup_id(id)?; println!("Family name: {fam}"); println!("Multicast group: {grp}"); Ok(()) } neli-0.7.4/examples/neli.rs000064400000000000000000000040461046102023000137020ustar 00000000000000use std::error::Error; use neli::{ consts::{genl::*, nl::*, socket::NlFamily}, err::MsgError, genl::*, nl::{NlPayload, NlmsghdrBuilder}, router::synchronous::NlRouter, utils::Groups, }; fn main() -> Result<(), Box> { // Resolve generic netlink family ID let family_name = "your_family_name_here"; let (sock, _mcast_receiver) = NlRouter::connect(NlFamily::Generic, None, Groups::empty()).unwrap(); let _id = sock.resolve_genl_family(family_name).unwrap(); // Resolve generic netlink multicast group ID let family_name = "your_family_name_here"; let group_name = "your_group_name_here"; let (sock, _mcast_receiver) = NlRouter::connect(NlFamily::Generic, None, Groups::empty()).unwrap(); let _id = sock .resolve_nl_mcast_group(family_name, group_name) .unwrap(); // The following outlines how to parse netlink attributes // This was received from the socket let nlmsg = NlmsghdrBuilder::default() .nl_type(GenlId::Ctrl) .nl_flags(NlmF::empty()) .nl_payload(NlPayload::Payload( GenlmsghdrBuilder::default() .cmd(CtrlCmd::Unspec) .version(2) .build()?, )) .build() .unwrap(); // Get parsing handler for the attributes in this message where the next call // to either get_nested_attributes() or get_payload() will expect a u16 type // to be provided let handle = nlmsg .get_payload() .ok_or_else(|| MsgError::new("No payload found"))? .attrs() .get_attr_handle(); // Get the nested attribute where the Nlattr field of nla_type is equal to 1 and return // a handler containing only this nested attribute internally let next = handle.get_nested_attributes::(1).unwrap(); // Get the nested attribute where the Nlattr field of nla_type is equal to 1 and return // the payload of this attribute as a u32 let _thirty_two_bit_integer = next.get_attr_payload_as::(1).unwrap(); Ok(()) } neli-0.7.4/examples/newvlan.rs000064400000000000000000000076271046102023000144350ustar 00000000000000use neli::{ consts::{ nl::{NlmF, NlmsgerrAttr}, rtnl::{Ifla, IflaInfo, IflaVlan, RtAddrFamily, Rtm}, socket::NlFamily, }, err::MsgError, nl::NlPayload, router::synchronous::NlRouter, rtnl::{Ifinfomsg, IfinfomsgBuilder, RtattrBuilder}, types::{Buffer, RtBuffer}, utils::Groups, }; use std::{env, error::Error}; fn main() -> Result<(), Box> { env_logger::init(); let if_name = env::args() .nth(1) .ok_or_else(|| MsgError::new("Interface name required"))?; let vlan_id = env::args() .nth(2) .ok_or_else(|| MsgError::new("VLAN number required")) .and_then(|arg| arg.parse::().map_err(|e| MsgError::new(e.to_string())))?; let (rtnl, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty())?; rtnl.enable_ext_ack(true)?; rtnl.enable_strict_checking(true)?; let mut recv = rtnl.send::( Rtm::Getlink, NlmF::ROOT, NlPayload::::Payload( IfinfomsgBuilder::default() .ifi_family(RtAddrFamily::Netlink) .build()?, ), )?; let if_index = recv .try_fold(None, |prev, response| -> Result<_, Box> { if let Some(prev) = prev { return Ok(Some(prev)); } let header = response?; if let NlPayload::Payload(if_info) = header.nl_payload() { if header.nl_type() != &Rtm::Newlink { return Err(Box::new(MsgError::new("Netlink error retrieving info")).into()); } if if_info .rtattrs() .get_attr_handle() .get_attr_payload_as_with_len_borrowed::<&str>(Ifla::Ifname) .map(|name| name.trim_end_matches('\0') == if_name) .unwrap_or_default() { return Ok(Some(*if_info.ifi_index())); } } Ok(None) })? .ok_or_else(|| Box::new(MsgError::new("Interface index not found")))?; let mut attrs = RtBuffer::::new(); let name = format!("{if_name}.{vlan_id}"); attrs.push( RtattrBuilder::default() .rta_type(Ifla::Ifname) .rta_payload(name) .build()?, ); attrs.push( RtattrBuilder::default() .rta_type(Ifla::Link) .rta_payload(if_index) .build()?, ); let mut vlan_attrs = RtBuffer::::new(); vlan_attrs.push( RtattrBuilder::default() .rta_type(IflaVlan::Id) .rta_payload(vlan_id) .build()?, ); let mut info_attrs = RtBuffer::::new(); info_attrs.push( RtattrBuilder::default() .rta_type(IflaInfo::Kind) .rta_payload("vlan\0") .build()?, ); info_attrs.push( RtattrBuilder::default() .rta_type(IflaInfo::Data) .rta_payload(vlan_attrs) .build()?, ); attrs.push( RtattrBuilder::default() .rta_type(Ifla::Linkinfo) .rta_payload(info_attrs) .build()?, ); let ifinfomsg = IfinfomsgBuilder::default() .ifi_family(RtAddrFamily::Netlink) .rtattrs(attrs) .build()?; let recv = rtnl.send::<_, _, Rtm, Ifinfomsg>( Rtm::Newlink, NlmF::CREATE | NlmF::EXCL | NlmF::ACK, NlPayload::Payload(ifinfomsg), )?; for response in recv { if let NlPayload::DumpExtAck(ack) = response?.nl_payload() { println!("{ack:?}"); println!( "MSG: {}", ack.ext_ack() .get_attr_handle() .get_attr_payload_as_with_len::(NlmsgerrAttr::Msg)? ); } } Ok(()) } neli-0.7.4/examples/nl80211.rs000064400000000000000000000170721046102023000137630ustar 00000000000000use std::{error::Error, iter::once}; #[cfg(feature = "async")] use neli::router::asynchronous::NlRouter; #[cfg(not(feature = "async"))] use neli::router::synchronous::NlRouter; use neli::{ attr::Attribute, consts::{ nl::{GenlId, NlmF}, socket::NlFamily, }, genl::{AttrTypeBuilder, NlattrBuilder}, genl::{Genlmsghdr, GenlmsghdrBuilder, NoUserHeader}, nl::{NlPayload, Nlmsghdr}, types::GenlBuffer, utils::Groups, }; #[neli::neli_enum(serialized_type = "u8")] pub enum Nl80211Command { Unspecified = 0, GetWiPhy = 1, GetInterface = 5, /* Many many more elided */ } impl neli::consts::genl::Cmd for Nl80211Command {} #[neli::neli_enum(serialized_type = "u16")] pub enum Nl80211Attribute { Unspecified = 0, Wiphy = 1, WiphyName = 2, Ifname = 4, Iftype = 5, Ssid = 52, Wdev = 153, /* Literally hundreds elided */ } impl neli::consts::genl::NlAttrType for Nl80211Attribute {} #[neli::neli_enum(serialized_type = "u32")] pub enum Nl80211IfType { Unspecified = 0, Station = 2, Ap = 3, Monitor = 6, P2pDevice = 10, /* Several more, common ones above */ } fn handle(msg: Nlmsghdr>) { // Messages with the NlmF::DUMP flag end with an empty payload message // Don't parse message unless receive proper payload (non-error, non-empty, non-ack) let payload = match msg.nl_payload() { NlPayload::Payload(p) => p, _ => return, }; let attr_handle = payload.attrs().get_attr_handle(); for attr in attr_handle.iter() { match attr.nla_type().nla_type() { Nl80211Attribute::Wiphy => { let wiphy = attr.get_payload_as::().unwrap(); println!("{:<12}{}", "Wiphy:", wiphy); } Nl80211Attribute::WiphyName => { let wiphy_name = attr.get_payload_as_with_len::().unwrap(); println!("{:<12}{}", "WiphyName:", wiphy_name); } Nl80211Attribute::Ifname => { let ifname = attr.get_payload_as_with_len::().unwrap(); println!("{:<12}{}", "Ifname:", ifname); } Nl80211Attribute::Iftype => { let iftype = attr.get_payload_as::().unwrap(); println!("{:<12}{:?}", "Iftype:", iftype); } Nl80211Attribute::Wdev => { // Wdev is unique 64-bit identifier per WiFi interface containing both the // WiFi radio (wiphy, upper 32 bits) and WiFi interface identifiers (lower 32 bits) // Print lower 9 bytes (36 bits) to simplify printout and match 'iw wlan0 info' output let wdev = attr.get_payload_as::().unwrap(); println!("{:<12}0x{:09x}", "Wdev:", wdev); } Nl80211Attribute::Ssid => { // Kernel references this attribute as binary data. // For simplicity, just try to parse as UTF-8 let ssid: &[u8] = attr.get_payload_as_with_len_borrowed().unwrap(); println!("{:<12}{}", "Ssid:", std::str::from_utf8(ssid).unwrap()); } _ => (), } } } #[cfg(feature = "async")] #[tokio::main] async fn main() -> Result<(), Box> { env_logger::init(); // Initialize NlRouter and resolve 'nl80211' genl family let (sock, _) = NlRouter::connect( NlFamily::Generic, /* family */ Some(0), /* pid */ Groups::empty(), /* groups */ ) .await?; let family_id = sock.resolve_genl_family("nl80211").await?; // Query system for WiFi radios using the 'GetWiphy' command let mut recv = sock .send::<_, _, u16, Genlmsghdr>( family_id, NlmF::DUMP | NlmF::ACK, NlPayload::Payload( GenlmsghdrBuilder::::default() .cmd(Nl80211Command::GetWiPhy) .version(1) .build()?, ), ) .await?; // Print response data which contains WiFi radios detected and configured by system println!("WiFi radios:"); while let Some(Ok(msg)) = recv.next().await { handle(msg); println!(); } // Query system for WiFi interfaces using the 'GetInterface' command // Empty payload for 'Ifname' attribute will dump all WiFi interfaces let attrs = once( NlattrBuilder::default() .nla_type( AttrTypeBuilder::default() .nla_type(Nl80211Attribute::Ifname) .build() .unwrap(), ) .nla_payload(()) .build() .unwrap(), ) .collect::>(); let mut recv = sock .send::<_, _, u16, Genlmsghdr>( family_id, NlmF::DUMP | NlmF::ACK, NlPayload::Payload( GenlmsghdrBuilder::::default() .cmd(Nl80211Command::GetInterface) .attrs(attrs) .version(1) .build()?, ), ) .await?; // Print response data which contains WiFi interfaces detected and configured by system println!("WiFi interfaces:"); while let Some(Ok(msg)) = recv.next().await { handle(msg); println!(); } Ok(()) } #[cfg(not(feature = "async"))] fn main() -> Result<(), Box> { env_logger::init(); // Initialize NlRouter and resolve 'nl80211' genl family let (sock, _) = NlRouter::connect( NlFamily::Generic, /* family */ Some(0), /* pid */ Groups::empty(), /* groups */ )?; let family_id = sock.resolve_genl_family("nl80211")?; // Query system for WiFi radios using the 'GetWiphy' command let recv = sock.send( family_id, NlmF::DUMP | NlmF::ACK, NlPayload::Payload( GenlmsghdrBuilder::::default() .cmd(Nl80211Command::GetWiPhy) .version(1) .build()?, ), )?; // Print response data which contains WiFi radios detected and configured by system println!("WiFi radios:"); for msg in recv { let msg = msg?; handle(msg); println!(); } // Query system for WiFi interfaces using the 'GetInterface' command // Empty payload for 'Ifname' attribute will dump all WiFi interfaces let attrs = once( NlattrBuilder::default() .nla_type( AttrTypeBuilder::default() .nla_type(Nl80211Attribute::Ifname) .build() .unwrap(), ) .nla_payload(()) .build() .unwrap(), ) .collect::>(); let recv = sock.send( family_id, NlmF::DUMP | NlmF::ACK, NlPayload::Payload( GenlmsghdrBuilder::::default() .cmd(Nl80211Command::GetInterface) .attrs(attrs) .version(1) .build()?, ), )?; // Print response data which contains WiFi interfaces detected and configured by system println!("WiFi interfaces:"); for msg in recv { let msg = msg?; handle(msg); println!(); } Ok(()) } neli-0.7.4/examples/procmon.rs000064400000000000000000000052031046102023000144240ustar 00000000000000//! A small process monitor example using netlink connector messages. //! //! You must run this example with root privileges, in the root pid namespace //! //! See this blog post for more details: //! https://nick-black.com/dankwiki/index.php/The_Proc_Connector_and_Socket_Filters use neli::{ connector::{CnMsg, ProcEvent, ProcEventHeader}, consts::{ connector::{CnMsgIdx, CnMsgVal, ProcCnMcastOp}, nl::{NlmF, Nlmsg}, socket::NlFamily, }, nl::{NlPayload, NlmsghdrBuilder}, socket::synchronous::NlSocketHandle, utils::Groups, }; use std::fs; fn main() -> Result<(), Box> { env_logger::init(); let pid = std::process::id(); let socket = NlSocketHandle::connect( NlFamily::Connector, Some(pid), Groups::new_bitmask(CnMsgIdx::Proc.into()), )?; let subscribe = NlmsghdrBuilder::default() .nl_type(Nlmsg::Done) .nl_flags(NlmF::empty()) .nl_pid(pid) .nl_payload(NlPayload::Payload( neli::connector::CnMsgBuilder::default() .idx(CnMsgIdx::Proc) .val(CnMsgVal::Proc) .payload(ProcCnMcastOp::Listen) .build()?, )) .build()?; socket.send(&subscribe)?; loop { for event in socket.recv::>()?.0 { let ProcEvent::Exec { process_pid, .. } = event? .get_payload() .ok_or("Failed to extract payload")? .payload() .event else { continue; }; let exe = fs::read_link(format!("/proc/{process_pid}/exe")) .map(|p| p.display().to_string()) .unwrap_or_else(|_| "unknown".to_string()); let cmdline = cmdline_to_string(process_pid).unwrap_or_else(|_| "unknown".to_string()); println!("Process created: PID: {process_pid}, Exe: {exe}, Cmdline: {cmdline}"); } } } fn cmdline_to_string(pid: i32) -> std::io::Result { // 1) Read the entire file into a byte‐buffer in one go let mut data = fs::read(format!("/proc/{pid}/cmdline"))?; // 2) In‐place map all remaining NULs to spaces for b in &mut data { if *b == 0 { *b = b' '; } } // 3) SAFELY convert bytes → String without re‐checking UTF‐8 // // We can do this because /proc/cmdline is guaranteed to be valid UTF-8 // on Linux (arguments must be passed as UTF-8), so we skip the cost // of a UTF-8 validation pass. let s = unsafe { String::from_utf8_unchecked(data) }; Ok(s) } neli-0.7.4/examples/route-list.rs000064400000000000000000000117621046102023000150650ustar 00000000000000use std::{ collections::HashMap, error::Error, io::Read, net::{IpAddr, Ipv4Addr, Ipv6Addr}, }; use neli::{ consts::{nl::*, rtnl::*, socket::*}, err::{MsgError, RouterError}, nl::{NlPayload, Nlmsghdr}, router::synchronous::NlRouter, rtnl::*, types::Buffer, utils::Groups, }; fn parse_route_table( ifs: &HashMap, rtm: Nlmsghdr, ) -> Result<(), RouterError> { if let Some(payload) = rtm.get_payload() { // This sample is only interested in the main table. if payload.rtm_table() == &RtTable::Main { let mut src = None; let mut dst = None; let mut gateway = None; for attr in payload.rtattrs().iter() { fn to_addr(b: &[u8]) -> Option { if let Ok(tup) = <&[u8; 4]>::try_from(b) { Some(IpAddr::from(*tup)) } else if let Ok(tup) = <&[u8; 16]>::try_from(b) { Some(IpAddr::from(*tup)) } else { None } } match attr.rta_type() { Rta::Dst => dst = to_addr(attr.rta_payload().as_ref()), Rta::Prefsrc => src = to_addr(attr.rta_payload().as_ref()), Rta::Gateway => gateway = to_addr(attr.rta_payload().as_ref()), _ => (), } } if let Some(dst) = dst { print!("{}/{} ", dst, payload.rtm_dst_len()); } else { print!("default "); if let Some(gateway) = gateway { print!("via {gateway} "); } } if let Some(src) = src { print!("dev {}", ifs.get(&src).expect("Should be present")); } if payload.rtm_scope() != &RtScope::Universe { print!( " proto {:?} scope {:?} ", payload.rtm_protocol(), payload.rtm_scope() ) } if let Some(src) = src { print!(" src {src} "); } println!(); } } Ok(()) } /// This sample is a simple imitation of the `ip route` command, to demonstrate interaction /// with the rtnetlink subsystem. fn main() -> Result<(), Box> { env_logger::init(); let (socket, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty()).unwrap(); let ifmsg = IfaddrmsgBuilder::default() .ifa_family(RtAddrFamily::Unspecified) .ifa_prefixlen(0) .ifa_scope(RtScope::Universe) .ifa_index(0) .build()?; let recv = socket.send::<_, _, NlTypeWrapper, _>( Rtm::Getaddr, NlmF::DUMP, NlPayload::Payload(ifmsg), )?; let mut ifs = HashMap::new(); for msg in recv { let msg = msg?; if let NlPayload::<_, Ifaddrmsg>::Payload(p) = msg.nl_payload() { let handle = p.rtattrs().get_attr_handle(); let addr = { if let Ok(mut ip_bytes) = handle.get_attr_payload_as_with_len_borrowed::<&[u8]>(Ifa::Address) { if ip_bytes.len() == 4 { let mut bytes = [0u8; 4]; ip_bytes.read_exact(&mut bytes)?; Some(IpAddr::from(Ipv4Addr::from( u32::from_ne_bytes(bytes).to_be(), ))) } else if ip_bytes.len() == 16 { let mut bytes = [0u8; 16]; ip_bytes.read_exact(&mut bytes)?; Some(IpAddr::from(Ipv6Addr::from( u128::from_ne_bytes(bytes).to_be(), ))) } else { return Err(Box::new(MsgError::new(format!( "Unrecognized address length of {} found", ip_bytes.len() )))); } } else { None } }; let name = handle .get_attr_payload_as_with_len::(Ifa::Label) .ok(); if let (Some(addr), Some(name)) = (addr, name) { ifs.insert(addr, name); } } } let rtmsg = RtmsgBuilder::default() .rtm_family(RtAddrFamily::Inet) .rtm_dst_len(0) .rtm_src_len(0) .rtm_tos(0) .rtm_table(RtTable::Unspec) .rtm_protocol(Rtprot::Unspec) .rtm_scope(RtScope::Universe) .rtm_type(Rtn::Unspec) .build()?; let recv = socket.send(Rtm::Getroute, NlmF::DUMP, NlPayload::Payload(rtmsg))?; for rtm_result in recv { let rtm = rtm_result?; if let NlTypeWrapper::Rtm(_) = rtm.nl_type() { parse_route_table(&ifs, rtm)?; } } Ok(()) } neli-0.7.4/src/attr.rs000064400000000000000000000070221046102023000126730ustar 00000000000000//! Shared attribute code for all types of netlink attributes. //! //! This module is relatively small right now and will eventually //! contain more code once type parameters in associated //! types defined in traits are stabilized. Due to `neli` being //! supported on stable and nightly, I cannot currently use //! this feature and have opted to define implementations of the //! trait separately for [`Rtattr`][crate::rtnl::Rtattr] and //! [`Nlattr`][crate::genl::Nlattr] types in the //! `rtnl.rs` and `genl.rs` modules respectively. use std::{io::Cursor, slice::Iter}; use crate::{ err::{DeError, SerError}, types::Buffer, FromBytes, FromBytesWithInput, FromBytesWithInputBorrowed, Size, ToBytes, }; /// Trait that defines shared operations for netlink attributes. /// Currently, this applies to generic netlink and routing netlink /// attributes. pub trait Attribute { /// Get the payload of the given attribute. /// /// Due to Rust's requirement that all elements of a [`Vec`] are of /// the same type, payloads are represented as a byte buffer so /// that nested attributes that contain multiple types for the /// payload can be type checked before serialization yet still /// contained all in the same top level attribute. fn payload(&self) -> &Buffer; /// Set the payload to a data type that implements [`ToBytes`] - /// this function will overwrite the current payload. /// /// This method serializes the `payload` parameter and stores /// the resulting byte buffer as the payload. fn set_payload

(&mut self, payload: &P) -> Result<(), SerError> where P: Size + ToBytes; /// Get an [`Nlattr`][crate::genl::Nlattr] payload as the /// provided type parameter, `R`. fn get_payload_as(&self) -> Result where R: FromBytes, { R::from_bytes(&mut Cursor::new(self.payload().as_ref())) } /// Get an [`Nlattr`][crate::genl::Nlattr] payload as the /// provided type parameter, `R`. fn get_payload_as_with_len(&self) -> Result where R: FromBytesWithInput, { R::from_bytes_with_input( &mut Cursor::new(self.payload().as_ref()), self.payload().len(), ) } /// Get an [`Nlattr`][crate::genl::Nlattr] payload as the /// provided type parameter, `R`. This method borrows the data instead /// of copying it. fn get_payload_as_with_len_borrowed<'a, R>(&'a self) -> Result where R: FromBytesWithInputBorrowed<'a, Input = usize>, { R::from_bytes_with_input( &mut Cursor::new(self.payload().as_ref()), self.payload().len(), ) } } /// Handle returned for traversing nested attribute structures pub enum AttrHandle<'a, O, I> { /// Owned vector Owned(O), /// Vector reference Borrowed(&'a [I]), } impl<'a, O, I> AttrHandle<'a, O, I> where O: AsRef<[I]>, { /// Create new [`AttrHandle`] pub fn new(owned: O) -> Self { AttrHandle::Owned(owned) } /// Create new borrowed [`AttrHandle`] pub fn new_borrowed(borrowed: &'a [I]) -> Self { AttrHandle::Borrowed(borrowed) } /// Pass back iterator over attributes pub fn iter(&self) -> Iter<'_, I> { self.get_attrs().iter() } /// Get the underlying owned value as a reference pub fn get_attrs(&self) -> &[I] { match *self { AttrHandle::Owned(ref o) => o.as_ref(), AttrHandle::Borrowed(b) => b, } } } neli-0.7.4/src/connector.rs000064400000000000000000000403121046102023000137120ustar 00000000000000//! Connector module for Linux Netlink connector messages. //! //! This module provides support for the Linux Netlink connector subsystem, //! which creates a communication channel between userspace programs and the kernel. //! It allows applications to receive notifications about various kernel events. //! //! This module currently provides full support for the Linux proc connector protocol, //! enabling the reception and handling of process lifecycle events such as creation, //! termination, exec, UID/GID/sid changes, tracing, name changes, and core dumps. //! //! ## Supported protocols //! At this time, only the proc connector (`PROC_CN`) protocol is fully implemented. //! //! ## Extensibility //! The implementation can be extended in two ways: //! 1. By defining additional types and logic in your own crate and using them with this module. //! 2. By using a `Vec` as a payload and manually parsing protocol messages to suit other connector protocols. //! //! This design allows both high-level ergonomic handling of proc events and low-level manual parsing for custom needs. use std::{io::Cursor, io::Read}; use derive_builder::{Builder, UninitializedFieldError}; use getset::Getters; use log::trace; use crate::{ self as neli, consts::connector::{CnMsgIdx, CnMsgVal, ProcEventType}, err::{DeError, MsgError, SerError}, FromBytes, FromBytesWithInput, Header, Size, ToBytes, }; /// Netlink connector message header and payload. #[derive( Builder, Getters, Clone, Debug, PartialEq, Eq, Size, ToBytes, FromBytesWithInput, Header, )] #[neli(from_bytes_bound = "P: Size + FromBytesWithInput")] #[builder(pattern = "owned")] pub struct CnMsg { /// Index of the connector (idx) #[getset(get = "pub")] idx: CnMsgIdx, /// Value (val) #[getset(get = "pub")] val: CnMsgVal, /// Sequence number #[builder(default)] #[getset(get = "pub")] seq: u32, /// Acknowledgement number #[builder(default)] #[getset(get = "pub")] ack: u32, /// Length of the payload #[builder( setter(skip), default = "self.payload.as_ref().ok_or_else(|| UninitializedFieldError::new(\"payload\"))?.unpadded_size() as _" )] #[getset(get = "pub")] len: u16, /// Flags #[builder(default)] #[getset(get = "pub")] flags: u16, /// Payload of the netlink message /// /// You can either use predefined types like `ProcCnMcastOp` or `ProcEventHeader`, /// a custom type defined by you or `Vec` for raw payload. #[neli(size = "len as usize")] #[neli(input = "input - Self::header_size()")] #[getset(get = "pub")] pub(crate) payload: P, } // -- proc connector structs -- /// Header for process event messages. #[derive(Debug, Size)] pub struct ProcEventHeader { /// The CPU on which the event occurred. pub cpu: u32, /// Nanosecond timestamp of the event. pub timestamp_ns: u64, /// The process event data. pub event: ProcEvent, } /// Ergonomic enum for process event data. #[derive(Debug, Size, Copy, Clone)] pub enum ProcEvent { /// Acknowledgement event, typically for PROC_EVENT_NONE. Ack { /// Error code (0 for success). err: u32, }, /// Fork event, triggered when a process forks. Fork { /// Parent process PID. parent_pid: i32, /// Parent process TGID (thread group ID). parent_tgid: i32, /// Child process PID. child_pid: i32, /// Child process TGID. child_tgid: i32, }, /// Exec event, triggered when a process calls exec(). Exec { /// Process PID. process_pid: i32, /// Process TGID. process_tgid: i32, }, /// UID change event, triggered when a process changes its UID. Uid { /// Process PID. process_pid: i32, /// Process TGID. process_tgid: i32, /// Real UID. ruid: u32, /// Effective UID. euid: u32, }, /// GID change event, triggered when a process changes its GID. Gid { /// Process PID. process_pid: i32, /// Process TGID. process_tgid: i32, /// Real GID. rgid: u32, /// Effective GID. egid: u32, }, /// SID change event, triggered when a process changes its session ID. Sid { /// Process PID. process_pid: i32, /// Process TGID. process_tgid: i32, }, /// Ptrace event, triggered when a process is traced. Ptrace { /// Process PID. process_pid: i32, /// Process TGID. process_tgid: i32, /// Tracer process PID. tracer_pid: i32, /// Tracer process TGID. tracer_tgid: i32, }, /// Comm event, triggered when a process changes its command name. Comm { /// Process PID. process_pid: i32, /// Process TGID. process_tgid: i32, /// Command name (null-terminated, max 16 bytes). comm: [u8; 16], }, /// Coredump event, triggered when a process dumps core. Coredump { /// Process PID. process_pid: i32, /// Process TGID. process_tgid: i32, /// Parent process PID. parent_pid: i32, /// Parent process TGID. parent_tgid: i32, }, /// Exit event, triggered when a process exits. Exit { /// Process PID. process_pid: i32, /// Process TGID. process_tgid: i32, /// Exit code. exit_code: u32, /// Exit signal. exit_signal: u32, /// Parent process PID. parent_pid: i32, /// Parent process TGID. parent_tgid: i32, }, } impl From<&ProcEvent> for ProcEventType { fn from(ev: &ProcEvent) -> Self { match ev { ProcEvent::Ack { .. } => ProcEventType::None, ProcEvent::Fork { .. } => ProcEventType::Fork, ProcEvent::Exec { .. } => ProcEventType::Exec, ProcEvent::Uid { .. } => ProcEventType::Uid, ProcEvent::Gid { .. } => ProcEventType::Gid, ProcEvent::Sid { .. } => ProcEventType::Sid, ProcEvent::Ptrace { .. } => ProcEventType::Ptrace, ProcEvent::Comm { .. } => ProcEventType::Comm, ProcEvent::Coredump { .. } => ProcEventType::Coredump, ProcEvent::Exit { exit_code, .. } => { if *exit_code == 0 { ProcEventType::Exit } else { ProcEventType::NonzeroExit } } } } } impl ToBytes for ProcEventHeader { fn to_bytes(&self, buffer: &mut Cursor>) -> Result<(), SerError> { ProcEventType::from(&self.event).to_bytes(buffer)?; self.cpu.to_bytes(buffer)?; self.timestamp_ns.to_bytes(buffer)?; match self.event { ProcEvent::Ack { err } => { err.to_bytes(buffer)?; } ProcEvent::Fork { parent_pid, parent_tgid, child_pid, child_tgid, } => { parent_pid.to_bytes(buffer)?; parent_tgid.to_bytes(buffer)?; child_pid.to_bytes(buffer)?; child_tgid.to_bytes(buffer)?; } ProcEvent::Exec { process_pid, process_tgid, } => { process_pid.to_bytes(buffer)?; process_tgid.to_bytes(buffer)?; } ProcEvent::Uid { process_pid, process_tgid, ruid, euid, } => { process_pid.to_bytes(buffer)?; process_tgid.to_bytes(buffer)?; ruid.to_bytes(buffer)?; euid.to_bytes(buffer)?; } ProcEvent::Gid { process_pid, process_tgid, rgid, egid, } => { process_pid.to_bytes(buffer)?; process_tgid.to_bytes(buffer)?; rgid.to_bytes(buffer)?; egid.to_bytes(buffer)?; } ProcEvent::Sid { process_pid, process_tgid, } => { process_pid.to_bytes(buffer)?; process_tgid.to_bytes(buffer)?; } ProcEvent::Ptrace { process_pid, process_tgid, tracer_pid, tracer_tgid, } => { process_pid.to_bytes(buffer)?; process_tgid.to_bytes(buffer)?; tracer_pid.to_bytes(buffer)?; tracer_tgid.to_bytes(buffer)?; } ProcEvent::Comm { process_pid, process_tgid, comm, } => { process_pid.to_bytes(buffer)?; process_tgid.to_bytes(buffer)?; comm.to_bytes(buffer)?; } ProcEvent::Coredump { process_pid, process_tgid, parent_pid, parent_tgid, } => { process_pid.to_bytes(buffer)?; process_tgid.to_bytes(buffer)?; parent_pid.to_bytes(buffer)?; parent_tgid.to_bytes(buffer)?; } ProcEvent::Exit { process_pid, process_tgid, exit_code, exit_signal, parent_pid, parent_tgid, } => { process_pid.to_bytes(buffer)?; process_tgid.to_bytes(buffer)?; exit_code.to_bytes(buffer)?; exit_signal.to_bytes(buffer)?; parent_pid.to_bytes(buffer)?; parent_tgid.to_bytes(buffer)?; } }; Ok(()) } } impl FromBytesWithInput for ProcEventHeader { type Input = usize; fn from_bytes_with_input( buffer: &mut Cursor>, input: Self::Input, ) -> Result { let start = buffer.position(); trace!("Parsing ProcEventHeader at position {start} with input size {input}"); // Minimum size for header (16) + smallest event (ack: 4) is 20. if input < 16 || buffer.position() as usize + input > buffer.get_ref().as_ref().len() { return Err(DeError::InvalidInput(input)); } // Read header fields: what (u32), cpu (u32), timestamp_ns (u64) fn parse(buffer: &mut Cursor>) -> Result { let what_val = u32::from_bytes(buffer)?; let what = ProcEventType::from(what_val); let cpu = u32::from_bytes(buffer)?; let timestamp_ns = u64::from_bytes(buffer)?; let event = match what { ProcEventType::None => ProcEvent::Ack { err: u32::from_bytes(buffer)?, }, ProcEventType::Fork => ProcEvent::Fork { parent_pid: i32::from_bytes(buffer)?, parent_tgid: i32::from_bytes(buffer)?, child_pid: i32::from_bytes(buffer)?, child_tgid: i32::from_bytes(buffer)?, }, ProcEventType::Exec => ProcEvent::Exec { process_pid: i32::from_bytes(buffer)?, process_tgid: i32::from_bytes(buffer)?, }, ProcEventType::Uid => ProcEvent::Uid { process_pid: i32::from_bytes(buffer)?, process_tgid: i32::from_bytes(buffer)?, ruid: u32::from_bytes(buffer)?, euid: u32::from_bytes(buffer)?, }, ProcEventType::Gid => ProcEvent::Gid { process_pid: i32::from_bytes(buffer)?, process_tgid: i32::from_bytes(buffer)?, rgid: u32::from_bytes(buffer)?, egid: u32::from_bytes(buffer)?, }, ProcEventType::Sid => ProcEvent::Sid { process_pid: i32::from_bytes(buffer)?, process_tgid: i32::from_bytes(buffer)?, }, ProcEventType::Ptrace => ProcEvent::Ptrace { process_pid: i32::from_bytes(buffer)?, process_tgid: i32::from_bytes(buffer)?, tracer_pid: i32::from_bytes(buffer)?, tracer_tgid: i32::from_bytes(buffer)?, }, ProcEventType::Comm => { let process_pid = i32::from_bytes(buffer)?; let process_tgid = i32::from_bytes(buffer)?; let mut comm = [0u8; 16]; buffer.read_exact(&mut comm)?; ProcEvent::Comm { process_pid, process_tgid, comm, } } ProcEventType::Coredump => ProcEvent::Coredump { process_pid: i32::from_bytes(buffer)?, process_tgid: i32::from_bytes(buffer)?, parent_pid: i32::from_bytes(buffer)?, parent_tgid: i32::from_bytes(buffer)?, }, ProcEventType::Exit | ProcEventType::NonzeroExit => ProcEvent::Exit { process_pid: i32::from_bytes(buffer)?, process_tgid: i32::from_bytes(buffer)?, exit_code: u32::from_bytes(buffer)?, exit_signal: u32::from_bytes(buffer)?, parent_pid: i32::from_bytes(buffer)?, parent_tgid: i32::from_bytes(buffer)?, }, ProcEventType::UnrecognizedConst(i) => { return Err(DeError::Msg(MsgError::new(format!( "Unrecognized Proc event type: {i} (raw value: {what_val})" )))); } }; Ok(ProcEventHeader { cpu, timestamp_ns, event, }) } let event = match parse(buffer) { Ok(ev) => ev, Err(e) => { buffer.set_position(start); return Err(e); } }; buffer.set_position(start + input as u64); // consume the entire len, because the kernel can pad the event data with zeros Ok(event) } } #[cfg(test)] mod tests { use super::*; fn build_endian_agnostic_response() -> Vec { let mut cursor = Cursor::new(vec![]); let msg = CnMsg { idx: CnMsgIdx::Proc, val: CnMsgVal::Proc, seq: 643, ack: 0, len: 40, flags: 0, payload: ProcEventHeader { cpu: 1, timestamp_ns: 2504390882488, event: ProcEvent::Exec { process_pid: 5759, process_tgid: 5759, }, }, }; msg.to_bytes(&mut cursor).unwrap(); cursor.into_inner() } #[test] fn parse_static_proc_header() { let mut cursor = Cursor::new(build_endian_agnostic_response()); let len = cursor.get_ref().len(); let msg: CnMsg = CnMsg::from_bytes_with_input(&mut cursor, len).unwrap(); assert_eq!(msg.idx(), &CnMsgIdx::Proc); assert_eq!(msg.val(), &CnMsgVal::Proc); assert_eq!(msg.payload.cpu, 1); assert_eq!(msg.payload.timestamp_ns, 2504390882488); match &msg.payload.event { ProcEvent::Exec { process_pid, process_tgid, } => { assert_eq!(*process_pid, 5759); assert_eq!(*process_tgid, 5759); } _ => panic!("Expected Exec event"), } } #[test] fn parse_static_raw_data() { let mut cursor = Cursor::new(build_endian_agnostic_response()); let len = cursor.get_ref().len(); let msg: CnMsg> = CnMsg::from_bytes_with_input(&mut cursor, len).unwrap(); assert_eq!(msg.idx(), &CnMsgIdx::Proc); assert_eq!(msg.val(), &CnMsgVal::Proc); } } neli-0.7.4/src/consts/connector.rs000064400000000000000000000030331046102023000152220ustar 00000000000000use crate as neli; /// Values for `idx` in [`CnMsg`][crate::connector::CnMsg]. #[neli::neli_enum(serialized_type = "u32")] pub enum CnMsgIdx { Proc = libc::CN_IDX_PROC, Cifs = libc::CN_IDX_CIFS, W1 = libc::CN_W1_IDX, V86d = libc::CN_IDX_V86D, Bb = libc::CN_IDX_BB, Dst = libc::CN_DST_IDX, Dm = libc::CN_IDX_DM, Drbd = libc::CN_IDX_DRBD, Kvp = libc::CN_KVP_IDX, Vss = libc::CN_VSS_IDX, } /// Values for `val` in [`CnMsg`][crate::connector::CnMsg]. #[neli::neli_enum(serialized_type = "u32")] pub enum CnMsgVal { Proc = libc::CN_VAL_PROC, Cifs = libc::CN_VAL_CIFS, W1 = libc::CN_W1_VAL, V86dUvesafb = libc::CN_VAL_V86D_UVESAFB, Dst = libc::CN_DST_VAL, DmUserspaceLog = libc::CN_VAL_DM_USERSPACE_LOG, Drbd = libc::CN_VAL_DRBD, Kvp = libc::CN_KVP_VAL, Vss = libc::CN_VSS_VAL, } /// Process event type as reported by the kernel connector. #[neli::neli_enum(serialized_type = "u32")] pub enum ProcEventType { None = libc::PROC_EVENT_NONE, Fork = libc::PROC_EVENT_FORK, Exec = libc::PROC_EVENT_EXEC, Uid = libc::PROC_EVENT_UID, Gid = libc::PROC_EVENT_GID, Sid = libc::PROC_EVENT_SID, Ptrace = libc::PROC_EVENT_PTRACE, Comm = libc::PROC_EVENT_COMM, NonzeroExit = libc::PROC_EVENT_NONZERO_EXIT, Coredump = libc::PROC_EVENT_COREDUMP, Exit = libc::PROC_EVENT_EXIT, } /// Process event operations. #[neli::neli_enum(serialized_type = "u32")] pub enum ProcCnMcastOp { Listen = libc::PROC_CN_MCAST_LISTEN, Ignore = libc::PROC_CN_MCAST_IGNORE, } neli-0.7.4/src/consts/genl.rs000064400000000000000000000060771046102023000141700ustar 00000000000000use std::{io::Cursor, mem::size_of}; use neli_proc_macros::neli_enum; use crate::{ self as neli, consts::{ netfilter::{NfLogAttr, NfLogCfg}, nl::NlmsgerrAttr, }, err::{DeError, SerError}, FromBytes, Size, ToBytes, TypeSize, }; impl_trait!( /// Trait marking constants valid for use in /// [`Genlmsghdr`][crate::genl::Genlmsghdr] field, `cmd`. pub Cmd, u8, /// Wrapper valid for use with all values in the [`Genlmsghdr`] /// field, `cmd` CmdConsts, CtrlCmd ); /// Values for `cmd` in [`Genlmsghdr`][crate::genl::Genlmsghdr]. #[neli_enum(serialized_type = "u8")] pub enum CtrlCmd { Unspec = libc::CTRL_CMD_UNSPEC as u8, Newfamily = libc::CTRL_CMD_NEWFAMILY as u8, Delfamily = libc::CTRL_CMD_DELFAMILY as u8, Getfamily = libc::CTRL_CMD_GETFAMILY as u8, Newops = libc::CTRL_CMD_NEWOPS as u8, Delops = libc::CTRL_CMD_DELOPS as u8, Getops = libc::CTRL_CMD_GETOPS as u8, NewmcastGrp = libc::CTRL_CMD_NEWMCAST_GRP as u8, DelmcastGrp = libc::CTRL_CMD_DELMCAST_GRP as u8, GetmcastGrp = libc::CTRL_CMD_GETMCAST_GRP as u8, } impl_trait!( /// Marker trait for types usable in the /// [`Nlattr`][crate::genl::Nlattr] field, `nla_type` pub NlAttrType, u16, /// Wrapper that is usable with all values in the /// [`Nlattr`][crate::genl::Nlattr] field, `nla_type`. pub NlAttrTypeWrapper, CtrlAttr, CtrlAttrMcastGrp, NfLogAttr, NfLogCfg, Index, NlmsgerrAttr, ); /// Values for `nla_type` in [`Nlattr`][crate::genl::Nlattr] #[neli_enum(serialized_type = "u16")] pub enum CtrlAttr { Unspec = libc::CTRL_ATTR_UNSPEC as u16, FamilyId = libc::CTRL_ATTR_FAMILY_ID as u16, FamilyName = libc::CTRL_ATTR_FAMILY_NAME as u16, Version = libc::CTRL_ATTR_VERSION as u16, Hdrsize = libc::CTRL_ATTR_HDRSIZE as u16, Maxattr = libc::CTRL_ATTR_MAXATTR as u16, Ops = libc::CTRL_ATTR_OPS as u16, McastGroups = libc::CTRL_ATTR_MCAST_GROUPS as u16, } /// Values for `nla_type` in [`Nlattr`][crate::genl::Nlattr] #[neli_enum(serialized_type = "u16")] pub enum CtrlAttrMcastGrp { Unspec = libc::CTRL_ATTR_MCAST_GRP_UNSPEC as u16, Name = libc::CTRL_ATTR_MCAST_GRP_NAME as u16, Id = libc::CTRL_ATTR_MCAST_GRP_ID as u16, } /// Type representing attribute list types as indices #[derive(Debug, PartialEq, Eq, Clone, Copy, Size)] pub struct Index(u16); impl Index { fn is_unrecognized(self) -> bool { false } } impl TypeSize for Index { fn type_size() -> usize { size_of::() } } impl ToBytes for Index { fn to_bytes(&self, buffer: &mut Cursor>) -> Result<(), SerError> { self.0.to_bytes(buffer) } } impl FromBytes for Index { fn from_bytes(buffer: &mut Cursor>) -> Result { Ok(Index(u16::from_bytes(buffer)?)) } } impl From for u16 { fn from(i: Index) -> Self { i.0 } } impl From<&Index> for u16 { fn from(i: &Index) -> Self { i.0 } } impl From for Index { fn from(v: u16) -> Self { Index(v) } } neli-0.7.4/src/consts/macros.rs000064400000000000000000000152631046102023000145240ustar 00000000000000/// For generating a marker trait that flags a new enum as usable in a /// field that accepts a generic type. This way, the type parameter /// can be constrained by a trait bound to only accept enums that /// implement the marker trait. /// /// # Usage /// /// ``` /// use neli::neli_enum; /// /// /// Define an enum /// #[neli_enum(serialized_type = "u16")] /// pub enum MyFamilyEnum { /// One = 1, /// Two = 2, /// Three = 3 /// } /// /// /// Define another enum /// #[neli_enum(serialized_type = "u16")] /// pub enum MyOtherFamilyEnum { /// Four = 4, /// Five = 5, /// Six = 6, /// } /// /// /// Define a marker trait and implement it for MyFamilyEnum and /// /// MyOtherFamilyEnum. /// neli::impl_trait!( /// MyMarkerTrait, /// u16, /// MyFamilyWrapperType, /// MyFamilyEnum, /// MyOtherFamilyEnum /// ); /// ``` /// /// The result of the example above will be: /// * One enum called `MyFamilyEnum`. /// * Another called `MyOtherFamilyEnum`. /// * A marker trait called `MyMarkerTrait`. This can be used to /// constain type parameter so that only `MyFamilyEnum` and /// `MyOtherFamilyEnum` variants can be passed in as a value. /// * A wrapper enum called `MyFamilyWrapperType`. The definition is /// as follows: /// ``` /// enum MyFamilyEnum { /// One, /// Two, /// Three, /// } /// /// enum MyOtherFamilyEnum { /// Four, /// Five, /// Six, /// } /// /// enum MyFamilyWrapperType { /// MyFamilyEnum(MyFamilyEnum), /// MyOtherFamilyEnum(MyOtherFamilyEnum), /// } /// ``` /// If you are unsure of which type will be passed back, the wrapper /// type can be used to automatically determine this for you when /// deserializing and accept all values defined across both enums. #[macro_export] macro_rules! impl_trait { ( $(#[$outer:meta])* $vis_trait:vis $trait_name:ident, $to_from_ty:ty, $( #[$wrapper_outer:meta] )* $vis_enum:vis $wrapper_type:ident, $( $const_enum:ident ),+ $(,)? ) => { $(#[$outer])* $vis_trait trait $trait_name: PartialEq + Clone + From<$to_from_ty> + Into<$to_from_ty> + Copy + $crate::Size + $crate::TypeSize + $crate::FromBytes + $crate::ToBytes + std::fmt::Debug {} impl $trait_name for $to_from_ty {} $( impl $trait_name for $const_enum {} )+ #[derive(Debug, PartialEq, Eq, Clone, Copy)] $( #[$wrapper_outer] )* $vis_enum enum $wrapper_type { $( #[allow(missing_docs)] $const_enum($const_enum), )+ /// Constant could not be parsed into a type UnrecognizedConst($to_from_ty), } impl $crate::Size for $wrapper_type { fn unpadded_size(&self) -> usize { std::mem::size_of::<$to_from_ty>() } } impl $crate::TypeSize for $wrapper_type { fn type_size() -> usize { std::mem::size_of::<$to_from_ty>() } } impl $crate::ToBytes for $wrapper_type { fn to_bytes(&self, buffer: &mut std::io::Cursor>) -> Result<(), $crate::err::SerError> { Ok(match self { $( $wrapper_type::$const_enum(val) => val.to_bytes(buffer)?, )* $wrapper_type::UnrecognizedConst(val) => val.to_bytes(buffer)?, }) } } impl $crate::FromBytes for $wrapper_type { fn from_bytes(buffer: &mut std::io::Cursor>) -> Result { Ok($wrapper_type::from(<$to_from_ty as $crate::FromBytes>::from_bytes( buffer )?)) } } impl $trait_name for $wrapper_type {} $( impl From<$const_enum> for $wrapper_type { fn from(e: $const_enum) -> Self { $wrapper_type::$const_enum(e) } } )+ impl From<$wrapper_type> for $to_from_ty { fn from(w: $wrapper_type) -> Self { match w { $( $wrapper_type::$const_enum(inner) => inner.into(), )+ $wrapper_type::UnrecognizedConst(v) => v, } } } impl From<&$wrapper_type> for $to_from_ty { fn from(w: &$wrapper_type) -> Self { match w { $( $wrapper_type::$const_enum(inner) => inner.into(), )+ $wrapper_type::UnrecognizedConst(v) => *v, } } } impl From<$to_from_ty> for $wrapper_type { fn from(v: $to_from_ty) -> Self { $( let var = $const_enum::from(v); if !var.is_unrecognized() { return $wrapper_type::$const_enum(var); } )* $wrapper_type::UnrecognizedConst(v) } } }; } /// Implement a container for bit flag enums using the [`bitflags`][bitflags] crate. /// /// # Usage /// /// ``` /// use neli::neli_enum; /// /// neli::impl_flags!( /// pub MyFlags: u16 { /// ThisFlag = 1, /// ThatFlag = 2, /// } /// ); /// ``` /// /// See [here][bitflags] for the methods that are autogenerated by `bitflags` on /// the struct. #[macro_export] macro_rules! impl_flags { ($(#[$outer:meta])* $vis:vis $name:ident: $bin_type:ty { $($(#[$inner:ident $($tt:tt)*])* $var:ident = $const:expr),* $(,)? }) => { #[derive(Debug, Clone, Copy, Eq, PartialEq, neli_proc_macros::Size, neli_proc_macros::FromBytes, neli_proc_macros::ToBytes)] $(#[$outer])* $vis struct $name($bin_type); bitflags::bitflags! { impl $name: $bin_type { $( $(#[$inner $($tt)*])* #[allow(missing_docs)] const $var = $const; )* } } impl From<$bin_type> for $name { fn from(bin: $bin_type) -> Self { $name::from_bits_truncate(bin) } } impl From<$name> for $bin_type { fn from(ty: $name) -> Self { ty.bits() } } impl $crate::TypeSize for $name { fn type_size() -> usize { <$bin_type as $crate::TypeSize>::type_size() } } }; } neli-0.7.4/src/consts/mod.rs000064400000000000000000000070751046102023000140210ustar 00000000000000//! # High level notes //! //! The contents of this module are generated mostly by macros, which //! implement the appropriate traits necessary to both be //! serialized/deserialized and also provide an additional level of //! type safety when constructing netlink packets. Some of the traits //! generated in this module allow netlink structures to implement //! trait bounds assuring that only compatible constant-based enums //! are allowed to be passed in as parameters. The macros are //! exported; you can use them too! See [`impl_trait`][crate::impl_trait] //! and [`impl_flags`][crate::impl_flags] for more details. //! //! Note that most of these constants come from the Linux kernel //! headers, which can be found in `/usr/include/linux` on many //! distros. You can also see `man 3 netlink`, `man 7 netlink`, //! and `man 7 rtnetlink` for more information. //! //! # Design decisions //! //! * Macros are exported so that these conventions are extensible and //! usable for data types implemented by the user in the case of new //! netlink families (which is supported by the protocol). In this //! case, there is no way in which I can support every custom netlink //! family but my aim is to make this library as flexible as possible //! so that it is painless to hook your custom netlink data type into //! the existing library support. //! * Enums are used so that: //! * Values can be checked based on a finite number of inputs as //! opposed to the range of whatever integer data type C defines as //! the struct member type. This makes it easier to catch garbage //! responses and corruption when an invalid netlink message is sent //! to the kernel. //! * Only the enum or an enum implementing a marker trait in the //! case of type parameters can be used in the appropriate places //! when constructing netlink messages. This takes guess work out of //! which constants can be used where. Netlink documentation is not //! always complete and sometimes takes a bit of trial and error //! sending messages to the kernel to figure out if you are using //! the correct constants. This setup should let you know at compile //! time if you are doing something you should not be doing. //! * `UnrecognizedVariant` is included in each enum because //! completeness cannot be guaranteed for every constant for every //! protocol. This allows you to inspect the integer value returned //! and if you are sure that it is correct, you can use it. If it is //! a garbage value, this can also be useful for error reporting. #[macro_use] mod macros; /// Constants related to netlink connector interface pub mod connector; /// Constants related to generic netlink pub mod genl; /// Constants related to netfilter netlink integration pub mod netfilter; /// Constants related to generic netlink top level headers pub mod nl; /// Constants related to rtnetlink pub mod rtnl; /// Constants related to netlink socket operations pub mod socket; /// Reimplementation of alignto macro in C pub fn alignto(len: usize) -> usize { (len + libc::NLA_ALIGNTO as usize - 1) & !(libc::NLA_ALIGNTO as usize - 1) } /// Max supported message length for netlink messages supported by /// the kernel. pub const MAX_NL_LENGTH: usize = 32768; #[cfg(test)] mod test { use super::genl::*; #[test] fn test_generated_enum_into_from() { let unspec: u8 = CtrlCmd::Unspec.into(); assert_eq!(unspec, libc::CTRL_CMD_UNSPEC as u8); let unspec_variant = CtrlCmd::from(libc::CTRL_CMD_UNSPEC as u8); assert_eq!(unspec_variant, CtrlCmd::Unspec); } } neli-0.7.4/src/consts/netfilter.rs000064400000000000000000000057461046102023000152410ustar 00000000000000//! Constants for netfilter related protocols //! //! Note that this doesn't cover everything yet, both the list of //! types and variants in enums will be added over time. use neli_proc_macros::neli_enum; use crate as neli; /// Attributes inside a netfilter log packet message. /// /// These are send by the kernel and describe a logged packet. #[neli_enum(serialized_type = "u16")] pub enum NfLogAttr { PacketHdr = libc::NFULA_PACKET_HDR as u16, Mark = libc::NFULA_MARK as u16, Timestamp = libc::NFULA_TIMESTAMP as u16, IfindexIndev = libc::NFULA_IFINDEX_INDEV as u16, IfindexOutdev = libc::NFULA_IFINDEX_OUTDEV as u16, IfindexPhyindev = libc::NFULA_IFINDEX_PHYSINDEV as u16, IfindexPhyoutdev = libc::NFULA_IFINDEX_PHYSOUTDEV as u16, Hwaddr = libc::NFULA_HWADDR as u16, Payload = libc::NFULA_PAYLOAD as u16, Prefix = libc::NFULA_PREFIX as u16, Uid = libc::NFULA_UID as u16, Seq = libc::NFULA_SEQ as u16, SeqGlobal = libc::NFULA_SEQ_GLOBAL as u16, Gid = libc::NFULA_GID as u16, Hwtype = libc::NFULA_HWTYPE as u16, Hwheader = libc::NFULA_HWHEADER as u16, Hwlen = libc::NFULA_HWLEN as u16, Ct = libc::NFULA_CT as u16, CtInfo = libc::NFULA_CT_INFO as u16, } /// Configuration attributes for netfilter logging. #[neli_enum(serialized_type = "u16")] pub enum NfLogCfg { Cmd = libc::NFULA_CFG_CMD as u16, Mode = libc::NFULA_CFG_MODE as u16, NlBufSize = libc::NFULA_CFG_NLBUFSIZ as u16, Timeout = libc::NFULA_CFG_TIMEOUT as u16, QThresh = libc::NFULA_CFG_QTHRESH as u16, Flags = libc::NFULA_CFG_FLAGS as u16, } const fn nfnl_msg_type(subsys: u8, msg: u8) -> u16 { ((subsys as u16) << 8) | (msg as u16) } /// Messages related to the netfilter netlink protocols. /// /// These appear on the /// [`NlFamily::Netfilter`][crate::consts::socket::NlFamily::Netfilter] /// sockets. #[neli_enum(serialized_type = "u16")] pub enum NetfilterMsg { // TODO: Docs here /// A logged packet, going from kernel to userspace. LogPacket = nfnl_msg_type(libc::NFNL_SUBSYS_ULOG as u8, libc::NFULNL_MSG_PACKET as u8), // TODO: Docs here /// A logging configuration request, going from userspace to kernel. LogConfig = nfnl_msg_type(libc::NFNL_SUBSYS_ULOG as u8, libc::NFULNL_MSG_CONFIG as u8), } impl_trait! { /// Parameters for the [`NfLogCfg::Cmd`]. pub LogCfgCmd, u8, /// Wrapper that is valid anywhere that accepts a value /// implementing the [`LogCfgCmd`] trait pub LogCfgCmdWrapper, LogCmd } /// Command value for the [`NfLogCfg::Cmd`]. #[neli_enum(serialized_type = "u8")] pub enum LogCmd { Bind = libc::NFULNL_CFG_CMD_BIND as u8, Unbind = libc::NFULNL_CFG_CMD_UNBIND as u8, PfBind = libc::NFULNL_CFG_CMD_PF_BIND as u8, PfUnbind = libc::NFULNL_CFG_CMD_PF_UNBIND as u8, } /// Copy mode of the logged packets. #[neli_enum(serialized_type = "u8")] pub enum LogCopyMode { None = libc::NFULNL_COPY_NONE as u8, Meta = libc::NFULNL_COPY_META as u8, Packet = libc::NFULNL_COPY_PACKET as u8, } neli-0.7.4/src/consts/nl.rs000064400000000000000000000045171046102023000136510ustar 00000000000000use neli_proc_macros::neli_enum; use crate::{ self as neli, consts::{netfilter::NetfilterMsg, rtnl::Rtm}, }; impl_trait!( /// Trait marking constants valid for use in /// [`Nlmsghdr`][crate::nl::Nlmsghdr] field, `nl_type`. pub NlType, u16, /// Wrapper that is usable with all values in /// [`Nlmsghdr`][crate::nl::Nlmsghdr] field, /// `nl_type`. pub NlTypeWrapper, Nlmsg, GenlId, Rtm, NetfilterMsg ); /// Values for `nl_type` in [`Nlmsghdr`][crate::nl::Nlmsghdr] #[neli_enum(serialized_type = "u16")] pub enum Nlmsg { Noop = libc::NLMSG_NOOP as u16, Error = libc::NLMSG_ERROR as u16, Done = libc::NLMSG_DONE as u16, Overrun = libc::NLMSG_OVERRUN as u16, } /// Values for `nl_type` in [`Nlmsghdr`][crate::nl::Nlmsghdr] #[neli_enum(serialized_type = "u16")] pub enum GenlId { Ctrl = libc::GENL_ID_CTRL as u16, #[cfg(target_env = "gnu")] VfsDquot = libc::GENL_ID_VFS_DQUOT as u16, #[cfg(target_env = "gnu")] Pmcraid = libc::GENL_ID_PMCRAID as u16, } impl_flags!( #[allow(missing_docs)] pub NlmF: u16 { /// This flag is required for all kernel requests REQUEST = libc::NLM_F_REQUEST as u16, MULTI = libc::NLM_F_MULTI as u16, ACK = libc::NLM_F_ACK as u16, ECHO = libc::NLM_F_ECHO as u16, DUMP_INTR = libc::NLM_F_DUMP_INTR as u16, DUMP_FILTERED = libc::NLM_F_DUMP_FILTERED as u16, ROOT = libc::NLM_F_ROOT as u16, MATCH = libc::NLM_F_MATCH as u16, ATOMIC = libc::NLM_F_ATOMIC as u16, DUMP = libc::NLM_F_DUMP as u16, REPLACE = libc::NLM_F_REPLACE as u16, EXCL = libc::NLM_F_EXCL as u16, CREATE = libc::NLM_F_CREATE as u16, APPEND = libc::NLM_F_APPEND as u16, CAPPED = libc::NLM_F_CAPPED as u16, ACK_TLVS = libc::NLM_F_ACK_TLVS as u16, } ); #[neli_enum(serialized_type = "u16")] pub enum NlmsgerrAttr { Unused = 0, /// Error message string (string) Msg = 1, /// Offset of the invalid attribute in the original /// message, counting from the beginning of the /// header (u32) Offset = 2, /// Arbitrary subsystem specific cookie to /// be used - in the success case - to identify a created /// object or operation or similar (binary) Cookie = 3, /// Policy for a rejected attribute Policy = 4, } neli-0.7.4/src/consts/rtnl.rs000064400000000000000000000352251046102023000142170ustar 00000000000000use neli_proc_macros::neli_enum; use crate as neli; /// Internet address families #[neli_enum(serialized_type = "libc::c_uchar")] pub enum Af { Inet = libc::AF_INET as libc::c_uchar, Inet6 = libc::AF_INET6 as libc::c_uchar, } /// General address families for sockets #[neli_enum(serialized_type = "u8")] pub enum RtAddrFamily { Unspecified = libc::AF_UNSPEC as u8, UnixOrLocal = libc::AF_UNIX as u8, Inet = libc::AF_INET as u8, Inet6 = libc::AF_INET6 as u8, Ipx = libc::AF_IPX as u8, Netlink = libc::AF_NETLINK as u8, X25 = libc::AF_X25 as u8, Ax25 = libc::AF_AX25 as u8, Atmpvc = libc::AF_ATMPVC as u8, Appletalk = libc::AF_APPLETALK as u8, Packet = libc::AF_PACKET as u8, Alg = libc::AF_ALG as u8, } /// `rtm_type` /// The results of a lookup from a route table #[neli_enum(serialized_type = "libc::c_uchar")] pub enum Rtn { Unspec = libc::RTN_UNSPEC, Unicast = libc::RTN_UNICAST, Local = libc::RTN_LOCAL, Broadcast = libc::RTN_BROADCAST, Anycast = libc::RTN_ANYCAST, Multicast = libc::RTN_MULTICAST, Blackhole = libc::RTN_BLACKHOLE, Unreachable = libc::RTN_UNREACHABLE, Prohibit = libc::RTN_PROHIBIT, Throw = libc::RTN_THROW, Nat = libc::RTN_NAT, Xresolve = libc::RTN_XRESOLVE, } /// `rtm_protocol` /// The origins of routes that are defined in the kernel #[neli_enum(serialized_type = "libc::c_uchar")] pub enum Rtprot { Unspec = libc::RTPROT_UNSPEC, Redirect = libc::RTPROT_REDIRECT, Kernel = libc::RTPROT_KERNEL, Boot = libc::RTPROT_BOOT, Static = libc::RTPROT_STATIC, } /// `rtm_scope` /// The distance between destinations #[neli_enum(serialized_type = "libc::c_uchar")] pub enum RtScope { Universe = libc::RT_SCOPE_UNIVERSE, Site = libc::RT_SCOPE_SITE, Link = libc::RT_SCOPE_LINK, Host = libc::RT_SCOPE_HOST, Nowhere = libc::RT_SCOPE_NOWHERE, } /// `rt_class_t` /// Reserved route table identifiers #[neli_enum(serialized_type = "libc::c_uchar")] pub enum RtTable { Unspec = libc::RT_TABLE_UNSPEC, Compat = libc::RT_TABLE_COMPAT, Default = libc::RT_TABLE_DEFAULT, Main = libc::RT_TABLE_MAIN, Local = libc::RT_TABLE_LOCAL, } impl_trait!( /// Marker trait for [`Rtattr`][crate::rtnl::Rtattr] field, /// `rta_type`. pub RtaType, libc::c_ushort, /// Wrapper that is usable for all values in /// [`Rtattr`][crate::rtnl::Rtattr] field, `rta_type` pub RtaTypeWrapper, Ifla, Ifa, Rta, Tca, Nda, IflaInfo, IflaVlan, IflaVlanQos, ); /// Enum usable with [`Rtattr`][crate::rtnl::Rtattr] field, /// `rta_type`. /// Values are interface information message attributes. Used with /// [`Ifinfomsg`][crate::rtnl::Ifinfomsg]. #[neli_enum(serialized_type = "libc::c_ushort")] pub enum Ifla { Unspec = libc::IFLA_UNSPEC, Address = libc::IFLA_ADDRESS, Broadcast = libc::IFLA_BROADCAST, Ifname = libc::IFLA_IFNAME, Mtu = libc::IFLA_MTU, Link = libc::IFLA_LINK, Qdisc = libc::IFLA_QDISC, Stats = libc::IFLA_STATS, Cost = libc::IFLA_COST, Priority = libc::IFLA_PRIORITY, Master = libc::IFLA_MASTER, Wireless = libc::IFLA_WIRELESS, Protinfo = libc::IFLA_PROTINFO, Txqlen = libc::IFLA_TXQLEN, Map = libc::IFLA_MAP, Weight = libc::IFLA_WEIGHT, Operstate = libc::IFLA_OPERSTATE, Linkmode = libc::IFLA_LINKMODE, Linkinfo = libc::IFLA_LINKINFO, NetNsPid = libc::IFLA_NET_NS_PID, Ifalias = libc::IFLA_IFALIAS, NumVf = libc::IFLA_NUM_VF, VfinfoList = libc::IFLA_VFINFO_LIST, Stats64 = libc::IFLA_STATS64, VfPorts = libc::IFLA_VF_PORTS, PortSelf = libc::IFLA_PORT_SELF, AfSpec = libc::IFLA_AF_SPEC, Group = libc::IFLA_GROUP, NetNsFd = libc::IFLA_NET_NS_FD, ExtMask = libc::IFLA_EXT_MASK, Promiscuity = libc::IFLA_PROMISCUITY, NumTxQueues = libc::IFLA_NUM_TX_QUEUES, NumRxQueues = libc::IFLA_NUM_RX_QUEUES, Carrier = libc::IFLA_CARRIER, PhysPortId = libc::IFLA_PHYS_PORT_ID, CarrierChanges = libc::IFLA_CARRIER_CHANGES, PhysSwitchId = libc::IFLA_PHYS_SWITCH_ID, LinkNetnsid = libc::IFLA_LINK_NETNSID, PhysPortName = libc::IFLA_PHYS_PORT_NAME, ProtoDown = libc::IFLA_PROTO_DOWN, GsoMaxSegs = libc::IFLA_GSO_MAX_SEGS, GsoMaxSize = libc::IFLA_GSO_MAX_SIZE, Pad = libc::IFLA_PAD, Xdp = libc::IFLA_XDP, Event = libc::IFLA_EVENT, NewNetnsid = libc::IFLA_NEW_NETNSID, IfNetnsid = libc::IFLA_IF_NETNSID, CarrierUpCount = libc::IFLA_CARRIER_UP_COUNT, CarrierDownCount = libc::IFLA_CARRIER_DOWN_COUNT, NewIfindex = libc::IFLA_NEW_IFINDEX, MinMtu = libc::IFLA_MIN_MTU, MaxMtu = libc::IFLA_MAX_MTU, PropList = libc::IFLA_PROP_LIST, AltIfname = libc::IFLA_ALT_IFNAME, PermAddress = libc::IFLA_PERM_ADDRESS, ProtoDownReason = libc::IFLA_PROTO_DOWN_REASON, } /// Enum usable with [`Rtattr`][crate::rtnl::Rtattr] field, /// `rta_type`. /// Values are nested attributes to IFLA_LINKMODE. #[neli_enum(serialized_type = "libc::c_ushort")] pub enum IflaInfo { Unspec = libc::IFLA_INFO_UNSPEC, Kind = libc::IFLA_INFO_KIND, Data = libc::IFLA_INFO_DATA, Xstats = libc::IFLA_INFO_XSTATS, SlaveKind = libc::IFLA_INFO_SLAVE_KIND, SlaveData = libc::IFLA_INFO_SLAVE_DATA, } /// Enum usable with [`Rtattr`][crate::rtnl::Rtattr] field, /// `rta_type`. /// Values are interface information message attributes. Used with /// [`Ifinfomsg`][crate::rtnl::Ifinfomsg]. #[neli_enum(serialized_type = "libc::c_ushort")] pub enum IflaVlan { Unspec = 0, Id = 1, Flags = 2, EgressQos = 3, IngressQos = 4, Protocol = 5, } /// Enum usable with [`Rtattr`][crate::rtnl::Rtattr] field, /// `rta_type`. /// Values are interface information message attributes. Used with /// [`Ifinfomsg`][crate::rtnl::Ifinfomsg]. #[neli_enum(serialized_type = "libc::c_ushort")] pub enum IflaVlanQos { Unspec = 0, Mapping = 1, } /// rtnetlink-related values for `nl_type` in /// [`Nlmsghdr`][crate::nl::Nlmsghdr]. #[neli_enum(serialized_type = "u16")] #[allow(missing_docs)] pub enum Rtm { Newlink = libc::RTM_NEWLINK, Dellink = libc::RTM_DELLINK, Getlink = libc::RTM_GETLINK, Setlink = libc::RTM_SETLINK, Newaddr = libc::RTM_NEWADDR, Deladdr = libc::RTM_DELADDR, Getaddr = libc::RTM_GETADDR, Newroute = libc::RTM_NEWROUTE, Delroute = libc::RTM_DELROUTE, Getroute = libc::RTM_GETROUTE, Newneigh = libc::RTM_NEWNEIGH, Delneigh = libc::RTM_DELNEIGH, Getneigh = libc::RTM_GETNEIGH, Newrule = libc::RTM_NEWRULE, Delrule = libc::RTM_DELRULE, Getrule = libc::RTM_GETRULE, Newqdisc = libc::RTM_NEWQDISC, Delqdisc = libc::RTM_DELQDISC, Getqdisc = libc::RTM_GETQDISC, Newtclass = libc::RTM_NEWTCLASS, Deltclass = libc::RTM_DELTCLASS, Gettclass = libc::RTM_GETTCLASS, Newtfilter = libc::RTM_NEWTFILTER, Deltfilter = libc::RTM_DELTFILTER, Gettfilter = libc::RTM_GETTFILTER, Newaction = libc::RTM_NEWACTION, Delaction = libc::RTM_DELACTION, Getaction = libc::RTM_GETACTION, Newprefix = libc::RTM_NEWPREFIX, Getmulticast = libc::RTM_GETMULTICAST, Getanycast = libc::RTM_GETANYCAST, Newneightbl = libc::RTM_NEWNEIGHTBL, Getneightbl = libc::RTM_GETNEIGHTBL, Setneightbl = libc::RTM_SETNEIGHTBL, Newnduseropt = libc::RTM_NEWNDUSEROPT, Newaddrlabel = libc::RTM_NEWADDRLABEL, Deladdrlabel = libc::RTM_DELADDRLABEL, Getaddrlabel = libc::RTM_GETADDRLABEL, Getdcb = libc::RTM_GETDCB, Setdcb = libc::RTM_SETDCB, Newnetconf = libc::RTM_NEWNETCONF, Getnetconf = libc::RTM_GETNETCONF, Newmdb = libc::RTM_NEWMDB, Delmdb = libc::RTM_DELMDB, Getmdb = libc::RTM_GETMDB, Newnsid = libc::RTM_NEWNSID, Delnsid = libc::RTM_DELNSID, Getnsid = libc::RTM_GETNSID, } /// Enum usable with [`Rtattr`][crate::rtnl::Rtattr] field, /// `rta_type`. /// Values are routing message attributes. Used with /// [`Rtmsg`][crate::rtnl::Rtmsg]. #[neli_enum(serialized_type = "libc::c_ushort")] pub enum Rta { Unspec = libc::RTA_UNSPEC, Dst = libc::RTA_DST, Src = libc::RTA_SRC, Iif = libc::RTA_IIF, Oif = libc::RTA_OIF, Gateway = libc::RTA_GATEWAY, Priority = libc::RTA_PRIORITY, Prefsrc = libc::RTA_PREFSRC, Metrics = libc::RTA_METRICS, Multipath = libc::RTA_MULTIPATH, Protoinfo = libc::RTA_PROTOINFO, // no longer used in Linux Flow = libc::RTA_FLOW, Cacheinfo = libc::RTA_CACHEINFO, Session = libc::RTA_SESSION, // no longer used in Linux MpAlgo = libc::RTA_MP_ALGO, // no longer used in Linux Table = libc::RTA_TABLE, Mark = libc::RTA_MARK, MfcStats = libc::RTA_MFC_STATS, #[cfg(target_env = "gnu")] Via = libc::RTA_VIA, #[cfg(target_env = "gnu")] Newdst = libc::RTA_NEWDST, #[cfg(target_env = "gnu")] Pref = libc::RTA_PREF, #[cfg(target_env = "gnu")] EncapType = libc::RTA_ENCAP_TYPE, #[cfg(target_env = "gnu")] Encap = libc::RTA_ENCAP, #[cfg(target_env = "gnu")] Expires = libc::RTA_EXPIRES, #[cfg(target_env = "gnu")] Pad = libc::RTA_PAD, #[cfg(target_env = "gnu")] Uid = libc::RTA_UID, #[cfg(target_env = "gnu")] TtlPropagate = libc::RTA_TTL_PROPAGATE, } /// Enum usable with [`Rtattr`][crate::rtnl::Rtattr] field, /// `rta_type`. /// Values specify queuing discipline attributes. Used with /// [`Tcmsg`][crate::rtnl::Tcmsg]. #[neli_enum(serialized_type = "libc::c_ushort")] pub enum Tca { Unspec = libc::TCA_UNSPEC, Kind = libc::TCA_KIND, Options = libc::TCA_OPTIONS, Stats = libc::TCA_STATS, Xstats = libc::TCA_XSTATS, Rate = libc::TCA_RATE, Fcnt = libc::TCA_FCNT, Stats2 = libc::TCA_STATS2, Stab = libc::TCA_STAB, } /// Enum usable with [`Rtattr`][crate::rtnl::Rtattr] field, /// `rta_type`. /// Values specify neighbor table attributes #[neli_enum(serialized_type = "libc::c_ushort")] pub enum Nda { Unspec = libc::NDA_UNSPEC, Dst = libc::NDA_DST, Lladdr = libc::NDA_LLADDR, Cacheinfo = libc::NDA_CACHEINFO, Probes = libc::NDA_PROBES, Vlan = libc::NDA_VLAN, Port = libc::NDA_PORT, Vni = libc::NDA_VNI, Ifindex = libc::NDA_IFINDEX, #[cfg(target_env = "gnu")] Master = libc::NDA_MASTER, #[cfg(target_env = "gnu")] LinkNetnsid = libc::NDA_LINK_NETNSID, #[cfg(target_env = "gnu")] SrcVni = libc::NDA_SRC_VNI, } /// Interface types #[neli_enum(serialized_type = "libc::c_ushort")] pub enum Arphrd { Netrom = libc::ARPHRD_NETROM, Ether = libc::ARPHRD_ETHER, Eether = libc::ARPHRD_EETHER, AX25 = libc::ARPHRD_AX25, Pronet = libc::ARPHRD_PRONET, Chaos = libc::ARPHRD_CHAOS, Ieee802 = libc::ARPHRD_IEEE802, Arcnet = libc::ARPHRD_ARCNET, Appletlk = libc::ARPHRD_APPLETLK, Dlci = libc::ARPHRD_DLCI, Atm = libc::ARPHRD_APPLETLK, Metricom = libc::ARPHRD_METRICOM, Ieee1394 = libc::ARPHRD_IEEE1394, Eui64 = libc::ARPHRD_EUI64, Infiniband = libc::ARPHRD_INFINIBAND, Loopback = libc::ARPHRD_LOOPBACK, // Possibly more types here - need to look into ARP more Void = libc::ARPHRD_VOID, None = libc::ARPHRD_NONE, } /// Enum usable with [`Rtattr`][crate::rtnl::Rtattr] field, /// `rta_type`. /// Values are interface address message attributes. Used with /// [`Ifaddrmsg`][crate::rtnl::Ifaddrmsg]. #[allow(missing_docs)] #[neli_enum(serialized_type = "u16")] pub enum Ifa { Unspec = libc::IFA_UNSPEC, Address = libc::IFA_ADDRESS, Local = libc::IFA_LOCAL, Label = libc::IFA_LABEL, Broadcast = libc::IFA_BROADCAST, Anycast = libc::IFA_ANYCAST, Cacheinfo = libc::IFA_CACHEINFO, Multicast = libc::IFA_MULTICAST, Flags = libc::IFA_FLAGS, } impl_flags!( /// Values for `ifi_flags` in /// [`Ifinfomsg`][crate::rtnl::Ifinfomsg]. pub Iff: libc::c_uint { UP = libc::IFF_UP as libc::c_uint, BROADCAST = libc::IFF_BROADCAST as libc::c_uint, DEBUG = libc::IFF_DEBUG as libc::c_uint, LOOPBACK = libc::IFF_LOOPBACK as libc::c_uint, POINTOPOINT = libc::IFF_POINTOPOINT as libc::c_uint, RUNNING = libc::IFF_RUNNING as libc::c_uint, NOARP = libc::IFF_NOARP as libc::c_uint, PROMISC = libc::IFF_PROMISC as libc::c_uint, NOTRAILERS = libc::IFF_NOTRAILERS as libc::c_uint, ALLMULTI = libc::IFF_ALLMULTI as libc::c_uint, MASTER = libc::IFF_MASTER as libc::c_uint, SLAVE = libc::IFF_SLAVE as libc::c_uint, MULTICAST = libc::IFF_MULTICAST as libc::c_uint, PORTSEL = libc::IFF_PORTSEL as libc::c_uint, AUTOMEDIA = libc::IFF_AUTOMEDIA as libc::c_uint, DYNAMIC = libc::IFF_DYNAMIC as libc::c_uint, LOWERUP = libc::IFF_LOWER_UP as libc::c_uint, DORMANT = libc::IFF_DORMANT as libc::c_uint, ECHO = libc::IFF_ECHO as libc::c_uint, // Possibly more types here - need to look into private flags for interfaces } ); impl_flags!( /// Interface address flags pub IfaF: u8 { SECONDARY = libc::IFA_F_SECONDARY as u8, TEMPORARY = libc::IFA_F_TEMPORARY as u8, NODAD = libc::IFA_F_NODAD as u8, OPTIMISTIC = libc::IFA_F_OPTIMISTIC as u8, DADFAILED = libc::IFA_F_DADFAILED as u8, HOMEADDRESS = libc::IFA_F_HOMEADDRESS as u8, DEPRECATED = libc::IFA_F_DEPRECATED as u8, TENTATIVE = libc::IFA_F_TENTATIVE as u8, PERMANENT = libc::IFA_F_PERMANENT as u8, } ); impl_flags!( /// `rtm_flags` /// Flags for rtnetlink messages pub RtmF: libc::c_uint { NOTIFY = libc::RTM_F_NOTIFY, CLONED = libc::RTM_F_CLONED, EQUALIZE = libc::RTM_F_EQUALIZE, PREFIX = libc::RTM_F_PREFIX, #[cfg(target_env = "gnu")] LOOKUPTABLE = libc::RTM_F_LOOKUP_TABLE, #[cfg(target_env = "gnu")] FIBMATCH = libc::RTM_F_FIB_MATCH, } ); impl_flags!( /// Arp neighbor cache entry states #[allow(missing_docs)] pub Nud: u16 { NONE = libc::NUD_NONE, INCOMPLETE = libc::NUD_INCOMPLETE, REACHABLE = libc::NUD_REACHABLE, STALE = libc::NUD_STALE, DELAY = libc::NUD_DELAY, PROBE = libc::NUD_PROBE, FAILED = libc::NUD_FAILED, NOARP = libc::NUD_NOARP, PERMANENT = libc::NUD_PERMANENT, } ); impl_flags!( /// Arp neighbor cache entry flags #[allow(missing_docs)] pub Ntf: u8 { USE = libc::NTF_USE, SELF = libc::NTF_SELF, MASTER = libc::NTF_MASTER, PROXY = libc::NTF_PROXY, #[cfg(target_env = "gnu")] EXT_LEARNED = libc::NTF_EXT_LEARNED, #[cfg(target_env = "gnu")] OFFLOADED = libc::NTF_OFFLOADED, ROUTER = libc::NTF_ROUTER, } ); impl_flags!( /// Vlan flags #[allow(missing_docs)] pub VlanFlags: u8 { REORDER_HDR = 0x1, GVRP = 0x2, LOOSE_BINDING = 0x4, MVRP = 0x8, BRIDGE_BINDING = 0x10, } ); neli-0.7.4/src/consts/socket.rs000064400000000000000000000034161046102023000145250ustar 00000000000000use neli_proc_macros::neli_enum; use crate as neli; /// General address families for sockets #[neli_enum(serialized_type = "libc::c_int")] pub enum AddrFamily { UnixOrLocal = libc::AF_UNIX, Inet = libc::AF_INET, Inet6 = libc::AF_INET6, Ipx = libc::AF_IPX, Netlink = libc::AF_NETLINK, X25 = libc::AF_X25, Ax25 = libc::AF_AX25, Atmpvc = libc::AF_ATMPVC, Appletalk = libc::AF_APPLETALK, Packet = libc::AF_PACKET, Alg = libc::AF_ALG, } /// Values for `nl_family` in `NlSocket` #[neli_enum(serialized_type = "libc::c_int")] pub enum NlFamily { Route = libc::NETLINK_ROUTE, Unused = libc::NETLINK_UNUSED, Usersock = libc::NETLINK_USERSOCK, Firewall = libc::NETLINK_FIREWALL, SockOrInetDiag = libc::NETLINK_SOCK_DIAG, Nflog = libc::NETLINK_NFLOG, Xfrm = libc::NETLINK_XFRM, Selinux = libc::NETLINK_SELINUX, Iscsi = libc::NETLINK_ISCSI, Audit = libc::NETLINK_AUDIT, FibLookup = libc::NETLINK_FIB_LOOKUP, Connector = libc::NETLINK_CONNECTOR, Netfilter = libc::NETLINK_NETFILTER, Ip6Fw = libc::NETLINK_IP6_FW, Dnrtmsg = libc::NETLINK_DNRTMSG, KobjectUevent = libc::NETLINK_KOBJECT_UEVENT, Generic = libc::NETLINK_GENERIC, Scsitransport = libc::NETLINK_SCSITRANSPORT, Ecryptfs = libc::NETLINK_ECRYPTFS, Rdma = libc::NETLINK_RDMA, Crypto = libc::NETLINK_CRYPTO, } impl_flags!( /// Flags to be used in [NlSocket][crate::socket::NlSocket::recv] calls. pub Msg: u32 { CMSG_CLOEXEC = libc::MSG_CMSG_CLOEXEC as u32, DONTWAIT = libc::MSG_DONTWAIT as u32, ERRQUEUE = libc::MSG_ERRQUEUE as u32, OOB = libc::MSG_OOB as u32, PEEK = libc::MSG_PEEK as u32, TRUNC = libc::MSG_TRUNC as u32, WAITALL = libc::MSG_WAITALL as u32, } ); neli-0.7.4/src/err.rs000064400000000000000000000500101046102023000125040ustar 00000000000000//! This is the module that contains the error types used in `neli` //! //! There are five main types: //! * [`Nlmsgerr`] - an application error //! returned from netlink as a packet. //! * [`RouterError`] - errors returned by //! [`NlRouter`][crate::router::synchronous::NlRouter]. //! * [`SocketError`] - errors returned by //! [`NlSocketHandle`][crate::socket::synchronous::NlSocketHandle]. //! * [`DeError`] - error while deserializing //! * [`SerError`] - error while serializing //! //! # Design decisions //! All errors implement [`std::error::Error`] in an attempt to allow //! them to be used in conjunction with [`Result`] for easier error //! management even at the protocol error level. use std::{ error::Error, fmt::{self, Debug, Display}, io::{self, Cursor, ErrorKind}, str::Utf8Error, string::FromUtf8Error, sync::Arc, }; use derive_builder::{Builder, UninitializedFieldError}; use getset::Getters; use crate::{ self as neli, consts::nl::{NlType, NlmF, NlmsgerrAttr}, genl::{AttrTypeBuilderError, GenlmsghdrBuilderError, NlattrBuilderError}, nl::{Nlmsghdr, NlmsghdrBuilderError}, rtnl::{ IfaddrmsgBuilderError, IfinfomsgBuilderError, NdaCacheinfoBuilderError, NdmsgBuilderError, RtattrBuilderError, RtgenmsgBuilderError, RtmsgBuilderError, TcmsgBuilderError, }, types::{Buffer, GenlBuffer}, FromBytes, FromBytesWithInput, Header, Size, ToBytes, TypeSize, }; /// A special struct that represents the contents of an ACK /// returned at the application level. #[derive(Builder, Getters, Clone, Debug, PartialEq, Eq, Size, ToBytes, FromBytes)] #[neli(header_bound = "T: TypeSize")] #[neli(from_bytes_bound = "T: NlType")] #[builder(pattern = "owned")] pub struct NlmsghdrAck { /// Length of the netlink message #[getset(get = "pub")] nl_len: u32, /// Type of the netlink message #[getset(get = "pub")] nl_type: T, /// Flags indicating properties of the request or response #[getset(get = "pub")] nl_flags: NlmF, /// Sequence number for netlink protocol #[getset(get = "pub")] nl_seq: u32, /// ID of the netlink destination for requests and source for /// responses. #[getset(get = "pub")] nl_pid: u32, } impl NlmsghdrAck { /// Create a typed ACK from an ACK that can represent all types. pub fn to_typed(self) -> Result, RouterError> where T: NlType, { Ok(NlmsghdrAckBuilder::default() .nl_len(self.nl_len) .nl_type(T::from(self.nl_type)) .nl_flags(self.nl_flags) .nl_seq(self.nl_seq) .nl_pid(self.nl_pid) .build()?) } } /// A special struct that represents the contents of an error /// returned at the application level. #[derive(Builder, Getters, Clone, Debug, PartialEq, Eq, Size, ToBytes, FromBytes, Header)] #[neli(header_bound = "T: TypeSize")] #[neli(from_bytes_bound = "T: NlType + TypeSize")] #[neli(from_bytes_bound = "P: FromBytesWithInput")] #[builder(build_fn(skip))] #[builder(pattern = "owned")] pub struct NlmsghdrErr { /// Length of the netlink message #[getset(get = "pub")] #[builder(setter(skip))] nl_len: u32, /// Type of the netlink message #[getset(get = "pub")] nl_type: T, /// Flags indicating properties of the request or response #[getset(get = "pub")] nl_flags: NlmF, /// Sequence number for netlink protocol #[getset(get = "pub")] nl_seq: u32, /// ID of the netlink destination for requests and source for /// responses. #[getset(get = "pub")] nl_pid: u32, /// Payload of netlink message #[neli(input = "nl_len as usize - Self::header_size()")] #[getset(get = "pub")] nl_payload: P, } impl NlmsghdrErrBuilder where T: NlType, P: Size + FromBytesWithInput, { /// Build [`NlmsghdrErr`]. pub fn build(self) -> Result, NlmsghdrErrBuilderError> { let nl_type = self.nl_type.ok_or_else(|| { NlmsghdrErrBuilderError::from(UninitializedFieldError::new("nl_type")) })?; let nl_flags = self.nl_flags.unwrap_or(NlmF::empty()); let nl_seq = self.nl_seq.unwrap_or(0); let nl_pid = self.nl_pid.unwrap_or(0); let nl_payload = self.nl_payload.ok_or_else(|| { NlmsghdrErrBuilderError::from(UninitializedFieldError::new("nl_payload")) })?; let mut nl = NlmsghdrErr { nl_len: 0, nl_type, nl_flags, nl_seq, nl_pid, nl_payload, }; nl.nl_len = nl.padded_size() as u32; Ok(nl) } } impl NlmsghdrErr { /// Create a typed error from an error that can represent all types. pub fn to_typed(self) -> Result, RouterError> where T: NlType, P: Size + FromBytesWithInput, { Ok(NlmsghdrErrBuilder::default() .nl_type(T::from(self.nl_type)) .nl_flags(self.nl_flags) .nl_seq(self.nl_seq) .nl_pid(self.nl_pid) .nl_payload(P::from_bytes_with_input( &mut Cursor::new(self.nl_payload), self.nl_len as usize - Self::header_size(), )?) .build()?) } } /// Struct representing netlink packets containing errors #[derive(Builder, Getters, Clone, Debug, PartialEq, Eq, Size, FromBytesWithInput, ToBytes)] #[neli(from_bytes_bound = "M: Size + FromBytes")] #[builder(pattern = "owned")] pub struct Nlmsgerr { /// Error code #[builder(default = "0")] #[getset(get = "pub")] error: libc::c_int, /// Packet header for request that failed #[getset(get = "pub")] #[neli(skip_debug)] nlmsg: M, #[neli(input = "input - error.padded_size() - nlmsg.padded_size()")] /// Contains attributes representing the extended ACK #[builder(default = "GenlBuffer::new()")] #[getset(get = "pub")] ext_ack: GenlBuffer, } impl Display for Nlmsgerr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", io::Error::from_raw_os_error(-self.error)) } } impl Error for Nlmsgerr where M: Debug {} impl Nlmsgerr> { /// Create a typed error from an error that can represent all types. pub fn to_typed(self) -> Result>, RouterError> where T: NlType, P: Size + FromBytesWithInput, { Ok(NlmsgerrBuilder::default() .error(self.error) .nlmsg(self.nlmsg.to_typed()?) .build()?) } } impl Nlmsgerr> { /// Create a typed ACK from an ACK that can represent all types. pub fn to_typed(self) -> Result>, RouterError> where T: NlType, { Ok(NlmsgerrBuilder::default() .error(self.error) .nlmsg(self.nlmsg.to_typed()?) .build()?) } } #[derive(Debug)] #[allow(missing_docs)] pub enum BuilderError { #[allow(missing_docs)] Nlmsghdr(NlmsghdrBuilderError), #[allow(missing_docs)] Nlmsgerr(NlmsgerrBuilderError), #[allow(missing_docs)] NlmsghdrErr(NlmsghdrErrBuilderError), #[allow(missing_docs)] Genlmsghdr(GenlmsghdrBuilderError), #[allow(missing_docs)] Nlattr(NlattrBuilderError), #[allow(missing_docs)] AttrType(AttrTypeBuilderError), #[allow(missing_docs)] Ifinfomsg(IfinfomsgBuilderError), #[allow(missing_docs)] Ifaddrmsg(IfaddrmsgBuilderError), #[allow(missing_docs)] Rtgenmsg(RtgenmsgBuilderError), #[allow(missing_docs)] Rtmsg(RtmsgBuilderError), #[allow(missing_docs)] Ndmsg(NdmsgBuilderError), #[allow(missing_docs)] NdaCacheinfo(NdaCacheinfoBuilderError), #[allow(missing_docs)] Tcmsg(TcmsgBuilderError), #[allow(missing_docs)] Rtattr(RtattrBuilderError), #[allow(missing_docs)] NlmsghdrAck(NlmsghdrAckBuilderError), } impl Error for BuilderError {} impl Display for BuilderError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { BuilderError::Nlmsghdr(err) => write!(f, "{err}"), BuilderError::Nlmsgerr(err) => write!(f, "{err}"), BuilderError::NlmsghdrErr(err) => write!(f, "{err}"), BuilderError::Genlmsghdr(err) => write!(f, "{err}"), BuilderError::Nlattr(err) => write!(f, "{err}"), BuilderError::AttrType(err) => write!(f, "{err}"), BuilderError::Ifinfomsg(err) => write!(f, "{err}"), BuilderError::Ifaddrmsg(err) => write!(f, "{err}"), BuilderError::Rtgenmsg(err) => write!(f, "{err}"), BuilderError::Rtmsg(err) => write!(f, "{err}"), BuilderError::Ndmsg(err) => write!(f, "{err}"), BuilderError::NdaCacheinfo(err) => write!(f, "{err}"), BuilderError::Tcmsg(err) => write!(f, "{err}"), BuilderError::Rtattr(err) => write!(f, "{err}"), BuilderError::NlmsghdrAck(err) => write!(f, "{err}"), } } } impl From for BuilderError { fn from(e: NlmsghdrBuilderError) -> Self { BuilderError::Nlmsghdr(e) } } impl From for BuilderError { fn from(e: NlmsgerrBuilderError) -> Self { BuilderError::Nlmsgerr(e) } } impl From for BuilderError { fn from(e: NlmsghdrErrBuilderError) -> Self { BuilderError::NlmsghdrErr(e) } } impl From for BuilderError { fn from(e: GenlmsghdrBuilderError) -> Self { BuilderError::Genlmsghdr(e) } } impl From for BuilderError { fn from(e: NlattrBuilderError) -> Self { BuilderError::Nlattr(e) } } impl From for BuilderError { fn from(e: AttrTypeBuilderError) -> Self { BuilderError::AttrType(e) } } impl From for BuilderError { fn from(e: IfinfomsgBuilderError) -> Self { BuilderError::Ifinfomsg(e) } } impl From for BuilderError { fn from(e: IfaddrmsgBuilderError) -> Self { BuilderError::Ifaddrmsg(e) } } impl From for BuilderError { fn from(e: RtgenmsgBuilderError) -> Self { BuilderError::Rtgenmsg(e) } } impl From for BuilderError { fn from(e: RtmsgBuilderError) -> Self { BuilderError::Rtmsg(e) } } impl From for BuilderError { fn from(e: NdmsgBuilderError) -> Self { BuilderError::Ndmsg(e) } } impl From for BuilderError { fn from(e: NdaCacheinfoBuilderError) -> Self { BuilderError::NdaCacheinfo(e) } } impl From for BuilderError { fn from(e: TcmsgBuilderError) -> Self { BuilderError::Tcmsg(e) } } impl From for BuilderError { fn from(e: RtattrBuilderError) -> Self { BuilderError::Rtattr(e) } } impl From for BuilderError { fn from(e: NlmsghdrAckBuilderError) -> Self { BuilderError::NlmsghdrAck(e) } } /// Sendable, clonable error that can be sent across channels in the router infrastructure /// to provide typed errors to all receivers indicating what went wrong. #[derive(Clone, Debug)] pub enum RouterError { /// Arbitrary message Msg(MsgError), /// errno indicating what went wrong in an IO error. Io(ErrorKind), /// Deserialization error. De(DeError), /// Error from socket infrastructure. Socket(SocketError), /// An error packet sent back by netlink. Nlmsgerr(Nlmsgerr>), /// A bad sequence number or PID was received. BadSeqOrPid(Nlmsghdr), /// No ack was received when /// [`NlmF::Ack`][crate::consts::nl::NlmF] was specified in the /// request. NoAck, /// An ack was received when /// [`NlmF::Ack`][crate::consts::nl::NlmF] was not specified in the /// request. UnexpectedAck, /// A channel has closed and message processing cannot continue. ClosedChannel, } impl RouterError { /// Create a new arbitrary error message. pub fn new(d: D) -> Self where D: Display, { RouterError::Msg(MsgError::new(d.to_string())) } } impl RouterError { /// Convert to typed router error from a router error that can represent all types. pub fn to_typed(self) -> Result, RouterError> where T: NlType, P: Size + FromBytesWithInput, { match self { RouterError::Msg(msg) => Ok(RouterError::Msg(msg)), RouterError::Io(kind) => Ok(RouterError::Io(kind)), RouterError::De(err) => Ok(RouterError::De(err)), RouterError::Socket(err) => Ok(RouterError::Socket(err)), RouterError::Nlmsgerr(err) => Ok(RouterError::Nlmsgerr(err.to_typed()?)), RouterError::BadSeqOrPid(msg) => Ok(RouterError::BadSeqOrPid(msg.to_typed()?)), RouterError::NoAck => Ok(RouterError::NoAck), RouterError::UnexpectedAck => Ok(RouterError::UnexpectedAck), RouterError::ClosedChannel => Ok(RouterError::ClosedChannel), } } } impl Display for RouterError where T: Debug, P: Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RouterError::Msg(msg) => write!(f, "{msg}"), RouterError::Io(kind) => write!(f, "IO error: {kind}"), RouterError::De(err) => write!(f, "Deserialization failed: {err}"), RouterError::Socket(err) => write!(f, "Socket error: {err}"), RouterError::Nlmsgerr(msg) => { write!(f, "Application error was returned by netlink: {msg:?}") } RouterError::BadSeqOrPid(msg) => { write!(f, "A bad sequence number or PID was received: {msg:?}") } RouterError::NoAck => write!(f, "No ACK received"), RouterError::UnexpectedAck => write!(f, "ACK received when none was expected"), RouterError::ClosedChannel => { write!(f, "A channel required for message processing closed") } } } } impl From for RouterError where BuilderError: From, { fn from(e: E) -> Self { RouterError::new(BuilderError::from(e).to_string()) } } impl From for RouterError { fn from(e: DeError) -> Self { RouterError::De(e) } } impl From for RouterError { fn from(e: SocketError) -> Self { RouterError::Socket(e) } } impl From for RouterError { fn from(e: MsgError) -> Self { RouterError::Msg(e) } } impl Error for RouterError where T: Debug, P: Debug, { } /// General netlink error #[derive(Clone, Debug)] pub enum SocketError { /// Variant for [`String`]-based messages. Msg(MsgError), /// A serialization error. Ser(SerError), /// A deserialization error. De(DeError), /// IO error. Io(Arc), } impl From for SocketError { fn from(err: SerError) -> Self { SocketError::Ser(err) } } impl From for SocketError { fn from(err: DeError) -> Self { SocketError::De(err) } } impl From for SocketError { fn from(err: io::Error) -> Self { SocketError::Io(Arc::new(err)) } } impl From for SocketError where BuilderError: From, { fn from(err: E) -> Self { SocketError::new(BuilderError::from(err).to_string()) } } impl From for SocketError { fn from(e: MsgError) -> Self { SocketError::Msg(e) } } impl SocketError { /// Create new error from a data type implementing /// [`Display`] pub fn new(s: D) -> Self where D: Display, { SocketError::Msg(MsgError::new(s)) } } impl Display for SocketError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { SocketError::Msg(ref msg) => write!(f, "{msg}"), SocketError::Ser(ref err) => { write!(f, "Serialization error: {err}") } SocketError::De(ref err) => { write!(f, "Deserialization error: {err}") } SocketError::Io(ref err) => { write!(f, "IO error: {err}") } } } } impl Error for SocketError {} /// [`String`] or [`str`] UTF error. #[derive(Clone, Debug)] pub enum Utf8 { #[allow(missing_docs)] Str(Utf8Error), #[allow(missing_docs)] String(FromUtf8Error), } impl Display for Utf8 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Utf8::Str(e) => write!(f, "{e}"), Utf8::String(e) => write!(f, "{e}"), } } } /// Serialization error #[derive(Clone, Debug)] pub enum SerError { /// Abitrary error message. Msg(MsgError), /// IO error. Io(ErrorKind), /// String UTF conversion error. Utf8(Utf8), } impl SerError { /// Create a new error with the given message as description. pub fn new(msg: D) -> Self where D: Display, { SerError::Msg(MsgError::new(msg)) } } impl Display for SerError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { SerError::Msg(ref s) => write!(f, "{s}"), SerError::Io(ref err) => write!(f, "IO error: {err}"), SerError::Utf8(ref err) => write!(f, "UTF error: {err}"), } } } impl Error for SerError {} impl From for SerError { fn from(err: io::Error) -> Self { SerError::Io(err.kind()) } } impl From for SerError { fn from(err: Utf8Error) -> Self { SerError::Utf8(Utf8::Str(err)) } } impl From for SerError { fn from(err: FromUtf8Error) -> Self { SerError::Utf8(Utf8::String(err)) } } impl From for SerError { fn from(e: MsgError) -> Self { SerError::Msg(e) } } /// Deserialization error #[derive(Clone, Debug)] pub enum DeError { /// Abitrary error message. Msg(MsgError), /// IO error Io(ErrorKind), /// String UTF conversion error. Utf8(Utf8), /// Invalid input parameter for [`FromBytesWithInput`]. InvalidInput(usize), } impl DeError { /// Create new error from a type implementing /// [`Display`] pub fn new(s: D) -> Self where D: Display, { DeError::Msg(MsgError::new(s)) } } impl Display for DeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { DeError::Msg(s) => write!(f, "{s}"), DeError::Utf8(err) => write!(f, "UTF8 error: {err}"), DeError::Io(err) => write!(f, "IO error: {err}"), DeError::InvalidInput(input) => write!(f, "Invalid input was provided: {input}"), } } } impl Error for DeError {} impl From for DeError { fn from(err: io::Error) -> Self { DeError::Io(err.kind()) } } impl From for DeError { fn from(err: Utf8Error) -> Self { DeError::Utf8(Utf8::Str(err)) } } impl From for DeError { fn from(err: FromUtf8Error) -> Self { DeError::Utf8(Utf8::String(err)) } } impl From for DeError where BuilderError: From, { fn from(err: E) -> Self { DeError::new(BuilderError::from(err).to_string()) } } impl From for DeError { fn from(e: MsgError) -> Self { DeError::Msg(e) } } /// Arbitrary error message. #[derive(Clone, Debug)] pub struct MsgError(String); impl MsgError { /// Construct a new error message. pub fn new(d: D) -> Self where D: Display, { MsgError(d.to_string()) } } impl Display for MsgError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } } impl Error for MsgError {} neli-0.7.4/src/genl.rs000064400000000000000000000275341046102023000126600ustar 00000000000000//! This module contains generic netlink parsing data structures. //! This is all handled by the [`Genlmsghdr`] //! header struct which contains all of the information needed for //! the generic netlink layer. //! //! # Design decisions //! //! The generic netlink `attrs` field has been changed to a //! [`GenlBuffer`] of [`Nlattr`]s instead of the //! original [`Vec`][Vec] to allow simpler parsing at the top //! level when one [`Nlattr`] structure is not //! nested within another, a use case that is instead handled using //! [`AttrHandle`]. use std::io::Cursor; use derive_builder::{Builder, UninitializedFieldError}; use getset::Getters; use crate::{ self as neli, attr::{AttrHandle, Attribute}, consts::genl::{Cmd, NlAttrType}, err::{DeError, SerError}, types::{Buffer, GenlBuffer}, FromBytes, FromBytesWithInput, FromBytesWithInputBorrowed, Header, Size, ToBytes, TypeSize, }; /// Struct indicating that no user header is in the generic netlink packet. #[derive(Clone, Debug, PartialEq, Eq, Size, ToBytes, FromBytes)] pub struct NoUserHeader; impl TypeSize for NoUserHeader { fn type_size() -> usize { 0 } } /// Struct representing generic netlink header and payload #[derive( Builder, Getters, Clone, Debug, PartialEq, Eq, Size, ToBytes, FromBytesWithInput, Header, )] #[neli(to_bytes_bound = "C: Cmd")] #[neli(to_bytes_bound = "T: NlAttrType")] #[neli(from_bytes_bound = "C: Cmd + TypeSize")] #[neli(from_bytes_bound = "T: NlAttrType")] #[neli(header_bound = "C: TypeSize")] #[neli(from_bytes_bound = "H: TypeSize + FromBytes")] #[neli(header_bound = "H: TypeSize")] #[builder(pattern = "owned")] #[builder(build_fn(skip))] pub struct Genlmsghdr { /// Generic netlink message command #[getset(get = "pub")] cmd: C, /// Version of generic netlink family protocol #[getset(get = "pub")] version: u8, #[builder(setter(skip))] reserved: u16, /// User specific header to send with netlink packet; defaults to an empty type /// to maintain backwards compatibility #[getset(get = "pub")] header: H, /// Attributes included in generic netlink message #[getset(get = "pub")] #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")] attrs: GenlBuffer, } impl GenlmsghdrBuilder { /// Build a [`Genlmsghdr`]. pub fn build(self) -> Result, GenlmsghdrBuilderError> { let cmd = self .cmd .ok_or_else(|| GenlmsghdrBuilderError::from(UninitializedFieldError::new("cmd")))?; let version = self .version .ok_or_else(|| GenlmsghdrBuilderError::from(UninitializedFieldError::new("version")))?; let reserved = 0; let header = self.header.unwrap_or(NoUserHeader); let attrs = self.attrs.unwrap_or_default(); Ok(Genlmsghdr { cmd, version, reserved, header, attrs, }) } } impl GenlmsghdrBuilder { /// Build a [`Genlmsghdr`] with a required user header type. pub fn build_with_header(self) -> Result, GenlmsghdrBuilderError> { let cmd = self .cmd .ok_or_else(|| GenlmsghdrBuilderError::from(UninitializedFieldError::new("cmd")))?; let version = self .version .ok_or_else(|| GenlmsghdrBuilderError::from(UninitializedFieldError::new("version")))?; let reserved = 0; let header = self .header .ok_or_else(|| GenlmsghdrBuilderError::from(UninitializedFieldError::new("header")))?; let attrs = self.attrs.unwrap_or_default(); Ok(Genlmsghdr { cmd, version, reserved, header, attrs, }) } } /// The infomation packed into `nla_type` field of `nlattr` /// for the C data structure. #[derive(Builder, Getters, Debug, PartialEq, Eq, Clone)] #[builder(pattern = "owned")] pub struct AttrType { /// If true, the payload contains nested attributes. #[getset(get = "pub")] #[builder(default = "false")] nla_nested: bool, /// If true, the payload is in net work byte order. #[getset(get = "pub")] #[builder(default = "false")] nla_network_order: bool, /// Enum representing the type of the attribute payload #[getset(get = "pub")] nla_type: T, } impl Size for AttrType where T: Size, { fn unpadded_size(&self) -> usize { self.nla_type.unpadded_size() } } impl TypeSize for AttrType where T: TypeSize, { fn type_size() -> usize { T::type_size() } } impl ToBytes for AttrType where T: NlAttrType, { fn to_bytes(&self, buffer: &mut Cursor>) -> Result<(), SerError> { let int: u16 = self.into(); int.to_bytes(buffer) } } impl FromBytes for AttrType where T: NlAttrType, { fn from_bytes(buffer: &mut Cursor>) -> Result { let int = u16::from_bytes(buffer)?; Ok(AttrType::from(int)) } } impl From> for u16 where T: NlAttrType, { fn from(v: AttrType) -> Self { let mut int: u16 = v.nla_type.into(); int |= u16::from(v.nla_nested) << 15; int |= u16::from(v.nla_network_order) << 14; int } } impl<'a, T> From<&'a AttrType> for u16 where T: NlAttrType, { fn from(v: &'a AttrType) -> Self { let mut int: u16 = v.nla_type.into(); int |= u16::from(v.nla_nested) << 15; int |= u16::from(v.nla_network_order) << 14; int } } impl From for AttrType where T: NlAttrType, { fn from(int: u16) -> Self { AttrType { nla_nested: (int & 1 << 15) == (1 << 15), nla_network_order: (int & 1 << 14) == (1 << 14), nla_type: T::from(!(3 << 14) & int), } } } /// Struct representing netlink attributes and payloads #[derive(Builder, Getters, Clone, Debug, PartialEq, Eq, Size, FromBytes, ToBytes, Header)] #[neli(from_bytes_bound = "T: NlAttrType")] #[neli(from_bytes_bound = "P: FromBytesWithInput")] #[neli(to_bytes_bound = "T: NlAttrType")] #[neli(header_bound = "T: TypeSize")] #[neli(padding)] #[builder(pattern = "owned")] #[builder(build_fn(skip))] pub struct Nlattr { /// Length of the attribute header and payload together #[getset(get = "pub")] #[builder(setter(skip))] nla_len: u16, /// Type information for the netlink attribute #[getset(get = "pub")] nla_type: AttrType, /// Payload of the attribute - either parsed or a binary buffer #[neli( input = "(nla_len as usize).checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(nla_len as usize))?" )] #[getset(get = "pub")] nla_payload: P, } impl NlattrBuilder where T: Size, P: Size + ToBytes, { /// Build [`Nlattr`]. pub fn build(self) -> Result, NlattrBuilderError> { let nla_type = self .nla_type .ok_or_else(|| NlattrBuilderError::from(UninitializedFieldError::new("nla_type")))?; let nla_payload = self .nla_payload .ok_or_else(|| NlattrBuilderError::from(UninitializedFieldError::new("nla_payload")))?; let mut buffer = Cursor::new(vec![0; nla_payload.unpadded_size()]); nla_payload.to_bytes(&mut buffer).map_err(|_| { NlattrBuilderError::ValidationError( "Could not convert payload to binary representation".to_string(), ) })?; let mut nlattr = Nlattr { nla_len: 0, nla_type, nla_payload: Buffer::from(buffer.into_inner()), }; nlattr.nla_len = nlattr.unpadded_size() as u16; Ok(nlattr) } } impl Nlattr where T: NlAttrType, { /// Builder method to add a nested attribute to the end of the payload. /// /// Use this to construct an attribute and nest attributes within it in one method chain. #[inline] pub fn nest(mut self, attr: &Nlattr) -> Result where TT: NlAttrType, P: ToBytes, { self.add_nested_attribute(attr)?; Ok(self) } /// Add a nested attribute to the end of the payload. fn add_nested_attribute(&mut self, attr: &Nlattr) -> Result<(), SerError> where TT: NlAttrType, P: ToBytes, { let mut buffer = Cursor::new(Vec::new()); self.nla_type.nla_nested = true; attr.to_bytes(&mut buffer)?; self.nla_payload.extend_from_slice(buffer.get_ref()); self.nla_len += buffer.get_ref().len() as u16; Ok(()) } /// Return an `AttrHandle` for attributes nested in the given attribute payload pub fn get_attr_handle(&self) -> Result, DeError> where R: NlAttrType, { Ok(AttrHandle::new(GenlBuffer::from_bytes_with_input( &mut Cursor::new(self.nla_payload.as_ref()), self.nla_payload.unpadded_size(), )?)) } } impl Attribute for Nlattr where T: NlAttrType, { fn payload(&self) -> &Buffer { &self.nla_payload } fn set_payload

(&mut self, payload: &P) -> Result<(), SerError> where P: Size + ToBytes, { let mut buffer = Cursor::new(Vec::new()); payload.to_bytes(&mut buffer)?; // Update Nlattr with new length self.nla_len -= self.nla_payload.unpadded_size() as u16; self.nla_len += buffer.get_ref().len() as u16; self.nla_payload = Buffer::from(buffer.into_inner()); Ok(()) } } /// Type representing a generic netlink attribute handle. pub type GenlAttrHandle<'a, T> = AttrHandle<'a, GenlBuffer, Nlattr>; impl<'a, T> GenlAttrHandle<'a, T> where T: NlAttrType, { /// Get the payload of an attribute as a handle for parsing /// nested attributes pub fn get_nested_attributes(&self, subattr: T) -> Result, DeError> where S: NlAttrType, { let attr = self .get_attribute(subattr) .ok_or_else(|| DeError::new("Couldn't find specified attribute"))?; Ok(AttrHandle::new(GenlBuffer::from_bytes_with_input( &mut Cursor::new(attr.nla_payload.as_ref()), attr.nla_payload.unpadded_size(), )?)) } /// Get nested attributes from a parsed handle pub fn get_attribute(&self, t: T) -> Option<&Nlattr> { self.get_attrs() .iter() .find(|item| item.nla_type.nla_type == t) } /// Parse binary payload as a type that implements [`FromBytes`]. pub fn get_attr_payload_as(&self, attr: T) -> Result where R: FromBytes, { match self.get_attribute(attr) { Some(a) => a.get_payload_as::(), _ => Err(DeError::new("Failed to find specified attribute")), } } /// Parse binary payload as a type that implements /// [`FromBytesWithInput`] pub fn get_attr_payload_as_with_len(&self, attr: T) -> Result where R: FromBytesWithInput, { match self.get_attribute(attr) { Some(a) => a.get_payload_as_with_len::(), _ => Err(DeError::new("Failed to find specified attribute")), } } /// Parse binary payload as a type that implements /// [`FromBytesWithInputBorrowed`] pub fn get_attr_payload_as_with_len_borrowed(&'a self, attr: T) -> Result where R: FromBytesWithInputBorrowed<'a, Input = usize>, { match self.get_attribute(attr) { Some(a) => a.get_payload_as_with_len_borrowed::(), _ => Err(DeError::new("Failed to find specified attribute")), } } } neli-0.7.4/src/iter.rs000064400000000000000000000034751046102023000126740ustar 00000000000000//! Module for iteration over netlink responses use std::{io::Cursor, marker::PhantomData}; use log::trace; use crate::{ consts::nl::NlType, err::SocketError, nl::Nlmsghdr, FromBytes, FromBytesWithInput, Size, }; /// Iterator over a single buffer received from a [`recv`][crate::socket::NlSocket::recv] /// call. pub struct NlBufferIter { buffer: Cursor, next_is_none: bool, data: PhantomData<(T, P)>, } impl NlBufferIter where B: AsRef<[u8]>, { #[cfg(any(feature = "sync", feature = "async"))] pub(crate) fn new(buffer: Cursor) -> Self { NlBufferIter { buffer, next_is_none: false, data: PhantomData, } } /// Optional method for parsing messages of varied types in the same buffer. Models /// the [`Iterator`] API. pub fn next_typed(&mut self) -> Option, SocketError>> where TT: NlType, PP: Size + FromBytesWithInput, { if self.buffer.position() as usize == self.buffer.get_ref().as_ref().len() || self.next_is_none { None } else { match Nlmsghdr::from_bytes(&mut self.buffer).map_err(SocketError::from) { Ok(msg) => { trace!("Message received: {msg:?}"); Some(Ok(msg)) } Err(e) => { self.next_is_none = true; Some(Err(e)) } } } } } impl Iterator for NlBufferIter where B: AsRef<[u8]>, T: NlType, P: Size + FromBytesWithInput, { type Item = Result, SocketError>; fn next(&mut self) -> Option { self.next_typed::() } } neli-0.7.4/src/lib.rs000064400000000000000000000520111046102023000124650ustar 00000000000000//! # neli: Type safety for netlink //! //! ## Rationale //! //! This crate aims to be a pure Rust implementation that defines //! the necessary constants and wraps them in enums to distinguish //! between various categories of constants in the context of netlink. //! //! ## The project is broken down into the following modules: //! * `attr` - This defines a generic interface for netlink attributes //! (both generic and routing netlink attributes). //! * `consts` - This is where all of the C-defined constants are //! wrapped into type safe enums for use in the library. //! * `err` - This module contains all of the protocol and //! library-level errors encountered in the code. //! * `genl` - This code provides parsing for the generic netlink //! * `iter` - This code handles iterating over received netlink //! packets. //! * `nl` - This is the top level netlink header code that handles //! the header that all netlink messages are encapsulated in. //! * `router` - High level API handling ACK and PID validation as well as automatic //! sequence number handling. //! * `rtnl` - Routing netlink subsystem of the netlink protocol. //! * `socket` - Lower level API for use in sending and receiving messages. //! * `types` - Wrapper data types used in the library primarily to represent parts //! of netlink messages. //! * `utils` - Data structures used for FFI and synchronization in socket operations. //! //! ## Design decisions //! //! This library has a range of APIs. Some APIs like [`NlSocket`][crate::socket::NlSocket] //! are basically just wrappers for syscalls, while higher level APIs like //! [`NlRouter`][crate::router::synchronous::NlRouter] provide features like ACK //! validation, socket PID validation, and sequence number handling. //! //! The goal of this library is completeness for handling netlink and //! am working to incorporate features that will make this library //! easier to use in all use cases. If you have a use case you //! would like to see supported, please open an issue on Github. //! //! ## Examples //! //! Examples of working code exist in the `examples/` subdirectory on //! Github. Run `cargo build --examples` to build the examples. //! //! Workflows usually follow a pattern of socket creation, and //! then either sending and receiving messages in request/response //! formats: //! //! ``` //! use std::error::Error; //! //! use neli::{ //! consts::{genl::*, nl::*, socket::*}, //! err::RouterError, //! genl::{Genlmsghdr, GenlmsghdrBuilder, Nlattr}, //! nl::{NlmsghdrBuilder, NlPayload}, //! router::synchronous::NlRouter, //! types::{Buffer, GenlBuffer}, //! utils::Groups, //! }; //! //! const GENL_VERSION: u8 = 1; //! //! fn request_response() -> Result<(), Box> { //! let (socket, _) = NlRouter::connect( //! NlFamily::Generic, //! None, //! Groups::empty(), //! )?; //! //! let recv = socket.send::<_, _, NlTypeWrapper, Genlmsghdr>( //! GenlId::Ctrl, //! NlmF::DUMP, //! NlPayload::Payload( //! GenlmsghdrBuilder::<_, CtrlAttr, _>::default() //! .cmd(CtrlCmd::Getfamily) //! .version(GENL_VERSION) //! .build()? //! ), //! )?; //! //! for msg in recv { //! let msg = msg?; //! // Do things with response here... //! } //! //! Ok(()) //! } //! ``` //! //! or a subscriptions to a stream of event notifications from netlink: //! //! ``` //! use std::error::Error; //! //! use neli::{ //! consts::{genl::*, nl::*, socket::*}, //! err::RouterError, //! genl::Genlmsghdr, //! router::synchronous::NlRouter, //! utils::Groups, //! }; //! //! fn subscribe_to_mcast() -> Result<(), Box> { //! let (s, multicast) = NlRouter::connect( //! NlFamily::Generic, //! None, //! Groups::empty(), //! )?; //! let id = s.resolve_nl_mcast_group( //! "my_family_name", //! "my_multicast_group_name", //! )?; //! s.add_mcast_membership(Groups::new_groups(&[id]))?; //! for next in multicast { //! // Do stuff here with parsed packets... //! //! // like printing a debug representation of them: //! println!("{:?}", next?); //! } //! //! Ok(()) //! } //! ``` //! //! ## Documentation //! //! Each module has been documented extensively to provide information //! on how to use the code contained in the module. Pull requests for //! documentation mistakes, updates, and rewording for clarity is a //! valuable contribution as this project aims to be as simple to use //! as possible. #![deny(missing_docs)] pub mod attr; pub mod connector; pub mod consts; pub mod err; pub mod genl; pub mod iter; pub mod nl; pub mod router; pub mod rtnl; pub mod socket; pub mod types; pub mod utils; use std::{ fmt::Debug, io::{Cursor, Read, Write}, marker::PhantomData, str, }; use byteorder::{BigEndian, NativeEndian, ReadBytesExt}; pub use neli_proc_macros::{neli_enum, FromBytes, FromBytesWithInput, Header, Size, ToBytes}; use crate::{ self as neli, consts::alignto, err::{DeError, SerError}, }; /// A trait defining methods that apply to all netlink data /// structures related to sizing of data types. pub trait Size { /// Size of the unpadded data structure. This will usually /// only be unaligned for variable length types like /// strings or byte buffers. fn unpadded_size(&self) -> usize; /// Get the size of the payload and align it to /// the required netlink byte alignment. fn padded_size(&self) -> usize { alignto(self.unpadded_size()) } } /// A trait defining methods that apply to constant-sized /// data types related to size. pub trait TypeSize { /// Get the size of a constant-sized data type. fn type_size() -> usize; } /// A trait defining a netlink data structure's conversion to /// a byte buffer. pub trait ToBytes: Debug { /// Takes a byte buffer and serializes the data structure into /// it. fn to_bytes(&self, buffer: &mut Cursor>) -> Result<(), SerError>; /// Pad a netlink message to the appropriate alignment. fn pad(&self, buffer: &mut Cursor>) -> Result<(), SerError> { let num_pad_bytes = alignto(buffer.position() as usize) - buffer.position() as usize; buffer.write_all(&[0; libc::NLA_ALIGNTO as usize][..num_pad_bytes])?; Ok(()) } } /// A trait defining how to convert from a byte buffer to a netlink /// data structure. pub trait FromBytes: Sized + Debug { /// Takes a byte buffer and returns the deserialized data /// structure. fn from_bytes(buffer: &mut Cursor>) -> Result; /// Strip padding from a netlink message. fn strip(buffer: &mut Cursor>) -> Result<(), DeError> { let num_strip_bytes = alignto(buffer.position() as usize) - buffer.position() as usize; buffer.read_exact(&mut [0; libc::NLA_ALIGNTO as usize][..num_strip_bytes])?; Ok(()) } } /// Takes an arbitrary input which serves as additional information /// for guiding the conversion from a byte buffer to a data /// structure. A common workflow is a data structure that has a size /// to determine how much more of the data in the byte buffer is /// part of a given data structure. pub trait FromBytesWithInput: Sized + Debug { /// The type of the additional input. type Input: Debug; /// Takes a byte buffer and an additional input and returns /// the deserialized data structure. fn from_bytes_with_input( buffer: &mut Cursor>, input: Self::Input, ) -> Result; /// Strip padding from a netlink message. fn strip(buffer: &mut Cursor>) -> Result<(), DeError> { let num_strip_bytes = alignto(buffer.position() as usize) - buffer.position() as usize; buffer.read_exact(&mut [0; libc::NLA_ALIGNTO as usize][..num_strip_bytes])?; Ok(()) } } /// Takes an arbitrary input which serves as additional information /// for guiding the conversion from a byte buffer to a data /// structure. A common workflow is a data structure that has a size /// to determine how much more of the data in the byte buffer is /// part of a given data structure. /// /// This trait borrows instead of copying. pub trait FromBytesWithInputBorrowed<'a>: Sized + Debug { /// The type of the additional input. type Input: Debug; /// Takes a byte buffer and an additional input and returns /// the deserialized data structure. fn from_bytes_with_input( buffer: &mut Cursor<&'a [u8]>, input: Self::Input, ) -> Result; /// Strip padding from a netlink message. fn strip(buffer: &mut Cursor<&'a [u8]>) -> Result<(), DeError> { let num_strip_bytes = alignto(buffer.position() as usize) - buffer.position() as usize; buffer.read_exact(&mut [0; libc::NLA_ALIGNTO as usize][..num_strip_bytes])?; Ok(()) } } /// Defined for data structures that contain a header. pub trait Header { /// Return the size in bytes of the data structure header. fn header_size() -> usize; } macro_rules! impl_nl_int { (impl__ $ty:ty) => { impl $crate::Size for $ty { fn unpadded_size(&self) -> usize { std::mem::size_of::<$ty>() } } impl $crate::TypeSize for $ty { fn type_size() -> usize { std::mem::size_of::<$ty>() } } }; ($ty:ty, $read_method:ident, $write_method:ident) => { impl_nl_int!(impl__ $ty); impl $crate::ToBytes for $ty { fn to_bytes(&self, buffer: &mut std::io::Cursor>) -> Result<(), $crate::err::SerError> { > as byteorder::WriteBytesExt>::$write_method(buffer, *self)?; Ok(()) } } impl $crate::FromBytes for $ty { fn from_bytes(buffer: &mut std::io::Cursor>) -> Result { Ok( as byteorder::ReadBytesExt>::$read_method(buffer)?) } } }; ($ty:ty, $read_method:ident, $write_method:ident, $endianness:ty) => { impl_nl_int!(impl__ $ty); impl $crate::ToBytes for $ty { fn to_bytes(&self, buffer: &mut std::io::Cursor>) -> Result<(), $crate::err::SerError> { > as byteorder::WriteBytesExt>::$write_method::<$endianness>(buffer, *self)?; Ok(()) } } impl $crate::FromBytes for $ty { fn from_bytes(buffer: &mut std::io::Cursor>) -> Result { Ok( as byteorder::ReadBytesExt>::$read_method::<$endianness>(buffer)?) } } } } impl_nl_int!(u8, read_u8, write_u8); impl_nl_int!(u16, read_u16, write_u16, NativeEndian); impl_nl_int!(u32, read_u32, write_u32, NativeEndian); impl_nl_int!(u64, read_u64, write_u64, NativeEndian); impl_nl_int!(u128, read_u128, write_u128, NativeEndian); impl_nl_int!(i8, read_i8, write_i8); impl_nl_int!(i16, read_i16, write_i16, NativeEndian); impl_nl_int!(i32, read_i32, write_i32, NativeEndian); impl_nl_int!(i64, read_i64, write_i64, NativeEndian); impl_nl_int!(i128, read_i128, write_i128, NativeEndian); impl_nl_int!(f32, read_f32, write_f32, NativeEndian); impl_nl_int!(f64, read_f64, write_f64, NativeEndian); impl Size for () { fn unpadded_size(&self) -> usize { 0 } } impl ToBytes for () { fn to_bytes(&self, _: &mut Cursor>) -> Result<(), SerError> { Ok(()) } } impl FromBytes for () { fn from_bytes(_: &mut Cursor>) -> Result { Ok(()) } } impl FromBytesWithInput for () { type Input = usize; fn from_bytes_with_input( _: &mut Cursor>, input: usize, ) -> Result { assert_eq!(input, 0); Ok(()) } } impl Size for PhantomData { fn unpadded_size(&self) -> usize { 0 } } impl TypeSize for PhantomData { fn type_size() -> usize { 0 } } impl ToBytes for PhantomData { fn to_bytes(&self, _: &mut Cursor>) -> Result<(), SerError> { Ok(()) } } impl FromBytes for PhantomData { fn from_bytes(_: &mut Cursor>) -> Result { Ok(PhantomData) } } impl Size for &'_ str { fn unpadded_size(&self) -> usize { self.len() + 1 } } impl ToBytes for &'_ str { fn to_bytes(&self, buffer: &mut Cursor>) -> Result<(), SerError> { buffer.write_all(self.as_bytes())?; buffer.write_all(&[0])?; Ok(()) } } impl<'a> FromBytesWithInputBorrowed<'a> for &'a str { type Input = usize; fn from_bytes_with_input(buffer: &mut Cursor<&'a [u8]>, input: usize) -> Result { let position = buffer.position() as usize; Ok(str::from_utf8( &buffer.get_ref()[position..position + input], )?) } } impl Size for String { fn unpadded_size(&self) -> usize { self.as_str().unpadded_size() } } impl ToBytes for String { fn to_bytes(&self, buffer: &mut Cursor>) -> Result<(), SerError> { self.as_str().to_bytes(buffer)?; Ok(()) } } impl FromBytesWithInput for String { type Input = usize; fn from_bytes_with_input( buffer: &mut Cursor>, input: usize, ) -> Result { let s = String::from_utf8( buffer.get_ref().as_ref() [buffer.position() as usize..buffer.position() as usize + input - 1] .to_vec(), )?; buffer.set_position(buffer.position() + input as u64); Ok(s) } } impl FromBytes for [u8; N] { fn from_bytes(buffer: &mut Cursor>) -> Result { let mut arr = [0u8; N]; buffer.read_exact(&mut arr)?; Ok(arr) } } impl Size for &'_ [u8] { fn unpadded_size(&self) -> usize { self.len() } } impl Size for [u8; N] { fn unpadded_size(&self) -> usize { N } } impl ToBytes for &'_ [u8] { fn to_bytes(&self, buffer: &mut Cursor>) -> Result<(), SerError> { buffer.write_all(self)?; Ok(()) } } impl ToBytes for [u8; N] { fn to_bytes(&self, buffer: &mut Cursor>) -> Result<(), SerError> { buffer.write_all(self)?; Ok(()) } } impl<'a> FromBytesWithInputBorrowed<'a> for &'a [u8] { type Input = usize; fn from_bytes_with_input(buffer: &mut Cursor<&'a [u8]>, input: usize) -> Result { let position = buffer.position() as usize; Ok(&buffer.get_ref()[position..position + input]) } } impl Size for Vec where T: Size, { fn unpadded_size(&self) -> usize { self.iter() .fold(0, |count, elem| count + elem.unpadded_size()) } } impl ToBytes for Vec where T: ToBytes, { fn to_bytes(&self, buffer: &mut Cursor>) -> Result<(), SerError> { for elem in self.iter() { elem.to_bytes(buffer)?; } Ok(()) } } impl FromBytesWithInput for Vec where T: FromBytes, { type Input = usize; fn from_bytes_with_input( buffer: &mut Cursor>, input: Self::Input, ) -> Result { if buffer.position() as usize + input > buffer.get_ref().as_ref().len() { return Err(DeError::InvalidInput(input)); } let mut vec = Vec::new(); let orig_pos = buffer.position(); loop { if buffer.position() as usize == orig_pos as usize + input { break; } match T::from_bytes(buffer) { Ok(elem) => vec.push(elem), Err(e) => { buffer.set_position(orig_pos); return Err(e); } } if buffer.position() as usize > orig_pos as usize + input { buffer.set_position(orig_pos); return Err(DeError::InvalidInput(input)); } } Ok(vec) } } #[derive(Copy, Debug, Clone, PartialEq, Eq, Size)] /// A `u64` data type that will always be serialized as big endian pub struct BeU64(u64); impl BeU64 { /// Create a big endian `u64` type from a native endian `u64` pub fn new(v: u64) -> Self { BeU64(v) } /// As native endian `u64` pub fn as_ne_u64(self) -> u64 { self.0 } } impl ToBytes for BeU64 { fn to_bytes(&self, buffer: &mut Cursor>) -> Result<(), SerError> { buffer.write_all(&self.0.to_be_bytes() as &[u8])?; Ok(()) } } impl FromBytes for BeU64 { fn from_bytes(buffer: &mut Cursor>) -> Result { Ok(BeU64(buffer.read_u64::()?)) } } #[cfg(test)] fn serialize(t: &T) -> Result, SerError> where T: ToBytes, { let mut buffer = Cursor::new(Vec::new()); t.to_bytes(&mut buffer)?; Ok(buffer.into_inner()) } #[cfg(test)] mod test { use super::*; use env_logger::init; use lazy_static::lazy_static; lazy_static! { static ref LOGGER: () = init(); } #[allow(clippy::no_effect)] pub fn setup() { *LOGGER; } #[test] fn test_nl_u8() { setup(); let v = 5u8; let ser_buffer = serialize(&v).unwrap(); assert_eq!(ser_buffer.as_slice()[0], v); let de = u8::from_bytes(&mut Cursor::new(&[5u8] as &[u8])).unwrap(); assert_eq!(de, 5) } #[test] fn test_nl_u16() { setup(); let v = 6000u16; let desired_buffer = v.to_ne_bytes(); let ser_buffer = serialize(&v).unwrap(); assert_eq!(ser_buffer.as_slice(), &desired_buffer); let de = u16::from_bytes(&mut Cursor::new(&v.to_ne_bytes() as &[u8])).unwrap(); assert_eq!(de, 6000); } #[test] fn test_nl_i32() { setup(); let v = 600_000i32; let desired_buffer = v.to_ne_bytes(); let ser_buffer = serialize(&v).unwrap(); assert_eq!(ser_buffer.as_slice(), &desired_buffer); let de = i32::from_bytes(&mut Cursor::new(&v.to_ne_bytes() as &[u8])).unwrap(); assert_eq!(de, 600_000); let v = -600_000i32; let desired_buffer = v.to_ne_bytes(); let ser_buffer = serialize(&v).unwrap(); assert_eq!(ser_buffer.as_slice(), &desired_buffer); let de = i32::from_bytes(&mut Cursor::new(&v.to_ne_bytes() as &[u8])).unwrap(); assert_eq!(de, -600_000) } #[test] fn test_nl_u32() { setup(); let v = 600_000u32; let desired_buffer = v.to_ne_bytes(); let ser_buffer = serialize(&v).unwrap(); assert_eq!(ser_buffer.as_slice(), &desired_buffer); let de = u32::from_bytes(&mut Cursor::new(&v.to_ne_bytes() as &[u8])).unwrap(); assert_eq!(de, 600_000) } #[test] fn test_nl_u64() { setup(); let v = 12_345_678_901_234u64; let desired_buffer = v.to_ne_bytes(); let ser_buffer = serialize(&v).unwrap(); assert_eq!(ser_buffer.as_slice(), &desired_buffer); let de = u64::from_bytes(&mut Cursor::new(&v.to_ne_bytes() as &[u8])).unwrap(); assert_eq!(de, 12_345_678_901_234); } #[test] fn test_nl_u128() { setup(); let v = 123_456_789_012_345_678_901_234_567_890_123_456_789u128; let desired_buffer = v.to_ne_bytes(); let ser_buffer = serialize(&v).unwrap(); assert_eq!(ser_buffer.as_slice(), &desired_buffer); let de = u128::from_bytes(&mut Cursor::new(&v.to_ne_bytes() as &[u8])).unwrap(); assert_eq!(de, 123_456_789_012_345_678_901_234_567_890_123_456_789); } #[test] fn test_nl_be_u64() { setup(); let v = 571_987_654u64; let desired_buffer = v.to_be_bytes(); let ser_buffer = serialize(&BeU64(v)).unwrap(); assert_eq!(ser_buffer.as_slice(), &desired_buffer); let de = BeU64::from_bytes(&mut Cursor::new(&v.to_be_bytes() as &[u8])).unwrap(); assert_eq!(de, BeU64(571_987_654)); } #[test] fn test_nl_vec() { setup(); let vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; let ser_buffer = serialize(&vec).unwrap(); assert_eq!(vec.as_slice(), ser_buffer.as_slice()); let v: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9]; let de = Vec::::from_bytes_with_input(&mut Cursor::new(v), 9).unwrap(); assert_eq!(vec, de.as_slice()); } #[test] fn test_nl_string() { setup(); let s = "AAAAA".to_string(); let desired_s = "AAAAA\0"; let ser_buffer = serialize(&s).unwrap(); assert_eq!(desired_s.as_bytes(), ser_buffer.as_slice()); let de_s = "AAAAA".to_string(); let de = String::from_bytes_with_input(&mut Cursor::new(desired_s.as_bytes()), 6).unwrap(); assert_eq!(de_s, de) } } neli-0.7.4/src/nl.rs000064400000000000000000000235711046102023000123410ustar 00000000000000//! This module contains the top level netlink header code. Every //! netlink message will be encapsulated in a top level `Nlmsghdr`. //! //! [`Nlmsghdr`] is the structure representing a //! header that all netlink protocols require to be passed to the //! correct destination. //! //! # Design decisions //! //! Payloads for [`Nlmsghdr`] can be any type. //! //! The payload is wrapped in an enum to facilitate better //! application-level error handling. use std::{ io::Cursor, mem::{size_of, swap}, }; use derive_builder::{Builder, UninitializedFieldError}; use getset::Getters; use log::trace; use crate::{ self as neli, consts::nl::{NlType, NlmF, Nlmsg}, err::{DeError, Nlmsgerr, NlmsgerrBuilder, NlmsghdrAck, NlmsghdrErr, RouterError}, types::{Buffer, GenlBuffer}, FromBytes, FromBytesWithInput, Header, Size, ToBytes, TypeSize, }; /// An enum representing either the desired payload as requested /// by the payload type parameter, an ACK received at the end /// of a message or stream of messages, or an error. #[derive(Clone, Debug, PartialEq, Eq, Size, ToBytes)] pub enum NlPayload { /// Represents an ACK returned by netlink. Ack(Nlmsgerr>), /// Represents an ACK extracted from the DONE packet returned by netlink /// on a DUMP. DumpExtAck(Nlmsgerr<()>), /// Represents an application level error returned by netlink. Err(Nlmsgerr>), /// Represents the requested payload. Payload(P), /// Indicates an empty payload. Empty, } impl FromBytesWithInput for NlPayload where P: Size + FromBytesWithInput, T: NlType, { type Input = (usize, T, NlmF); fn from_bytes_with_input( buffer: &mut Cursor>, (input_size, input_type, flags): (usize, T, NlmF), ) -> Result { let pos = buffer.position(); let mut processing = || { trace!("Deserializing data type {}", std::any::type_name::()); let ty_const: u16 = input_type.into(); if ty_const == Nlmsg::Done.into() { if buffer.position() == buffer.get_ref().as_ref().len() as u64 { Ok(NlPayload::Empty) } else if flags.contains(NlmF::MULTI) { trace!( "Deserializing field type {}", std::any::type_name::>(), ); trace!("Input: {input_size:?}"); let ext = Nlmsgerr::from_bytes_with_input(buffer, input_size)?; Ok(NlPayload::DumpExtAck(ext)) } else { // This is specifically targeting the connector protocol. // As more protocols are added, this may need to be changed. Ok(NlPayload::Payload(P::from_bytes_with_input( buffer, input_size, )?)) } } else if ty_const == Nlmsg::Error.into() { trace!( "Deserializing field type {}", std::any::type_name::() ); let code = libc::c_int::from_bytes(buffer)?; trace!("Field deserialized: {code:?}"); if code == 0 { trace!( "Deserializing field type {}", std::any::type_name::>() ); trace!("Input: {input_size:?}"); let nlmsg = NlmsghdrAck::::from_bytes(buffer)?; trace!("Field deserialized: {nlmsg:?}"); Ok(NlPayload::Ack( NlmsgerrBuilder::default().nlmsg(nlmsg).build()?, )) } else { trace!( "Deserializing field type {}", std::any::type_name::>() ); let nlmsg = NlmsghdrErr::::from_bytes(buffer)?; trace!("Field deserialized: {nlmsg:?}"); trace!( "Deserializing field type {}", std::any::type_name::>() ); let input = input_size - size_of::() - nlmsg.padded_size(); trace!("Input: {input:?}"); let ext_ack = GenlBuffer::from_bytes_with_input(buffer, input)?; trace!("Field deserialized: {ext_ack:?}"); Ok(NlPayload::Err( NlmsgerrBuilder::default() .error(code) .nlmsg(nlmsg) .ext_ack(ext_ack) .build()?, )) } } else { Ok(NlPayload::Payload(P::from_bytes_with_input( buffer, input_size, )?)) } }; match processing() { Ok(o) => Ok(o), Err(e) => { buffer.set_position(pos); Err(e) } } } } /// Top level netlink header and payload #[derive(Builder, Getters, Clone, Debug, PartialEq, Eq, Size, ToBytes, FromBytes, Header)] #[neli(header_bound = "T: TypeSize")] #[neli(from_bytes_bound = "T: NlType")] #[neli(from_bytes_bound = "P: Size + FromBytesWithInput")] #[neli(padding)] #[builder(build_fn(skip))] #[builder(pattern = "owned")] pub struct Nlmsghdr { /// Length of the netlink message #[builder(setter(skip))] #[getset(get = "pub")] nl_len: u32, /// Type of the netlink message #[getset(get = "pub")] nl_type: T, /// Flags indicating properties of the request or response #[getset(get = "pub")] nl_flags: NlmF, /// Sequence number for netlink protocol #[getset(get = "pub")] nl_seq: u32, /// ID of the netlink destination for requests and source for /// responses. #[getset(get = "pub")] nl_pid: u32, /// Payload of netlink message #[neli(input = "(nl_len as usize - Self::header_size() as usize, nl_type, nl_flags)")] #[neli(size = "nl_len as usize - Self::header_size() as usize")] #[getset(get = "pub")] pub(crate) nl_payload: NlPayload, } impl NlmsghdrBuilder where T: NlType, P: Size, { /// Build [`Nlmsghdr`]. pub fn build(self) -> Result, NlmsghdrBuilderError> { let nl_type = self .nl_type .ok_or_else(|| NlmsghdrBuilderError::from(UninitializedFieldError::new("nl_type")))?; let nl_flags = self .nl_flags .ok_or_else(|| NlmsghdrBuilderError::from(UninitializedFieldError::new("nl_flags")))?; let nl_seq = self.nl_seq.unwrap_or(0); let nl_pid = self.nl_pid.unwrap_or(0); let nl_payload = self.nl_payload.ok_or_else(|| { NlmsghdrBuilderError::from(UninitializedFieldError::new("nl_payload")) })?; let mut nl = Nlmsghdr { nl_len: 0, nl_type, nl_flags, nl_seq, nl_pid, nl_payload, }; nl.nl_len = nl.padded_size() as u32; Ok(nl) } } impl Nlmsghdr where T: NlType, { /// Get the payload if there is one. pub fn get_payload(&self) -> Option<&P> { match self.nl_payload { NlPayload::Payload(ref p) => Some(p), _ => None, } } /// Get an error from the payload if it exists. /// /// Takes a mutable reference because the payload will be swapped for /// [`Empty`][NlPayload::Empty] to gain ownership of the error. pub fn get_err(&mut self) -> Option>> { match self.nl_payload { NlPayload::Err(_) => { let mut payload = NlPayload::Empty; swap(&mut self.nl_payload, &mut payload); match payload { NlPayload::Err(e) => Some(e), _ => unreachable!(), } } _ => None, } } } impl NlPayload { /// Convert a typed payload from a payload that can represent all types. pub fn to_typed(self, payload_size: usize) -> Result, RouterError> where T: NlType, P: Size + FromBytesWithInput, { match self { NlPayload::Ack(a) => Ok(NlPayload::Ack(a.to_typed()?)), NlPayload::Err(e) => Ok(NlPayload::Err(e.to_typed()?)), NlPayload::DumpExtAck(a) => Ok(NlPayload::DumpExtAck(a)), NlPayload::Payload(p) => Ok(NlPayload::Payload(P::from_bytes_with_input( &mut Cursor::new(p), payload_size, )?)), NlPayload::Empty => Ok(NlPayload::Empty), } } } impl Nlmsghdr where T: NlType, P: Size, { /// Set the payload for [`Nlmsghdr`] and handle the change in length internally. pub fn set_payload(&mut self, p: NlPayload) { self.nl_len -= self.nl_payload.padded_size() as u32; self.nl_len += p.padded_size() as u32; self.nl_payload = p; } } impl Nlmsghdr { /// Set the payload for [`Nlmsghdr`] and handle the change in length internally. pub fn to_typed(self) -> Result, RouterError> where T: NlType, P: Size + FromBytesWithInput, { Ok(NlmsghdrBuilder::default() .nl_type(T::from(self.nl_type)) .nl_flags(self.nl_flags) .nl_seq(self.nl_seq) .nl_pid(self.nl_pid) .nl_payload( self.nl_payload .to_typed::(self.nl_len as usize - Self::header_size())?, ) .build()?) } } neli-0.7.4/src/router/asynchronous.rs000064400000000000000000000453071046102023000160040ustar 00000000000000use std::{ collections::{HashMap, HashSet}, iter::once, marker::PhantomData, sync::Arc, }; use log::{error, trace, warn}; use tokio::{ select, spawn, sync::{ mpsc::{channel, Receiver, Sender}, Mutex, }, }; use crate::{ consts::{ genl::{CtrlAttr, CtrlAttrMcastGrp, CtrlCmd, Index}, nl::{GenlId, NlType, NlmF, Nlmsg}, socket::NlFamily, }, err::{RouterError, SocketError}, genl::{AttrTypeBuilder, Genlmsghdr, GenlmsghdrBuilder, NlattrBuilder, NoUserHeader}, nl::{NlPayload, Nlmsghdr, NlmsghdrBuilder}, socket::asynchronous::NlSocketHandle, types::{Buffer, GenlBuffer, NlBuffer}, utils::{Groups, NetlinkBitArray}, FromBytesWithInput, Size, ToBytes, }; type GenlFamily = Result< NlBuffer>, RouterError>, >; type MCastSender = Sender, RouterError>>; type Senders = Arc, RouterError>>>>>; type ProcThreadReturn = ( Sender<()>, Receiver, RouterError>>, ); /// A high-level handle for sending messages and generating a handle that validates /// all of the received messages. pub struct NlRouter { socket: Arc, seq: Mutex, senders: Senders, exit_sender: Sender<()>, } async fn processing_loop( socket: &Arc, senders: &Senders, multicast_sender: &MCastSender, iter: impl Iterator, SocketError>>, group: Groups, ) { for msg in iter { trace!("Message received: {msg:?}"); let mut seqs_to_remove = HashSet::new(); match msg { Ok(m) => { let seq = *m.nl_seq(); let lock = senders.lock().await; if !group.is_empty() { if multicast_sender.send(Ok(m)).await.is_err() { warn!("{}", RouterError::::ClosedChannel); } } else if let Some(sender) = lock.get(m.nl_seq()) { if &socket.pid() == m.nl_pid() { if sender.send(Ok(m)).await.is_err() { error!("{}", RouterError::::ClosedChannel); seqs_to_remove.insert(seq); } } else { for (seq, sender) in lock.iter() { if sender .send(Err(RouterError::BadSeqOrPid(m.clone()))) .await .is_err() { error!("{}", RouterError::::ClosedChannel); seqs_to_remove.insert(*seq); } } } } } Err(e) => { let lock = senders.lock().await; for (seq, sender) in lock.iter() { if sender .send(Err(RouterError::from(e.clone()))) .await .is_err() { error!("{}", RouterError::::ClosedChannel); seqs_to_remove.insert(*seq); } } } } for seq in seqs_to_remove { senders.lock().await.remove(&seq); } } } fn spawn_processing_thread(socket: Arc, senders: Senders) -> ProcThreadReturn { let (exit_sender, mut exit_receiver) = channel(1); let (multicast_sender, multicast_receiver) = channel(1024); spawn(async move { loop { select! { res = exit_receiver.recv() => { if res.is_none() { warn!("Failed to read from exit channel"); } return; } res = socket.recv::() => { match res { Ok((iter, group)) => { processing_loop(&socket, &senders, &multicast_sender, iter, group).await } Err(e) => { let mut seqs_to_remove = HashSet::new(); let mut lock = senders.lock().await; for (seq, sender) in lock.iter() { if sender .send(Err(RouterError::from(e.clone()))) .await .is_err() { seqs_to_remove.insert(*seq); error!("{}", RouterError::::ClosedChannel); break; } } for seq in seqs_to_remove { lock.remove(&seq); } } } } } } }); (exit_sender, multicast_receiver) } impl NlRouter { /// Equivalent of `socket` and `bind` calls. pub async fn connect( proto: NlFamily, pid: Option, groups: Groups, ) -> Result< ( Self, NlRouterReceiverHandle>, ), RouterError, > { let socket = Arc::new(NlSocketHandle::connect(proto, pid, groups)?); let senders = Arc::new(Mutex::new(HashMap::default())); let (exit_sender, multicast_receiver) = spawn_processing_thread(Arc::clone(&socket), Arc::clone(&senders)); let multicast_receiver = NlRouterReceiverHandle::new(multicast_receiver, Arc::clone(&senders), false, None); Ok(( NlRouter { socket, senders, seq: Mutex::new(0), exit_sender, }, multicast_receiver, )) } /// Join multicast groups for a socket. pub fn add_mcast_membership(&self, groups: Groups) -> Result<(), RouterError> { self.socket .add_mcast_membership(groups) .map_err(RouterError::from) } /// Leave multicast groups for a socket. pub fn drop_mcast_membership(&self, groups: Groups) -> Result<(), RouterError> { self.socket .drop_mcast_membership(groups) .map_err(RouterError::from) } /// List joined groups for a socket. pub fn list_mcast_membership(&self) -> Result> { self.socket .list_mcast_membership() .map_err(RouterError::from) } /// If [`true`] is passed in, enable extended ACKs for this socket. If [`false`] /// is passed in, disable extended ACKs for this socket. pub fn enable_ext_ack(&self, enable: bool) -> Result<(), RouterError> { self.socket .enable_ext_ack(enable) .map_err(RouterError::from) } /// Return [`true`] if an extended ACK is enabled for this socket. pub fn get_ext_ack_enabled(&self) -> Result> { self.socket.get_ext_ack_enabled().map_err(RouterError::from) } /// If [`true`] is passed in, enable strict checking for this socket. If [`false`] /// is passed in, disable strict checking for for this socket. /// Only supported by `NlFamily::Route` sockets. /// Requires Linux >= 4.20. pub fn enable_strict_checking(&self, enable: bool) -> Result<(), RouterError> { self.socket .enable_strict_checking(enable) .map_err(RouterError::from) } /// Return [`true`] if strict checking is enabled for this socket. /// Only supported by `NlFamily::Route` sockets. /// Requires Linux >= 4.20. pub fn get_strict_checking_enabled(&self) -> Result> { self.socket .get_strict_checking_enabled() .map_err(RouterError::from) } /// Get the PID for the current socket. pub fn pid(&self) -> u32 { self.socket.pid() } async fn next_seq(&self) -> u32 { let mut lock = self.seq.lock().await; let next = *lock; *lock = lock.wrapping_add(1); next } /// Send a message and return a handle for receiving responses from this message. pub async fn send( &self, nl_type: ST, nl_flags: NlmF, nl_payload: NlPayload, ) -> Result, RouterError> where ST: NlType, SP: Size + ToBytes, { let msg = NlmsghdrBuilder::default() .nl_type(nl_type) .nl_flags( // Required for messages nl_flags | NlmF::REQUEST, ) .nl_pid(self.socket.pid()) .nl_seq(self.next_seq().await) .nl_payload(nl_payload) .build()?; let seq = *msg.nl_seq(); let (sender, receiver) = channel(1024); self.senders.lock().await.insert(seq, sender); let flags = *msg.nl_flags(); self.socket.send(&msg).await?; Ok(NlRouterReceiverHandle::new( receiver, Arc::clone(&self.senders), flags.contains(NlmF::ACK) && !flags.contains(NlmF::DUMP), Some(seq), )) } async fn get_genl_family(&self, family_name: &str) -> GenlFamily { let mut recv = self .send::<_, _, u16, Genlmsghdr>( GenlId::Ctrl, NlmF::ACK, NlPayload::Payload( GenlmsghdrBuilder::default() .cmd(CtrlCmd::Getfamily) .version(2) .attrs( once( NlattrBuilder::default() .nla_type( AttrTypeBuilder::default() .nla_type(CtrlAttr::FamilyName) .build()?, ) .nla_payload(family_name) .build()?, ) .collect::>(), ) .build()?, ), ) .await?; let mut buffer = NlBuffer::new(); while let Some(msg) = recv.next().await { buffer.push(msg?); } Ok(buffer) } /// Convenience function for resolving a [`str`] containing the /// generic netlink family name to a numeric generic netlink ID. pub async fn resolve_genl_family( &self, family_name: &str, ) -> Result>> { let mut res = Err(RouterError::new(format!( "Generic netlink family {family_name} was not found" ))); let nlhdrs = self.get_genl_family(family_name).await?; for nlhdr in nlhdrs.into_iter() { if let NlPayload::Payload(p) = nlhdr.nl_payload() { let handle = p.attrs().get_attr_handle(); if let Ok(u) = handle.get_attr_payload_as::(CtrlAttr::FamilyId) { res = Ok(u); } } } res } /// Convenience function for resolving a [`str`] containing the /// multicast group name to a numeric multicast group ID. pub async fn resolve_nl_mcast_group( &self, family_name: &str, mcast_name: &str, ) -> Result>> { let mut res = Err(RouterError::new(format!( "Failed to resolve multicast group ID for family name {family_name}, multicast group name {mcast_name}" ))); let nlhdrs = self.get_genl_family(family_name).await?; for nlhdr in nlhdrs { if let NlPayload::Payload(p) = nlhdr.nl_payload() { let handle = p.attrs().get_attr_handle(); let mcast_groups = handle.get_nested_attributes::(CtrlAttr::McastGroups)?; if let Some(id) = mcast_groups.iter().find_map(|item| { let nested_attrs = item.get_attr_handle::().ok()?; let string = nested_attrs .get_attr_payload_as_with_len::(CtrlAttrMcastGrp::Name) .ok()?; if string.as_str() == mcast_name { nested_attrs .get_attr_payload_as::(CtrlAttrMcastGrp::Id) .ok() } else { None } }) { res = Ok(id); } } } res } /// Look up netlink family and multicast group name by ID. pub async fn lookup_id( &self, id: u32, ) -> Result<(String, String), RouterError>> { let mut res = Err(RouterError::new( "ID does not correspond to a multicast group", )); let mut recv = self .send::<_, _, u16, Genlmsghdr>( GenlId::Ctrl, NlmF::DUMP, NlPayload::Payload( GenlmsghdrBuilder::::default() .cmd(CtrlCmd::Getfamily) .version(2) .attrs(GenlBuffer::new()) .build()?, ), ) .await?; while let Some(res_msg) = recv.next().await { let msg = res_msg?; if let NlPayload::Payload(p) = msg.nl_payload() { let attributes = p.attrs().get_attr_handle(); let name = attributes.get_attr_payload_as_with_len::(CtrlAttr::FamilyName)?; let groups = match attributes.get_nested_attributes::(CtrlAttr::McastGroups) { Ok(grps) => grps, Err(_) => continue, }; for group_by_index in groups.iter() { let attributes = group_by_index.get_attr_handle::()?; if let Ok(mcid) = attributes.get_attr_payload_as::(CtrlAttrMcastGrp::Id) { if mcid == id { let mcast_name = attributes .get_attr_payload_as_with_len::(CtrlAttrMcastGrp::Name)?; res = Ok((name.clone(), mcast_name)); } } } } } res } } impl Drop for NlRouter { fn drop(&mut self) { if self.exit_sender.try_send(()).is_err() { warn!("Failed to send shutdown message; processing thread should exit anyway"); } } } /// A handle for receiving and validating all messages that correspond to a request. pub struct NlRouterReceiverHandle { receiver: Receiver, RouterError>>, senders: Senders, needs_ack: bool, seq: Option, next_is_none: bool, next_is_ack: bool, data: PhantomData<(T, P)>, } impl NlRouterReceiverHandle { fn new( receiver: Receiver, RouterError>>, senders: Senders, needs_ack: bool, seq: Option, ) -> Self { NlRouterReceiverHandle { receiver, senders, needs_ack, seq, next_is_none: false, next_is_ack: false, data: PhantomData, } } } impl NlRouterReceiverHandle where T: NlType, P: Size + FromBytesWithInput, { /// Imitates the [`Iterator`][Iterator] API but allows parsing differently typed /// messages in a sequence of messages meant for this receiver. pub async fn next(&mut self) -> Option, RouterError>> where TT: NlType, PP: Size + FromBytesWithInput, { if self.next_is_none { return None; } let mut msg = match self.receiver.recv().await { Some(untyped) => match untyped { Ok(u) => match u.to_typed::() { Ok(t) => t, Err(e) => { self.next_is_none = true; return Some(Err(e)); } }, Err(e) => { self.next_is_none = true; return Some(Err(match e.to_typed() { Ok(e) => e, Err(e) => e, })); } }, None => { self.next_is_none = true; return Some(Err(RouterError::ClosedChannel)); } }; let nl_type = Nlmsg::from((*msg.nl_type()).into()); if let NlPayload::Ack(_) = msg.nl_payload() { self.next_is_none = true; if !self.needs_ack { return Some(Err(RouterError::UnexpectedAck)); } } else if let Some(e) = msg.get_err() { self.next_is_none = true; if self.next_is_ack { return Some(Err(RouterError::NoAck)); } else { return Some(Err(RouterError::::Nlmsgerr(e))); } } else if (!msg.nl_flags().contains(NlmF::MULTI) || nl_type == Nlmsg::Done) && self.seq.is_some() { assert!(!self.next_is_ack); if self.needs_ack { self.next_is_ack = true; } else { self.next_is_none = true; } } else if self.next_is_ack { self.next_is_none = true; return Some(Err(RouterError::NoAck)); } trace!("Router received message: {msg:?}"); Some(Ok(msg)) } } impl Drop for NlRouterReceiverHandle { fn drop(&mut self) { if let Some(seq) = self.seq { if let Ok(mut lock) = self.senders.try_lock() { lock.remove(&seq); } } } } neli-0.7.4/src/router/mod.rs000064400000000000000000000046571046102023000140330ustar 00000000000000//! High level API that performs sequence and PID checking as well as ACK validation. //! //! ## Workflow //! * [`NlRouter::send`][crate::router::synchronous::NlRouter] sends a message and //! does automatic seq handling. //! * A thread in the background receives all messages that sent to the socket in //! response. //! * Each message is sent on the channel match the sequence number to the //! [`NlRouterReceiverHandle`][crate::router::synchronous::NlRouterReceiverHandle] that corresponds //! to the request. //! * Errors in packet reception and parsing are broadcast to all receivers. //! * An [`NlRouterReceiverHandle`][crate::router::synchronous::NlRouterReceiverHandle] //! can be used as an iterator and will return [`None`] either when all //! messages corresponding to the request have been received or there is a fatal error. //! //! ## Design decisions //! Older users of the library might recognize some of the funtionality in //! [`NlRouter`][crate::router::synchronous::NlRouter] as code that previously was //! associated with [`NlSocketHandle`][crate::socket::synchronous::NlSocketHandle]. //! The reason for this migration is primarily due to some deficiencies found in the //! previous implementation. //! [`NlSocketHandle`][crate::socket::synchronous::NlSocketHandle] //! relied heavily on a `.send()`/`.recv()` workflow. This meant that, while it //! was designed to address ACK handling and receiving all responses associated //! with a given request, the implementation actually was unable to handle two //! separate responses corresponding to two seaparate requests interleaved with each //! other. Effectively, this meant that the socket handle had no awareness of multiple //! requests being sent before all data was read from the socket and would result //! in parsing errors if used in this way. //! //! [`NlRouter`][crate::router::synchronous::NlRouter] aims to address this by //! associating all messages received by the socket with a request or multicast //! group so that messages can be interleaved and still processed in the correct //! order by the handle associated with the request that generated it. //! //! ## Features //! The `async` feature exposed by `cargo` allows the socket to use //! Rust's [tokio](https://tokio.rs) for async IO. /// Asynchronous packet routing functionality. #[cfg(feature = "async")] pub mod asynchronous; /// Synchronous packet routing functionality. #[cfg(feature = "sync")] pub mod synchronous; neli-0.7.4/src/router/synchronous.rs000064400000000000000000000575331046102023000156470ustar 00000000000000use std::{ collections::{HashMap, HashSet}, io, iter::once, marker::PhantomData, mem::MaybeUninit, os::fd::{AsRawFd, FromRawFd, OwnedFd}, sync::{ mpsc::{channel, Receiver, Sender}, Arc, }, thread::spawn, }; use log::{debug, error, trace, warn}; use parking_lot::Mutex; use crate::{ consts::{ genl::{CtrlAttr, CtrlAttrMcastGrp, CtrlCmd, Index}, nl::{GenlId, NlType, NlmF, Nlmsg}, socket::NlFamily, }, err::{RouterError, SocketError}, genl::{AttrTypeBuilder, Genlmsghdr, GenlmsghdrBuilder, NlattrBuilder, NoUserHeader}, nl::{NlPayload, Nlmsghdr, NlmsghdrBuilder}, socket::synchronous::NlSocketHandle, types::{Buffer, GenlBuffer, NlBuffer}, utils::{Groups, NetlinkBitArray}, FromBytesWithInput, Size, ToBytes, }; type GenlFamily = Result< NlBuffer>, RouterError>, >; type MCastSender = Sender, RouterError>>; type Senders = Arc, RouterError>>>>>; type ConnectReturn = Result< ( T, NlRouterReceiverHandle>, ), RouterError, >; type ProcThreadReturn = Result< ( Receiver, RouterError>>, OwnedFd, ), RouterError, >; /// A high-level handle for sending messages and generating a handle that validates /// all of the received messages. pub struct NlRouter { socket: Arc, seq: Mutex, senders: Senders, fd: OwnedFd, } fn processing_loop( socket: &Arc, senders: &Senders, multicast_sender: &MCastSender, iter: impl Iterator, SocketError>>, group: Groups, ) { for msg in iter { trace!("Message received: {msg:?}"); let mut seqs_to_remove = HashSet::new(); match msg { Ok(m) => { let seq = *m.nl_seq(); let lock = senders.lock(); if !group.is_empty() { if multicast_sender.send(Ok(m)).is_err() { warn!("{}", RouterError::::ClosedChannel); } } else if let Some(sender) = lock.get(m.nl_seq()) { if &socket.pid() == m.nl_pid() { if sender.send(Ok(m)).is_err() { error!("{}", RouterError::::ClosedChannel); seqs_to_remove.insert(seq); } } else { for (seq, sender) in lock.iter() { if sender .send(Err(RouterError::BadSeqOrPid(m.clone()))) .is_err() { error!("{}", RouterError::::ClosedChannel); seqs_to_remove.insert(*seq); } } } } else { for (seq, sender) in lock.iter() { if sender .send(Err(RouterError::BadSeqOrPid(m.clone()))) .is_err() { error!("{}", RouterError::::ClosedChannel); seqs_to_remove.insert(*seq); } } } } Err(e) => { let lock = senders.lock(); for (seq, sender) in lock.iter() { if sender.send(Err(RouterError::from(e.clone()))).is_err() { error!("{}", RouterError::::ClosedChannel); seqs_to_remove.insert(*seq); } } } } for seq in seqs_to_remove { senders.lock().remove(&seq); } } } fn error_handling(senders: &Senders, e: SocketError) { let mut seqs_to_remove = HashSet::new(); let mut lock = senders.lock(); for (seq, sender) in lock.iter() { if sender.send(Err(RouterError::from(e.clone()))).is_err() { seqs_to_remove.insert(*seq); error!("{}", RouterError::::ClosedChannel); break; } } for seq in seqs_to_remove { lock.remove(&seq); } } fn spawn_processing_thread(socket: Arc, senders: Senders) -> ProcThreadReturn { let owned_event_fd = { let event_fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) }; if event_fd < 0 { return Err(RouterError::new(format!( "Failed to initialize eventfd for signaling processing thread exit: errno {event_fd}" ))); } unsafe { OwnedFd::from_raw_fd(event_fd) } }; let owned_duped_event_fd = { let duped_event_fd = unsafe { libc::dup(owned_event_fd.as_raw_fd()) }; if duped_event_fd < 0 { return Err(RouterError::new(format!( "Failed to duplicate eventfd for signaling processing thread exit: errno {duped_event_fd}" ))); } unsafe { OwnedFd::from_raw_fd(duped_event_fd) } }; socket.set_nonblock()?; let epoll_fd = unsafe { libc::epoll_create(1) }; if epoll_fd < 0 { return Err(RouterError::Io( io::Error::from_raw_os_error(epoll_fd).kind(), )); } let epoll = unsafe { OwnedFd::from_raw_fd(epoll_fd) }; let (multicast_sender, multicast_receiver) = channel(); spawn(move || { const EVENT_FD_TOKEN: u64 = 0; const SOCKET_TOKEN: u64 = 1; let mut event_fd_epoll_event = libc::epoll_event { events: libc::EPOLLIN as u32, u64: EVENT_FD_TOKEN, }; unsafe { libc::epoll_ctl( epoll_fd.as_raw_fd(), libc::EPOLL_CTL_ADD, owned_event_fd.as_raw_fd(), &mut event_fd_epoll_event as *mut _, ) }; let mut socket_epoll_event = libc::epoll_event { events: libc::EPOLLIN as u32, u64: SOCKET_TOKEN, }; unsafe { libc::epoll_ctl( epoll_fd.as_raw_fd(), libc::EPOLL_CTL_ADD, socket.as_raw_fd(), &mut socket_epoll_event as *mut _, ) }; let mut events = vec![MaybeUninit::::uninit(); 2]; loop { let event_count = unsafe { libc::epoll_wait( epoll.as_raw_fd(), events.as_mut_ptr() as *mut _, events.len() as libc::c_int, 100, /* ms */ ) }; if event_count < 0 { warn!( "Failed to epoll file descriptors: errno {event_count}; exiting processing thread" ); return; } for event in events.iter().take(event_count as usize) { if unsafe { event.assume_init_ref() }.u64 == EVENT_FD_TOKEN { let mut buffer = [0u8; 8]; let ret = unsafe { libc::read( owned_event_fd.as_raw_fd(), buffer.as_mut_ptr() as *mut _, buffer.len(), ) }; match ret as i32 { i if i > 0 => { debug!("Processing thread signaled to exit; exiting"); return; } libc::EAGAIN => (), i => { warn!("Unexpected return value from read: {i}"); } } } else if unsafe { event.assume_init_ref() }.u64 == SOCKET_TOKEN { match socket.recv::() { Ok((iter, group)) => { processing_loop(&socket, &senders, &multicast_sender, iter, group) } Err(e) => { if let SocketError::Io(ref io_e) = e { if io_e.kind() != io::ErrorKind::WouldBlock { error_handling(&senders, e); } } else { error_handling(&senders, e); } } } } } } }); Ok((multicast_receiver, owned_duped_event_fd)) } impl NlRouter { /// Equivalent of `socket` and `bind` calls. pub fn connect(proto: NlFamily, pid: Option, groups: Groups) -> ConnectReturn { let socket = Arc::new(NlSocketHandle::connect(proto, pid, groups)?); let senders = Arc::new(Mutex::new(HashMap::default())); let (multicast_receiver, fd) = spawn_processing_thread(Arc::clone(&socket), Arc::clone(&senders))?; let multicast_receiver = NlRouterReceiverHandle::new(multicast_receiver, Arc::clone(&senders), false, None); Ok(( NlRouter { socket, senders, seq: Mutex::new(0), fd, }, multicast_receiver, )) } /// Join multicast groups for a socket. pub fn add_mcast_membership(&self, groups: Groups) -> Result<(), RouterError> { self.socket .add_mcast_membership(groups) .map_err(RouterError::from) } /// Leave multicast groups for a socket. pub fn drop_mcast_membership(&self, groups: Groups) -> Result<(), RouterError> { self.socket .drop_mcast_membership(groups) .map_err(RouterError::from) } /// List joined groups for a socket. pub fn list_mcast_membership(&self) -> Result> { self.socket .list_mcast_membership() .map_err(RouterError::from) } /// If [`true`] is passed in, enable extended ACKs for this socket. If [`false`] /// is passed in, disable extended ACKs for this socket. pub fn enable_ext_ack(&self, enable: bool) -> Result<(), RouterError> { self.socket .enable_ext_ack(enable) .map_err(RouterError::from) } /// Return [`true`] if an extended ACK is enabled for this socket. pub fn get_ext_ack_enabled(&self) -> Result> { self.socket.get_ext_ack_enabled().map_err(RouterError::from) } /// If [`true`] is passed in, enable strict checking for this socket. If [`false`] /// is passed in, disable strict checking for for this socket. /// Only supported by `NlFamily::Route` sockets. /// Requires Linux >= 4.20. pub fn enable_strict_checking(&self, enable: bool) -> Result<(), RouterError> { self.socket .enable_strict_checking(enable) .map_err(RouterError::from) } /// Return [`true`] if strict checking is enabled for this socket. /// Only supported by `NlFamily::Route` sockets. /// Requires Linux >= 4.20. pub fn get_strict_checking_enabled(&self) -> Result> { self.socket .get_strict_checking_enabled() .map_err(RouterError::from) } /// Get the PID for the current socket. pub fn pid(&self) -> u32 { self.socket.pid() } fn next_seq(&self) -> u32 { let mut lock = self.seq.lock(); let next = *lock; *lock = lock.wrapping_add(1); next } /// Send a message and return a handle for receiving responses from this message. pub fn send( &self, nl_type: ST, nl_flags: NlmF, nl_payload: NlPayload, ) -> Result, RouterError> where ST: NlType, SP: Size + ToBytes, { let msg = NlmsghdrBuilder::default() .nl_type(nl_type) .nl_flags( // Required for messages nl_flags | NlmF::REQUEST, ) .nl_pid(self.socket.pid()) .nl_seq(self.next_seq()) .nl_payload(nl_payload) .build()?; let (sender, receiver) = channel(); let seq = *msg.nl_seq(); self.senders.lock().insert(seq, sender); let flags = *msg.nl_flags(); self.socket.send(&msg)?; Ok(NlRouterReceiverHandle::new( receiver, Arc::clone(&self.senders), flags.contains(NlmF::ACK) && !flags.contains(NlmF::DUMP), Some(seq), )) } fn get_genl_family(&self, family_name: &str) -> GenlFamily { let recv = self.send( GenlId::Ctrl, NlmF::ACK, NlPayload::Payload( GenlmsghdrBuilder::default() .cmd(CtrlCmd::Getfamily) .version(2) .attrs( once( NlattrBuilder::default() .nla_type( AttrTypeBuilder::default() .nla_type(CtrlAttr::FamilyName) .build()?, ) .nla_payload(family_name) .build()?, ) .collect::>(), ) .build()?, ), )?; let mut buffer = NlBuffer::new(); for msg in recv { buffer.push(msg?) } Ok(buffer) } /// Convenience function for resolving a [`str`] containing the /// generic netlink family name to a numeric generic netlink ID. pub fn resolve_genl_family( &self, family_name: &str, ) -> Result>> { let mut res = Err(RouterError::new(format!( "Generic netlink family {family_name} was not found" ))); let nlhdrs = self.get_genl_family(family_name)?; for nlhdr in nlhdrs.into_iter() { if let NlPayload::Payload(p) = nlhdr.nl_payload() { let handle = p.attrs().get_attr_handle(); if let Ok(u) = handle.get_attr_payload_as::(CtrlAttr::FamilyId) { res = Ok(u); } } } res } /// Convenience function for resolving a [`str`] containing the /// multicast group name to a numeric multicast group ID. pub fn resolve_nl_mcast_group( &self, family_name: &str, mcast_name: &str, ) -> Result>> { let mut res = Err(RouterError::new(format!( "Failed to resolve multicast group ID for family name {family_name}, multicast group name {mcast_name}" ))); let nlhdrs = self.get_genl_family(family_name)?; for nlhdr in nlhdrs { if let NlPayload::Payload(p) = nlhdr.nl_payload() { let handle = p.attrs().get_attr_handle(); let mcast_groups = handle.get_nested_attributes::(CtrlAttr::McastGroups)?; if let Some(id) = mcast_groups.iter().find_map(|item| { let nested_attrs = item.get_attr_handle::().ok()?; let string = nested_attrs .get_attr_payload_as_with_len::(CtrlAttrMcastGrp::Name) .ok()?; if string.as_str() == mcast_name { nested_attrs .get_attr_payload_as::(CtrlAttrMcastGrp::Id) .ok() } else { None } }) { res = Ok(id); } } } res } /// Look up netlink family and multicast group name by ID. pub fn lookup_id( &self, id: u32, ) -> Result<(String, String), RouterError>> { let mut res = Err(RouterError::new( "ID does not correspond to a multicast group", )); let recv = self.send( GenlId::Ctrl, NlmF::DUMP, NlPayload::Payload( GenlmsghdrBuilder::::default() .cmd(CtrlCmd::Getfamily) .version(2) .attrs(GenlBuffer::new()) .build()?, ), )?; for res_msg in recv { let msg = res_msg?; if let NlPayload::Payload(p) = msg.nl_payload() { let attributes = p.attrs().get_attr_handle(); let name = attributes.get_attr_payload_as_with_len::(CtrlAttr::FamilyName)?; let groups = match attributes.get_nested_attributes::(CtrlAttr::McastGroups) { Ok(grps) => grps, Err(_) => continue, }; for group_by_index in groups.iter() { let attributes = group_by_index.get_attr_handle::()?; if let Ok(mcid) = attributes.get_attr_payload_as::(CtrlAttrMcastGrp::Id) { if mcid == id { let mcast_name = attributes .get_attr_payload_as_with_len::(CtrlAttrMcastGrp::Name)?; res = Ok((name.clone(), mcast_name)); } } } } } res } } impl Drop for NlRouter { fn drop(&mut self) { let buffer: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 1]; let ret = unsafe { libc::write( self.fd.as_raw_fd(), buffer.as_ptr() as *const _, buffer.len(), ) }; if ret < 0 { warn!("Failed to signal processing thread to exit: errno {ret}"); } } } /// A handle for receiving and validating all messages that correspond to a request. pub struct NlRouterReceiverHandle { receiver: Receiver, RouterError>>, senders: Senders, needs_ack: bool, seq: Option, next_is_none: bool, next_is_ack: bool, data: PhantomData<(T, P)>, } impl NlRouterReceiverHandle { fn new( receiver: Receiver, RouterError>>, senders: Senders, needs_ack: bool, seq: Option, ) -> Self { NlRouterReceiverHandle { receiver, senders, needs_ack, seq, next_is_none: false, next_is_ack: false, data: PhantomData, } } } impl NlRouterReceiverHandle where T: NlType, P: Size + FromBytesWithInput, { /// Imitates the [`Iterator`] API but allows parsing differently typed /// messages in a sequence of messages meant for this receiver. pub fn next_typed(&mut self) -> Option, RouterError>> where TT: NlType, PP: Size + FromBytesWithInput, { if self.next_is_none { return None; } let mut msg = match self.receiver.recv() { Ok(untyped) => match untyped { Ok(u) => match u.to_typed::() { Ok(t) => t, Err(e) => { self.next_is_none = true; return Some(Err(e)); } }, Err(e) => { self.next_is_none = true; return Some(Err(match e.to_typed() { Ok(e) => e, Err(e) => e, })); } }, Err(_) => { self.next_is_none = true; return Some(Err(RouterError::ClosedChannel)); } }; let nl_type = Nlmsg::from((*msg.nl_type()).into()); if let NlPayload::Ack(_) = msg.nl_payload() { self.next_is_none = true; if !self.needs_ack { return Some(Err(RouterError::UnexpectedAck)); } } else if let Some(e) = msg.get_err() { self.next_is_none = true; if self.next_is_ack { return Some(Err(RouterError::NoAck)); } else { return Some(Err(RouterError::::Nlmsgerr(e))); } } else if (!msg.nl_flags().contains(NlmF::MULTI) || nl_type == Nlmsg::Done) && self.seq.is_some() { assert!(!self.next_is_ack); if self.needs_ack { self.next_is_ack = true; } else { self.next_is_none = true; } } else if self.next_is_ack { self.next_is_none = true; return Some(Err(RouterError::NoAck)); } trace!("Router received message: {msg:?}"); Some(Ok(msg)) } } impl Iterator for NlRouterReceiverHandle where T: NlType, P: Size + FromBytesWithInput, { type Item = Result, RouterError>; fn next(&mut self) -> Option { self.next_typed::() } } impl Drop for NlRouterReceiverHandle { fn drop(&mut self) { if let Some(seq) = self.seq { self.senders.lock().remove(&seq); } } } #[cfg(test)] mod test { use super::*; use crate::test::setup; #[test] fn real_test_mcast_groups() { setup(); let (sock, _multicast) = NlRouter::connect(NlFamily::Generic, None, Groups::empty()).unwrap(); sock.enable_strict_checking(true).unwrap(); let notify_id_result = sock.resolve_nl_mcast_group("nlctrl", "notify"); let config_id_result = sock.resolve_nl_mcast_group("devlink", "config"); let ids = match (notify_id_result, config_id_result) { (Ok(ni), Ok(ci)) => { sock.add_mcast_membership(Groups::new_groups(&[ni, ci])) .unwrap(); vec![ni, ci] } (Ok(ni), Err(RouterError::Nlmsgerr(_))) => { sock.add_mcast_membership(Groups::new_groups(&[ni])) .unwrap(); vec![ni] } (Err(RouterError::Nlmsgerr(_)), Ok(ci)) => { sock.add_mcast_membership(Groups::new_groups(&[ci])) .unwrap(); vec![ci] } (Err(RouterError::Nlmsgerr(_)), Err(RouterError::Nlmsgerr(_))) => { return; } (Err(e), _) => panic!("Unexpected result from resolve_nl_mcast_group: {e:?}"), (_, Err(e)) => panic!("Unexpected result from resolve_nl_mcast_group: {e:?}"), }; let groups = sock.list_mcast_membership().unwrap(); for id in ids.iter() { assert!(groups.is_set(*id as usize)); } sock.drop_mcast_membership(Groups::new_groups(ids.as_slice())) .unwrap(); let groups = sock.list_mcast_membership().unwrap(); for id in ids.iter() { assert!(!groups.is_set(*id as usize)); } } } neli-0.7.4/src/rtnl.rs000064400000000000000000000451351046102023000127070ustar 00000000000000//! This module provides an implementation of routing netlink //! structures and the routing attributes that are at the end of //! most routing netlink responses. //! //! # Design decisions //! //! This module is based very heavily on the information in //! `man 7 rtnetlink` so it is mainly a series of structs organized //! in a style similar to the rest of the library. use std::io::Cursor; use derive_builder::{Builder, UninitializedFieldError}; use getset::Getters; use crate::{ self as neli, attr::{AttrHandle, Attribute}, consts::rtnl::*, err::{DeError, SerError}, types::{Buffer, RtBuffer}, FromBytes, FromBytesWithInput, FromBytesWithInputBorrowed, Header, Size, ToBytes, }; /// Struct representing interface information messages #[derive(Builder, Getters, Clone, Debug, Size, ToBytes, FromBytesWithInput, Header)] #[builder(pattern = "owned")] pub struct Ifinfomsg { /// Interface address family #[getset(get = "pub")] ifi_family: RtAddrFamily, #[builder(setter(skip))] #[builder(default = "0")] padding: u8, /// Interface type #[getset(get = "pub")] #[builder(default = "Arphrd::from(0)")] ifi_type: Arphrd, /// Interface index #[getset(get = "pub")] #[builder(default = "0")] ifi_index: libc::c_int, /// Interface flags #[getset(get = "pub")] #[builder(default = "Iff::empty()")] ifi_flags: Iff, /// Interface change mask #[getset(get = "pub")] #[builder(default = "Iff::empty()")] ifi_change: Iff, /// Payload of [`Rtattr`]s #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")] #[getset(get = "pub")] #[builder(default = "RtBuffer::new()")] rtattrs: RtBuffer, } impl IfinfomsgBuilder { /// Set the link with the given index up (equivalent to /// `ip link set dev DEV up`) pub fn up(mut self) -> Self { self.ifi_flags = Some(self.ifi_flags.unwrap_or_else(Iff::empty) | Iff::UP); self.ifi_change = Some(self.ifi_change.unwrap_or_else(Iff::empty) | Iff::UP); self } /// Set the link with the given index down (equivalent to /// `ip link set dev DEV down`) pub fn down(mut self) -> Self { self.ifi_flags = Some(self.ifi_flags.unwrap_or_else(Iff::empty) & !Iff::UP); self.ifi_change = Some(self.ifi_change.unwrap_or_else(Iff::empty) | Iff::UP); self } } /// Struct representing interface address messages #[derive(Builder, Getters, Clone, Debug, Size, ToBytes, FromBytesWithInput, Header)] #[builder(pattern = "owned")] pub struct Ifaddrmsg { /// Interface address family #[getset(get = "pub")] ifa_family: RtAddrFamily, /// Interface address prefix length #[getset(get = "pub")] ifa_prefixlen: libc::c_uchar, /// Interface address flags #[getset(get = "pub")] #[builder(default = "IfaF::empty()")] ifa_flags: IfaF, /// Interface address scope #[getset(get = "pub")] ifa_scope: RtScope, /// Index of the interface that the address is associated with #[getset(get = "pub")] ifa_index: libc::c_uint, /// Payload of [`Rtattr`]s #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")] #[getset(get = "pub")] #[builder(default = "RtBuffer::new()")] rtattrs: RtBuffer, } /// General form of address family dependent message. Used for /// requesting things from rtnetlink. #[derive(Builder, Getters, Debug, Size, ToBytes, FromBytesWithInput, Header)] #[builder(pattern = "owned")] pub struct Rtgenmsg { /// Address family for the request #[getset(get = "pub")] rtgen_family: RtAddrFamily, /// Payload of [`Rtattr`]s #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")] #[getset(get = "pub")] #[builder(default = "RtBuffer::new()")] rtattrs: RtBuffer, } /// Route message #[derive(Builder, Getters, Clone, Debug, Size, ToBytes, FromBytesWithInput, Header)] #[builder(pattern = "owned")] pub struct Rtmsg { /// Address family of route #[getset(get = "pub")] rtm_family: RtAddrFamily, /// Length of destination #[getset(get = "pub")] rtm_dst_len: libc::c_uchar, /// Length of source #[getset(get = "pub")] rtm_src_len: libc::c_uchar, /// TOS filter #[getset(get = "pub")] rtm_tos: libc::c_uchar, /// Routing table ID #[getset(get = "pub")] rtm_table: RtTable, /// Routing protocol #[getset(get = "pub")] rtm_protocol: Rtprot, /// Routing scope #[getset(get = "pub")] rtm_scope: RtScope, /// Routing type #[getset(get = "pub")] rtm_type: Rtn, /// Routing flags #[builder(default = "RtmF::empty()")] #[getset(get = "pub")] rtm_flags: RtmF, /// Payload of [`Rtattr`]s #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")] #[getset(get = "pub")] #[builder(default = "RtBuffer::new()")] rtattrs: RtBuffer, } /// Represents an ARP (neighbor table) entry #[derive(Builder, Getters, Debug, Size, ToBytes, FromBytesWithInput, Header)] #[builder(pattern = "owned")] pub struct Ndmsg { /// Address family of entry #[getset(get = "pub")] ndm_family: RtAddrFamily, #[builder(setter(skip))] #[builder(default = "0")] pad1: u8, #[builder(setter(skip))] #[builder(default = "0")] pad2: u16, /// Index of entry #[getset(get = "pub")] ndm_index: libc::c_int, /// State of entry #[getset(get = "pub")] ndm_state: Nud, /// Flags for entry #[getset(get = "pub")] #[builder(default = "Ntf::empty()")] ndm_flags: Ntf, /// Type of entry #[getset(get = "pub")] ndm_type: Rtn, /// Payload of [`Rtattr`]s #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")] #[getset(get = "pub")] #[builder(default = "RtBuffer::new()")] rtattrs: RtBuffer, } /// Struct representing ARP cache info #[derive(Builder, Getters, Debug, Size, ToBytes, FromBytes)] #[builder(pattern = "owned")] pub struct NdaCacheinfo { /// Confirmed #[getset(get = "pub")] ndm_confirmed: u32, /// Used #[getset(get = "pub")] ndm_used: u32, /// Updated #[getset(get = "pub")] ndm_updated: u32, /// Reference count #[getset(get = "pub")] ndm_refcnt: u32, } /// Message in response to queuing discipline operations #[derive(Builder, Getters, Clone, Debug, Size, ToBytes, FromBytesWithInput, Header)] #[builder(pattern = "owned")] pub struct Tcmsg { /// Family #[getset(get = "pub")] tcm_family: libc::c_uchar, #[builder(setter(skip))] #[builder(default = "0")] padding_char: libc::c_uchar, #[builder(setter(skip))] #[builder(default = "0")] padding_short: libc::c_ushort, /// Interface index #[getset(get = "pub")] tcm_ifindex: libc::c_int, /// Queuing discipline handle #[getset(get = "pub")] tcm_handle: u32, /// Parent queuing discipline #[getset(get = "pub")] tcm_parent: u32, /// Info #[getset(get = "pub")] tcm_info: u32, /// Payload of [`Rtattr`]s #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")] #[getset(get = "pub")] #[builder(default = "RtBuffer::new()")] rtattrs: RtBuffer, } /// Struct representing VLAN Flags #[derive(Builder, Getters, Debug, Size, ToBytes, FromBytes)] #[builder(pattern = "owned")] pub struct IflaVlanFlags { /// Flags #[getset(get = "pub")] flags: VlanFlags, /// Mask #[getset(get = "pub")] mask: VlanFlags, } /// Struct representing VLAN QoS mapping #[derive(Builder, Getters, Debug, Size, ToBytes, FromBytes)] #[builder(pattern = "owned")] pub struct IflaVlanQosMapping { /// From #[getset(get = "pub")] from: u32, /// To #[getset(get = "pub")] to: u32, } /// Struct representing route netlink attributes #[derive(Builder, Getters, Clone, Debug, Size, ToBytes, FromBytes, Header)] #[neli(header_bound = "T: RtaType")] #[neli(from_bytes_bound = "T: RtaType")] #[neli(from_bytes_bound = "P: FromBytesWithInput")] #[neli(padding)] #[builder(pattern = "owned")] #[builder(build_fn(skip))] pub struct Rtattr { /// Length of the attribute #[getset(get = "pub")] #[builder(setter(skip))] rta_len: libc::c_ushort, /// Type of the attribute #[getset(get = "pub")] rta_type: T, /// Payload of the attribute #[neli( input = "(rta_len as usize).checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(rta_len as usize))?" )] #[getset(get = "pub")] rta_payload: P, } impl RtattrBuilder where T: Size, P: Size + ToBytes, { /// Build an [`Rtattr`]. pub fn build(self) -> Result, RtattrBuilderError> { let rta_type = self .rta_type .ok_or_else(|| RtattrBuilderError::from(UninitializedFieldError::new("rta_type")))?; let rta_payload = self .rta_payload .ok_or_else(|| RtattrBuilderError::from(UninitializedFieldError::new("rta_payload")))?; let mut buffer = Cursor::new(vec![0; rta_payload.unpadded_size()]); rta_payload.to_bytes(&mut buffer).map_err(|_| { RtattrBuilderError::ValidationError( "Could not convert payload to binary representation".to_string(), ) })?; let mut rtattr = Rtattr { rta_len: 0, rta_type, rta_payload: Buffer::from(buffer.into_inner()), }; rtattr.rta_len = rtattr.unpadded_size() as libc::c_ushort; Ok(rtattr) } } impl Rtattr where T: RtaType, { /// Builder method to add a nested attribute to the end of the payload. /// /// Use this to construct an attribute and nest attributes within it in one method chain. pub fn nest(mut self, attr: &Rtattr) -> Result where TT: RtaType, P: ToBytes, { self.add_nested_attribute(attr)?; Ok(self) } /// Add a nested attribute to the end of the payload. fn add_nested_attribute(&mut self, attr: &Rtattr) -> Result<(), SerError> where TT: RtaType, P: ToBytes, { let mut buffer = Cursor::new(Vec::new()); attr.to_bytes(&mut buffer)?; self.rta_payload.extend_from_slice(buffer.get_ref()); self.rta_len += buffer.get_ref().len() as u16; Ok(()) } /// Return an [`AttrHandle`] for /// attributes nested in the given attribute payload. pub fn get_attr_handle(&self) -> Result, DeError> where R: RtaType, { Ok(AttrHandle::new(RtBuffer::from_bytes_with_input( &mut Cursor::new(self.rta_payload.as_ref()), self.rta_payload.len(), )?)) } } impl Attribute for Rtattr where T: RtaType, { fn payload(&self) -> &Buffer { &self.rta_payload } fn set_payload

(&mut self, payload: &P) -> Result<(), SerError> where P: Size + ToBytes, { let mut buffer = Cursor::new(Vec::new()); payload.to_bytes(&mut buffer)?; // Update `Nlattr` with new length self.rta_len -= self.rta_payload.unpadded_size() as u16; self.rta_len += buffer.get_ref().len() as u16; self.rta_payload = Buffer::from(buffer.into_inner()); Ok(()) } } /// Represents a routing netlink attribute handle. pub type RtAttrHandle<'a, T> = AttrHandle<'a, RtBuffer, Rtattr>; impl<'a, T> RtAttrHandle<'a, T> where T: RtaType, { /// Get the payload of an attribute as a handle for parsing /// nested attributes. pub fn get_nested_attributes(&self, subattr: T) -> Result, DeError> where S: RtaType, { let payload = self .get_attribute(subattr) .ok_or_else(|| DeError::new("Couldn't find specified attribute"))? .rta_payload .as_ref(); Ok(AttrHandle::new(RtBuffer::from_bytes_with_input( &mut Cursor::new(payload), payload.len(), )?)) } /// Get nested attributes from a parsed handle. pub fn get_attribute(&self, t: T) -> Option<&Rtattr> { self.get_attrs().iter().find(|item| item.rta_type == t) } /// Parse binary payload as a type that implements [`FromBytes`]. pub fn get_attr_payload_as(&self, attr: T) -> Result where R: FromBytes, { match self.get_attribute(attr) { Some(a) => a.get_payload_as::(), _ => Err(DeError::new("Failed to find specified attribute")), } } /// Parse binary payload as a type that implements [`FromBytesWithInput`]. pub fn get_attr_payload_as_with_len(&self, attr: T) -> Result where R: FromBytesWithInput, { match self.get_attribute(attr) { Some(a) => a.get_payload_as_with_len::(), _ => Err(DeError::new("Failed to find specified attribute")), } } /// Parse binary payload as a type that implements [`FromBytesWithInput`]. pub fn get_attr_payload_as_with_len_borrowed(&'a self, attr: T) -> Result where R: FromBytesWithInputBorrowed<'a, Input = usize>, { match self.get_attribute(attr) { Some(a) => a.get_payload_as_with_len_borrowed::(), _ => Err(DeError::new("Failed to find specified attribute")), } } } #[cfg(test)] mod test { use super::*; use std::net::Ipv4Addr; use byteorder::{NativeEndian, WriteBytesExt}; use crate::{ consts::{nl::NlmF, socket::NlFamily}, err::RouterError, nl::NlPayload, router::synchronous::NlRouter, test::setup, utils::Groups, }; #[test] fn test_rta_deserialize() { setup(); let mut buf = Cursor::new(vec![]); buf.write_u16::(4).unwrap(); buf.write_u16::(0).unwrap(); buf.set_position(0); Rtattr::::from_bytes(&mut buf).unwrap(); } #[test] fn test_rta_deserialize_err() { setup(); // 3 bytes is below minimum length let mut buf = Cursor::new(vec![]); buf.write_u16::(3).unwrap(); buf.write_u16::(0).unwrap(); buf.set_position(0); Rtattr::::from_bytes(&mut buf).unwrap_err(); } #[test] fn test_rtattr_padding() { setup(); let attr = Rtattr { rta_len: 5, rta_type: Rta::Unspec, rta_payload: vec![0u8], }; let mut buffer = Cursor::new(Vec::new()); let buf_res = attr.to_bytes(&mut buffer); buf_res.unwrap(); // padding check assert_eq!(buffer.into_inner().len(), 8); } #[test] fn real_test_ifinfomsg() { setup(); let (sock, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty()).unwrap(); sock.enable_strict_checking(true).unwrap(); let mut recv = sock .send::<_, _, Rtm, Ifinfomsg>( Rtm::Getlink, NlmF::DUMP | NlmF::ACK, NlPayload::Payload( IfinfomsgBuilder::default() .ifi_family(RtAddrFamily::Unspecified) .build() .unwrap(), ), ) .unwrap(); let all_msgs = recv .try_fold(Vec::new(), |mut v, m| { v.push(m?); Result::<_, RouterError>::Ok(v) }) .unwrap(); let non_err_payloads = all_msgs.iter().fold(Vec::new(), |mut v, m| { if let Some(p) = m.get_payload() { v.push(p); } v }); if non_err_payloads.is_empty() { panic!("Only received done message and no additional information"); } for payload in non_err_payloads { let handle = payload.rtattrs.get_attr_handle(); handle .get_attr_payload_as_with_len::(Ifla::Ifname) .unwrap(); // Assert length of ethernet address if let Ok(attr) = handle.get_attr_payload_as_with_len::>(Ifla::Address) { assert_eq!(attr.len(), 6); } } } #[test] fn real_test_tcmsg() { setup(); let (sock, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty()).unwrap(); sock.enable_strict_checking(true).unwrap(); let recv = sock .send::<_, _, Rtm, Tcmsg>( Rtm::Getqdisc, NlmF::DUMP | NlmF::ACK, NlPayload::Payload( TcmsgBuilder::default() .tcm_family(0) .tcm_ifindex(0) .tcm_handle(0) .tcm_parent(0) .tcm_info(0) .build() .unwrap(), ), ) .unwrap(); for msg in recv { let msg = msg.unwrap(); assert!(matches!(msg.get_payload(), Some(Tcmsg { .. }) | None)); assert!(matches!( msg.nl_type(), Rtm::Newqdisc | Rtm::UnrecognizedConst(3) )); } } #[test] #[cfg(target_env = "gnu")] fn real_test_rtmsg_search() { setup(); let dstip = Ipv4Addr::new(127, 0, 0, 1); let raw_dstip = u32::from(dstip).to_be(); let route_attr = RtattrBuilder::default() .rta_type(Rta::Dst) .rta_payload(raw_dstip) .build() .unwrap(); let mut route_payload = RtBuffer::new(); route_payload.push(route_attr); let (rtnl, _) = NlRouter::connect(NlFamily::Route, None, Groups::empty()).unwrap(); let ifroutemsg = RtmsgBuilder::default() .rtm_family(RtAddrFamily::Inet) .rtm_dst_len(32) .rtm_src_len(0) .rtm_tos(0) .rtm_table(RtTable::Unspec) .rtm_protocol(Rtprot::Unspec) .rtm_scope(RtScope::Universe) .rtm_type(Rtn::Unspec) .rtm_flags(RtmF::from(libc::RTM_F_LOOKUP_TABLE)) .rtattrs(route_payload) .build() .unwrap(); let recv = rtnl .send::<_, _, Rtm, Rtmsg>(Rtm::Getroute, NlmF::REQUEST, NlPayload::Payload(ifroutemsg)) .unwrap(); assert!(recv.count() > 0); } } neli-0.7.4/src/socket/asynchronous.rs000064400000000000000000000171461046102023000157540ustar 00000000000000use std::{ fmt::Debug, io::Cursor, os::unix::io::{AsRawFd, IntoRawFd, RawFd}, }; use log::trace; use tokio::io::unix::AsyncFd; use crate::{ consts::{nl::*, socket::*}, err::SocketError, iter::NlBufferIter, nl::Nlmsghdr, socket::shared::NlSocket, types::NlBuffer, utils::{ asynchronous::{BufferPool, BufferPoolGuard}, Groups, NetlinkBitArray, }, FromBytesWithInput, Size, ToBytes, }; /// Tokio-enabled Netlink socket struct pub struct NlSocketHandle { pub(super) socket: AsyncFd, pool: BufferPool, pid: u32, } impl NlSocketHandle { /// Set up asynchronous socket handle. pub fn connect(proto: NlFamily, pid: Option, groups: Groups) -> Result { let socket = NlSocket::connect(proto, pid, groups)?; socket.nonblock()?; let pid = socket.pid()?; Ok(NlSocketHandle { socket: AsyncFd::new(socket)?, pool: BufferPool::default(), pid, }) } /// Join multicast groups for a socket. pub fn add_mcast_membership(&self, groups: Groups) -> Result<(), SocketError> { self.socket .get_ref() .add_mcast_membership(groups) .map_err(SocketError::from) } /// Leave multicast groups for a socket. pub fn drop_mcast_membership(&self, groups: Groups) -> Result<(), SocketError> { self.socket .get_ref() .drop_mcast_membership(groups) .map_err(SocketError::from) } /// List joined groups for a socket. pub fn list_mcast_membership(&self) -> Result { self.socket .get_ref() .list_mcast_membership() .map_err(SocketError::from) } /// Get the PID for the current socket. pub fn pid(&self) -> u32 { self.pid } /// Send a message on the socket asynchronously. pub async fn send(&self, msg: &Nlmsghdr) -> Result<(), SocketError> where T: NlType, P: Size + ToBytes, { let mut buffer = Cursor::new(vec![0; msg.padded_size()]); msg.to_bytes(&mut buffer)?; loop { let mut guard = self.socket.writable().await?; match guard.try_io(|socket| socket.get_ref().send(buffer.get_ref(), Msg::empty())) { Ok(Ok(_)) => { break; } Ok(Err(e)) => return Err(SocketError::from(e)), Err(_) => (), }; } Ok(()) } /// Receive a message from the socket asynchronously. pub async fn recv( &self, ) -> Result<(NlBufferIter>, Groups), SocketError> where T: NlType, P: Size + FromBytesWithInput, { let groups; let mut buffer = self.pool.acquire().await; loop { let mut guard = self.socket.readable().await?; match guard.try_io(|socket| socket.get_ref().recv(buffer.as_mut_slice(), Msg::empty())) { Ok(Ok((bytes, group))) => { buffer.reduce_size(bytes); groups = group; break; } Ok(Err(e)) => return Err(SocketError::from(e)), Err(_) => (), }; } trace!("Buffer received: {:?}", buffer.as_ref()); Ok((NlBufferIter::new(Cursor::new(buffer)), groups)) } /// Parse all [`Nlmsghdr`][crate::nl::Nlmsghdr] structs sent in /// one network packet and return them all in a list. /// /// Failure to parse any packet will cause the entire operation /// to fail. If an error is detected at the application level, /// this method will discard any non-error /// [`Nlmsghdr`][crate::nl::Nlmsghdr] structs and only return the /// error. For a more granular approach, use either [`NlSocketHandle::recv`]. pub async fn recv_all(&self) -> Result<(NlBuffer, Groups), SocketError> where T: NlType + Debug, P: Size + FromBytesWithInput + Debug, { let groups; let mut buffer = self.pool.acquire().await; let bytes_read; loop { let mut guard = self.socket.readable().await?; match guard.try_io(|socket| socket.get_ref().recv(buffer.as_mut_slice(), Msg::empty())) { Ok(Ok((bytes, group))) => { if bytes == 0 { return Ok((NlBuffer::new(), Groups::empty())); } groups = group; bytes_read = bytes; buffer.reduce_size(bytes); break; } Ok(Err(e)) => return Err(SocketError::from(e)), Err(_) => (), }; } let vec = NlBuffer::from_bytes_with_input(&mut Cursor::new(buffer), bytes_read)?; trace!("Messages received: {vec:?}"); Ok((vec, groups)) } /// Set the size of the receive buffer for the socket. /// /// This can be useful when communicating with a service that sends a high volume of /// messages (especially multicast), and your application cannot process them fast enough, /// leading to the kernel dropping messages. A larger buffer may help mitigate this. /// /// The value passed is a hint to the kernel to set the size of the receive buffer. /// The kernel will double the value provided to account for bookkeeping overhead. /// The doubled value is capped by the value in `/proc/sys/net/core/rmem_max`. /// /// The default value is `/proc/sys/net/core/rmem_default` /// /// See `socket(7)` documentation for `SO_RCVBUF` for more information. pub fn set_recv_buffer_size(&self, size: usize) -> Result<(), SocketError> { self.socket .get_ref() .set_recv_buffer_size(size) .map_err(SocketError::from) } /// If [`true`] is passed in, enable extended ACKs for this socket. If [`false`] /// is passed in, disable extended ACKs for this socket. pub fn enable_ext_ack(&self, enable: bool) -> Result<(), SocketError> { self.socket .get_ref() .enable_ext_ack(enable) .map_err(SocketError::from) } /// Return [`true`] if an extended ACK is enabled for this socket. pub fn get_ext_ack_enabled(&self) -> Result { self.socket .get_ref() .get_ext_ack_enabled() .map_err(SocketError::from) } /// If [`true`] is passed in, enable strict checking for this socket. If [`false`] /// is passed in, disable strict checking for for this socket. /// Only supported by `NlFamily::Route` sockets. /// Requires Linux >= 4.20. pub fn enable_strict_checking(&self, enable: bool) -> Result<(), SocketError> { self.socket .get_ref() .enable_strict_checking(enable) .map_err(SocketError::from) } /// Return [`true`] if strict checking is enabled for this socket. /// Only supported by `NlFamily::Route` sockets. /// Requires Linux >= 4.20. pub fn get_strict_checking_enabled(&self) -> Result { self.socket .get_ref() .get_strict_checking_enabled() .map_err(SocketError::from) } } impl AsRawFd for NlSocketHandle { fn as_raw_fd(&self) -> RawFd { self.socket.get_ref().as_raw_fd() } } impl IntoRawFd for NlSocketHandle { fn into_raw_fd(self) -> RawFd { self.socket.into_inner().into_raw_fd() } } neli-0.7.4/src/socket/mod.rs000064400000000000000000000037511046102023000137750ustar 00000000000000//! This module provides code that glues all of the other modules //! together and allows message send and receive operations. //! //! ## Important methods //! * [`NlSocket::send`][crate::socket::NlSocket::send] and //! [`NlSocket::recv`][crate::socket::NlSocket::recv] methods are meant to //! be the most low level calls. They essentially do what the C //! system calls `send` and `recv` do with very little abstraction. //! * [`NlSocketHandle::send`][crate::socket::NlSocket::send] and //! [`NlSocketHandle::recv`][crate::socket::NlSocket::recv] methods //! are meant to provide an interface that is more idiomatic for //! the library. //! //! ## Features //! The `async` feature exposed by `cargo` allows the socket to use //! Rust's [tokio](https://tokio.rs) for async IO. //! //! ## Additional methods //! //! There are methods for blocking and non-blocking, resolving //! generic netlink multicast group IDs, and other convenience //! functions so see if your use case is supported. If it isn't, //! please open a Github issue and submit a feature request. //! //! ## Design decisions //! //! The buffer allocated in the [`BufferPool`][crate::utils::synchronous::BufferPool] //! structure should be allocated on the heap. This is intentional as a buffer //! that large could be a problem on the stack. //! //! neli now uses [`BufferPool`][crate::utils::synchronous::BufferPool] to manage //! parallel message receive operations. Memory usage can be tuned using the following //! environment variables at compile time: //! * `NELI_AUTO_BUFFER_LEN`: This configures how many bytes are allocated for each //! buffer in the buffer pool. //! * `NELI_MAX_PARALLEL_READ_OPS`: This configures how many buffers of size //! `NELI_AUTO_BUFFER_LEN` are allocated for parallel receive operations. /// Asynchronous socket operations #[cfg(feature = "async")] pub mod asynchronous; mod shared; /// Synchronous socket operations #[cfg(feature = "sync")] pub mod synchronous; pub use crate::socket::shared::NlSocket; neli-0.7.4/src/socket/shared.rs000064400000000000000000000326451046102023000144700ustar 00000000000000use std::{ io, mem::{size_of, zeroed, MaybeUninit}, os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}, }; use libc::{c_int, c_void, sockaddr, sockaddr_nl}; #[cfg(feature = "async")] use crate::socket::asynchronous; #[cfg(feature = "sync")] use crate::socket::synchronous; use crate::{ consts::socket::*, utils::{Groups, NetlinkBitArray}, }; /// Low level access to a netlink socket. pub struct NlSocket { fd: c_int, } impl NlSocket { /// Wrapper around `socket()` syscall filling in the /// netlink-specific information. pub fn new(proto: NlFamily) -> Result { let fd = match unsafe { libc::socket( AddrFamily::Netlink.into(), libc::SOCK_RAW | libc::SOCK_CLOEXEC, proto.into(), ) } { i if i >= 0 => Ok(i), _ => Err(io::Error::last_os_error()), }?; Ok(NlSocket { fd }) } /// Equivalent of `socket` and `bind` calls. pub fn connect(proto: NlFamily, pid: Option, groups: Groups) -> Result { let s = NlSocket::new(proto)?; s.bind(pid, groups)?; Ok(s) } /// Set underlying socket file descriptor to be blocking. pub fn block(&self) -> Result<(), io::Error> { match unsafe { libc::fcntl( self.fd, libc::F_SETFL, libc::fcntl(self.fd, libc::F_GETFL, 0) & !libc::O_NONBLOCK, ) } { i if i < 0 => Err(io::Error::last_os_error()), _ => Ok(()), } } /// Set underlying socket file descriptor to be non blocking. pub fn nonblock(&self) -> Result<(), io::Error> { match unsafe { libc::fcntl( self.fd, libc::F_SETFL, libc::fcntl(self.fd, libc::F_GETFL, 0) | libc::O_NONBLOCK, ) } { i if i < 0 => Err(io::Error::last_os_error()), _ => Ok(()), } } /// Determines if underlying file descriptor is blocking. pub fn is_blocking(&self) -> Result { let is_blocking = match unsafe { libc::fcntl(self.fd, libc::F_GETFL, 0) } { i if i >= 0 => i & libc::O_NONBLOCK == 0, _ => return Err(io::Error::last_os_error()), }; Ok(is_blocking) } /// Use this function to bind to a netlink ID and subscribe to /// groups. See netlink(7) man pages for more information on /// netlink IDs and groups. pub fn bind(&self, pid: Option, groups: Groups) -> Result<(), io::Error> { let mut nladdr = unsafe { zeroed::() }; nladdr.nl_family = c_int::from(AddrFamily::Netlink) as u16; nladdr.nl_pid = pid.unwrap_or(0); match unsafe { libc::bind( self.fd, &nladdr as *const _ as *const libc::sockaddr, size_of::() as u32, ) } { i if i >= 0 => (), _ => return Err(io::Error::last_os_error()), }; self.add_mcast_membership(groups)?; Ok(()) } /// Set the size of the receive buffer for the socket. /// /// This can be useful when communicating with a service that sends a high volume of /// messages (especially multicast), and your application cannot process them fast enough, /// leading to the kernel dropping messages. A larger buffer may help mitigate this. /// /// The value passed is a hint to the kernel to set the size of the receive buffer. /// The kernel will double the value provided to account for bookkeeping overhead. /// The doubled value is capped by the value in `/proc/sys/net/core/rmem_max`. /// /// The default value is `/proc/sys/net/core/rmem_default` /// /// See `socket(7)` documentation for `SO_RCVBUF` for more information. pub fn set_recv_buffer_size(&self, size: usize) -> Result<(), io::Error> { let size = size as c_int; match unsafe { libc::setsockopt( self.fd, libc::SOL_SOCKET, libc::SO_RCVBUF, &size as *const _ as *const c_void, size_of::() as libc::socklen_t, ) } { 0 => Ok(()), _ => Err(io::Error::last_os_error()), } } /// Join multicast groups for a socket. pub fn add_mcast_membership(&self, groups: Groups) -> Result<(), io::Error> { for group in groups.as_groups() { match unsafe { libc::setsockopt( self.fd, libc::SOL_NETLINK, libc::NETLINK_ADD_MEMBERSHIP, &group as *const _ as *const libc::c_void, size_of::() as libc::socklen_t, ) } { 0 => (), _ => return Err(io::Error::last_os_error()), } } Ok(()) } /// Leave multicast groups for a socket. pub fn drop_mcast_membership(&self, groups: Groups) -> Result<(), io::Error> { for group in groups.as_groups() { match unsafe { libc::setsockopt( self.fd, libc::SOL_NETLINK, libc::NETLINK_DROP_MEMBERSHIP, &group as *const _ as *const libc::c_void, size_of::() as libc::socklen_t, ) } { 0 => (), _ => return Err(io::Error::last_os_error()), } } Ok(()) } /// List joined groups for a socket. pub fn list_mcast_membership(&self) -> Result { let mut bit_array = NetlinkBitArray::new(4); let mut len: libc::socklen_t = bit_array.len() as libc::socklen_t; if unsafe { libc::getsockopt( self.fd, libc::SOL_NETLINK, libc::NETLINK_LIST_MEMBERSHIPS, bit_array.as_mut_slice() as *mut _ as *mut libc::c_void, &mut len as *mut _ as *mut libc::socklen_t, ) } != 0 { return Err(io::Error::last_os_error()); } if len > bit_array.len() as libc::socklen_t { bit_array.resize(len as usize); if unsafe { libc::getsockopt( self.fd, libc::SOL_NETLINK, libc::NETLINK_LIST_MEMBERSHIPS, bit_array.as_mut_slice() as *mut _ as *mut libc::c_void, &mut len as *mut _ as *mut libc::socklen_t, ) } != 0 { return Err(io::Error::last_os_error()); } } Ok(bit_array) } /// Send message encoded as byte slice to the netlink ID /// specified in the netlink header /// [`Nlmsghdr`][crate::nl::Nlmsghdr] pub fn send(&self, buf: B, flags: Msg) -> Result where B: AsRef<[u8]>, { match unsafe { libc::send( self.fd, buf.as_ref() as *const _ as *const c_void, buf.as_ref().len(), flags.bits() as i32, ) } { i if i >= 0 => Ok(i as libc::size_t), _ => Err(io::Error::last_os_error()), } } /// Receive message encoded as byte slice from the netlink socket. pub fn recv(&self, mut buf: B, flags: Msg) -> Result<(libc::size_t, Groups), io::Error> where B: AsMut<[u8]>, { let mut addr = unsafe { std::mem::zeroed::() }; let mut size: u32 = size_of::().try_into().unwrap_or(0); match unsafe { libc::recvfrom( self.fd, buf.as_mut() as *mut _ as *mut c_void, buf.as_mut().len(), flags.bits() as i32, &mut addr as *mut _ as *mut sockaddr, &mut size, ) } { i if i >= 0 => Ok((i as libc::size_t, Groups::new_bitmask(addr.nl_groups))), i if i == -libc::EWOULDBLOCK as isize => { Err(io::Error::from(io::ErrorKind::WouldBlock)) } _ => Err(io::Error::last_os_error()), } } /// Get the PID for this socket. pub fn pid(&self) -> Result { let mut sock_len = size_of::() as u32; let mut sock_addr: MaybeUninit = MaybeUninit::uninit(); match unsafe { libc::getsockname( self.fd, sock_addr.as_mut_ptr() as *mut _, &mut sock_len as *mut _, ) } { i if i >= 0 => Ok(unsafe { sock_addr.assume_init() }.nl_pid), _ => Err(io::Error::last_os_error()), } } /// If [`true`] is passed in, enable extended ACKs for this socket. If [`false`] /// is passed in, disable extended ACKs for this socket. pub fn enable_ext_ack(&self, enable: bool) -> Result<(), io::Error> { match unsafe { libc::setsockopt( self.fd, libc::SOL_NETLINK, libc::NETLINK_EXT_ACK, &c_int::from(enable) as *const _ as *const libc::c_void, size_of::() as libc::socklen_t, ) } { 0 => Ok(()), _ => Err(io::Error::last_os_error()), } } /// Return [`true`] if an extended ACK is enabled for this socket. pub fn get_ext_ack_enabled(&self) -> Result { let mut sock_len = size_of::() as libc::socklen_t; let mut sock_val: MaybeUninit = MaybeUninit::uninit(); match unsafe { libc::getsockopt( self.fd, libc::SOL_NETLINK, libc::NETLINK_EXT_ACK, &mut sock_val as *mut _ as *mut libc::c_void, &mut sock_len as *mut _ as *mut libc::socklen_t, ) } { 0 => Ok(unsafe { sock_val.assume_init() } != 0), _ => Err(io::Error::last_os_error()), } } /// If [`true`] is passed in, enable strict checking for this socket. If [`false`] /// is passed in, disable strict checking for for this socket. /// Only supported by `NlFamily::Route` sockets. /// Requires Linux >= 4.20. pub fn enable_strict_checking(&self, enable: bool) -> Result<(), io::Error> { match unsafe { libc::setsockopt( self.fd, libc::SOL_NETLINK, libc::NETLINK_GET_STRICT_CHK, &libc::c_int::from(enable) as *const _ as *const libc::c_void, size_of::() as libc::socklen_t, ) } { 0 => Ok(()), _ => Err(io::Error::last_os_error()), } } /// Return [`true`] if strict checking is enabled for this socket. /// Only supported by `NlFamily::Route` sockets. /// Requires Linux >= 4.20. pub fn get_strict_checking_enabled(&self) -> Result { let mut sock_len = size_of::() as libc::socklen_t; let mut sock_val: MaybeUninit = MaybeUninit::uninit(); match unsafe { libc::getsockopt( self.fd, libc::SOL_NETLINK, libc::NETLINK_GET_STRICT_CHK, &mut sock_val as *mut _ as *mut libc::c_void, &mut sock_len as *mut _ as *mut libc::socklen_t, ) } { 0 => Ok(unsafe { sock_val.assume_init() } != 0), _ => Err(io::Error::last_os_error()), } } } #[cfg(feature = "sync")] impl From for NlSocket { fn from(s: synchronous::NlSocketHandle) -> Self { s.socket } } #[cfg(feature = "async")] impl From for NlSocket { fn from(s: asynchronous::NlSocketHandle) -> Self { s.socket.into_inner() } } impl AsRawFd for NlSocket { fn as_raw_fd(&self) -> RawFd { self.fd } } impl IntoRawFd for NlSocket { fn into_raw_fd(self) -> RawFd { let fd = self.fd; std::mem::forget(self); fd } } impl FromRawFd for NlSocket { unsafe fn from_raw_fd(fd: RawFd) -> Self { NlSocket { fd } } } impl Drop for NlSocket { /// Closes underlying file descriptor to avoid file descriptor /// leaks. fn drop(&mut self) { unsafe { libc::close(self.fd); } } } #[cfg(test)] mod test { use super::*; use crate::test::setup; #[test] fn real_test_pid() { setup(); let s = NlSocket::connect(NlFamily::Generic, Some(5555), Groups::empty()).unwrap(); assert_eq!(s.pid().unwrap(), 5555); } #[test] fn real_ext_ack() { setup(); let s = NlSocket::connect(NlFamily::Generic, None, Groups::empty()).unwrap(); assert!(!s.get_ext_ack_enabled().unwrap()); s.enable_ext_ack(true).unwrap(); assert!(s.get_ext_ack_enabled().unwrap()); } #[test] fn real_strict_checking() { setup(); let s = NlSocket::connect(NlFamily::Route, None, Groups::empty()).unwrap(); assert!(!s.get_strict_checking_enabled().unwrap()); s.enable_strict_checking(true).unwrap(); assert!(s.get_strict_checking_enabled().unwrap()); } } neli-0.7.4/src/socket/synchronous.rs000064400000000000000000000152771046102023000156160ustar 00000000000000use std::{ fmt::Debug, io::Cursor, os::unix::io::{AsRawFd, IntoRawFd, RawFd}, }; use log::trace; use crate::{ consts::{nl::*, socket::*}, err::SocketError, iter::NlBufferIter, nl::Nlmsghdr, socket::shared::NlSocket, types::NlBuffer, utils::{ synchronous::{BufferPool, BufferPoolGuard}, Groups, NetlinkBitArray, }, FromBytesWithInput, Size, ToBytes, }; /// Higher level handle for socket operations. pub struct NlSocketHandle { pub(super) socket: NlSocket, pid: u32, pool: BufferPool, } impl NlSocketHandle { /// Equivalent of `socket` and `bind` calls. pub fn connect(proto: NlFamily, pid: Option, groups: Groups) -> Result { let socket = NlSocket::connect(proto, pid, groups)?; socket.block()?; let pid = socket.pid()?; Ok(NlSocketHandle { socket, pid, pool: BufferPool::default(), }) } /// Join multicast groups for a socket. pub fn add_mcast_membership(&self, groups: Groups) -> Result<(), SocketError> { self.socket .add_mcast_membership(groups) .map_err(SocketError::from) } /// Leave multicast groups for a socket. pub fn drop_mcast_membership(&self, groups: Groups) -> Result<(), SocketError> { self.socket .drop_mcast_membership(groups) .map_err(SocketError::from) } /// List joined groups for a socket. pub fn list_mcast_membership(&self) -> Result { self.socket .list_mcast_membership() .map_err(SocketError::from) } /// Get the PID for the current socket. pub fn pid(&self) -> u32 { self.pid } /// Convenience function to send an [`Nlmsghdr`] struct pub fn send(&self, msg: &Nlmsghdr) -> Result<(), SocketError> where T: NlType + Debug, P: Size + ToBytes + Debug, { trace!("Message sent:\n{msg:?}"); let mut buffer = Cursor::new(vec![0; msg.padded_size()]); msg.to_bytes(&mut buffer)?; trace!("Buffer sent: {:?}", buffer.get_ref()); self.socket.send(buffer.get_ref(), Msg::empty())?; Ok(()) } /// Convenience function to read a stream of [`Nlmsghdr`] /// structs one by one using an iterator. /// /// Returns [`None`] when the stream of messages has been completely processed in /// the current buffer resulting from a single /// [`NlSocket::recv`][crate::socket::NlSocket::recv] call. /// /// See [`NlBufferIter`] for more detailed information. pub fn recv( &self, ) -> Result<(NlBufferIter>, Groups), SocketError> where T: NlType + Debug, P: Size + FromBytesWithInput + Debug, { let mut buffer = self.pool.acquire(); let (mem_read, groups) = self.socket.recv(&mut buffer, Msg::empty())?; buffer.reduce_size(mem_read); trace!("Buffer received: {:?}", buffer.as_ref()); Ok((NlBufferIter::new(Cursor::new(buffer)), groups)) } /// Parse all [`Nlmsghdr`] structs sent in /// one network packet and return them all in a list. /// /// Failure to parse any packet will cause the entire operation /// to fail. If an error is detected at the application level, /// this method will discard any non-error /// [`Nlmsghdr`] structs and only return the /// error. For a more granular approach, use [`NlSocketHandle::recv`]. pub fn recv_all(&self) -> Result<(NlBuffer, Groups), SocketError> where T: NlType + Debug, P: Size + FromBytesWithInput + Debug, { let mut buffer = self.pool.acquire(); let (mem_read, groups) = self.socket.recv(&mut buffer, Msg::empty())?; if mem_read == 0 { return Ok((NlBuffer::new(), Groups::empty())); } buffer.reduce_size(mem_read); let vec = NlBuffer::from_bytes_with_input(&mut Cursor::new(buffer), mem_read)?; trace!("Messages received: {vec:?}"); Ok((vec, groups)) } /// Set the size of the receive buffer for the socket. /// /// This can be useful when communicating with a service that sends a high volume of /// messages (especially multicast), and your application cannot process them fast enough, /// leading to the kernel dropping messages. A larger buffer may help mitigate this. /// /// The value passed is a hint to the kernel to set the size of the receive buffer. /// The kernel will double the value provided to account for bookkeeping overhead. /// The doubled value is capped by the value in `/proc/sys/net/core/rmem_max`. /// /// The default value is `/proc/sys/net/core/rmem_default` /// /// See `socket(7)` documentation for `SO_RCVBUF` for more information. pub fn set_recv_buffer_size(&self, size: usize) -> Result<(), SocketError> { self.socket .set_recv_buffer_size(size) .map_err(SocketError::from) } /// If [`true`] is passed in, enable extended ACKs for this socket. If [`false`] /// is passed in, disable extended ACKs for this socket. pub fn enable_ext_ack(&self, enable: bool) -> Result<(), SocketError> { self.socket .enable_ext_ack(enable) .map_err(SocketError::from) } /// Return [`true`] if an extended ACK is enabled for this socket. pub fn get_ext_ack_enabled(&self) -> Result { self.socket.get_ext_ack_enabled().map_err(SocketError::from) } /// If [`true`] is passed in, enable strict checking for this socket. If [`false`] /// is passed in, disable strict checking for for this socket. /// Only supported by `NlFamily::Route` sockets. /// Requires Linux >= 4.20. pub fn enable_strict_checking(&self, enable: bool) -> Result<(), SocketError> { self.socket .enable_strict_checking(enable) .map_err(SocketError::from) } /// Return [`true`] if strict checking is enabled for this socket. /// Only supported by `NlFamily::Route` sockets. /// Requires Linux >= 4.20. pub fn get_strict_checking_enabled(&self) -> Result { self.socket .get_strict_checking_enabled() .map_err(SocketError::from) } pub(in super::super) fn set_nonblock(&self) -> Result<(), SocketError> { self.socket.nonblock().map_err(SocketError::from) } } impl AsRawFd for NlSocketHandle { fn as_raw_fd(&self) -> RawFd { self.socket.as_raw_fd() } } impl IntoRawFd for NlSocketHandle { fn into_raw_fd(self) -> RawFd { self.socket.into_raw_fd() } } neli-0.7.4/src/types.rs000064400000000000000000000316311046102023000130700ustar 00000000000000//! Module containing various types used across the various netlink //! structures used in `neli`. //! //! # Design decisions //! These structures are new types rather than type aliases in most //! cases to allow the internal representation to change without //! resulting in a breaking change. use std::{ fmt::{self, Debug}, io::{Read, Write}, iter::FromIterator, slice::{Iter, IterMut}, }; use crate::{ self as neli, attr::AttrHandle, consts::{genl::NlAttrType, nl::NlType, rtnl::RtaType}, err::DeError, genl::{AttrTypeBuilder, GenlAttrHandle, Nlattr, NlattrBuilder}, nl::Nlmsghdr, rtnl::{RtAttrHandle, Rtattr}, FromBytesWithInput, Size, ToBytes, }; /// A buffer of bytes. #[derive(Clone, PartialEq, Eq, Size)] pub struct Buffer(Vec); impl FromBytesWithInput for Buffer { type Input = usize; fn from_bytes_with_input( buffer: &mut std::io::Cursor>, input: Self::Input, ) -> Result { if buffer.position() as usize + input > buffer.get_ref().as_ref().len() { return Err(DeError::InvalidInput(input)); } let mut vec = vec![0u8; input]; buffer.read_exact(&mut vec)?; Ok(Self::from(vec)) } } impl ToBytes for Buffer { fn to_bytes(&self, buffer: &mut std::io::Cursor>) -> Result<(), crate::err::SerError> { buffer.write_all(self.0.as_slice())?; Ok(()) } } impl Debug for Buffer { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Buffer") } } impl AsRef<[u8]> for Buffer { fn as_ref(&self) -> &[u8] { self.0.as_slice() } } impl AsMut<[u8]> for Buffer { fn as_mut(&mut self) -> &mut [u8] { self.0.as_mut_slice() } } impl<'a> From<&'a [u8]> for Buffer { fn from(slice: &'a [u8]) -> Self { Buffer(Vec::from(slice)) } } impl From> for Buffer { fn from(vec: Vec) -> Self { Buffer(vec) } } impl From for Vec { fn from(buf: Buffer) -> Self { buf.0 } } impl Buffer { /// Create a new general purpose byte buffer. pub fn new() -> Self { Buffer(Vec::new()) } /// Extend the given buffer with the contents of another slice. pub fn extend_from_slice(&mut self, slice: &[u8]) { self.0.extend_from_slice(slice) } /// Get the current length of the buffer. pub fn len(&self) -> usize { self.0.len() } /// Check whether the buffer is empty. pub fn is_empty(&self) -> bool { self.0.is_empty() } } impl Default for Buffer { fn default() -> Self { Self::new() } } /// A buffer of netlink messages. #[derive(Debug, PartialEq, Eq, Size, FromBytesWithInput, ToBytes)] #[neli(from_bytes_bound = "T: NlType")] #[neli(from_bytes_bound = "P: Size + FromBytesWithInput")] pub struct NlBuffer(#[neli(input)] Vec>); impl FromIterator> for NlBuffer { fn from_iter(i: I) -> Self where I: IntoIterator>, { NlBuffer(Vec::from_iter(i)) } } impl AsRef<[Nlmsghdr]> for NlBuffer { fn as_ref(&self) -> &[Nlmsghdr] { self.0.as_slice() } } impl NlBuffer { /// Create a new buffer of netlink messages. pub fn new() -> Self { NlBuffer(Vec::new()) } /// Add a new netlink message to the end of the buffer. pub fn push(&mut self, msg: Nlmsghdr) { self.0.push(msg); } /// Get a netlink message from the end of the buffer. pub fn pop(&mut self) -> Option> { self.0.pop() } /// Return an iterator over immutable references to the elements /// in the buffer. pub fn iter(&self) -> Iter<'_, Nlmsghdr> { self.0.iter() } /// Return an iterator over mutable references to the elements /// in the buffer. pub fn iter_mut(&mut self) -> IterMut<'_, Nlmsghdr> { self.0.iter_mut() } /// Returns the number of elements in the buffer. pub fn len(&self) -> usize { self.0.len() } /// Returns whether the number of elements in the buffer is 0. pub fn is_empty(&self) -> bool { self.0.is_empty() } } impl IntoIterator for NlBuffer { type Item = Nlmsghdr; type IntoIter = > as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl Default for NlBuffer { fn default() -> Self { Self::new() } } /// A buffer of generic netlink attributes. #[derive(Clone, Debug, PartialEq, Eq, ToBytes, FromBytesWithInput)] #[neli(to_bytes_bound = "T: NlAttrType")] #[neli(from_bytes_bound = "T: NlAttrType")] #[neli(from_bytes_bound = "P: FromBytesWithInput")] pub struct GenlBuffer(#[neli(input)] Vec>); impl neli::Size for GenlBuffer where T: Size, P: Size, { fn unpadded_size(&self) -> usize { self.0.iter().map(|attr| attr.padded_size()).sum() } } impl GenlBuffer { /// Get a data structure with an immutable reference to the /// underlying [`Nlattr`]s. pub fn get_attr_handle(&self) -> AttrHandle<'_, Self, Nlattr> { AttrHandle::new_borrowed(self.0.as_ref()) } } impl GenlBuffer { /// Convert a [`GenlBuffer`] that can represent all types to a buffer that /// is of a particular type. pub fn get_typed_attr_handle(&self) -> Result, DeError> where T: NlAttrType, { Ok(AttrHandle::new({ let mut attrs = GenlBuffer::new(); for attr in self.0.iter() { attrs.push( NlattrBuilder::default() .nla_type( AttrTypeBuilder::default() .nla_type(T::from(*attr.nla_type().nla_type())) .nla_nested(*attr.nla_type().nla_nested()) .nla_network_order(*attr.nla_type().nla_network_order()) .build()?, ) .nla_payload(attr.nla_payload().clone()) .build()?, ); } attrs })) } } impl AsRef<[Nlattr]> for GenlBuffer { fn as_ref(&self) -> &[Nlattr] { self.0.as_slice() } } impl AsMut<[Nlattr]> for GenlBuffer { fn as_mut(&mut self) -> &mut [Nlattr] { self.0.as_mut_slice() } } impl FromIterator> for GenlBuffer { fn from_iter(i: I) -> Self where I: IntoIterator>, { GenlBuffer(Vec::from_iter(i)) } } impl IntoIterator for GenlBuffer { type Item = Nlattr; type IntoIter = > as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl GenlBuffer { /// Create a new buffer of generic netlink attributes. pub fn new() -> Self { GenlBuffer(Vec::new()) } /// Add a new generic netlink attribute to the end of the buffer. pub fn push(&mut self, attr: Nlattr) { self.0.push(attr) } /// Get a generic netlink attribute from the end of the buffer. pub fn pop(&mut self) -> Option> { self.0.pop() } /// Return an iterator over immutable references to the elements /// in the buffer. pub fn iter(&self) -> Iter<'_, Nlattr> { self.0.iter() } /// Return an iterator over mutable references to the elements /// in the buffer. pub fn iter_mut(&mut self) -> IterMut<'_, Nlattr> { self.0.iter_mut() } /// Returns the number of elements in the buffer. pub fn len(&self) -> usize { self.0.len() } /// Returns whether the number of elements in the buffer is 0. pub fn is_empty(&self) -> bool { self.0.is_empty() } } impl Default for GenlBuffer { fn default() -> Self { Self::new() } } /// A buffer of rtnetlink attributes. #[derive(Clone, Debug, FromBytesWithInput, ToBytes)] #[neli(from_bytes_bound = "T: RtaType")] #[neli(from_bytes_bound = "P: FromBytesWithInput")] pub struct RtBuffer(#[neli(input)] Vec>); impl neli::Size for RtBuffer where T: Size, P: Size, { fn unpadded_size(&self) -> usize { self.0.iter().map(|attr| attr.padded_size()).sum() } } impl RtBuffer { /// Get a data structure with an immutable reference to the /// underlying [`Rtattr`]s. pub fn get_attr_handle(&self) -> RtAttrHandle<'_, T> { AttrHandle::new_borrowed(self.0.as_ref()) } } impl FromIterator> for RtBuffer { fn from_iter(i: I) -> Self where I: IntoIterator>, { RtBuffer(Vec::from_iter(i)) } } impl IntoIterator for RtBuffer { type Item = Rtattr; type IntoIter = > as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl AsRef<[Rtattr]> for RtBuffer { fn as_ref(&self) -> &[Rtattr] { self.0.as_slice() } } impl AsMut<[Rtattr]> for RtBuffer { fn as_mut(&mut self) -> &mut [Rtattr] { self.0.as_mut_slice() } } impl RtBuffer { /// Create a new buffer of routing netlink attributes. pub fn new() -> Self { RtBuffer(Vec::new()) } /// Add a new routing netlink attribute to the end of the buffer. pub fn push(&mut self, attr: Rtattr) { self.0.push(attr) } /// Get a routing netlink attribute from the end of the buffer. pub fn pop(&mut self) -> Option> { self.0.pop() } /// Return an iterator over immutable references to the elements /// in the buffer. pub fn iter(&self) -> Iter<'_, Rtattr> { self.0.iter() } /// Return an iterator over mutable references to the elements /// in the buffer. pub fn iter_mut(&mut self) -> IterMut<'_, Rtattr> { self.0.iter_mut() } /// Returns the number of elements in the buffer. pub fn len(&self) -> usize { self.0.len() } /// Returns whether the number of elements in the buffer is 0. pub fn is_empty(&self) -> bool { self.0.is_empty() } } impl Default for RtBuffer { fn default() -> Self { Self::new() } } #[cfg(test)] mod test { use super::*; use crate::{ consts::{genl::Index, rtnl::Ifa}, genl::{AttrTypeBuilder, NlattrBuilder}, rtnl::RtattrBuilder, }; #[test] fn test_genlbuffer_align() { assert_eq!( vec![ NlattrBuilder::default() .nla_type( AttrTypeBuilder::default() .nla_type(Index::from(0)) .build() .unwrap(), ) .nla_payload(0u8) .build() .unwrap(), NlattrBuilder::default() .nla_type( AttrTypeBuilder::default() .nla_type(Index::from(1)) .build() .unwrap(), ) .nla_payload(1u8) .build() .unwrap(), NlattrBuilder::default() .nla_type( AttrTypeBuilder::default() .nla_type(Index::from(2)) .build() .unwrap(), ) .nla_payload(2u8) .build() .unwrap(), ] .into_iter() .collect::>() .unpadded_size(), 24 ) } #[test] fn test_rtbuffer_align() { assert_eq!( vec![ RtattrBuilder::default() .rta_type(Ifa::Unspec) .rta_payload(0u8) .build() .unwrap(), RtattrBuilder::default() .rta_type(Ifa::Address) .rta_payload(1u8) .build() .unwrap(), RtattrBuilder::default() .rta_type(Ifa::Local) .rta_payload(2u8) .build() .unwrap(), ] .into_iter() .collect::>() .unpadded_size(), 24 ) } } neli-0.7.4/src/utils.rs000064400000000000000000000455061046102023000130720ustar 00000000000000//! A module containing utilities for working with constructs like //! bitflags and other low level operations. //! //! # Design decisions //! Some of the less documented aspects of interacting with netlink //! are handled internally in the types so that the user does not //! have to be aware of them. use std::mem::size_of; #[cfg(any(feature = "sync", feature = "async"))] use crate::consts::MAX_NL_LENGTH; use crate::err::MsgError; type BitArrayType = u32; /// A bit array meant to be compatible with the bit array /// returned by the `NETLINK_LIST_MEMBERSHIPS` socket operation /// on netlink sockets. pub struct NetlinkBitArray(Vec); /// bittest/bitset instrinsics are not stable in Rust so this /// needs to be implemented this way. #[allow(clippy::len_without_is_empty)] impl NetlinkBitArray { const BIT_SIZE: usize = BitArrayType::BITS as usize; /// Create a new bit array. /// /// This method will round `bit_len` up to the nearest /// multiple of [`size_of::()`][std::mem::size_of]. pub fn new(bit_len: usize) -> Self { let round = Self::BIT_SIZE - 1; NetlinkBitArray(vec![0; ((bit_len + round) & !round) / Self::BIT_SIZE]) } /// Resize the underlying vector to have enough space for /// the nearest multiple of [`size_of::()`][std::mem::size_of] /// rounded up. pub fn resize_bits(&mut self, bit_len: usize) { let round = Self::BIT_SIZE - 1; self.0 .resize(((bit_len + round) & !round) / Self::BIT_SIZE, 0); } /// Resize the underlying vector to have enough space for /// the nearest multiple of [`size_of::()`][std::mem::size_of]. pub fn resize(&mut self, bytes: usize) { let byte_round = size_of::() - 1; self.0.resize( ((bytes + byte_round) & !byte_round) / size_of::(), 0, ); } /// Returns true if the `n`th bit is set. Not zero indexed. pub fn is_set(&self, n: usize) -> bool { if n == 0 { return false; } let n_1 = n - 1; let bit_segment = self.0[n_1 / Self::BIT_SIZE]; if let Some(bit_shifted_n) = u32::try_from(n_1 % Self::BIT_SIZE) .ok() .and_then(|rem| 1u32.checked_shl(rem)) { bit_segment & bit_shifted_n == bit_shifted_n } else { false } } /// Set the `n`th bit. Not zero indexed. pub fn set(&mut self, n: usize) { if n == 0 { return; } let n_1 = n - 1; let bit_segment = self.0[n_1 / Self::BIT_SIZE]; if let Some(bit_shifted_n) = u32::try_from(n_1 % Self::BIT_SIZE) .ok() .and_then(|rem| 1u32.checked_shl(rem)) { self.0[n_1 / Self::BIT_SIZE] = bit_segment | bit_shifted_n; } } /// Get a vector representation of all of the bit positions set /// to 1 in this bit array. /// /// ## Example /// ``` /// use neli::utils::NetlinkBitArray; /// /// let mut array = NetlinkBitArray::new(24); /// array.set(4); /// array.set(7); /// array.set(23); /// assert_eq!(array.to_vec(), vec![4, 7, 23]); /// ``` pub fn to_vec(&self) -> Vec { let mut bits = Vec::new(); for bit in 0..self.len_bits() { let bit_shifted = 1 << (bit % Self::BIT_SIZE); if bit_shifted & self.0[bit / Self::BIT_SIZE] == bit_shifted { bits.push(bit as u32 + 1); } } bits } /// Return the number of bits that can be contained in this bit /// array. pub fn len_bits(&self) -> usize { self.0.len() * Self::BIT_SIZE } /// Return the length in bytes for this bit array. pub fn len(&self) -> usize { self.0.len() * size_of::() } pub(crate) fn as_mut_slice(&mut self) -> &mut [BitArrayType] { self.0.as_mut_slice() } } fn slice_to_mask(groups: &[u32]) -> Result { groups.iter().try_fold(0, |mask, next| { if *next == 0 { Ok(mask) } else if next - 1 > 31 { Err(MsgError::new(format!( "Group {next} cannot be represented with a bit width of 32" ))) } else { Ok(mask | (1 << (*next - 1))) } }) } fn mask_to_vec(mask: u32) -> Vec { (1..size_of::() as u32 * u8::BITS) .filter(|i| (1 << (i - 1)) & mask == (1 << (i - 1))) .collect::>() } /// Struct implementing handling of groups both as numerical values and as /// bitmasks. pub struct Groups(Vec); impl Groups { /// Create an empty set of netlink multicast groups pub fn empty() -> Self { Groups(vec![]) } /// Create a new set of groups with a bitmask. Each bit represents a group. pub fn new_bitmask(mask: u32) -> Self { Groups(mask_to_vec(mask)) } /// Add a new bitmask to the existing group set. Each bit represents a group. pub fn add_bitmask(&mut self, mask: u32) { for group in mask_to_vec(mask) { if !self.0.contains(&group) { self.0.push(group); } } } /// Remove a bitmask from the existing group set. Each bit represents a group /// and each bit set to 1 will be removed. pub fn remove_bitmask(&mut self, mask: u32) { let remove_items = mask_to_vec(mask); self.0 = self .0 .drain(..) .filter(|g| !remove_items.contains(g)) .collect::>(); } /// Create a new set of groups from a list of numerical groups values. This differs /// from the bitmask representation where the value 3 represents group 3 in this /// format as opposed to 0x4 in the bitmask format. pub fn new_groups(groups: &[u32]) -> Self { let mut vec = groups.to_owned(); vec.retain(|g| g != &0); Groups(vec) } /// Add a list of numerical groups values to the set of groups. This differs /// from the bitmask representation where the value 3 represents group 3 in this /// format as opposed to 0x4 in the bitmask format. pub fn add_groups(&mut self, groups: &[u32]) { for group in groups { if *group != 0 && !self.0.contains(group) { self.0.push(*group) } } } /// Remove a list of numerical groups values from the set of groups. This differs /// from the bitmask representation where the value 3 represents group 3 in this /// format as opposed to 0x4 in the bitmask format. pub fn remove_groups(&mut self, groups: &[u32]) { self.0.retain(|g| !groups.contains(g)); } /// Return the set of groups as a bitmask. The representation of a bitmask is u32. pub fn as_bitmask(&self) -> Result { slice_to_mask(&self.0) } /// Return the set of groups as a vector of group values. pub fn as_groups(&self) -> Vec { self.0.clone() } /// Return the set of groups as a vector of group values. pub fn into_groups(self) -> Vec { self.0 } /// Returns true if no group is set. pub fn is_empty(&self) -> bool { self.0.is_empty() } } /// Synchronous (blocking) utils. #[cfg(feature = "sync")] pub mod synchronous { use super::*; use std::{ mem::swap, ops::{Deref, DerefMut}, }; use log::trace; use parking_lot::{Condvar, Mutex}; /// Type containing information pertaining to the semaphore tracking. struct SemInfo { max: u64, count: u64, } /// Guard indicating that a buffer has been acquired and the semaphore has been /// incremented. pub struct BufferPoolGuard<'a>(&'a BufferPool, Vec); impl Deref for BufferPoolGuard<'_> { type Target = Vec; fn deref(&self) -> &Self::Target { &self.1 } } impl DerefMut for BufferPoolGuard<'_> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.1 } } impl AsRef<[u8]> for BufferPoolGuard<'_> { fn as_ref(&self) -> &[u8] { self.1.as_ref() } } impl AsMut<[u8]> for BufferPoolGuard<'_> { fn as_mut(&mut self) -> &mut [u8] { self.1.as_mut() } } impl BufferPoolGuard<'_> { /// Reduce the size of the internal buffer to the number of bytes read. pub fn reduce_size(&mut self, bytes_read: usize) { assert!(bytes_read <= self.1.len()); self.1.resize(bytes_read, 0); } /// Reset the buffer to the original size. pub fn reset(&mut self) { self.1.resize( option_env!("NELI_AUTO_BUFFER_LEN") .and_then(|s| s.parse::().ok()) .unwrap_or(MAX_NL_LENGTH), 0, ); } } impl Drop for BufferPoolGuard<'_> { fn drop(&mut self) { { let mut vec = Vec::new(); swap(&mut self.1, &mut vec); let mut sem_info = self.0.sem_info.lock(); let mut pool = self.0.pool.lock(); sem_info.count -= 1; vec.resize( option_env!("NELI_AUTO_BUFFER_LEN") .and_then(|s| s.parse::().ok()) .unwrap_or(MAX_NL_LENGTH), 0, ); pool.push(vec); trace!( "Semaphore released; current count is {}, available is {}", sem_info.count, sem_info.max - sem_info.count ); } self.0.condvar.notify_one(); } } /// A pool of buffers available for reading concurrent netlink messages without /// truncation. pub struct BufferPool { pool: Mutex>>, sem_info: Mutex, condvar: Condvar, } impl Default for BufferPool { fn default() -> Self { let max_parallel = option_env!("NELI_MAX_PARALLEL_READ_OPS") .and_then(|s| s.parse::().ok()) .unwrap_or(3); let buffer_size = option_env!("NELI_AUTO_BUFFER_LEN") .and_then(|s| s.parse::().ok()) .unwrap_or(MAX_NL_LENGTH); BufferPool { pool: Mutex::new( (0..max_parallel) .map(|_| vec![0; buffer_size]) .collect::>(), ), sem_info: Mutex::new(SemInfo { count: 0, max: max_parallel, }), condvar: Condvar::new(), } } } impl BufferPool { /// Acquire a buffer for use. /// /// This method is backed by a semaphore. pub fn acquire(&self) -> BufferPoolGuard<'_> { let mut sem_info = self.sem_info.lock(); self.condvar .wait_while(&mut sem_info, |sem_info| sem_info.count >= sem_info.max); let mut pool = self.pool.lock(); sem_info.count += 1; trace!( "Semaphore acquired; current count is {}, available is {}", sem_info.count, sem_info.max - sem_info.count ); BufferPoolGuard( self, pool.pop() .expect("Checked that there is an available permit"), ) } } #[cfg(test)] mod tests { use super::*; use std::{ io::Write, thread::{scope, sleep}, time::Duration, }; use crate::test::setup; #[test] fn test_buffer_pool() { setup(); let pool = BufferPool::default(); scope(|s| { s.spawn(|| { let mut guard = pool.acquire(); sleep(Duration::from_secs(2)); guard.as_mut_slice().write_all(&[4]).unwrap(); assert_eq!(Some(&4), guard.first()); }); s.spawn(|| { let mut guard = pool.acquire(); sleep(Duration::from_secs(3)); guard.as_mut_slice().write_all(&[1]).unwrap(); assert_eq!(Some(&1), guard.first()); }); s.spawn(|| { let mut guard = pool.acquire(); sleep(Duration::from_secs(3)); guard.as_mut_slice().write_all(&[1]).unwrap(); assert_eq!(Some(&1), guard.first()); }); s.spawn(|| { sleep(Duration::from_secs(1)); let mut guard = pool.acquire(); guard.as_mut_slice().write_all(&[1]).unwrap(); assert_eq!(Some(&1), guard.first()); }); }); let pool = pool.pool.lock(); assert_eq!(pool.len(), 3); for buf in pool.iter() { assert_eq!(Some(&1), buf.first()); } } } } /// Asynchronous utils. #[cfg(feature = "async")] pub mod asynchronous { use super::*; use std::{ mem::swap, ops::{Deref, DerefMut}, }; use log::trace; use parking_lot::Mutex; use tokio::sync::{Semaphore, SemaphorePermit}; /// Guard indicating that a buffer has been acquired and the semaphore has been /// incremented. #[allow(dead_code)] pub struct BufferPoolGuard<'a>(&'a BufferPool, SemaphorePermit<'a>, Vec); impl Deref for BufferPoolGuard<'_> { type Target = Vec; fn deref(&self) -> &Self::Target { &self.2 } } impl DerefMut for BufferPoolGuard<'_> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.2 } } impl AsRef<[u8]> for BufferPoolGuard<'_> { fn as_ref(&self) -> &[u8] { self.2.as_ref() } } impl AsMut<[u8]> for BufferPoolGuard<'_> { fn as_mut(&mut self) -> &mut [u8] { self.2.as_mut() } } impl BufferPoolGuard<'_> { /// Reduce the size of the internal buffer to the number of bytes read. pub fn reduce_size(&mut self, bytes_read: usize) { assert!(bytes_read <= self.2.len()); self.2.resize(bytes_read, 0); } /// Reset the buffer to the original size. pub fn reset(&mut self) { self.2.resize( option_env!("NELI_AUTO_BUFFER_LEN") .and_then(|s| s.parse::().ok()) .unwrap_or(MAX_NL_LENGTH), 0, ); } } impl Drop for BufferPoolGuard<'_> { fn drop(&mut self) { { let mut vec = Vec::new(); swap(&mut self.2, &mut vec); let mut pool = self.0.pool.lock(); vec.resize( option_env!("NELI_AUTO_BUFFER_LEN") .and_then(|s| s.parse::().ok()) .unwrap_or(MAX_NL_LENGTH), 0, ); pool.push(vec); trace!( "Semaphore released; current count is {}, max is {}", self.0.max - self.0.semaphore.available_permits(), self.0.semaphore.available_permits() ); } } } /// A pool of buffers available for reading concurrent netlink messages without /// truncation. pub struct BufferPool { pool: Mutex>>, max: usize, semaphore: Semaphore, } impl Default for BufferPool { fn default() -> Self { let max_parallel = option_env!("NELI_MAX_PARALLEL_READ_OPS") .and_then(|s| s.parse::().ok()) .unwrap_or(3); let buffer_size = option_env!("NELI_AUTO_BUFFER_LEN") .and_then(|s| s.parse::().ok()) .unwrap_or(MAX_NL_LENGTH); BufferPool { pool: Mutex::new( (0..max_parallel) .map(|_| vec![0; buffer_size]) .collect::>(), ), max: max_parallel, semaphore: Semaphore::new(max_parallel), } } } impl BufferPool { /// Acquire a buffer for use. /// /// This method is backed by a semaphore. pub async fn acquire(&self) -> BufferPoolGuard<'_> { let permit = self .semaphore .acquire() .await .expect("Semaphore is never closed"); let mut pool = self.pool.lock(); trace!( "Semaphore acquired; current count is {}, available is {}", self.max - self.semaphore.available_permits(), self.semaphore.available_permits(), ); BufferPoolGuard( self, permit, pool.pop() .expect("Checked that there is an available permit"), ) } } } #[cfg(test)] mod test { use super::*; use crate::test::setup; #[test] fn test_bit_array() { setup(); let mut bit_array = NetlinkBitArray::new(7); assert_eq!(bit_array.0.len(), 1); bit_array.set(4); assert_eq!(bit_array.0[0], 0b1000); assert!(bit_array.is_set(4)); assert!(!bit_array.is_set(0)); assert!(!bit_array.is_set(1)); assert!(!bit_array.is_set(2)); assert!(!bit_array.is_set(3)); assert_eq!(bit_array.len(), 4); assert_eq!(bit_array.len_bits(), 32); let mut bit_array = NetlinkBitArray::new(33); bit_array.set(32); bit_array.set(33); assert!(bit_array.0[0] == 1 << 31); assert!(bit_array.0[1] == 1); assert!(bit_array.is_set(32)); assert!(bit_array.is_set(33)); let mut bit_array = NetlinkBitArray::new(32); assert_eq!(bit_array.len(), 4); bit_array.resize_bits(33); assert_eq!(bit_array.len(), 8); bit_array.resize_bits(1); assert_eq!(bit_array.len(), 4); let mut bit_array = NetlinkBitArray::new(33); assert_eq!(bit_array.len(), 8); bit_array.resize(1); assert_eq!(bit_array.len(), 4); bit_array.resize(9); assert_eq!(bit_array.len(), 12); let bit_array = NetlinkBitArray(vec![8, 8, 8]); assert_eq!(bit_array.to_vec(), vec![4, 36, 68]); } #[test] fn test_groups() { setup(); assert_eq!(Groups::new_groups(&[0, 0, 0, 0]).as_bitmask().unwrap(), 0); let groups = Groups::new_groups(&[0, 0, 0, 0]).as_groups(); assert!(groups.is_empty()); } }