Repository: little-dude/netlink Branch: master Commit: 8fc3843dcc5d Files: 373 Total size: 1.2 MB Directory structure: gitextract_sbpvo7hy/ ├── .github/ │ └── workflows/ │ ├── clippy-rustfmt.yml │ ├── license.yml │ └── main.yml ├── .gitignore ├── .licenserc.yaml ├── CHANGELOG ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── RELEASE_PROCESS.md ├── audit/ │ ├── Cargo.toml │ ├── examples/ │ │ ├── add_rules.rs │ │ ├── dump_audit_rules.rs │ │ ├── events.rs │ │ └── events_async.rs │ └── src/ │ ├── errors.rs │ ├── handle.rs │ └── lib.rs ├── ethtool/ │ ├── Cargo.toml │ ├── examples/ │ │ ├── dump_coalesce.rs │ │ ├── dump_features.rs │ │ ├── dump_link_mode.rs │ │ ├── dump_pause.rs │ │ └── dump_rings.rs │ ├── src/ │ │ ├── coalesce/ │ │ │ ├── attr.rs │ │ │ ├── get.rs │ │ │ ├── handle.rs │ │ │ └── mod.rs │ │ ├── connection.rs │ │ ├── error.rs │ │ ├── feature/ │ │ │ ├── attr.rs │ │ │ ├── get.rs │ │ │ ├── handle.rs │ │ │ └── mod.rs │ │ ├── handle.rs │ │ ├── header.rs │ │ ├── lib.rs │ │ ├── link_mode/ │ │ │ ├── attr.rs │ │ │ ├── get.rs │ │ │ ├── handle.rs │ │ │ └── mod.rs │ │ ├── macros.rs │ │ ├── message.rs │ │ ├── pause/ │ │ │ ├── attr.rs │ │ │ ├── get.rs │ │ │ ├── handle.rs │ │ │ └── mod.rs │ │ └── ring/ │ │ ├── attr.rs │ │ ├── get.rs │ │ ├── handle.rs │ │ └── mod.rs │ └── tests/ │ ├── dump_link_modes.rs │ └── get_features_lo.rs ├── genetlink/ │ ├── Cargo.toml │ ├── examples/ │ │ ├── dump_family_policy.rs │ │ └── list_genetlink_family.rs │ └── src/ │ ├── connection.rs │ ├── error.rs │ ├── handle.rs │ ├── lib.rs │ ├── message.rs │ └── resolver.rs ├── mptcp-pm/ │ ├── Cargo.toml │ ├── examples/ │ │ └── dump_mptcp.rs │ ├── src/ │ │ ├── address/ │ │ │ ├── attr.rs │ │ │ ├── get.rs │ │ │ ├── handle.rs │ │ │ └── mod.rs │ │ ├── connection.rs │ │ ├── error.rs │ │ ├── handle.rs │ │ ├── lib.rs │ │ ├── limits/ │ │ │ ├── attr.rs │ │ │ ├── get.rs │ │ │ ├── handle.rs │ │ │ └── mod.rs │ │ ├── macros.rs │ │ └── message.rs │ └── tests/ │ └── dump_mptcp.rs ├── netlink-packet-audit/ │ ├── Cargo.toml │ ├── fuzz/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── fuzz_targets/ │ │ └── netlink.rs │ └── src/ │ ├── buffer.rs │ ├── codec.rs │ ├── constants.rs │ ├── lib.rs │ ├── message.rs │ ├── rules/ │ │ ├── action.rs │ │ ├── buffer.rs │ │ ├── field.rs │ │ ├── flags.rs │ │ ├── mod.rs │ │ ├── rule.rs │ │ ├── syscalls.rs │ │ └── tests.rs │ └── status.rs ├── netlink-packet-core/ │ ├── Cargo.toml │ ├── examples/ │ │ ├── protocol.rs │ │ └── rtnetlink.rs │ └── src/ │ ├── buffer.rs │ ├── constants.rs │ ├── error.rs │ ├── header.rs │ ├── lib.rs │ ├── message.rs │ ├── payload.rs │ └── traits.rs ├── netlink-packet-generic/ │ ├── Cargo.toml │ ├── examples/ │ │ └── list_generic_family.rs │ ├── src/ │ │ ├── buffer.rs │ │ ├── constants.rs │ │ ├── ctrl/ │ │ │ ├── mod.rs │ │ │ └── nlas/ │ │ │ ├── mcast.rs │ │ │ ├── mod.rs │ │ │ ├── oppolicy.rs │ │ │ ├── ops.rs │ │ │ └── policy.rs │ │ ├── header.rs │ │ ├── lib.rs │ │ ├── message.rs │ │ └── traits.rs │ └── tests/ │ └── query_family_id.rs ├── netlink-packet-netfilter/ │ ├── Cargo.toml │ ├── examples/ │ │ └── nflog.rs │ └── src/ │ ├── buffer.rs │ ├── constants.rs │ ├── lib.rs │ ├── message.rs │ └── nflog/ │ ├── message.rs │ ├── mod.rs │ └── nlas/ │ ├── config/ │ │ ├── config_cmd.rs │ │ ├── config_flags.rs │ │ ├── config_mode.rs │ │ ├── mod.rs │ │ ├── nla.rs │ │ └── timeout.rs │ ├── mod.rs │ └── packet/ │ ├── hw_addr.rs │ ├── mod.rs │ ├── nla.rs │ ├── packet_hdr.rs │ └── timestamp.rs ├── netlink-packet-route/ │ ├── Cargo.toml │ ├── benches/ │ │ ├── link_message.rs │ │ └── rtnetlink_dump.rs │ ├── data/ │ │ ├── README.md │ │ └── rtnetlink.pcap │ ├── examples/ │ │ ├── dump_neighbours.rs │ │ ├── dump_packet_link_bridge_vlan.rs │ │ ├── dump_packet_links.rs │ │ ├── dump_rules.rs │ │ └── new_rule.rs │ ├── fuzz/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── fuzz_targets/ │ │ └── netlink.rs │ └── src/ │ ├── lib.rs │ └── rtnl/ │ ├── address/ │ │ ├── buffer.rs │ │ ├── message.rs │ │ ├── mod.rs │ │ └── nlas/ │ │ ├── cache_info.rs │ │ └── mod.rs │ ├── buffer.rs │ ├── constants.rs │ ├── link/ │ │ ├── buffer.rs │ │ ├── header.rs │ │ ├── message.rs │ │ ├── mod.rs │ │ └── nlas/ │ │ ├── af_spec_bridge.rs │ │ ├── af_spec_inet.rs │ │ ├── bond.rs │ │ ├── inet/ │ │ │ ├── dev_conf.rs │ │ │ └── mod.rs │ │ ├── inet6/ │ │ │ ├── cache.rs │ │ │ ├── dev_conf.rs │ │ │ ├── icmp6_stats.rs │ │ │ ├── mod.rs │ │ │ └── stats.rs │ │ ├── link_infos.rs │ │ ├── link_state.rs │ │ ├── map.rs │ │ ├── mod.rs │ │ ├── prop_list.rs │ │ ├── stats.rs │ │ ├── stats64.rs │ │ └── tests.rs │ ├── message.rs │ ├── mod.rs │ ├── neighbour/ │ │ ├── buffer.rs │ │ ├── header.rs │ │ ├── message.rs │ │ ├── mod.rs │ │ └── nlas/ │ │ ├── cache_info.rs │ │ └── mod.rs │ ├── neighbour_table/ │ │ ├── buffer.rs │ │ ├── header.rs │ │ ├── message.rs │ │ ├── mod.rs │ │ └── nlas/ │ │ ├── config.rs │ │ ├── mod.rs │ │ └── stats.rs │ ├── nsid/ │ │ ├── buffer.rs │ │ ├── header.rs │ │ ├── message.rs │ │ ├── mod.rs │ │ └── nlas.rs │ ├── route/ │ │ ├── buffer.rs │ │ ├── header.rs │ │ ├── message.rs │ │ ├── mod.rs │ │ ├── nlas/ │ │ │ ├── cache_info.rs │ │ │ ├── metrics.rs │ │ │ ├── mfc_stats.rs │ │ │ ├── mod.rs │ │ │ ├── mpls_ip_tunnel.rs │ │ │ └── next_hops.rs │ │ └── test.rs │ ├── rule/ │ │ ├── buffer.rs │ │ ├── header.rs │ │ ├── message.rs │ │ ├── mod.rs │ │ └── nlas/ │ │ └── mod.rs │ ├── tc/ │ │ ├── buffer.rs │ │ ├── constants.rs │ │ ├── message.rs │ │ ├── mod.rs │ │ ├── nlas/ │ │ │ ├── action/ │ │ │ │ ├── mirred.rs │ │ │ │ └── mod.rs │ │ │ ├── filter/ │ │ │ │ ├── mod.rs │ │ │ │ └── u32.rs │ │ │ ├── mod.rs │ │ │ ├── options.rs │ │ │ ├── qdisc/ │ │ │ │ └── mod.rs │ │ │ ├── stats.rs │ │ │ ├── stats_basic.rs │ │ │ ├── stats_queue.rs │ │ │ └── test.rs │ │ └── test.rs │ └── test.rs ├── netlink-packet-sock-diag/ │ ├── Cargo.toml │ ├── examples/ │ │ └── dump_ipv4.rs │ └── src/ │ ├── buffer.rs │ ├── constants.rs │ ├── inet/ │ │ ├── mod.rs │ │ ├── nlas.rs │ │ ├── request.rs │ │ ├── response.rs │ │ ├── socket_id.rs │ │ └── tests.rs │ ├── lib.rs │ ├── message.rs │ └── unix/ │ ├── mod.rs │ ├── nlas.rs │ ├── request.rs │ ├── response.rs │ └── tests.rs ├── netlink-packet-utils/ │ ├── Cargo.toml │ └── src/ │ ├── errors.rs │ ├── lib.rs │ ├── macros.rs │ ├── nla.rs │ ├── parsers.rs │ └── traits.rs ├── netlink-packet-wireguard/ │ ├── Cargo.toml │ ├── examples/ │ │ └── get_wireguard_info.rs │ └── src/ │ ├── constants.rs │ ├── lib.rs │ ├── nlas/ │ │ ├── allowedip.rs │ │ ├── device.rs │ │ ├── mod.rs │ │ └── peer.rs │ └── raw.rs ├── netlink-proto/ │ ├── Cargo.toml │ ├── examples/ │ │ ├── audit_netlink_events.rs │ │ ├── dump_links.rs │ │ └── dump_links_async.rs │ └── src/ │ ├── codecs.rs │ ├── connection.rs │ ├── errors.rs │ ├── framed.rs │ ├── handle.rs │ ├── lib.rs │ └── protocol/ │ ├── mod.rs │ ├── protocol.rs │ └── request.rs ├── netlink-sys/ │ ├── Cargo.toml │ ├── examples/ │ │ ├── audit_events.rs │ │ ├── audit_events_async_std.rs │ │ ├── audit_events_tokio.rs │ │ └── audit_events_tokio_manual_thread_builder.rs │ └── src/ │ ├── addr.rs │ ├── async_socket.rs │ ├── async_socket_ext.rs │ ├── constants.rs │ ├── lib.rs │ ├── mio.rs │ ├── smol.rs │ ├── socket.rs │ └── tokio.rs ├── rtnetlink/ │ ├── Cargo.toml │ ├── examples/ │ │ ├── add_address.rs │ │ ├── add_neighbour.rs │ │ ├── add_netns.rs │ │ ├── add_netns_async.rs │ │ ├── add_route.rs │ │ ├── add_route_pref_src.rs │ │ ├── add_rule.rs │ │ ├── add_tc_qdisc_ingress.rs │ │ ├── create_bond.rs │ │ ├── create_bridge.rs │ │ ├── create_macvlan.rs │ │ ├── create_macvtap.rs │ │ ├── create_veth.rs │ │ ├── create_vxlan.rs │ │ ├── del_link.rs │ │ ├── del_netns.rs │ │ ├── del_netns_async.rs │ │ ├── flush_addresses.rs │ │ ├── get_address.rs │ │ ├── get_links.rs │ │ ├── get_links_async.rs │ │ ├── get_links_thread_builder.rs │ │ ├── get_neighbours.rs │ │ ├── get_route.rs │ │ ├── get_rule.rs │ │ ├── ip_monitor.rs │ │ ├── listen.rs │ │ ├── property_altname.rs │ │ └── set_link_down.rs │ └── src/ │ ├── addr/ │ │ ├── add.rs │ │ ├── del.rs │ │ ├── get.rs │ │ ├── handle.rs │ │ └── mod.rs │ ├── connection.rs │ ├── constants.rs │ ├── errors.rs │ ├── handle.rs │ ├── lib.rs │ ├── link/ │ │ ├── add.rs │ │ ├── del.rs │ │ ├── get.rs │ │ ├── handle.rs │ │ ├── mod.rs │ │ ├── property_add.rs │ │ ├── property_del.rs │ │ ├── set.rs │ │ └── test.rs │ ├── macros.rs │ ├── neighbour/ │ │ ├── add.rs │ │ ├── del.rs │ │ ├── get.rs │ │ ├── handle.rs │ │ └── mod.rs │ ├── ns.rs │ ├── route/ │ │ ├── add.rs │ │ ├── del.rs │ │ ├── get.rs │ │ ├── handle.rs │ │ └── mod.rs │ ├── rule/ │ │ ├── add.rs │ │ ├── del.rs │ │ ├── get.rs │ │ ├── handle.rs │ │ └── mod.rs │ └── traffic_control/ │ ├── add_filter.rs │ ├── add_qdisc.rs │ ├── del_qdisc.rs │ ├── get.rs │ ├── handle.rs │ ├── mod.rs │ └── test.rs └── rustfmt.toml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/clippy-rustfmt.yml ================================================ name: Rustfmt and clippy check on: pull_request: types: [opened, synchronize, reopened] push: branches: - master jobs: rustfmt_clippy: strategy: fail-fast: true name: Rustfmt and clippy check runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust Nightly uses: actions-rs/toolchain@v1 with: toolchain: nightly override: true components: rustfmt, clippy - name: rustfmt run: cargo fmt --all -- --check - name: clippy-tokio-socket run: cargo clippy --workspace - name: clippy-smol-socket run: cargo clippy --no-default-features --features smol_socket --workspace ================================================ FILE: .github/workflows/license.yml ================================================ name: license on: pull_request: types: [opened, synchronize, reopened] push: branches: - master jobs: check-license: name: Check License runs-on: ubuntu-latest timeout-minutes: 3 steps: - uses: actions/checkout@v3 - name: Check License Header uses: apache/skywalking-eyes@v0.3.0 ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: pull_request: types: [opened, synchronize, reopened] push: branches: - master jobs: ci: name: CI (stable) runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust Stable uses: actions-rs/toolchain@v1 with: toolchain: stable override: true - name: test (netlink-sys) run: | cargo test -p netlink-sys cargo test -p netlink-sys --features tokio_socket - name: test (netlink-packet-audit) run: cargo test -p netlink-packet-audit - name: test (netlink-packet-core) run: cargo test -p netlink-packet-core - name: test (netlink-packet-generic) run: cargo test -p netlink-packet-generic - name: test (netlink-packet-netfilter) run: cargo test -p netlink-packet-netfilter - name: test (netlink-packet-route) run: | cargo test -p netlink-packet-route cargo test -p netlink-packet-route --features rich_nlas - name: test (netlink-packet-sock-diag) run: cargo test -p netlink-packet-sock-diag - name: test (netlink-packet-utils) run: cargo test -p netlink-packet-utils - name: test (netlink-packet-wireguard) run: cargo test -p netlink-packet-wireguard - name: test (netlink-proto) run: cargo test -p netlink-proto - name: test (rtnetlink) env: # Needed for the `link::test::create_get_delete_w` test to pass. CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: "sudo -E" run: cargo test -p rtnetlink - name: test (audit) run: cargo test -p audit - name: test (genetlink) run: cargo test -p genetlink --features tokio_socket - name: test (ethtool) run: cargo test -p ethtool - name: test (mptcp-pm) env: # Needed root permission to modify MPTCP CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: "sudo -E" run: cargo test -p mptcp-pm ================================================ FILE: .gitignore ================================================ **/target **/*.rs.bk Cargo.lock .idea ================================================ FILE: .licenserc.yaml ================================================ header: license: content: | SPDX-License-Identifier: MIT paths-ignore: - 'target' - '**/*.toml' - '**/*.lock' - '**/*.yml' - '**/*.md' - 'CHANGELOG' - 'LICENSE-MIT' - '.gitignore' comment: on-failure ================================================ FILE: CHANGELOG ================================================ # Changelog ## On-going ### Versioning ### Breaking Changes ### New Features ### Bug fixes ## [20220715] 2022-07-15 ### Versioning * rtnetlink 0.10.1 -> 0.11.0 * netlink-packet-route 0.12.0 -> 0.13.0 ### Breaking Changes * netlink-packet-route: `rtnl::link::nlas::InfoData::Bond` changed from `Vec` to `Vec`. (99b5765) ### New Features * Add support bond. (99b5765) ### Bug fixes * Fix the flag of link deletion. (9dccf92) ## [20220624] 2022-06-24 ### Versioning * audit 0.7.0 -> 0.7.1 * ethtool 0.2.2 -> 0.2.3 * genetlink 0.2.2 -> 0.2.3 * mptcp-pm 0.1.0 -> 0.1.1 * netlink-packet-audit 0.4.1 -> 0.4.2 * netlink-packet-wireguard 0.2.1 -> 0.2.2 * rtnetlink 0.10.0 -> 0.10.1 ### Breaking Changes * netlink-proto: removed `netlink-proto::ErrorKind`. (3d799df) ### New Features * N/A ### Bug fixes * N/A ## [20220623] 2022-06-23 ### Versioning * audit 0.6.0 -> 0.7.0 * genetlink 0.2.1 -> 0.2.2 * mptcp-pm NULL -> 0.1.0 * netlink-packet-generic 0.2.0 -> 0.3.1 * netlink-packet-audit 0.4.0 -> 0.4.1 * netlink-packet-route 0.11.0 -> 0.12.0 * netlink-proto 0.9.2 -> 0.9.3 * netlink-sys 0.8.2 -> 0.8.3 * rtnetlink 0.9.1 -> 0.10.0 ### Breaking Changes * audit: removed `audit::proto::ErrorKind`. (3d799df) * netlink-packet-route: changed from `AfSpecBridge::VlanInfo(Vec)` to `AfSpecBridge::VlanInfo(BridgeVlanInfo)`. (f21ddb2) * netlink-packet-route: changed from `Nla::AfSpecBridge(Vec)` to `Nla::AfSpecBridge(Vec)`. (f21ddb2) * netlink-packet-route: removed `InfoBridge::Flags` and `InfoBridge::VlanInfo` as they should in `Nla::AfSpecBridge`. (b688737) * netlink-packet-route: changed `NextHop.gateway` to `NextHop.nlas`. (f6b3b9a) * rtnetlink: Removed `rtnetlink::proto::ErrorKind`. (3d799df) ### New Features * New crate mptcp-pm for MPTCP path manager. (1903b39) * netlink-packet-route: Add tc filter support. (2c41fb0) * netlink-packet-route: Add tc qdisc support. (921a936) * rtnetlink: Add tc filter support. (2c41fb0) * rtnetlink: Add tc qdisc support. (921a936) ### Bug fixes * netlink-packet-audit: Simplfied codec error handling. (33cc558, 0027b82, 9cdc870) * netlink-packet-route: Fixed tc buffer error. (d2a5109) * netlink-proto: fix netlink_proto::Error recursive problem. (3d799df) * netlink-proto: Simplfied codec error handling. (0027b82, 9cdc870) ## [20220220] 2022-02-20 ### Versioning * netlink-packet-netfilter NULL -> 0.1.0 * netlink-sys 0.8.1 -> 0.8.2 * netlink-packet-core 0.4.1 -> 0.4.2 * netlink-packet-utils 0.5.0 -> 0.5.1 * netlink-packet-route 0.10.0 -> 0.11.0 * netlink-packet-sock-diag 0.3.0 -> 0.3.1 * netlink-packet-wireguard 0.2.0 -> 0.2.1 * netlink-proto 0.9.1 -> 0.9.2 * ethtool 0.2.1 -> 0.2.2 * rtnetlink 0.9.0 -> 0.9.1 ### New Features * **new crate netlink-packet-netfilter!** Thank you @dzamlo :) (https://github.com/little-dude/netlink/pull/235) * netlink-packet-utils: speed up computation of netlink attributes paddins (https://github.com/little-dude/netlink/pull/229) * netlink-packet-route: support additional MPLS attributes (https://github.com/little-dude/netlink/pull/233) ### Bug fixes * netlink-packet-sys, netlink-packet-sock-diag, netlink-packet-route, netlink-packet-wireguard, ethtool, netlink-packet-core: clippy fixes (https://github.com/little-dude/netlink/pull/238) * netlink-packet-route: fix encoding of link info attribute (https://github.com/little-dude/netlink/pull/239) ## [20220212] 2022-02-12 ### Versioning * netlink-packet-wireguard 0.1.1 -> 0.2 ### Breaking changes * netlink-packet-wireguard (https://github.com/little-dude/netlink/pull/225): * In `netlink_packet_wireguard::nlas::device`: `WgDeviceAttrs::Peers(Vec>)` is now `WgDeviceAttrs::Peers(Vec>)` * In `netlink_packet_wireguard::nlas::peer`: `WgDeviceAttrs::AllowedIps(Vec>)` is now `WgDeviceAttrs::AllowedIps(Vec>)` ### New Features None ### Bug fixes * netlink-packet-wireguard (https://github.com/little-dude/netlink/pull/225): various serialization fixes ## [20211229] 2022-01-15 ### Versioning * ethtool 0.2.0 -> 0.2.1 (0.2.0 yanked) * genetlink 0.2.0 -> 0.2.1 (0.2.0 yanked) * netlink-packet-wireguard -> 0.1.1 (0.1.0 yanked) ### Breaking changes None ### New Features None ### Bug fixes Fix dependencies in the netlink generic crates. See: https://github.com/little-dude/netlink/pull/223/files ## [20211229] 2021-12-29 ### Versioning * audit 0.4.0 -> 0.6.0 (botched 0.5.0 release) * ethtool 0.1.0 -> 0.2.0 * genetlink 0.1.0 -> 0.2.0 * netlink-packet-audit 0.2.2 -> 0.4.0 (botched 0.4.0 release) * netlink-packet-core 0.2.4 -> 0.4.1 (botched 0.3.0 release, 0.4.0 was published with downgraded dependencies to break cycles) * netlink-packet-generic 0.1.0 -> 0.2.0 * netlink-packet-route 0.8.0 -> 0.10.0 (botched 0.9.0 release) * netlink-packet-sock-diag 0.1.0 -> 0.3.0 (botched 0.2.0 release) * netlink-packet-utils 0.4.1 -> 0.5 * netlink-packet-wireguard NULL -> 0.1.0 * netlink-proto 0.7.0 -> 0.9.1 (botched 0.8.0 release, 0.9.0 was published with downgraded dev-dependences to break cycles) * netlink-sys 0.7.0 -> 0.8.1 (0.8.0 was published with downgraded dev-dependencies to break cycles) * rtnetlink 0.8.1 -> 0.9.0 ### Breaking Changes - `netlink-packet-route`: - add `InfoBridge::VlanInfo` (https://github.com/little-dude/netlink/pull/212 https://github.com/little-dude/netlink/pull/213) - `rtnetlink`: - add `LinkGetRequest::match_name` to filter links by name more efficiently, and remove `LinkGetRequest::set_name_filter` (https://github.com/little-dude/netlink/pull/208) - refactor `netlink_packet_core::traits::NetlinkSerializable` and `netlink_packet_core::trait::NetlinkDeserializable` such that they are not generic (https://github.com/little-dude/netlink/pull/195/, specifically https://github.com/little-dude/netlink/pull/195/commits/94c263282d9a34d01422513de6a7f683ac08addc) - `netlink-proto`: Add new type parameter for `Connection` which represents the socket (https://github.com/little-dude/netlink/pull/195, specifically 944307ce292682283891f41db8a0ec4706419664) ### New Features - new `netlink-packet-wireguard` crate for the wireguard netlink protocol (https://github.com/little-dude/netlink/pull/191) - new `rich_nlas` feature for `netlink-packet-route` that enables parsing more message types (https://github.com/little-dude/netlink/pull/199 https://github.com/little-dude/netlink/pull/205) - `rtnetlink`: - add `NeighbourAddRequest::add_bridge` helper to create a bridge interface (https://github.com/little-dude/netlink/pull/203) - allow the requests to be built with the `NLM_F_REPLACE` flag to optimize "create or update" operations (https://github.com/little-dude/netlink/pull/202) - add helper to create macvlan links (https://github.com/little-dude/netlink/pull/194) - `netlink-packet-utils`: add `parse_ip` function ### Bug fixes - fix UB in unsafe code (https://github.com/little-dude/netlink/pull/195/ specifically 7e6cfd743bf822e917e260eb24fbf5b2c541922e) - fix `netlink_sys::SmolSocket::recv` error handling (https://github.com/little-dude/netlink/pull/195/ specifically 1cd3e0fbb8d77d6b9c4fe43b8c4aa745fa6ba66c) - various fixes in the `netlink-proto` encoder (https://github.com/little-dude/netlink/pull/168) ## [20210927] 2021-09-27 ### Versioning * audit 0.4.0 * ethtool NULL -> 0.1.0 * genetlink NULL -> 0.1.0 * netlink-packet-audit 0.2.2 * netlink-packet-core 0.2.4 * netlink-packet-generic NULL -> 0.1.0 * netlink-packet-route 0.7.0 -> 0.8.0 * netlink-packet-sock-diag 0.1.0 * netlink-packet-utils 0.4.1 * netlink-proto 0.7.0 * netlink-sys 0.7.0 * rtnetlink 0.8.0 -> 0.8.1 ### Breaking Changes * `netlink_packet_route::rtnl::link::nlas::Nla::PropList` changed from `PropList(Vec)` to `PropList(Vec)` (b4b3c46) ### New Features * ethtool: New crate for ethtool netlink protocol (7998f8c, 2b79197, bc43fd6, 2ec5f17, cb8738b) * genetlink: New create for higher level abstraction of generic netlink protocol (89ee697) * netlink-packet-generic: New crate for generic netlink protocol (89ee697) * netlink-packet-route: Add support of property addition and deletion (cc073b3) * rtnetlink: Add support of preferred source address (720e764) ### Bug fixes * netlink-packet-route: vlan: Fix endianness when creating VLAN (b0fd2ea) * rtnetlink: drop byteordered dependency (8bca238) ================================================ FILE: Cargo.toml ================================================ [workspace] members = [ "netlink-sys", "netlink-packet-core", "netlink-packet-utils", "netlink-packet-generic", "netlink-packet-route", "netlink-packet-route/fuzz", "netlink-packet-audit", "netlink-packet-audit/fuzz", "netlink-packet-sock-diag", "netlink-packet-netfilter", "netlink-packet-wireguard", "netlink-proto", "ethtool", "genetlink", "rtnetlink", "audit", "mptcp-pm", ] # omit fuzz projects default-members = [ "netlink-sys", "netlink-packet-core", "netlink-packet-utils", "netlink-packet-generic", "netlink-packet-route", "netlink-packet-audit", "netlink-packet-sock-diag", "netlink-packet-netfilter", "netlink-packet-wireguard", "netlink-proto", "ethtool", "genetlink", "rtnetlink", "audit", "mptcp-pm", ] ================================================ FILE: LICENSE-MIT ================================================ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Distributions of all or part of the Software intended to be used by the recipients as they would use the unmodified Software, containing modifications that substantially alter, remove, or disable functionality of the Software, outside of the documented configuration mechanisms provided by the Software, shall be modified such that the Original Author's bug reporting email addresses and urls are either replaced with the contact information of the parties responsible for the changes, or removed entirely. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![Build Status](https://travis-ci.org/little-dude/netlink.svg?branch=master)](https://travis-ci.org/little-dude/netlink) # This repository has been deprecated. Subsequent development will take place [@rust-netlink](https://github.com/rust-netlink). # Netlink This project aims at providing building blocks for [netlink][man-netlink] (see `man 7 netlink`). ## Organization - the [`netlink_sys`](./netlink-sys) crate provides netlink sockets. Integration with [`mio`][mio] and [`tokio`][tokio] is optional. - Each netlink protocol has a `netlink-packet-` crate that provides the packets for this protocol: - [`netlink-packet-route`](./netlink-packet-route) provides messages for the [route protocol][man-rtnetlink] - [`netlink-packet-audit`](./netlink-packet-audit) provides messages for the [audit][man-audit] protocol - [`netlink-packet-sock-diag`](./netlink-packet-sock-diag) provides messages for the [sock-diag][man-sock-diag] protocol - [`netlink-packet-generic`](./netlink-packet-generic) provides message for the [generic netlink][man-genl] protocol - [`netlink-packet-netfilter`](./netlink-packet-netfilter) provides message for the `NETLINK_NETFILTER` protocol - the [`netlink-packet-core`](./netlink-packet-core) is the glue for all the other `netlink-packet-*` crates. It provides a `NetlinkMessage` type that represent any netlink message for any sub-protocol. - the [`netlink_proto`](./netlink-proto) crate is an asynchronous implementation of the netlink protocol. It only depends on `netlink-packet-core` for the `NetlinkMessage` type and `netlink-sys` for the socket. - the [`rtnetlink`](./rtnetlink) crate provides higher level abstraction for the [route protocol][man-rtnetlink] - the [`audit`](./audit) crate provides higher level abstractions for the audit protocol. - the [`genetlink`](./genetlink) crate provide higher level abstraction for the [generic netlink protocol][man-genl] - the [`ethtool`](./ethtool) crate provide higher level abstraction for [ethtool netlink protocol][ethtool-kernel-doc] ## Altnernatives - https://github.com/jbaublitz/neli: the main alternative to these crates, as it is actively developed. - Other but less actively developed alternatives: - https://github.com/achanda/netlink - https://github.com/polachok/pnetlink - https://github.com/crhino/netlink-rs - https://github.com/carrotsrc/rsnl - https://github.com/TaborKelly/nl-utils ## Credits My main resource so far has been the source code of [`pyroute2`][pyroute2] (python) and [`netlink`][netlink-go] (golang) a lot. These two projects are great, and very nicely written. As someone who does not read C fluently, and that does not know much about netlink, they have been invaluable. I'd also like to praise [`libnl`][libnl] for its documentation. It helped me a lot in understanding the protocol basics. The whole packet parsing logic is inspired by @whitequark excellent blog posts ([part 1][whitequark-1], [part 2][whitequark-2] and [part 3][whitequark-3], although I've only really used the concepts described in the first blog post). Thanks also to the people behind [tokio](tokio.rs) for the amazing tool they are building, and the support they provide. [man-netlink]: https://www.man7.org/linux/man-pages/man7/netlink.7.html [man-audit]: https://man7.org/linux/man-pages/man3/audit_open.3.html [man-sock-diag]: https://www.man7.org/linux/man-pages/man7/sock_diag.7.html [man-rtnetlink]: https://www.man7.org/linux/man-pages/man7/rtnetlink.7.html [man-genl]: https://www.man7.org/linux/man-pages/man8/genl.8.html [generic-netlink-lwn]: https://lwn.net/Articles/208755/ [mio]: https://github.com/tokio-rs/mio [tokio]: https://github.com/tokio-rs/tokio [route-proto-doc]: https://www.infradead.org/~tgr/libnl/doc/route.html [netlink-go]: https://github.com/vishvananda/netlink [pyroute2]: https://github.com/svinota/pyroute2/tree/master/pyroute2/netlink [libnl]: https://www.infradead.org/~tgr/libnl [whitequark-1]: https://lab.whitequark.org/notes/2016-12-13/abstracting-over-mutability-in-rust [whitequark-2]: https://lab.whitequark.org/notes/2016-12-17/owning-collections-in-heap-less-rust [whitequark-3]: https://lab.whitequark.org/notes/2017-01-16/abstracting-over-mutability-in-rust-macros [ethtool-kernel-doc]: https://www.kernel.org/doc/html/latest/networking/ethtool-netlink.html ================================================ FILE: RELEASE_PROCESS.md ================================================ # Release process ## Summary - bump the versions in the Cargo.toml using `git blame` and `git log`, starting from the `netlink-packet-*` and `netlink-sys` crates - Update the `CHANGELOG` file with version changes, new features, bug fixes and breaking changes - Check that `cargo test` still works once your done - Create pull request for the changes for CHANGELOG and version dumping. - Create new tag via command `git tag --sign $(date +%Y%m%d)` - Publish the tag to github via command `git push --tags upstream` - Create new release page at [github webpage][github_new_release] - Publish the crates via command `cargo publish` in changed crate folders ## Detailed process ### Crate groups First, distinguish three groups of crates: - `netlink-packet-*` crates - `netlink-sys` - `netlink-proto`, `audit` and `rtnetlink`, which depend on the two other groups Usually start by bumping the versions of the first group of crates, then `netlink-sys`, and then the last group of crates. ### Dependency graph Here are the dependency tree for each group. ``` netlink-packet-utils v0.4.0 netlink-packet-core v0.2.4 └── netlink-packet-utils v0.4.0 [dev-dependencies] └── netlink-packet-route v0.7.0 ├── netlink-packet-core v0.2.4 └── netlink-packet-utils v0.4.0 netlink-packet-route v0.7.0 ├── netlink-packet-core v0.2.4 │ └── netlink-packet-utils v0.4.0 └── netlink-packet-utils v0.4.0 [dev-dependencies] └── netlink-sys v0.6.0 netlink-packet-audit v0.2.2 ├── netlink-packet-core v0.2.4 │ └── netlink-packet-utils v0.4.0 └── netlink-packet-utils v0.4.0 netlink-packet-sock-diag v0.1.0 ├── netlink-packet-core v0.2.4 │ └── netlink-packet-utils v0.4.0 └── netlink-packet-utils v0.4.0 [dev-dependencies] └── netlink-sys v0.6.0 ``` Then `netlink-sys`: ``` netlink-sys v0.6.0 [dev-dependencies] └── netlink-packet-audit v0.2.2 ├── netlink-packet-core v0.2.4 │ └── netlink-packet-utils v0.4.0 └── netlink-packet-utils v0.4.0 ``` Finally, `netlink-proto`, `audit` and `rtnetlink`, which use both the `netlink-packet-*` crates and `netlink-sys`: ``` netlink-proto v0.6.0 ├── netlink-packet-core v0.2.4 │ └── netlink-packet-utils v0.4.0 └── netlink-sys v0.6.0 [dev-dependencies] ├── netlink-packet-audit v0.2.2 └── netlink-packet-route v0.7.0 audit v0.3.1 ├── netlink-packet-audit v0.2.2 │ ├── netlink-packet-core v0.2.4 │ │ └── netlink-packet-utils v0.4.0 │ └── netlink-packet-utils v0.4.0 └── netlink-proto v0.6.0 ├── netlink-packet-core v0.2.4 └── netlink-sys v0.6.0 rtnetlink v0.7.0 ├── netlink-packet-route v0.7.0 │ ├── netlink-packet-core v0.2.4 │ │ └── netlink-packet-utils v0.4.0 │ └── netlink-packet-utils v0.4.0 └── netlink-proto v0.6.0 ├── netlink-packet-core v0.2.4 └── netlink-sys v0.6.0 ``` ### Version bump For each crate, look at when was the last time the version was changed. For instance for `rtnetlink`: ``` $ git blame rtnetlink/Cargo.toml | grep "version = " 88dde610 rtnetlink/Cargo.toml (little-dude 2021-01-20 20:09:23 +0100 3) version = "0.7.0" 2f721807 rtnetlink/Cargo.toml (Stefan Bühler 2021-06-06 14:20:15 +0200 26) netlink-proto = { default-features = false, version = "0.6" } 83da33e2 rtnetlink/Cargo.toml (gabrik 2021-01-22 16:22:16 +0100 29) tokio = { version = "1.0.1", features = ["rt"], optional = true} 83da33e2 rtnetlink/Cargo.toml (gabrik 2021-01-22 16:22:16 +0100 30) async-std = { version = "1.9.0", features = ["unstable"], optional = true} ef3a79a8 rtnetlink/Cargo.toml (Tim Zhang 2021-01-15 19:31:38 +0800 35) tokio = { version = "1.0.1", features = ["macros", "rt", "rt-multi-thread"] } 83da33e2 rtnetlink/Cargo.toml (gabrik 2021-01-22 16:22:16 +0100 36) async-std = { version = "1.9.0", features = ["attributes"]} $ git log --oneline 88dde610.. rtnetlink/ 2f72180 Cargo.toml: move path specs to workspace [patch.crates-io] section 1e8bc53 CI: Fix rtnetlink example cae6e09 Merge pull request #97 from SkamDart/SkamDart/ip-monitor 35b6cb9 use `unwrap()` instead of `.is_ok()` so that the error is printed 2f0877a (origin/release) rustfmt, clippy 5c39136 Merge pull request #130 from benjumanji/wireguard-type af1ee71 Merge pull request #137 from little-dude/rtnetlink-macros 83da33e added features and examples to rtnetlink 079b5f3 (origin/rtnetlink-macros) rtnetlink: use macros in response handling b681f35 Add basic test for bringing up interface 5201dcd ip monitor clone ``` Based on the changes, decide whether bumping the patch or minor version. For crates that like `rtnetlink`, usually just bump the minor version. For `netlink-packet-*` and `netlink-sys`, try to bump it only if really necessary, because bumping it means bumping all the crates that depend on it. Once we have bumped all the version locally, push to a `release` branch to have CI running. If CI passes, just go with `cargo publish`, again starting from the `netlink-packet-*` and `netlink-sys` crates. `--dry-run` is nice but it doesn't really work. For instance if `netlink-packet-utils` is bumped from 0.x to 0.x+1, then `cargo publish --dry-run` will not work for `netlink-packet-core`, because the crate depends on `netlink-packet-utils` 0.x+1, which hasn't be published yet. [github_new_release]: https://github.com/little-dude/netlink/releases/new ================================================ FILE: audit/Cargo.toml ================================================ [package] name = "audit" version = "0.7.1" authors = ["Corentin Henry "] edition = "2018" homepage = "https://github.com/little-dude/netlink" keywords = ["netlink", "ip", "linux", "audit"] license = "MIT" readme = "../README.md" repository = "https://github.com/little-dude/netlink" description = "linux audit via netlink" [dependencies] futures = "0.3.11" thiserror = "1" netlink-packet-audit = { version = "0.4.1", path = "../netlink-packet-audit" } netlink-proto = { default-features = false, version = "0.10.0", path = "../netlink-proto" } [features] default = ["tokio_socket"] tokio_socket = ["netlink-proto/tokio_socket"] smol_socket = ["netlink-proto/smol_socket"] [dev-dependencies] tokio = { version = "1.0.1", default-features = false, features = ["macros", "rt-multi-thread"] } async-std = { version = "1.9.0", features = ["attributes"] } env_logger = "0.8.2" [[example]] name = "events_async" required-features = ["smol_socket"] ================================================ FILE: audit/examples/add_rules.rs ================================================ // SPDX-License-Identifier: MIT //! In this example, we create two rules which is equivalent to the following commands: //! //! auditctl -w /etc/passwd -p rwxa -k my_key //! auditctl -a always,exit -F arch=b64 -S personality -F key=bypass use audit::{ new_connection, packet::{ constants::AUDIT_ARCH_X86_64, RuleAction, RuleField, RuleFieldFlags, RuleFlags, RuleMessage, RuleSyscalls, }, Error, Handle, }; #[tokio::main] async fn main() -> Result<(), String> { let (connection, handle, _) = new_connection().map_err(|e| format!("{}", e))?; tokio::spawn(connection); add_rules(handle).await.map_err(|e| format!("{}", e)) } async fn add_rules(mut handle: Handle) -> Result<(), Error> { let etc_passwd_rule = RuleMessage { flags: RuleFlags::FilterExit, action: RuleAction::Always, fields: vec![ ( RuleField::Watch("/etc/passwd".into()), RuleFieldFlags::Equal, ), (RuleField::Perm(15), RuleFieldFlags::Equal), (RuleField::Filterkey("my_key".into()), RuleFieldFlags::Equal), ], syscalls: RuleSyscalls::new_maxed(), }; handle.add_rule(etc_passwd_rule).await?; let mut syscalls = RuleSyscalls::new_zeroed(); syscalls.set(135); let personality_syscall_rule = RuleMessage { flags: RuleFlags::FilterExit, action: RuleAction::Always, fields: vec![ (RuleField::Arch(AUDIT_ARCH_X86_64), RuleFieldFlags::Equal), (RuleField::Filterkey("bypass".into()), RuleFieldFlags::Equal), ], syscalls, }; handle.add_rule(personality_syscall_rule).await?; Ok(()) } ================================================ FILE: audit/examples/dump_audit_rules.rs ================================================ // SPDX-License-Identifier: MIT //! In this example, we create a netlink connection, and send a request to retrieve the list of //! rules. We receive a stream of rule messages that we just prints to the terminal. use audit::{new_connection, Error, Handle}; use futures::stream::TryStreamExt; #[tokio::main] async fn main() -> Result<(), String> { let (connection, handle, _) = new_connection().map_err(|e| format!("{}", e))?; tokio::spawn(connection); list_rules(handle).await.map_err(|e| format!("{}", e)) } async fn list_rules(mut handle: Handle) -> Result<(), Error> { let mut rules = handle.list_rules(); while let Some(rule) = rules.try_next().await? { println!("{:?}", rule); } Ok(()) } ================================================ FILE: audit/examples/events.rs ================================================ // SPDX-License-Identifier: MIT //! This example opens a netlink socket, enables audit events, and prints the events that are being //! received. use audit::new_connection; use futures::stream::StreamExt; #[tokio::main] async fn main() -> Result<(), String> { let (connection, mut handle, mut messages) = new_connection().map_err(|e| format!("{}", e))?; tokio::spawn(connection); handle.enable_events().await.map_err(|e| format!("{}", e))?; env_logger::init(); while let Some((msg, _)) = messages.next().await { println!("{:?}", msg); } Ok(()) } ================================================ FILE: audit/examples/events_async.rs ================================================ // SPDX-License-Identifier: MIT //! This example opens a netlink socket, enables audit events, and prints the events that are being //! received. use audit::new_connection; use futures::stream::StreamExt; #[async_std::main] async fn main() -> Result<(), String> { let (connection, mut handle, mut messages) = new_connection().map_err(|e| format!("{}", e))?; async_std::task::spawn(connection); handle.enable_events().await.map_err(|e| format!("{}", e))?; env_logger::init(); while let Some((msg, _)) = messages.next().await { println!("{:?}", msg); } Ok(()) } ================================================ FILE: audit/src/errors.rs ================================================ // SPDX-License-Identifier: MIT use thiserror::Error; use crate::packet::{AuditMessage, ErrorMessage, NetlinkMessage}; #[derive(Clone, Eq, PartialEq, Debug, Error)] pub enum Error { #[error("Received an unexpected message {0:?}")] UnexpectedMessage(NetlinkMessage), #[error("Received a netlink error message {0:?}")] NetlinkError(ErrorMessage), #[error("Request failed")] RequestFailed, } ================================================ FILE: audit/src/handle.rs ================================================ // SPDX-License-Identifier: MIT use std::process; use futures::{ future::{self, Either}, stream::{Stream, StreamExt, TryStream}, FutureExt, }; use netlink_proto::{sys::SocketAddr, ConnectionHandle}; use crate::packet::{ rules::RuleMessage, AuditMessage, NetlinkMessage, NetlinkPayload, StatusMessage, NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_EXCL, NLM_F_NONREC, NLM_F_REQUEST, }; // ========================================== // mask values // ========================================== pub const AUDIT_STATUS_ENABLED: u32 = 1; pub const AUDIT_STATUS_FAILURE: u32 = 2; pub const AUDIT_STATUS_PID: u32 = 4; pub const AUDIT_STATUS_RATE_LIMIT: u32 = 8; pub const AUDIT_STATUS_BACKLOG_LIMIT: u32 = 16; pub const AUDIT_STATUS_BACKLOG_WAIT_TIME: u32 = 32; pub const AUDIT_STATUS_LOST: u32 = 64; pub const AUDIT_FEATURE_BITMAP_BACKLOG_LIMIT: u32 = 1; pub const AUDIT_FEATURE_BITMAP_BACKLOG_WAIT_TIME: u32 = 2; pub const AUDIT_FEATURE_BITMAP_EXECUTABLE_PATH: u32 = 4; pub const AUDIT_FEATURE_BITMAP_EXCLUDE_EXTEND: u32 = 8; pub const AUDIT_FEATURE_BITMAP_SESSIONID_FILTER: u32 = 16; pub const AUDIT_FEATURE_BITMAP_LOST_RESET: u32 = 32; pub const AUDIT_FEATURE_BITMAP_FILTER_FS: u32 = 64; pub const AUDIT_FEATURE_BITMAP_ALL: u32 = 127; pub const AUDIT_VERSION_LATEST: u32 = 127; pub const AUDIT_VERSION_BACKLOG_LIMIT: u32 = 1; pub const AUDIT_VERSION_BACKLOG_WAIT_TIME: u32 = 2; use crate::Error; /// A handle to the netlink connection, used to send and receive netlink messsage #[derive(Clone, Debug)] pub struct Handle(ConnectionHandle); impl Handle { pub(crate) fn new(conn: ConnectionHandle) -> Self { Handle(conn) } /// Send a netlink message, and get the reponse as a stream of messages. pub fn request( &mut self, message: NetlinkMessage, ) -> Result>, Error> { self.0 .request(message, SocketAddr::new(0, 0)) .map_err(|_| Error::RequestFailed) } /// Send a netlink message that expects an acknowledgement. The returned future resolved when /// that ACK is received. If anything else is received, the future resolves into an error. async fn acked_request(&mut self, message: NetlinkMessage) -> Result<(), Error> { let mut response = self.request(message)?; if let Some(message) = response.next().await { let (header, payload) = message.into_parts(); // NetlinkError and AuditMessage are forwarded to the // handle. Ack is signaled by the stream finishing. if let NetlinkPayload::Error(err_msg) = payload { Err(Error::NetlinkError(err_msg)) } else { Err(Error::UnexpectedMessage(NetlinkMessage::new( header, payload, ))) } } else { Ok(()) } } /// Add the given rule pub async fn add_rule(&mut self, rule: RuleMessage) -> Result<(), Error> { let mut req = NetlinkMessage::from(AuditMessage::AddRule(rule)); req.header.flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE; self.acked_request(req).await } /// Deletes a given rule pub async fn del_rule(&mut self, rule: RuleMessage) -> Result<(), Error> { let mut req = NetlinkMessage::from(AuditMessage::DelRule(rule)); req.header.flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_NONREC; self.acked_request(req).await } /// List the current rules pub fn list_rules(&mut self) -> impl TryStream { let mut req = NetlinkMessage::from(AuditMessage::ListRules(None)); req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; match self.request(req) { Ok(response) => Either::Left(response.map(move |msg| { let (header, payload) = msg.into_parts(); match payload { NetlinkPayload::InnerMessage(AuditMessage::ListRules(Some(rule_msg))) => { Ok(rule_msg) } NetlinkPayload::Error(err_msg) => Err(Error::NetlinkError(err_msg)), _ => Err(Error::UnexpectedMessage(NetlinkMessage::new( header, payload, ))), } })), Err(e) => Either::Right(future::err::(e).into_stream()), } } /// Enable receiving audit events pub async fn enable_events(&mut self) -> Result<(), Error> { let mut status = StatusMessage::new(); status.enabled = 1; status.pid = process::id(); status.mask = AUDIT_STATUS_ENABLED | AUDIT_STATUS_PID; let mut req = NetlinkMessage::from(AuditMessage::SetStatus(status)); req.header.flags = NLM_F_REQUEST | NLM_F_ACK; self.acked_request(req).await } /// Get current audit status pub async fn get_status(&mut self) -> Result { let mut req = NetlinkMessage::from(AuditMessage::GetStatus(None)); req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; let mut request = self.request(req)?; let response = request.next().await.ok_or(Error::RequestFailed)?; match response.into_parts() { (_, NetlinkPayload::InnerMessage(AuditMessage::GetStatus(Some(status)))) => Ok(status), (header, payload) => Err(Error::UnexpectedMessage(NetlinkMessage::new( header, payload, ))), } } } ================================================ FILE: audit/src/lib.rs ================================================ // SPDX-License-Identifier: MIT mod handle; pub use crate::handle::*; mod errors; pub use crate::errors::*; pub use netlink_packet_audit as packet; pub mod proto { pub use netlink_proto::{Connection, ConnectionHandle, Error}; } pub use netlink_proto::sys; use std::io; use futures::channel::mpsc::UnboundedReceiver; #[allow(clippy::type_complexity)] #[cfg(feature = "tokio_socket")] pub fn new_connection() -> io::Result<( proto::Connection, Handle, UnboundedReceiver<( packet::NetlinkMessage, sys::SocketAddr, )>, )> { new_connection_with_socket() } #[allow(clippy::type_complexity)] pub fn new_connection_with_socket() -> io::Result<( proto::Connection, Handle, UnboundedReceiver<( packet::NetlinkMessage, sys::SocketAddr, )>, )> where S: sys::AsyncSocket, { let (conn, handle, messages) = netlink_proto::new_connection_with_codec(sys::protocols::NETLINK_AUDIT)?; Ok((conn, Handle::new(handle), messages)) } ================================================ FILE: ethtool/Cargo.toml ================================================ [package] name = "ethtool" version = "0.2.3" authors = ["Gris Ge "] license = "MIT" edition = "2018" description = "Linux Ethtool Communication Library" keywords = ["network"] categories = ["network-programming", "os"] readme = "../README.md" [lib] name = "ethtool" path = "src/lib.rs" crate-type = ["lib"] [features] default = ["tokio_socket"] tokio_socket = ["netlink-proto/tokio_socket", "tokio"] smol_socket = ["netlink-proto/smol_socket", "async-std"] [dependencies] anyhow = "1.0.44" async-std = { version = "1.9.0", optional = true} byteorder = "1.4.3" futures = "0.3.17" log = "0.4.14" genetlink = { default-features = false, version = "0.2.1", path = "../genetlink" } netlink-packet-core = { version = "0.4.2", path = "../netlink-packet-core" } netlink-packet-generic = { version = "0.3.1", path = "../netlink-packet-generic" } netlink-packet-utils = { version = "0.5.1", path = "../netlink-packet-utils" } netlink-proto = { default-features = false, version = "0.10", path = "../netlink-proto" } netlink-sys = { version = "0.8.3", path = "../netlink-sys" } thiserror = "1.0.29" tokio = { version = "1.0.1", features = ["rt"], optional = true} [dev-dependencies] tokio = { version = "1.11.0", features = ["macros", "rt", "rt-multi-thread"] } env_logger = "0.9.0" [[example]] name = "dump_pause" required-features = ["tokio_socket"] ================================================ FILE: ethtool/examples/dump_coalesce.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; // Once we find a way to load netsimdev kernel module in CI, we can convert this // to a test fn main() { let rt = tokio::runtime::Builder::new_current_thread() .enable_io() .build() .unwrap(); rt.block_on(get_coalesce(None)); } async fn get_coalesce(iface_name: Option<&str>) { let (connection, mut handle, _) = ethtool::new_connection().unwrap(); tokio::spawn(connection); let mut coalesce_handle = handle.coalesce().get(iface_name).execute().await; let mut msgs = Vec::new(); while let Some(msg) = coalesce_handle.try_next().await.unwrap() { msgs.push(msg); } assert!(!msgs.is_empty()); for msg in msgs { println!("{:?}", msg); } } ================================================ FILE: ethtool/examples/dump_features.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; // Once we find a way to load netsimdev kernel module in CI, we can convert this // to a test fn main() { let rt = tokio::runtime::Builder::new_current_thread() .enable_io() .build() .unwrap(); rt.block_on(get_feature(None)); } async fn get_feature(iface_name: Option<&str>) { let (connection, mut handle, _) = ethtool::new_connection().unwrap(); tokio::spawn(connection); let mut feature_handle = handle.feature().get(iface_name).execute().await; let mut msgs = Vec::new(); while let Some(msg) = feature_handle.try_next().await.unwrap() { msgs.push(msg); } assert!(!msgs.is_empty()); for msg in msgs { println!("{:?}", msg); } } ================================================ FILE: ethtool/examples/dump_link_mode.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; // Once we find a way to load netsimdev kernel module in CI, we can convert this // to a test fn main() { let rt = tokio::runtime::Builder::new_current_thread() .enable_io() .build() .unwrap(); rt.block_on(get_link_mode(None)); } async fn get_link_mode(iface_name: Option<&str>) { let (connection, mut handle, _) = ethtool::new_connection().unwrap(); tokio::spawn(connection); let mut link_mode_handle = handle.link_mode().get(iface_name).execute().await; let mut msgs = Vec::new(); while let Some(msg) = link_mode_handle.try_next().await.unwrap() { msgs.push(msg); } assert!(!msgs.is_empty()); for msg in msgs { println!("{:?}", msg); } } ================================================ FILE: ethtool/examples/dump_pause.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; // Once we find a way to load netsimdev kernel module in CI, we can convert this // to a test fn main() { env_logger::init(); let rt = tokio::runtime::Builder::new_current_thread() .enable_io() .build() .unwrap(); rt.block_on(get_pause(None)); } async fn get_pause(iface_name: Option<&str>) { let (connection, mut handle, _) = ethtool::new_connection().unwrap(); tokio::spawn(connection); let mut pause_handle = handle.pause().get(iface_name).execute().await; let mut msgs = Vec::new(); while let Some(msg) = pause_handle.try_next().await.unwrap() { msgs.push(msg); } assert!(!msgs.is_empty()); println!("{:?}", msgs); } ================================================ FILE: ethtool/examples/dump_rings.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; // Once we find a way to load netsimdev kernel module in CI, we can convert this // to a test fn main() { let rt = tokio::runtime::Builder::new_current_thread() .enable_io() .build() .unwrap(); rt.block_on(get_ring(None)); } async fn get_ring(iface_name: Option<&str>) { let (connection, mut handle, _) = ethtool::new_connection().unwrap(); tokio::spawn(connection); let mut ring_handle = handle.ring().get(iface_name).execute().await; let mut msgs = Vec::new(); while let Some(msg) = ring_handle.try_next().await.unwrap() { msgs.push(msg); } assert!(!msgs.is_empty()); for msg in msgs { println!("{:?}", msg); } } ================================================ FILE: ethtool/src/coalesce/attr.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{DefaultNla, Nla, NlaBuffer, NlasIterator, NLA_F_NESTED}, parsers::{parse_u32, parse_u8}, DecodeError, Emitable, Parseable, }; use crate::{EthtoolAttr, EthtoolHeader}; const ETHTOOL_A_COALESCE_HEADER: u16 = 1; const ETHTOOL_A_COALESCE_RX_USECS: u16 = 2; const ETHTOOL_A_COALESCE_RX_MAX_FRAMES: u16 = 3; const ETHTOOL_A_COALESCE_RX_USECS_IRQ: u16 = 4; const ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ: u16 = 5; const ETHTOOL_A_COALESCE_TX_USECS: u16 = 6; const ETHTOOL_A_COALESCE_TX_MAX_FRAMES: u16 = 7; const ETHTOOL_A_COALESCE_TX_USECS_IRQ: u16 = 8; const ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ: u16 = 9; const ETHTOOL_A_COALESCE_STATS_BLOCK_USECS: u16 = 10; const ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX: u16 = 11; const ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX: u16 = 12; const ETHTOOL_A_COALESCE_PKT_RATE_LOW: u16 = 13; const ETHTOOL_A_COALESCE_RX_USECS_LOW: u16 = 14; const ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW: u16 = 15; const ETHTOOL_A_COALESCE_TX_USECS_LOW: u16 = 16; const ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW: u16 = 17; const ETHTOOL_A_COALESCE_PKT_RATE_HIGH: u16 = 18; const ETHTOOL_A_COALESCE_RX_USECS_HIGH: u16 = 19; const ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH: u16 = 20; const ETHTOOL_A_COALESCE_TX_USECS_HIGH: u16 = 21; const ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH: u16 = 22; const ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL: u16 = 23; #[derive(Debug, PartialEq, Eq, Clone)] pub enum EthtoolCoalesceAttr { Header(Vec), RxUsecs(u32), RxMaxFrames(u32), RxUsecsIrq(u32), RxMaxFramesIrq(u32), TxUsecs(u32), TxMaxFrames(u32), TxUsecsIrq(u32), TxMaxFramesIrq(u32), StatsBlockUsecs(u32), UseAdaptiveRx(bool), UseAdaptiveTx(bool), PktRateLow(u32), RxUsecsLow(u32), RxMaxFramesLow(u32), TxUsecsLow(u32), TxMaxFramesLow(u32), PktRateHigh(u32), RxUsecsHigh(u32), RxMaxFramesHigh(u32), TxUsecsHigh(u32), TxMaxFramesHigh(u32), RateSampleInterval(u32), Other(DefaultNla), } impl Nla for EthtoolCoalesceAttr { fn value_len(&self) -> usize { match self { Self::Header(hdrs) => hdrs.as_slice().buffer_len(), Self::RxUsecs(_) | Self::RxMaxFrames(_) | Self::RxUsecsIrq(_) | Self::RxMaxFramesIrq(_) | Self::TxUsecs(_) | Self::TxMaxFrames(_) | Self::TxUsecsIrq(_) | Self::TxMaxFramesIrq(_) | Self::StatsBlockUsecs(_) | Self::PktRateLow(_) | Self::RxUsecsLow(_) | Self::RxMaxFramesLow(_) | Self::TxUsecsLow(_) | Self::TxMaxFramesLow(_) | Self::PktRateHigh(_) | Self::RxUsecsHigh(_) | Self::RxMaxFramesHigh(_) | Self::TxUsecsHigh(_) | Self::TxMaxFramesHigh(_) | Self::RateSampleInterval(_) => 4, Self::UseAdaptiveRx(_) | Self::UseAdaptiveTx(_) => 1, Self::Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { Self::Header(_) => ETHTOOL_A_COALESCE_HEADER | NLA_F_NESTED, Self::RxUsecs(_) => ETHTOOL_A_COALESCE_RX_USECS, Self::RxMaxFrames(_) => ETHTOOL_A_COALESCE_RX_MAX_FRAMES, Self::RxUsecsIrq(_) => ETHTOOL_A_COALESCE_RX_USECS_IRQ, Self::RxMaxFramesIrq(_) => ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ, Self::TxUsecs(_) => ETHTOOL_A_COALESCE_TX_USECS, Self::TxMaxFrames(_) => ETHTOOL_A_COALESCE_TX_MAX_FRAMES, Self::TxUsecsIrq(_) => ETHTOOL_A_COALESCE_TX_USECS_IRQ, Self::TxMaxFramesIrq(_) => ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ, Self::StatsBlockUsecs(_) => ETHTOOL_A_COALESCE_STATS_BLOCK_USECS, Self::UseAdaptiveRx(_) => ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX, Self::UseAdaptiveTx(_) => ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX, Self::PktRateLow(_) => ETHTOOL_A_COALESCE_PKT_RATE_LOW, Self::RxUsecsLow(_) => ETHTOOL_A_COALESCE_RX_USECS_LOW, Self::RxMaxFramesLow(_) => ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW, Self::TxUsecsLow(_) => ETHTOOL_A_COALESCE_TX_USECS_LOW, Self::TxMaxFramesLow(_) => ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW, Self::PktRateHigh(_) => ETHTOOL_A_COALESCE_PKT_RATE_HIGH, Self::RxUsecsHigh(_) => ETHTOOL_A_COALESCE_RX_USECS_HIGH, Self::RxMaxFramesHigh(_) => ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH, Self::TxUsecsHigh(_) => ETHTOOL_A_COALESCE_TX_USECS_HIGH, Self::TxMaxFramesHigh(_) => ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH, Self::RateSampleInterval(_) => ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL, Self::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { Self::Header(ref nlas) => nlas.as_slice().emit(buffer), Self::Other(ref attr) => attr.emit(buffer), Self::RxUsecs(d) | Self::RxMaxFrames(d) | Self::RxUsecsIrq(d) | Self::RxMaxFramesIrq(d) | Self::TxUsecs(d) | Self::TxMaxFrames(d) | Self::TxUsecsIrq(d) | Self::TxMaxFramesIrq(d) | Self::StatsBlockUsecs(d) | Self::PktRateLow(d) | Self::RxUsecsLow(d) | Self::RxMaxFramesLow(d) | Self::TxUsecsLow(d) | Self::TxMaxFramesLow(d) | Self::PktRateHigh(d) | Self::RxUsecsHigh(d) | Self::RxMaxFramesHigh(d) | Self::TxUsecsHigh(d) | Self::TxMaxFramesHigh(d) | Self::RateSampleInterval(d) => NativeEndian::write_u32(buffer, *d), Self::UseAdaptiveRx(d) | Self::UseAdaptiveTx(d) => buffer[0] = if *d { 1 } else { 0 }, } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for EthtoolCoalesceAttr { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { ETHTOOL_A_COALESCE_HEADER => { let mut nlas = Vec::new(); let error_msg = "failed to parse coalesce header attributes"; for nla in NlasIterator::new(payload) { let nla = &nla.context(error_msg)?; let parsed = EthtoolHeader::parse(nla).context(error_msg)?; nlas.push(parsed); } Self::Header(nlas) } ETHTOOL_A_COALESCE_RX_USECS => Self::RxUsecs( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_RX_USECS value")?, ), ETHTOOL_A_COALESCE_RX_MAX_FRAMES => Self::RxMaxFrames( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_RX_MAX_FRAMES value")?, ), ETHTOOL_A_COALESCE_RX_USECS_IRQ => Self::RxUsecsIrq( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_RX_USECS_IRQ value")?, ), ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ => Self::RxMaxFramesIrq( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ value")?, ), ETHTOOL_A_COALESCE_TX_USECS => Self::TxUsecs( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_TX_USECS value")?, ), ETHTOOL_A_COALESCE_TX_MAX_FRAMES => Self::TxMaxFrames( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_TX_MAX_FRAMES value")?, ), ETHTOOL_A_COALESCE_TX_USECS_IRQ => Self::TxUsecsIrq( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_TX_USECS_IRQ value")?, ), ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ => Self::TxMaxFramesIrq( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ value")?, ), ETHTOOL_A_COALESCE_STATS_BLOCK_USECS => Self::StatsBlockUsecs( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_STATS_BLOCK_USECS value")?, ), ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX => Self::UseAdaptiveRx( parse_u8(payload).context("Invalid ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX value")? == 1, ), ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX => Self::UseAdaptiveTx( parse_u8(payload).context("Invalid ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX value")? == 1, ), ETHTOOL_A_COALESCE_PKT_RATE_LOW => Self::PktRateLow( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_PKT_RATE_LOW value")?, ), ETHTOOL_A_COALESCE_RX_USECS_LOW => Self::RxUsecsLow( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_RX_USECS_LOW value")?, ), ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW => Self::RxMaxFramesLow( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW value")?, ), ETHTOOL_A_COALESCE_TX_USECS_LOW => Self::TxUsecsLow( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_TX_USECS_LOW value")?, ), ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW => Self::TxMaxFramesLow( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW value")?, ), ETHTOOL_A_COALESCE_PKT_RATE_HIGH => Self::PktRateHigh( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_PKT_RATE_HIGH value")?, ), ETHTOOL_A_COALESCE_RX_USECS_HIGH => Self::RxUsecsHigh( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_RX_USECS_HIGH value")?, ), ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH => Self::RxMaxFramesHigh( parse_u32(payload) .context("Invalid ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH value")?, ), ETHTOOL_A_COALESCE_TX_USECS_HIGH => Self::TxUsecsHigh( parse_u32(payload).context("Invalid ETHTOOL_A_COALESCE_TX_USECS_HIGH value")?, ), ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH => Self::TxMaxFramesHigh( parse_u32(payload) .context("Invalid ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH value")?, ), ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL => Self::RateSampleInterval( parse_u32(payload) .context("Invalid ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL value")?, ), _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), }) } } pub(crate) fn parse_coalesce_nlas(buffer: &[u8]) -> Result, DecodeError> { let mut nlas = Vec::new(); for nla in NlasIterator::new(buffer) { let error_msg = format!( "Failed to parse ethtool coalesce message attribute {:?}", nla ); let nla = &nla.context(error_msg.clone())?; let parsed = EthtoolCoalesceAttr::parse(nla).context(error_msg)?; nlas.push(EthtoolAttr::Coalesce(parsed)); } Ok(nlas) } ================================================ FILE: ethtool/src/coalesce/get.rs ================================================ // SPDX-License-Identifier: MIT use futures::TryStream; use netlink_packet_generic::GenlMessage; use crate::{ethtool_execute, EthtoolError, EthtoolHandle, EthtoolMessage}; pub struct EthtoolCoalesceGetRequest { handle: EthtoolHandle, iface_name: Option, } impl EthtoolCoalesceGetRequest { pub(crate) fn new(handle: EthtoolHandle, iface_name: Option<&str>) -> Self { EthtoolCoalesceGetRequest { handle, iface_name: iface_name.map(|i| i.to_string()), } } pub async fn execute( self, ) -> impl TryStream, Error = EthtoolError> { let EthtoolCoalesceGetRequest { mut handle, iface_name, } = self; let ethtool_msg = EthtoolMessage::new_coalesce_get(iface_name.as_deref()); ethtool_execute(&mut handle, iface_name.is_none(), ethtool_msg).await } } ================================================ FILE: ethtool/src/coalesce/handle.rs ================================================ // SPDX-License-Identifier: MIT use crate::{EthtoolCoalesceGetRequest, EthtoolHandle}; pub struct EthtoolCoalesceHandle(EthtoolHandle); impl EthtoolCoalesceHandle { pub fn new(handle: EthtoolHandle) -> Self { EthtoolCoalesceHandle(handle) } /// Retrieve the ethtool coalesces of a interface (equivalent to `ethtool -c eth1`) pub fn get(&mut self, iface_name: Option<&str>) -> EthtoolCoalesceGetRequest { EthtoolCoalesceGetRequest::new(self.0.clone(), iface_name) } } ================================================ FILE: ethtool/src/coalesce/mod.rs ================================================ // SPDX-License-Identifier: MIT mod attr; mod get; mod handle; pub(crate) use attr::parse_coalesce_nlas; pub use attr::EthtoolCoalesceAttr; pub use get::EthtoolCoalesceGetRequest; pub use handle::EthtoolCoalesceHandle; ================================================ FILE: ethtool/src/connection.rs ================================================ // SPDX-License-Identifier: MIT use std::io; use futures::channel::mpsc::UnboundedReceiver; use genetlink::message::RawGenlMessage; use netlink_packet_core::NetlinkMessage; use netlink_proto::Connection; use netlink_sys::{AsyncSocket, SocketAddr}; use crate::EthtoolHandle; #[cfg(feature = "tokio_socket")] #[allow(clippy::type_complexity)] pub fn new_connection() -> io::Result<( Connection, EthtoolHandle, UnboundedReceiver<(NetlinkMessage, SocketAddr)>, )> { new_connection_with_socket() } #[allow(clippy::type_complexity)] pub fn new_connection_with_socket() -> io::Result<( Connection, EthtoolHandle, UnboundedReceiver<(NetlinkMessage, SocketAddr)>, )> where S: AsyncSocket, { let (conn, handle, messages) = genetlink::new_connection_with_socket()?; Ok((conn, EthtoolHandle::new(handle), messages)) } ================================================ FILE: ethtool/src/error.rs ================================================ // SPDX-License-Identifier: MIT use thiserror::Error; use netlink_packet_core::{ErrorMessage, NetlinkMessage}; use netlink_packet_generic::GenlMessage; use crate::EthtoolMessage; #[derive(Clone, Eq, PartialEq, Debug, Error)] pub enum EthtoolError { #[error("Received an unexpected message {0:?}")] UnexpectedMessage(NetlinkMessage>), #[error("Received a netlink error message {0}")] NetlinkError(ErrorMessage), #[error("A netlink request failed")] RequestFailed(String), #[error("A bug in this crate")] Bug(String), } ================================================ FILE: ethtool/src/feature/attr.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use log::warn; use netlink_packet_utils::{ nla::{DefaultNla, Nla, NlaBuffer, NlasIterator, NLA_F_NESTED}, parsers::{parse_string, parse_u32}, DecodeError, Emitable, Parseable, }; use crate::{EthtoolAttr, EthtoolHeader}; const ETHTOOL_A_FEATURES_HEADER: u16 = 1; const ETHTOOL_A_FEATURES_HW: u16 = 2; // User changable features const ETHTOOL_A_FEATURES_WANTED: u16 = 3; // User requested fatures const ETHTOOL_A_FEATURES_ACTIVE: u16 = 4; // Active features const ETHTOOL_A_FEATURES_NOCHANGE: u16 = 5; const ETHTOOL_A_BITSET_BITS: u16 = 3; const ETHTOOL_A_BITSET_BITS_BIT: u16 = 1; const ETHTOOL_A_BITSET_BIT_INDEX: u16 = 1; const ETHTOOL_A_BITSET_BIT_NAME: u16 = 2; const ETHTOOL_A_BITSET_BIT_VALUE: u16 = 3; #[derive(Debug, PartialEq, Eq, Clone)] pub struct EthtoolFeatureBit { pub index: u32, pub name: String, pub value: bool, } impl EthtoolFeatureBit { fn new(has_mask: bool) -> Self { Self { index: 0, name: "".into(), value: !has_mask, } } } fn feature_bits_len(_feature_bits: &[EthtoolFeatureBit]) -> usize { todo!("Does not support changing ethtool feature yet") } fn feature_bits_emit(_feature_bits: &[EthtoolFeatureBit], _buffer: &mut [u8]) { todo!("Does not support changing ethtool feature yet") } #[derive(Debug, PartialEq, Eq, Clone)] pub enum EthtoolFeatureAttr { Header(Vec), Hw(Vec), Wanted(Vec), Active(Vec), NoChange(Vec), Other(DefaultNla), } impl Nla for EthtoolFeatureAttr { fn value_len(&self) -> usize { match self { Self::Header(hdrs) => hdrs.as_slice().buffer_len(), Self::Hw(feature_bits) | Self::Wanted(feature_bits) | Self::Active(feature_bits) | Self::NoChange(feature_bits) => feature_bits_len(feature_bits.as_slice()), Self::Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { Self::Header(_) => ETHTOOL_A_FEATURES_HEADER | NLA_F_NESTED, Self::Hw(_) => ETHTOOL_A_FEATURES_HW | NLA_F_NESTED, Self::Wanted(_) => ETHTOOL_A_FEATURES_WANTED | NLA_F_NESTED, Self::Active(_) => ETHTOOL_A_FEATURES_ACTIVE | NLA_F_NESTED, Self::NoChange(_) => ETHTOOL_A_FEATURES_NOCHANGE | NLA_F_NESTED, Self::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { Self::Header(ref nlas) => nlas.as_slice().emit(buffer), Self::Hw(feature_bits) | Self::Wanted(feature_bits) | Self::Active(feature_bits) | Self::NoChange(feature_bits) => feature_bits_emit(feature_bits.as_slice(), buffer), Self::Other(ref attr) => attr.emit(buffer), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for EthtoolFeatureAttr { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { ETHTOOL_A_FEATURES_HEADER => { let mut nlas = Vec::new(); let error_msg = "failed to parse feature header attributes"; for nla in NlasIterator::new(payload) { let nla = &nla.context(error_msg)?; let parsed = EthtoolHeader::parse(nla).context(error_msg)?; nlas.push(parsed); } Self::Header(nlas) } ETHTOOL_A_FEATURES_HW => { Self::Hw(parse_bitset_bits_nlas( payload, true, /* ETHTOOL_A_FEATURES_HW is using mask */ )?) } ETHTOOL_A_FEATURES_WANTED => { Self::Wanted(parse_bitset_bits_nlas( payload, false, /* ETHTOOL_A_FEATURES_WANTED does not use mask */ )?) } ETHTOOL_A_FEATURES_ACTIVE => { Self::Active(parse_bitset_bits_nlas( payload, false, /* ETHTOOL_A_FEATURES_ACTIVE does not use mask */ )?) } ETHTOOL_A_FEATURES_NOCHANGE => { Self::NoChange(parse_bitset_bits_nlas( payload, false, /* ETHTOOL_A_FEATURES_NOCHANGE does not use mask */ )?) } _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), }) } } fn parse_bitset_bits_nlas( raw: &[u8], has_mask: bool, ) -> Result, DecodeError> { let error_msg = "failed to parse feature bit sets"; for nla in NlasIterator::new(raw) { let nla = &nla.context(error_msg)?; if nla.kind() == ETHTOOL_A_BITSET_BITS { return parse_bitset_bits_nla(nla.value(), has_mask); } } Err("No ETHTOOL_A_BITSET_BITS NLA found".into()) } fn parse_bitset_bits_nla( raw: &[u8], has_mask: bool, ) -> Result, DecodeError> { let mut feature_bits = Vec::new(); let error_msg = "Failed to parse ETHTOOL_A_BITSET_BITS attributes"; for bit_nla in NlasIterator::new(raw) { let bit_nla = &bit_nla.context(error_msg)?; match bit_nla.kind() { ETHTOOL_A_BITSET_BITS_BIT => { let error_msg = "Failed to parse ETHTOOL_A_BITSET_BITS_BIT attributes"; let nlas = NlasIterator::new(bit_nla.value()); let mut cur_bit_info = EthtoolFeatureBit::new(has_mask); for nla in nlas { let nla = &nla.context(error_msg)?; let payload = nla.value(); match nla.kind() { ETHTOOL_A_BITSET_BIT_INDEX => { if cur_bit_info.index != 0 && !&cur_bit_info.name.is_empty() { feature_bits.push(cur_bit_info); cur_bit_info = EthtoolFeatureBit::new(has_mask); } cur_bit_info.index = parse_u32(payload) .context("Invald ETHTOOL_A_BITSET_BIT_INDEX value")?; } ETHTOOL_A_BITSET_BIT_NAME => { cur_bit_info.name = parse_string(payload) .context("Invald ETHTOOL_A_BITSET_BIT_NAME value")?; } ETHTOOL_A_BITSET_BIT_VALUE => { cur_bit_info.value = true; } _ => { warn!( "Unknown ETHTOOL_A_BITSET_BITS_BIT {} {:?}", nla.kind(), nla.value(), ); } } } if cur_bit_info.index != 0 && !&cur_bit_info.name.is_empty() { feature_bits.push(cur_bit_info); } } _ => { warn!( "Unknown ETHTOOL_A_BITSET_BITS kind {}, {:?}", bit_nla.kind(), bit_nla.value() ); } }; } Ok(feature_bits) } pub(crate) fn parse_feature_nlas(buffer: &[u8]) -> Result, DecodeError> { let mut nlas = Vec::new(); for nla in NlasIterator::new(buffer) { let error_msg = format!( "Failed to parse ethtool feature message attribute {:?}", nla ); let nla = &nla.context(error_msg.clone())?; let parsed = EthtoolFeatureAttr::parse(nla).context(error_msg)?; nlas.push(EthtoolAttr::Feature(parsed)); } Ok(nlas) } ================================================ FILE: ethtool/src/feature/get.rs ================================================ // SPDX-License-Identifier: MIT use futures::TryStream; use netlink_packet_generic::GenlMessage; use crate::{ethtool_execute, EthtoolError, EthtoolHandle, EthtoolMessage}; pub struct EthtoolFeatureGetRequest { handle: EthtoolHandle, iface_name: Option, } impl EthtoolFeatureGetRequest { pub(crate) fn new(handle: EthtoolHandle, iface_name: Option<&str>) -> Self { EthtoolFeatureGetRequest { handle, iface_name: iface_name.map(|i| i.to_string()), } } pub async fn execute( self, ) -> impl TryStream, Error = EthtoolError> { let EthtoolFeatureGetRequest { mut handle, iface_name, } = self; let ethtool_msg = EthtoolMessage::new_feature_get(iface_name.as_deref()); ethtool_execute(&mut handle, iface_name.is_none(), ethtool_msg).await } } ================================================ FILE: ethtool/src/feature/handle.rs ================================================ // SPDX-License-Identifier: MIT use crate::{EthtoolFeatureGetRequest, EthtoolHandle}; pub struct EthtoolFeatureHandle(EthtoolHandle); impl EthtoolFeatureHandle { pub fn new(handle: EthtoolHandle) -> Self { EthtoolFeatureHandle(handle) } /// Retrieve the ethtool features of a interface (equivalent to `ethtool -k eth1`) pub fn get(&mut self, iface_name: Option<&str>) -> EthtoolFeatureGetRequest { EthtoolFeatureGetRequest::new(self.0.clone(), iface_name) } } ================================================ FILE: ethtool/src/feature/mod.rs ================================================ // SPDX-License-Identifier: MIT mod attr; mod get; mod handle; pub(crate) use attr::parse_feature_nlas; pub use attr::{EthtoolFeatureAttr, EthtoolFeatureBit}; pub use get::EthtoolFeatureGetRequest; pub use handle::EthtoolFeatureHandle; ================================================ FILE: ethtool/src/handle.rs ================================================ // SPDX-License-Identifier: MIT use futures::{future::Either, FutureExt, Stream, StreamExt, TryStream}; use genetlink::GenetlinkHandle; use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_DUMP, NLM_F_REQUEST}; use netlink_packet_generic::GenlMessage; use netlink_packet_utils::DecodeError; use crate::{ try_ethtool, EthtoolCoalesceHandle, EthtoolError, EthtoolFeatureHandle, EthtoolLinkModeHandle, EthtoolMessage, EthtoolPauseHandle, EthtoolRingHandle, }; #[derive(Clone, Debug)] pub struct EthtoolHandle { pub handle: GenetlinkHandle, } impl EthtoolHandle { pub(crate) fn new(handle: GenetlinkHandle) -> Self { EthtoolHandle { handle } } pub fn pause(&mut self) -> EthtoolPauseHandle { EthtoolPauseHandle::new(self.clone()) } pub fn feature(&mut self) -> EthtoolFeatureHandle { EthtoolFeatureHandle::new(self.clone()) } pub fn link_mode(&mut self) -> EthtoolLinkModeHandle { EthtoolLinkModeHandle::new(self.clone()) } pub fn ring(&mut self) -> EthtoolRingHandle { EthtoolRingHandle::new(self.clone()) } pub fn coalesce(&mut self) -> EthtoolCoalesceHandle { EthtoolCoalesceHandle::new(self.clone()) } pub async fn request( &mut self, message: NetlinkMessage>, ) -> Result< impl Stream>, DecodeError>>, EthtoolError, > { self.handle .request(message) .await .map_err(|e| EthtoolError::RequestFailed(format!("BUG: Request failed with {}", e))) } } pub(crate) async fn ethtool_execute( handle: &mut EthtoolHandle, is_dump: bool, ethtool_msg: EthtoolMessage, ) -> impl TryStream, Error = EthtoolError> { let nl_header_flags = if is_dump { // The NLM_F_ACK is required due to bug of kernel: // https://bugzilla.redhat.com/show_bug.cgi?id=1953847 // without `NLM_F_MULTI`, rust-netlink will not parse // multiple netlink message in single socket reply. // Using NLM_F_ACK will force rust-netlink to parse all till // acked at the end. NLM_F_DUMP | NLM_F_REQUEST | NLM_F_ACK } else { NLM_F_REQUEST }; let mut nl_msg = NetlinkMessage::from(GenlMessage::from_payload(ethtool_msg)); nl_msg.header.flags = nl_header_flags; match handle.request(nl_msg).await { Ok(response) => Either::Left(response.map(move |msg| Ok(try_ethtool!(msg)))), Err(e) => Either::Right( futures::future::err::, EthtoolError>(e).into_stream(), ), } } ================================================ FILE: ethtool/src/header.rs ================================================ // SPDX-License-Identifier: MIT use std::ffi::CString; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{DefaultNla, Nla, NlaBuffer}, parsers::{parse_string, parse_u32}, DecodeError, Parseable, }; const ALTIFNAMSIZ: usize = 128; const ETHTOOL_A_HEADER_DEV_INDEX: u16 = 1; const ETHTOOL_A_HEADER_DEV_NAME: u16 = 2; const ETHTOOL_A_HEADER_FLAGS: u16 = 3; #[derive(Debug, PartialEq, Eq, Clone)] pub enum EthtoolHeader { DevIndex(u32), DevName(String), Flags(u32), Other(DefaultNla), } impl Nla for EthtoolHeader { fn value_len(&self) -> usize { match self { Self::DevIndex(_) | Self::Flags(_) => 4, Self::DevName(s) => { if s.len() + 1 > ALTIFNAMSIZ { ALTIFNAMSIZ } else { s.len() + 1 } } Self::Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { Self::DevIndex(_) => ETHTOOL_A_HEADER_DEV_INDEX, Self::DevName(_) => ETHTOOL_A_HEADER_DEV_NAME, Self::Flags(_) => ETHTOOL_A_HEADER_FLAGS, Self::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { Self::DevIndex(value) | Self::Flags(value) => NativeEndian::write_u32(buffer, *value), Self::DevName(s) => str_to_zero_ended_u8_array(s, buffer, ALTIFNAMSIZ), Self::Other(ref attr) => attr.emit_value(buffer), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for EthtoolHeader { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { ETHTOOL_A_HEADER_DEV_INDEX => Self::DevIndex( parse_u32(payload).context("invalid ETHTOOL_A_HEADER_DEV_INDEX value")?, ), ETHTOOL_A_HEADER_FLAGS => { Self::Flags(parse_u32(payload).context("invalid ETHTOOL_A_HEADER_FLAGS value")?) } ETHTOOL_A_HEADER_DEV_NAME => Self::DevName( parse_string(payload).context("invalid ETHTOOL_A_HEADER_DEV_NAME value")?, ), _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), }) } } fn str_to_zero_ended_u8_array(src_str: &str, buffer: &mut [u8], max_size: usize) { if let Ok(src_cstring) = CString::new(src_str.as_bytes()) { let src_null_ended_str = src_cstring.into_bytes_with_nul(); if src_null_ended_str.len() > max_size { buffer[..max_size].clone_from_slice(&src_null_ended_str[..max_size]) } else { buffer[..src_null_ended_str.len()].clone_from_slice(&src_null_ended_str) } } } ================================================ FILE: ethtool/src/lib.rs ================================================ // SPDX-License-Identifier: MIT mod coalesce; mod connection; mod error; mod feature; mod handle; mod header; mod link_mode; mod macros; mod message; mod pause; mod ring; pub use coalesce::{EthtoolCoalesceAttr, EthtoolCoalesceGetRequest, EthtoolCoalesceHandle}; #[cfg(feature = "tokio_socket")] pub use connection::new_connection; pub use connection::new_connection_with_socket; pub use error::EthtoolError; pub use feature::{ EthtoolFeatureAttr, EthtoolFeatureBit, EthtoolFeatureGetRequest, EthtoolFeatureHandle, }; pub use handle::EthtoolHandle; pub use header::EthtoolHeader; pub use link_mode::{ EthtoolLinkModeAttr, EthtoolLinkModeDuplex, EthtoolLinkModeGetRequest, EthtoolLinkModeHandle, }; pub use message::{EthtoolAttr, EthtoolCmd, EthtoolMessage}; pub use pause::{ EthtoolPauseAttr, EthtoolPauseGetRequest, EthtoolPauseHandle, EthtoolPauseStatAttr, }; pub use ring::{EthtoolRingAttr, EthtoolRingGetRequest, EthtoolRingHandle}; pub(crate) use handle::ethtool_execute; ================================================ FILE: ethtool/src/link_mode/attr.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use log::warn; use netlink_packet_utils::{ nla::{DefaultNla, Nla, NlaBuffer, NlasIterator, NLA_F_NESTED}, parsers::{parse_string, parse_u32, parse_u8}, DecodeError, Emitable, Parseable, }; use crate::{EthtoolAttr, EthtoolHeader}; const ETHTOOL_A_LINKMODES_HEADER: u16 = 1; const ETHTOOL_A_LINKMODES_AUTONEG: u16 = 2; const ETHTOOL_A_LINKMODES_OURS: u16 = 3; const ETHTOOL_A_LINKMODES_PEER: u16 = 4; const ETHTOOL_A_LINKMODES_SPEED: u16 = 5; const ETHTOOL_A_LINKMODES_DUPLEX: u16 = 6; const ETHTOOL_A_LINKMODES_SUBORDINATE_CFG: u16 = 7; const ETHTOOL_A_LINKMODES_SUBORDINATE_STATE: u16 = 8; const ETHTOOL_A_LINKMODES_LANES: u16 = 9; const ETHTOOL_A_BITSET_BITS: u16 = 3; const ETHTOOL_A_BITSET_BITS_BIT: u16 = 1; const ETHTOOL_A_BITSET_BIT_INDEX: u16 = 1; const ETHTOOL_A_BITSET_BIT_NAME: u16 = 2; const ETHTOOL_A_BITSET_BIT_VALUE: u16 = 3; const DUPLEX_HALF: u8 = 0x00; const DUPLEX_FULL: u8 = 0x01; const DUPLEX_UNKNOWN: u8 = 0xff; #[derive(Debug, PartialEq, Eq, Clone)] pub enum EthtoolLinkModeDuplex { Half, Full, Unknown, Other(u8), } impl From for EthtoolLinkModeDuplex { fn from(d: u8) -> Self { match d { DUPLEX_HALF => Self::Half, DUPLEX_FULL => Self::Full, DUPLEX_UNKNOWN => Self::Unknown, _ => Self::Other(d), } } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum EthtoolLinkModeAttr { Header(Vec), Autoneg(bool), Ours(Vec), Peer(Vec), Speed(u32), Duplex(EthtoolLinkModeDuplex), ControllerSubordinateCfg(u8), ControllerSubordinateState(u8), Lanes(u32), Other(DefaultNla), } impl Nla for EthtoolLinkModeAttr { fn value_len(&self) -> usize { match self { Self::Header(hdrs) => hdrs.as_slice().buffer_len(), Self::Autoneg(_) | Self::Duplex(_) | Self::ControllerSubordinateCfg(_) | Self::ControllerSubordinateState(_) => 1, Self::Ours(_) => { todo!("Does not support changing ethtool link mode yet") } Self::Peer(_) => { todo!("Does not support changing ethtool link mode yet") } Self::Speed(_) | Self::Lanes(_) => 4, Self::Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { Self::Header(_) => ETHTOOL_A_LINKMODES_HEADER | NLA_F_NESTED, Self::Autoneg(_) => ETHTOOL_A_LINKMODES_AUTONEG, Self::Ours(_) => ETHTOOL_A_LINKMODES_OURS, Self::Peer(_) => ETHTOOL_A_LINKMODES_PEER, Self::Speed(_) => ETHTOOL_A_LINKMODES_SPEED, Self::Duplex(_) => ETHTOOL_A_LINKMODES_DUPLEX, Self::ControllerSubordinateCfg(_) => ETHTOOL_A_LINKMODES_SUBORDINATE_CFG, Self::ControllerSubordinateState(_) => ETHTOOL_A_LINKMODES_SUBORDINATE_STATE, Self::Lanes(_) => ETHTOOL_A_LINKMODES_LANES, Self::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { Self::Header(ref nlas) => nlas.as_slice().emit(buffer), Self::Other(ref attr) => attr.emit(buffer), _ => todo!("Does not support changing ethtool link mode yet"), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for EthtoolLinkModeAttr { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { ETHTOOL_A_LINKMODES_HEADER => { let mut nlas = Vec::new(); let error_msg = "failed to parse link_mode header attributes"; for nla in NlasIterator::new(payload) { let nla = &nla.context(error_msg)?; let parsed = EthtoolHeader::parse(nla).context(error_msg)?; nlas.push(parsed); } Self::Header(nlas) } ETHTOOL_A_LINKMODES_AUTONEG => Self::Autoneg( parse_u8(payload).context("Invalid ETHTOOL_A_LINKMODES_AUTONEG value")? == 1, ), ETHTOOL_A_LINKMODES_OURS => Self::Ours(parse_bitset_bits_nlas(payload)?), ETHTOOL_A_LINKMODES_PEER => Self::Peer(parse_bitset_bits_nlas(payload)?), ETHTOOL_A_LINKMODES_SPEED => { Self::Speed(parse_u32(payload).context("Invalid ETHTOOL_A_LINKMODES_SPEED value")?) } ETHTOOL_A_LINKMODES_DUPLEX => Self::Duplex( parse_u8(payload) .context("Invalid ETHTOOL_A_LINKMODES_DUPLEX value")? .into(), ), ETHTOOL_A_LINKMODES_SUBORDINATE_CFG => Self::ControllerSubordinateCfg( parse_u8(payload).context("Invalid ETHTOOL_A_LINKMODES_SUBORDINATE_CFG value")?, ), ETHTOOL_A_LINKMODES_SUBORDINATE_STATE => Self::ControllerSubordinateState( parse_u8(payload).context("Invalid ETHTOOL_A_LINKMODES_SUBORDINATE_STATE value")?, ), ETHTOOL_A_LINKMODES_LANES => { Self::Lanes(parse_u32(payload).context("Invalid ETHTOOL_A_LINKMODES_LANES value")?) } _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), }) } } fn parse_bitset_bits_nlas(raw: &[u8]) -> Result, DecodeError> { let error_msg = "failed to parse mode bit sets"; for nla in NlasIterator::new(raw) { let nla = &nla.context(error_msg)?; if nla.kind() == ETHTOOL_A_BITSET_BITS { return parse_bitset_bits_nla(nla.value()); } } Err("No ETHTOOL_A_BITSET_BITS NLA found".into()) } fn parse_bitset_bits_nla(raw: &[u8]) -> Result, DecodeError> { let mut modes = Vec::new(); let error_msg = "Failed to parse ETHTOOL_A_BITSET_BITS attributes"; for bit_nla in NlasIterator::new(raw) { let bit_nla = &bit_nla.context(error_msg)?; match bit_nla.kind() { ETHTOOL_A_BITSET_BITS_BIT => { let error_msg = "Failed to parse ETHTOOL_A_BITSET_BITS_BIT attributes"; let nlas = NlasIterator::new(bit_nla.value()); for nla in nlas { let nla = &nla.context(error_msg)?; let payload = nla.value(); match nla.kind() { ETHTOOL_A_BITSET_BIT_INDEX | ETHTOOL_A_BITSET_BIT_VALUE => { // ignored } ETHTOOL_A_BITSET_BIT_NAME => { modes.push( parse_string(payload) .context("Invald ETHTOOL_A_BITSET_BIT_NAME value")?, ); } _ => { warn!( "Unknown ETHTOOL_A_BITSET_BITS_BIT {} {:?}", nla.kind(), nla.value(), ); } } } } _ => { warn!( "Unknown ETHTOOL_A_BITSET_BITS kind {}, {:?}", bit_nla.kind(), bit_nla.value() ); } }; } Ok(modes) } pub(crate) fn parse_link_mode_nlas(buffer: &[u8]) -> Result, DecodeError> { let mut nlas = Vec::new(); for nla in NlasIterator::new(buffer) { let error_msg = format!( "Failed to parse ethtool link_mode message attribute {:?}", nla ); let nla = &nla.context(error_msg.clone())?; let parsed = EthtoolLinkModeAttr::parse(nla).context(error_msg)?; nlas.push(EthtoolAttr::LinkMode(parsed)); } Ok(nlas) } ================================================ FILE: ethtool/src/link_mode/get.rs ================================================ // SPDX-License-Identifier: MIT use futures::TryStream; use netlink_packet_generic::GenlMessage; use crate::{ethtool_execute, EthtoolError, EthtoolHandle, EthtoolMessage}; pub struct EthtoolLinkModeGetRequest { handle: EthtoolHandle, iface_name: Option, } impl EthtoolLinkModeGetRequest { pub(crate) fn new(handle: EthtoolHandle, iface_name: Option<&str>) -> Self { EthtoolLinkModeGetRequest { handle, iface_name: iface_name.map(|i| i.to_string()), } } pub async fn execute( self, ) -> impl TryStream, Error = EthtoolError> { let EthtoolLinkModeGetRequest { mut handle, iface_name, } = self; let ethtool_msg = EthtoolMessage::new_link_mode_get(iface_name.as_deref()); ethtool_execute(&mut handle, iface_name.is_none(), ethtool_msg).await } } ================================================ FILE: ethtool/src/link_mode/handle.rs ================================================ // SPDX-License-Identifier: MIT use crate::{EthtoolHandle, EthtoolLinkModeGetRequest}; pub struct EthtoolLinkModeHandle(EthtoolHandle); impl EthtoolLinkModeHandle { pub fn new(handle: EthtoolHandle) -> Self { EthtoolLinkModeHandle(handle) } /// Retrieve the ethtool link_modes(duplex, link speed and etc) of a interface pub fn get(&mut self, iface_name: Option<&str>) -> EthtoolLinkModeGetRequest { EthtoolLinkModeGetRequest::new(self.0.clone(), iface_name) } } ================================================ FILE: ethtool/src/link_mode/mod.rs ================================================ // SPDX-License-Identifier: MIT mod attr; mod get; mod handle; pub(crate) use attr::parse_link_mode_nlas; pub use attr::{EthtoolLinkModeAttr, EthtoolLinkModeDuplex}; pub use get::EthtoolLinkModeGetRequest; pub use handle::EthtoolLinkModeHandle; ================================================ FILE: ethtool/src/macros.rs ================================================ // SPDX-License-Identifier: MIT #[macro_export] macro_rules! try_ethtool { ($msg: expr) => {{ use netlink_packet_core::{NetlinkMessage, NetlinkPayload}; use $crate::EthtoolError; match $msg { Ok(msg) => { let (header, payload) = msg.into_parts(); match payload { NetlinkPayload::InnerMessage(msg) => msg, NetlinkPayload::Error(err) => return Err(EthtoolError::NetlinkError(err)), _ => { return Err(EthtoolError::UnexpectedMessage(NetlinkMessage::new( header, payload, ))) } } } Err(e) => return Err(EthtoolError::Bug(format!("BUG: decode error {:?}", e))), } }}; } ================================================ FILE: ethtool/src/message.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_core::DecodeError; use netlink_packet_generic::{GenlFamily, GenlHeader}; use netlink_packet_utils::{nla::Nla, Emitable, ParseableParametrized}; use crate::{ coalesce::{parse_coalesce_nlas, EthtoolCoalesceAttr}, feature::{parse_feature_nlas, EthtoolFeatureAttr}, link_mode::{parse_link_mode_nlas, EthtoolLinkModeAttr}, pause::{parse_pause_nlas, EthtoolPauseAttr}, ring::{parse_ring_nlas, EthtoolRingAttr}, EthtoolHeader, }; const ETHTOOL_MSG_PAUSE_GET: u8 = 21; const ETHTOOL_MSG_PAUSE_GET_REPLY: u8 = 22; const ETHTOOL_MSG_FEATURES_GET: u8 = 11; const ETHTOOL_MSG_FEATURES_GET_REPLY: u8 = 11; const ETHTOOL_MSG_LINKMODES_GET: u8 = 4; const ETHTOOL_MSG_LINKMODES_GET_REPLY: u8 = 4; const ETHTOOL_MSG_RINGS_GET: u8 = 15; const ETHTOOL_MSG_RINGS_GET_REPLY: u8 = 16; const ETHTOOL_MSG_COALESCE_GET: u8 = 19; const ETHTOOL_MSG_COALESCE_GET_REPLY: u8 = 20; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum EthtoolCmd { PauseGet, PauseGetReply, FeatureGet, FeatureGetReply, LinkModeGet, LinkModeGetReply, RingGet, RingGetReply, CoalesceGet, CoalesceGetReply, } impl From for u8 { fn from(cmd: EthtoolCmd) -> Self { match cmd { EthtoolCmd::PauseGet => ETHTOOL_MSG_PAUSE_GET, EthtoolCmd::PauseGetReply => ETHTOOL_MSG_PAUSE_GET_REPLY, EthtoolCmd::FeatureGet => ETHTOOL_MSG_FEATURES_GET, EthtoolCmd::FeatureGetReply => ETHTOOL_MSG_FEATURES_GET_REPLY, EthtoolCmd::LinkModeGet => ETHTOOL_MSG_LINKMODES_GET, EthtoolCmd::LinkModeGetReply => ETHTOOL_MSG_LINKMODES_GET_REPLY, EthtoolCmd::RingGet => ETHTOOL_MSG_RINGS_GET, EthtoolCmd::RingGetReply => ETHTOOL_MSG_RINGS_GET_REPLY, EthtoolCmd::CoalesceGet => ETHTOOL_MSG_COALESCE_GET, EthtoolCmd::CoalesceGetReply => ETHTOOL_MSG_COALESCE_GET_REPLY, } } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum EthtoolAttr { Pause(EthtoolPauseAttr), Feature(EthtoolFeatureAttr), LinkMode(EthtoolLinkModeAttr), Ring(EthtoolRingAttr), Coalesce(EthtoolCoalesceAttr), } impl Nla for EthtoolAttr { fn value_len(&self) -> usize { match self { Self::Pause(attr) => attr.value_len(), Self::Feature(attr) => attr.value_len(), Self::LinkMode(attr) => attr.value_len(), Self::Ring(attr) => attr.value_len(), Self::Coalesce(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { Self::Pause(attr) => attr.kind(), Self::Feature(attr) => attr.kind(), Self::LinkMode(attr) => attr.kind(), Self::Ring(attr) => attr.kind(), Self::Coalesce(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { Self::Pause(attr) => attr.emit_value(buffer), Self::Feature(attr) => attr.emit_value(buffer), Self::LinkMode(attr) => attr.emit_value(buffer), Self::Ring(attr) => attr.emit_value(buffer), Self::Coalesce(attr) => attr.emit_value(buffer), } } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct EthtoolMessage { pub cmd: EthtoolCmd, pub nlas: Vec, } impl GenlFamily for EthtoolMessage { fn family_name() -> &'static str { "ethtool" } fn version(&self) -> u8 { 1 } fn command(&self) -> u8 { self.cmd.into() } } impl EthtoolMessage { pub fn new_pause_get(iface_name: Option<&str>) -> Self { let nlas = match iface_name { Some(s) => vec![EthtoolAttr::Pause(EthtoolPauseAttr::Header(vec![ EthtoolHeader::DevName(s.to_string()), ]))], None => vec![EthtoolAttr::Pause(EthtoolPauseAttr::Header(vec![]))], }; EthtoolMessage { cmd: EthtoolCmd::PauseGet, nlas, } } pub fn new_feature_get(iface_name: Option<&str>) -> Self { let nlas = match iface_name { Some(s) => vec![EthtoolAttr::Feature(EthtoolFeatureAttr::Header(vec![ EthtoolHeader::DevName(s.to_string()), ]))], None => vec![EthtoolAttr::Feature(EthtoolFeatureAttr::Header(vec![]))], }; EthtoolMessage { cmd: EthtoolCmd::FeatureGet, nlas, } } pub fn new_link_mode_get(iface_name: Option<&str>) -> Self { let nlas = match iface_name { Some(s) => vec![EthtoolAttr::LinkMode(EthtoolLinkModeAttr::Header(vec![ EthtoolHeader::DevName(s.to_string()), ]))], None => vec![EthtoolAttr::LinkMode(EthtoolLinkModeAttr::Header(vec![]))], }; EthtoolMessage { cmd: EthtoolCmd::LinkModeGet, nlas, } } pub fn new_ring_get(iface_name: Option<&str>) -> Self { let nlas = match iface_name { Some(s) => vec![EthtoolAttr::Ring(EthtoolRingAttr::Header(vec![ EthtoolHeader::DevName(s.to_string()), ]))], None => vec![EthtoolAttr::Ring(EthtoolRingAttr::Header(vec![]))], }; EthtoolMessage { cmd: EthtoolCmd::RingGet, nlas, } } pub fn new_coalesce_get(iface_name: Option<&str>) -> Self { let nlas = match iface_name { Some(s) => vec![EthtoolAttr::Coalesce(EthtoolCoalesceAttr::Header(vec![ EthtoolHeader::DevName(s.to_string()), ]))], None => vec![EthtoolAttr::Coalesce(EthtoolCoalesceAttr::Header(vec![]))], }; EthtoolMessage { cmd: EthtoolCmd::CoalesceGet, nlas, } } } impl Emitable for EthtoolMessage { fn buffer_len(&self) -> usize { self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.nlas.as_slice().emit(buffer) } } impl ParseableParametrized<[u8], GenlHeader> for EthtoolMessage { fn parse_with_param(buffer: &[u8], header: GenlHeader) -> Result { Ok(match header.cmd { ETHTOOL_MSG_PAUSE_GET_REPLY => Self { cmd: EthtoolCmd::PauseGetReply, nlas: parse_pause_nlas(buffer)?, }, ETHTOOL_MSG_FEATURES_GET_REPLY => Self { cmd: EthtoolCmd::FeatureGetReply, nlas: parse_feature_nlas(buffer)?, }, ETHTOOL_MSG_LINKMODES_GET_REPLY => Self { cmd: EthtoolCmd::LinkModeGetReply, nlas: parse_link_mode_nlas(buffer)?, }, ETHTOOL_MSG_RINGS_GET_REPLY => Self { cmd: EthtoolCmd::RingGetReply, nlas: parse_ring_nlas(buffer)?, }, ETHTOOL_MSG_COALESCE_GET_REPLY => Self { cmd: EthtoolCmd::CoalesceGetReply, nlas: parse_coalesce_nlas(buffer)?, }, cmd => { return Err(DecodeError::from(format!( "Unsupported ethtool reply command: {}", cmd ))) } }) } } ================================================ FILE: ethtool/src/pause/attr.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{DefaultNla, Nla, NlaBuffer, NlasIterator, NLA_F_NESTED}, parsers::{parse_u64, parse_u8}, DecodeError, Emitable, Parseable, }; use crate::{EthtoolAttr, EthtoolHeader}; const ETHTOOL_A_PAUSE_HEADER: u16 = 1; const ETHTOOL_A_PAUSE_AUTONEG: u16 = 2; const ETHTOOL_A_PAUSE_RX: u16 = 3; const ETHTOOL_A_PAUSE_TX: u16 = 4; const ETHTOOL_A_PAUSE_STATS: u16 = 5; const ETHTOOL_A_PAUSE_STAT_TX_FRAMES: u16 = 2; const ETHTOOL_A_PAUSE_STAT_RX_FRAMES: u16 = 3; #[derive(Debug, PartialEq, Eq, Clone)] pub enum EthtoolPauseStatAttr { Rx(u64), Tx(u64), Other(DefaultNla), } impl Nla for EthtoolPauseStatAttr { fn value_len(&self) -> usize { match self { Self::Rx(_) | Self::Tx(_) => 8, Self::Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { Self::Rx(_) => ETHTOOL_A_PAUSE_STAT_RX_FRAMES, Self::Tx(_) => ETHTOOL_A_PAUSE_STAT_RX_FRAMES, Self::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { Self::Rx(value) | Self::Tx(value) => NativeEndian::write_u64(buffer, *value), Self::Other(ref attr) => attr.emit_value(buffer), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for EthtoolPauseStatAttr { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { ETHTOOL_A_PAUSE_STAT_TX_FRAMES => Self::Tx( parse_u64(payload).context("invalid ETHTOOL_A_PAUSE_STAT_TX_FRAMES value")?, ), ETHTOOL_A_PAUSE_STAT_RX_FRAMES => Self::Rx( parse_u64(payload).context("invalid ETHTOOL_A_PAUSE_STAT_RX_FRAMES value")?, ), _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), }) } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum EthtoolPauseAttr { Header(Vec), AutoNeg(bool), Rx(bool), Tx(bool), Stats(Vec), Other(DefaultNla), } impl Nla for EthtoolPauseAttr { fn value_len(&self) -> usize { match self { Self::Header(hdrs) => hdrs.as_slice().buffer_len(), Self::AutoNeg(_) | Self::Rx(_) | Self::Tx(_) => 1, Self::Stats(ref nlas) => nlas.as_slice().buffer_len(), Self::Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { Self::Header(_) => ETHTOOL_A_PAUSE_HEADER | NLA_F_NESTED, Self::AutoNeg(_) => ETHTOOL_A_PAUSE_AUTONEG, Self::Rx(_) => ETHTOOL_A_PAUSE_RX, Self::Tx(_) => ETHTOOL_A_PAUSE_TX, Self::Stats(_) => ETHTOOL_A_PAUSE_STATS | NLA_F_NESTED, Self::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { Self::Header(ref nlas) => nlas.as_slice().emit(buffer), Self::AutoNeg(value) | Self::Rx(value) | Self::Tx(value) => buffer[0] = *value as u8, Self::Stats(ref nlas) => nlas.as_slice().emit(buffer), Self::Other(ref attr) => attr.emit(buffer), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for EthtoolPauseAttr { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { ETHTOOL_A_PAUSE_HEADER => { let mut nlas = Vec::new(); let error_msg = "failed to parse pause header attributes"; for nla in NlasIterator::new(payload) { let nla = &nla.context(error_msg)?; let parsed = EthtoolHeader::parse(nla).context(error_msg)?; nlas.push(parsed); } Self::Header(nlas) } ETHTOOL_A_PAUSE_AUTONEG => Self::AutoNeg( parse_u8(payload).context("invalid ETHTOOL_A_PAUSE_AUTONEG value")? == 1, ), ETHTOOL_A_PAUSE_RX => { Self::Rx(parse_u8(payload).context("invalid ETHTOOL_A_PAUSE_RX value")? == 1) } ETHTOOL_A_PAUSE_TX => { Self::Tx(parse_u8(payload).context("invalid ETHTOOL_A_PAUSE_TX value")? == 1) } ETHTOOL_A_PAUSE_STATS => { let mut nlas = Vec::new(); let error_msg = "failed to parse pause stats attributes"; for nla in NlasIterator::new(payload) { let nla = &nla.context(error_msg)?; let parsed = EthtoolPauseStatAttr::parse(nla).context(error_msg)?; nlas.push(parsed); } Self::Stats(nlas) } _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), }) } } pub(crate) fn parse_pause_nlas(buffer: &[u8]) -> Result, DecodeError> { let mut nlas = Vec::new(); for nla in NlasIterator::new(buffer) { let error_msg = format!("Failed to parse ethtool pause message attribute {:?}", nla); let nla = &nla.context(error_msg.clone())?; let parsed = EthtoolPauseAttr::parse(nla).context(error_msg)?; nlas.push(EthtoolAttr::Pause(parsed)); } Ok(nlas) } ================================================ FILE: ethtool/src/pause/get.rs ================================================ // SPDX-License-Identifier: MIT use futures::TryStream; use netlink_packet_generic::GenlMessage; use crate::{ethtool_execute, EthtoolError, EthtoolHandle, EthtoolMessage}; pub struct EthtoolPauseGetRequest { handle: EthtoolHandle, iface_name: Option, } impl EthtoolPauseGetRequest { pub(crate) fn new(handle: EthtoolHandle, iface_name: Option<&str>) -> Self { EthtoolPauseGetRequest { handle, iface_name: iface_name.map(|i| i.to_string()), } } pub async fn execute( self, ) -> impl TryStream, Error = EthtoolError> { let EthtoolPauseGetRequest { mut handle, iface_name, } = self; let ethtool_msg = EthtoolMessage::new_pause_get(iface_name.as_deref()); ethtool_execute(&mut handle, iface_name.is_none(), ethtool_msg).await } } ================================================ FILE: ethtool/src/pause/handle.rs ================================================ // SPDX-License-Identifier: MIT use crate::{EthtoolHandle, EthtoolPauseGetRequest}; pub struct EthtoolPauseHandle(EthtoolHandle); impl EthtoolPauseHandle { pub fn new(handle: EthtoolHandle) -> Self { EthtoolPauseHandle(handle) } /// Retrieve the pause setting of a interface (equivalent to `ethtool -a eth1`) pub fn get(&mut self, iface_name: Option<&str>) -> EthtoolPauseGetRequest { EthtoolPauseGetRequest::new(self.0.clone(), iface_name) } } ================================================ FILE: ethtool/src/pause/mod.rs ================================================ // SPDX-License-Identifier: MIT mod attr; mod get; mod handle; pub(crate) use attr::parse_pause_nlas; pub use attr::{EthtoolPauseAttr, EthtoolPauseStatAttr}; pub use get::EthtoolPauseGetRequest; pub use handle::EthtoolPauseHandle; ================================================ FILE: ethtool/src/ring/attr.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{DefaultNla, Nla, NlaBuffer, NlasIterator, NLA_F_NESTED}, parsers::parse_u32, DecodeError, Emitable, Parseable, }; use crate::{EthtoolAttr, EthtoolHeader}; const ETHTOOL_A_RINGS_HEADER: u16 = 1; const ETHTOOL_A_RINGS_RX_MAX: u16 = 2; const ETHTOOL_A_RINGS_RX_MINI_MAX: u16 = 3; const ETHTOOL_A_RINGS_RX_JUMBO_MAX: u16 = 4; const ETHTOOL_A_RINGS_TX_MAX: u16 = 5; const ETHTOOL_A_RINGS_RX: u16 = 6; const ETHTOOL_A_RINGS_RX_MINI: u16 = 7; const ETHTOOL_A_RINGS_RX_JUMBO: u16 = 8; const ETHTOOL_A_RINGS_TX: u16 = 9; #[derive(Debug, PartialEq, Eq, Clone)] pub enum EthtoolRingAttr { Header(Vec), RxMax(u32), RxMiniMax(u32), RxJumboMax(u32), TxMax(u32), Rx(u32), RxMini(u32), RxJumbo(u32), Tx(u32), Other(DefaultNla), } impl Nla for EthtoolRingAttr { fn value_len(&self) -> usize { match self { Self::Header(hdrs) => hdrs.as_slice().buffer_len(), Self::RxMax(_) | Self::RxMiniMax(_) | Self::RxJumboMax(_) | Self::TxMax(_) | Self::Rx(_) | Self::RxMini(_) | Self::RxJumbo(_) | Self::Tx(_) => 4, Self::Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { Self::Header(_) => ETHTOOL_A_RINGS_HEADER | NLA_F_NESTED, Self::RxMax(_) => ETHTOOL_A_RINGS_RX_MAX, Self::RxMiniMax(_) => ETHTOOL_A_RINGS_RX_MINI_MAX, Self::RxJumboMax(_) => ETHTOOL_A_RINGS_RX_JUMBO_MAX, Self::TxMax(_) => ETHTOOL_A_RINGS_TX_MAX, Self::Rx(_) => ETHTOOL_A_RINGS_RX, Self::RxMini(_) => ETHTOOL_A_RINGS_RX_MINI, Self::RxJumbo(_) => ETHTOOL_A_RINGS_RX_JUMBO, Self::Tx(_) => ETHTOOL_A_RINGS_TX, Self::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { Self::Header(ref nlas) => nlas.as_slice().emit(buffer), Self::RxMax(d) | Self::RxMiniMax(d) | Self::RxJumboMax(d) | Self::TxMax(d) | Self::Rx(d) | Self::RxMini(d) | Self::RxJumbo(d) | Self::Tx(d) => NativeEndian::write_u32(buffer, *d), Self::Other(ref attr) => attr.emit(buffer), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for EthtoolRingAttr { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { ETHTOOL_A_RINGS_HEADER => { let mut nlas = Vec::new(); let error_msg = "failed to parse ring header attributes"; for nla in NlasIterator::new(payload) { let nla = &nla.context(error_msg)?; let parsed = EthtoolHeader::parse(nla).context(error_msg)?; nlas.push(parsed); } Self::Header(nlas) } ETHTOOL_A_RINGS_RX_MAX => { Self::RxMax(parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_RX_MAX value")?) } ETHTOOL_A_RINGS_RX_MINI_MAX => Self::RxMiniMax( parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_RX_MINI_MAX value")?, ), ETHTOOL_A_RINGS_RX_JUMBO_MAX => Self::RxJumboMax( parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_RX_JUMBO_MAX value")?, ), ETHTOOL_A_RINGS_TX_MAX => { Self::TxMax(parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_TX_MAX value")?) } ETHTOOL_A_RINGS_RX => { Self::Rx(parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_RX value")?) } ETHTOOL_A_RINGS_RX_MINI => { Self::RxMini(parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_RX_MINI value")?) } ETHTOOL_A_RINGS_RX_JUMBO => { Self::RxJumbo(parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_RX_JUMBO value")?) } ETHTOOL_A_RINGS_TX => { Self::Tx(parse_u32(payload).context("Invalid ETHTOOL_A_RINGS_TX value")?) } _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), }) } } pub(crate) fn parse_ring_nlas(buffer: &[u8]) -> Result, DecodeError> { let mut nlas = Vec::new(); for nla in NlasIterator::new(buffer) { let error_msg = format!("Failed to parse ethtool ring message attribute {:?}", nla); let nla = &nla.context(error_msg.clone())?; let parsed = EthtoolRingAttr::parse(nla).context(error_msg)?; nlas.push(EthtoolAttr::Ring(parsed)); } Ok(nlas) } ================================================ FILE: ethtool/src/ring/get.rs ================================================ // SPDX-License-Identifier: MIT use futures::TryStream; use netlink_packet_generic::GenlMessage; use crate::{ethtool_execute, EthtoolError, EthtoolHandle, EthtoolMessage}; pub struct EthtoolRingGetRequest { handle: EthtoolHandle, iface_name: Option, } impl EthtoolRingGetRequest { pub(crate) fn new(handle: EthtoolHandle, iface_name: Option<&str>) -> Self { EthtoolRingGetRequest { handle, iface_name: iface_name.map(|i| i.to_string()), } } pub async fn execute( self, ) -> impl TryStream, Error = EthtoolError> { let EthtoolRingGetRequest { mut handle, iface_name, } = self; let ethtool_msg = EthtoolMessage::new_ring_get(iface_name.as_deref()); ethtool_execute(&mut handle, iface_name.is_none(), ethtool_msg).await } } ================================================ FILE: ethtool/src/ring/handle.rs ================================================ // SPDX-License-Identifier: MIT use crate::{EthtoolHandle, EthtoolRingGetRequest}; pub struct EthtoolRingHandle(EthtoolHandle); impl EthtoolRingHandle { pub fn new(handle: EthtoolHandle) -> Self { EthtoolRingHandle(handle) } /// Retrieve the ethtool rings of a interface (equivalent to `ethtool -g eth1`) pub fn get(&mut self, iface_name: Option<&str>) -> EthtoolRingGetRequest { EthtoolRingGetRequest::new(self.0.clone(), iface_name) } } ================================================ FILE: ethtool/src/ring/mod.rs ================================================ // SPDX-License-Identifier: MIT mod attr; mod get; mod handle; pub(crate) use attr::parse_ring_nlas; pub use attr::EthtoolRingAttr; pub use get::EthtoolRingGetRequest; pub use handle::EthtoolRingHandle; ================================================ FILE: ethtool/tests/dump_link_modes.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; #[test] // CI container normally have a veth for external communication which support link modes of ethtool. fn test_dump_link_modes() { let rt = tokio::runtime::Builder::new_current_thread() .enable_io() .build() .unwrap(); rt.block_on(dump_link_modes()); } async fn dump_link_modes() { let (connection, mut handle, _) = ethtool::new_connection().unwrap(); tokio::spawn(connection); let mut link_modes_handle = handle.link_mode().get(None).execute().await; let mut msgs = Vec::new(); while let Some(msg) = link_modes_handle.try_next().await.unwrap() { msgs.push(msg); } assert!(!msgs.is_empty()); let ethtool_msg = &msgs[0].payload; println!("ethtool_msg {:?}", ðtool_msg); assert!(ethtool_msg.cmd == ethtool::EthtoolCmd::LinkModeGetReply); assert!(ethtool_msg.nlas.len() > 1); } ================================================ FILE: ethtool/tests/get_features_lo.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; #[test] fn test_get_features_of_loopback() { let rt = tokio::runtime::Builder::new_current_thread() .enable_io() .build() .unwrap(); rt.block_on(get_feature(Some("lo"))); } async fn get_feature(iface_name: Option<&str>) { let (connection, mut handle, _) = ethtool::new_connection().unwrap(); tokio::spawn(connection); let mut feature_handle = handle.feature().get(iface_name).execute().await; let mut msgs = Vec::new(); while let Some(msg) = feature_handle.try_next().await.unwrap() { msgs.push(msg); } assert!(msgs.len() == 1); let ethtool_msg = &msgs[0].payload; assert!(ethtool_msg.cmd == ethtool::EthtoolCmd::FeatureGetReply); assert!(ethtool_msg.nlas.len() > 1); assert!( ethtool_msg.nlas[0] == ethtool::EthtoolAttr::Feature(ethtool::EthtoolFeatureAttr::Header(vec![ ethtool::EthtoolHeader::DevIndex(1), ethtool::EthtoolHeader::DevName("lo".into()) ])) ); } ================================================ FILE: genetlink/Cargo.toml ================================================ [package] name = "genetlink" version = "0.2.3" authors = ["Leo "] edition = "2018" homepage = "https://github.com/little-dude/netlink" repository = "https://github.com/little-dude/netlink" keywords = ["netlink", "linux"] license = "MIT" readme = "../README.md" description = "communicate with generic netlink" [features] default = ["tokio_socket"] tokio_socket = ["netlink-proto/tokio_socket", "tokio"] smol_socket = ["netlink-proto/smol_socket","async-std"] [dependencies] futures = "0.3.16" netlink-proto = { default-features = false, version = "0.10" , path = "../netlink-proto" } netlink-packet-generic = { version = "0.3.1", path = "../netlink-packet-generic" } netlink-packet-utils = { version = "0.5.1", path = "../netlink-packet-utils" } netlink-packet-core = { version = "0.4.2", path = "../netlink-packet-core" } tokio = { version = "1.9.0", features = ["rt"], optional = true } async-std = { version = "1.9.0", optional = true } thiserror = "1.0.26" [dev-dependencies] anyhow = "1.0.42" tokio = { version = "1.9.0", features = ["rt", "rt-multi-thread", "macros"] } [[example]] name = "list_genetlink_family" required-features = ["tokio_socket"] [[example]] name = "dump_family_policy" required-features = ["tokio_socket"] ================================================ FILE: genetlink/examples/dump_family_policy.rs ================================================ // SPDX-License-Identifier: MIT use std::env::args; use anyhow::{bail, Error}; use futures::StreamExt; use genetlink::new_connection; use netlink_packet_core::{ NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST, }; use netlink_packet_generic::{ ctrl::{nlas::GenlCtrlAttrs, GenlCtrl, GenlCtrlCmd}, GenlMessage, }; #[tokio::main] async fn main() -> Result<(), Error> { let argv: Vec<_> = args().collect(); if argv.len() < 2 { eprintln!("Usage: dump_family_policy "); bail!("Required arguments not given"); } let nlmsg = NetlinkMessage { header: NetlinkHeader { flags: NLM_F_REQUEST | NLM_F_DUMP, ..Default::default() }, payload: GenlMessage::from_payload(GenlCtrl { cmd: GenlCtrlCmd::GetPolicy, nlas: vec![GenlCtrlAttrs::FamilyName(argv[1].to_owned())], }) .into(), }; let (conn, mut handle, _) = new_connection()?; tokio::spawn(conn); let mut responses = handle.request(nlmsg).await?; while let Some(result) = responses.next().await { let resp = result?; match resp.payload { NetlinkPayload::InnerMessage(genlmsg) => { if genlmsg.payload.cmd == GenlCtrlCmd::GetPolicy { println!("<<< {:?}", genlmsg); } } NetlinkPayload::Error(err) => { eprintln!("Received a netlink error message: {:?}", err); bail!(err); } _ => {} } } Ok(()) } ================================================ FILE: genetlink/examples/list_genetlink_family.rs ================================================ // SPDX-License-Identifier: MIT //! Example of listing generic families based on `netlink_proto` //! //! This example's functionality is same as the identical name example in `netlink_packet_generic`. //! But this example shows you the usage of this crate to run generic netlink protocol asynchronously. use anyhow::{bail, Error}; use futures::StreamExt; use genetlink::new_connection; use netlink_packet_core::{ NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST, }; use netlink_packet_generic::{ ctrl::{nlas::GenlCtrlAttrs, GenlCtrl, GenlCtrlCmd}, GenlMessage, }; #[tokio::main] async fn main() -> Result<(), Error> { let nlmsg = NetlinkMessage { header: NetlinkHeader { flags: NLM_F_REQUEST | NLM_F_DUMP, ..Default::default() }, payload: GenlMessage::from_payload(GenlCtrl { cmd: GenlCtrlCmd::GetFamily, nlas: vec![], }) .into(), }; let (conn, mut handle, _) = new_connection()?; tokio::spawn(conn); let mut responses = handle.request(nlmsg).await?; while let Some(result) = responses.next().await { let resp = result?; match resp.payload { NetlinkPayload::InnerMessage(genlmsg) => { if genlmsg.payload.cmd == GenlCtrlCmd::NewFamily { print_entry(genlmsg.payload.nlas); } } NetlinkPayload::Error(err) => { eprintln!("Received a netlink error message: {:?}", err); bail!(err); } _ => {} } } Ok(()) } fn print_entry(entry: Vec) { let family_id = entry .iter() .find_map(|nla| { if let GenlCtrlAttrs::FamilyId(id) = nla { Some(*id) } else { None } }) .expect("Cannot find FamilyId attribute"); let family_name = entry .iter() .find_map(|nla| { if let GenlCtrlAttrs::FamilyName(name) = nla { Some(name.as_str()) } else { None } }) .expect("Cannot find FamilyName attribute"); let version = entry .iter() .find_map(|nla| { if let GenlCtrlAttrs::Version(ver) = nla { Some(*ver) } else { None } }) .expect("Cannot find Version attribute"); let hdrsize = entry .iter() .find_map(|nla| { if let GenlCtrlAttrs::HdrSize(hdr) = nla { Some(*hdr) } else { None } }) .expect("Cannot find HdrSize attribute"); if hdrsize == 0 { println!("0x{:04x} {} [Version {}]", family_id, family_name, version); } else { println!( "0x{:04x} {} [Version {}] [Header {} bytes]", family_id, family_name, version, hdrsize ); } } ================================================ FILE: genetlink/src/connection.rs ================================================ // SPDX-License-Identifier: MIT use crate::{message::RawGenlMessage, GenetlinkHandle}; use futures::channel::mpsc::UnboundedReceiver; use netlink_packet_core::NetlinkMessage; use netlink_proto::{ self, sys::{protocols::NETLINK_GENERIC, AsyncSocket, SocketAddr}, Connection, }; use std::io; /// Construct a generic netlink connection /// /// The function would return a tuple containing three objects. /// - an async netlink connection /// - a connection handle to interact with the connection /// - a receiver of the unsolicited messages /// /// The connection object is also a event loop which implements [`std::future::Future`]. /// In most cases, users spawn it on an async runtime and use the handle to send /// messages. For detailed documentation, please refer to [`netlink_proto::new_connection`]. /// /// The [`GenetlinkHandle`] can send and receive any type of generic netlink message. /// And it can automatic resolve the generic family id before sending. #[cfg(feature = "tokio_socket")] #[allow(clippy::type_complexity)] pub fn new_connection() -> io::Result<( Connection, GenetlinkHandle, UnboundedReceiver<(NetlinkMessage, SocketAddr)>, )> { new_connection_with_socket() } /// Variant of [`new_connection`] that allows specifying a socket type to use for async handling #[allow(clippy::type_complexity)] pub fn new_connection_with_socket() -> io::Result<( Connection, GenetlinkHandle, UnboundedReceiver<(NetlinkMessage, SocketAddr)>, )> where S: AsyncSocket, { let (conn, handle, messages) = netlink_proto::new_connection_with_socket(NETLINK_GENERIC)?; Ok((conn, GenetlinkHandle::new(handle), messages)) } ================================================ FILE: genetlink/src/error.rs ================================================ // SPDX-License-Identifier: MIT use crate::message::RawGenlMessage; /// Error type of genetlink #[derive(Debug, Error)] pub enum GenetlinkError { #[error("Failed to send netlink request")] ProtocolError(#[from] netlink_proto::Error), #[error("Failed to decode generic packet")] DecodeError(#[from] netlink_packet_utils::DecodeError), #[error("Netlink error message: {0}")] NetlinkError(std::io::Error), #[error("Cannot find specified netlink attribute: {0}")] AttributeNotFound(String), #[error("Desire netlink message type not received")] NoMessageReceived, } // Since `netlink_packet_core::error::ErrorMessage` doesn't impl `Error` trait, // it need to convert to `std::io::Error` here impl From for GenetlinkError { fn from(err_msg: netlink_packet_core::error::ErrorMessage) -> Self { Self::NetlinkError(err_msg.to_io()) } } ================================================ FILE: genetlink/src/handle.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ error::GenetlinkError, message::{map_from_rawgenlmsg, map_to_rawgenlmsg, RawGenlMessage}, resolver::Resolver, }; use futures::{lock::Mutex, Stream, StreamExt}; use netlink_packet_core::{DecodeError, NetlinkMessage, NetlinkPayload}; use netlink_packet_generic::{GenlFamily, GenlHeader, GenlMessage}; use netlink_packet_utils::{Emitable, ParseableParametrized}; use netlink_proto::{sys::SocketAddr, ConnectionHandle}; use std::{fmt::Debug, sync::Arc}; /// The generic netlink connection handle /// /// The handle is used to send messages to the connection. It also resolves /// the family id automatically before sending messages. /// /// # Family id resolving /// There is a resolver with cache inside each connection. When you send generic /// netlink message, the handle resolves and fills the family id into the message. /// /// Since the resolver is created in [`new_connection()`](crate::new_connection), /// the cache state wouldn't share between different connections. /// /// P.s. The cloned handles use the same connection with the original handle. So, /// they share the same cache state. /// /// # Detailed process of sending generic messages /// 1. Check if the message's family id is resolved. If yes, jump to step 6. /// 2. Query the family id using the builtin resolver. /// 3. If the id is in the cache, returning the id in the cache and skip step 4. /// 4. The resolver sends `CTRL_CMD_GETFAMILY` request to get the id and records it in the cache. /// 5. fill the family id using [`GenlMessage::set_resolved_family_id()`]. /// 6. Serialize the payload to [`RawGenlMessage`]. /// 7. Send it through the connection. /// - The family id filled into `message_type` field in [`NetlinkMessage::finalize()`]. /// 8. In the response stream, deserialize the payload back to [`GenlMessage`]. #[derive(Clone, Debug)] pub struct GenetlinkHandle { handle: ConnectionHandle, resolver: Arc>, } impl GenetlinkHandle { pub(crate) fn new(handle: ConnectionHandle) -> Self { Self { handle, resolver: Arc::new(Mutex::new(Resolver::new())), } } /// Resolve the family id of the given [`GenlFamily`]. pub async fn resolve_family_id(&self) -> Result where F: GenlFamily, { self.resolver .lock() .await .query_family_id(self, F::family_name()) .await } /// Clear the resolver's fanily id cache pub async fn clear_family_id_cache(&self) { self.resolver.lock().await.clear_cache(); } /// Send the generic netlink message and get the response stream /// /// The function resolves the family id before sending the request. If the /// resolving process is failed, the function would return an error. pub async fn request( &mut self, mut message: NetlinkMessage>, ) -> Result< impl Stream>, DecodeError>>, GenetlinkError, > where F: GenlFamily + Emitable + ParseableParametrized<[u8], GenlHeader> + Debug, { self.resolve_message_family_id(&mut message).await?; self.send_request(message) } /// Send the request without resolving family id /// /// This function is identical to [`request()`](Self::request) but it doesn't /// resolve the family id for you. pub fn send_request( &mut self, message: NetlinkMessage>, ) -> Result< impl Stream>, DecodeError>>, GenetlinkError, > where F: GenlFamily + Emitable + ParseableParametrized<[u8], GenlHeader> + Debug, { let raw_msg = map_to_rawgenlmsg(message); let stream = self.handle.request(raw_msg, SocketAddr::new(0, 0))?; Ok(stream.map(map_from_rawgenlmsg)) } /// Send the generic netlink message without returning the response stream pub async fn notify( &mut self, mut message: NetlinkMessage>, ) -> Result<(), GenetlinkError> where F: GenlFamily + Emitable + ParseableParametrized<[u8], GenlHeader> + Debug, { self.resolve_message_family_id(&mut message).await?; self.send_notify(message) } /// Send the notify without resolving family id pub fn send_notify( &mut self, message: NetlinkMessage>, ) -> Result<(), GenetlinkError> where F: GenlFamily + Emitable + ParseableParametrized<[u8], GenlHeader> + Debug, { let raw_msg = map_to_rawgenlmsg(message); self.handle.notify(raw_msg, SocketAddr::new(0, 0))?; Ok(()) } async fn resolve_message_family_id( &mut self, message: &mut NetlinkMessage>, ) -> Result<(), GenetlinkError> where F: GenlFamily + Debug, { if let NetlinkPayload::InnerMessage(genlmsg) = &mut message.payload { if genlmsg.family_id() == 0 { // The family id is not resolved // Resolve it before send it let id = self.resolve_family_id::().await?; genlmsg.set_resolved_family_id(id); } } Ok(()) } } ================================================ FILE: genetlink/src/lib.rs ================================================ // SPDX-License-Identifier: MIT #[macro_use] extern crate thiserror; mod connection; mod error; mod handle; pub mod message; mod resolver; #[cfg(feature = "tokio_socket")] pub use connection::new_connection; pub use connection::new_connection_with_socket; pub use error::GenetlinkError; pub use handle::GenetlinkHandle; ================================================ FILE: genetlink/src/message.rs ================================================ // SPDX-License-Identifier: MIT //! Raw generic netlink payload message //! //! # Design //! Since we use generic type to represent different generic family's message type, //! and it is not easy to create a underlying [`netlink_proto::new_connection()`] //! with trait object to multiplex different generic netlink family's message. //! //! Therefore, I decided to serialize the generic type payload into bytes before //! sending to the underlying connection. The [`RawGenlMessage`] is meant for this. //! //! This special message doesn't use generic type and its payload is `Vec`. //! Therefore, its type is easier to use. //! //! Another advantage is that it can let users know when the generic netlink payload //! fails to decode instead of just dropping the messages. //! (`netlink_proto` would drop messages if they fails to decode.) //! I think this can help developers debug their deserializing implementation. use netlink_packet_core::{ DecodeError, NetlinkDeserializable, NetlinkHeader, NetlinkMessage, NetlinkPayload, NetlinkSerializable, }; use netlink_packet_generic::{GenlBuffer, GenlFamily, GenlHeader, GenlMessage}; use netlink_packet_utils::{Emitable, Parseable, ParseableParametrized}; use std::fmt::Debug; /// Message type to hold serialized generic netlink payload /// /// **Note** This message type is not intend to be used by normal users, unless /// you need to use the `UnboundedReceiver<(NetlinkMessage, SocketAddr)>` /// return by [`new_connection()`](crate::new_connection) #[derive(Clone, Debug, PartialEq, Eq)] pub struct RawGenlMessage { pub header: GenlHeader, pub payload: Vec, pub family_id: u16, } impl RawGenlMessage { /// Construct the message pub fn new(header: GenlHeader, payload: Vec, family_id: u16) -> Self { Self { header, payload, family_id, } } /// Consume this message and return its header and payload pub fn into_parts(self) -> (GenlHeader, Vec) { (self.header, self.payload) } /// Serialize the generic netlink payload into raw bytes pub fn from_genlmsg(genlmsg: GenlMessage) -> Self where F: GenlFamily + Emitable + Debug, { let mut payload_buf = vec![0u8; genlmsg.payload.buffer_len()]; genlmsg.payload.emit(&mut payload_buf); Self { header: genlmsg.header, payload: payload_buf, family_id: genlmsg.family_id(), } } /// Try to deserialize the generic netlink payload from raw bytes pub fn parse_into_genlmsg(&self) -> Result, DecodeError> where F: GenlFamily + ParseableParametrized<[u8], GenlHeader> + Debug, { let inner = F::parse_with_param(&self.payload, self.header)?; Ok(GenlMessage::new(self.header, inner, self.family_id)) } } impl Emitable for RawGenlMessage { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.payload.len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); let buffer = &mut buffer[self.header.buffer_len()..]; buffer.copy_from_slice(&self.payload); } } impl<'a, T> ParseableParametrized, u16> for RawGenlMessage where T: AsRef<[u8]> + ?Sized, { fn parse_with_param(buf: &GenlBuffer<&'a T>, message_type: u16) -> Result { let header = GenlHeader::parse(buf)?; let payload_buf = buf.payload(); Ok(RawGenlMessage::new( header, payload_buf.to_vec(), message_type, )) } } impl NetlinkSerializable for RawGenlMessage { fn message_type(&self) -> u16 { self.family_id } fn buffer_len(&self) -> usize { ::buffer_len(self) } fn serialize(&self, buffer: &mut [u8]) { self.emit(buffer) } } impl NetlinkDeserializable for RawGenlMessage { type Error = DecodeError; fn deserialize(header: &NetlinkHeader, payload: &[u8]) -> Result { let buffer = GenlBuffer::new_checked(payload)?; RawGenlMessage::parse_with_param(&buffer, header.message_type) } } impl From for NetlinkPayload { fn from(message: RawGenlMessage) -> Self { NetlinkPayload::InnerMessage(message) } } /// Helper function to map the [`NetlinkPayload`] types in [`NetlinkMessage`] /// and serialize the generic netlink payload into raw bytes. pub fn map_to_rawgenlmsg( message: NetlinkMessage>, ) -> NetlinkMessage where F: GenlFamily + Emitable + Debug, { let raw_payload = match message.payload { NetlinkPayload::InnerMessage(genlmsg) => { NetlinkPayload::InnerMessage(RawGenlMessage::from_genlmsg(genlmsg)) } NetlinkPayload::Done => NetlinkPayload::Done, NetlinkPayload::Error(i) => NetlinkPayload::Error(i), NetlinkPayload::Ack(i) => NetlinkPayload::Ack(i), NetlinkPayload::Noop => NetlinkPayload::Noop, NetlinkPayload::Overrun(i) => NetlinkPayload::Overrun(i), }; NetlinkMessage::new(message.header, raw_payload) } /// Helper function to map the [`NetlinkPayload`] types in [`NetlinkMessage`] /// and try to deserialize the generic netlink payload from raw bytes. pub fn map_from_rawgenlmsg( raw_msg: NetlinkMessage, ) -> Result>, DecodeError> where F: GenlFamily + ParseableParametrized<[u8], GenlHeader> + Debug, { let payload = match raw_msg.payload { NetlinkPayload::InnerMessage(raw_genlmsg) => { NetlinkPayload::InnerMessage(raw_genlmsg.parse_into_genlmsg()?) } NetlinkPayload::Done => NetlinkPayload::Done, NetlinkPayload::Error(i) => NetlinkPayload::Error(i), NetlinkPayload::Ack(i) => NetlinkPayload::Ack(i), NetlinkPayload::Noop => NetlinkPayload::Noop, NetlinkPayload::Overrun(i) => NetlinkPayload::Overrun(i), }; Ok(NetlinkMessage::new(raw_msg.header, payload)) } ================================================ FILE: genetlink/src/resolver.rs ================================================ // SPDX-License-Identifier: MIT use crate::{error::GenetlinkError, GenetlinkHandle}; use futures::{future::Either, StreamExt}; use netlink_packet_core::{NetlinkMessage, NetlinkPayload, NLM_F_REQUEST}; use netlink_packet_generic::{ ctrl::{nlas::GenlCtrlAttrs, GenlCtrl, GenlCtrlCmd}, GenlMessage, }; use std::{collections::HashMap, future::Future}; #[derive(Clone, Debug, Default)] pub struct Resolver { cache: HashMap<&'static str, u16>, } impl Resolver { pub fn new() -> Self { Self { cache: HashMap::new(), } } pub fn get_cache_by_name(&self, family_name: &str) -> Option { self.cache.get(family_name).copied() } pub fn query_family_id( &mut self, handle: &GenetlinkHandle, family_name: &'static str, ) -> impl Future> + '_ { if let Some(id) = self.get_cache_by_name(family_name) { Either::Left(futures::future::ready(Ok(id))) } else { let mut handle = handle.clone(); Either::Right(async move { let mut genlmsg: GenlMessage = GenlMessage::from_payload(GenlCtrl { cmd: GenlCtrlCmd::GetFamily, nlas: vec![GenlCtrlAttrs::FamilyName(family_name.to_owned())], }); genlmsg.finalize(); // We don't have to set family id here, since nlctrl has static family id (0x10) let mut nlmsg = NetlinkMessage::from(genlmsg); nlmsg.header.flags = NLM_F_REQUEST; nlmsg.finalize(); let mut res = handle.send_request(nlmsg)?; while let Some(result) = res.next().await { let rx_packet = result?; match rx_packet.payload { NetlinkPayload::InnerMessage(genlmsg) => { let family_id = genlmsg .payload .nlas .iter() .find_map(|nla| { if let GenlCtrlAttrs::FamilyId(id) = nla { Some(*id) } else { None } }) .ok_or_else(|| { GenetlinkError::AttributeNotFound( "CTRL_ATTR_FAMILY_ID".to_owned(), ) })?; self.cache.insert(family_name, family_id); return Ok(family_id); } NetlinkPayload::Error(e) => return Err(e.into()), _ => (), } } Err(GenetlinkError::NoMessageReceived) }) } } pub fn clear_cache(&mut self) { self.cache.clear(); } } #[cfg(test)] mod test { use super::*; use crate::new_connection; use std::io::ErrorKind; #[tokio::test] async fn test_resolver_nlctrl() { let (conn, handle, _) = new_connection().unwrap(); tokio::spawn(conn); let mut resolver = Resolver::new(); // nlctrl should always be 0x10 let nlctrl_fid = resolver.query_family_id(&handle, "nlctrl").await.unwrap(); assert_eq!(nlctrl_fid, 0x10); } const TEST_FAMILIES: &[&str] = &[ "devlink", "ethtool", "acpi_event", "tcp_metrics", "TASKSTATS", "nl80211", ]; #[tokio::test] async fn test_resolver_cache() { let (conn, handle, _) = new_connection().unwrap(); tokio::spawn(conn); let mut resolver = Resolver::new(); // Test if family id cached for name in TEST_FAMILIES.iter().copied() { let id = resolver .query_family_id(&handle, name) .await .or_else(|e| { if let GenetlinkError::NetlinkError(io_err) = &e { if io_err.kind() == ErrorKind::NotFound { // Ignore non exist entries Ok(0) } else { Err(e) } } else { Err(e) } }) .unwrap(); if id == 0 { eprintln!( "Generic family \"{}\" not exist or not loaded in this environment. Ignored.", name ); continue; } let cache = resolver.get_cache_by_name(name).unwrap(); assert_eq!(id, cache); eprintln!("{:?}", (name, cache)); } } } ================================================ FILE: mptcp-pm/Cargo.toml ================================================ [package] name = "mptcp-pm" version = "0.1.1" authors = ["Gris Ge "] license = "MIT" edition = "2018" description = "Linux kernel MPTCP path manager netlink Library" keywords = ["network"] categories = ["network-programming", "os"] readme = "../README.md" [lib] name = "mptcp_pm" path = "src/lib.rs" crate-type = ["lib"] [features] default = ["tokio_socket"] tokio_socket = ["netlink-proto/tokio_socket", "tokio"] smol_socket = ["netlink-proto/smol_socket", "async-std"] [dependencies] anyhow = "1.0.44" async-std = { version = "1.9.0", optional = true} byteorder = "1.4.3" futures = "0.3.17" log = "0.4.14" thiserror = "1.0.29" tokio = { version = "1.0.1", features = ["rt"], optional = true} genetlink = { default-features = false, version = "0.2.1", path = "../genetlink" } netlink-packet-core = { version = "0.4.2", path = "../netlink-packet-core" } netlink-packet-generic = { version = "0.3.1", path = "../netlink-packet-generic" } netlink-packet-utils = { version = "0.5.1", path = "../netlink-packet-utils" } netlink-proto = { default-features = false, version = "0.10", path = "../netlink-proto" } netlink-sys = { version = "0.8.3", path = "../netlink-sys" } [dev-dependencies] tokio = { version = "1.11.0", features = ["macros", "rt", "rt-multi-thread"] } env_logger = "0.9.0" ================================================ FILE: mptcp-pm/examples/dump_mptcp.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; fn main() { let rt = tokio::runtime::Builder::new_current_thread() .enable_io() .build() .unwrap(); rt.block_on(get_addresses()); } async fn get_addresses() { let (connection, handle, _) = mptcp_pm::new_connection().unwrap(); tokio::spawn(connection); let mut address_handle = handle.address().get().execute().await; let mut msgs = Vec::new(); while let Some(msg) = address_handle.try_next().await.unwrap() { msgs.push(msg); } assert!(!msgs.is_empty()); for msg in msgs { println!("{:?}", msg); } let mut limits_handle = handle.limits().get().execute().await; let mut msgs = Vec::new(); while let Some(msg) = limits_handle.try_next().await.unwrap() { msgs.push(msg); } assert!(!msgs.is_empty()); for msg in msgs { println!("{:?}", msg); } } ================================================ FILE: mptcp-pm/src/address/attr.rs ================================================ // SPDX-License-Identifier: MIT use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{DefaultNla, Nla, NlaBuffer}, parsers::{parse_i32, parse_ip, parse_u16, parse_u32, parse_u8}, DecodeError, Emitable, Parseable, }; const MPTCP_PM_ADDR_ATTR_FAMILY: u16 = 1; const MPTCP_PM_ADDR_ATTR_ID: u16 = 2; const MPTCP_PM_ADDR_ATTR_ADDR4: u16 = 3; const MPTCP_PM_ADDR_ATTR_ADDR6: u16 = 4; const MPTCP_PM_ADDR_ATTR_PORT: u16 = 5; const MPTCP_PM_ADDR_ATTR_FLAGS: u16 = 6; const MPTCP_PM_ADDR_ATTR_IF_IDX: u16 = 7; const MPTCP_PM_ADDR_FLAG_SIGNAL: u32 = 1 << 0; const MPTCP_PM_ADDR_FLAG_SUBFLOW: u32 = 1 << 1; const MPTCP_PM_ADDR_FLAG_BACKUP: u32 = 1 << 2; const MPTCP_PM_ADDR_FLAG_FULLMESH: u32 = 1 << 3; const MPTCP_PM_ADDR_FLAG_IMPLICIT: u32 = 1 << 4; #[derive(Debug, PartialEq, Eq, Clone)] pub enum MptcpPathManagerAddressAttrFlag { Signal, Subflow, Backup, Fullmesh, Implicit, Other(u32), } fn u32_to_vec_flags(value: u32) -> Vec { let mut ret = Vec::new(); let mut found = 0u32; if (value & MPTCP_PM_ADDR_FLAG_SIGNAL) > 0 { found += MPTCP_PM_ADDR_FLAG_SIGNAL; ret.push(MptcpPathManagerAddressAttrFlag::Signal); } if (value & MPTCP_PM_ADDR_FLAG_SUBFLOW) > 0 { found += MPTCP_PM_ADDR_FLAG_SUBFLOW; ret.push(MptcpPathManagerAddressAttrFlag::Subflow); } if (value & MPTCP_PM_ADDR_FLAG_BACKUP) > 0 { found += MPTCP_PM_ADDR_FLAG_BACKUP; ret.push(MptcpPathManagerAddressAttrFlag::Backup); } if (value & MPTCP_PM_ADDR_FLAG_FULLMESH) > 0 { found += MPTCP_PM_ADDR_FLAG_FULLMESH; ret.push(MptcpPathManagerAddressAttrFlag::Fullmesh); } if (value & MPTCP_PM_ADDR_FLAG_IMPLICIT) > 0 { found += MPTCP_PM_ADDR_FLAG_IMPLICIT; ret.push(MptcpPathManagerAddressAttrFlag::Implicit); } if (value - found) > 0 { ret.push(MptcpPathManagerAddressAttrFlag::Other(value - found)); } ret } impl From<&MptcpPathManagerAddressAttrFlag> for u32 { fn from(v: &MptcpPathManagerAddressAttrFlag) -> u32 { match v { MptcpPathManagerAddressAttrFlag::Signal => MPTCP_PM_ADDR_FLAG_SIGNAL, MptcpPathManagerAddressAttrFlag::Subflow => MPTCP_PM_ADDR_FLAG_SUBFLOW, MptcpPathManagerAddressAttrFlag::Backup => MPTCP_PM_ADDR_FLAG_BACKUP, MptcpPathManagerAddressAttrFlag::Fullmesh => MPTCP_PM_ADDR_FLAG_FULLMESH, MptcpPathManagerAddressAttrFlag::Implicit => MPTCP_PM_ADDR_FLAG_IMPLICIT, MptcpPathManagerAddressAttrFlag::Other(d) => *d, } } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum MptcpPathManagerAddressAttr { Family(u16), Id(u8), Addr4(Ipv4Addr), Addr6(Ipv6Addr), Port(u16), Flags(Vec), IfIndex(i32), Other(DefaultNla), } impl Nla for MptcpPathManagerAddressAttr { fn value_len(&self) -> usize { match self { Self::Family(_) | Self::Port(_) => 2, Self::Addr4(_) | Self::Flags(_) | Self::IfIndex(_) => 4, Self::Id(_) => 1, Self::Addr6(_) => 16, Self::Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { Self::Family(_) => MPTCP_PM_ADDR_ATTR_FAMILY, Self::Id(_) => MPTCP_PM_ADDR_ATTR_ID, Self::Addr4(_) => MPTCP_PM_ADDR_ATTR_ADDR4, Self::Addr6(_) => MPTCP_PM_ADDR_ATTR_ADDR6, Self::Port(_) => MPTCP_PM_ADDR_ATTR_PORT, Self::Flags(_) => MPTCP_PM_ADDR_ATTR_FLAGS, Self::IfIndex(_) => MPTCP_PM_ADDR_ATTR_IF_IDX, Self::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { Self::Family(d) | Self::Port(d) => NativeEndian::write_u16(buffer, *d), Self::Addr4(i) => buffer.copy_from_slice(&i.octets()), Self::Addr6(i) => buffer.copy_from_slice(&i.octets()), Self::Id(d) => buffer[0] = *d, Self::Flags(flags) => { let mut value = 0u32; for flag in flags { value += u32::from(flag); } NativeEndian::write_u32(buffer, value) } Self::IfIndex(d) => NativeEndian::write_i32(buffer, *d), Self::Other(ref attr) => attr.emit(buffer), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for MptcpPathManagerAddressAttr { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { MPTCP_PM_ADDR_ATTR_FAMILY => { let err_msg = format!("Invalid MPTCP_PM_ADDR_ATTR_FAMILY value {:?}", payload); Self::Family(parse_u16(payload).context(err_msg)?) } MPTCP_PM_ADDR_ATTR_ID => { Self::Id(parse_u8(payload).context("Invalid MPTCP_PM_ADDR_ATTR_ID value")?) } MPTCP_PM_ADDR_ATTR_ADDR4 | MPTCP_PM_ADDR_ATTR_ADDR6 => { match parse_ip(payload) .context("Invalid MPTCP_PM_ADDR_ATTR_ADDR4/MPTCP_PM_ADDR_ATTR_ADDR6 value")? { IpAddr::V4(i) => Self::Addr4(i), IpAddr::V6(i) => Self::Addr6(i), } } MPTCP_PM_ADDR_ATTR_PORT => { Self::Port(parse_u16(payload).context("Invalid MPTCP_PM_ADDR_ATTR_PORT value")?) } MPTCP_PM_ADDR_ATTR_FLAGS => Self::Flags(u32_to_vec_flags( parse_u32(payload).context("Invalid MPTCP_PM_ADDR_ATTR_FLAGS value")?, )), MPTCP_PM_ADDR_ATTR_IF_IDX => Self::IfIndex( parse_i32(payload).context("Invalid MPTCP_PM_ADDR_ATTR_IF_IDX value")?, ), _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), }) } } ================================================ FILE: mptcp-pm/src/address/get.rs ================================================ // SPDX-License-Identifier: MIT use futures::TryStream; use netlink_packet_generic::GenlMessage; use crate::{ mptcp_execute, MptcpPathManagerError, MptcpPathManagerHandle, MptcpPathManagerMessage, }; pub struct MptcpPathManagerAddressGetRequest { handle: MptcpPathManagerHandle, } impl MptcpPathManagerAddressGetRequest { pub(crate) fn new(handle: MptcpPathManagerHandle) -> Self { MptcpPathManagerAddressGetRequest { handle } } pub async fn execute( self, ) -> impl TryStream, Error = MptcpPathManagerError> { let MptcpPathManagerAddressGetRequest { mut handle } = self; let mptcp_msg = MptcpPathManagerMessage::new_address_get(); mptcp_execute(&mut handle, mptcp_msg).await } } ================================================ FILE: mptcp-pm/src/address/handle.rs ================================================ // SPDX-License-Identifier: MIT use crate::{MptcpPathManagerAddressGetRequest, MptcpPathManagerHandle}; pub struct MptcpPathManagerAddressHandle(MptcpPathManagerHandle); impl MptcpPathManagerAddressHandle { pub fn new(handle: MptcpPathManagerHandle) -> Self { MptcpPathManagerAddressHandle(handle) } /// Retrieve the multipath-TCP addresses /// (equivalent to `ip mptcp endpoint show`) pub fn get(&mut self) -> MptcpPathManagerAddressGetRequest { MptcpPathManagerAddressGetRequest::new(self.0.clone()) } } ================================================ FILE: mptcp-pm/src/address/mod.rs ================================================ // SPDX-License-Identifier: MIT mod attr; mod get; mod handle; pub use attr::{MptcpPathManagerAddressAttr, MptcpPathManagerAddressAttrFlag}; pub use get::MptcpPathManagerAddressGetRequest; pub use handle::MptcpPathManagerAddressHandle; ================================================ FILE: mptcp-pm/src/connection.rs ================================================ // SPDX-License-Identifier: MIT use std::io; use futures::channel::mpsc::UnboundedReceiver; use genetlink::message::RawGenlMessage; use netlink_packet_core::NetlinkMessage; use netlink_proto::Connection; use netlink_sys::{AsyncSocket, SocketAddr}; use crate::MptcpPathManagerHandle; #[cfg(feature = "tokio_socket")] #[allow(clippy::type_complexity)] pub fn new_connection() -> io::Result<( Connection, MptcpPathManagerHandle, UnboundedReceiver<(NetlinkMessage, SocketAddr)>, )> { new_connection_with_socket() } #[allow(clippy::type_complexity)] pub fn new_connection_with_socket() -> io::Result<( Connection, MptcpPathManagerHandle, UnboundedReceiver<(NetlinkMessage, SocketAddr)>, )> where S: AsyncSocket, { let (conn, handle, messages) = genetlink::new_connection_with_socket()?; Ok((conn, MptcpPathManagerHandle::new(handle), messages)) } ================================================ FILE: mptcp-pm/src/error.rs ================================================ // SPDX-License-Identifier: MIT use thiserror::Error; use netlink_packet_core::{ErrorMessage, NetlinkMessage}; use netlink_packet_generic::GenlMessage; use crate::MptcpPathManagerMessage; #[derive(Clone, Eq, PartialEq, Debug, Error)] pub enum MptcpPathManagerError { #[error("Received an unexpected message {0:?}")] UnexpectedMessage(NetlinkMessage>), #[error("Received a netlink error message {0}")] NetlinkError(ErrorMessage), #[error("A netlink request failed")] RequestFailed(String), #[error("A bug in this crate")] Bug(String), } ================================================ FILE: mptcp-pm/src/handle.rs ================================================ // SPDX-License-Identifier: MIT use futures::{future::Either, FutureExt, Stream, StreamExt, TryStream}; use genetlink::GenetlinkHandle; use netlink_packet_core::{NetlinkMessage, NLM_F_DUMP, NLM_F_REQUEST}; use netlink_packet_generic::GenlMessage; use netlink_packet_utils::DecodeError; use crate::{ try_mptcp, MptcpPathManagerAddressHandle, MptcpPathManagerCmd, MptcpPathManagerError, MptcpPathManagerLimitsHandle, MptcpPathManagerMessage, }; #[derive(Clone, Debug)] pub struct MptcpPathManagerHandle { pub handle: GenetlinkHandle, } impl MptcpPathManagerHandle { pub(crate) fn new(handle: GenetlinkHandle) -> Self { MptcpPathManagerHandle { handle } } // equivalent to `ip mptcp endpoint` command // Instead of using `endpoint`, we are aligning with kernel netlink name // `address` here. pub fn address(&self) -> MptcpPathManagerAddressHandle { MptcpPathManagerAddressHandle::new(self.clone()) } // equivalent to `ip mptcp limits` command pub fn limits(&self) -> MptcpPathManagerLimitsHandle { MptcpPathManagerLimitsHandle::new(self.clone()) } pub async fn request( &mut self, message: NetlinkMessage>, ) -> Result< impl Stream>, DecodeError>>, MptcpPathManagerError, > { self.handle.request(message).await.map_err(|e| { MptcpPathManagerError::RequestFailed(format!("BUG: Request failed with {}", e)) }) } } pub(crate) async fn mptcp_execute( handle: &mut MptcpPathManagerHandle, mptcp_msg: MptcpPathManagerMessage, ) -> impl TryStream, Error = MptcpPathManagerError> { let nl_header_flags = match mptcp_msg.cmd { MptcpPathManagerCmd::AddressGet => NLM_F_REQUEST | NLM_F_DUMP, MptcpPathManagerCmd::LimitsGet => NLM_F_REQUEST, }; let mut nl_msg = NetlinkMessage::from(GenlMessage::from_payload(mptcp_msg)); nl_msg.header.flags = nl_header_flags; match handle.request(nl_msg).await { Ok(response) => Either::Left(response.map(move |msg| Ok(try_mptcp!(msg)))), Err(e) => Either::Right( futures::future::err::, MptcpPathManagerError>(e) .into_stream(), ), } } ================================================ FILE: mptcp-pm/src/lib.rs ================================================ // SPDX-License-Identifier: MIT mod address; mod connection; mod error; mod handle; mod limits; mod macros; mod message; pub use address::{ MptcpPathManagerAddressAttr, MptcpPathManagerAddressAttrFlag, MptcpPathManagerAddressGetRequest, MptcpPathManagerAddressHandle, }; #[cfg(feature = "tokio_socket")] pub use connection::new_connection; pub use connection::new_connection_with_socket; pub use error::MptcpPathManagerError; pub use handle::MptcpPathManagerHandle; pub use limits::{ MptcpPathManagerLimitsAttr, MptcpPathManagerLimitsGetRequest, MptcpPathManagerLimitsHandle, }; pub use message::{MptcpPathManagerAttr, MptcpPathManagerCmd, MptcpPathManagerMessage}; pub(crate) use handle::mptcp_execute; ================================================ FILE: mptcp-pm/src/limits/attr.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{DefaultNla, Nla, NlaBuffer}, parsers::parse_u32, DecodeError, Emitable, Parseable, }; const MPTCP_PM_ATTR_RCV_ADD_ADDRS: u16 = 2; const MPTCP_PM_ATTR_SUBFLOWS: u16 = 3; #[derive(Debug, PartialEq, Eq, Clone)] pub enum MptcpPathManagerLimitsAttr { RcvAddAddrs(u32), Subflows(u32), Other(DefaultNla), } impl Nla for MptcpPathManagerLimitsAttr { fn value_len(&self) -> usize { match self { Self::Other(attr) => attr.value_len(), _ => 4, } } fn kind(&self) -> u16 { match self { Self::RcvAddAddrs(_) => MPTCP_PM_ATTR_RCV_ADD_ADDRS, Self::Subflows(_) => MPTCP_PM_ATTR_SUBFLOWS, Self::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { Self::RcvAddAddrs(d) | Self::Subflows(d) => NativeEndian::write_u32(buffer, *d), Self::Other(ref attr) => attr.emit(buffer), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for MptcpPathManagerLimitsAttr { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { MPTCP_PM_ATTR_RCV_ADD_ADDRS => Self::RcvAddAddrs( parse_u32(payload).context("Invalid MPTCP_PM_ATTR_RCV_ADD_ADDRS value")?, ), MPTCP_PM_ATTR_SUBFLOWS => { Self::Subflows(parse_u32(payload).context("Invalid MPTCP_PM_ATTR_SUBFLOWS value")?) } _ => Self::Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), }) } } ================================================ FILE: mptcp-pm/src/limits/get.rs ================================================ // SPDX-License-Identifier: MIT use futures::TryStream; use netlink_packet_generic::GenlMessage; use crate::{ mptcp_execute, MptcpPathManagerError, MptcpPathManagerHandle, MptcpPathManagerMessage, }; pub struct MptcpPathManagerLimitsGetRequest { handle: MptcpPathManagerHandle, } impl MptcpPathManagerLimitsGetRequest { pub(crate) fn new(handle: MptcpPathManagerHandle) -> Self { MptcpPathManagerLimitsGetRequest { handle } } pub async fn execute( self, ) -> impl TryStream, Error = MptcpPathManagerError> { let MptcpPathManagerLimitsGetRequest { mut handle } = self; let mptcp_msg = MptcpPathManagerMessage::new_limits_get(); mptcp_execute(&mut handle, mptcp_msg).await } } ================================================ FILE: mptcp-pm/src/limits/handle.rs ================================================ // SPDX-License-Identifier: MIT use crate::{MptcpPathManagerHandle, MptcpPathManagerLimitsGetRequest}; pub struct MptcpPathManagerLimitsHandle(MptcpPathManagerHandle); impl MptcpPathManagerLimitsHandle { pub fn new(handle: MptcpPathManagerHandle) -> Self { MptcpPathManagerLimitsHandle(handle) } /// Retrieve the multipath-TCP addresses /// (equivalent to `ip mptcp endpoint show`) pub fn get(&mut self) -> MptcpPathManagerLimitsGetRequest { MptcpPathManagerLimitsGetRequest::new(self.0.clone()) } } ================================================ FILE: mptcp-pm/src/limits/mod.rs ================================================ // SPDX-License-Identifier: MIT mod attr; mod get; mod handle; pub use attr::MptcpPathManagerLimitsAttr; pub use get::MptcpPathManagerLimitsGetRequest; pub use handle::MptcpPathManagerLimitsHandle; ================================================ FILE: mptcp-pm/src/macros.rs ================================================ // SPDX-License-Identifier: MIT #[macro_export] macro_rules! try_mptcp { ($msg: expr) => {{ use netlink_packet_core::{NetlinkMessage, NetlinkPayload}; use $crate::MptcpPathManagerError; match $msg { Ok(msg) => { let (header, payload) = msg.into_parts(); match payload { NetlinkPayload::InnerMessage(msg) => msg, NetlinkPayload::Error(err) => { return Err(MptcpPathManagerError::NetlinkError(err)) } _ => { return Err(MptcpPathManagerError::UnexpectedMessage( NetlinkMessage::new(header, payload), )) } } } Err(e) => { return Err(MptcpPathManagerError::Bug(format!( "BUG: decode error {:?}", e ))) } } }}; } ================================================ FILE: mptcp-pm/src/message.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use netlink_packet_core::DecodeError; use netlink_packet_generic::{GenlFamily, GenlHeader}; use netlink_packet_utils::{ nla::{DefaultNla, Nla, NlasIterator}, Emitable, Parseable, ParseableParametrized, }; use crate::{address::MptcpPathManagerAddressAttr, limits::MptcpPathManagerLimitsAttr}; const MPTCP_PM_CMD_GET_ADDR: u8 = 3; const MPTCP_PM_CMD_GET_LIMITS: u8 = 6; const MPTCP_PM_ATTR_ADDR: u16 = 1; const MPTCP_PM_ATTR_RCV_ADD_ADDRS: u16 = 2; const MPTCP_PM_ATTR_SUBFLOWS: u16 = 3; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum MptcpPathManagerCmd { AddressGet, LimitsGet, } impl From for u8 { fn from(cmd: MptcpPathManagerCmd) -> Self { match cmd { MptcpPathManagerCmd::AddressGet => MPTCP_PM_CMD_GET_ADDR, MptcpPathManagerCmd::LimitsGet => MPTCP_PM_CMD_GET_LIMITS, } } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum MptcpPathManagerAttr { Address(MptcpPathManagerAddressAttr), Limits(MptcpPathManagerLimitsAttr), Other(DefaultNla), } impl Nla for MptcpPathManagerAttr { fn value_len(&self) -> usize { match self { Self::Address(attr) => attr.value_len(), Self::Limits(attr) => attr.value_len(), Self::Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { Self::Address(attr) => attr.kind(), Self::Limits(attr) => attr.kind(), Self::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { Self::Address(attr) => attr.emit_value(buffer), Self::Limits(attr) => attr.emit_value(buffer), Self::Other(ref attr) => attr.emit(buffer), } } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct MptcpPathManagerMessage { pub cmd: MptcpPathManagerCmd, pub nlas: Vec, } impl GenlFamily for MptcpPathManagerMessage { fn family_name() -> &'static str { "mptcp_pm" } fn version(&self) -> u8 { 1 } fn command(&self) -> u8 { self.cmd.into() } } impl MptcpPathManagerMessage { pub fn new_address_get() -> Self { MptcpPathManagerMessage { cmd: MptcpPathManagerCmd::AddressGet, nlas: vec![], } } pub fn new_limits_get() -> Self { MptcpPathManagerMessage { cmd: MptcpPathManagerCmd::LimitsGet, nlas: vec![], } } } impl Emitable for MptcpPathManagerMessage { fn buffer_len(&self) -> usize { self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.nlas.as_slice().emit(buffer) } } fn parse_nlas(buffer: &[u8]) -> Result, DecodeError> { let mut nlas = Vec::new(); for nla in NlasIterator::new(buffer) { let error_msg = format!("Failed to parse mptcp address message attribute {:?}", nla); let nla = &nla.context(error_msg)?; match nla.kind() { MPTCP_PM_ATTR_ADDR => { for addr_nla in NlasIterator::new(nla.value()) { let error_msg = format!("Failed to parse MPTCP_PM_ATTR_ADDR {:?}", addr_nla); let addr_nla = &addr_nla.context(error_msg)?; nlas.push(MptcpPathManagerAttr::Address( MptcpPathManagerAddressAttr::parse(addr_nla) .context("Failed to parse MPTCP_PM_ATTR_ADDR")?, )) } } MPTCP_PM_ATTR_RCV_ADD_ADDRS => nlas.push(MptcpPathManagerAttr::Limits( MptcpPathManagerLimitsAttr::parse(nla) .context("Failed to parse MPTCP_PM_ATTR_RCV_ADD_ADDRS")?, )), MPTCP_PM_ATTR_SUBFLOWS => nlas.push(MptcpPathManagerAttr::Limits( MptcpPathManagerLimitsAttr::parse(nla) .context("Failed to parse MPTCP_PM_ATTR_RCV_ADD_ADDRS")?, )), _ => nlas.push(MptcpPathManagerAttr::Other( DefaultNla::parse(nla).context("invalid NLA (unknown kind)")?, )), } } Ok(nlas) } impl ParseableParametrized<[u8], GenlHeader> for MptcpPathManagerMessage { fn parse_with_param(buffer: &[u8], header: GenlHeader) -> Result { Ok(match header.cmd { MPTCP_PM_CMD_GET_ADDR => Self { cmd: MptcpPathManagerCmd::AddressGet, nlas: parse_nlas(buffer)?, }, MPTCP_PM_CMD_GET_LIMITS => Self { cmd: MptcpPathManagerCmd::LimitsGet, nlas: parse_nlas(buffer)?, }, cmd => { return Err(DecodeError::from(format!( "Unsupported mptcp reply command: {}", cmd ))) } }) } } ================================================ FILE: mptcp-pm/tests/dump_mptcp.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use std::process::Command; use mptcp_pm::{MptcpPathManagerAttr, MptcpPathManagerLimitsAttr}; #[test] fn test_mptcp_empty_addresses_and_limits() { Command::new("sysctl") .arg("-w") .arg("net.mptcp.enabled=1") .spawn() .unwrap(); // OK to fail as Github CI has no ip-mptcp Command::new("ip") .arg("mptcp") .arg("endpoint") .arg("flush") .spawn() .ok(); // OK to fail as Github CI has no ip-mptcp Command::new("ip") .arg("mptcp") .arg("limits") .arg("set") .arg("subflows") .arg("0") .arg("add_addr_accepted") .arg("0") .spawn() .ok(); let rt = tokio::runtime::Builder::new_current_thread() .enable_io() .build() .unwrap(); rt.block_on(assert_empty_addresses_and_limits()); } async fn assert_empty_addresses_and_limits() { let (connection, handle, _) = mptcp_pm::new_connection().unwrap(); tokio::spawn(connection); let mut address_handle = handle.address().get().execute().await; let mut msgs = Vec::new(); while let Some(msg) = address_handle.try_next().await.unwrap() { msgs.push(msg); } assert!(msgs.is_empty()); let mut limits_handle = handle.limits().get().execute().await; let mut msgs = Vec::new(); while let Some(msg) = limits_handle.try_next().await.unwrap() { msgs.push(msg); } assert_eq!(msgs.len(), 1); let mptcp_nlas = &msgs[0].payload.nlas; assert_eq!( mptcp_nlas[0], MptcpPathManagerAttr::Limits(MptcpPathManagerLimitsAttr::RcvAddAddrs(0)) ); assert_eq!( mptcp_nlas[1], MptcpPathManagerAttr::Limits(MptcpPathManagerLimitsAttr::Subflows(0)) ); } ================================================ FILE: netlink-packet-audit/Cargo.toml ================================================ [package] authors = ["Corentin Henry "] name = "netlink-packet-audit" version = "0.4.2" edition = "2018" homepage = "https://github.com/little-dude/netlink" keywords = ["netlink", "linux"] license = "MIT" readme = "../README.md" repository = "https://github.com/little-dude/netlink" description = "netlink packet types" [dependencies] anyhow = "1.0.31" bytes = "1.0" byteorder = "1.3.2" log = "0.4.8" netlink-packet-core = { version = "0.4.2", path = "../netlink-packet-core" } netlink-packet-utils = { version = "0.5.1", path = "../netlink-packet-utils" } netlink-proto = { default-features = false, version = "0.10", path = "../netlink-proto" } [dev-dependencies] lazy_static = "1.4.0" ================================================ FILE: netlink-packet-audit/fuzz/.gitignore ================================================ target corpus artifacts ================================================ FILE: netlink-packet-audit/fuzz/Cargo.toml ================================================ [package] name = "netlink-packet-audit-fuzz" version = "0.0.1" authors = ["Automatically generated"] publish = false edition = "2018" [package.metadata] cargo-fuzz = true [dependencies] netlink-packet-audit = { version = "0.4.1", path = "../../netlink-packet-audit" } netlink-packet-core = { version = "0.4.2", path = "../../netlink-packet-core" } libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } [[bin]] name = "netlink-audit" path = "fuzz_targets/netlink.rs" ================================================ FILE: netlink-packet-audit/fuzz/fuzz_targets/netlink.rs ================================================ // SPDX-License-Identifier: MIT #![no_main] use libfuzzer_sys::fuzz_target; use netlink_packet_audit::{AuditMessage, NetlinkMessage}; fuzz_target!(|data: &[u8]| { let _ = NetlinkMessage::::deserialize(data); }); ================================================ FILE: netlink-packet-audit/src/buffer.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ constants::*, rules::{RuleBuffer, RuleMessage}, traits::{Parseable, ParseableParametrized}, AuditMessage, DecodeError, StatusMessage, StatusMessageBuffer, }; use anyhow::Context; pub struct AuditBuffer { buffer: T, } impl> AuditBuffer { pub fn new(buffer: T) -> AuditBuffer { AuditBuffer { buffer } } pub fn length(&self) -> usize { self.buffer.as_ref().len() } pub fn new_checked(buffer: T) -> Result, DecodeError> { Ok(Self::new(buffer)) } } impl<'a, T: AsRef<[u8]> + ?Sized> AuditBuffer<&'a T> { pub fn inner(&self) -> &'a [u8] { self.buffer.as_ref() } } impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> AuditBuffer<&'a mut T> { pub fn inner_mut(&mut self) -> &mut [u8] { self.buffer.as_mut() } } impl<'a, T: AsRef<[u8]> + ?Sized> ParseableParametrized, u16> for AuditMessage { fn parse_with_param(buf: &AuditBuffer<&'a T>, message_type: u16) -> Result { use self::AuditMessage::*; let message = match message_type { AUDIT_GET if buf.length() == 0 => GetStatus(None), AUDIT_GET => { let err = "failed to parse AUDIT_GET message"; let buf = StatusMessageBuffer::new(buf.inner()); GetStatus(Some(StatusMessage::parse(&buf).context(err)?)) } AUDIT_SET => { let err = "failed to parse AUDIT_SET message"; let buf = StatusMessageBuffer::new(buf.inner()); SetStatus(StatusMessage::parse(&buf).context(err)?) } AUDIT_ADD_RULE => { let err = "failed to parse AUDIT_ADD_RULE message"; let buf = RuleBuffer::new_checked(buf.inner()).context(err)?; AddRule(RuleMessage::parse(&buf).context(err)?) } AUDIT_DEL_RULE => { let err = "failed to parse AUDIT_DEL_RULE message"; let buf = RuleBuffer::new_checked(buf.inner()).context(err)?; DelRule(RuleMessage::parse(&buf).context(err)?) } AUDIT_LIST_RULES if buf.length() == 0 => ListRules(None), AUDIT_LIST_RULES => { let err = "failed to parse AUDIT_LIST_RULES message"; let buf = RuleBuffer::new_checked(buf.inner()).context(err)?; ListRules(Some(RuleMessage::parse(&buf).context(err)?)) } i if (AUDIT_EVENT_MESSAGE_MIN..AUDIT_EVENT_MESSAGE_MAX).contains(&i) => { let data = String::from_utf8(buf.inner().to_vec()) .context("failed to parse audit event data as a valid string")?; Event((i, data)) } i => { let data = String::from_utf8(buf.inner().to_vec()) .context("failed to parse audit event data as a valid string")?; Other((i, data)) } }; Ok(message) } } ================================================ FILE: netlink-packet-audit/src/codec.rs ================================================ // SPDX-License-Identifier: MIT use std::{fmt::Debug, io}; use bytes::BytesMut; use netlink_packet_core::{ NetlinkBuffer, NetlinkDeserializable, NetlinkMessage, NetlinkSerializable, }; pub(crate) use netlink_proto::{NetlinkCodec, NetlinkMessageCodec}; /// audit specific implementation of [`NetlinkMessageCodec`] due to the /// protocol violations in messages generated by kernal audit. /// /// Among the known bugs in kernel audit messages: /// - `nlmsg_len` sometimes contains the padding too (it shouldn't) /// - `nlmsg_len` sometimes doesn't contain the header (it really should) /// /// See also: /// - https://blog.des.no/2020/08/netlink-auditing-and-counting-bytes/ /// - https://github.com/torvalds/linux/blob/b5013d084e03e82ceeab4db8ae8ceeaebe76b0eb/kernel/audit.c#L2386 /// - https://github.com/mozilla/libaudit-go/issues/24 /// - https://github.com/linux-audit/audit-userspace/issues/78 pub struct NetlinkAuditCodec { // we don't need an instance of this, just the type _private: (), } impl NetlinkMessageCodec for NetlinkAuditCodec { fn decode(src: &mut BytesMut) -> io::Result>> where T: NetlinkDeserializable + Debug, { debug!("NetlinkAuditCodec: decoding next message"); loop { // If there's nothing to read, return Ok(None) if src.is_empty() { trace!("buffer is empty"); return Ok(None); } // This is a bit hacky because we don't want to keep `src` // borrowed, since we need to mutate it later. let src_len = src.len(); let len = match NetlinkBuffer::new_checked(src.as_mut()) { Ok(mut buf) => { if (src_len as isize - buf.length() as isize) <= 16 { // The audit messages are sometimes truncated, // because the length specified in the header, // does not take the header itself into // account. To workaround this, we tweak the // length. We've noticed two occurences of // truncated packets: // // - the length of the header is not included (see also: // https://github.com/mozilla/libaudit-go/issues/24) // - some rule message have some padding for alignment (see // https://github.com/linux-audit/audit-userspace/issues/78) which is not // taken into account in the buffer length. // // How do we know that's the right length? Due to an implementation detail and to // the fact that netlink is a datagram protocol. // // - our implementation of Stream always calls the codec with at most 1 message in // the buffer, so we know the extra bytes do not belong to another message. // - because netlink is a datagram protocol, we receive entire messages, so we know // that if those extra bytes do not belong to another message, they belong to // this one. warn!("found what looks like a truncated audit packet"); // also write correct length to buffer so parsing does not fail: warn!( "setting packet length to {} instead of {}", src_len, buf.length() ); buf.set_length(src_len as u32); src_len } else { buf.length() as usize } } Err(e) => { // We either received a truncated packet, or the // packet if malformed (invalid length field). In // both case, we can't decode the datagram, and we // cannot find the start of the next one (if // any). The only solution is to clear the buffer // and potentially lose some datagrams. error!( "failed to decode datagram, clearing buffer: {:?}: {:#x?}.", e, src.as_ref() ); src.clear(); return Ok(None); } }; let bytes = src.split_to(len); let parsed = NetlinkMessage::::deserialize(&bytes); match parsed { Ok(packet) => { trace!("<<< {:?}", packet); return Ok(Some(packet)); } Err(e) => { error!("failed to decode packet {:#x?}: {}", &bytes, e); // continue looping, there may be more datagrams in the buffer } } } } fn encode(msg: NetlinkMessage, buf: &mut BytesMut) -> io::Result<()> where T: Debug + NetlinkSerializable, { NetlinkCodec::encode(msg, buf) } } ================================================ FILE: netlink-packet-audit/src/constants.rs ================================================ // SPDX-License-Identifier: MIT pub use netlink_packet_core::constants::*; // ========================================== // 1000 - 1099 are for commanding the audit system // ========================================== /// Get status pub const AUDIT_GET: u16 = 1000; /// Set status (enable/disable/auditd) pub const AUDIT_SET: u16 = 1001; /// List syscall rules -- deprecated pub const AUDIT_LIST: u16 = 1002; /// Add syscall rule -- deprecated pub const AUDIT_ADD: u16 = 1003; /// Delete syscall rule -- deprecated pub const AUDIT_DEL: u16 = 1004; /// Message from userspace -- deprecated pub const AUDIT_USER: u16 = 1005; /// Define the login id and information pub const AUDIT_LOGIN: u16 = 1006; /// Insert file/dir watch entry pub const AUDIT_WATCH_INS: u16 = 1007; /// Remove file/dir watch entry pub const AUDIT_WATCH_REM: u16 = 1008; /// List all file/dir watches pub const AUDIT_WATCH_LIST: u16 = 1009; /// Get info about sender of signal to auditd pub const AUDIT_SIGNAL_INFO: u16 = 1010; /// Add syscall filtering rule pub const AUDIT_ADD_RULE: u16 = 1011; /// Delete syscall filtering rule pub const AUDIT_DEL_RULE: u16 = 1012; /// List syscall filtering rules pub const AUDIT_LIST_RULES: u16 = 1013; /// Trim junk from watched tree pub const AUDIT_TRIM: u16 = 1014; /// Append to watched tree pub const AUDIT_MAKE_EQUIV: u16 = 1015; /// Get TTY auditing status pub const AUDIT_TTY_GET: u16 = 1016; /// Set TTY auditing status pub const AUDIT_TTY_SET: u16 = 1017; /// Turn an audit feature on or off pub const AUDIT_SET_FEATURE: u16 = 1018; /// Get which features are enabled pub const AUDIT_GET_FEATURE: u16 = 1019; // ========================================== // 1100 - 1199 user space trusted application messages // ========================================== /// Userspace messages mostly uninteresting to kernel pub const AUDIT_FIRST_USER_MSG: u16 = 1100; /// We filter this differently pub const AUDIT_USER_AVC: u16 = 1107; /// Non-ICANON TTY input meaning pub const AUDIT_USER_TTY: u16 = 1124; pub const AUDIT_LAST_USER_MSG: u16 = 1199; /// More user space messages; pub const AUDIT_FIRST_USER_MSG2: u16 = 2100; pub const AUDIT_LAST_USER_MSG2: u16 = 2999; // ========================================== // 1200 - 1299 messages internal to the audit daemon // ========================================== /// Daemon startup record pub const AUDIT_DAEMON_START: u16 = 1200; /// Daemon normal stop record pub const AUDIT_DAEMON_END: u16 = 1201; /// Daemon error stop record pub const AUDIT_DAEMON_ABORT: u16 = 1202; /// Daemon config change pub const AUDIT_DAEMON_CONFIG: u16 = 1203; // ========================================== // 1300 - 1399 audit event messages // ========================================== pub const AUDIT_EVENT_MESSAGE_MIN: u16 = 1300; pub const AUDIT_EVENT_MESSAGE_MAX: u16 = 1399; /// Syscall event pub const AUDIT_SYSCALL: u16 = 1300; /// Filename path information pub const AUDIT_PATH: u16 = 1302; /// IPC record pub const AUDIT_IPC: u16 = 1303; /// sys_socketcall arguments pub const AUDIT_SOCKETCALL: u16 = 1304; /// Audit system configuration change pub const AUDIT_CONFIG_CHANGE: u16 = 1305; /// sockaddr copied as syscall arg pub const AUDIT_SOCKADDR: u16 = 1306; /// Current working directory pub const AUDIT_CWD: u16 = 1307; /// execve arguments pub const AUDIT_EXECVE: u16 = 1309; /// IPC new permissions record type pub const AUDIT_IPC_SET_PERM: u16 = 1311; /// POSIX MQ open record type pub const AUDIT_MQ_OPEN: u16 = 1312; /// POSIX MQ send/receive record type pub const AUDIT_MQ_SENDRECV: u16 = 1313; /// POSIX MQ notify record type pub const AUDIT_MQ_NOTIFY: u16 = 1314; /// POSIX MQ get/set attribute record type pub const AUDIT_MQ_GETSETATTR: u16 = 1315; /// For use by 3rd party modules pub const AUDIT_KERNEL_OTHER: u16 = 1316; /// audit record for pipe/socketpair pub const AUDIT_FD_PAIR: u16 = 1317; /// ptrace target pub const AUDIT_OBJ_PID: u16 = 1318; /// Input on an administrative TTY pub const AUDIT_TTY: u16 = 1319; /// End of multi-record event pub const AUDIT_EOE: u16 = 1320; /// Information about fcaps increasing perms pub const AUDIT_BPRM_FCAPS: u16 = 1321; /// Record showing argument to sys_capset pub const AUDIT_CAPSET: u16 = 1322; /// Record showing descriptor and flags in mmap pub const AUDIT_MMAP: u16 = 1323; /// Packets traversing netfilter chains pub const AUDIT_NETFILTER_PKT: u16 = 1324; /// Netfilter chain modifications pub const AUDIT_NETFILTER_CFG: u16 = 1325; /// Secure Computing event pub const AUDIT_SECCOMP: u16 = 1326; /// Proctitle emit event pub const AUDIT_PROCTITLE: u16 = 1327; /// audit log listing feature changes pub const AUDIT_FEATURE_CHANGE: u16 = 1328; /// Replace auditd if this packet unanswerd pub const AUDIT_REPLACE: u16 = 1329; /// Kernel Module events pub const AUDIT_KERN_MODULE: u16 = 1330; /// Fanotify access decision pub const AUDIT_FANOTIFY: u16 = 1331; // ========================================== // 1400 - 1499 SE Linux use // ========================================== /// SE Linux avc denial or grant pub const AUDIT_AVC: u16 = 1400; /// Internal SE Linux Errors pub const AUDIT_SELINUX_ERR: u16 = 1401; /// dentry, vfsmount pair from avc pub const AUDIT_AVC_PATH: u16 = 1402; /// Policy file load pub const AUDIT_MAC_POLICY_LOAD: u16 = 1403; /// Changed enforcing,permissive,off pub const AUDIT_MAC_STATUS: u16 = 1404; /// Changes to booleans pub const AUDIT_MAC_CONFIG_CHANGE: u16 = 1405; /// NetLabel: allow unlabeled traffic pub const AUDIT_MAC_UNLBL_ALLOW: u16 = 1406; /// NetLabel: add CIPSOv4 DOI entry pub const AUDIT_MAC_CIPSOV4_ADD: u16 = 1407; /// NetLabel: del CIPSOv4 DOI entry pub const AUDIT_MAC_CIPSOV4_DEL: u16 = 1408; /// NetLabel: add LSM domain mapping pub const AUDIT_MAC_MAP_ADD: u16 = 1409; /// NetLabel: del LSM domain mapping pub const AUDIT_MAC_MAP_DEL: u16 = 1410; /// Not used pub const AUDIT_MAC_IPSEC_ADDSA: u16 = 1411; /// Not used pub const AUDIT_MAC_IPSEC_DELSA: u16 = 1412; /// Not used pub const AUDIT_MAC_IPSEC_ADDSPD: u16 = 1413; /// Not used pub const AUDIT_MAC_IPSEC_DELSPD: u16 = 1414; /// Audit an IPSec event pub const AUDIT_MAC_IPSEC_EVENT: u16 = 1415; /// NetLabel: add a static label pub const AUDIT_MAC_UNLBL_STCADD: u16 = 1416; /// NetLabel: del a static label pub const AUDIT_MAC_UNLBL_STCDEL: u16 = 1417; /// NetLabel: add CALIPSO DOI entry pub const AUDIT_MAC_CALIPSO_ADD: u16 = 1418; /// NetLabel: del CALIPSO DOI entry pub const AUDIT_MAC_CALIPSO_DEL: u16 = 1419; // ========================================== // 1700 - 1799 kernel anomaly records // ========================================== pub const AUDIT_FIRST_KERN_ANOM_MSG: u16 = 1700; pub const AUDIT_LAST_KERN_ANOM_MSG: u16 = 1799; /// Device changed promiscuous mode pub const AUDIT_ANOM_PROMISCUOUS: u16 = 1700; /// Process ended abnormally pub const AUDIT_ANOM_ABEND: u16 = 1701; /// Suspicious use of file links pub const AUDIT_ANOM_LINK: u16 = 1702; // ========================================== // 1800 - 1899 kernel integrity events // ========================================== /// Data integrity verification pub const AUDIT_INTEGRITY_DATA: u16 = 1800; /// Metadata integrity verification pub const AUDIT_INTEGRITY_METADATA: u16 = 1801; /// Integrity enable status pub const AUDIT_INTEGRITY_STATUS: u16 = 1802; /// Integrity HASH type pub const AUDIT_INTEGRITY_HASH: u16 = 1803; /// PCR invalidation msgs pub const AUDIT_INTEGRITY_PCR: u16 = 1804; /// policy rule pub const AUDIT_INTEGRITY_RULE: u16 = 1805; // 2000 is for otherwise unclassified kernel audit messages (legacy) pub const AUDIT_KERNEL: u16 = 2000; // rule flags /// Apply rule to user-generated messages pub const AUDIT_FILTER_USER: u32 = 0; /// Apply rule at task creation (not syscall) pub const AUDIT_FILTER_TASK: u32 = 1; /// Apply rule at syscall entry pub const AUDIT_FILTER_ENTRY: u32 = 2; /// Apply rule to file system watches pub const AUDIT_FILTER_WATCH: u32 = 3; /// Apply rule at syscall exit pub const AUDIT_FILTER_EXIT: u32 = 4; /// Apply rule at audit_log_start pub const AUDIT_FILTER_TYPE: u32 = 5; pub const AUDIT_FILTER_FS: u32 = 6; /// Mask to get actual filter pub const AUDIT_NR_FILTERS: u32 = 7; pub const AUDIT_FILTER_PREPEND: u32 = 16; /// Filter is unset pub const AUDIT_FILTER_UNSET: u32 = 128; // Rule actions /// Do not build context if rule matches pub const AUDIT_NEVER: u32 = 0; /// Build context if rule matches pub const AUDIT_POSSIBLE: u32 = 1; /// Generate audit record if rule matches pub const AUDIT_ALWAYS: u32 = 2; pub const AUDIT_MAX_FIELDS: usize = 64; pub const AUDIT_MAX_KEY_LEN: usize = 256; pub const AUDIT_BITMASK_SIZE: usize = 64; pub const AUDIT_SYSCALL_CLASSES: u32 = 16; pub const AUDIT_CLASS_DIR_WRITE: u32 = 0; pub const AUDIT_CLASS_DIR_WRITE_32: u32 = 1; pub const AUDIT_CLASS_CHATTR: u32 = 2; pub const AUDIT_CLASS_CHATTR_32: u32 = 3; pub const AUDIT_CLASS_READ: u32 = 4; pub const AUDIT_CLASS_READ_32: u32 = 5; pub const AUDIT_CLASS_WRITE: u32 = 6; pub const AUDIT_CLASS_WRITE_32: u32 = 7; pub const AUDIT_CLASS_SIGNAL: u32 = 8; pub const AUDIT_CLASS_SIGNAL_32: u32 = 9; pub const AUDIT_UNUSED_BITS: u32 = 134216704; // Field Comparing Constants pub const AUDIT_COMPARE_UID_TO_OBJ_UID: u32 = 1; pub const AUDIT_COMPARE_GID_TO_OBJ_GID: u32 = 2; pub const AUDIT_COMPARE_EUID_TO_OBJ_UID: u32 = 3; pub const AUDIT_COMPARE_EGID_TO_OBJ_GID: u32 = 4; pub const AUDIT_COMPARE_AUID_TO_OBJ_UID: u32 = 5; pub const AUDIT_COMPARE_SUID_TO_OBJ_UID: u32 = 6; pub const AUDIT_COMPARE_SGID_TO_OBJ_GID: u32 = 7; pub const AUDIT_COMPARE_FSUID_TO_OBJ_UID: u32 = 8; pub const AUDIT_COMPARE_FSGID_TO_OBJ_GID: u32 = 9; pub const AUDIT_COMPARE_UID_TO_AUID: u32 = 10; pub const AUDIT_COMPARE_UID_TO_EUID: u32 = 11; pub const AUDIT_COMPARE_UID_TO_FSUID: u32 = 12; pub const AUDIT_COMPARE_UID_TO_SUID: u32 = 13; pub const AUDIT_COMPARE_AUID_TO_FSUID: u32 = 14; pub const AUDIT_COMPARE_AUID_TO_SUID: u32 = 15; pub const AUDIT_COMPARE_AUID_TO_EUID: u32 = 16; pub const AUDIT_COMPARE_EUID_TO_SUID: u32 = 17; pub const AUDIT_COMPARE_EUID_TO_FSUID: u32 = 18; pub const AUDIT_COMPARE_SUID_TO_FSUID: u32 = 19; pub const AUDIT_COMPARE_GID_TO_EGID: u32 = 20; pub const AUDIT_COMPARE_GID_TO_FSGID: u32 = 21; pub const AUDIT_COMPARE_GID_TO_SGID: u32 = 22; pub const AUDIT_COMPARE_EGID_TO_FSGID: u32 = 23; pub const AUDIT_COMPARE_EGID_TO_SGID: u32 = 24; pub const AUDIT_COMPARE_SGID_TO_FSGID: u32 = 25; pub const AUDIT_MAX_FIELD_COMPARE: u32 = 25; // ======================================================================= // rule fields // ======================================================================= pub const AUDIT_PID: u32 = 0; pub const AUDIT_UID: u32 = 1; pub const AUDIT_EUID: u32 = 2; pub const AUDIT_SUID: u32 = 3; pub const AUDIT_FSUID: u32 = 4; pub const AUDIT_GID: u32 = 5; pub const AUDIT_EGID: u32 = 6; pub const AUDIT_SGID: u32 = 7; pub const AUDIT_FSGID: u32 = 8; pub const AUDIT_LOGINUID: u32 = 9; pub const AUDIT_PERS: u32 = 10; pub const AUDIT_ARCH: u32 = 11; pub const AUDIT_MSGTYPE: u32 = 12; pub const AUDIT_SUBJ_USER: u32 = 13; pub const AUDIT_SUBJ_ROLE: u32 = 14; pub const AUDIT_SUBJ_TYPE: u32 = 15; pub const AUDIT_SUBJ_SEN: u32 = 16; pub const AUDIT_SUBJ_CLR: u32 = 17; pub const AUDIT_PPID: u32 = 18; pub const AUDIT_OBJ_USER: u32 = 19; pub const AUDIT_OBJ_ROLE: u32 = 20; pub const AUDIT_OBJ_TYPE: u32 = 21; pub const AUDIT_OBJ_LEV_LOW: u32 = 22; pub const AUDIT_OBJ_LEV_HIGH: u32 = 23; pub const AUDIT_LOGINUID_SET: u32 = 24; pub const AUDIT_SESSIONID: u32 = 25; pub const AUDIT_FSTYPE: u32 = 26; pub const AUDIT_DEVMAJOR: u32 = 100; pub const AUDIT_DEVMINOR: u32 = 101; pub const AUDIT_INODE: u32 = 102; pub const AUDIT_EXIT: u32 = 103; pub const AUDIT_SUCCESS: u32 = 104; pub const AUDIT_WATCH: u32 = 105; pub const AUDIT_PERM: u32 = 106; pub const AUDIT_DIR: u32 = 107; pub const AUDIT_FILETYPE: u32 = 108; pub const AUDIT_OBJ_UID: u32 = 109; pub const AUDIT_OBJ_GID: u32 = 110; pub const AUDIT_FIELD_COMPARE: u32 = 111; pub const AUDIT_EXE: u32 = 112; pub const AUDIT_ARG0: u32 = 200; pub const AUDIT_ARG1: u32 = 201; pub const AUDIT_ARG2: u32 = 202; pub const AUDIT_ARG3: u32 = 203; pub const AUDIT_FILTERKEY: u32 = 210; pub const AUDIT_BIT_MASK: u32 = 0x0800_0000; pub const AUDIT_LESS_THAN: u32 = 0x1000_0000; pub const AUDIT_GREATER_THAN: u32 = 0x2000_0000; pub const AUDIT_NOT_EQUAL: u32 = 0x3000_0000; pub const AUDIT_EQUAL: u32 = 0x4000_0000; pub const AUDIT_BIT_TEST: u32 = AUDIT_BIT_MASK | AUDIT_EQUAL; pub const AUDIT_LESS_THAN_OR_EQUAL: u32 = AUDIT_LESS_THAN | AUDIT_EQUAL; pub const AUDIT_GREATER_THAN_OR_EQUAL: u32 = AUDIT_GREATER_THAN | AUDIT_EQUAL; pub const AUDIT_OPERATORS: u32 = AUDIT_EQUAL | AUDIT_NOT_EQUAL | AUDIT_BIT_MASK; // ============================================ // failure to log actions // ============================================ pub const AUDIT_FAIL_SILENT: u32 = 0; pub const AUDIT_FAIL_PRINTK: u32 = 1; pub const AUDIT_FAIL_PANIC: u32 = 2; pub const AUDIT_PERM_EXEC: u32 = 1; pub const AUDIT_PERM_WRITE: u32 = 2; pub const AUDIT_PERM_READ: u32 = 4; pub const AUDIT_PERM_ATTR: u32 = 8; pub const AUDIT_MESSAGE_TEXT_MAX: u32 = 8560; pub const AUDIT_FEATURE_VERSION: u32 = 1; pub const AUDIT_FEATURE_ONLY_UNSET_LOGINUID: u32 = 0; pub const AUDIT_FEATURE_LOGINUID_IMMUTABLE: u32 = 1; pub const AUDIT_LAST_FEATURE: u32 = 1; /// Unused multicast group for audit pub const AUDIT_NLGRP_NONE: u32 = 0; /// Multicast group to listen for audit events pub const AUDIT_NLGRP_READLOG: u32 = 1; pub const __AUDIT_ARCH_CONVENTION_MASK: u32 = 0x3000_0000; pub const __AUDIT_ARCH_CONVENTION_MIPS64_N32: u32 = 0x2000_0000; pub const __AUDIT_ARCH_64BIT: u32 = 0x0800_0000; pub const __AUDIT_ARCH_LE: u32 = 0x4000_0000; pub const AUDIT_ARCH_AARCH64: u32 = 0xC000_00B7; pub const AUDIT_ARCH_ALPHA: u32 = 0xC000_9026; pub const AUDIT_ARCH_ARM: u32 = 0x4000_0028; pub const AUDIT_ARCH_ARMEB: u32 = 0x28; pub const AUDIT_ARCH_CRIS: u32 = 0x4000_004C; pub const AUDIT_ARCH_FRV: u32 = 0x5441; pub const AUDIT_ARCH_I386: u32 = 0x4000_0003; pub const AUDIT_ARCH_IA64: u32 = 0xC000_0032; pub const AUDIT_ARCH_M32R: u32 = 0x58; pub const AUDIT_ARCH_M68K: u32 = 0x04; pub const AUDIT_ARCH_MICROBLAZE: u32 = 0xBD; pub const AUDIT_ARCH_MIPS: u32 = 0x08; pub const AUDIT_ARCH_MIPSEL: u32 = 0x4000_0008; pub const AUDIT_ARCH_MIPS64: u32 = 0x8000_0008; pub const AUDIT_ARCH_MIPS64N32: u32 = 0xA000_0008; pub const AUDIT_ARCH_MIPSEL64: u32 = 0xC000_0008; pub const AUDIT_ARCH_MIPSEL64N32: u32 = 0xE000_0008; pub const AUDIT_ARCH_OPENRISC: u32 = 92; pub const AUDIT_ARCH_PARISC: u32 = 15; pub const AUDIT_ARCH_PARISC64: u32 = 0x8000_000F; pub const AUDIT_ARCH_PPC: u32 = 20; pub const AUDIT_ARCH_PPC64: u32 = 0x8000_0015; pub const AUDIT_ARCH_PPC64LE: u32 = 0xC000_0015; pub const AUDIT_ARCH_S390: u32 = 22; pub const AUDIT_ARCH_S390X: u32 = 0x8000_0016; pub const AUDIT_ARCH_SH: u32 = 42; pub const AUDIT_ARCH_SHEL: u32 = 0x4000_002A; pub const AUDIT_ARCH_SH64: u32 = 0x8000_002A; pub const AUDIT_ARCH_SHEL64: u32 = 0xC000_002A; pub const AUDIT_ARCH_SPARC: u32 = 2; pub const AUDIT_ARCH_SPARC64: u32 = 0x8000_002B; pub const AUDIT_ARCH_TILEGX: u32 = 0xC000_00BF; pub const AUDIT_ARCH_TILEGX32: u32 = 0x4000_00BF; pub const AUDIT_ARCH_TILEPRO: u32 = 0x4000_00BC; pub const AUDIT_ARCH_X86_64: u32 = 0xC000_003E; ================================================ FILE: netlink-packet-audit/src/lib.rs ================================================ // SPDX-License-Identifier: MIT #[macro_use] extern crate log; pub(crate) extern crate netlink_packet_utils as utils; pub use self::utils::{traits, DecodeError}; pub use netlink_packet_core::{ ErrorMessage, NetlinkBuffer, NetlinkHeader, NetlinkMessage, NetlinkPayload, }; pub(crate) use netlink_packet_core::{NetlinkDeserializable, NetlinkSerializable}; use core::ops::Range; /// Represent a multi-bytes field with a fixed size in a packet pub(crate) type Field = Range; mod codec; pub use codec::NetlinkAuditCodec; pub mod status; pub use self::status::*; pub mod rules; pub use self::rules::*; mod message; pub use self::message::*; mod buffer; pub use self::buffer::*; pub mod constants; pub use self::constants::*; #[cfg(test)] #[macro_use] extern crate lazy_static; ================================================ FILE: netlink-packet-audit/src/message.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ constants::*, rules::RuleMessage, traits::{Emitable, ParseableParametrized}, AuditBuffer, DecodeError, NetlinkDeserializable, NetlinkHeader, NetlinkPayload, NetlinkSerializable, StatusMessage, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum AuditMessage { GetStatus(Option), SetStatus(StatusMessage), AddRule(RuleMessage), DelRule(RuleMessage), ListRules(Option), /// Event message (message types 1300 through 1399). This includes the following message types /// (this list is non-exhaustive, and not really kept up to date): `AUDIT_SYSCALL`, /// `AUDIT_PATH`, `AUDIT_IPC`, `AUDIT_SOCKETCALL`, `AUDIT_CONFIG_CHANGE`, `AUDIT_SOCKADDR`, /// `AUDIT_CWD`, `AUDIT_EXECVE`, `AUDIT_IPC_SET_PERM`, `AUDIT_MQ_OPEN`, `AUDIT_MQ_SENDRECV`, /// `AUDIT_MQ_NOTIFY`, `AUDIT_MQ_GETSETATTR`, `AUDIT_KERNEL_OTHER`, `AUDIT_FD_PAIR`, /// `AUDIT_OBJ_PID`, `AUDIT_TTY`, `AUDIT_EOE`, `AUDIT_BPRM_FCAPS`, `AUDIT_CAPSET`, /// `AUDIT_MMAP`, `AUDIT_NETFILTER_PKT`, `AUDIT_NETFILTER_CFG`, `AUDIT_SECCOMP`, /// `AUDIT_PROCTITLE`, `AUDIT_FEATURE_CHANGE`, `AUDIT_REPLACE`, `AUDIT_KERN_MODULE`, /// `AUDIT_FANOTIFY`. /// /// The first element of the tuple is the message type, and the second is the event data. Event((u16, String)), /// All the other events are parsed as such as they can be parsed also. Other((u16, String)), } impl AuditMessage { pub fn is_event(&self) -> bool { matches!(self, AuditMessage::Event(_)) } pub fn is_get_status(&self) -> bool { matches!(self, AuditMessage::GetStatus(_)) } pub fn is_set_status(&self) -> bool { matches!(self, AuditMessage::GetStatus(_)) } pub fn is_add_rule(&self) -> bool { matches!(self, AuditMessage::AddRule(_)) } pub fn is_del_rule(&self) -> bool { matches!(self, AuditMessage::DelRule(_)) } pub fn is_list_rules(&self) -> bool { matches!(self, AuditMessage::ListRules(_)) } pub fn message_type(&self) -> u16 { use self::AuditMessage::*; match self { GetStatus(_) => AUDIT_GET, SetStatus(_) => AUDIT_SET, ListRules(_) => AUDIT_LIST_RULES, AddRule(_) => AUDIT_ADD_RULE, DelRule(_) => AUDIT_DEL_RULE, Event((message_type, _)) => *message_type, Other((message_type, _)) => *message_type, } } } impl Emitable for AuditMessage { fn buffer_len(&self) -> usize { use self::AuditMessage::*; match self { GetStatus(Some(ref msg)) => msg.buffer_len(), SetStatus(ref msg) => msg.buffer_len(), AddRule(ref msg) => msg.buffer_len(), DelRule(ref msg) => msg.buffer_len(), ListRules(Some(ref msg)) => msg.buffer_len(), GetStatus(None) | ListRules(None) => 0, Event((_, ref data)) => data.len(), Other((_, ref data)) => data.len(), } } fn emit(&self, buffer: &mut [u8]) { use self::AuditMessage::*; match self { GetStatus(Some(ref msg)) => msg.emit(buffer), SetStatus(ref msg) => msg.emit(buffer), AddRule(ref msg) => msg.emit(buffer), DelRule(ref msg) => msg.emit(buffer), ListRules(Some(ref msg)) => msg.emit(buffer), ListRules(None) | GetStatus(None) => {} Event((_, ref data)) => buffer.copy_from_slice(data.as_bytes()), Other((_, ref data)) => buffer.copy_from_slice(data.as_bytes()), } } } impl NetlinkSerializable for AuditMessage { fn message_type(&self) -> u16 { self.message_type() } fn buffer_len(&self) -> usize { ::buffer_len(self) } fn serialize(&self, buffer: &mut [u8]) { self.emit(buffer) } } impl NetlinkDeserializable for AuditMessage { type Error = DecodeError; fn deserialize(header: &NetlinkHeader, payload: &[u8]) -> Result { match AuditBuffer::new_checked(payload) { Err(e) => Err(e), Ok(buffer) => match AuditMessage::parse_with_param(&buffer, header.message_type) { Err(e) => Err(e), Ok(message) => Ok(message), }, } } } impl From for NetlinkPayload { fn from(message: AuditMessage) -> Self { NetlinkPayload::InnerMessage(message) } } ================================================ FILE: netlink-packet-audit/src/rules/action.rs ================================================ // SPDX-License-Identifier: MIT use crate::constants::*; #[derive(Copy, Debug, PartialEq, Eq, Clone)] pub enum RuleAction { Never, Possible, Always, Unknown(u32), } impl From for RuleAction { fn from(value: u32) -> Self { use self::RuleAction::*; match value { AUDIT_NEVER => Never, AUDIT_POSSIBLE => Possible, AUDIT_ALWAYS => Always, _ => Unknown(value), } } } impl From for u32 { fn from(value: RuleAction) -> Self { use self::RuleAction::*; match value { Never => AUDIT_NEVER, Possible => AUDIT_POSSIBLE, Always => AUDIT_ALWAYS, Unknown(value) => value, } } } ================================================ FILE: netlink-packet-audit/src/rules/buffer.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use crate::{constants::*, rules::*, traits::Parseable, DecodeError, Field}; // FIXME: when const fn are stable, use them, instead of defining a macro // const fn u32_array(start: usize, len: usize) -> Field { // start..(start + 4 * len) // } macro_rules! u32_array { ($start:expr, $len:expr) => { $start..($start + 4 * $len) }; } const FLAGS: Field = 0..4; const ACTION: Field = 4..8; const FIELD_COUNT: Field = 8..12; const SYSCALLS: Field = u32_array!(FIELD_COUNT.end, AUDIT_BITMASK_SIZE); const FIELDS: Field = u32_array!(SYSCALLS.end, AUDIT_MAX_FIELDS); const VALUES: Field = u32_array!(FIELDS.end, AUDIT_MAX_FIELDS); const FIELD_FLAGS: Field = u32_array!(VALUES.end, AUDIT_MAX_FIELDS); const BUFLEN: Field = FIELD_FLAGS.end..FIELD_FLAGS.end + 4; pub(crate) const RULE_BUF_MIN_LEN: usize = BUFLEN.end; #[allow(non_snake_case)] fn BUF(len: usize) -> Field { BUFLEN.end..(BUFLEN.end + len) } #[derive(Debug, PartialEq, Eq, Clone)] pub struct RuleBuffer { buffer: T, } impl> RuleBuffer { pub fn new(buffer: T) -> RuleBuffer { RuleBuffer { buffer } } pub fn new_checked(buffer: T) -> Result { let packet = Self::new(buffer); packet.check_len()?; Ok(packet) } pub(crate) fn check_len(&self) -> Result<(), DecodeError> { let len = self.buffer.as_ref().len(); if len < BUFLEN.end { Err(format!( "buffer size is {}, whereas a rule buffer is at least {} long", len, BUFLEN.end ) .into()) } else if len < BUFLEN.end + self.buflen() as usize { Err(format!( "buffer length is {}, but it should be {} (header) + {} (length field)", len, BUFLEN.end, self.buflen() ) .into()) } else { Ok(()) } } pub fn flags(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[FLAGS]) } pub fn action(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[ACTION]) } pub fn field_count(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[FIELD_COUNT]) } pub fn buflen(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[BUFLEN]) } } impl<'a, T: AsRef<[u8]> + ?Sized> RuleBuffer<&'a T> { pub fn syscalls(&self) -> &'a [u8] { &self.buffer.as_ref()[SYSCALLS] } pub fn fields(&self) -> &'a [u8] { &self.buffer.as_ref()[FIELDS] } pub fn values(&self) -> &'a [u8] { &self.buffer.as_ref()[VALUES] } pub fn field_flags(&self) -> &'a [u8] { &self.buffer.as_ref()[FIELD_FLAGS] } pub fn buf(&self) -> &'a [u8] { let field = BUF(self.buflen() as usize); &self.buffer.as_ref()[field.start..field.end] } } impl + AsMut<[u8]>> RuleBuffer { pub fn set_flags(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[FLAGS], value) } pub fn set_action(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[ACTION], value) } pub fn set_field_count(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[FIELD_COUNT], value) } pub fn set_buflen(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[BUFLEN], value) } pub fn syscalls_mut(&mut self) -> &mut [u8] { &mut self.buffer.as_mut()[SYSCALLS] } pub fn fields_mut(&mut self) -> &mut [u8] { &mut self.buffer.as_mut()[FIELDS] } pub fn set_field(&mut self, position: usize, value: u32) { let offset = FIELDS.start + (position * 4); assert!(position <= FIELDS.end - 4); NativeEndian::write_u32(&mut self.buffer.as_mut()[offset..offset + 4], value) } pub fn values_mut(&mut self) -> &mut [u8] { &mut self.buffer.as_mut()[VALUES] } pub fn set_value(&mut self, position: usize, value: u32) { let offset = VALUES.start + (position * 4); assert!(position <= VALUES.end - 4); NativeEndian::write_u32(&mut self.buffer.as_mut()[offset..offset + 4], value) } pub fn field_flags_mut(&mut self) -> &mut [u8] { &mut self.buffer.as_mut()[FIELD_FLAGS] } pub fn set_field_flags(&mut self, position: usize, value: u32) { let offset = FIELD_FLAGS.start + (position * 4); assert!(position <= FIELD_FLAGS.end - 4); NativeEndian::write_u32(&mut self.buffer.as_mut()[offset..offset + 4], value) } pub fn buf_mut(&mut self) -> &mut [u8] { let field = BUF(self.buflen() as usize); &mut self.buffer.as_mut()[field.start..field.end] } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for RuleMessage { fn parse(buf: &RuleBuffer<&'a T>) -> Result { use self::RuleField::*; buf.check_len().context("invalid rule message buffer")?; let mut rule = RuleMessage::new(); rule.flags = buf.flags().into(); rule.action = buf.action().into(); rule.syscalls = RuleSyscalls::from_slice(buf.syscalls())?; let mut offset = 0; let fields = buf.fields().chunks(4).map(NativeEndian::read_u32); let values = buf.values().chunks(4).map(NativeEndian::read_u32); let field_flags = buf .field_flags() .chunks(4) .map(|chunk| RuleFieldFlags::from(NativeEndian::read_u32(chunk))); for (field, value, flags) in fields .zip(values.zip(field_flags)) .map(|(field, (value, flags))| (field, value, flags)) .take(buf.field_count() as usize) { let field = match field { AUDIT_PID => Pid(value), AUDIT_UID => Uid(value), AUDIT_EUID => Euid(value), AUDIT_SUID => Suid(value), AUDIT_FSUID => Fsuid(value), AUDIT_GID => Gid(value), AUDIT_EGID => Egid(value), AUDIT_SGID => Sgid(value), AUDIT_FSGID => Fsgid(value), AUDIT_LOGINUID => Loginuid(value), AUDIT_PERS => Pers(value), AUDIT_ARCH => Arch(value), AUDIT_MSGTYPE => Msgtype(value), AUDIT_PPID => Ppid(value), AUDIT_LOGINUID_SET => LoginuidSet(value), AUDIT_SESSIONID => Sessionid(value), AUDIT_FSTYPE => Fstype(value), AUDIT_DEVMAJOR => Devmajor(value), AUDIT_DEVMINOR => Devminor(value), AUDIT_INODE => Inode(value), AUDIT_EXIT => Exit(value), AUDIT_SUCCESS => Success(value), AUDIT_PERM => Perm(value), AUDIT_FILETYPE => Filetype(value), AUDIT_OBJ_UID => ObjUid(value), AUDIT_OBJ_GID => ObjGid(value), AUDIT_FIELD_COMPARE => FieldCompare(value), AUDIT_EXE => Exe(value), AUDIT_ARG0 => Arg0(value), AUDIT_ARG1 => Arg1(value), AUDIT_ARG2 => Arg2(value), AUDIT_ARG3 => Arg3(value), _ => { // For all the other fields, the value is a string let str_end = offset + value as usize; if str_end > buf.buf().len() { return Err(format!( "failed to decode field. type={} (value should be a string?)", field ) .into()); } let s: String = String::from_utf8_lossy(&buf.buf()[offset..str_end]).into(); offset = str_end; match field { AUDIT_WATCH => Watch(s), AUDIT_DIR => Dir(s), AUDIT_FILTERKEY => Filterkey(s), AUDIT_SUBJ_USER => SubjUser(s), AUDIT_SUBJ_ROLE => SubjRole(s), AUDIT_SUBJ_TYPE => SubjType(s), AUDIT_SUBJ_SEN => SubjSen(s), AUDIT_SUBJ_CLR => SubjClr(s), AUDIT_OBJ_USER => ObjUser(s), AUDIT_OBJ_ROLE => ObjRole(s), AUDIT_OBJ_TYPE => ObjType(s), AUDIT_OBJ_LEV_LOW => ObjLevLow(s), AUDIT_OBJ_LEV_HIGH => ObjLevHigh(s), _ => { return Err(format!( "failed to decode field (unknown type) type={}, value={}", field, s ) .into()); } } } }; rule.fields.push((field, flags)); } Ok(rule) } } ================================================ FILE: netlink-packet-audit/src/rules/field.rs ================================================ // SPDX-License-Identifier: MIT use crate::constants::*; #[derive(Debug, PartialEq, Eq, Clone)] pub enum RuleField { Pid(u32), Uid(u32), Euid(u32), Suid(u32), Fsuid(u32), Gid(u32), Egid(u32), Sgid(u32), Fsgid(u32), Loginuid(u32), Pers(u32), Arch(u32), Msgtype(u32), Ppid(u32), LoginuidSet(u32), Sessionid(u32), Fstype(u32), Devmajor(u32), Devminor(u32), Inode(u32), Exit(u32), Success(u32), Perm(u32), Filetype(u32), ObjUid(u32), ObjGid(u32), FieldCompare(u32), Exe(u32), Arg0(u32), Arg1(u32), Arg2(u32), Arg3(u32), Watch(String), Dir(String), Filterkey(String), SubjUser(String), SubjRole(String), SubjType(String), SubjSen(String), SubjClr(String), ObjUser(String), ObjRole(String), ObjType(String), ObjLevLow(String), ObjLevHigh(String), } #[derive(Copy, Debug, PartialEq, Eq, Clone)] pub enum RuleFieldFlags { BitMask, BitTest, LessThan, GreaterThan, NotEqual, Equal, LessThanOrEqual, GreaterThanOrEqual, None, Unknown(u32), } impl From for RuleFieldFlags { fn from(value: u32) -> Self { use self::RuleFieldFlags::*; match value { AUDIT_BIT_MASK => BitMask, AUDIT_BIT_TEST => BitTest, AUDIT_LESS_THAN => LessThan, AUDIT_GREATER_THAN => GreaterThan, AUDIT_NOT_EQUAL => NotEqual, AUDIT_EQUAL => Equal, AUDIT_LESS_THAN_OR_EQUAL => LessThanOrEqual, AUDIT_GREATER_THAN_OR_EQUAL => GreaterThanOrEqual, 0 => None, _ => Unknown(value), } } } impl From for u32 { fn from(value: RuleFieldFlags) -> Self { use self::RuleFieldFlags::*; match value { BitMask => AUDIT_BIT_MASK, BitTest => AUDIT_BIT_TEST, LessThan => AUDIT_LESS_THAN, GreaterThan => AUDIT_GREATER_THAN, NotEqual => AUDIT_NOT_EQUAL, Equal => AUDIT_EQUAL, LessThanOrEqual => AUDIT_LESS_THAN_OR_EQUAL, GreaterThanOrEqual => AUDIT_GREATER_THAN_OR_EQUAL, None => 0, Unknown(value) => value, } } } ================================================ FILE: netlink-packet-audit/src/rules/flags.rs ================================================ // SPDX-License-Identifier: MIT use crate::constants::*; #[derive(Copy, Debug, PartialEq, Eq, Clone)] pub enum RuleFlags { FilterUser, FilterTask, FilterEntry, FilterWatch, FilterExit, FilterType, FilterFs, NrFilters, FilterPrepend, Unset, Unknown(u32), } impl From for RuleFlags { fn from(value: u32) -> Self { use self::RuleFlags::*; match value { AUDIT_FILTER_USER => FilterUser, AUDIT_FILTER_TASK => FilterTask, AUDIT_FILTER_ENTRY => FilterEntry, AUDIT_FILTER_WATCH => FilterWatch, AUDIT_FILTER_EXIT => FilterExit, AUDIT_FILTER_TYPE => FilterType, AUDIT_FILTER_FS => FilterFs, AUDIT_NR_FILTERS => NrFilters, AUDIT_FILTER_PREPEND => FilterPrepend, AUDIT_FILTER_UNSET => Unset, _ => Unknown(value), } } } impl From for u32 { fn from(value: RuleFlags) -> Self { use self::RuleFlags::*; match value { FilterUser => AUDIT_FILTER_USER, FilterTask => AUDIT_FILTER_TASK, FilterEntry => AUDIT_FILTER_ENTRY, FilterWatch => AUDIT_FILTER_WATCH, FilterExit => AUDIT_FILTER_EXIT, FilterType => AUDIT_FILTER_TYPE, FilterFs => AUDIT_FILTER_FS, NrFilters => AUDIT_NR_FILTERS, FilterPrepend => AUDIT_FILTER_PREPEND, Unset => AUDIT_FILTER_UNSET, Unknown(value) => value, } } } ================================================ FILE: netlink-packet-audit/src/rules/mod.rs ================================================ // SPDX-License-Identifier: MIT mod action; pub use self::action::*; mod field; pub use self::field::*; mod flags; pub use self::flags::*; mod syscalls; pub use self::syscalls::*; mod buffer; pub use self::buffer::*; mod rule; pub use self::rule::*; #[cfg(test)] mod tests; ================================================ FILE: netlink-packet-audit/src/rules/rule.rs ================================================ // SPDX-License-Identifier: MIT use byteorder::{ByteOrder, NativeEndian}; use crate::{ constants::AUDIT_MAX_FIELDS, rules::{ RuleAction, RuleBuffer, RuleField, RuleFieldFlags, RuleFlags, RuleSyscalls, RULE_BUF_MIN_LEN, }, traits::Emitable, }; use crate::constants::*; #[derive(Debug, PartialEq, Eq, Clone)] pub struct RuleMessage { pub flags: RuleFlags, pub action: RuleAction, pub fields: Vec<(RuleField, RuleFieldFlags)>, pub syscalls: RuleSyscalls, } impl Default for RuleMessage { fn default() -> Self { RuleMessage::new() } } impl RuleMessage { pub fn new() -> Self { RuleMessage { flags: RuleFlags::from(0), action: RuleAction::from(0), fields: Vec::with_capacity(AUDIT_MAX_FIELDS), syscalls: RuleSyscalls::new_zeroed(), } } #[rustfmt::skip] fn compute_string_values_length(&self) -> usize { use self::RuleField::*; let mut len = 0; for (field, _) in self.fields.iter() { match field { Watch(ref s) | Dir(ref s) | Filterkey(ref s) | SubjUser(ref s) | SubjRole(ref s) | SubjType(ref s) | SubjSen(ref s) | SubjClr(ref s) | ObjUser(ref s) | ObjRole(ref s) | ObjType(ref s) | ObjLevLow(ref s) | ObjLevHigh(ref s) => len += s.len(), _ => {} } } len } } fn set_str_field(rule_buffer: &mut RuleBuffer, position: usize, buflen: &mut usize, s: &str) where T: AsRef<[u8]> + AsMut<[u8]>, { // append the string to the strings buffer rule_buffer.buf_mut()[*buflen..*buflen + s.len()].copy_from_slice(s.as_bytes()); // set the field's value to the string length rule_buffer.set_value(position, s.len() as u32); *buflen += s.len(); } impl Emitable for RuleMessage { fn buffer_len(&self) -> usize { RULE_BUF_MIN_LEN + self.compute_string_values_length() } fn emit(&self, buffer: &mut [u8]) { use self::RuleField::*; let mut rule_buffer = RuleBuffer::new(buffer); rule_buffer.set_flags(self.flags.into()); rule_buffer.set_action(self.action.into()); rule_buffer.set_field_count(self.fields.len() as u32); { let syscalls = rule_buffer.syscalls_mut(); for (i, word) in self.syscalls.0.iter().enumerate() { NativeEndian::write_u32(&mut syscalls[i * 4..i * 4 + 4], *word); } } rule_buffer.set_buflen(self.compute_string_values_length() as u32); let mut buflen = 0; for (i, (field, flags)) in self.fields.iter().enumerate() { rule_buffer.set_field_flags(i, (*flags).into()); match field { Watch(ref s) => { rule_buffer.set_field(i, AUDIT_WATCH); set_str_field(&mut rule_buffer, i, &mut buflen, s); } Dir(ref s) => { rule_buffer.set_field(i, AUDIT_DIR); set_str_field(&mut rule_buffer, i, &mut buflen, s); } Filterkey(ref s) => { rule_buffer.set_field(i, AUDIT_FILTERKEY); set_str_field(&mut rule_buffer, i, &mut buflen, s); } SubjUser(ref s) => { rule_buffer.set_field(i, AUDIT_SUBJ_USER); set_str_field(&mut rule_buffer, i, &mut buflen, s); } SubjRole(ref s) => { rule_buffer.set_field(i, AUDIT_SUBJ_ROLE); set_str_field(&mut rule_buffer, i, &mut buflen, s); } SubjType(ref s) => { rule_buffer.set_field(i, AUDIT_SUBJ_TYPE); set_str_field(&mut rule_buffer, i, &mut buflen, s); } SubjSen(ref s) => { rule_buffer.set_field(i, AUDIT_SUBJ_SEN); set_str_field(&mut rule_buffer, i, &mut buflen, s); } SubjClr(ref s) => { rule_buffer.set_field(i, AUDIT_SUBJ_CLR); set_str_field(&mut rule_buffer, i, &mut buflen, s); } ObjUser(ref s) => { rule_buffer.set_field(i, AUDIT_OBJ_USER); set_str_field(&mut rule_buffer, i, &mut buflen, s); } ObjRole(ref s) => { rule_buffer.set_field(i, AUDIT_OBJ_ROLE); set_str_field(&mut rule_buffer, i, &mut buflen, s); } ObjType(ref s) => { rule_buffer.set_field(i, AUDIT_OBJ_TYPE); set_str_field(&mut rule_buffer, i, &mut buflen, s); } ObjLevLow(ref s) => { rule_buffer.set_field(i, AUDIT_OBJ_LEV_LOW); set_str_field(&mut rule_buffer, i, &mut buflen, s); } ObjLevHigh(ref s) => { rule_buffer.set_field(i, AUDIT_OBJ_LEV_HIGH); set_str_field(&mut rule_buffer, i, &mut buflen, s); } Pid(val) => { rule_buffer.set_field(i, AUDIT_PID); rule_buffer.set_value(i, *val); } Uid(val) => { rule_buffer.set_field(i, AUDIT_UID); rule_buffer.set_value(i, *val); } Euid(val) => { rule_buffer.set_field(i, AUDIT_EUID); rule_buffer.set_value(i, *val); } Suid(val) => { rule_buffer.set_field(i, AUDIT_SUID); rule_buffer.set_value(i, *val); } Fsuid(val) => { rule_buffer.set_field(i, AUDIT_FSUID); rule_buffer.set_value(i, *val); } Gid(val) => { rule_buffer.set_field(i, AUDIT_GID); rule_buffer.set_value(i, *val); } Egid(val) => { rule_buffer.set_field(i, AUDIT_EGID); rule_buffer.set_value(i, *val); } Sgid(val) => { rule_buffer.set_field(i, AUDIT_SGID); rule_buffer.set_value(i, *val); } Fsgid(val) => { rule_buffer.set_field(i, AUDIT_FSGID); rule_buffer.set_value(i, *val); } Loginuid(val) => { rule_buffer.set_field(i, AUDIT_LOGINUID); rule_buffer.set_value(i, *val); } Pers(val) => { rule_buffer.set_field(i, AUDIT_PERS); rule_buffer.set_value(i, *val); } Arch(val) => { rule_buffer.set_field(i, AUDIT_ARCH); rule_buffer.set_value(i, *val); } Msgtype(val) => { rule_buffer.set_field(i, AUDIT_MSGTYPE); rule_buffer.set_value(i, *val); } Ppid(val) => { rule_buffer.set_field(i, AUDIT_PPID); rule_buffer.set_value(i, *val); } LoginuidSet(val) => { rule_buffer.set_field(i, AUDIT_LOGINUID_SET); rule_buffer.set_value(i, *val); } Sessionid(val) => { rule_buffer.set_field(i, AUDIT_SESSIONID); rule_buffer.set_value(i, *val); } Fstype(val) => { rule_buffer.set_field(i, AUDIT_FSTYPE); rule_buffer.set_value(i, *val); } Devmajor(val) => { rule_buffer.set_field(i, AUDIT_DEVMAJOR); rule_buffer.set_value(i, *val); } Devminor(val) => { rule_buffer.set_field(i, AUDIT_DEVMINOR); rule_buffer.set_value(i, *val); } Inode(val) => { rule_buffer.set_field(i, AUDIT_INODE); rule_buffer.set_value(i, *val); } Exit(val) => { rule_buffer.set_field(i, AUDIT_EXIT); rule_buffer.set_value(i, *val); } Success(val) => { rule_buffer.set_field(i, AUDIT_SUCCESS); rule_buffer.set_value(i, *val); } Perm(val) => { rule_buffer.set_field(i, AUDIT_PERM); rule_buffer.set_value(i, *val); } Filetype(val) => { rule_buffer.set_field(i, AUDIT_FILETYPE); rule_buffer.set_value(i, *val); } ObjUid(val) => { rule_buffer.set_field(i, AUDIT_OBJ_UID); rule_buffer.set_value(i, *val); } ObjGid(val) => { rule_buffer.set_field(i, AUDIT_OBJ_GID); rule_buffer.set_value(i, *val); } FieldCompare(val) => { rule_buffer.set_field(i, AUDIT_FIELD_COMPARE); rule_buffer.set_value(i, *val); } Exe(val) => { rule_buffer.set_field(i, AUDIT_EXE); rule_buffer.set_value(i, *val); } Arg0(val) => { rule_buffer.set_field(i, AUDIT_ARG0); rule_buffer.set_value(i, *val); } Arg1(val) => { rule_buffer.set_field(i, AUDIT_ARG1); rule_buffer.set_value(i, *val); } Arg2(val) => { rule_buffer.set_field(i, AUDIT_ARG2); rule_buffer.set_value(i, *val); } Arg3(val) => { rule_buffer.set_field(i, AUDIT_ARG3); rule_buffer.set_value(i, *val); } } } } } ================================================ FILE: netlink-packet-audit/src/rules/syscalls.rs ================================================ // SPDX-License-Identifier: MIT use byteorder::{ByteOrder, NativeEndian}; use crate::{constants::*, DecodeError}; #[derive(Debug, PartialEq, Eq, Clone)] pub struct RuleSyscalls(pub(crate) Vec); const BITMASK_BYTE_LEN: usize = AUDIT_BITMASK_SIZE * 4; const BITMASK_BIT_LEN: u32 = AUDIT_BITMASK_SIZE as u32 * 32; // FIXME: I'm not 100% sure this implementation is correct wrt to endianness. impl RuleSyscalls { // FIXME: this should be a TryFrom when it stabilized... pub fn from_slice(slice: &[u8]) -> Result { if slice.len() != BITMASK_BYTE_LEN { return Err(DecodeError::from(format!( "invalid bitmask size: expected {} bytes got {}", BITMASK_BYTE_LEN, slice.len() ))); } let mut mask = RuleSyscalls::new_zeroed(); let mut word = 0; while word < AUDIT_BITMASK_SIZE { mask.0[word] = NativeEndian::read_u32(&slice[word * 4..word * 4 + 4]); word += 1; } Ok(mask) } pub fn new_zeroed() -> Self { RuleSyscalls(vec![0; AUDIT_BITMASK_SIZE]) } pub fn new_maxed() -> Self { RuleSyscalls(vec![0xffff_ffff; AUDIT_BITMASK_SIZE]) } /// Unset all the bits pub fn unset_all(&mut self) -> &mut Self { self.0 = vec![0; AUDIT_BITMASK_SIZE]; self } /// Return `true` if all the syscalls are set, `false` otherwise pub fn is_all(&self) -> bool { for i in 0..AUDIT_BITMASK_SIZE { if self.0[i] != 0xffff_ffff { return false; } } true } /// Set all the bits pub fn set_all(&mut self) -> &mut Self { self.0 = vec![0xffff_ffff; AUDIT_BITMASK_SIZE]; self } /// Unset the bit corresponding to the given syscall pub fn unset(&mut self, syscall: u32) -> &mut Self { let (word, mask) = Self::syscall_coordinates(syscall); self.0[word] &= !mask; self } /// Set the bit corresponding to the given syscall pub fn set(&mut self, syscall: u32) -> &mut Self { let (word, mask) = Self::syscall_coordinates(syscall); self.0[word] |= mask; self } /// Check if the bit corresponding to the given syscall is set pub fn has(&self, syscall: u32) -> bool { let (word, mask) = Self::syscall_coordinates(syscall); self.0[word] & mask == mask } fn syscall_coordinates(syscall: u32) -> (usize, u32) { let word_index = syscall / 32; let mask = 0x0000_0001 << (syscall - word_index * 32); (word_index as usize, mask) } } // FIXME: There is a LOT of copy paste for those iterator implementations... This feels wrong but I // could not figure out how to avoid it :( pub struct RuleSyscallsIter { index: u32, syscalls: T, } impl IntoIterator for RuleSyscalls { type Item = u32; type IntoIter = RuleSyscallsIter; fn into_iter(self) -> Self::IntoIter { RuleSyscallsIter { index: 0, syscalls: self, } } } impl Iterator for RuleSyscallsIter { type Item = u32; fn next(&mut self) -> Option { while self.index < BITMASK_BIT_LEN { let index = self.index; self.index += 1; if self.syscalls.has(index) { return Some(index as u32); } } None } } impl<'a> IntoIterator for &'a RuleSyscalls { type Item = u32; type IntoIter = RuleSyscallsIter<&'a RuleSyscalls>; fn into_iter(self) -> Self::IntoIter { RuleSyscallsIter { index: 0, syscalls: self, } } } impl<'a> Iterator for RuleSyscallsIter<&'a RuleSyscalls> { type Item = u32; fn next(&mut self) -> Option { while self.index < BITMASK_BIT_LEN { let index = self.index; self.index += 1; if self.syscalls.has(index) { return Some(index as u32); } } None } } impl<'a> IntoIterator for &'a mut RuleSyscalls { type Item = u32; type IntoIter = RuleSyscallsIter<&'a mut RuleSyscalls>; fn into_iter(self) -> Self::IntoIter { RuleSyscallsIter { index: 0, syscalls: self, } } } impl<'a> Iterator for RuleSyscallsIter<&'a mut RuleSyscalls> { type Item = u32; fn next(&mut self) -> Option { while self.index < BITMASK_BIT_LEN { let index = self.index; self.index += 1; if self.syscalls.has(index) { return Some(index as u32); } } None } } #[cfg(test)] mod test { use super::*; #[test] fn test_from_slice() { let s: Vec = vec![0xff; BITMASK_BYTE_LEN]; let syscalls = RuleSyscalls::from_slice(&s[..]).unwrap(); assert_eq!(syscalls.0, vec![0xffff_ffff; AUDIT_BITMASK_SIZE]); let s: Vec = vec![0; BITMASK_BYTE_LEN]; let syscalls = RuleSyscalls::from_slice(&s[..]).unwrap(); assert_eq!(syscalls.0, vec![0; AUDIT_BITMASK_SIZE]); } #[test] fn test_iter() { let s: Vec = vec![0xff; BITMASK_BYTE_LEN]; let syscalls = RuleSyscalls::from_slice(&s[..]).unwrap(); let mut iter = syscalls.into_iter(); for i in 0..BITMASK_BIT_LEN { assert_eq!(i as u32, iter.next().unwrap()); } assert!(iter.next().is_none()); let s: Vec = vec![0; BITMASK_BYTE_LEN]; let syscalls = RuleSyscalls::from_slice(&s[..]).unwrap(); let mut iter = syscalls.into_iter(); assert!(iter.next().is_none()); } #[test] fn test_set_unset() { let mut syscalls = RuleSyscalls::new_zeroed(); for i in 0..BITMASK_BIT_LEN { syscalls.set(i); } assert_eq!(syscalls.0, vec![0xffff_ffff; AUDIT_BITMASK_SIZE]); for i in 0..BITMASK_BIT_LEN { syscalls.unset(BITMASK_BIT_LEN - 1 - i); } assert_eq!(syscalls.0, vec![0; AUDIT_BITMASK_SIZE]); } } ================================================ FILE: netlink-packet-audit/src/rules/tests.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ rules::{ RuleAction, RuleBuffer, RuleField, RuleFieldFlags, RuleFlags, RuleMessage, RuleSyscalls, }, traits::{Emitable, Parseable}, }; const AUDIT_ARCH_X86_64: u32 = 0xC000_003E; #[test] fn parse_rule_1() { let buf = RuleBuffer::new_checked(&M1_BYTES[..]).unwrap(); let parsed = RuleMessage::parse(&buf).unwrap(); assert_eq!(parsed, *M1); } #[test] fn emit_rule_1() { assert_eq!(M1.buffer_len(), M1_BYTES.len()); let mut buf = vec![0; M1_BYTES.len()]; M1.emit(&mut buf[..]); assert_eq!(&buf[..], &M1_BYTES[..]); } #[test] fn parse_rule_2() { let buf = RuleBuffer::new_checked(&M2_BYTES[..]).unwrap(); let parsed = RuleMessage::parse(&buf).unwrap(); assert_eq!(parsed, *M2); } #[test] fn emit_rule_2() { assert_eq!(M2.buffer_len(), M2_BYTES.len()); let mut buf = vec![0; M2_BYTES.len()]; M2.emit(&mut buf[..]); assert_eq!(&buf[..], &M2_BYTES[..]); } #[test] fn parse_rule_3() { let buf = RuleBuffer::new_checked(&M3_BYTES[..]).unwrap(); let parsed = RuleMessage::parse(&buf).unwrap(); assert_eq!(parsed, *M3); } #[test] fn emit_rule_3() { assert_eq!(M3.buffer_len(), M3_BYTES.len()); let mut buf = vec![0; M3_BYTES.len()]; M3.emit(&mut buf[..]); assert_eq!(&buf[..], &M3_BYTES[..]); } lazy_static! { // -w /etc/passwd -p rwxa static ref M1_BYTES: Vec = vec![ 0x04, 0x00, 0x00, 0x00, // flags 0x02, 0x00, 0x00, 0x00, // actions 0x02, 0x00, 0x00, 0x00, // field count // syscalls (64 u32) 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // fields (AUDIT_MAX_FIELD=64, but only the first 8 bytes are being used here) // // 0x69 = AUDIT_WATCH => the value is a path, so the field value is the path length, and the string itself is at the end of the buffer. 0x69, 0x00, 0x00, 0x00, // 0x6a = AUDIT_PERM 0x6a, 0x00, 0x00, 0x00, // Unused 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // values (AUDIT_MAX_FIELD=64, but only the first 8 bytes are being used here) 0x0b, 0x00, 0x00, 0x00, // 11 = length of the string 0x0f, 0x00, 0x00, 0x00, // 15 (not sure how it's interpreted) // Unused 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // fieldflags 0x00, 0x00, 0x00, 0x40, // equal 0x00, 0x00, 0x00, 0x40, // equal // Unused 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, // total buf length = 11 // buf /etc/passwd 0x2f, 0x65, 0x74, 0x63, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x64]; static ref M1: RuleMessage = RuleMessage { flags: RuleFlags::FilterExit, action: RuleAction::Always, fields: vec![ ( RuleField::Watch("/etc/passwd".into()), RuleFieldFlags::Equal, ), (RuleField::Perm(15), RuleFieldFlags::Equal), ], syscalls: RuleSyscalls::new_maxed(), }; // -w /etc/passwd -p rwxa -k mykey static ref M2_BYTES: Vec = vec![ 0x04, 0x00, 0x00, 0x00, // flags 0x02, 0x00, 0x00, 0x00, // actions 0x03, 0x00, 0x00, 0x00, // fields count // syscalls 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // fields 0x69, 0x00, 0x00, 0x00, // AUDIT_WATCH 0x6a, 0x00, 0x00, 0x00, // AUDIT_PERM 0xd2, 0x00, 0x00, 0x00, // AUDIT_FILTERKEY // unused 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // values 0x0b, 0x00, 0x00, 0x00, // length of the AUDIT_WATCH value (11) 0x0f, 0x00, 0x00, 0x00, // value of the AUDIT_PERM field (15) 0x05, 0x00, 0x00, 0x00, // length of the AUDIT_FILTERKEY field (5) // unused 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // fieldflags 0x00, 0x00, 0x00, 0x40, // equal 0x00, 0x00, 0x00, 0x40, // equal 0x00, 0x00, 0x00, 0x40, // equal // unused 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // string buffer 0x10, 0x00, 0x00, 0x00, // buffer length = 16 0x2f, 0x65, 0x74, 0x63, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x64, // value of AUDIT_WATCH ("/etc/passwd") 0x6d, 0x79, 0x6b, 0x65, 0x79 // value of AUDIT_FILTERKEY ("mykey") ]; static ref M2: RuleMessage = RuleMessage { flags: RuleFlags::FilterExit, action: RuleAction::Always, fields: vec![ (RuleField::Watch("/etc/passwd".into()), RuleFieldFlags::Equal), (RuleField::Perm(15), RuleFieldFlags::Equal), (RuleField::Filterkey("mykey".into()), RuleFieldFlags::Equal), ], syscalls: RuleSyscalls::new_maxed(), }; // -a always,exit -F arch=b64 -S personality -F key=bypass static ref M3_BYTES: Vec = vec![ 0x04, 0x00, 0x00, 0x00, // flags 0x02, 0x00, 0x00, 0x00, // actions 0x02, 0x00, 0x00, 0x00, // field count // syscalls (execve) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // fields 0x0b, 0x00, 0x00, 0x00, // AUDIT_ARCH 0xd2, 0x00, 0x00, 0x00, // AUDIT_FILTERKEY // unused 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // values 0x3e, 0x00, 0x00, 0xc0, // AUDIT_ARCH_X86_64 0x06, 0x00, 0x00, 0x00, // length of the string = 6 // unused 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // fieldflags 0x00, 0x00, 0x00, 0x40, // equal 0x00, 0x00, 0x00, 0x40, // equal // unused 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, // buffer totoal length (6) 0x62, 0x79, 0x70, 0x61, 0x73, 0x73 // bypass ]; static ref M3: RuleMessage = { let mut syscalls = RuleSyscalls::new_zeroed(); syscalls.set(135); RuleMessage { flags: RuleFlags::FilterExit, action: RuleAction::Always, fields: vec![ (RuleField::Arch(AUDIT_ARCH_X86_64), RuleFieldFlags::Equal), (RuleField::Filterkey("bypass".into()), RuleFieldFlags::Equal), ], syscalls, } }; } ================================================ FILE: netlink-packet-audit/src/status.rs ================================================ // SPDX-License-Identifier: MIT use byteorder::{ByteOrder, NativeEndian}; use crate::{ traits::{Emitable, Parseable}, DecodeError, Field, }; const MASK: Field = 0..4; const ENABLED: Field = 4..8; const FAILURE: Field = 8..12; const PID: Field = 12..16; const RATE_LIMITING: Field = 16..20; const BACKLOG_LIMIT: Field = 20..24; const LOST: Field = 24..28; const BACKLOG: Field = 28..32; const FEATURE_BITMAP: Field = 32..36; const BACKLOG_WAIT_TIME: Field = 36..40; pub const STATUS_MESSAGE_LEN: usize = BACKLOG_WAIT_TIME.end; #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct StatusMessage { /// Bit mask for valid entries pub mask: u32, pub enabled: u32, /// Failure-to-log action pub failure: u32, /// PID of auditd process pub pid: u32, /// Message rate limit (per second) pub rate_limiting: u32, /// Waiting messages limit pub backlog_limit: u32, /// Messages lost pub lost: u32, /// Messages waiting in queue pub backlog: u32, /// bitmap of kernel audit features pub feature_bitmap: u32, /// Message queue wait timeout pub backlog_wait_time: u32, } impl StatusMessage { pub fn new() -> Self { Default::default() } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct StatusMessageBuffer { buffer: T, } impl> StatusMessageBuffer { pub fn new(buffer: T) -> StatusMessageBuffer { StatusMessageBuffer { buffer } } pub fn new_checked(buffer: T) -> Result, DecodeError> { let buf = Self::new(buffer); buf.check_buffer_length()?; Ok(buf) } fn check_buffer_length(&self) -> Result<(), DecodeError> { let len = self.buffer.as_ref().len(); if len < STATUS_MESSAGE_LEN { return Err(format!( "invalid StatusMessageBuffer buffer: length is {} instead of {}", len, STATUS_MESSAGE_LEN ) .into()); } Ok(()) } pub fn into_inner(self) -> T { self.buffer } pub fn mask(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[MASK]) } pub fn enabled(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[ENABLED]) } pub fn failure(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[FAILURE]) } pub fn pid(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[PID]) } pub fn rate_limiting(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[RATE_LIMITING]) } pub fn backlog_limit(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[BACKLOG_LIMIT]) } pub fn lost(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[LOST]) } pub fn backlog(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[BACKLOG]) } pub fn feature_bitmap(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[FEATURE_BITMAP]) } pub fn backlog_wait_time(&self) -> u32 { NativeEndian::read_u32(&self.buffer.as_ref()[BACKLOG_WAIT_TIME]) } } impl + AsMut<[u8]>> StatusMessageBuffer { pub fn set_mask(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[MASK], value) } pub fn set_enabled(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[ENABLED], value) } pub fn set_failure(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[FAILURE], value) } pub fn set_pid(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[PID], value) } pub fn set_rate_limiting(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[RATE_LIMITING], value) } pub fn set_backlog_limit(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[BACKLOG_LIMIT], value) } pub fn set_lost(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[LOST], value) } pub fn set_backlog(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[BACKLOG], value) } pub fn set_feature_bitmap(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[FEATURE_BITMAP], value) } pub fn set_backlog_wait_time(&mut self, value: u32) { NativeEndian::write_u32(&mut self.buffer.as_mut()[BACKLOG_WAIT_TIME], value) } } impl> Parseable> for StatusMessage { fn parse(buf: &StatusMessageBuffer) -> Result { buf.check_buffer_length()?; Ok(StatusMessage { mask: buf.mask(), enabled: buf.enabled(), failure: buf.failure(), pid: buf.pid(), rate_limiting: buf.rate_limiting(), backlog_limit: buf.backlog_limit(), lost: buf.lost(), backlog: buf.backlog(), feature_bitmap: buf.feature_bitmap(), backlog_wait_time: buf.backlog_wait_time(), }) } } impl Emitable for StatusMessage { fn buffer_len(&self) -> usize { STATUS_MESSAGE_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = StatusMessageBuffer::new(buffer); buffer.set_mask(self.mask); buffer.set_enabled(self.enabled); buffer.set_failure(self.failure); buffer.set_pid(self.pid); buffer.set_rate_limiting(self.rate_limiting); buffer.set_backlog_limit(self.backlog_limit); buffer.set_lost(self.lost); buffer.set_backlog(self.backlog); buffer.set_feature_bitmap(self.feature_bitmap); buffer.set_backlog_wait_time(self.backlog_wait_time); } } ================================================ FILE: netlink-packet-core/Cargo.toml ================================================ [package] authors = ["Corentin Henry "] name = "netlink-packet-core" version = "0.4.2" edition = "2018" homepage = "https://github.com/little-dude/netlink" keywords = ["netlink", "linux"] license = "MIT" readme = "../README.md" repository = "https://github.com/little-dude/netlink" description = "netlink packet types" [dependencies] anyhow = "1.0.31" byteorder = "1.3.2" libc = "0.2.66" netlink-packet-utils = { version = "0.5.1", path = "../netlink-packet-utils" } [dev-dependencies] netlink-packet-route = { version = "0.13.0", path = "../netlink-packet-route" } ================================================ FILE: netlink-packet-core/examples/protocol.rs ================================================ // SPDX-License-Identifier: MIT use std::{error::Error, fmt}; use netlink_packet_core::{ NetlinkDeserializable, NetlinkHeader, NetlinkMessage, NetlinkPayload, NetlinkSerializable, }; // PingPongMessage represent the messages for the "ping-pong" netlink // protocol. There are only two types of messages. #[derive(Debug, Clone, Eq, PartialEq)] pub enum PingPongMessage { Ping(Vec), Pong(Vec), } // The netlink header contains a "message type" field that identifies // the message it carries. Some values are reserved, and we // arbitrarily decided that "ping" type is 18 and "pong" type is 20. pub const PING_MESSAGE: u16 = 18; pub const PONG_MESSAGE: u16 = 20; // A custom error type for when deserialization fails. This is // required because `NetlinkDeserializable::Error` must implement // `std::error::Error`, so a simple `String` won't cut it. #[derive(Debug, Clone, Eq, PartialEq)] pub struct DeserializeError(&'static str); impl Error for DeserializeError { fn description(&self) -> &str { self.0 } fn source(&self) -> Option<&(dyn Error + 'static)> { None } } impl fmt::Display for DeserializeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } // NetlinkDeserializable implementation impl NetlinkDeserializable for PingPongMessage { type Error = DeserializeError; fn deserialize(header: &NetlinkHeader, payload: &[u8]) -> Result { match header.message_type { PING_MESSAGE => Ok(PingPongMessage::Ping(payload.to_vec())), PONG_MESSAGE => Ok(PingPongMessage::Pong(payload.to_vec())), _ => Err(DeserializeError( "invalid ping-pong message: invalid message type", )), } } } // NetlinkSerializable implementation impl NetlinkSerializable for PingPongMessage { fn message_type(&self) -> u16 { match self { PingPongMessage::Ping(_) => PING_MESSAGE, PingPongMessage::Pong(_) => PONG_MESSAGE, } } fn buffer_len(&self) -> usize { match self { PingPongMessage::Ping(vec) | PingPongMessage::Pong(vec) => vec.len(), } } fn serialize(&self, buffer: &mut [u8]) { match self { PingPongMessage::Ping(vec) | PingPongMessage::Pong(vec) => { buffer.copy_from_slice(&vec[..]) } } } } // It can be convenient to be able to create a NetlinkMessage directly // from a PingPongMessage. Since NetlinkMessage already implements // From>, we just need to implement // From> for this to work. impl From for NetlinkPayload { fn from(message: PingPongMessage) -> Self { NetlinkPayload::InnerMessage(message) } } fn main() { let ping_pong_message = PingPongMessage::Ping(vec![0, 1, 2, 3]); let mut packet = NetlinkMessage::from(ping_pong_message); // Before serializing the packet, it is very important to call // finalize() to ensure the header of the message is consistent // with its payload. Otherwise, a panic may occur when calling // `serialize()` packet.finalize(); // Prepare a buffer to serialize the packet. Note that we never // set explicitely `packet.header.length` above. This was done // automatically when we called `finalize()` let mut buf = vec![0; packet.header.length as usize]; // Serialize the packet packet.serialize(&mut buf[..]); // Deserialize the packet let deserialized_packet = NetlinkMessage::::deserialize(&buf) .expect("Failed to deserialize message"); // Normally, the deserialized packet should be exactly the same // than the serialized one. assert_eq!(deserialized_packet, packet); // This should print: // NetlinkMessage { header: NetlinkHeader { length: 20, message_type: 18, flags: 0, sequence_number: 0, port_number: 0 }, payload: InnerMessage(Ping([0, 1, 2, 3])) } println!("{:?}", packet); } ================================================ FILE: netlink-packet-core/examples/rtnetlink.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_core::{NetlinkHeader, NetlinkMessage, NLM_F_DUMP, NLM_F_REQUEST}; use netlink_packet_route::rtnl::{LinkMessage, RtnlMessage}; fn main() { // Create the netlink message, that contains the rtnetlink // message let mut packet = NetlinkMessage { header: NetlinkHeader { sequence_number: 1, flags: NLM_F_DUMP | NLM_F_REQUEST, ..Default::default() }, payload: RtnlMessage::GetLink(LinkMessage::default()).into(), }; // Set a few fields in the packet's header packet.header.flags = NLM_F_DUMP | NLM_F_REQUEST; packet.header.sequence_number = 1; // Before serializing the packet, it is very important to call // finalize() to ensure the header of the message is consistent // with its payload. Otherwise, a panic may occur when calling // `serialize()` packet.finalize(); // Prepare a buffer to serialize the packet. Note that we never // set explicitely `packet.header.length` above. This was done // automatically when we called `finalize()` let mut buf = vec![0; packet.header.length as usize]; // Serialize the packet packet.serialize(&mut buf[..]); // Deserialize the packet let deserialized_packet = NetlinkMessage::::deserialize(&buf).expect("Failed to deserialize message"); // Normally, the deserialized packet should be exactly the same // than the serialized one. assert_eq!(deserialized_packet, packet); println!("{:?}", packet); } ================================================ FILE: netlink-packet-core/src/buffer.rs ================================================ // SPDX-License-Identifier: MIT use byteorder::{ByteOrder, NativeEndian}; use crate::{DecodeError, Field, Rest}; const LENGTH: Field = 0..4; const MESSAGE_TYPE: Field = 4..6; const FLAGS: Field = 6..8; const SEQUENCE_NUMBER: Field = 8..12; const PORT_NUMBER: Field = 12..16; const PAYLOAD: Rest = 16..; /// Length of a Netlink packet header pub const NETLINK_HEADER_LEN: usize = PAYLOAD.start; // Prevent some doctest snippers to be formatted, since we cannot add // the attribute directly in the doctest #[rustfmt::skip] #[derive(Debug, PartialEq, Eq, Clone)] /// A raw Netlink buffer that provides getters and setter for the various header fields, and to /// retrieve the payloads. /// /// # Example: reading a packet /// /// ```rust /// use netlink_packet_core::{NetlinkBuffer, NLM_F_MATCH, NLM_F_REQUEST, NLM_F_ROOT}; /// /// const RTM_GETLINK: u16 = 18; /// /// fn main() { /// // Artificially create an array of bytes that represents a netlink packet. /// // Normally, we would read it from a socket. /// let buffer = vec![ /// 0x28, 0x00, 0x00, 0x00, // length = 40 /// 0x12, 0x00, // message type = 18 (RTM_GETLINK) /// 0x01, 0x03, // flags = Request + Specify Tree Root + Return All Matching /// 0x34, 0x0e, 0xf9, 0x5a, // sequence number = 1526271540 /// 0x00, 0x00, 0x00, 0x00, // port id = 0 /// // payload /// 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /// 0x08, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x00]; /// /// // Wrap the storage into a NetlinkBuffer /// let packet = NetlinkBuffer::new_checked(&buffer[..]).unwrap(); /// /// // Check that the different accessor return the expected values /// assert_eq!(packet.length(), 40); /// assert_eq!(packet.message_type(), RTM_GETLINK); /// assert_eq!(packet.sequence_number(), 1526271540); /// assert_eq!(packet.port_number(), 0); /// assert_eq!(packet.payload_length(), 24); /// assert_eq!(packet.payload(), &buffer[16..]); /// assert_eq!( /// Into::::into(packet.flags()), /// NLM_F_ROOT | NLM_F_REQUEST | NLM_F_MATCH /// ); /// } /// ``` /// /// # Example: writing a packet /// /// ```rust /// use netlink_packet_core::{NetlinkBuffer, NLM_F_MATCH, NLM_F_REQUEST, NLM_F_ROOT}; /// /// const RTM_GETLINK: u16 = 18; /// /// fn main() { /// // The packet we want to write. /// let expected_buffer = vec![ /// 0x28, 0x00, 0x00, 0x00, // length = 40 /// 0x12, 0x00, // message type = 18 (RTM_GETLINK) /// 0x01, 0x03, // flags = Request + Specify Tree Root + Return All Matching /// 0x34, 0x0e, 0xf9, 0x5a, // sequence number = 1526271540 /// 0x00, 0x00, 0x00, 0x00, // port id = 0 /// // payload /// 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /// 0x08, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x00]; /// /// // Create a storage that is big enough for our packet /// let mut buf = vec![0; 40]; /// // the extra scope is to restrict the scope of the borrow /// { /// // Create a NetlinkBuffer. /// let mut packet = NetlinkBuffer::new(&mut buf); /// // Set the various fields /// packet.set_length(40); /// packet.set_message_type(RTM_GETLINK); /// packet.set_sequence_number(1526271540); /// packet.set_port_number(0); /// packet.set_flags(From::from(NLM_F_ROOT | NLM_F_REQUEST | NLM_F_MATCH)); /// // we kind of cheat here to keep the example short /// packet.payload_mut().copy_from_slice(&expected_buffer[16..]); /// } /// // Check that the storage contains the expected values /// assert_eq!(&buf[..], &expected_buffer[..]); /// } /// ``` /// /// Note that in this second example we don't call /// [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked) because the length field is /// initialized to 0, so `new_checked()` would return an error. pub struct NetlinkBuffer { pub buffer: T, } // Prevent some doc strings to be formatted, since we cannot add the // attribute directly in the doctest #[rustfmt::skip] impl> NetlinkBuffer { /// Create a new `NetlinkBuffer` that uses the given buffer as storage. Note that when calling /// this method no check is performed, so trying to access fields may panic. If you're not sure /// the given buffer contains a valid netlink packet, use /// [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked) instead. pub fn new(buffer: T) -> NetlinkBuffer { NetlinkBuffer { buffer } } // Prevent some doc strings to be formatted, since we cannot add // the attribute directly in the doctest #[rustfmt::skip] /// Check the length of the given buffer and make sure it's big enough so that trying to access /// packet fields won't panic. If the buffer is big enough, create a new `NewlinkBuffer` that /// uses this buffer as storage. /// /// # Example /// /// With a buffer that does not even contain a full header: /// /// ```rust /// use netlink_packet_core::NetlinkBuffer; /// static BYTES: [u8; 4] = [0x28, 0x00, 0x00, 0x00]; /// assert!(NetlinkBuffer::new_checked(&BYTES[..]).is_err()); /// ``` /// /// Here is a slightly more tricky error, where technically, the buffer is big enough to /// contains a valid packet. Here, accessing the packet header fields would not panic but /// accessing the payload would, so `new_checked` also checks the length field in the packet /// header: /// /// ```rust /// use netlink_packet_core::NetlinkBuffer; /// // The buffer is 24 bytes long. It contains a valid header but a truncated payload /// static BYTES: [u8; 24] = [ /// // The length field says the buffer is 40 bytes long /// 0x28, 0x00, 0x00, 0x00, /// 0x12, 0x00, // message type /// 0x01, 0x03, // flags /// 0x34, 0x0e, 0xf9, 0x5a, // sequence number /// 0x00, 0x00, 0x00, 0x00, // port id /// // payload /// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; /// assert!(NetlinkBuffer::new_checked(&BYTES[..]).is_err()); /// ``` pub fn new_checked(buffer: T) -> Result, DecodeError> { let packet = Self::new(buffer); packet.check_buffer_length()?; Ok(packet) } fn check_buffer_length(&self) -> Result<(), DecodeError> { let len = self.buffer.as_ref().len(); if len < PORT_NUMBER.end { Err(format!( "invalid netlink buffer: length is {} but netlink packets are at least {} bytes", len, PORT_NUMBER.end ) .into()) } else if len < self.length() as usize { Err(format!( "invalid netlink buffer: length field says {} the buffer is {} bytes long", self.length(), len ) .into()) } else if (self.length() as usize) < PORT_NUMBER.end { Err(format!( "invalid netlink buffer: length field says {} but netlink packets are at least {} bytes", self.length(), len ).into()) } else { Ok(()) } } /// Return the payload length. /// /// # Panic /// /// This panic is the underlying storage is too small or if the `length` field in the header is /// set to a value that exceeds the storage length (see /// [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn payload_length(&self) -> usize { let total_length = self.length() as usize; let payload_offset = PAYLOAD.start; // This may panic! total_length - payload_offset } /// Consume the packet, returning the underlying buffer. pub fn into_inner(self) -> T { self.buffer } /// Return the `length` field /// /// # Panic /// /// This panic is the underlying storage is too small (see [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn length(&self) -> u32 { let data = self.buffer.as_ref(); NativeEndian::read_u32(&data[LENGTH]) } /// Return the `type` field /// /// # Panic /// /// This panic is the underlying storage is too small (see [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn message_type(&self) -> u16 { let data = self.buffer.as_ref(); NativeEndian::read_u16(&data[MESSAGE_TYPE]) } /// Return the `flags` field /// /// # Panic /// /// This panic is the underlying storage is too small (see [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn flags(&self) -> u16 { let data = self.buffer.as_ref(); NativeEndian::read_u16(&data[FLAGS]) } /// Return the `sequence_number` field /// /// # Panic /// /// This panic is the underlying storage is too small (see [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn sequence_number(&self) -> u32 { let data = self.buffer.as_ref(); NativeEndian::read_u32(&data[SEQUENCE_NUMBER]) } /// Return the `port_number` field /// /// # Panic /// /// This panic is the underlying storage is too small (see [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn port_number(&self) -> u32 { let data = self.buffer.as_ref(); NativeEndian::read_u32(&data[PORT_NUMBER]) } } impl + AsMut<[u8]>> NetlinkBuffer { /// Set the packet header `length` field /// /// # Panic /// /// This panic is the underlying storage is too small (see [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn set_length(&mut self, value: u32) { let data = self.buffer.as_mut(); NativeEndian::write_u32(&mut data[LENGTH], value) } /// Set the packet header `message_type` field /// /// # Panic /// /// This panic is the underlying storage is too small (see [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn set_message_type(&mut self, value: u16) { let data = self.buffer.as_mut(); NativeEndian::write_u16(&mut data[MESSAGE_TYPE], value) } /// Set the packet header `flags` field /// /// # Panic /// /// This panic is the underlying storage is too small (see [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn set_flags(&mut self, value: u16) { let data = self.buffer.as_mut(); NativeEndian::write_u16(&mut data[FLAGS], value) } /// Set the packet header `sequence_number` field /// /// # Panic /// /// This panic is the underlying storage is too small (see [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn set_sequence_number(&mut self, value: u32) { let data = self.buffer.as_mut(); NativeEndian::write_u32(&mut data[SEQUENCE_NUMBER], value) } /// Set the packet header `port_number` field /// /// # Panic /// /// This panic is the underlying storage is too small (see [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn set_port_number(&mut self, value: u32) { let data = self.buffer.as_mut(); NativeEndian::write_u32(&mut data[PORT_NUMBER], value) } } impl<'a, T: AsRef<[u8]> + ?Sized> NetlinkBuffer<&'a T> { /// Return a pointer to the packet payload. /// /// # Panic /// /// This panic is the underlying storage is too small or if the `length` field in the header is /// set to a value that exceeds the storage length (see /// [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn payload(&self) -> &'a [u8] { let range = PAYLOAD.start..self.length() as usize; let data = self.buffer.as_ref(); &data[range] } } impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> NetlinkBuffer<&'a mut T> { /// Return a mutable pointer to the payload. /// /// # Panic /// /// This panic is the underlying storage is too small or if the `length` field in the header is /// set to a value that exceeds the storage length (see /// [`new_checked()`](struct.NetlinkBuffer.html#method.new_checked)) pub fn payload_mut(&mut self) -> &mut [u8] { let range = PAYLOAD.start..self.length() as usize; let data = self.buffer.as_mut(); &mut data[range] } } #[cfg(test)] mod tests { use crate::{ constants::{NLM_F_MATCH, NLM_F_REQUEST, NLM_F_ROOT}, NetlinkBuffer, }; const RTM_GETLINK: u16 = 18; // a packet captured with tcpdump that was sent when running `ip link show` #[rustfmt::skip] static IP_LINK_SHOW_PKT: [u8; 40] = [ 0x28, 0x00, 0x00, 0x00, // length = 40 0x12, 0x00, // message type = 18 (RTM_GETLINK) 0x01, 0x03, // flags = Request + Specify Tree Root + Return All Matching 0x34, 0x0e, 0xf9, 0x5a, // sequence number = 1526271540 0x00, 0x00, 0x00, 0x00, // port id = 0 // payload 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x00]; #[test] fn packet_read() { let packet = NetlinkBuffer::new(&IP_LINK_SHOW_PKT[..]); assert_eq!(packet.length(), 40); assert_eq!(packet.message_type(), RTM_GETLINK); assert_eq!(packet.sequence_number(), 1526271540); assert_eq!(packet.port_number(), 0); let flags = packet.flags(); assert!(flags & NLM_F_ROOT == NLM_F_ROOT); assert!(flags & NLM_F_REQUEST == NLM_F_REQUEST); assert!(flags & NLM_F_MATCH == NLM_F_MATCH); assert_eq!(flags, NLM_F_ROOT | NLM_F_REQUEST | NLM_F_MATCH); assert_eq!(packet.payload_length(), 24); assert_eq!(packet.payload(), &IP_LINK_SHOW_PKT[16..]); } #[test] fn packet_build() { let mut buf = vec![0; 40]; { let mut packet = NetlinkBuffer::new(&mut buf); packet.set_length(40); packet.set_message_type(RTM_GETLINK); packet.set_sequence_number(1526271540); packet.set_port_number(0); packet.set_flags(NLM_F_ROOT | NLM_F_REQUEST | NLM_F_MATCH); packet .payload_mut() .copy_from_slice(&IP_LINK_SHOW_PKT[16..]); } assert_eq!(&buf[..], &IP_LINK_SHOW_PKT[..]); } } ================================================ FILE: netlink-packet-core/src/constants.rs ================================================ // SPDX-License-Identifier: MIT /// Must be set on all request messages (typically from user space to kernel space) pub const NLM_F_REQUEST: u16 = 1; /// Indicates the message is part of a multipart message terminated by NLMSG_DONE pub const NLM_F_MULTIPART: u16 = 2; /// Request for an acknowledgment on success. Typical direction of request is from user space /// (CPC) to kernel space (FEC). pub const NLM_F_ACK: u16 = 4; /// Echo this request. Typical direction of request is from user space (CPC) to kernel space /// (FEC). pub const NLM_F_ECHO: u16 = 8; /// Dump was inconsistent due to sequence change pub const NLM_F_DUMP_INTR: u16 = 16; /// Dump was filtered as requested pub const NLM_F_DUMP_FILTERED: u16 = 32; /// Return the complete table instead of a single entry. pub const NLM_F_ROOT: u16 = 256; /// Return all entries matching criteria passed in message content. pub const NLM_F_MATCH: u16 = 512; /// Return an atomic snapshot of the table. Requires `CAP_NET_ADMIN` capability or a effective UID /// of 0. pub const NLM_F_ATOMIC: u16 = 1024; pub const NLM_F_DUMP: u16 = 768; /// Replace existing matching object. pub const NLM_F_REPLACE: u16 = 256; /// Don't replace if the object already exists. pub const NLM_F_EXCL: u16 = 512; /// Create object if it doesn't already exist. pub const NLM_F_CREATE: u16 = 1024; /// Add to the end of the object list. pub const NLM_F_APPEND: u16 = 2048; /// Do not delete recursively pub const NLM_F_NONREC: u16 = 256; /// request was capped pub const NLM_F_CAPPED: u16 = 256; /// extended ACK TVLs were included pub const NLM_F_ACK_TLVS: u16 = 512; ================================================ FILE: netlink-packet-core/src/error.rs ================================================ // SPDX-License-Identifier: MIT use std::{fmt, io, mem::size_of}; use byteorder::{ByteOrder, NativeEndian}; use crate::{DecodeError, Emitable, Field, Parseable, Rest}; const CODE: Field = 0..4; const PAYLOAD: Rest = 4..; const ERROR_HEADER_LEN: usize = PAYLOAD.start; #[derive(Debug, PartialEq, Eq, Clone)] pub struct ErrorBuffer { buffer: T, } impl> ErrorBuffer { pub fn new(buffer: T) -> ErrorBuffer { ErrorBuffer { buffer } } /// Consume the packet, returning the underlying buffer. pub fn into_inner(self) -> T { self.buffer } pub fn new_checked(buffer: T) -> Result { let packet = Self::new(buffer); packet.check_buffer_length()?; Ok(packet) } fn check_buffer_length(&self) -> Result<(), DecodeError> { let len = self.buffer.as_ref().len(); if len < ERROR_HEADER_LEN { Err(format!( "invalid ErrorBuffer: length is {} but ErrorBuffer are at least {} bytes", len, ERROR_HEADER_LEN ) .into()) } else { Ok(()) } } /// Return the error code pub fn code(&self) -> i32 { let data = self.buffer.as_ref(); NativeEndian::read_i32(&data[CODE]) } } impl<'a, T: AsRef<[u8]> + ?Sized> ErrorBuffer<&'a T> { /// Return a pointer to the payload. pub fn payload(&self) -> &'a [u8] { let data = self.buffer.as_ref(); &data[PAYLOAD] } } impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> ErrorBuffer<&'a mut T> { /// Return a mutable pointer to the payload. pub fn payload_mut(&mut self) -> &mut [u8] { let data = self.buffer.as_mut(); &mut data[PAYLOAD] } } impl + AsMut<[u8]>> ErrorBuffer { /// set the error code field pub fn set_code(&mut self, value: i32) { let data = self.buffer.as_mut(); NativeEndian::write_i32(&mut data[CODE], value) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ErrorMessage { pub code: i32, pub header: Vec, } pub type AckMessage = ErrorMessage; impl Emitable for ErrorMessage { fn buffer_len(&self) -> usize { size_of::() + self.header.len() } fn emit(&self, buffer: &mut [u8]) { let mut buffer = ErrorBuffer::new(buffer); buffer.set_code(self.code); buffer.payload_mut().copy_from_slice(&self.header) } } impl<'buffer, T: AsRef<[u8]> + 'buffer> Parseable> for ErrorMessage { fn parse(buf: &ErrorBuffer<&'buffer T>) -> Result { // FIXME: The payload of an error is basically a truncated packet, which requires custom // logic to parse correctly. For now we just return it as a Vec // let header: NetlinkHeader = { // NetlinkBuffer::new_checked(self.payload()) // .context("failed to parse netlink header")? // .parse() // .context("failed to parse nelink header")? // }; Ok(ErrorMessage { code: buf.code(), header: buf.payload().to_vec(), }) } } impl ErrorMessage { /// According to [`netlink(7)`](https://linux.die.net/man/7/netlink) /// the `NLMSG_ERROR` return Negative errno or 0 for acknowledgements. /// /// convert into [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html) /// using the absolute value from errno code pub fn to_io(&self) -> io::Error { io::Error::from_raw_os_error(self.code.abs()) } } impl fmt::Display for ErrorMessage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.to_io(), f) } } impl From for io::Error { fn from(e: ErrorMessage) -> io::Error { e.to_io() } } #[cfg(test)] mod tests { use super::*; #[test] fn into_io_error() { let io_err = io::Error::from_raw_os_error(95); let err_msg = ErrorMessage { code: -95, header: vec![], }; let to_io: io::Error = err_msg.to_io(); assert_eq!(err_msg.to_string(), io_err.to_string()); assert_eq!(to_io.raw_os_error(), io_err.raw_os_error()); } } ================================================ FILE: netlink-packet-core/src/header.rs ================================================ // SPDX-License-Identifier: MIT use crate::{buffer::NETLINK_HEADER_LEN, DecodeError, Emitable, NetlinkBuffer, Parseable}; /// A Netlink header representation. A netlink header has the following structure: /// /// ```no_rust /// 0 8 16 24 32 /// +----------------+----------------+----------------+----------------+ /// | packet length (including header) | /// +----------------+----------------+----------------+----------------+ /// | message type | flags | /// +----------------+----------------+----------------+----------------+ /// | sequence number | /// +----------------+----------------+----------------+----------------+ /// | port number (formerly known as PID) | /// +----------------+----------------+----------------+----------------+ /// ``` #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Default)] pub struct NetlinkHeader { /// Length of the netlink packet, including the header and the payload pub length: u32, /// NetlinkMessage type. The meaning of this field depends on the netlink protocol family in use. pub message_type: u16, /// Flags. It should be set to one of the `NLM_F_*` constants. pub flags: u16, /// Sequence number of the packet pub sequence_number: u32, /// Port number (usually set to the the process ID) pub port_number: u32, } impl Emitable for NetlinkHeader { fn buffer_len(&self) -> usize { NETLINK_HEADER_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = NetlinkBuffer::new(buffer); buffer.set_message_type(self.message_type); buffer.set_length(self.length); buffer.set_flags(self.flags); buffer.set_sequence_number(self.sequence_number); buffer.set_port_number(self.port_number); } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for NetlinkHeader { fn parse(buf: &NetlinkBuffer<&'a T>) -> Result { Ok(NetlinkHeader { length: buf.length(), message_type: buf.message_type(), flags: buf.flags(), sequence_number: buf.sequence_number(), port_number: buf.port_number(), }) } } #[cfg(test)] mod tests { use super::*; use crate::constants::*; // a packet captured with tcpdump that was sent when running `ip link show` #[rustfmt::skip] static IP_LINK_SHOW_PKT: [u8; 40] = [ 0x28, 0x00, 0x00, 0x00, // length = 40 0x12, 0x00, // message type = 18 (RTM_GETLINK) 0x01, 0x03, // flags = Request + Specify Tree Root + Return All Matching 0x34, 0x0e, 0xf9, 0x5a, // sequence number = 1526271540 0x00, 0x00, 0x00, 0x00, // port id = 0 // payload 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x00]; const RTM_GETLINK: u16 = 18; #[test] fn repr_parse() { let repr = NetlinkHeader::parse(&NetlinkBuffer::new_checked(&IP_LINK_SHOW_PKT[..]).unwrap()) .unwrap(); assert_eq!(repr.length, 40); assert_eq!(repr.message_type, RTM_GETLINK); assert_eq!(repr.sequence_number, 1_526_271_540); assert_eq!(repr.port_number, 0); assert_eq!(repr.flags, NLM_F_ROOT | NLM_F_REQUEST | NLM_F_MATCH); } #[test] fn repr_emit() { let repr = NetlinkHeader { length: 40, message_type: RTM_GETLINK, sequence_number: 1_526_271_540, flags: NLM_F_ROOT | NLM_F_REQUEST | NLM_F_MATCH, port_number: 0, }; assert_eq!(repr.buffer_len(), 16); let mut buf = vec![0; 16]; repr.emit(&mut buf[..]); assert_eq!(&buf[..], &IP_LINK_SHOW_PKT[..16]); } } ================================================ FILE: netlink-packet-core/src/lib.rs ================================================ // SPDX-License-Identifier: MIT //! `netlink-packet-core` provides a generic netlink message //! `NetlinkMessage` that is independant of the sub-protocol. Such //! messages are not very useful by themselves, since they are just //! used to carry protocol-dependant messages. That is what the `T` //! represent: `T` is the `NetlinkMessage`'s protocol-dependant //! message. This can be any type that implements //! `NetlinkSerializable` and `NetlinkDeserializable`. //! //! For instance, the `netlink-packet-route` crate provides rtnetlink //! messages via `netlink_packet_route::RtnlMessage`, and //! `netlink-packet-audit` provides audit messages via //! `netlink_packet_audit::AuditMessage`. //! //! By itself, the `netlink-packet-core` crate is not very //! useful. However, it is used in `netlink-proto` to provide an //! asynchronous implementation of the netlink protocol for any //! sub-protocol. Thus, a crate that defines messages for a given //! netlink sub-protocol could integrate with `netlink-packet-core` //! and would get an asynchronous implementation for free. See the //! second example below for such an integration, via the //! `NetlinkSerializable` and `NetlinkDeserializable` traits. //! //! # Example: usage with `netlink-packet-route` //! //! This example shows how to serialize and deserialize netlink packet //! for the rtnetlink sub-protocol. It requires //! `netlink-packet-route`. //! //! ```rust //! use netlink_packet_core::{NetlinkHeader, NetlinkMessage, NLM_F_DUMP, NLM_F_REQUEST}; //! use netlink_packet_route::{LinkMessage, RtnlMessage}; //! //! // Create the netlink message, that contains the rtnetlink //! // message //! let mut packet = NetlinkMessage { //! header: NetlinkHeader { //! sequence_number: 1, //! flags: NLM_F_DUMP | NLM_F_REQUEST, //! ..Default::default() //! }, //! payload: RtnlMessage::GetLink(LinkMessage::default()).into(), //! }; //! //! // Before serializing the packet, it is important to call //! // finalize() to ensure the header of the message is consistent //! // with its payload. Otherwise, a panic may occur when calling //! // serialize() //! packet.finalize(); //! //! // Prepare a buffer to serialize the packet. Note that we never //! // set explicitely `packet.header.length` above. This was done //! // automatically when we called `finalize()` //! let mut buf = vec![0; packet.header.length as usize]; //! // Serialize the packet //! packet.serialize(&mut buf[..]); //! //! // Deserialize the packet //! let deserialized_packet = //! NetlinkMessage::::deserialize(&buf).expect("Failed to deserialize message"); //! //! // Normally, the deserialized packet should be exactly the same //! // than the serialized one. //! assert_eq!(deserialized_packet, packet); //! //! println!("{:?}", packet); //! ``` //! //! # Example: adding messages for new netlink sub-protocol //! //! Let's assume we have a netlink protocol called "ping pong" that //! defines two types of messages: "ping" messages, which payload can //! be any sequence of bytes, and "pong" message, which payload is //! also a sequence of bytes. The protocol works as follow: when an //! enpoint receives a "ping" message, it answers with a "pong", with //! the payload of the "ping" it's answering to. //! //! "ping" messages have type 18 and "pong" have type "20". Here is //! what a "ping" message that would look like if its payload is `[0, //! 1, 2, 3]`: //! //! ```no_rust //! 0 8 16 24 32 //! +----------------+----------------+----------------+----------------+ //! | packet length (including header) = 16 + 4 = 20 | //! +----------------+----------------+----------------+----------------+ //! | message type = 18 (ping) | flags | //! +----------------+----------------+----------------+----------------+ //! | sequence number | //! +----------------+----------------+----------------+----------------+ //! | port number | //! +----------------+----------------+----------------+----------------+ //! | 0 | 1 | 2 | 3 | //! +----------------+----------------+----------------+----------------+ //! ``` //! //! And the "pong" response would be: //! //! ```no_rust //! 0 8 16 24 32 //! +----------------+----------------+----------------+----------------+ //! | packet length (including header) = 16 + 4 = 20 | //! +----------------+----------------+----------------+----------------+ //! | message type = 20 (pong) | flags | //! +----------------+----------------+----------------+----------------+ //! | sequence number | //! +----------------+----------------+----------------+----------------+ //! | port number | //! +----------------+----------------+----------------+----------------+ //! | 0 | 1 | 2 | 3 | //! +----------------+----------------+----------------+----------------+ //! ``` //! //! Here is how we could implement the messages for such a protocol //! and integrate this implementation with `netlink-packet-core`: //! //! ```rust //! use netlink_packet_core::{ //! NetlinkDeserializable, NetlinkHeader, NetlinkMessage, NetlinkPayload, NetlinkSerializable, //! }; //! use std::error::Error; //! use std::fmt; //! //! // PingPongMessage represent the messages for the "ping-pong" netlink //! // protocol. There are only two types of messages. //! #[derive(Debug, Clone, Eq, PartialEq)] //! pub enum PingPongMessage { //! Ping(Vec), //! Pong(Vec), //! } //! //! // The netlink header contains a "message type" field that identifies //! // the message it carries. Some values are reserved, and we //! // arbitrarily decided that "ping" type is 18 and "pong" type is 20. //! pub const PING_MESSAGE: u16 = 18; //! pub const PONG_MESSAGE: u16 = 20; //! //! // A custom error type for when deserialization fails. This is //! // required because `NetlinkDeserializable::Error` must implement //! // `std::error::Error`, so a simple `String` won't cut it. //! #[derive(Debug, Clone, Eq, PartialEq)] //! pub struct DeserializeError(&'static str); //! //! impl Error for DeserializeError { //! fn description(&self) -> &str { //! self.0 //! } //! fn source(&self) -> Option<&(dyn Error + 'static)> { //! None //! } //! } //! //! impl fmt::Display for DeserializeError { //! fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { //! write!(f, "{}", self.0) //! } //! } //! //! // NetlinkDeserializable implementation //! impl NetlinkDeserializable for PingPongMessage { //! type Error = DeserializeError; //! //! fn deserialize(header: &NetlinkHeader, payload: &[u8]) -> Result { //! match header.message_type { //! PING_MESSAGE => Ok(PingPongMessage::Ping(payload.to_vec())), //! PONG_MESSAGE => Ok(PingPongMessage::Pong(payload.to_vec())), //! _ => Err(DeserializeError( //! "invalid ping-pong message: invalid message type", //! )), //! } //! } //! } //! //! // NetlinkSerializable implementation //! impl NetlinkSerializable for PingPongMessage { //! fn message_type(&self) -> u16 { //! match self { //! PingPongMessage::Ping(_) => PING_MESSAGE, //! PingPongMessage::Pong(_) => PONG_MESSAGE, //! } //! } //! //! fn buffer_len(&self) -> usize { //! match self { //! PingPongMessage::Ping(vec) | PingPongMessage::Pong(vec) => vec.len(), //! } //! } //! //! fn serialize(&self, buffer: &mut [u8]) { //! match self { //! PingPongMessage::Ping(vec) | PingPongMessage::Pong(vec) => { //! buffer.copy_from_slice(&vec[..]) //! } //! } //! } //! } //! //! // It can be convenient to be able to create a NetlinkMessage directly //! // from a PingPongMessage. Since NetlinkMessage already implements //! // From>, we just need to implement //! // From> for this to work. //! impl From for NetlinkPayload { //! fn from(message: PingPongMessage) -> Self { //! NetlinkPayload::InnerMessage(message) //! } //! } //! //! fn main() { //! let ping_pong_message = PingPongMessage::Ping(vec![0, 1, 2, 3]); //! let mut packet = NetlinkMessage::from(ping_pong_message); //! //! // Before serializing the packet, it is very important to call //! // finalize() to ensure the header of the message is consistent //! // with its payload. Otherwise, a panic may occur when calling //! // `serialize()` //! packet.finalize(); //! //! // Prepare a buffer to serialize the packet. Note that we never //! // set explicitely `packet.header.length` above. This was done //! // automatically when we called `finalize()` //! let mut buf = vec![0; packet.header.length as usize]; //! // Serialize the packet //! packet.serialize(&mut buf[..]); //! //! // Deserialize the packet //! let deserialized_packet = NetlinkMessage::::deserialize(&buf) //! .expect("Failed to deserialize message"); //! //! // Normally, the deserialized packet should be exactly the same //! // than the serialized one. //! assert_eq!(deserialized_packet, packet); //! //! // This should print: //! // NetlinkMessage { header: NetlinkHeader { length: 20, message_type: 18, flags: 0, sequence_number: 0, port_number: 0 }, payload: InnerMessage(Ping([0, 1, 2, 3])) } //! println!("{:?}", packet); //! } //! ``` use core::ops::{Range, RangeFrom}; /// Represent a multi-bytes field with a fixed size in a packet pub(crate) type Field = Range; /// Represent a field that starts at a given index in a packet pub(crate) type Rest = RangeFrom; pub mod error; pub use self::error::*; pub mod buffer; pub use self::buffer::*; pub mod header; pub use self::header::*; mod traits; pub use self::traits::*; mod payload; pub use self::payload::*; mod message; pub use self::message::*; pub mod constants; pub use self::constants::*; pub use self::utils::errors::*; pub(crate) use self::utils::traits::*; pub(crate) use netlink_packet_utils as utils; ================================================ FILE: netlink-packet-core/src/message.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use std::fmt::Debug; use crate::{ payload::{NLMSG_DONE, NLMSG_ERROR, NLMSG_NOOP, NLMSG_OVERRUN}, AckMessage, DecodeError, Emitable, ErrorBuffer, ErrorMessage, NetlinkBuffer, NetlinkDeserializable, NetlinkHeader, NetlinkPayload, NetlinkSerializable, Parseable, }; /// Represent a netlink message. #[derive(Debug, PartialEq, Eq, Clone)] pub struct NetlinkMessage { /// Message header (this is common to all the netlink protocols) pub header: NetlinkHeader, /// Inner message, which depends on the netlink protocol being used. pub payload: NetlinkPayload, } impl NetlinkMessage { /// Create a new netlink message from the given header and payload pub fn new(header: NetlinkHeader, payload: NetlinkPayload) -> Self { NetlinkMessage { header, payload } } /// Consume this message and return its header and payload pub fn into_parts(self) -> (NetlinkHeader, NetlinkPayload) { (self.header, self.payload) } } impl NetlinkMessage where I: NetlinkDeserializable, { /// Parse the given buffer as a netlink message pub fn deserialize(buffer: &[u8]) -> Result { let netlink_buffer = NetlinkBuffer::new_checked(&buffer)?; >>::parse(&netlink_buffer) } } impl NetlinkMessage where I: NetlinkSerializable, { /// Return the length of this message in bytes pub fn buffer_len(&self) -> usize { ::buffer_len(self) } /// Serialize this message and write the serialized data into the /// given buffer. `buffer` must big large enough for the whole /// message to fit, otherwise, this method will panic. To know how /// big the serialized message is, call `buffer_len()`. /// /// # Panic /// /// This method panics if the buffer is not big enough. pub fn serialize(&self, buffer: &mut [u8]) { self.emit(buffer) } /// Ensure the header (`NetlinkHeader`) is consistent with the payload (`NetlinkPayload`): /// /// - compute the payload length and set the header's length field /// - check the payload type and set the header's message type field accordingly /// /// If you are not 100% sure the header is correct, this method should be called before calling /// [`Emitable::emit()`](trait.Emitable.html#tymethod.emit), as it could panic if the header is /// inconsistent with the rest of the message. pub fn finalize(&mut self) { self.header.length = self.buffer_len() as u32; self.header.message_type = self.payload.message_type(); } } impl<'buffer, B, I> Parseable> for NetlinkMessage where B: AsRef<[u8]> + 'buffer, I: NetlinkDeserializable, { fn parse(buf: &NetlinkBuffer<&'buffer B>) -> Result { use self::NetlinkPayload::*; let header = >>::parse(buf) .context("failed to parse netlink header")?; let bytes = buf.payload(); let payload = match header.message_type { NLMSG_ERROR => { let buf = ErrorBuffer::new_checked(&bytes).context("failed to parse NLMSG_ERROR")?; let msg = ErrorMessage::parse(&buf).context("failed to parse NLMSG_ERROR")?; if msg.code >= 0 { Ack(msg as AckMessage) } else { Error(msg) } } NLMSG_NOOP => Noop, NLMSG_DONE => Done, NLMSG_OVERRUN => Overrun(bytes.to_vec()), message_type => { let inner_msg = I::deserialize(&header, bytes).context(format!( "Failed to parse message with type {}", message_type ))?; InnerMessage(inner_msg) } }; Ok(NetlinkMessage { header, payload }) } } impl Emitable for NetlinkMessage where I: NetlinkSerializable, { fn buffer_len(&self) -> usize { use self::NetlinkPayload::*; let payload_len = match self.payload { Noop | Done => 0, Overrun(ref bytes) => bytes.len(), Error(ref msg) => msg.buffer_len(), Ack(ref msg) => msg.buffer_len(), InnerMessage(ref msg) => msg.buffer_len(), }; self.header.buffer_len() + payload_len } fn emit(&self, buffer: &mut [u8]) { use self::NetlinkPayload::*; self.header.emit(buffer); let buffer = &mut buffer[self.header.buffer_len()..self.header.length as usize]; match self.payload { Noop | Done => {} Overrun(ref bytes) => buffer.copy_from_slice(bytes), Error(ref msg) => msg.emit(buffer), Ack(ref msg) => msg.emit(buffer), InnerMessage(ref msg) => msg.serialize(buffer), } } } impl From for NetlinkMessage where T: Into>, { fn from(inner_message: T) -> Self { NetlinkMessage { header: NetlinkHeader::default(), payload: inner_message.into(), } } } ================================================ FILE: netlink-packet-core/src/payload.rs ================================================ // SPDX-License-Identifier: MIT use std::fmt::Debug; use crate::{AckMessage, ErrorMessage, NetlinkSerializable}; /// The message is ignored. pub const NLMSG_NOOP: u16 = 1; /// The message signals an error and the payload contains a nlmsgerr structure. This can be looked /// at as a NACK and typically it is from FEC to CPC. pub const NLMSG_ERROR: u16 = 2; /// The message terminates a multipart message. /// Data lost pub const NLMSG_DONE: u16 = 3; pub const NLMSG_OVERRUN: u16 = 4; pub const NLMSG_ALIGNTO: u16 = 4; #[derive(Debug, PartialEq, Eq, Clone)] pub enum NetlinkPayload { Done, Error(ErrorMessage), Ack(AckMessage), Noop, Overrun(Vec), InnerMessage(I), } impl NetlinkPayload where I: NetlinkSerializable, { pub fn message_type(&self) -> u16 { match self { NetlinkPayload::Done => NLMSG_DONE, NetlinkPayload::Error(_) | NetlinkPayload::Ack(_) => NLMSG_ERROR, NetlinkPayload::Noop => NLMSG_NOOP, NetlinkPayload::Overrun(_) => NLMSG_OVERRUN, NetlinkPayload::InnerMessage(message) => message.message_type(), } } } ================================================ FILE: netlink-packet-core/src/traits.rs ================================================ // SPDX-License-Identifier: MIT use crate::NetlinkHeader; use std::error::Error; /// A `NetlinkDeserializable` type can be deserialized from a buffer pub trait NetlinkDeserializable: Sized { type Error: Error + Send + Sync + 'static; /// Deserialize the given buffer into `Self`. fn deserialize(header: &NetlinkHeader, payload: &[u8]) -> Result; } pub trait NetlinkSerializable { fn message_type(&self) -> u16; /// Return the length of the serialized data. /// /// Most netlink messages are encoded following a /// [TLV](https://en.wikipedia.org/wiki/Type-length-value) scheme /// and this library takes advantage of this by pre-allocating /// buffers of the appropriate size when serializing messages, /// which is why `buffer_len` is needed. fn buffer_len(&self) -> usize; /// Serialize this types and write the serialized data into the given buffer. /// `buffer`'s length is exactly `InnerMessage::buffer_len()`. /// It means that if `InnerMessage::buffer_len()` is buggy and does not return the appropriate length, /// bad things can happen: /// /// - if `buffer_len()` returns a value _smaller than the actual data_, `emit()` may panics /// - if `buffer_len()` returns a value _bigger than the actual data_, the buffer will contain garbage /// /// # Panic /// /// This method panics if the buffer is not big enough. fn serialize(&self, buffer: &mut [u8]); } ================================================ FILE: netlink-packet-generic/Cargo.toml ================================================ [package] name = "netlink-packet-generic" version = "0.3.1" authors = ["Leo "] edition = "2018" homepage = "https://github.com/little-dude/netlink" repository = "https://github.com/little-dude/netlink" keywords = ["netlink", "linux"] license = "MIT" readme = "../README.md" description = "generic netlink packet types" [dependencies] anyhow = "1.0.39" libc = "0.2.86" byteorder = "1.4.2" netlink-packet-core = { version = "0.4.2", path = "../netlink-packet-core" } netlink-packet-utils = { version = "0.5.1", path = "../netlink-packet-utils" } [dev-dependencies] netlink-sys = { path = "../netlink-sys", version = "0.8.3" } ================================================ FILE: netlink-packet-generic/examples/list_generic_family.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_core::{NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST}; use netlink_packet_generic::{ ctrl::{nlas::GenlCtrlAttrs, GenlCtrl, GenlCtrlCmd}, GenlMessage, }; use netlink_sys::{protocols::NETLINK_GENERIC, Socket, SocketAddr}; fn main() { let mut socket = Socket::new(NETLINK_GENERIC).unwrap(); socket.bind_auto().unwrap(); socket.connect(&SocketAddr::new(0, 0)).unwrap(); let mut genlmsg = GenlMessage::from_payload(GenlCtrl { cmd: GenlCtrlCmd::GetFamily, nlas: vec![], }); genlmsg.finalize(); let mut nlmsg = NetlinkMessage::from(genlmsg); nlmsg.header.flags = NLM_F_REQUEST | NLM_F_DUMP; nlmsg.finalize(); let mut txbuf = vec![0u8; nlmsg.buffer_len()]; nlmsg.serialize(&mut txbuf); socket.send(&txbuf, 0).unwrap(); let mut rxbuf = vec![0u8; 4096]; let mut offset = 0; 'outer: loop { let size = socket.recv(&mut rxbuf, 0).unwrap(); loop { let buf = &rxbuf[offset..]; // Parse the message let msg = >>::deserialize(buf).unwrap(); match msg.payload { NetlinkPayload::Done => break 'outer, NetlinkPayload::InnerMessage(genlmsg) => { if GenlCtrlCmd::NewFamily == genlmsg.payload.cmd { print_entry(genlmsg.payload.nlas); } } NetlinkPayload::Error(err) => { eprintln!("Received a netlink error message: {:?}", err); return; } _ => {} } offset += msg.header.length as usize; if offset == size || msg.header.length == 0 { offset = 0; break; } } } } fn print_entry(entry: Vec) { let family_id = entry .iter() .find_map(|nla| { if let GenlCtrlAttrs::FamilyId(id) = nla { Some(*id) } else { None } }) .expect("Cannot find FamilyId attribute"); let family_name = entry .iter() .find_map(|nla| { if let GenlCtrlAttrs::FamilyName(name) = nla { Some(name.as_str()) } else { None } }) .expect("Cannot find FamilyName attribute"); let version = entry .iter() .find_map(|nla| { if let GenlCtrlAttrs::Version(ver) = nla { Some(*ver) } else { None } }) .expect("Cannot find Version attribute"); let hdrsize = entry .iter() .find_map(|nla| { if let GenlCtrlAttrs::HdrSize(hdr) = nla { Some(*hdr) } else { None } }) .expect("Cannot find HdrSize attribute"); if hdrsize == 0 { println!("0x{:04x} {} [Version {}]", family_id, family_name, version); } else { println!( "0x{:04x} {} [Version {}] [Header {} bytes]", family_id, family_name, version, hdrsize ); } } ================================================ FILE: netlink-packet-generic/src/buffer.rs ================================================ // SPDX-License-Identifier: MIT //! Buffer definition of generic netlink packet use crate::{constants::GENL_HDRLEN, header::GenlHeader, message::GenlMessage}; use netlink_packet_core::DecodeError; use netlink_packet_utils::{Parseable, ParseableParametrized}; use std::fmt::Debug; buffer!(GenlBuffer(GENL_HDRLEN) { cmd: (u8, 0), version: (u8, 1), payload: (slice, GENL_HDRLEN..), }); impl ParseableParametrized<[u8], u16> for GenlMessage where F: ParseableParametrized<[u8], GenlHeader> + Debug, { fn parse_with_param(buf: &[u8], message_type: u16) -> Result { let buf = GenlBuffer::new_checked(buf)?; Self::parse_with_param(&buf, message_type) } } impl<'a, F, T> ParseableParametrized, u16> for GenlMessage where F: ParseableParametrized<[u8], GenlHeader> + Debug, T: AsRef<[u8]> + ?Sized, { fn parse_with_param(buf: &GenlBuffer<&'a T>, message_type: u16) -> Result { let header = GenlHeader::parse(buf)?; let payload_buf = buf.payload(); Ok(GenlMessage::new( header, F::parse_with_param(payload_buf, header)?, message_type, )) } } ================================================ FILE: netlink-packet-generic/src/constants.rs ================================================ // SPDX-License-Identifier: MIT //! Define constants related to generic netlink pub const GENL_ID_CTRL: u16 = libc::GENL_ID_CTRL as u16; pub const GENL_HDRLEN: usize = 4; pub const CTRL_CMD_UNSPEC: u8 = libc::CTRL_CMD_UNSPEC as u8; pub const CTRL_CMD_NEWFAMILY: u8 = libc::CTRL_CMD_NEWFAMILY as u8; pub const CTRL_CMD_DELFAMILY: u8 = libc::CTRL_CMD_DELFAMILY as u8; pub const CTRL_CMD_GETFAMILY: u8 = libc::CTRL_CMD_GETFAMILY as u8; pub const CTRL_CMD_NEWOPS: u8 = libc::CTRL_CMD_NEWOPS as u8; pub const CTRL_CMD_DELOPS: u8 = libc::CTRL_CMD_DELOPS as u8; pub const CTRL_CMD_GETOPS: u8 = libc::CTRL_CMD_GETOPS as u8; pub const CTRL_CMD_NEWMCAST_GRP: u8 = libc::CTRL_CMD_NEWMCAST_GRP as u8; pub const CTRL_CMD_DELMCAST_GRP: u8 = libc::CTRL_CMD_DELMCAST_GRP as u8; pub const CTRL_CMD_GETMCAST_GRP: u8 = libc::CTRL_CMD_GETMCAST_GRP as u8; pub const CTRL_CMD_GETPOLICY: u8 = 10; pub const CTRL_ATTR_UNSPEC: u16 = libc::CTRL_ATTR_UNSPEC as u16; pub const CTRL_ATTR_FAMILY_ID: u16 = libc::CTRL_ATTR_FAMILY_ID as u16; pub const CTRL_ATTR_FAMILY_NAME: u16 = libc::CTRL_ATTR_FAMILY_NAME as u16; pub const CTRL_ATTR_VERSION: u16 = libc::CTRL_ATTR_VERSION as u16; pub const CTRL_ATTR_HDRSIZE: u16 = libc::CTRL_ATTR_HDRSIZE as u16; pub const CTRL_ATTR_MAXATTR: u16 = libc::CTRL_ATTR_MAXATTR as u16; pub const CTRL_ATTR_OPS: u16 = libc::CTRL_ATTR_OPS as u16; pub const CTRL_ATTR_MCAST_GROUPS: u16 = libc::CTRL_ATTR_MCAST_GROUPS as u16; pub const CTRL_ATTR_POLICY: u16 = 8; pub const CTRL_ATTR_OP_POLICY: u16 = 9; pub const CTRL_ATTR_OP: u16 = 10; pub const CTRL_ATTR_OP_UNSPEC: u16 = libc::CTRL_ATTR_OP_UNSPEC as u16; pub const CTRL_ATTR_OP_ID: u16 = libc::CTRL_ATTR_OP_ID as u16; pub const CTRL_ATTR_OP_FLAGS: u16 = libc::CTRL_ATTR_OP_FLAGS as u16; pub const CTRL_ATTR_MCAST_GRP_UNSPEC: u16 = libc::CTRL_ATTR_MCAST_GRP_UNSPEC as u16; pub const CTRL_ATTR_MCAST_GRP_NAME: u16 = libc::CTRL_ATTR_MCAST_GRP_NAME as u16; pub const CTRL_ATTR_MCAST_GRP_ID: u16 = libc::CTRL_ATTR_MCAST_GRP_ID as u16; pub const CTRL_ATTR_POLICY_UNSPEC: u16 = 0; pub const CTRL_ATTR_POLICY_DO: u16 = 1; pub const CTRL_ATTR_POLICY_DUMP: u16 = 2; pub const NL_ATTR_TYPE_INVALID: u32 = 0; pub const NL_ATTR_TYPE_FLAG: u32 = 1; pub const NL_ATTR_TYPE_U8: u32 = 2; pub const NL_ATTR_TYPE_U16: u32 = 3; pub const NL_ATTR_TYPE_U32: u32 = 4; pub const NL_ATTR_TYPE_U64: u32 = 5; pub const NL_ATTR_TYPE_S8: u32 = 6; pub const NL_ATTR_TYPE_S16: u32 = 7; pub const NL_ATTR_TYPE_S32: u32 = 8; pub const NL_ATTR_TYPE_S64: u32 = 9; pub const NL_ATTR_TYPE_BINARY: u32 = 10; pub const NL_ATTR_TYPE_STRING: u32 = 11; pub const NL_ATTR_TYPE_NUL_STRING: u32 = 12; pub const NL_ATTR_TYPE_NESTED: u32 = 13; pub const NL_ATTR_TYPE_NESTED_ARRAY: u32 = 14; pub const NL_ATTR_TYPE_BITFIELD32: u32 = 15; pub const NL_POLICY_TYPE_ATTR_UNSPEC: u16 = 0; pub const NL_POLICY_TYPE_ATTR_TYPE: u16 = 1; pub const NL_POLICY_TYPE_ATTR_MIN_VALUE_S: u16 = 2; pub const NL_POLICY_TYPE_ATTR_MAX_VALUE_S: u16 = 3; pub const NL_POLICY_TYPE_ATTR_MIN_VALUE_U: u16 = 4; pub const NL_POLICY_TYPE_ATTR_MAX_VALUE_U: u16 = 5; pub const NL_POLICY_TYPE_ATTR_MIN_LENGTH: u16 = 6; pub const NL_POLICY_TYPE_ATTR_MAX_LENGTH: u16 = 7; pub const NL_POLICY_TYPE_ATTR_POLICY_IDX: u16 = 8; pub const NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE: u16 = 9; pub const NL_POLICY_TYPE_ATTR_BITFIELD32_MASK: u16 = 10; pub const NL_POLICY_TYPE_ATTR_PAD: u16 = 11; pub const NL_POLICY_TYPE_ATTR_MASK: u16 = 12; ================================================ FILE: netlink-packet-generic/src/ctrl/mod.rs ================================================ // SPDX-License-Identifier: MIT //! Generic netlink controller implementation //! //! This module provides the definition of the controller packet. //! It also serves as an example for creating a generic family. use self::nlas::*; use crate::{constants::*, traits::*, GenlHeader}; use anyhow::Context; use netlink_packet_utils::{nla::NlasIterator, traits::*, DecodeError}; use std::convert::{TryFrom, TryInto}; /// Netlink attributes for this family pub mod nlas; /// Command code definition of Netlink controller (nlctrl) family #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum GenlCtrlCmd { /// Notify from event NewFamily, /// Notify from event DelFamily, /// Request to get family info GetFamily, /// Currently unused NewOps, /// Currently unused DelOps, /// Currently unused GetOps, /// Notify from event NewMcastGrp, /// Notify from event DelMcastGrp, /// Currently unused GetMcastGrp, /// Request to get family policy GetPolicy, } impl From for u8 { fn from(cmd: GenlCtrlCmd) -> u8 { use GenlCtrlCmd::*; match cmd { NewFamily => CTRL_CMD_NEWFAMILY, DelFamily => CTRL_CMD_DELFAMILY, GetFamily => CTRL_CMD_GETFAMILY, NewOps => CTRL_CMD_NEWOPS, DelOps => CTRL_CMD_DELOPS, GetOps => CTRL_CMD_GETOPS, NewMcastGrp => CTRL_CMD_NEWMCAST_GRP, DelMcastGrp => CTRL_CMD_DELMCAST_GRP, GetMcastGrp => CTRL_CMD_GETMCAST_GRP, GetPolicy => CTRL_CMD_GETPOLICY, } } } impl TryFrom for GenlCtrlCmd { type Error = DecodeError; fn try_from(value: u8) -> Result { use GenlCtrlCmd::*; Ok(match value { CTRL_CMD_NEWFAMILY => NewFamily, CTRL_CMD_DELFAMILY => DelFamily, CTRL_CMD_GETFAMILY => GetFamily, CTRL_CMD_NEWOPS => NewOps, CTRL_CMD_DELOPS => DelOps, CTRL_CMD_GETOPS => GetOps, CTRL_CMD_NEWMCAST_GRP => NewMcastGrp, CTRL_CMD_DELMCAST_GRP => DelMcastGrp, CTRL_CMD_GETMCAST_GRP => GetMcastGrp, CTRL_CMD_GETPOLICY => GetPolicy, cmd => { return Err(DecodeError::from(format!( "Unknown control command: {}", cmd ))) } }) } } /// Payload of generic netlink controller #[derive(Clone, Debug, PartialEq, Eq)] pub struct GenlCtrl { /// Command code of this message pub cmd: GenlCtrlCmd, /// Netlink attributes in this message pub nlas: Vec, } impl GenlFamily for GenlCtrl { fn family_name() -> &'static str { "nlctrl" } fn family_id(&self) -> u16 { GENL_ID_CTRL } fn command(&self) -> u8 { self.cmd.into() } fn version(&self) -> u8 { 2 } } impl Emitable for GenlCtrl { fn emit(&self, buffer: &mut [u8]) { self.nlas.as_slice().emit(buffer) } fn buffer_len(&self) -> usize { self.nlas.as_slice().buffer_len() } } impl ParseableParametrized<[u8], GenlHeader> for GenlCtrl { fn parse_with_param(buf: &[u8], header: GenlHeader) -> Result { Ok(Self { cmd: header.cmd.try_into()?, nlas: parse_ctrlnlas(buf)?, }) } } fn parse_ctrlnlas(buf: &[u8]) -> Result, DecodeError> { let nlas = NlasIterator::new(buf) .map(|nla| nla.and_then(|nla| GenlCtrlAttrs::parse(&nla))) .collect::, _>>() .context("failed to parse control message attributes")?; Ok(nlas) } ================================================ FILE: netlink-packet-generic/src/ctrl/nlas/mcast.rs ================================================ // SPDX-License-Identifier: MIT use crate::constants::*; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{Nla, NlaBuffer}, parsers::*, traits::*, DecodeError, }; use std::mem::size_of_val; #[derive(Clone, Debug, PartialEq, Eq)] pub enum McastGrpAttrs { Name(String), Id(u32), } impl Nla for McastGrpAttrs { fn value_len(&self) -> usize { use McastGrpAttrs::*; match self { Name(s) => s.as_bytes().len() + 1, Id(v) => size_of_val(v), } } fn kind(&self) -> u16 { use McastGrpAttrs::*; match self { Name(_) => CTRL_ATTR_MCAST_GRP_NAME, Id(_) => CTRL_ATTR_MCAST_GRP_ID, } } fn emit_value(&self, buffer: &mut [u8]) { use McastGrpAttrs::*; match self { Name(s) => { buffer[..s.len()].copy_from_slice(s.as_bytes()); buffer[s.len()] = 0; } Id(v) => NativeEndian::write_u32(buffer, *v), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for McastGrpAttrs { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { CTRL_ATTR_MCAST_GRP_NAME => { Self::Name(parse_string(payload).context("invalid CTRL_ATTR_MCAST_GRP_NAME value")?) } CTRL_ATTR_MCAST_GRP_ID => { Self::Id(parse_u32(payload).context("invalid CTRL_ATTR_MCAST_GRP_ID value")?) } kind => return Err(DecodeError::from(format!("Unknown NLA type: {}", kind))), }) } } ================================================ FILE: netlink-packet-generic/src/ctrl/nlas/mod.rs ================================================ // SPDX-License-Identifier: MIT use crate::constants::*; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{Nla, NlaBuffer, NlasIterator}, parsers::*, traits::*, DecodeError, }; use std::mem::size_of_val; mod mcast; mod oppolicy; mod ops; mod policy; pub use mcast::*; pub use oppolicy::*; pub use ops::*; pub use policy::*; #[derive(Clone, Debug, PartialEq, Eq)] pub enum GenlCtrlAttrs { FamilyId(u16), FamilyName(String), Version(u32), HdrSize(u32), MaxAttr(u32), Ops(Vec>), McastGroups(Vec>), Policy(PolicyAttr), OpPolicy(OppolicyAttr), Op(u32), } impl Nla for GenlCtrlAttrs { fn value_len(&self) -> usize { use GenlCtrlAttrs::*; match self { FamilyId(v) => size_of_val(v), FamilyName(s) => s.len() + 1, Version(v) => size_of_val(v), HdrSize(v) => size_of_val(v), MaxAttr(v) => size_of_val(v), Ops(nlas) => nlas.iter().map(|op| op.as_slice().buffer_len()).sum(), McastGroups(nlas) => nlas.iter().map(|op| op.as_slice().buffer_len()).sum(), Policy(nla) => nla.buffer_len(), OpPolicy(nla) => nla.buffer_len(), Op(v) => size_of_val(v), } } fn kind(&self) -> u16 { use GenlCtrlAttrs::*; match self { FamilyId(_) => CTRL_ATTR_FAMILY_ID, FamilyName(_) => CTRL_ATTR_FAMILY_NAME, Version(_) => CTRL_ATTR_VERSION, HdrSize(_) => CTRL_ATTR_HDRSIZE, MaxAttr(_) => CTRL_ATTR_MAXATTR, Ops(_) => CTRL_ATTR_OPS, McastGroups(_) => CTRL_ATTR_MCAST_GROUPS, Policy(_) => CTRL_ATTR_POLICY, OpPolicy(_) => CTRL_ATTR_OP_POLICY, Op(_) => CTRL_ATTR_OP, } } fn emit_value(&self, buffer: &mut [u8]) { use GenlCtrlAttrs::*; match self { FamilyId(v) => NativeEndian::write_u16(buffer, *v), FamilyName(s) => { buffer[..s.len()].copy_from_slice(s.as_bytes()); buffer[s.len()] = 0; } Version(v) => NativeEndian::write_u32(buffer, *v), HdrSize(v) => NativeEndian::write_u32(buffer, *v), MaxAttr(v) => NativeEndian::write_u32(buffer, *v), Ops(nlas) => { let mut len = 0; for op in nlas { op.as_slice().emit(&mut buffer[len..]); len += op.as_slice().buffer_len(); } } McastGroups(nlas) => { let mut len = 0; for op in nlas { op.as_slice().emit(&mut buffer[len..]); len += op.as_slice().buffer_len(); } } Policy(nla) => nla.emit_value(buffer), OpPolicy(nla) => nla.emit_value(buffer), Op(v) => NativeEndian::write_u32(buffer, *v), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for GenlCtrlAttrs { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { CTRL_ATTR_FAMILY_ID => { Self::FamilyId(parse_u16(payload).context("invalid CTRL_ATTR_FAMILY_ID value")?) } CTRL_ATTR_FAMILY_NAME => Self::FamilyName( parse_string(payload).context("invalid CTRL_ATTR_FAMILY_NAME value")?, ), CTRL_ATTR_VERSION => { Self::Version(parse_u32(payload).context("invalid CTRL_ATTR_VERSION value")?) } CTRL_ATTR_HDRSIZE => { Self::HdrSize(parse_u32(payload).context("invalid CTRL_ATTR_HDRSIZE value")?) } CTRL_ATTR_MAXATTR => { Self::MaxAttr(parse_u32(payload).context("invalid CTRL_ATTR_MAXATTR value")?) } CTRL_ATTR_OPS => { let ops = NlasIterator::new(payload) .map(|nlas| { nlas.and_then(|nlas| { NlasIterator::new(nlas.value()) .map(|nla| nla.and_then(|nla| OpAttrs::parse(&nla))) .collect::, _>>() }) }) .collect::>, _>>() .context("failed to parse CTRL_ATTR_OPS")?; Self::Ops(ops) } CTRL_ATTR_MCAST_GROUPS => { let groups = NlasIterator::new(payload) .map(|nlas| { nlas.and_then(|nlas| { NlasIterator::new(nlas.value()) .map(|nla| nla.and_then(|nla| McastGrpAttrs::parse(&nla))) .collect::, _>>() }) }) .collect::>, _>>() .context("failed to parse CTRL_ATTR_MCAST_GROUPS")?; Self::McastGroups(groups) } CTRL_ATTR_POLICY => Self::Policy( PolicyAttr::parse(&NlaBuffer::new(payload)) .context("failed to parse CTRL_ATTR_POLICY")?, ), CTRL_ATTR_OP_POLICY => Self::OpPolicy( OppolicyAttr::parse(&NlaBuffer::new(payload)) .context("failed to parse CTRL_ATTR_OP_POLICY")?, ), CTRL_ATTR_OP => Self::Op(parse_u32(payload)?), kind => return Err(DecodeError::from(format!("Unknown NLA type: {}", kind))), }) } } ================================================ FILE: netlink-packet-generic/src/ctrl/nlas/oppolicy.rs ================================================ // SPDX-License-Identifier: MIT use crate::constants::*; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{Nla, NlaBuffer, NlasIterator}, parsers::*, traits::*, DecodeError, }; use std::mem::size_of_val; #[derive(Clone, Debug, PartialEq, Eq)] pub struct OppolicyAttr { pub cmd: u8, pub policy_idx: Vec, } impl Nla for OppolicyAttr { fn value_len(&self) -> usize { self.policy_idx.as_slice().buffer_len() } fn kind(&self) -> u16 { self.cmd as u16 } fn emit_value(&self, buffer: &mut [u8]) { self.policy_idx.as_slice().emit(buffer); } fn is_nested(&self) -> bool { true } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for OppolicyAttr { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); let policy_idx = NlasIterator::new(payload) .map(|nla| nla.and_then(|nla| OppolicyIndexAttr::parse(&nla))) .collect::, _>>() .context("failed to parse OppolicyAttr")?; Ok(Self { cmd: buf.kind() as u8, policy_idx, }) } } #[derive(Clone, Debug, PartialEq, Eq)] pub enum OppolicyIndexAttr { Do(u32), Dump(u32), } impl Nla for OppolicyIndexAttr { fn value_len(&self) -> usize { use OppolicyIndexAttr::*; match self { Do(v) => size_of_val(v), Dump(v) => size_of_val(v), } } fn kind(&self) -> u16 { use OppolicyIndexAttr::*; match self { Do(_) => CTRL_ATTR_POLICY_DO, Dump(_) => CTRL_ATTR_POLICY_DUMP, } } fn emit_value(&self, buffer: &mut [u8]) { use OppolicyIndexAttr::*; match self { Do(v) => NativeEndian::write_u32(buffer, *v), Dump(v) => NativeEndian::write_u32(buffer, *v), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for OppolicyIndexAttr { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { CTRL_ATTR_POLICY_DO => { Self::Do(parse_u32(payload).context("invalid CTRL_ATTR_POLICY_DO value")?) } CTRL_ATTR_POLICY_DUMP => { Self::Dump(parse_u32(payload).context("invalid CTRL_ATTR_POLICY_DUMP value")?) } kind => return Err(DecodeError::from(format!("Unknown NLA type: {}", kind))), }) } } ================================================ FILE: netlink-packet-generic/src/ctrl/nlas/ops.rs ================================================ // SPDX-License-Identifier: MIT use crate::constants::*; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{Nla, NlaBuffer}, parsers::*, traits::*, DecodeError, }; use std::mem::size_of_val; #[derive(Clone, Debug, PartialEq, Eq)] pub enum OpAttrs { Id(u32), Flags(u32), } impl Nla for OpAttrs { fn value_len(&self) -> usize { use OpAttrs::*; match self { Id(v) => size_of_val(v), Flags(v) => size_of_val(v), } } fn kind(&self) -> u16 { use OpAttrs::*; match self { Id(_) => CTRL_ATTR_OP_ID, Flags(_) => CTRL_ATTR_OP_FLAGS, } } fn emit_value(&self, buffer: &mut [u8]) { use OpAttrs::*; match self { Id(v) => NativeEndian::write_u32(buffer, *v), Flags(v) => NativeEndian::write_u32(buffer, *v), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for OpAttrs { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { CTRL_ATTR_OP_ID => { Self::Id(parse_u32(payload).context("invalid CTRL_ATTR_OP_ID value")?) } CTRL_ATTR_OP_FLAGS => { Self::Flags(parse_u32(payload).context("invalid CTRL_ATTR_OP_FLAGS value")?) } kind => return Err(DecodeError::from(format!("Unknown NLA type: {}", kind))), }) } } ================================================ FILE: netlink-packet-generic/src/ctrl/nlas/policy.rs ================================================ // SPDX-License-Identifier: MIT use crate::constants::*; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{Nla, NlaBuffer, NlasIterator}, parsers::*, traits::*, DecodeError, }; use std::{ convert::TryFrom, mem::{size_of, size_of_val}, }; // PolicyAttr #[derive(Clone, Debug, PartialEq, Eq)] pub struct PolicyAttr { pub index: u16, pub attr_policy: AttributePolicyAttr, } impl Nla for PolicyAttr { fn value_len(&self) -> usize { self.attr_policy.buffer_len() } fn kind(&self) -> u16 { self.index } fn emit_value(&self, buffer: &mut [u8]) { self.attr_policy.emit(buffer); } fn is_nested(&self) -> bool { true } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for PolicyAttr { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(Self { index: buf.kind(), attr_policy: AttributePolicyAttr::parse(&NlaBuffer::new(payload)) .context("failed to parse PolicyAttr")?, }) } } // AttributePolicyAttr #[derive(Clone, Debug, PartialEq, Eq)] pub struct AttributePolicyAttr { pub index: u16, pub policies: Vec, } impl Nla for AttributePolicyAttr { fn value_len(&self) -> usize { self.policies.as_slice().buffer_len() } fn kind(&self) -> u16 { self.index } fn emit_value(&self, buffer: &mut [u8]) { self.policies.as_slice().emit(buffer); } fn is_nested(&self) -> bool { true } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for AttributePolicyAttr { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); let policies = NlasIterator::new(payload) .map(|nla| nla.and_then(|nla| NlPolicyTypeAttrs::parse(&nla))) .collect::, _>>() .context("failed to parse AttributePolicyAttr")?; Ok(Self { index: buf.kind(), policies, }) } } // PolicyTypeAttrs #[derive(Clone, Debug, PartialEq, Eq)] pub enum NlPolicyTypeAttrs { Type(NlaType), MinValueSigned(i64), MaxValueSigned(i64), MaxValueUnsigned(u64), MinValueUnsigned(u64), MinLength(u32), MaxLength(u32), PolicyIdx(u32), PolicyMaxType(u32), Bitfield32Mask(u32), Mask(u64), } impl Nla for NlPolicyTypeAttrs { fn value_len(&self) -> usize { use NlPolicyTypeAttrs::*; match self { Type(v) => size_of_val(v), MinValueSigned(v) => size_of_val(v), MaxValueSigned(v) => size_of_val(v), MaxValueUnsigned(v) => size_of_val(v), MinValueUnsigned(v) => size_of_val(v), MinLength(v) => size_of_val(v), MaxLength(v) => size_of_val(v), PolicyIdx(v) => size_of_val(v), PolicyMaxType(v) => size_of_val(v), Bitfield32Mask(v) => size_of_val(v), Mask(v) => size_of_val(v), } } fn kind(&self) -> u16 { use NlPolicyTypeAttrs::*; match self { Type(_) => NL_POLICY_TYPE_ATTR_TYPE, MinValueSigned(_) => NL_POLICY_TYPE_ATTR_MIN_VALUE_S, MaxValueSigned(_) => NL_POLICY_TYPE_ATTR_MAX_VALUE_S, MaxValueUnsigned(_) => NL_POLICY_TYPE_ATTR_MIN_VALUE_U, MinValueUnsigned(_) => NL_POLICY_TYPE_ATTR_MAX_VALUE_U, MinLength(_) => NL_POLICY_TYPE_ATTR_MIN_LENGTH, MaxLength(_) => NL_POLICY_TYPE_ATTR_MAX_LENGTH, PolicyIdx(_) => NL_POLICY_TYPE_ATTR_POLICY_IDX, PolicyMaxType(_) => NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE, Bitfield32Mask(_) => NL_POLICY_TYPE_ATTR_BITFIELD32_MASK, Mask(_) => NL_POLICY_TYPE_ATTR_MASK, } } fn emit_value(&self, buffer: &mut [u8]) { use NlPolicyTypeAttrs::*; match self { Type(v) => NativeEndian::write_u32(buffer, u32::from(*v)), MinValueSigned(v) => NativeEndian::write_i64(buffer, *v), MaxValueSigned(v) => NativeEndian::write_i64(buffer, *v), MaxValueUnsigned(v) => NativeEndian::write_u64(buffer, *v), MinValueUnsigned(v) => NativeEndian::write_u64(buffer, *v), MinLength(v) => NativeEndian::write_u32(buffer, *v), MaxLength(v) => NativeEndian::write_u32(buffer, *v), PolicyIdx(v) => NativeEndian::write_u32(buffer, *v), PolicyMaxType(v) => NativeEndian::write_u32(buffer, *v), Bitfield32Mask(v) => NativeEndian::write_u32(buffer, *v), Mask(v) => NativeEndian::write_u64(buffer, *v), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for NlPolicyTypeAttrs { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { NL_POLICY_TYPE_ATTR_TYPE => { let value = parse_u32(payload).context("invalid NL_POLICY_TYPE_ATTR_TYPE value")?; Self::Type(NlaType::try_from(value)?) } NL_POLICY_TYPE_ATTR_MIN_VALUE_S => Self::MinValueSigned( parse_i64(payload).context("invalid NL_POLICY_TYPE_ATTR_MIN_VALUE_S value")?, ), NL_POLICY_TYPE_ATTR_MAX_VALUE_S => Self::MaxValueSigned( parse_i64(payload).context("invalid NL_POLICY_TYPE_ATTR_MAX_VALUE_S value")?, ), NL_POLICY_TYPE_ATTR_MIN_VALUE_U => Self::MinValueUnsigned( parse_u64(payload).context("invalid NL_POLICY_TYPE_ATTR_MIN_VALUE_U value")?, ), NL_POLICY_TYPE_ATTR_MAX_VALUE_U => Self::MaxValueUnsigned( parse_u64(payload).context("invalid NL_POLICY_TYPE_ATTR_MAX_VALUE_U value")?, ), NL_POLICY_TYPE_ATTR_MIN_LENGTH => Self::MinLength( parse_u32(payload).context("invalid NL_POLICY_TYPE_ATTR_MIN_LENGTH value")?, ), NL_POLICY_TYPE_ATTR_MAX_LENGTH => Self::MaxLength( parse_u32(payload).context("invalid NL_POLICY_TYPE_ATTR_MAX_LENGTH value")?, ), NL_POLICY_TYPE_ATTR_POLICY_IDX => Self::PolicyIdx( parse_u32(payload).context("invalid NL_POLICY_TYPE_ATTR_POLICY_IDX value")?, ), NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE => Self::PolicyMaxType( parse_u32(payload).context("invalid NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE value")?, ), NL_POLICY_TYPE_ATTR_BITFIELD32_MASK => Self::Bitfield32Mask( parse_u32(payload).context("invalid NL_POLICY_TYPE_ATTR_BITFIELD32_MASK value")?, ), NL_POLICY_TYPE_ATTR_MASK => { Self::Mask(parse_u64(payload).context("invalid NL_POLICY_TYPE_ATTR_MASK value")?) } kind => return Err(DecodeError::from(format!("Unknown NLA type: {}", kind))), }) } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum NlaType { Flag, U8, U16, U32, U64, S8, S16, S32, S64, Binary, String, NulString, Nested, NestedArray, Bitfield32, } impl From for u32 { fn from(nlatype: NlaType) -> u32 { match nlatype { NlaType::Flag => NL_ATTR_TYPE_FLAG, NlaType::U8 => NL_ATTR_TYPE_U8, NlaType::U16 => NL_ATTR_TYPE_U16, NlaType::U32 => NL_ATTR_TYPE_U32, NlaType::U64 => NL_ATTR_TYPE_U64, NlaType::S8 => NL_ATTR_TYPE_S8, NlaType::S16 => NL_ATTR_TYPE_S16, NlaType::S32 => NL_ATTR_TYPE_S32, NlaType::S64 => NL_ATTR_TYPE_S64, NlaType::Binary => NL_ATTR_TYPE_BINARY, NlaType::String => NL_ATTR_TYPE_STRING, NlaType::NulString => NL_ATTR_TYPE_NUL_STRING, NlaType::Nested => NL_ATTR_TYPE_NESTED, NlaType::NestedArray => NL_ATTR_TYPE_NESTED_ARRAY, NlaType::Bitfield32 => NL_ATTR_TYPE_BITFIELD32, } } } impl TryFrom for NlaType { type Error = DecodeError; fn try_from(value: u32) -> Result { Ok(match value { NL_ATTR_TYPE_FLAG => NlaType::Flag, NL_ATTR_TYPE_U8 => NlaType::U8, NL_ATTR_TYPE_U16 => NlaType::U16, NL_ATTR_TYPE_U32 => NlaType::U32, NL_ATTR_TYPE_U64 => NlaType::U64, NL_ATTR_TYPE_S8 => NlaType::S8, NL_ATTR_TYPE_S16 => NlaType::S16, NL_ATTR_TYPE_S32 => NlaType::S32, NL_ATTR_TYPE_S64 => NlaType::S64, NL_ATTR_TYPE_BINARY => NlaType::Binary, NL_ATTR_TYPE_STRING => NlaType::String, NL_ATTR_TYPE_NUL_STRING => NlaType::NulString, NL_ATTR_TYPE_NESTED => NlaType::Nested, NL_ATTR_TYPE_NESTED_ARRAY => NlaType::NestedArray, NL_ATTR_TYPE_BITFIELD32 => NlaType::Bitfield32, _ => return Err(DecodeError::from(format!("invalid NLA type: {}", value))), }) } } // FIXME: Add this into netlink_packet_utils::parser fn parse_i64(payload: &[u8]) -> Result { if payload.len() != size_of::() { return Err(format!("invalid i64: {:?}", payload).into()); } Ok(NativeEndian::read_i64(payload)) } ================================================ FILE: netlink-packet-generic/src/header.rs ================================================ // SPDX-License-Identifier: MIT //! header definition of generic netlink packet use crate::{buffer::GenlBuffer, constants::GENL_HDRLEN}; use netlink_packet_core::DecodeError; use netlink_packet_utils::{Emitable, Parseable}; /// Generic Netlink header #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct GenlHeader { pub cmd: u8, pub version: u8, } impl Emitable for GenlHeader { fn buffer_len(&self) -> usize { GENL_HDRLEN } fn emit(&self, buffer: &mut [u8]) { let mut packet = GenlBuffer::new(buffer); packet.set_cmd(self.cmd); packet.set_version(self.version); } } impl> Parseable> for GenlHeader { fn parse(buf: &GenlBuffer) -> Result { Ok(Self { cmd: buf.cmd(), version: buf.version(), }) } } ================================================ FILE: netlink-packet-generic/src/lib.rs ================================================ // SPDX-License-Identifier: MIT //! This crate provides the packet of generic netlink family and its controller. //! //! The `[GenlMessage]` provides a generic netlink family message which is //! sub-protocol independant. //! You can wrap your message into the type, then it can be used in `netlink-proto` crate. //! //! # Implementing a generic netlink family //! A generic netlink family contains several commands, and a version number in //! the header. //! //! The payload usually consists of netlink attributes, carrying the messages to //! the peer. In order to help you to make your payload into a valid netlink //! packet, this crate requires the informations about the family id, //! and the informations in the generic header. So, you need to implement some //! traits on your types. //! //! All the things in the payload including all netlink attributes used //! and the optional header should be handled by your implementation. //! //! ## Serializaion / Deserialization //! To implement your generic netlink family, you should handle the payload //! serialization process including its specific header (if any) and the netlink //! attributes. //! //! To achieve this, you should implement [`netlink_packet_utils::Emitable`] //! trait for the payload type. //! //! For deserialization, [`netlink_packet_utils::ParseableParametrized<[u8], GenlHeader>`](netlink_packet_utils::ParseableParametrized) //! trait should be implemented. As mention above, to provide more scalability, //! we use the simplest buffer type: `[u8]` here. You can turn it into other //! buffer type easily during deserializing. //! //! ## `GenlFamily` trait //! The trait is aim to provide some necessary informations in order to build //! the packet headers of netlink (nlmsghdr) and generic netlink (genlmsghdr). //! //! ### `family_name()` //! The method let the resolver to obtain the name registered in the kernel. //! //! ### `family_id()` //! Few netlink family has static family ID (e.g. controller). The method is //! mainly used to let those family to return their familt ID. //! //! If you don't know what is this, please **DO NOT** implement this method. //! Since the default implementation return `GENL_ID_GENERATE`, which means //! the family ID is allocated by the kernel dynamically. //! //! ### `command()` //! This method tells the generic netlink command id of the packet //! The return value is used to fill the `cmd` field in the generic netlink header. //! //! ### `version()` //! This method return the family version of the payload. //! The return value is used to fill the `version` field in the generic netlink header. //! //! ## Family Header //! Few family would use a family specific message header. For simplification //! and scalability, this crate treats it as a part of the payload, and make //! implementations to handle the header by themselves. //! //! If you are implementing such a generic family, note that you should define //! the header data structure in your payload type and handle the serialization. #[macro_use] extern crate netlink_packet_utils; pub mod buffer; pub use self::buffer::GenlBuffer; pub mod constants; pub mod ctrl; pub mod header; pub use self::header::GenlHeader; pub mod message; pub use self::message::GenlMessage; pub mod traits; pub use self::traits::GenlFamily; ================================================ FILE: netlink-packet-generic/src/message.rs ================================================ // SPDX-License-Identifier: MIT //! Message definition and method implementations use crate::{buffer::GenlBuffer, header::GenlHeader, traits::*}; use netlink_packet_core::{ DecodeError, NetlinkDeserializable, NetlinkHeader, NetlinkPayload, NetlinkSerializable, }; use netlink_packet_utils::{Emitable, ParseableParametrized}; use std::fmt::Debug; #[cfg(doc)] use netlink_packet_core::NetlinkMessage; /// Represent the generic netlink messages /// /// This type can wrap data types `F` which represents a generic family payload. /// The message can be serialize/deserialize if the type `F` implements [`GenlFamily`], /// [`Emitable`], and [`ParseableParametrized<[u8], GenlHeader>`](ParseableParametrized). #[derive(Clone, Debug, PartialEq, Eq)] pub struct GenlMessage { pub header: GenlHeader, pub payload: F, resolved_family_id: u16, } impl GenlMessage where F: Debug, { /// Construct the message pub fn new(header: GenlHeader, payload: F, family_id: u16) -> Self { Self { header, payload, resolved_family_id: family_id, } } /// Construct the message by the given header and payload pub fn from_parts(header: GenlHeader, payload: F) -> Self { Self { header, payload, resolved_family_id: 0, } } /// Consume this message and return its header and payload pub fn into_parts(self) -> (GenlHeader, F) { (self.header, self.payload) } /// Return the previously set resolved family ID in this message. /// /// This value would be used to serialize the message only if /// the ([`GenlFamily::family_id()`]) return 0 in the underlying type. pub fn resolved_family_id(&self) -> u16 { self.resolved_family_id } /// Set the resolved dynamic family ID of the message, if the generic family /// uses dynamic generated ID by kernel. /// /// This method is a interface to provide other high level library to /// set the resolved family ID before the message is serialized. /// /// # Usage /// Normally, you don't have to call this function directly if you are /// using library which helps you handle the dynamic family id. /// /// If you are the developer of some high level generic netlink library, /// you can call this method to set the family id resolved by your resolver. /// Without having to modify the `message_type` field of the serialized /// netlink packet header before sending it. pub fn set_resolved_family_id(&mut self, family_id: u16) { self.resolved_family_id = family_id; } } impl GenlMessage where F: GenlFamily + Debug, { /// Build the message from the payload /// /// This function would automatically fill the header for you. You can directly emit /// the message without having to call [`finalize()`](Self::finalize). pub fn from_payload(payload: F) -> Self { Self { header: GenlHeader { cmd: payload.command(), version: payload.version(), }, payload, resolved_family_id: 0, } } /// Ensure the header ([`GenlHeader`]) is consistent with the payload (`F: GenlFamily`): /// /// - Fill the command and version number into the header /// /// If you are not 100% sure the header is correct, this method should be called before calling /// [`Emitable::emit()`], as it could get error result if the header is inconsistent with the message. pub fn finalize(&mut self) { self.header.cmd = self.payload.command(); self.header.version = self.payload.version(); } /// Return the resolved family ID which should be filled into the `message_type` /// field in [`NetlinkHeader`]. /// /// The implementation of [`NetlinkSerializable::message_type()`] would use /// this function's result as its the return value. Thus, the family id can /// be automatically filled into the `message_type` during the call to /// [`NetlinkMessage::finalize()`]. pub fn family_id(&self) -> u16 { let static_id = self.payload.family_id(); if static_id == 0 { self.resolved_family_id } else { static_id } } } impl Emitable for GenlMessage where F: GenlFamily + Emitable + Debug, { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.payload.buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); let buffer = &mut buffer[self.header.buffer_len()..]; self.payload.emit(buffer); } } impl NetlinkSerializable for GenlMessage where F: GenlFamily + Emitable + Debug, { fn message_type(&self) -> u16 { self.family_id() } fn buffer_len(&self) -> usize { ::buffer_len(self) } fn serialize(&self, buffer: &mut [u8]) { self.emit(buffer) } } impl NetlinkDeserializable for GenlMessage where F: ParseableParametrized<[u8], GenlHeader> + Debug, { type Error = DecodeError; fn deserialize(header: &NetlinkHeader, payload: &[u8]) -> Result { let buffer = GenlBuffer::new_checked(payload)?; GenlMessage::parse_with_param(&buffer, header.message_type) } } impl From> for NetlinkPayload> where F: Debug, { fn from(message: GenlMessage) -> Self { NetlinkPayload::InnerMessage(message) } } ================================================ FILE: netlink-packet-generic/src/traits.rs ================================================ // SPDX-License-Identifier: MIT //! Traits for implementing generic netlink family /// Provide the definition for generic netlink family /// /// Family payload type should implement this trait to provide necessary /// informations in order to build the packet headers (`nlmsghdr` and `genlmsghdr`). /// /// If you are looking for an example implementation, you can refer to the /// [`crate::ctrl`] module. pub trait GenlFamily { /// Return the unique family name registered in the kernel /// /// Let the resolver lookup the dynamically assigned ID fn family_name() -> &'static str; /// Return the assigned family ID /// /// # Note /// The implementation of generic family should assign the ID to `GENL_ID_GENERATE` (0x0). /// So the controller can dynamically assign the family ID. /// /// Regarding to the reason above, you should not have to implement the function /// unless the family uses static ID. fn family_id(&self) -> u16 { 0 } /// Return the command type of the current message fn command(&self) -> u8; /// Indicate the protocol version fn version(&self) -> u8; } ================================================ FILE: netlink-packet-generic/tests/query_family_id.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_core::{NetlinkMessage, NetlinkPayload, NLM_F_REQUEST}; use netlink_packet_generic::{ ctrl::{nlas::GenlCtrlAttrs, GenlCtrl, GenlCtrlCmd}, GenlMessage, }; use netlink_sys::{protocols::NETLINK_GENERIC, Socket, SocketAddr}; #[test] fn query_family_id() { let mut socket = Socket::new(NETLINK_GENERIC).unwrap(); socket.bind_auto().unwrap(); socket.connect(&SocketAddr::new(0, 0)).unwrap(); let mut genlmsg = GenlMessage::from_payload(GenlCtrl { cmd: GenlCtrlCmd::GetFamily, nlas: vec![GenlCtrlAttrs::FamilyName("nlctrl".to_owned())], }); genlmsg.finalize(); let mut nlmsg = NetlinkMessage::from(genlmsg); nlmsg.header.flags = NLM_F_REQUEST; nlmsg.finalize(); println!("Buffer length: {}", nlmsg.buffer_len()); let mut txbuf = vec![0u8; nlmsg.buffer_len()]; nlmsg.serialize(&mut txbuf); socket.send(&txbuf, 0).unwrap(); let (rxbuf, _addr) = socket.recv_from_full().unwrap(); let rx_packet = >>::deserialize(&rxbuf).unwrap(); if let NetlinkPayload::InnerMessage(genlmsg) = rx_packet.payload { if GenlCtrlCmd::NewFamily == genlmsg.payload.cmd { let family_id = genlmsg .payload .nlas .iter() .find_map(|nla| { if let GenlCtrlAttrs::FamilyId(id) = nla { Some(*id) } else { None } }) .expect("Cannot find FamilyId attribute"); // nlctrl's family must be 0x10 assert_eq!(0x10, family_id); } else { panic!("Invalid payload type: {:?}", genlmsg.payload.cmd); } } else { panic!("Failed to get family ID"); } } ================================================ FILE: netlink-packet-netfilter/Cargo.toml ================================================ [package] authors = ["Loïc Damien "] name = "netlink-packet-netfilter" version = "0.1.0" edition = "2018" homepage = "https://github.com/little-dude/netlink" keywords = ["netlink", "linux", "netfilter"] license = "MIT" readme = "../README.md" repository = "https://github.com/little-dude/netlink" description = "netlink packet types for the netfilter subprotocol" [dependencies] anyhow = "1.0.32" byteorder = "1.3.4" netlink-packet-core = { version = "0.4.2", path = "../netlink-packet-core" } netlink-packet-utils = { version = "0.5.1", path = "../netlink-packet-utils" } bitflags = "1.2.1" libc = "0.2.77" derive_more = "0.99.16" [dev-dependencies] netlink-sys = { version = "0.8.3", path = "../netlink-sys" } ================================================ FILE: netlink-packet-netfilter/examples/nflog.rs ================================================ // SPDX-License-Identifier: MIT // To run this example: // 1) create a iptables/nft rules that send packet with group 1, for example: // sudo iptables -A INPUT -j NFLOG --nflog-group 1 // 2) build the example: // cargo build --example nflog // 3) run it as root: // sudo ../target/debug/examples/nflog use std::{net::Ipv4Addr, time::Duration}; use byteorder::{ByteOrder, NetworkEndian}; use netlink_packet_netfilter::{ constants::*, nflog::{ config_request, nlas::{ config::{ConfigCmd, ConfigFlags, ConfigMode, Timeout}, packet::PacketNla, }, NfLogMessage, }, nl::{NetlinkMessage, NetlinkPayload}, NetfilterMessage, NetfilterMessageInner, }; use netlink_sys::{constants::NETLINK_NETFILTER, Socket}; fn get_packet_nlas(message: &NetlinkMessage) -> &[PacketNla] { if let NetlinkPayload::InnerMessage(NetfilterMessage { inner: NetfilterMessageInner::NfLog(NfLogMessage::Packet(nlas)), .. }) = &message.payload { nlas } else { &[] } } fn main() { let mut receive_buffer = vec![0; 4096]; // First, we bind the socket let mut socket = Socket::new(NETLINK_NETFILTER).unwrap(); socket.bind_auto().unwrap(); // Then we issue the PfBind command let packet = config_request(AF_INET, 0, vec![ConfigCmd::PfBind.into()]); let mut buf = vec![0; packet.header.length as usize]; packet.serialize(&mut buf[..]); println!(">>> {:?}", packet); socket.send(&buf[..], 0).unwrap(); // And check there is no error let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); let bytes = &receive_buffer[..size]; let rx_packet = >::deserialize(bytes).unwrap(); println!("<<< {:?}", rx_packet); assert!(matches!(rx_packet.payload, NetlinkPayload::Ack(_))); // After that we issue a Bind command, to start receiving packets. We can also set various parameters at the same time let timeout: Timeout = Duration::from_millis(100).into(); let packet = config_request( AF_INET, 1, vec![ ConfigCmd::Bind.into(), ConfigFlags::SEQ_GLOBAL.into(), ConfigMode::PACKET_MAX.into(), timeout.into(), ], ); let mut buf = vec![0; packet.header.length as usize]; packet.serialize(&mut buf[..]); println!(">>> {:?}", packet); socket.send(&buf[..], 0).unwrap(); let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); let bytes = &receive_buffer[..size]; let rx_packet = >::deserialize(bytes).unwrap(); println!("<<< {:?}", rx_packet); assert!(matches!(rx_packet.payload, NetlinkPayload::Ack(_))); // And now we can receive the packets loop { match socket.recv(&mut &mut receive_buffer[..], 0) { Ok(size) => { let mut offset = 0; loop { let bytes = &receive_buffer[offset..]; let rx_packet = >::deserialize(bytes).unwrap(); for nla in get_packet_nlas(&rx_packet) { if let PacketNla::Payload(payload) = nla { let src = Ipv4Addr::from(NetworkEndian::read_u32(&payload[12..])); let dst = Ipv4Addr::from(NetworkEndian::read_u32(&payload[16..])); println!("Packet from {} to {}", src, dst); break; } } offset += rx_packet.header.length as usize; if offset == size || rx_packet.header.length == 0 { break; } } } Err(e) => { println!("error while receiving packets: {:?}", e); break; } } } } ================================================ FILE: netlink-packet-netfilter/src/buffer.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ message::{NetfilterHeader, NetfilterMessage, NetfilterMessageInner, NETFILTER_HEADER_LEN}, nflog::NfLogMessage, traits::{Parseable, ParseableParametrized}, DecodeError, }; use anyhow::Context; use netlink_packet_utils::{ buffer, nla::{DefaultNla, NlaBuffer, NlasIterator}, }; buffer!(NetfilterBuffer(NETFILTER_HEADER_LEN) { header: (slice, ..NETFILTER_HEADER_LEN), payload: (slice, NETFILTER_HEADER_LEN..), }); impl<'a, T: AsRef<[u8]> + ?Sized> NetfilterBuffer<&'a T> { pub fn nlas(&self) -> impl Iterator, DecodeError>> { NlasIterator::new(self.payload()) } pub fn parse_all_nlas(&self, f: F) -> Result, DecodeError> where F: Fn(NlaBuffer<&[u8]>) -> Result, { Ok(self .nlas() .map(|buf| f(buf?)) .collect::, _>>() .context("failed to parse NLAs")?) } pub fn default_nlas(&self) -> Result, DecodeError> { self.parse_all_nlas(|buf| DefaultNla::parse(&buf)) } } impl<'a, T: AsRef<[u8]> + ?Sized> ParseableParametrized, u16> for NetfilterMessage { fn parse_with_param( buf: &NetfilterBuffer<&'a T>, message_type: u16, ) -> Result { let header_buf = crate::message::NetfilterHeaderBuffer::new(buf.inner()); let header = NetfilterHeader::parse(&header_buf).context("failed to parse netfilter header")?; let subsys = (message_type >> 8) as u8; let message_type = message_type as u8; let inner = match subsys { NfLogMessage::SUBSYS => NetfilterMessageInner::NfLog( NfLogMessage::parse_with_param(buf, message_type) .context("failed to parse nflog payload")?, ), _ => NetfilterMessageInner::Other { subsys, message_type, nlas: buf.default_nlas()?, }, }; Ok(NetfilterMessage::new(header, inner)) } } ================================================ FILE: netlink-packet-netfilter/src/constants.rs ================================================ // SPDX-License-Identifier: MIT pub use netlink_packet_core::constants::*; pub const AF_UNSPEC: u8 = libc::AF_UNSPEC as u8; pub const AF_UNIX: u8 = libc::AF_UNIX as u8; pub const AF_LOCAL: u8 = libc::AF_LOCAL as u8; pub const AF_INET: u8 = libc::AF_INET as u8; pub const AF_AX25: u8 = libc::AF_AX25 as u8; pub const AF_IPX: u8 = libc::AF_IPX as u8; pub const AF_APPLETALK: u8 = libc::AF_APPLETALK as u8; pub const AF_NETROM: u8 = libc::AF_NETROM as u8; pub const AF_BRIDGE: u8 = libc::AF_BRIDGE as u8; pub const AF_ATMPVC: u8 = libc::AF_ATMPVC as u8; pub const AF_X25: u8 = libc::AF_X25 as u8; pub const AF_INET6: u8 = libc::AF_INET6 as u8; pub const AF_ROSE: u8 = libc::AF_ROSE as u8; pub const AF_DECNET: u8 = libc::AF_DECnet as u8; pub const AF_NETBEUI: u8 = libc::AF_NETBEUI as u8; pub const AF_SECURITY: u8 = libc::AF_SECURITY as u8; pub const AF_KEY: u8 = libc::AF_KEY as u8; pub const AF_NETLINK: u8 = libc::AF_NETLINK as u8; pub const AF_ROUTE: u8 = libc::AF_ROUTE as u8; pub const AF_PACKET: u8 = libc::AF_PACKET as u8; pub const AF_ASH: u8 = libc::AF_ASH as u8; pub const AF_ECONET: u8 = libc::AF_ECONET as u8; pub const AF_ATMSVC: u8 = libc::AF_ATMSVC as u8; pub const AF_RDS: u8 = libc::AF_RDS as u8; pub const AF_SNA: u8 = libc::AF_SNA as u8; pub const AF_IRDA: u8 = libc::AF_IRDA as u8; pub const AF_PPPOX: u8 = libc::AF_PPPOX as u8; pub const AF_WANPIPE: u8 = libc::AF_WANPIPE as u8; pub const AF_LLC: u8 = libc::AF_LLC as u8; pub const AF_CAN: u8 = libc::AF_CAN as u8; pub const AF_TIPC: u8 = libc::AF_TIPC as u8; pub const AF_BLUETOOTH: u8 = libc::AF_BLUETOOTH as u8; pub const AF_IUCV: u8 = libc::AF_IUCV as u8; pub const AF_RXRPC: u8 = libc::AF_RXRPC as u8; pub const AF_ISDN: u8 = libc::AF_ISDN as u8; pub const AF_PHONET: u8 = libc::AF_PHONET as u8; pub const AF_IEEE802154: u8 = libc::AF_IEEE802154 as u8; pub const AF_CAIF: u8 = libc::AF_CAIF as u8; pub const AF_ALG: u8 = libc::AF_ALG as u8; pub const NFNETLINK_V0: u8 = libc::NFNETLINK_V0 as u8; pub const NFNL_SUBSYS_NONE: u8 = libc::NFNL_SUBSYS_NONE as u8; pub const NFNL_SUBSYS_CTNETLINK: u8 = libc::NFNL_SUBSYS_CTNETLINK as u8; pub const NFNL_SUBSYS_CTNETLINK_EXP: u8 = libc::NFNL_SUBSYS_CTNETLINK_EXP as u8; pub const NFNL_SUBSYS_QUEUE: u8 = libc::NFNL_SUBSYS_QUEUE as u8; pub const NFNL_SUBSYS_ULOG: u8 = libc::NFNL_SUBSYS_ULOG as u8; pub const NFNL_SUBSYS_OSF: u8 = libc::NFNL_SUBSYS_OSF as u8; pub const NFNL_SUBSYS_IPSET: u8 = libc::NFNL_SUBSYS_IPSET as u8; pub const NFNL_SUBSYS_ACCT: u8 = libc::NFNL_SUBSYS_ACCT as u8; pub const NFNL_SUBSYS_CTNETLINK_TIMEOUT: u8 = libc::NFNL_SUBSYS_CTNETLINK_TIMEOUT as u8; pub const NFNL_SUBSYS_CTHELPER: u8 = libc::NFNL_SUBSYS_CTHELPER as u8; pub const NFNL_SUBSYS_NFTABLES: u8 = libc::NFNL_SUBSYS_NFTABLES as u8; pub const NFNL_SUBSYS_NFT_COMPAT: u8 = libc::NFNL_SUBSYS_NFT_COMPAT as u8; pub const NFULA_CFG_CMD: u16 = libc::NFULA_CFG_CMD as u16; pub const NFULA_CFG_MODE: u16 = libc::NFULA_CFG_MODE as u16; pub const NFULA_CFG_NLBUFSIZ: u16 = libc::NFULA_CFG_NLBUFSIZ as u16; pub const NFULA_CFG_TIMEOUT: u16 = libc::NFULA_CFG_TIMEOUT as u16; pub const NFULA_CFG_QTHRESH: u16 = libc::NFULA_CFG_QTHRESH as u16; pub const NFULA_CFG_FLAGS: u16 = libc::NFULA_CFG_FLAGS as u16; pub const NLBUFSIZ_MAX: u32 = 131072; pub const NFULA_PACKET_HDR: u16 = libc::NFULA_PACKET_HDR as u16; pub const NFULA_MARK: u16 = libc::NFULA_MARK as u16; pub const NFULA_TIMESTAMP: u16 = libc::NFULA_TIMESTAMP as u16; pub const NFULA_IFINDEX_INDEV: u16 = libc::NFULA_IFINDEX_INDEV as u16; pub const NFULA_IFINDEX_OUTDEV: u16 = libc::NFULA_IFINDEX_OUTDEV as u16; pub const NFULA_IFINDEX_PHYSINDEV: u16 = libc::NFULA_IFINDEX_PHYSINDEV as u16; pub const NFULA_IFINDEX_PHYSOUTDEV: u16 = libc::NFULA_IFINDEX_PHYSOUTDEV as u16; pub const NFULA_HWADDR: u16 = libc::NFULA_HWADDR as u16; pub const NFULA_PAYLOAD: u16 = libc::NFULA_PAYLOAD as u16; pub const NFULA_PREFIX: u16 = libc::NFULA_PREFIX as u16; pub const NFULA_UID: u16 = libc::NFULA_UID as u16; pub const NFULA_SEQ: u16 = libc::NFULA_SEQ as u16; pub const NFULA_SEQ_GLOBAL: u16 = libc::NFULA_SEQ_GLOBAL as u16; pub const NFULA_GID: u16 = libc::NFULA_GID as u16; pub const NFULA_HWTYPE: u16 = libc::NFULA_HWTYPE as u16; pub const NFULA_HWHEADER: u16 = libc::NFULA_HWHEADER as u16; pub const NFULA_HWLEN: u16 = libc::NFULA_HWLEN as u16; pub const NFULA_CT: u16 = libc::NFULA_CT as u16; pub const NFULA_CT_INFO: u16 = libc::NFULA_CT_INFO as u16; pub const NFULNL_MSG_CONFIG: u8 = libc::NFULNL_MSG_CONFIG as u8; pub const NFULNL_MSG_PACKET: u8 = libc::NFULNL_MSG_PACKET as u8; ================================================ FILE: netlink-packet-netfilter/src/lib.rs ================================================ // SPDX-License-Identifier: MIT pub extern crate netlink_packet_core as nl; pub(crate) extern crate netlink_packet_utils as utils; pub use self::utils::{nla, traits, DecodeError}; pub(crate) mod buffer; pub mod constants; mod message; pub use message::{NetfilterHeader, NetfilterMessage, NetfilterMessageInner}; pub mod nflog; ================================================ FILE: netlink-packet-netfilter/src/message.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_core::{ DecodeError, NetlinkDeserializable, NetlinkHeader, NetlinkPayload, NetlinkSerializable, }; use netlink_packet_utils::{buffer, nla::DefaultNla, Emitable, Parseable, ParseableParametrized}; use crate::{buffer::NetfilterBuffer, nflog::NfLogMessage}; pub const NETFILTER_HEADER_LEN: usize = 4; buffer!(NetfilterHeaderBuffer(NETFILTER_HEADER_LEN) { family: (u8, 0), version: (u8, 1), res_id: (u16, 2..4), }); #[derive(Clone, Debug, PartialEq, Eq)] pub struct NetfilterHeader { pub family: u8, pub version: u8, pub res_id: u16, } impl NetfilterHeader { pub fn new(family: u8, version: u8, res_id: u16) -> Self { Self { family, version, res_id, } } } impl Emitable for NetfilterHeader { fn buffer_len(&self) -> usize { NETFILTER_HEADER_LEN } fn emit(&self, buf: &mut [u8]) { let mut buf = NetfilterHeaderBuffer::new(buf); buf.set_family(self.family); buf.set_version(self.version); buf.set_res_id(self.res_id.to_be()); } } impl> Parseable> for NetfilterHeader { fn parse(buf: &NetfilterHeaderBuffer) -> Result { buf.check_buffer_length()?; Ok(NetfilterHeader { family: buf.family(), version: buf.version(), res_id: u16::from_be(buf.res_id()), }) } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum NetfilterMessageInner { NfLog(NfLogMessage), Other { subsys: u8, message_type: u8, nlas: Vec, }, } impl From for NetfilterMessageInner { fn from(message: NfLogMessage) -> Self { Self::NfLog(message) } } impl Emitable for NetfilterMessageInner { fn buffer_len(&self) -> usize { match self { NetfilterMessageInner::NfLog(message) => message.buffer_len(), NetfilterMessageInner::Other { nlas, .. } => nlas.as_slice().buffer_len(), } } fn emit(&self, buffer: &mut [u8]) { match self { NetfilterMessageInner::NfLog(message) => message.emit(buffer), NetfilterMessageInner::Other { nlas, .. } => nlas.as_slice().emit(buffer), } } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct NetfilterMessage { pub header: NetfilterHeader, pub inner: NetfilterMessageInner, } impl NetfilterMessage { pub fn new>(header: NetfilterHeader, inner: T) -> Self { Self { header, inner: inner.into(), } } pub fn subsys(&self) -> u8 { match self.inner { NetfilterMessageInner::NfLog(_) => NfLogMessage::SUBSYS, NetfilterMessageInner::Other { subsys, .. } => subsys, } } pub fn message_type(&self) -> u8 { match self.inner { NetfilterMessageInner::NfLog(ref message) => message.message_type(), NetfilterMessageInner::Other { message_type, .. } => message_type, } } } impl Emitable for NetfilterMessage { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.inner.buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); self.inner.emit(&mut buffer[self.header.buffer_len()..]); } } impl NetlinkSerializable for NetfilterMessage { fn message_type(&self) -> u16 { ((self.subsys() as u16) << 8) | self.message_type() as u16 } fn buffer_len(&self) -> usize { ::buffer_len(self) } fn serialize(&self, buffer: &mut [u8]) { self.emit(buffer) } } impl NetlinkDeserializable for NetfilterMessage { type Error = DecodeError; fn deserialize(header: &NetlinkHeader, payload: &[u8]) -> Result { match NetfilterBuffer::new_checked(payload) { Err(e) => Err(e), Ok(buffer) => match NetfilterMessage::parse_with_param(&buffer, header.message_type) { Err(e) => Err(e), Ok(message) => Ok(message), }, } } } impl From for NetlinkPayload { fn from(message: NetfilterMessage) -> Self { NetlinkPayload::InnerMessage(message) } } ================================================ FILE: netlink-packet-netfilter/src/nflog/message.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ buffer::NetfilterBuffer, constants::{NFNL_SUBSYS_ULOG, NFULNL_MSG_CONFIG, NFULNL_MSG_PACKET}, nflog::nlas::{config::ConfigNla, packet::PacketNla}, nla::DefaultNla, traits::{Emitable, Parseable, ParseableParametrized}, DecodeError, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum NfLogMessage { Config(Vec), Packet(Vec), Other { message_type: u8, nlas: Vec, }, } impl NfLogMessage { pub const SUBSYS: u8 = NFNL_SUBSYS_ULOG; pub fn message_type(&self) -> u8 { match self { NfLogMessage::Config(_) => NFULNL_MSG_CONFIG, NfLogMessage::Packet(_) => NFULNL_MSG_PACKET, NfLogMessage::Other { message_type, .. } => *message_type, } } } impl Emitable for NfLogMessage { fn buffer_len(&self) -> usize { match self { NfLogMessage::Config(nlas) => nlas.as_slice().buffer_len(), NfLogMessage::Packet(nlas) => nlas.as_slice().buffer_len(), NfLogMessage::Other { nlas, .. } => nlas.as_slice().buffer_len(), } } fn emit(&self, buffer: &mut [u8]) { match self { NfLogMessage::Config(nlas) => nlas.as_slice().emit(buffer), NfLogMessage::Packet(nlas) => nlas.as_slice().emit(buffer), NfLogMessage::Other { nlas, .. } => nlas.as_slice().emit(buffer), }; } } impl<'a, T: AsRef<[u8]> + ?Sized> ParseableParametrized, u8> for NfLogMessage { fn parse_with_param( buf: &NetfilterBuffer<&'a T>, message_type: u8, ) -> Result { Ok(match message_type { NFULNL_MSG_CONFIG => { let nlas = buf.parse_all_nlas(|nla_buf| ConfigNla::parse(&nla_buf))?; NfLogMessage::Config(nlas) } NFULNL_MSG_PACKET => { let nlas = buf.parse_all_nlas(|nla_buf| PacketNla::parse(&nla_buf))?; NfLogMessage::Packet(nlas) } _ => NfLogMessage::Other { message_type, nlas: buf.default_nlas()?, }, }) } } ================================================ FILE: netlink-packet-netfilter/src/nflog/mod.rs ================================================ // SPDX-License-Identifier: MIT mod message; pub use message::NfLogMessage; pub mod nlas; use crate::{ constants::NFNETLINK_V0, nflog::nlas::config::ConfigNla, nl::{NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_REQUEST}, NetfilterHeader, NetfilterMessage, }; pub fn config_request( family: u8, group_num: u16, nlas: Vec, ) -> NetlinkMessage { let mut message = NetlinkMessage { header: NetlinkHeader { flags: NLM_F_REQUEST | NLM_F_ACK, ..Default::default() }, payload: NetlinkPayload::from(NetfilterMessage::new( NetfilterHeader::new(family, NFNETLINK_V0, group_num), NfLogMessage::Config(nlas), )), }; message.finalize(); message } ================================================ FILE: netlink-packet-netfilter/src/nflog/nlas/config/config_cmd.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_utils::nla::Nla; const NFULA_CFG_CMD: u16 = libc::NFULA_CFG_CMD as u16; const NFULNL_CFG_CMD_NONE: u8 = libc::NFULNL_CFG_CMD_NONE as u8; const NFULNL_CFG_CMD_BIND: u8 = libc::NFULNL_CFG_CMD_BIND as u8; const NFULNL_CFG_CMD_UNBIND: u8 = libc::NFULNL_CFG_CMD_UNBIND as u8; const NFULNL_CFG_CMD_PF_BIND: u8 = libc::NFULNL_CFG_CMD_PF_BIND as u8; const NFULNL_CFG_CMD_PF_UNBIND: u8 = libc::NFULNL_CFG_CMD_PF_UNBIND as u8; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ConfigCmd { None, Bind, Unbind, PfBind, PfUnbind, Other(u8), } impl From for u8 { fn from(cmd: ConfigCmd) -> Self { match cmd { ConfigCmd::None => NFULNL_CFG_CMD_NONE, ConfigCmd::Bind => NFULNL_CFG_CMD_BIND, ConfigCmd::Unbind => NFULNL_CFG_CMD_UNBIND, ConfigCmd::PfBind => NFULNL_CFG_CMD_PF_BIND, ConfigCmd::PfUnbind => NFULNL_CFG_CMD_PF_UNBIND, ConfigCmd::Other(cmd) => cmd, } } } impl From for ConfigCmd { fn from(cmd: u8) -> Self { match cmd { NFULNL_CFG_CMD_NONE => ConfigCmd::None, NFULNL_CFG_CMD_BIND => ConfigCmd::Bind, NFULNL_CFG_CMD_UNBIND => ConfigCmd::Unbind, NFULNL_CFG_CMD_PF_BIND => ConfigCmd::PfBind, NFULNL_CFG_CMD_PF_UNBIND => ConfigCmd::PfUnbind, cmd => ConfigCmd::Other(cmd), } } } impl Nla for ConfigCmd { fn value_len(&self) -> usize { 1 } fn kind(&self) -> u16 { NFULA_CFG_CMD } fn emit_value(&self, buffer: &mut [u8]) { buffer[0] = (*self).into(); } } ================================================ FILE: netlink-packet-netfilter/src/nflog/nlas/config/config_flags.rs ================================================ // SPDX-License-Identifier: MIT use std::mem::size_of; use bitflags::bitflags; use byteorder::{BigEndian, ByteOrder}; use netlink_packet_utils::nla::Nla; const NFULA_CFG_FLAGS: u16 = libc::NFULA_CFG_FLAGS as u16; bitflags! { pub struct ConfigFlags: u16 { const SEQ = libc:: NFULNL_CFG_F_SEQ as u16; const SEQ_GLOBAL = libc:: NFULNL_CFG_F_SEQ_GLOBAL as u16; const CONNTRACK = libc:: NFULNL_CFG_F_CONNTRACK as u16; } } // see https://github.com/bitflags/bitflags/issues/263 impl ConfigFlags { pub fn from_bits_preserve(bits: u16) -> Self { ConfigFlags { bits } } } impl Nla for ConfigFlags { fn value_len(&self) -> usize { size_of::() } fn kind(&self) -> u16 { NFULA_CFG_FLAGS } fn emit_value(&self, buffer: &mut [u8]) { BigEndian::write_u16(buffer, self.bits); } } ================================================ FILE: netlink-packet-netfilter/src/nflog/nlas/config/config_mode.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_utils::{buffer, errors::DecodeError, nla::Nla, Parseable}; const NFULA_CFG_MODE: u16 = libc::NFULA_CFG_MODE as u16; const NFULNL_COPY_NONE: u8 = libc::NFULNL_COPY_NONE as u8; const NFULNL_COPY_META: u8 = libc::NFULNL_COPY_META as u8; const NFULNL_COPY_PACKET: u8 = libc::NFULNL_COPY_PACKET as u8; const CONFIG_MODE_LEN: usize = 6; buffer!(ConfigModeBuffer(CONFIG_MODE_LEN) { copy_range: (u32, 0..4), copy_mode: (u8, 4), }); #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CopyMode { None, Meta, Packet, Other(u8), } impl From for u8 { fn from(cmd: CopyMode) -> Self { match cmd { CopyMode::None => NFULNL_COPY_NONE, CopyMode::Meta => NFULNL_COPY_META, CopyMode::Packet => NFULNL_COPY_PACKET, CopyMode::Other(cmd) => cmd, } } } impl From for CopyMode { fn from(cmd: u8) -> Self { match cmd { NFULNL_COPY_NONE => CopyMode::None, NFULNL_COPY_META => CopyMode::Meta, NFULNL_COPY_PACKET => CopyMode::Packet, cmd => CopyMode::Other(cmd), } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ConfigMode { copy_range: u32, copy_mode: CopyMode, } impl ConfigMode { pub const NONE: Self = Self { copy_range: 0, copy_mode: CopyMode::None, }; pub const META: Self = Self { copy_range: 0, copy_mode: CopyMode::Meta, }; pub const PACKET_MAX: Self = Self { copy_range: 0, copy_mode: CopyMode::Packet, }; pub fn new(copy_range: u32, copy_mode: CopyMode) -> Self { Self { copy_range, copy_mode, } } pub fn new_packet(copy_range: u32) -> Self { Self::new(copy_range, CopyMode::Packet) } } impl Nla for ConfigMode { fn value_len(&self) -> usize { CONFIG_MODE_LEN } fn kind(&self) -> u16 { NFULA_CFG_MODE } fn emit_value(&self, buf: &mut [u8]) { let mut buf = ConfigModeBuffer::new(buf); buf.set_copy_range(self.copy_range.to_be()); buf.set_copy_mode(self.copy_mode.into()) } } impl> Parseable> for ConfigMode { fn parse(buf: &ConfigModeBuffer) -> Result { Ok(ConfigMode { copy_range: u32::from_be(buf.copy_range()), copy_mode: buf.copy_mode().into(), }) } } ================================================ FILE: netlink-packet-netfilter/src/nflog/nlas/config/mod.rs ================================================ // SPDX-License-Identifier: MIT mod config_cmd; mod config_flags; mod config_mode; mod nla; mod timeout; pub use config_cmd::ConfigCmd; pub use config_flags::ConfigFlags; pub use config_mode::{ConfigMode, CopyMode}; pub use nla::ConfigNla; pub use timeout::Timeout; ================================================ FILE: netlink-packet-netfilter/src/nflog/nlas/config/nla.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use byteorder::{BigEndian, ByteOrder}; use derive_more::{From, IsVariant}; use crate::{ constants::{ NFULA_CFG_CMD, NFULA_CFG_FLAGS, NFULA_CFG_MODE, NFULA_CFG_NLBUFSIZ, NFULA_CFG_QTHRESH, NFULA_CFG_TIMEOUT, }, nflog::nlas::config::{ config_mode::ConfigModeBuffer, ConfigCmd, ConfigFlags, ConfigMode, Timeout, }, nl::DecodeError, nla::{DefaultNla, Nla, NlaBuffer}, traits::Parseable, utils::parsers::{parse_u16_be, parse_u32_be, parse_u8}, }; #[derive(Clone, Debug, PartialEq, Eq, From, IsVariant)] pub enum ConfigNla { Cmd(ConfigCmd), Mode(ConfigMode), #[from(ignore)] NlBufSiz(u32), Timeout(Timeout), #[from(ignore)] QThresh(u32), Flags(ConfigFlags), Other(DefaultNla), } impl Nla for ConfigNla { fn value_len(&self) -> usize { match self { ConfigNla::Cmd(attr) => attr.value_len(), ConfigNla::Mode(attr) => attr.value_len(), ConfigNla::NlBufSiz(_) => 4, ConfigNla::Timeout(attr) => attr.value_len(), ConfigNla::QThresh(_) => 4, ConfigNla::Flags(attr) => attr.value_len(), ConfigNla::Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { ConfigNla::Cmd(attr) => attr.kind(), ConfigNla::Mode(attr) => attr.kind(), ConfigNla::NlBufSiz(_) => NFULA_CFG_NLBUFSIZ, ConfigNla::Timeout(attr) => attr.kind(), ConfigNla::QThresh(_) => NFULA_CFG_QTHRESH, ConfigNla::Flags(attr) => attr.kind(), ConfigNla::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { ConfigNla::Cmd(attr) => attr.emit_value(buffer), ConfigNla::Mode(attr) => attr.emit_value(buffer), ConfigNla::NlBufSiz(buf_siz) => BigEndian::write_u32(buffer, *buf_siz), ConfigNla::Timeout(attr) => attr.emit_value(buffer), ConfigNla::QThresh(q_thresh) => BigEndian::write_u32(buffer, *q_thresh), ConfigNla::Flags(attr) => attr.emit_value(buffer), ConfigNla::Other(attr) => attr.emit_value(buffer), } } } impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> for ConfigNla { fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { let kind = buf.kind(); let payload = buf.value(); let nla = match kind { NFULA_CFG_CMD => { ConfigCmd::from(parse_u8(payload).context("invalid NFULA_CFG_CMD value")?).into() } NFULA_CFG_MODE => { let buf = ConfigModeBuffer::new_checked(payload)?; ConfigMode::parse(&buf)?.into() } NFULA_CFG_NLBUFSIZ => ConfigNla::NlBufSiz( parse_u32_be(payload).context("invalid NFULA_CFG_NLBUFSIZ value")?, ), NFULA_CFG_TIMEOUT => { Timeout::new(parse_u32_be(payload).context("invalid NFULA_CFG_TIMEOUT value")?) .into() } NFULA_CFG_QTHRESH => ConfigNla::QThresh( parse_u32_be(payload).context("invalid NFULA_CFG_QTHRESH value")?, ), NFULA_CFG_FLAGS => ConfigFlags::from_bits_preserve( parse_u16_be(payload).context("invalid NFULA_CFG_FLAGS value")?, ) .into(), _ => ConfigNla::Other(DefaultNla::parse(buf)?), }; Ok(nla) } } ================================================ FILE: netlink-packet-netfilter/src/nflog/nlas/config/timeout.rs ================================================ // SPDX-License-Identifier: MIT use std::{convert::TryInto, mem::size_of, time::Duration}; use byteorder::{BigEndian, ByteOrder}; use netlink_packet_utils::nla::Nla; const NFULA_CFG_TIMEOUT: u16 = libc::NFULA_CFG_TIMEOUT as u16; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Timeout { hundredth: u32, } impl Timeout { pub fn new(hundredth: u32) -> Self { Self { hundredth } } } impl From for Timeout { fn from(duration: Duration) -> Self { let hundredth = (duration.as_millis() / 10).try_into().unwrap_or(u32::MAX); Self { hundredth } } } impl Nla for Timeout { fn value_len(&self) -> usize { size_of::() } fn kind(&self) -> u16 { NFULA_CFG_TIMEOUT } fn emit_value(&self, buffer: &mut [u8]) { BigEndian::write_u32(buffer, self.hundredth); } } ================================================ FILE: netlink-packet-netfilter/src/nflog/nlas/mod.rs ================================================ // SPDX-License-Identifier: MIT pub mod config; pub mod packet; ================================================ FILE: netlink-packet-netfilter/src/nflog/nlas/packet/hw_addr.rs ================================================ // SPDX-License-Identifier: MIT use crate::{constants::NFULA_HWADDR, nla::Nla, traits::Parseable, utils::buffer, DecodeError}; const HW_ADDR_LEN: usize = 12; buffer!(HwAddrBuffer(HW_ADDR_LEN) { hw_addr_len: (u16, 0..2), hw_addr_0: (u8, 4), hw_addr_1: (u8, 5), hw_addr_2: (u8, 6), hw_addr_3: (u8, 7), hw_addr_4: (u8, 8), hw_addr_5: (u8, 9), hw_addr_6: (u8, 10), hw_addr_7: (u8, 11), }); #[derive(Clone, Debug, PartialEq, Eq)] pub struct HwAddr { len: u16, address: [u8; 8], } impl Nla for HwAddr { fn value_len(&self) -> usize { HW_ADDR_LEN } fn kind(&self) -> u16 { NFULA_HWADDR } fn emit_value(&self, buf: &mut [u8]) { let mut buf = HwAddrBuffer::new(buf); buf.set_hw_addr_len(self.len.to_be()); buf.set_hw_addr_0(self.address[0]); buf.set_hw_addr_1(self.address[1]); buf.set_hw_addr_2(self.address[2]); buf.set_hw_addr_3(self.address[3]); buf.set_hw_addr_4(self.address[4]); buf.set_hw_addr_5(self.address[5]); buf.set_hw_addr_6(self.address[6]); buf.set_hw_addr_7(self.address[7]); } } impl> Parseable> for HwAddr { fn parse(buf: &HwAddrBuffer) -> Result { Ok(HwAddr { len: u16::from_be(buf.hw_addr_len()), address: [ buf.hw_addr_0(), buf.hw_addr_1(), buf.hw_addr_2(), buf.hw_addr_3(), buf.hw_addr_4(), buf.hw_addr_5(), buf.hw_addr_6(), buf.hw_addr_7(), ], }) } } ================================================ FILE: netlink-packet-netfilter/src/nflog/nlas/packet/mod.rs ================================================ // SPDX-License-Identifier: MIT mod hw_addr; mod nla; mod packet_hdr; mod timestamp; pub use hw_addr::{HwAddr, HwAddrBuffer}; pub use nla::PacketNla; pub use packet_hdr::{PacketHdr, PacketHdrBuffer}; pub use timestamp::{TimeStamp, TimeStampBuffer}; ================================================ FILE: netlink-packet-netfilter/src/nflog/nlas/packet/nla.rs ================================================ // SPDX-License-Identifier: MIT use std::ffi::{CStr, CString}; use anyhow::Context; use byteorder::{BigEndian, ByteOrder}; use derive_more::{From, IsVariant}; use crate::{ constants::{ NFULA_GID, NFULA_HWADDR, NFULA_HWHEADER, NFULA_HWLEN, NFULA_HWTYPE, NFULA_IFINDEX_INDEV, NFULA_IFINDEX_OUTDEV, NFULA_IFINDEX_PHYSINDEV, NFULA_IFINDEX_PHYSOUTDEV, NFULA_MARK, NFULA_PACKET_HDR, NFULA_PAYLOAD, NFULA_PREFIX, NFULA_SEQ, NFULA_SEQ_GLOBAL, NFULA_TIMESTAMP, NFULA_UID, }, nflog::nlas::packet::{ hw_addr::{HwAddr, HwAddrBuffer}, packet_hdr::{PacketHdr, PacketHdrBuffer}, timestamp::{TimeStamp, TimeStampBuffer}, }, nla::{DefaultNla, Nla, NlaBuffer}, traits::Parseable, utils::parsers::{parse_u16_be, parse_u32_be}, DecodeError, }; #[derive(Clone, Debug, PartialEq, Eq, From, IsVariant)] pub enum PacketNla { #[from] PacketHdr(PacketHdr), Mark(u32), #[from] Timestamp(TimeStamp), IfIndexInDev(u32), IfIndexOutDev(u32), IfIndexPhysInDev(u32), IfIndexPhysOutDev(u32), #[from] HwAddr(HwAddr), Payload(Vec), Prefix(CString), Uid(u32), Seq(u32), SeqGlobal(u32), Gid(u32), HwType(u16), HwHeader(Vec), HwHeaderLen(u16), #[from] Other(DefaultNla), } impl Nla for PacketNla { fn value_len(&self) -> usize { match self { PacketNla::PacketHdr(attr) => attr.value_len(), PacketNla::Mark(_) => 4, PacketNla::Timestamp(attr) => attr.value_len(), PacketNla::IfIndexInDev(_) => 4, PacketNla::IfIndexOutDev(_) => 4, PacketNla::IfIndexPhysInDev(_) => 4, PacketNla::IfIndexPhysOutDev(_) => 4, PacketNla::HwAddr(attr) => attr.value_len(), PacketNla::Payload(vec) => vec.len(), PacketNla::Prefix(cstring) => cstring.as_bytes_with_nul().len(), PacketNla::Uid(_) => 4, PacketNla::Seq(_) => 4, PacketNla::SeqGlobal(_) => 4, PacketNla::Gid(_) => 4, PacketNla::HwType(_) => 2, PacketNla::HwHeader(vec) => vec.len(), PacketNla::HwHeaderLen(_) => 2, PacketNla::Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { match self { PacketNla::PacketHdr(attr) => attr.kind(), PacketNla::Mark(_) => NFULA_MARK, PacketNla::Timestamp(attr) => attr.kind(), PacketNla::IfIndexInDev(_) => NFULA_IFINDEX_INDEV, PacketNla::IfIndexOutDev(_) => NFULA_IFINDEX_OUTDEV, PacketNla::IfIndexPhysInDev(_) => NFULA_IFINDEX_PHYSINDEV, PacketNla::IfIndexPhysOutDev(_) => NFULA_IFINDEX_PHYSOUTDEV, PacketNla::HwAddr(attr) => attr.kind(), PacketNla::Payload(_) => NFULA_PAYLOAD, PacketNla::Prefix(_) => NFULA_PREFIX, PacketNla::Uid(_) => NFULA_UID, PacketNla::Seq(_) => NFULA_SEQ, PacketNla::SeqGlobal(_) => NFULA_SEQ_GLOBAL, PacketNla::Gid(_) => NFULA_GID, PacketNla::HwType(_) => NFULA_HWTYPE, PacketNla::HwHeader(_) => NFULA_HWHEADER, PacketNla::HwHeaderLen(_) => NFULA_HWLEN, PacketNla::Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { PacketNla::PacketHdr(attr) => attr.emit_value(buffer), PacketNla::Mark(value) => BigEndian::write_u32(buffer, *value), PacketNla::Timestamp(attr) => attr.emit_value(buffer), PacketNla::IfIndexInDev(value) => BigEndian::write_u32(buffer, *value), PacketNla::IfIndexOutDev(value) => BigEndian::write_u32(buffer, *value), PacketNla::IfIndexPhysInDev(value) => BigEndian::write_u32(buffer, *value), PacketNla::IfIndexPhysOutDev(value) => BigEndian::write_u32(buffer, *value), PacketNla::HwAddr(attr) => attr.emit_value(buffer), PacketNla::Payload(vec) => buffer.copy_from_slice(vec), PacketNla::Prefix(cstring) => buffer.copy_from_slice(cstring.as_bytes_with_nul()), PacketNla::Uid(value) => BigEndian::write_u32(buffer, *value), PacketNla::Seq(value) => BigEndian::write_u32(buffer, *value), PacketNla::SeqGlobal(value) => BigEndian::write_u32(buffer, *value), PacketNla::Gid(value) => BigEndian::write_u32(buffer, *value), PacketNla::HwType(value) => BigEndian::write_u16(buffer, *value), PacketNla::HwHeader(vec) => buffer.copy_from_slice(vec), PacketNla::HwHeaderLen(value) => BigEndian::write_u16(buffer, *value), PacketNla::Other(attr) => attr.emit_value(buffer), } } } impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> for PacketNla { fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { let kind = buf.kind(); let payload = buf.value(); let nla = match kind { NFULA_PACKET_HDR => { let buf = PacketHdrBuffer::new_checked(payload) .context("invalid NFULA_PACKET_HDR value")?; PacketHdr::parse(&buf)?.into() } NFULA_MARK => { PacketNla::Mark(parse_u32_be(payload).context("invalid NFULA_MARK value")?) } NFULA_TIMESTAMP => { let buf = TimeStampBuffer::new_checked(&payload) .context("invalid NFULA_TIMESTAMP value")?; PacketNla::Timestamp(TimeStamp::parse(&buf)?) } NFULA_IFINDEX_INDEV => PacketNla::IfIndexInDev( parse_u32_be(payload).context("invalid NFULA_IFINDEX_INDEV value")?, ), NFULA_IFINDEX_OUTDEV => PacketNla::IfIndexOutDev( parse_u32_be(payload).context("invalid NFULA_IFINDEX_OUTDEV value")?, ), NFULA_IFINDEX_PHYSINDEV => PacketNla::IfIndexPhysInDev( parse_u32_be(payload).context("invalid NFULA_IFINDEX_PHYSINDEV value")?, ), NFULA_IFINDEX_PHYSOUTDEV => PacketNla::IfIndexPhysOutDev( parse_u32_be(payload).context("invalid NFULA_IFINDEX_PHYSOUTDEV value")?, ), NFULA_HWADDR => { let buf = HwAddrBuffer::new_checked(payload).context("invalid NFULA_HWADDR value")?; PacketNla::HwAddr(HwAddr::parse(&buf)?) } NFULA_PAYLOAD => PacketNla::Payload(payload.to_vec()), NFULA_PREFIX => PacketNla::Prefix( CStr::from_bytes_with_nul(payload) .context("invalid NFULA_PREFIX value")? .to_owned(), ), NFULA_UID => PacketNla::Uid(parse_u32_be(payload).context("invalid NFULA_UID value")?), NFULA_SEQ => PacketNla::Seq(parse_u32_be(payload).context("invalid NFULA_SEQ value")?), NFULA_SEQ_GLOBAL => PacketNla::SeqGlobal( parse_u32_be(payload).context("invalid NFULA_SEQ_GLOBAL value")?, ), NFULA_GID => PacketNla::Gid(parse_u32_be(payload).context("invalid NFULA_GID value")?), NFULA_HWTYPE => { PacketNla::HwType(parse_u16_be(payload).context("invalid NFULA_HWTYPE value")?) } NFULA_HWHEADER => PacketNla::HwHeader(payload.to_vec()), NFULA_HWLEN => { PacketNla::HwHeaderLen(parse_u16_be(payload).context("invalid NFULA_HWLEN value")?) } _ => PacketNla::Other(DefaultNla::parse(buf)?), }; Ok(nla) } } ================================================ FILE: netlink-packet-netfilter/src/nflog/nlas/packet/packet_hdr.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_core::DecodeError; use netlink_packet_utils::{buffer, nla::Nla, Parseable}; const PACKET_HDR_LEN: usize = 4; pub const NFULA_PACKET_HDR: u16 = libc::NFULA_PACKET_HDR as u16; buffer!(PacketHdrBuffer(PACKET_HDR_LEN) { hw_protocol: (u16, 0..2), hook: (u8, 2), pad: (u8, 3), }); #[derive(Clone, Debug, PartialEq, Eq)] pub struct PacketHdr { hw_protocol: u16, hook: u8, } impl Nla for PacketHdr { fn value_len(&self) -> usize { PACKET_HDR_LEN } fn kind(&self) -> u16 { NFULA_PACKET_HDR } fn emit_value(&self, buf: &mut [u8]) { let mut buf = PacketHdrBuffer::new(buf); buf.set_hw_protocol(self.hw_protocol.to_be()); buf.set_hook(self.hook) } } impl> Parseable> for PacketHdr { fn parse(buf: &PacketHdrBuffer) -> Result { Ok(PacketHdr { hw_protocol: u16::from_be(buf.hw_protocol()), hook: buf.hook(), }) } } ================================================ FILE: netlink-packet-netfilter/src/nflog/nlas/packet/timestamp.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_core::DecodeError; use netlink_packet_utils::{buffer, nla::Nla, Parseable}; use crate::constants::NFULA_TIMESTAMP; const TIMESTAMP_LEN: usize = 16; buffer!(TimeStampBuffer(TIMESTAMP_LEN) { sec: (u64, 0..8), usec: (u64, 8..16), }); #[derive(Clone, Debug, PartialEq, Eq)] pub struct TimeStamp { sec: u64, usec: u64, } impl Nla for TimeStamp { fn value_len(&self) -> usize { TIMESTAMP_LEN } fn kind(&self) -> u16 { NFULA_TIMESTAMP } fn emit_value(&self, buf: &mut [u8]) { let mut buf = TimeStampBuffer::new(buf); buf.set_sec(self.sec.to_be()); buf.set_usec(self.usec.to_be()) } } impl> Parseable> for TimeStamp { fn parse(buf: &TimeStampBuffer) -> Result { Ok(TimeStamp { sec: u64::from_be(buf.sec()), usec: u64::from_be(buf.usec()), }) } } ================================================ FILE: netlink-packet-route/Cargo.toml ================================================ [package] authors = ["Corentin Henry "] name = "netlink-packet-route" version = "0.13.0" edition = "2018" homepage = "https://github.com/little-dude/netlink" keywords = ["netlink", "linux"] license = "MIT" readme = "../README.md" repository = "https://github.com/little-dude/netlink" description = "netlink packet types" [features] rich_nlas = [] [dependencies] anyhow = "1.0.31" byteorder = "1.3.2" libc = "0.2.66" netlink-packet-core = { version = "0.4.2", path = "../netlink-packet-core" } netlink-packet-utils = { version = "0.5.1", path = "../netlink-packet-utils" } bitflags = "1.2.1" [[example]] name = "dump_packet_links" [dev-dependencies] criterion = "0.3.0" pcap-file = "1.1.1" lazy_static = "1.4.0" netlink-sys = { version = "0.8.3", path = "../netlink-sys" } pretty_assertions = "0.7.2" [[bench]] name = "link_message" harness = false [[bench]] name = "rtnetlink_dump" harness = false ================================================ FILE: netlink-packet-route/benches/link_message.rs ================================================ // SPDX-License-Identifier: MIT use criterion::{criterion_group, criterion_main, Criterion}; use netlink_packet_route::{ nlas::link::Nla, traits::{Parseable, ParseableParametrized}, LinkHeader, LinkMessage, LinkMessageBuffer, }; const LINKMSG1: [u8; 96] = [ 0x00, // address family 0x00, // reserved 0x04, 0x03, // link layer type 772 = loopback 0x01, 0x00, 0x00, 0x00, // interface index = 1 // Note: in the wireshark capture, the thrid byte is 0x01 // but that does not correpond to any of the IFF_ flags... 0x49, 0x00, 0x00, 0x00, // device flags: UP, LOOPBACK, RUNNING, LOWERUP 0x00, 0x00, 0x00, 0x00, // reserved 2 (aka device change flag) // nlas 0x07, 0x00, 0x03, 0x00, 0x6c, 0x6f, 0x00, // device name L=7,T=3,V=lo 0x00, // padding 0x08, 0x00, 0x0d, 0x00, 0xe8, 0x03, 0x00, 0x00, // TxQueue length L=8,T=13,V=1000 0x05, 0x00, 0x10, 0x00, 0x00, // OperState L=5,T=16,V=0 (unknown) 0x00, 0x00, 0x00, // padding 0x05, 0x00, 0x11, 0x00, 0x00, // Link mode L=5,T=17,V=0 0x00, 0x00, 0x00, // padding 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, // MTU L=8,T=4,V=65536 0x08, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, // Group L=8,T=27,V=9 0x08, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, // Promiscuity L=8,T=30,V=0 0x08, 0x00, 0x1f, 0x00, 0x01, 0x00, 0x00, 0x00, // Number of Tx Queues L=8,T=31,V=1 0x08, 0x00, 0x28, 0x00, 0xff, 0xff, 0x00, 0x00, // Maximum GSO segment count L=8,T=40,V=65536 0x08, 0x00, 0x29, 0x00, 0x00, 0x00, 0x01, 0x00, // Maximum GSO size L=8,T=41,V=65536 ]; fn b1(c: &mut Criterion) { c.bench_function("parse LinkMessage header", |b| { b.iter(|| { LinkHeader::parse(&LinkMessageBuffer::new(&LINKMSG1[..])).unwrap(); }) }); c.bench_function("parse LinkMessage nlas", |b| { b.iter(|| { Vec::::parse_with_param(&LinkMessageBuffer::new(&&LINKMSG1[..]), 0_u8).unwrap(); }) }); c.bench_function("parse LinkMessage", |b| { b.iter(|| { LinkMessage::parse(&LinkMessageBuffer::new(&&LINKMSG1[..])).unwrap(); }) }); } criterion_group!(benches, b1); criterion_main!(benches); ================================================ FILE: netlink-packet-route/benches/rtnetlink_dump.rs ================================================ // SPDX-License-Identifier: MIT use std::fs::File; use criterion::{criterion_group, criterion_main, Criterion}; use pcap_file::PcapReader; use netlink_packet_route::{NetlinkMessage, RtnlMessage}; fn bench(c: &mut Criterion) { let pcap_reader = PcapReader::new(File::open("data/rtnetlink.pcap").unwrap()).unwrap(); let packets: Vec> = pcap_reader .map(|pkt| pkt.unwrap().data.into_owned().to_vec()) .collect(); c.bench_function("parse", move |b| { b.iter(|| { for (i, buf) in packets.iter().enumerate() { NetlinkMessage::::deserialize(&buf[16..]) .unwrap_or_else(|_| panic!("message {} failed", i)); } }) }); } criterion_group!(benches, bench); criterion_main!(benches); ================================================ FILE: netlink-packet-route/data/README.md ================================================ The rtnetlink dump was generated with: ``` sudo ip link add name qemu-br1 type bridge sudo ip link set qemu-br1 up sudo ip address add 192.168.10.1/24 dev qemu-br1 docker run -d -it busybox /bin/sh sudo ip netns add blue sudo ip link add veth0 type veth peer name veth1 sudo ip netns list sudo ip link set veth1 netns blue sudo ip -6 link add vxlan100 type vxlan id 100 dstport 4789 local 2001:db8:1::1 group ff05::100 dev veth0 ttl 5 sudo brctl addbr br100 sudo brctl addif br100 vxlan100 sudo ip link show sudo ip address show sudo ip neigh show sudo ip route show tc qdisc show ``` ================================================ FILE: netlink-packet-route/examples/dump_neighbours.rs ================================================ // SPDX-License-Identifier: MIT use std::{convert::TryFrom, net::IpAddr, string::ToString}; use netlink_packet_route::{ constants::*, nlas::neighbour::Nla, NeighbourMessage, NetlinkHeader, NetlinkMessage, NetlinkPayload, RtnlMessage, }; use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; fn main() { let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); let _port_number = socket.bind_auto().unwrap().port_number(); socket.connect(&SocketAddr::new(0, 0)).unwrap(); let mut req = NetlinkMessage { header: NetlinkHeader { flags: NLM_F_DUMP | NLM_F_REQUEST, ..Default::default() }, payload: NetlinkPayload::from(RtnlMessage::GetNeighbour(NeighbourMessage::default())), }; // IMPORTANT: call `finalize()` to automatically set the // `message_type` and `length` fields to the appropriate values in // the netlink header. req.finalize(); let mut buf = vec![0; req.header.length as usize]; req.serialize(&mut buf[..]); println!(">>> {:?}", req); socket.send(&buf[..], 0).unwrap(); let mut receive_buffer = vec![0; 4096]; let mut offset = 0; 'outer: loop { let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); loop { let bytes = &receive_buffer[offset..]; // Parse the message let msg: NetlinkMessage = NetlinkMessage::deserialize(bytes).unwrap(); match msg.payload { NetlinkPayload::Done => break 'outer, NetlinkPayload::InnerMessage(RtnlMessage::NewNeighbour(entry)) => { let address_family = entry.header.family as u16; if address_family == AF_INET || address_family == AF_INET6 { print_entry(entry); } } NetlinkPayload::Error(err) => { eprintln!("Received a netlink error message: {:?}", err); return; } _ => {} } offset += msg.header.length as usize; if offset == size || msg.header.length == 0 { offset = 0; break; } } } } fn format_ip(buf: &[u8]) -> String { if let Ok(bytes) = <&[u8; 4]>::try_from(buf) { IpAddr::from(*bytes).to_string() } else if let Ok(bytes) = <&[u8; 16]>::try_from(buf) { IpAddr::from(*bytes).to_string() } else { panic!("Invalid IP Address"); } } fn format_mac(buf: &[u8]) -> String { assert_eq!(buf.len(), 6); format!( "{:<02x}:{:<02x}:{:<02x}:{:<02x}:{:<02x}:{:<02x}", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5] ) } fn state_str(value: u16) -> &'static str { match value { NUD_INCOMPLETE => "INCOMPLETE", NUD_REACHABLE => "REACHABLE", NUD_STALE => "STALE", NUD_DELAY => "DELAY", NUD_PROBE => "PROBE", NUD_FAILED => "FAILED", NUD_NOARP => "NOARP", NUD_PERMANENT => "PERMANENT", NUD_NONE => "NONE", _ => "UNKNOWN", } } fn print_entry(entry: NeighbourMessage) { let state = state_str(entry.header.state); let dest = entry .nlas .iter() .find_map(|nla| { if let Nla::Destination(addr) = nla { Some(format_ip(&addr[..])) } else { None } }) .unwrap(); let lladdr = entry .nlas .iter() .find_map(|nla| { if let Nla::LinkLocalAddress(addr) = nla { Some(format_mac(&addr[..])) } else { None } }) .unwrap(); println!("{:<30} {:<20} ({})", dest, lladdr, state); } ================================================ FILE: netlink-packet-route/examples/dump_packet_link_bridge_vlan.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_route::{ nlas::link::Nla, LinkMessage, NetlinkHeader, NetlinkMessage, NetlinkPayload, RtnlMessage, AF_BRIDGE, NLM_F_DUMP, NLM_F_REQUEST, RTEXT_FILTER_BRVLAN_COMPRESSED, }; use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; fn main() { let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); let _port_number = socket.bind_auto().unwrap().port_number(); socket.connect(&SocketAddr::new(0, 0)).unwrap(); let mut message = LinkMessage::default(); message.header.interface_family = AF_BRIDGE as u8; message .nlas .push(Nla::ExtMask(RTEXT_FILTER_BRVLAN_COMPRESSED)); let mut packet = NetlinkMessage { header: NetlinkHeader::default(), payload: NetlinkPayload::from(RtnlMessage::GetLink(message)), }; packet.header.flags = NLM_F_DUMP | NLM_F_REQUEST; packet.header.sequence_number = 1; packet.finalize(); let mut buf = vec![0; packet.header.length as usize]; // Before calling serialize, it is important to check that the buffer in which we're emitting is big // enough for the packet, other `serialize()` panics. assert!(buf.len() == packet.buffer_len()); packet.serialize(&mut buf[..]); println!(">>> {:?}", packet); socket.send(&buf[..], 0).unwrap(); let mut receive_buffer = vec![0; 4096]; let mut offset = 0; // we set the NLM_F_DUMP flag so we expect a multipart rx_packet in response. loop { let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); loop { let bytes = &receive_buffer[offset..]; // Note that we're parsing a NetlinkBuffer<&&[u8]>, NOT a NetlinkBuffer<&[u8]> here. // This is important because Parseable is only implemented for // NetlinkBuffer<&'a T>, where T implements AsRef<[u8] + 'a. This is not // particularly user friendly, but this is a low level library anyway. // // Note also that the same could be written more explicitely with: // // let rx_packet = // as Parseable>::parse(NetlinkBuffer::new(&bytes)) // .unwrap(); // let rx_packet: NetlinkMessage = NetlinkMessage::deserialize(bytes).unwrap(); println!("<<< {:?}", rx_packet); if rx_packet.payload == NetlinkPayload::Done { println!("Done!"); return; } offset += rx_packet.header.length as usize; if offset == size || rx_packet.header.length == 0 { offset = 0; break; } } } } ================================================ FILE: netlink-packet-route/examples/dump_packet_links.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_route::{ LinkMessage, NetlinkHeader, NetlinkMessage, NetlinkPayload, RtnlMessage, NLM_F_DUMP, NLM_F_REQUEST, }; use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; fn main() { let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); let _port_number = socket.bind_auto().unwrap().port_number(); socket.connect(&SocketAddr::new(0, 0)).unwrap(); let mut packet = NetlinkMessage { header: NetlinkHeader::default(), payload: NetlinkPayload::from(RtnlMessage::GetLink(LinkMessage::default())), }; packet.header.flags = NLM_F_DUMP | NLM_F_REQUEST; packet.header.sequence_number = 1; packet.finalize(); let mut buf = vec![0; packet.header.length as usize]; // Before calling serialize, it is important to check that the buffer in which we're emitting is big // enough for the packet, other `serialize()` panics. assert!(buf.len() == packet.buffer_len()); packet.serialize(&mut buf[..]); println!(">>> {:?}", packet); socket.send(&buf[..], 0).unwrap(); let mut receive_buffer = vec![0; 4096]; let mut offset = 0; // we set the NLM_F_DUMP flag so we expect a multipart rx_packet in response. loop { let size = socket.recv(&mut &mut receive_buffer[..], 0).unwrap(); loop { let bytes = &receive_buffer[offset..]; // Note that we're parsing a NetlinkBuffer<&&[u8]>, NOT a NetlinkBuffer<&[u8]> here. // This is important because Parseable is only implemented for // NetlinkBuffer<&'a T>, where T implements AsRef<[u8] + 'a. This is not // particularly user friendly, but this is a low level library anyway. // // Note also that the same could be written more explicitely with: // // let rx_packet = // as Parseable>::parse(NetlinkBuffer::new(&bytes)) // .unwrap(); // let rx_packet: NetlinkMessage = NetlinkMessage::deserialize(bytes).unwrap(); println!("<<< {:?}", rx_packet); if rx_packet.payload == NetlinkPayload::Done { println!("Done!"); return; } offset += rx_packet.header.length as usize; if offset == size || rx_packet.header.length == 0 { offset = 0; break; } } } } ================================================ FILE: netlink-packet-route/examples/dump_rules.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_route::{ constants::*, NetlinkHeader, NetlinkMessage, NetlinkPayload, RtnlMessage, RuleMessage, }; use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; fn main() { let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); let _port_number = socket.bind_auto().unwrap().port_number(); socket.connect(&SocketAddr::new(0, 0)).unwrap(); let mut packet = NetlinkMessage { header: NetlinkHeader { flags: NLM_F_REQUEST | NLM_F_DUMP, ..Default::default() }, payload: NetlinkPayload::from(RtnlMessage::GetRule(RuleMessage::default())), }; packet.finalize(); let mut buf = vec![0; packet.header.length as usize]; // Before calling serialize, it is important to check that the buffer in which we're emitting is big // enough for the packet, other `serialize()` panics. assert!(buf.len() == packet.buffer_len()); packet.serialize(&mut buf[..]); println!(">>> {:?}", packet); if let Err(e) = socket.send(&buf[..], 0) { println!("SEND ERROR {}", e); } let mut receive_buffer = vec![0; 4096]; let mut offset = 0; // we set the NLM_F_DUMP flag so we expect a multipart rx_packet in response. while let Ok(size) = socket.recv(&mut &mut receive_buffer[..], 0) { loop { let bytes = &receive_buffer[offset..]; let rx_packet = >::deserialize(bytes).unwrap(); println!("<<< {:?}", rx_packet); if rx_packet.payload == NetlinkPayload::Done { println!("Done!"); return; } offset += rx_packet.header.length as usize; if offset == size || rx_packet.header.length == 0 { offset = 0; break; } } } } ================================================ FILE: netlink-packet-route/examples/new_rule.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_core::{NetlinkHeader, NetlinkMessage, NetlinkPayload}; use netlink_packet_route::{constants::*, rule, RtnlMessage, RuleHeader, RuleMessage}; use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; fn main() { let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); let _port_number = socket.bind_auto().unwrap().port_number(); socket.connect(&SocketAddr::new(0, 0)).unwrap(); let mut msg = NetlinkMessage { header: NetlinkHeader { flags: NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK, ..Default::default() }, payload: NetlinkPayload::from(RtnlMessage::NewRule(RuleMessage { header: RuleHeader { family: AF_INET as u8, table: RT_TABLE_DEFAULT, action: FR_ACT_TO_TBL, ..Default::default() }, nlas: vec![ rule::Nla::Table(254), rule::Nla::SuppressPrefixLen(4294967295), rule::Nla::Priority(1000), rule::Nla::Protocol(2), ], })), }; msg.finalize(); let mut buf = vec![0; 1024 * 8]; msg.serialize(&mut buf[..msg.buffer_len()]); println!(">>> {:?}", msg); socket .send(&buf, 0) .expect("failed to send netlink message"); let mut receive_buffer = vec![0; 4096]; while let Ok(_size) = socket.recv(&mut receive_buffer, 0) { loop { let bytes = &receive_buffer[..]; let rx_packet = >::deserialize(bytes); println!("<<< {:?}", rx_packet); if let Ok(rx_packet) = rx_packet { if let NetlinkPayload::Error(e) = rx_packet.payload { eprintln!("{:?}", e); } } return; } } } ================================================ FILE: netlink-packet-route/fuzz/.gitignore ================================================ target corpus artifacts ================================================ FILE: netlink-packet-route/fuzz/Cargo.toml ================================================ [package] name = "netlink-packet-route-fuzz" version = "0.0.1" authors = ["Automatically generated"] publish = false edition = "2018" [package.metadata] cargo-fuzz = true [dependencies] netlink-packet-route = { path = "../", version = "0.13" } libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } [[bin]] name = "netlink-route" path = "fuzz_targets/netlink.rs" ================================================ FILE: netlink-packet-route/fuzz/fuzz_targets/netlink.rs ================================================ // SPDX-License-Identifier: MIT #![no_main] use libfuzzer_sys::fuzz_target; use netlink_packet_route::{NetlinkMessage, RtnlMessage}; fuzz_target!(|data: &[u8]| { let _ = NetlinkMessage::::deserialize(data); }); ================================================ FILE: netlink-packet-route/src/lib.rs ================================================ // SPDX-License-Identifier: MIT #[macro_use] extern crate bitflags; #[macro_use] pub(crate) extern crate netlink_packet_utils as utils; pub(crate) use self::utils::parsers; pub use self::utils::{traits, DecodeError}; pub use netlink_packet_core::{ ErrorMessage, NetlinkBuffer, NetlinkHeader, NetlinkMessage, NetlinkPayload, }; pub(crate) use netlink_packet_core::{NetlinkDeserializable, NetlinkSerializable}; pub mod rtnl; pub use self::rtnl::*; #[cfg(test)] #[macro_use] extern crate lazy_static; #[cfg(test)] #[macro_use] extern crate pretty_assertions; ================================================ FILE: netlink-packet-route/src/rtnl/address/buffer.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ nlas::{NlaBuffer, NlasIterator}, DecodeError, }; pub const ADDRESS_HEADER_LEN: usize = 8; buffer!(AddressMessageBuffer(ADDRESS_HEADER_LEN) { family: (u8, 0), prefix_len: (u8, 1), flags: (u8, 2), scope: (u8, 3), index: (u32, 4..ADDRESS_HEADER_LEN), payload: (slice, ADDRESS_HEADER_LEN..), }); impl<'a, T: AsRef<[u8]> + ?Sized> AddressMessageBuffer<&'a T> { pub fn nlas(&self) -> impl Iterator, DecodeError>> { NlasIterator::new(self.payload()) } } ================================================ FILE: netlink-packet-route/src/rtnl/address/message.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use crate::{ nlas::address::Nla, traits::{Emitable, Parseable}, AddressMessageBuffer, DecodeError, ADDRESS_HEADER_LEN, }; #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct AddressMessage { pub header: AddressHeader, pub nlas: Vec, } #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct AddressHeader { pub family: u8, pub prefix_len: u8, pub flags: u8, pub scope: u8, pub index: u32, } impl Emitable for AddressHeader { fn buffer_len(&self) -> usize { ADDRESS_HEADER_LEN } fn emit(&self, buffer: &mut [u8]) { let mut packet = AddressMessageBuffer::new(buffer); packet.set_family(self.family); packet.set_prefix_len(self.prefix_len); packet.set_flags(self.flags); packet.set_scope(self.scope); packet.set_index(self.index); } } impl Emitable for AddressMessage { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); self.nlas .as_slice() .emit(&mut buffer[self.header.buffer_len()..]); } } impl> Parseable> for AddressHeader { fn parse(buf: &AddressMessageBuffer) -> Result { Ok(Self { family: buf.family(), prefix_len: buf.prefix_len(), flags: buf.flags(), scope: buf.scope(), index: buf.index(), }) } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for AddressMessage { fn parse(buf: &AddressMessageBuffer<&'a T>) -> Result { Ok(AddressMessage { header: AddressHeader::parse(buf).context("failed to parse address message header")?, nlas: Vec::::parse(buf).context("failed to parse address message NLAs")?, }) } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for Vec { fn parse(buf: &AddressMessageBuffer<&'a T>) -> Result { let mut nlas = vec![]; for nla_buf in buf.nlas() { nlas.push(Nla::parse(&nla_buf?)?); } Ok(nlas) } } ================================================ FILE: netlink-packet-route/src/rtnl/address/mod.rs ================================================ // SPDX-License-Identifier: MIT mod buffer; pub use self::buffer::*; mod message; pub use self::message::*; pub mod nlas; pub use self::nlas::*; ================================================ FILE: netlink-packet-route/src/rtnl/address/nlas/cache_info.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] pub struct CacheInfo { pub ifa_preferred: i32, pub ifa_valid: i32, pub cstamp: i32, pub tstamp: i32, } pub const ADDRESSS_CACHE_INFO_LEN: usize = 16; buffer!(CacheInfoBuffer(ADDRESSS_CACHE_INFO_LEN) { ifa_preferred: (i32, 0..4), ifa_valid: (i32, 4..8), cstamp: (i32, 8..12), tstamp: (i32, 12..16), }); impl> Parseable> for CacheInfo { fn parse(buf: &CacheInfoBuffer) -> Result { Ok(CacheInfo { ifa_preferred: buf.ifa_preferred(), ifa_valid: buf.ifa_valid(), cstamp: buf.cstamp(), tstamp: buf.tstamp(), }) } } impl Emitable for CacheInfo { fn buffer_len(&self) -> usize { ADDRESSS_CACHE_INFO_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = CacheInfoBuffer::new(buffer); buffer.set_ifa_preferred(self.ifa_preferred); buffer.set_ifa_valid(self.ifa_valid); buffer.set_cstamp(self.cstamp); buffer.set_tstamp(self.tstamp); } } ================================================ FILE: netlink-packet-route/src/rtnl/address/nlas/mod.rs ================================================ // SPDX-License-Identifier: MIT mod cache_info; pub use self::cache_info::*; use std::mem::size_of; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use crate::{ constants::*, nlas::{self, DefaultNla, NlaBuffer}, parsers::{parse_string, parse_u32}, traits::Parseable, DecodeError, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Nla { Unspec(Vec), Address(Vec), Local(Vec), Label(String), Broadcast(Vec), Anycast(Vec), CacheInfo(Vec), Multicast(Vec), Flags(u32), Other(DefaultNla), } impl nlas::Nla for Nla { #[rustfmt::skip] fn value_len(&self) -> usize { use self::Nla::*; match *self { // Vec Unspec(ref bytes) | Address(ref bytes) | Local(ref bytes) | Broadcast(ref bytes) | Anycast(ref bytes) | Multicast(ref bytes) => bytes.len(), // strings: +1 because we need to append a nul byte Label(ref string) => string.as_bytes().len() + 1, // u32 Flags(_) => size_of::(), // Native CacheInfo(ref buffer) => buffer.len(), // Defaults Other(ref attr) => attr.value_len(), } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::Nla::*; match *self { // Vec Unspec(ref bytes) | Address(ref bytes) | Local(ref bytes) | Broadcast(ref bytes) | Anycast(ref bytes) | CacheInfo(ref bytes) | Multicast(ref bytes) => buffer.copy_from_slice(bytes.as_slice()), // String Label(ref string) => { buffer[..string.len()].copy_from_slice(string.as_bytes()); buffer[string.len()] = 0; } // u32 Flags(ref value) => NativeEndian::write_u32(buffer, *value), // Default Other(ref attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Nla::*; match *self { Unspec(_) => IFA_UNSPEC, Address(_) => IFA_ADDRESS, Local(_) => IFA_LOCAL, Label(_) => IFA_LABEL, Broadcast(_) => IFA_BROADCAST, Anycast(_) => IFA_ANYCAST, CacheInfo(_) => IFA_CACHEINFO, Multicast(_) => IFA_MULTICAST, Flags(_) => IFA_FLAGS, Other(ref nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nla { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::Nla::*; let payload = buf.value(); Ok(match buf.kind() { IFA_UNSPEC => Unspec(payload.to_vec()), IFA_ADDRESS => Address(payload.to_vec()), IFA_LOCAL => Local(payload.to_vec()), IFA_LABEL => Label(parse_string(payload).context("invalid IFA_LABEL value")?), IFA_BROADCAST => Broadcast(payload.to_vec()), IFA_ANYCAST => Anycast(payload.to_vec()), IFA_CACHEINFO => CacheInfo(payload.to_vec()), IFA_MULTICAST => Multicast(payload.to_vec()), IFA_FLAGS => Flags(parse_u32(payload).context("invalid IFA_FLAGS value")?), kind => Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/buffer.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ constants::*, traits::{Parseable, ParseableParametrized}, AddressHeader, AddressMessage, AddressMessageBuffer, DecodeError, LinkMessage, LinkMessageBuffer, NeighbourMessage, NeighbourMessageBuffer, NeighbourTableMessage, NeighbourTableMessageBuffer, NsidMessage, NsidMessageBuffer, RouteHeader, RouteMessage, RouteMessageBuffer, RtnlMessage, RuleMessage, RuleMessageBuffer, TcMessage, TcMessageBuffer, }; use anyhow::Context; buffer!(RtnlMessageBuffer); impl<'a, T: AsRef<[u8]> + ?Sized> ParseableParametrized, u16> for RtnlMessage { #[rustfmt::skip] fn parse_with_param(buf: &RtnlMessageBuffer<&'a T>, message_type: u16) -> Result { use self::RtnlMessage::*; let message = match message_type { // Link messages RTM_NEWLINK | RTM_GETLINK | RTM_DELLINK | RTM_SETLINK => { let msg = match LinkMessageBuffer::new_checked(&buf.inner()) { Ok(buf) => LinkMessage::parse(&buf).context("invalid link message")?, // HACK: iproute2 sends invalid RTM_GETLINK message, where the header is // limited to the interface family (1 byte) and 3 bytes of padding. Err(e) => { if buf.inner().len() == 4 && message_type == RTM_GETLINK { let mut msg = LinkMessage::default(); msg.header.interface_family = buf.inner()[0]; msg } else { return Err(e); } } }; match message_type { RTM_NEWLINK => NewLink(msg), RTM_GETLINK => GetLink(msg), RTM_DELLINK => DelLink(msg), RTM_SETLINK => SetLink(msg), _ => unreachable!(), } } // Address messages RTM_NEWADDR | RTM_GETADDR | RTM_DELADDR => { let msg = match AddressMessageBuffer::new_checked(&buf.inner()) { Ok(buf) => AddressMessage::parse(&buf).context("invalid link message")?, // HACK: iproute2 sends invalid RTM_GETADDR message, where the header is // limited to the interface family (1 byte) and 3 bytes of padding. Err(e) => { if buf.inner().len() == 4 && message_type == RTM_GETADDR { let mut msg = AddressMessage { header: AddressHeader::default(), nlas: vec![], }; msg.header.family = buf.inner()[0]; msg } else { return Err(e); } } }; match message_type { RTM_NEWADDR => NewAddress(msg), RTM_GETADDR => GetAddress(msg), RTM_DELADDR => DelAddress(msg), _ => unreachable!(), } } // Neighbour messages RTM_NEWNEIGH | RTM_GETNEIGH | RTM_DELNEIGH => { let err = "invalid neighbour message"; let msg = NeighbourMessage::parse(&NeighbourMessageBuffer::new_checked(&buf.inner()).context(err)?).context(err)?; match message_type { RTM_GETNEIGH => GetNeighbour(msg), RTM_NEWNEIGH => NewNeighbour(msg), RTM_DELNEIGH => DelNeighbour(msg), _ => unreachable!(), } } // Neighbour table messages RTM_NEWNEIGHTBL | RTM_GETNEIGHTBL | RTM_SETNEIGHTBL => { let err = "invalid neighbour table message"; let msg = NeighbourTableMessage::parse(&NeighbourTableMessageBuffer::new_checked(&buf.inner()).context(err)?).context(err)?; match message_type { RTM_GETNEIGHTBL => GetNeighbourTable(msg), RTM_NEWNEIGHTBL => NewNeighbourTable(msg), RTM_SETNEIGHTBL => SetNeighbourTable(msg), _ => unreachable!(), } } // Route messages RTM_NEWROUTE | RTM_GETROUTE | RTM_DELROUTE => { let msg = match RouteMessageBuffer::new_checked(&buf.inner()) { Ok(buf) => RouteMessage::parse(&buf).context("invalid route message")?, // HACK: iproute2 sends invalid RTM_GETROUTE message, where the header is // limited to the interface family (1 byte) and 3 bytes of padding. Err(e) => { // Not only does iproute2 sends invalid messages, it's also inconsistent in // doing so: for link and address messages, the length advertised in the // netlink header includes the 3 bytes of padding but it does not seem to // be the case for the route message, hence the buf.length() == 1 check. if (buf.inner().len() == 4 || buf.inner().len() == 1) && message_type == RTM_GETROUTE { let mut msg = RouteMessage { header: RouteHeader::default(), nlas: vec![], }; msg.header.address_family = buf.inner()[0]; msg } else { return Err(e); } } }; match message_type { RTM_NEWROUTE => NewRoute(msg), RTM_GETROUTE => GetRoute(msg), RTM_DELROUTE => DelRoute(msg), _ => unreachable!(), } } RTM_NEWRULE | RTM_GETRULE | RTM_DELRULE => { let err = "invalid fib rule message"; let msg = RuleMessage::parse(&RuleMessageBuffer::new_checked(&buf.inner()).context(err)?).context(err)?; match message_type { RTM_NEWRULE => NewRule(msg), RTM_DELRULE => DelRule(msg), RTM_GETRULE => GetRule(msg), _ => unreachable!() } } // TC Messages RTM_NEWQDISC | RTM_DELQDISC | RTM_GETQDISC | RTM_NEWTCLASS | RTM_DELTCLASS | RTM_GETTCLASS | RTM_NEWTFILTER | RTM_DELTFILTER | RTM_GETTFILTER | RTM_NEWCHAIN | RTM_DELCHAIN | RTM_GETCHAIN => { let err = "invalid tc message"; let msg = TcMessage::parse(&TcMessageBuffer::new_checked(&buf.inner()).context(err)?).context(err)?; match message_type { RTM_NEWQDISC => NewQueueDiscipline(msg), RTM_DELQDISC => DelQueueDiscipline(msg), RTM_GETQDISC => GetQueueDiscipline(msg), RTM_NEWTCLASS => NewTrafficClass(msg), RTM_DELTCLASS => DelTrafficClass(msg), RTM_GETTCLASS => GetTrafficClass(msg), RTM_NEWTFILTER => NewTrafficFilter(msg), RTM_DELTFILTER => DelTrafficFilter(msg), RTM_GETTFILTER => GetTrafficFilter(msg), RTM_NEWCHAIN => NewTrafficChain(msg), RTM_DELCHAIN => DelTrafficChain(msg), RTM_GETCHAIN => GetTrafficChain(msg), _ => unreachable!(), } } // ND ID Messages RTM_NEWNSID | RTM_GETNSID | RTM_DELNSID => { let err = "invalid nsid message"; let msg = NsidMessage::parse(&NsidMessageBuffer::new_checked(&buf.inner()).context(err)?).context(err)?; match message_type { RTM_NEWNSID => NewNsId(msg), RTM_DELNSID => DelNsId(msg), RTM_GETNSID => GetNsId(msg), _ => unreachable!(), } } _ => return Err(format!("Unknown message type: {}", message_type).into()), }; Ok(message) } } ================================================ FILE: netlink-packet-route/src/rtnl/constants.rs ================================================ // SPDX-License-Identifier: MIT pub use netlink_packet_core::constants::*; pub const RTM_BASE: u16 = 16; pub const RTM_NEWLINK: u16 = 16; pub const RTM_DELLINK: u16 = 17; pub const RTM_GETLINK: u16 = 18; pub const RTM_SETLINK: u16 = 19; pub const RTM_NEWADDR: u16 = 20; pub const RTM_DELADDR: u16 = 21; pub const RTM_GETADDR: u16 = 22; pub const RTM_NEWROUTE: u16 = 24; pub const RTM_DELROUTE: u16 = 25; pub const RTM_GETROUTE: u16 = 26; pub const RTM_NEWNEIGH: u16 = 28; pub const RTM_DELNEIGH: u16 = 29; pub const RTM_GETNEIGH: u16 = 30; pub const RTM_NEWRULE: u16 = 32; pub const RTM_DELRULE: u16 = 33; pub const RTM_GETRULE: u16 = 34; pub const RTM_NEWQDISC: u16 = 36; pub const RTM_DELQDISC: u16 = 37; pub const RTM_GETQDISC: u16 = 38; pub const RTM_NEWTCLASS: u16 = 40; pub const RTM_DELTCLASS: u16 = 41; pub const RTM_GETTCLASS: u16 = 42; pub const RTM_NEWTFILTER: u16 = 44; pub const RTM_DELTFILTER: u16 = 45; pub const RTM_GETTFILTER: u16 = 46; pub const RTM_NEWACTION: u16 = 48; pub const RTM_DELACTION: u16 = 49; pub const RTM_GETACTION: u16 = 50; pub const RTM_NEWPREFIX: u16 = 52; pub const RTM_GETMULTICAST: u16 = 58; pub const RTM_GETANYCAST: u16 = 62; pub const RTM_NEWNEIGHTBL: u16 = 64; pub const RTM_GETNEIGHTBL: u16 = 66; pub const RTM_SETNEIGHTBL: u16 = 67; pub const RTM_NEWNDUSEROPT: u16 = 68; pub const RTM_NEWADDRLABEL: u16 = 72; pub const RTM_DELADDRLABEL: u16 = 73; pub const RTM_GETADDRLABEL: u16 = 74; pub const RTM_GETDCB: u16 = 78; pub const RTM_SETDCB: u16 = 79; pub const RTM_NEWNETCONF: u16 = 80; pub const RTM_DELNETCONF: u16 = 81; pub const RTM_GETNETCONF: u16 = 82; pub const RTM_NEWMDB: u16 = 84; pub const RTM_DELMDB: u16 = 85; pub const RTM_GETMDB: u16 = 86; pub const RTM_NEWNSID: u16 = 88; pub const RTM_DELNSID: u16 = 89; pub const RTM_GETNSID: u16 = 90; pub const RTM_NEWSTATS: u16 = 92; pub const RTM_GETSTATS: u16 = 94; pub const RTM_NEWCACHEREPORT: u16 = 96; pub const RTM_NEWCHAIN: u16 = 100; pub const RTM_DELCHAIN: u16 = 101; pub const RTM_GETCHAIN: u16 = 102; pub const RTM_NEWLINKPROP: u16 = 108; pub const RTM_DELLINKPROP: u16 = 109; /// Unknown route pub const RTN_UNSPEC: u8 = 0; /// A gateway or direct route pub const RTN_UNICAST: u8 = 1; /// A local interface route pub const RTN_LOCAL: u8 = 2; /// A local broadcast route (sent as a broadcast) pub const RTN_BROADCAST: u8 = 3; /// A local broadcast route (sent as a unicast) pub const RTN_ANYCAST: u8 = 4; /// A multicast route pub const RTN_MULTICAST: u8 = 5; /// A packet dropping route pub const RTN_BLACKHOLE: u8 = 6; /// An unreachable destination pub const RTN_UNREACHABLE: u8 = 7; /// A packet rejection route pub const RTN_PROHIBIT: u8 = 8; /// Continue routing lookup in another table pub const RTN_THROW: u8 = 9; /// A network address translation rule pub const RTN_NAT: u8 = 10; /// Refer to an external resolver (not implemented) pub const RTN_XRESOLVE: u8 = 11; /// Unknown pub const RTPROT_UNSPEC: u8 = 0; /// Route was learnt by an ICMP redirect pub const RTPROT_REDIRECT: u8 = 1; /// Route was learnt by the kernel pub const RTPROT_KERNEL: u8 = 2; /// Route was learnt during boot pub const RTPROT_BOOT: u8 = 3; /// Route was set statically pub const RTPROT_STATIC: u8 = 4; pub const RTPROT_GATED: u8 = 8; pub const RTPROT_RA: u8 = 9; pub const RTPROT_MRT: u8 = 10; pub const RTPROT_ZEBRA: u8 = 11; pub const RTPROT_BIRD: u8 = 12; pub const RTPROT_DNROUTED: u8 = 13; pub const RTPROT_XORP: u8 = 14; pub const RTPROT_NTK: u8 = 15; pub const RTPROT_DHCP: u8 = 16; pub const RTPROT_MROUTED: u8 = 17; pub const RTPROT_BABEL: u8 = 42; /// The destination is globally valid. pub const RT_SCOPE_UNIVERSE: u8 = 0; /// (IPv6 only) the destination is site local, i.e. it is valid inside this site. This is for interior /// routes in the local autonomous system pub const RT_SCOPE_SITE: u8 = 200; /// The destination is link local pub const RT_SCOPE_LINK: u8 = 253; /// The destination is valid only on this host pub const RT_SCOPE_HOST: u8 = 254; /// Destination doesn't exist pub const RT_SCOPE_NOWHERE: u8 = 255; /// An unspecified routing table pub const RT_TABLE_UNSPEC: u8 = 0; /// A route table introduced for compatibility with old software which do not support table IDs /// greater than 255. See commit `709772e6e065` in the kernel: /// /// ```no_rust /// commit 709772e6e06564ed94ba740de70185ac3d792773 /// Author: Krzysztof Piotr Oledzki /// Date: Tue Jun 10 15:44:49 2008 -0700 /// /// net: Fix routing tables with id > 255 for legacy software /// /// Most legacy software do not like tables > 255 as rtm_table is u8 /// so tb_id is sent &0xff and it is possible to mismatch for example /// table 510 with table 254 (main). /// /// This patch introduces RT_TABLE_COMPAT=252 so the code uses it if /// tb_id > 255. It makes such old applications happy, new /// ones are still able to use RTA_TABLE to get a proper table id. /// /// Signed-off-by: Krzysztof Piotr Oledzki /// Acked-by: Patrick McHardy /// Signed-off-by: David S. Miller /// ``` pub const RT_TABLE_COMPAT: u8 = 252; /// The default routing table. /// /// The default table is empty and has little use. It has been kept when the current incarnation of /// advanced routing has been introduced in Linux 2.1.68 after a first tentative using "classes" in /// Linux 2.1.15. /// # Source /// /// This documentation is taken from [Vincent Bernat's excellent /// blog](https://vincent.bernat.ch/en/blog/2017-ipv4-route-lookup-linux#builtin-tables) pub const RT_TABLE_DEFAULT: u8 = 253; /// The main routing table. /// /// By default, apart from the local ones which are added to the local table, routes that are added /// to this table. pub const RT_TABLE_MAIN: u8 = 254; /// The local table. /// /// This table is populated automatically by the kernel when addresses are configured. /// /// On a machine that has `192.168.44.211/24` configured on `wlp58s0`, `iproute2` shows the following routes in the local table: /// /// ```no_rust /// $ ip route show table local /// /// broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1 /// local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1 /// local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1 /// broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1 /// /// broadcast 192.168.44.0 dev wlp58s0 proto kernel scope link src 192.168.44.211 /// local 192.168.44.211 dev wlp58s0 proto kernel scope host src 192.168.44.211 /// broadcast 192.168.44.255 dev wlp58s0 proto kernel scope link src 192.168.44.211 /// ``` /// /// When the IP address `192.168.44.211` was configured on the `wlp58s0` interface, the kernel /// automatically added the appropriate routes: /// /// - a route for `192.168.44.211` for local unicast delivery to the IP address /// - a route for `192.168.44.255` for broadcast delivery to the broadcast address /// - a route for `192.168.44.0` for broadcast delivery to the network address /// /// When `127.0.0.1` was configured on the loopback interface, the same kind of routes were added to /// the local table. However, a loopback address receives a special treatment and the kernel also /// adds the whole subnet to the local table. /// /// Note that this is similar for IPv6: /// /// ```no_rust /// $ ip -6 route show table local /// local ::1 dev lo proto kernel metric 0 pref medium /// local fe80::7de1:4914:99b7:aa28 dev wlp58s0 proto kernel metric 0 pref medium /// ff00::/8 dev wlp58s0 metric 256 pref medium /// ``` /// /// # Source /// /// This documentation is adapted from [Vincent Bernat's excellent /// blog](https://vincent.bernat.ch/en/blog/2017-ipv4-route-lookup-linux#builtin-tables) pub const RT_TABLE_LOCAL: u8 = 255; /// If the route changes, notify the user via rtnetlink pub const RTM_F_NOTIFY: u32 = 256; /// This route is cloned. Cloned routes are routes coming from the cache instead of the FIB. For /// IPv4, the cache was removed in Linux 3.6 (see [IPv4 route lookup on Linux] for more information /// about IPv4 routing) /// /// [IPv4 route lookup on Linux]: https://vincent.bernat.ch/en/blog/2017-ipv4-route-lookup-linux pub const RTM_F_CLONED: u32 = 512; /// Multipath equalizer (not yet implemented) pub const RTM_F_EQUALIZE: u32 = 1024; /// Prefix addresses pub const RTM_F_PREFIX: u32 = 2048; /// Show the table from which the lookup result comes. Note that before commit `c36ba6603a11`, Linux /// would always hardcode [`RouteMessageHeader.table`] (known as `rtmsg.rtm_table` in the kernel) to /// `RT_TABLE_MAIN`. /// /// [`RouteMessageHeader.table`]: ../struct.RouteMessageHeader.html#structfield.table pub const RTM_F_LOOKUP_TABLE: u32 = 4096; /// Return the full FIB lookup match (see commit `b61798130f1be5bff08712308126c2d7ebe390ef`) pub const RTM_F_FIB_MATCH: u32 = 8192; pub const AF_UNSPEC: u16 = libc::AF_UNSPEC as u16; pub const AF_UNIX: u16 = libc::AF_UNIX as u16; // pub const AF_LOCAL: u16 = libc::AF_LOCAL as u16; pub const AF_INET: u16 = libc::AF_INET as u16; pub const AF_AX25: u16 = libc::AF_AX25 as u16; pub const AF_IPX: u16 = libc::AF_IPX as u16; pub const AF_APPLETALK: u16 = libc::AF_APPLETALK as u16; pub const AF_NETROM: u16 = libc::AF_NETROM as u16; pub const AF_BRIDGE: u16 = libc::AF_BRIDGE as u16; pub const AF_ATMPVC: u16 = libc::AF_ATMPVC as u16; pub const AF_X25: u16 = libc::AF_X25 as u16; pub const AF_INET6: u16 = libc::AF_INET6 as u16; pub const AF_ROSE: u16 = libc::AF_ROSE as u16; pub const AF_DECNET: u16 = libc::AF_DECnet as u16; pub const AF_NETBEUI: u16 = libc::AF_NETBEUI as u16; pub const AF_SECURITY: u16 = libc::AF_SECURITY as u16; pub const AF_KEY: u16 = libc::AF_KEY as u16; pub const AF_NETLINK: u16 = libc::AF_NETLINK as u16; // pub const AF_ROUTE: u16 = libc::AF_ROUTE as u16; pub const AF_PACKET: u16 = libc::AF_PACKET as u16; pub const AF_ASH: u16 = libc::AF_ASH as u16; pub const AF_ECONET: u16 = libc::AF_ECONET as u16; pub const AF_ATMSVC: u16 = libc::AF_ATMSVC as u16; pub const AF_RDS: u16 = libc::AF_RDS as u16; pub const AF_SNA: u16 = libc::AF_SNA as u16; pub const AF_IRDA: u16 = libc::AF_IRDA as u16; pub const AF_PPPOX: u16 = libc::AF_PPPOX as u16; pub const AF_WANPIPE: u16 = libc::AF_WANPIPE as u16; pub const AF_LLC: u16 = libc::AF_LLC as u16; pub const AF_CAN: u16 = libc::AF_CAN as u16; pub const AF_TIPC: u16 = libc::AF_TIPC as u16; pub const AF_BLUETOOTH: u16 = libc::AF_BLUETOOTH as u16; pub const AF_IUCV: u16 = libc::AF_IUCV as u16; pub const AF_RXRPC: u16 = libc::AF_RXRPC as u16; pub const AF_ISDN: u16 = libc::AF_ISDN as u16; pub const AF_PHONET: u16 = libc::AF_PHONET as u16; pub const AF_IEEE802154: u16 = libc::AF_IEEE802154 as u16; pub const AF_CAIF: u16 = libc::AF_CAIF as u16; pub const AF_ALG: u16 = libc::AF_ALG as u16; pub const NETNSA_NONE: u16 = 0; pub const NETNSA_NSID: u16 = 1; pub const NETNSA_PID: u16 = 2; pub const NETNSA_FD: u16 = 3; pub const NETNSA_NSID_NOT_ASSIGNED: i32 = -1; /// Neighbour cache entry state: the neighbour has not (yet) been resolved pub const NUD_INCOMPLETE: u16 = 1; /// Neighbour cache entry state: the neighbour entry is valid until its lifetime expires pub const NUD_REACHABLE: u16 = 2; /// Neighbour cache entry state: the neighbour entry is valid but suspicious pub const NUD_STALE: u16 = 4; /// Neighbour cache entry state: the validation of this entry is currently delayed pub const NUD_DELAY: u16 = 8; /// Neighbour cache entry state: the neighbour entry is being probed pub const NUD_PROBE: u16 = 16; /// Neighbour cache entry state: the validation of this entry has failed pub const NUD_FAILED: u16 = 32; /// Neighbour cache entry state: entry is valid and the kernel will not try to validate or refresh /// it. pub const NUD_NOARP: u16 = 64; /// Neighbour cache entry state: entry is valid forever and can only be removed explicitly from /// userspace. pub const NUD_PERMANENT: u16 = 128; /// Neighbour cache entry state: pseudo state for fresh entries or before deleting entries pub const NUD_NONE: u16 = 0; // Neighbour cache entry flags pub const NTF_USE: u8 = 1; pub const NTF_SELF: u8 = 2; pub const NTF_MASTER: u8 = 4; pub const NTF_PROXY: u8 = 8; pub const NTF_EXT_LEARNED: u8 = 16; pub const NTF_OFFLOADED: u8 = 32; pub const NTF_ROUTER: u8 = 128; pub const TCA_UNSPEC: u16 = 0; pub const TCA_KIND: u16 = 1; pub const TCA_OPTIONS: u16 = 2; pub const TCA_STATS: u16 = 3; pub const TCA_XSTATS: u16 = 4; pub const TCA_RATE: u16 = 5; pub const TCA_FCNT: u16 = 6; pub const TCA_STATS2: u16 = 7; pub const TCA_STAB: u16 = 8; pub const TCA_PAD: u16 = 9; pub const TCA_DUMP_INVISIBLE: u16 = 10; pub const TCA_CHAIN: u16 = 11; pub const TCA_HW_OFFLOAD: u16 = 12; pub const TCA_INGRESS_BLOCK: u16 = 13; pub const TCA_EGRESS_BLOCK: u16 = 14; pub const TCA_STATS_UNSPEC: u16 = 0; pub const TCA_STATS_BASIC: u16 = 1; pub const TCA_STATS_RATE_EST: u16 = 2; pub const TCA_STATS_QUEUE: u16 = 3; pub const TCA_STATS_APP: u16 = 4; pub const TCA_STATS_RATE_EST64: u16 = 5; pub const TCA_STATS_PAD: u16 = 6; pub const TCA_STATS_BASIC_HW: u16 = 7; pub const NDTA_UNSPEC: u16 = 0; pub const NDTA_NAME: u16 = 1; pub const NDTA_THRESH1: u16 = 2; pub const NDTA_THRESH2: u16 = 3; pub const NDTA_THRESH3: u16 = 4; pub const NDTA_CONFIG: u16 = 5; pub const NDTA_PARMS: u16 = 6; pub const NDTA_STATS: u16 = 7; pub const NDTA_GC_INTERVAL: u16 = 8; pub const NDTA_PAD: u16 = 9; pub const RTA_UNSPEC: u16 = 0; pub const RTA_DST: u16 = 1; pub const RTA_SRC: u16 = 2; pub const RTA_IIF: u16 = 3; pub const RTA_OIF: u16 = 4; pub const RTA_GATEWAY: u16 = 5; pub const RTA_PRIORITY: u16 = 6; pub const RTA_PREFSRC: u16 = 7; pub const RTA_METRICS: u16 = 8; pub const RTA_MULTIPATH: u16 = 9; pub const RTA_PROTOINFO: u16 = 10; pub const RTA_FLOW: u16 = 11; pub const RTA_CACHEINFO: u16 = 12; pub const RTA_SESSION: u16 = 13; pub const RTA_MP_ALGO: u16 = 14; pub const RTA_TABLE: u16 = 15; pub const RTA_MARK: u16 = 16; pub const RTA_MFC_STATS: u16 = 17; pub const RTA_VIA: u16 = 18; pub const RTA_NEWDST: u16 = 19; pub const RTA_PREF: u16 = 20; pub const RTA_ENCAP_TYPE: u16 = 21; pub const RTA_ENCAP: u16 = 22; pub const RTA_EXPIRES: u16 = 23; pub const RTA_PAD: u16 = 24; pub const RTA_UID: u16 = 25; pub const RTA_TTL_PROPAGATE: u16 = 26; pub const RTAX_UNSPEC: u16 = 0; pub const RTAX_LOCK: u16 = 1; pub const RTAX_MTU: u16 = 2; pub const RTAX_WINDOW: u16 = 3; pub const RTAX_RTT: u16 = 4; pub const RTAX_RTTVAR: u16 = 5; pub const RTAX_SSTHRESH: u16 = 6; pub const RTAX_CWND: u16 = 7; pub const RTAX_ADVMSS: u16 = 8; pub const RTAX_REORDERING: u16 = 9; pub const RTAX_HOPLIMIT: u16 = 10; pub const RTAX_INITCWND: u16 = 11; pub const RTAX_FEATURES: u16 = 12; pub const RTAX_RTO_MIN: u16 = 13; pub const RTAX_INITRWND: u16 = 14; pub const RTAX_QUICKACK: u16 = 15; pub const RTAX_CC_ALGO: u16 = 16; pub const RTAX_FASTOPEN_NO_COOKIE: u16 = 17; pub const IFLA_INFO_UNSPEC: u16 = 0; pub const IFLA_INFO_KIND: u16 = 1; pub const IFLA_INFO_DATA: u16 = 2; pub const IFLA_INFO_XSTATS: u16 = 3; pub const IFLA_INFO_SLAVE_KIND: u16 = 4; pub const IFLA_INFO_SLAVE_DATA: u16 = 5; // Bridge flags pub const IFLA_BRIDGE_FLAGS: u16 = 0; pub const BRIDGE_FLAGS_MASTER: u16 = 1; /* Bridge command to/from master */ pub const BRIDGE_FLAGS_SELF: u16 = 2; /* Bridge command to/from lowerdev */ pub const IFLA_BRIDGE_VLAN_INFO: u16 = 2; pub const BRIDGE_VLAN_INFO_MASTER: u16 = 1; pub const BRIDGE_VLAN_INFO_PVID: u16 = 4; pub const BRIDGE_VLAN_INFO_UNTAGGED: u16 = 8; pub const BRIDGE_VLAN_INFO_RANGE_BEGIN: u16 = 16; pub const BRIDGE_VLAN_INFO_RANGE_END: u16 = 32; pub const IFLA_BR_UNSPEC: u16 = 0; pub const IFLA_BR_FORWARD_DELAY: u16 = 1; pub const IFLA_BR_HELLO_TIME: u16 = 2; pub const IFLA_BR_MAX_AGE: u16 = 3; pub const IFLA_BR_AGEING_TIME: u16 = 4; pub const IFLA_BR_STP_STATE: u16 = 5; pub const IFLA_BR_PRIORITY: u16 = 6; pub const IFLA_BR_VLAN_FILTERING: u16 = 7; pub const IFLA_BR_VLAN_PROTOCOL: u16 = 8; pub const IFLA_BR_GROUP_FWD_MASK: u16 = 9; pub const IFLA_BR_ROOT_ID: u16 = 10; pub const IFLA_BR_BRIDGE_ID: u16 = 11; pub const IFLA_BR_ROOT_PORT: u16 = 12; pub const IFLA_BR_ROOT_PATH_COST: u16 = 13; pub const IFLA_BR_TOPOLOGY_CHANGE: u16 = 14; pub const IFLA_BR_TOPOLOGY_CHANGE_DETECTED: u16 = 15; pub const IFLA_BR_HELLO_TIMER: u16 = 16; pub const IFLA_BR_TCN_TIMER: u16 = 17; pub const IFLA_BR_TOPOLOGY_CHANGE_TIMER: u16 = 18; pub const IFLA_BR_GC_TIMER: u16 = 19; pub const IFLA_BR_GROUP_ADDR: u16 = 20; pub const IFLA_BR_FDB_FLUSH: u16 = 21; pub const IFLA_BR_MCAST_ROUTER: u16 = 22; pub const IFLA_BR_MCAST_SNOOPING: u16 = 23; pub const IFLA_BR_MCAST_QUERY_USE_IFADDR: u16 = 24; pub const IFLA_BR_MCAST_QUERIER: u16 = 25; pub const IFLA_BR_MCAST_HASH_ELASTICITY: u16 = 26; pub const IFLA_BR_MCAST_HASH_MAX: u16 = 27; pub const IFLA_BR_MCAST_LAST_MEMBER_CNT: u16 = 28; pub const IFLA_BR_MCAST_STARTUP_QUERY_CNT: u16 = 29; pub const IFLA_BR_MCAST_LAST_MEMBER_INTVL: u16 = 30; pub const IFLA_BR_MCAST_MEMBERSHIP_INTVL: u16 = 31; pub const IFLA_BR_MCAST_QUERIER_INTVL: u16 = 32; pub const IFLA_BR_MCAST_QUERY_INTVL: u16 = 33; pub const IFLA_BR_MCAST_QUERY_RESPONSE_INTVL: u16 = 34; pub const IFLA_BR_MCAST_STARTUP_QUERY_INTVL: u16 = 35; pub const IFLA_BR_NF_CALL_IPTABLES: u16 = 36; pub const IFLA_BR_NF_CALL_IP6TABLES: u16 = 37; pub const IFLA_BR_NF_CALL_ARPTABLES: u16 = 38; pub const IFLA_BR_VLAN_DEFAULT_PVID: u16 = 39; pub const IFLA_BR_PAD: u16 = 40; pub const IFLA_BR_VLAN_STATS_ENABLED: u16 = 41; pub const IFLA_BR_MCAST_STATS_ENABLED: u16 = 42; pub const IFLA_BR_MCAST_IGMP_VERSION: u16 = 43; pub const IFLA_BR_MCAST_MLD_VERSION: u16 = 44; pub const IFLA_BR_VLAN_STATS_PER_PORT: u16 = 45; pub const IFLA_BR_MULTI_BOOLOPT: u16 = 46; pub const IFLA_MACVLAN_UNSPEC: u16 = 0; pub const IFLA_MACVLAN_MODE: u16 = 1; pub const IFLA_MACVLAN_FLAGS: u16 = 2; pub const IFLA_MACVLAN_MACADDR_MODE: u16 = 3; pub const IFLA_MACVLAN_MACADDR: u16 = 4; pub const IFLA_MACVLAN_MACADDR_DATA: u16 = 5; pub const IFLA_MACVLAN_MACADDR_COUNT: u16 = 6; pub const IFLA_VLAN_UNSPEC: u16 = 0; pub const IFLA_VLAN_ID: u16 = 1; pub const IFLA_VLAN_FLAGS: u16 = 2; pub const IFLA_VLAN_EGRESS_QOS: u16 = 3; pub const IFLA_VLAN_INGRESS_QOS: u16 = 4; pub const IFLA_VLAN_PROTOCOL: u16 = 5; pub const IFLA_VRF_UNSPEC: u16 = 0; pub const IFLA_VRF_TABLE: u16 = 1; pub const IFLA_IPVLAN_UNSPEC: u16 = 0; pub const IFLA_IPVLAN_MODE: u16 = 1; pub const IFLA_IPVLAN_FLAGS: u16 = 2; pub const IFLA_IPOIB_UNSPEC: u16 = 0; pub const IFLA_IPOIB_PKEY: u16 = 1; pub const IFLA_IPOIB_MODE: u16 = 2; pub const IFLA_IPOIB_UMCAST: u16 = 3; pub const VETH_INFO_UNSPEC: u16 = 0; pub const VETH_INFO_PEER: u16 = 1; pub const ARPHRD_NETROM: u16 = 0; pub const ARPHRD_ETHER: u16 = 1; pub const ARPHRD_EETHER: u16 = 2; pub const ARPHRD_AX25: u16 = 3; pub const ARPHRD_PRONET: u16 = 4; pub const ARPHRD_CHAOS: u16 = 5; pub const ARPHRD_IEEE802: u16 = 6; pub const ARPHRD_ARCNET: u16 = 7; pub const ARPHRD_APPLETLK: u16 = 8; pub const ARPHRD_DLCI: u16 = 15; pub const ARPHRD_ATM: u16 = 19; pub const ARPHRD_METRICOM: u16 = 23; pub const ARPHRD_IEEE1394: u16 = 24; pub const ARPHRD_EUI64: u16 = 27; pub const ARPHRD_INFINIBAND: u16 = 32; pub const ARPHRD_SLIP: u16 = 256; pub const ARPHRD_CSLIP: u16 = 257; pub const ARPHRD_SLIP6: u16 = 258; pub const ARPHRD_CSLIP6: u16 = 259; pub const ARPHRD_RSRVD: u16 = 260; pub const ARPHRD_ADAPT: u16 = 264; pub const ARPHRD_ROSE: u16 = 270; pub const ARPHRD_X25: u16 = 271; pub const ARPHRD_HWX25: u16 = 272; pub const ARPHRD_CAN: u16 = 280; pub const ARPHRD_PPP: u16 = 512; pub const ARPHRD_CISCO: u16 = 513; pub const ARPHRD_HDLC: u16 = 513; pub const ARPHRD_LAPB: u16 = 516; pub const ARPHRD_DDCMP: u16 = 517; pub const ARPHRD_RAWHDLC: u16 = 518; pub const ARPHRD_RAWIP: u16 = 519; pub const ARPHRD_TUNNEL: u16 = 768; pub const ARPHRD_TUNNEL6: u16 = 769; pub const ARPHRD_FRAD: u16 = 770; pub const ARPHRD_SKIP: u16 = 771; pub const ARPHRD_LOOPBACK: u16 = 772; pub const ARPHRD_LOCALTLK: u16 = 773; pub const ARPHRD_FDDI: u16 = 774; pub const ARPHRD_BIF: u16 = 775; pub const ARPHRD_SIT: u16 = 776; pub const ARPHRD_IPDDP: u16 = 777; pub const ARPHRD_IPGRE: u16 = 778; pub const ARPHRD_PIMREG: u16 = 779; pub const ARPHRD_HIPPI: u16 = 780; pub const ARPHRD_ASH: u16 = 781; pub const ARPHRD_ECONET: u16 = 782; pub const ARPHRD_IRDA: u16 = 783; pub const ARPHRD_FCPP: u16 = 784; pub const ARPHRD_FCAL: u16 = 785; pub const ARPHRD_FCPL: u16 = 786; pub const ARPHRD_FCFABRIC: u16 = 787; pub const ARPHRD_IEEE802_TR: u16 = 800; pub const ARPHRD_IEEE80211: u16 = 801; pub const ARPHRD_IEEE80211_PRISM: u16 = 802; pub const ARPHRD_IEEE80211_RADIOTAP: u16 = 803; pub const ARPHRD_IEEE802154: u16 = 804; pub const ARPHRD_IEEE802154_MONITOR: u16 = 805; pub const ARPHRD_PHONET: u16 = 820; pub const ARPHRD_PHONET_PIPE: u16 = 821; pub const ARPHRD_CAIF: u16 = 822; pub const ARPHRD_IP6GRE: u16 = 823; pub const ARPHRD_NETLINK: u16 = 824; pub const ARPHRD_6LOWPAN: u16 = 825; pub const ARPHRD_VSOCKMON: u16 = 826; pub const ARPHRD_VOID: u16 = 65535; pub const ARPHRD_NONE: u16 = 65534; pub const IFA_UNSPEC: u16 = 0; pub const IFA_ADDRESS: u16 = 1; pub const IFA_LOCAL: u16 = 2; pub const IFA_LABEL: u16 = 3; pub const IFA_BROADCAST: u16 = 4; pub const IFA_ANYCAST: u16 = 5; pub const IFA_CACHEINFO: u16 = 6; pub const IFA_MULTICAST: u16 = 7; pub const IFA_FLAGS: u16 = 8; pub const IFLA_UNSPEC: u16 = 0; pub const IFLA_ADDRESS: u16 = 1; pub const IFLA_BROADCAST: u16 = 2; pub const IFLA_IFNAME: u16 = 3; pub const IFLA_MTU: u16 = 4; pub const IFLA_LINK: u16 = 5; pub const IFLA_QDISC: u16 = 6; pub const IFLA_STATS: u16 = 7; pub const IFLA_COST: u16 = 8; pub const IFLA_PRIORITY: u16 = 9; pub const IFLA_MASTER: u16 = 10; pub const IFLA_WIRELESS: u16 = 11; pub const IFLA_PROTINFO: u16 = 12; pub const IFLA_TXQLEN: u16 = 13; pub const IFLA_MAP: u16 = 14; pub const IFLA_WEIGHT: u16 = 15; pub const IFLA_OPERSTATE: u16 = 16; pub const IFLA_LINKMODE: u16 = 17; pub const IFLA_LINKINFO: u16 = 18; pub const IFLA_NET_NS_PID: u16 = 19; pub const IFLA_IFALIAS: u16 = 20; pub const IFLA_NUM_VF: u16 = 21; pub const IFLA_VFINFO_LIST: u16 = 22; pub const IFLA_STATS64: u16 = 23; pub const IFLA_VF_PORTS: u16 = 24; pub const IFLA_PORT_SELF: u16 = 25; pub const IFLA_AF_SPEC: u16 = 26; pub const IFLA_GROUP: u16 = 27; pub const IFLA_NET_NS_FD: u16 = 28; pub const IFLA_EXT_MASK: u16 = 29; pub const IFLA_PROMISCUITY: u16 = 30; pub const IFLA_NUM_TX_QUEUES: u16 = 31; pub const IFLA_NUM_RX_QUEUES: u16 = 32; pub const IFLA_CARRIER: u16 = 33; pub const IFLA_PHYS_PORT_ID: u16 = 34; pub const IFLA_CARRIER_CHANGES: u16 = 35; pub const IFLA_PHYS_SWITCH_ID: u16 = 36; pub const IFLA_LINK_NETNSID: u16 = 37; pub const IFLA_PHYS_PORT_NAME: u16 = 38; pub const IFLA_PROTO_DOWN: u16 = 39; pub const IFLA_GSO_MAX_SEGS: u16 = 40; pub const IFLA_GSO_MAX_SIZE: u16 = 41; pub const IFLA_PAD: u16 = 42; pub const IFLA_XDP: u16 = 43; pub const IFLA_EVENT: u16 = 44; pub const IFLA_NEW_NETNSID: u16 = 45; pub const IFLA_IF_NETNSID: u16 = 46; pub const IFLA_CARRIER_UP_COUNT: u16 = 47; pub const IFLA_CARRIER_DOWN_COUNT: u16 = 48; pub const IFLA_NEW_IFINDEX: u16 = 49; pub const IFLA_MIN_MTU: u16 = 50; pub const IFLA_MAX_MTU: u16 = 51; pub const IFLA_PROP_LIST: u16 = 52; pub const IFLA_ALT_IFNAME: u16 = 53; pub const IFLA_PERM_ADDRESS: u16 = 54; pub const IFLA_PROTO_DOWN_REASON: u16 = 55; pub const IFLA_INET_UNSPEC: u16 = 0; pub const IFLA_INET_CONF: u16 = 1; pub const IFLA_INET6_UNSPEC: u16 = 0; pub const IFLA_INET6_FLAGS: u16 = 1; pub const IFLA_INET6_CONF: u16 = 2; pub const IFLA_INET6_STATS: u16 = 3; // pub const IFLA_INET6_MCAST: u16 = 4; pub const IFLA_INET6_CACHEINFO: u16 = 5; pub const IFLA_INET6_ICMP6STATS: u16 = 6; pub const IFLA_INET6_TOKEN: u16 = 7; pub const IFLA_INET6_ADDR_GEN_MODE: u16 = 8; /// Link is up (administratively). pub const IFF_UP: u32 = libc::IFF_UP as u32; /// Link is up and carrier is OK (RFC2863 OPER_UP) pub const IFF_RUNNING: u32 = libc::IFF_RUNNING as u32; /// Link layer is operational pub const IFF_LOWER_UP: u32 = libc::IFF_LOWER_UP as u32; /// Driver signals IFF_DORMANT pub const IFF_DORMANT: u32 = libc::IFF_DORMANT as u32; /// Link supports broadcasting pub const IFF_BROADCAST: u32 = libc::IFF_BROADCAST as u32; /// Link supports multicasting pub const IFF_MULTICAST: u32 = libc::IFF_MULTICAST as u32; /// Link supports multicast routing pub const IFF_ALLMULTI: u32 = libc::IFF_ALLMULTI as u32; /// Tell driver to do debugging (currently unused) pub const IFF_DEBUG: u32 = libc::IFF_DEBUG as u32; /// Link loopback network pub const IFF_LOOPBACK: u32 = libc::IFF_LOOPBACK as u32; /// u32erface is point-to-point link pub const IFF_POINTOPOINT: u32 = libc::IFF_POINTOPOINT as u32; /// ARP is not supported pub const IFF_NOARP: u32 = libc::IFF_NOARP as u32; /// Receive all packets. pub const IFF_PROMISC: u32 = libc::IFF_PROMISC as u32; /// Master of a load balancer (bonding) pub const IFF_MASTER: u32 = libc::IFF_MASTER as u32; /// Slave of a load balancer pub const IFF_SLAVE: u32 = libc::IFF_SLAVE as u32; /// Link selects port automatically (only used by ARM ethernet) pub const IFF_PORTSEL: u32 = libc::IFF_PORTSEL as u32; /// Driver supports setting media type (only used by ARM ethernet) pub const IFF_AUTOMEDIA: u32 = libc::IFF_AUTOMEDIA as u32; // /// Echo sent packets (testing feature, CAN only) // pub const IFF_ECHO: u32 = libc::IFF_ECHO as u32; // /// Dialup device with changing addresses (unused, BSD compatibility) // pub const IFF_DYNAMIC: u32 = libc::IFF_DYNAMIC as u32; // /// Avoid use of trailers (unused, BSD compatibility) // pub const IFF_NOTRAILERS: u32 = libc::IFF_NOTRAILERS as u32; pub const IF_OPER_UNKNOWN: u8 = 0; pub const IF_OPER_NOTPRESENT: u8 = 1; pub const IF_OPER_DOWN: u8 = 2; pub const IF_OPER_LOWERLAYERDOWN: u8 = 3; pub const IF_OPER_TESTING: u8 = 4; pub const IF_OPER_DORMANT: u8 = 5; pub const IF_OPER_UP: u8 = 6; /// Neighbour cache entry type: unknown type pub const NDA_UNSPEC: u16 = 0; /// Neighbour cache entry type: entry for a network layer destination /// address pub const NDA_DST: u16 = 1; /// Neighbour cache entry type: entry for a link layer destination /// address pub const NDA_LLADDR: u16 = 2; /// Neighbour cache entry type: entry for cache statistics pub const NDA_CACHEINFO: u16 = 3; pub const NDA_PROBES: u16 = 4; pub const NDA_VLAN: u16 = 5; pub const NDA_PORT: u16 = 6; pub const NDA_VNI: u16 = 7; pub const NDA_IFINDEX: u16 = 8; pub const NDA_MASTER: u16 = 9; pub const NDA_LINK_NETNSID: u16 = 10; pub const NDA_SRC_VNI: u16 = 11; /// see `https://github.com/torvalds/linux/blob/master/include/uapi/linux/fib_rules.h` pub const FR_ACT_UNSPEC: u8 = 0; /// Pass to fixed table pub const FR_ACT_TO_TBL: u8 = 1; /// Jump to another rule pub const FR_ACT_GOTO: u8 = 2; /// No operation pub const FR_ACT_NOP: u8 = 3; pub const FR_ACT_RES3: u8 = 4; pub const FR_ACT_RES4: u8 = 5; /// Drop without notification pub const FR_ACT_BLACKHOLE: u8 = 6; /// Drop with `ENETUNREACH` pub const FR_ACT_UNREACHABLE: u8 = 7; /// Drop with `EACCES` pub const FR_ACT_PROHIBIT: u8 = 8; pub const FRA_UNSPEC: u16 = 0; /// Destination address pub const FRA_DST: u16 = 1; /// Source address pub const FRA_SRC: u16 = 2; /// Interface name pub const FRA_IIFNAME: u16 = 3; /// Target to jump to pub const FRA_GOTO: u16 = 4; pub const FRA_UNUSED2: u16 = 5; /// priority/preference pub const FRA_PRIORITY: u16 = 6; pub const FRA_UNUSED3: u16 = 7; pub const FRA_UNUSED4: u16 = 8; pub const FRA_UNUSED5: u16 = 9; /// mark pub const FRA_FWMARK: u16 = 10; /// flow/class id pub const FRA_FLOW: u16 = 11; pub const FRA_TUN_ID: u16 = 12; pub const FRA_SUPPRESS_IFGROUP: u16 = 13; pub const FRA_SUPPRESS_PREFIXLEN: u16 = 14; /// Extended table id pub const FRA_TABLE: u16 = 15; /// mask for netfilter mark pub const FRA_FWMASK: u16 = 16; pub const FRA_OIFNAME: u16 = 17; pub const FRA_PAD: u16 = 18; /// iif or oif is l3mdev goto its table pub const FRA_L3MDEV: u16 = 19; /// UID range pub const FRA_UID_RANGE: u16 = 20; /// Originator of the rule pub const FRA_PROTOCOL: u16 = 21; /// IP protocol pub const FRA_IP_PROTO: u16 = 22; /// Source port pub const FRA_SPORT_RANGE: u16 = 23; /// Destination port pub const FRA_DPORT_RANGE: u16 = 24; pub const FIB_RULE_PERMANENT: u32 = 1; pub const FIB_RULE_INVERT: u32 = 2; pub const FIB_RULE_UNRESOLVED: u32 = 4; pub const FIB_RULE_IIF_DETACHED: u32 = 8; pub const FIB_RULE_DEV_DETACHED: u32 = FIB_RULE_IIF_DETACHED; pub const FIB_RULE_OIF_DETACHED: u32 = 10; /// try to find source address in routing lookups pub const FIB_RULE_FIND_SADDR: u32 = 10000; // pub const MACVLAN_FLAG_NOPROMISC: int = 1; // pub const IPVLAN_F_PRIVATE: int = 1; // pub const IPVLAN_F_VEPA: int = 2; // pub const MAX_VLAN_LIST_LEN: int = 1; // pub const PORT_PROFILE_MAX: int = 40; // pub const PORT_UUID_MAX: int = 16; // pub const PORT_SELF_VF: int = -1; // pub const XDP_FLAGS_UPDATE_IF_NOEXIST: int = 1; // pub const XDP_FLAGS_SKB_MODE: int = 2; // pub const XDP_FLAGS_DRV_MODE: int = 4; // pub const XDP_FLAGS_HW_MODE: int = 8; // pub const XDP_FLAGS_MODES: int = 14; // pub const XDP_FLAGS_MASK: int = 15; pub const IFA_F_SECONDARY: u32 = 1; pub const IFA_F_TEMPORARY: u32 = 1; pub const IFA_F_NODAD: u32 = 2; pub const IFA_F_OPTIMISTIC: u32 = 4; pub const IFA_F_DADFAILED: u32 = 8; pub const IFA_F_HOMEADDRESS: u32 = 16; pub const IFA_F_DEPRECATED: u32 = 32; pub const IFA_F_TENTATIVE: u32 = 64; pub const IFA_F_PERMANENT: u32 = 128; pub const IFA_F_MANAGETEMPADDR: u32 = 256; pub const IFA_F_NOPREFIXROUTE: u32 = 512; pub const IFA_F_MCAUTOJOIN: u32 = 1024; pub const IFA_F_STABLE_PRIVACY: u32 = 2048; // pub const RTNL_FAMILY_IPMR: int = 128; // pub const RTNL_FAMILY_IP6MR: int = 129; // pub const RTNL_FAMILY_MAX: int = 129; // pub const RTA_ALIGNTO: int = 4; // pub const RTNH_F_DEAD: u8 = 1; pub const RTNH_F_PERVASIVE: u8 = 2; pub const RTNH_F_ONLINK: u8 = 4; pub const RTNH_F_OFFLOAD: u8 = 8; pub const RTNH_F_LINKDOWN: u8 = 16; pub const RTNH_F_UNRESOLVED: u8 = 32; // pub const RTNH_COMPARE_MASK: int = 25; // pub const RTNH_ALIGNTO: int = 4; // pub const RTNETLINK_HAVE_PEERINFO: int = 1; // pub const RTAX_FEATURE_ECN: int = 1; // pub const RTAX_FEATURE_SACK: int = 2; // pub const RTAX_FEATURE_TIMESTAMP: int = 4; // pub const RTAX_FEATURE_ALLFRAG: int = 8; // pub const RTAX_FEATURE_MASK: int = 15; pub const TCM_IFINDEX_MAGIC_BLOCK: u32 = 0xffff_ffff; // pub const TCA_FLAG_LARGE_DUMP_ON: int = 1; pub const RTEXT_FILTER_VF: u32 = 1; pub const RTEXT_FILTER_BRVLAN: u32 = 2; pub const RTEXT_FILTER_BRVLAN_COMPRESSED: u32 = 4; pub const RTEXT_FILTER_SKIP_STATS: u32 = 8; // pub const ARPOP_REQUEST: int = 1; // pub const ARPOP_REPLY: int = 2; // // pub const IN6_ADDR_GEN_MODE_EUI64: int = 0; // pub const IN6_ADDR_GEN_MODE_NONE: int = 1; // pub const IN6_ADDR_GEN_MODE_STABLE_PRIVACY: int = 2; // pub const IN6_ADDR_GEN_MODE_RANDOM: int = 3; // // pub const BRIDGE_MODE_UNSPEC: int = 0; // pub const BRIDGE_MODE_HAIRPIN: int = 1; // // pub const IFLA_BRPORT_UNSPEC: int = 0; // pub const IFLA_BRPORT_STATE: int = 1; // pub const IFLA_BRPORT_PRIORITY: int = 2; // pub const IFLA_BRPORT_COST: int = 3; // pub const IFLA_BRPORT_MODE: int = 4; // pub const IFLA_BRPORT_GUARD: int = 5; // pub const IFLA_BRPORT_PROTECT: int = 6; // pub const IFLA_BRPORT_FAST_LEAVE: int = 7; // pub const IFLA_BRPORT_LEARNING: int = 8; // pub const IFLA_BRPORT_UNICAST_FLOOD: int = 9; // pub const IFLA_BRPORT_PROXYARP: int = 10; // pub const IFLA_BRPORT_LEARNING_SYNC: int = 11; // pub const IFLA_BRPORT_PROXYARP_WIFI: int = 12; // pub const IFLA_BRPORT_ROOT_ID: int = 13; // pub const IFLA_BRPORT_BRIDGE_ID: int = 14; // pub const IFLA_BRPORT_DESIGNATED_PORT: int = 15; // pub const IFLA_BRPORT_DESIGNATED_COST: int = 16; // pub const IFLA_BRPORT_ID: int = 17; // pub const IFLA_BRPORT_NO: int = 18; // pub const IFLA_BRPORT_TOPOLOGY_CHANGE_ACK: int = 19; // pub const IFLA_BRPORT_CONFIG_PENDING: int = 20; // pub const IFLA_BRPORT_MESSAGE_AGE_TIMER: int = 21; // pub const IFLA_BRPORT_FORWARD_DELAY_TIMER: int = 22; // pub const IFLA_BRPORT_HOLD_TIMER: int = 23; // pub const IFLA_BRPORT_FLUSH: int = 24; // pub const IFLA_BRPORT_MULTICAST_ROUTER: int = 25; // pub const IFLA_BRPORT_PAD: int = 26; // pub const IFLA_BRPORT_MCAST_FLOOD: int = 27; // pub const IFLA_BRPORT_MCAST_TO_UCAST: int = 28; // pub const IFLA_BRPORT_VLAN_TUNNEL: int = 29; // pub const IFLA_BRPORT_BCAST_FLOOD: int = 30; // pub const IFLA_BRPORT_GROUP_FWD_MASK: int = 31; // pub const IFLA_BRPORT_NEIGH_SUPPRESS: int = 32; // // pub const IFLA_VLAN_QOS_UNSPEC: int = 0; // pub const IFLA_VLAN_QOS_MAPPING: int = 1; // // pub const IFLA_MACVLAN_UNSPEC: int = 0; // pub const IFLA_MACVLAN_MODE: int = 1; // pub const IFLA_MACVLAN_FLAGS: int = 2; // pub const IFLA_MACVLAN_MACADDR_MODE: int = 3; // pub const IFLA_MACVLAN_MACADDR: int = 4; // pub const IFLA_MACVLAN_MACADDR_DATA: int = 5; // pub const IFLA_MACVLAN_MACADDR_COUNT: int = 6; // // Available MACVLAN MODES pub const MACVLAN_MODE_PRIVATE: u32 = 1; pub const MACVLAN_MODE_VEPA: u32 = 2; pub const MACVLAN_MODE_BRIDGE: u32 = 4; pub const MACVLAN_MODE_PASSTHRU: u32 = 8; pub const MACVLAN_MODE_SOURCE: u32 = 16; // // pub const MACVLAN_MACADDR_ADD: int = 0; // pub const MACVLAN_MACADDR_DEL: int = 1; // pub const MACVLAN_MACADDR_FLUSH: int = 2; // pub const MACVLAN_MACADDR_SET: int = 3; // // pub const IFLA_VRF_UNSPEC: int = 0; // pub const IFLA_VRF_TABLE: int = 1; // // pub const IFLA_VRF_PORT_UNSPEC: int = 0; // pub const IFLA_VRF_PORT_TABLE: int = 1; // // pub const IFLA_MACSEC_UNSPEC: int = 0; // pub const IFLA_MACSEC_SCI: int = 1; // pub const IFLA_MACSEC_PORT: int = 2; // pub const IFLA_MACSEC_ICV_LEN: int = 3; // pub const IFLA_MACSEC_CIPHER_SUITE: int = 4; // pub const IFLA_MACSEC_WINDOW: int = 5; // pub const IFLA_MACSEC_ENCODING_SA: int = 6; // pub const IFLA_MACSEC_ENCRYPT: int = 7; // pub const IFLA_MACSEC_PROTECT: int = 8; // pub const IFLA_MACSEC_INC_SCI: int = 9; // pub const IFLA_MACSEC_ES: int = 10; // pub const IFLA_MACSEC_SCB: int = 11; // pub const IFLA_MACSEC_REPLAY_PROTECT: int = 12; // pub const IFLA_MACSEC_VALIDATION: int = 13; // pub const IFLA_MACSEC_PAD: int = 14; // // pub const MACSEC_VALIDATE_DISABLED: int = 0; // pub const MACSEC_VALIDATE_CHECK: int = 1; // pub const MACSEC_VALIDATE_STRICT: int = 2; // pub const MACSEC_VALIDATE_MAX: int = 2; // // pub const IFLA_IPVLAN_UNSPEC: int = 0; // pub const IFLA_IPVLAN_MODE: int = 1; // pub const IFLA_IPVLAN_FLAGS: int = 2; // // pub const IPVLAN_MODE_L2: int = 0; // pub const IPVLAN_MODE_L3: int = 1; // pub const IPVLAN_MODE_L3S: int = 2; // pub const IPVLAN_MODE_MAX: int = 3; // // FROM https://elixir.bootlin.com/linux/v5.9.8/source/include/uapi/linux/if_link.h#L531 pub const IFLA_VXLAN_UNSPEC: u16 = 0; pub const IFLA_VXLAN_ID: u16 = 1; pub const IFLA_VXLAN_GROUP: u16 = 2; pub const IFLA_VXLAN_LINK: u16 = 3; pub const IFLA_VXLAN_LOCAL: u16 = 4; pub const IFLA_VXLAN_TTL: u16 = 5; pub const IFLA_VXLAN_TOS: u16 = 6; pub const IFLA_VXLAN_LEARNING: u16 = 7; pub const IFLA_VXLAN_AGEING: u16 = 8; pub const IFLA_VXLAN_LIMIT: u16 = 9; pub const IFLA_VXLAN_PORT_RANGE: u16 = 10; pub const IFLA_VXLAN_PROXY: u16 = 11; pub const IFLA_VXLAN_RSC: u16 = 12; pub const IFLA_VXLAN_L2MISS: u16 = 13; pub const IFLA_VXLAN_L3MISS: u16 = 14; pub const IFLA_VXLAN_PORT: u16 = 15; pub const IFLA_VXLAN_GROUP6: u16 = 16; pub const IFLA_VXLAN_LOCAL6: u16 = 17; pub const IFLA_VXLAN_UDP_CSUM: u16 = 18; pub const IFLA_VXLAN_UDP_ZERO_CSUM6_TX: u16 = 19; pub const IFLA_VXLAN_UDP_ZERO_CSUM6_RX: u16 = 20; pub const IFLA_VXLAN_REMCSUM_TX: u16 = 21; pub const IFLA_VXLAN_REMCSUM_RX: u16 = 22; pub const IFLA_VXLAN_GBP: u16 = 23; pub const IFLA_VXLAN_REMCSUM_NOPARTIAL: u16 = 24; pub const IFLA_VXLAN_COLLECT_METADATA: u16 = 25; pub const IFLA_VXLAN_LABEL: u16 = 26; pub const IFLA_VXLAN_GPE: u16 = 27; pub const IFLA_VXLAN_TTL_INHERIT: u16 = 28; pub const IFLA_VXLAN_DF: u16 = 29; pub const __IFLA_VXLAN_MAX: u16 = 30; // // pub const IFLA_GENEVE_UNSPEC: int = 0; // pub const IFLA_GENEVE_ID: int = 1; // pub const IFLA_GENEVE_REMOTE: int = 2; // pub const IFLA_GENEVE_TTL: int = 3; // pub const IFLA_GENEVE_TOS: int = 4; // pub const IFLA_GENEVE_PORT: int = 5; // pub const IFLA_GENEVE_COLLECT_METADATA: int = 6; // pub const IFLA_GENEVE_REMOTE6: int = 7; // pub const IFLA_GENEVE_UDP_CSUM: int = 8; // pub const IFLA_GENEVE_UDP_ZERO_CSUM6_TX: int = 9; // pub const IFLA_GENEVE_UDP_ZERO_CSUM6_RX: int = 10; // pub const IFLA_GENEVE_LABEL: int = 11; // // pub const IFLA_PPP_UNSPEC: int = 0; // pub const IFLA_PPP_DEV_FD: int = 1; // // pub const GTP_ROLE_GGSN: int = 0; // pub const GTP_ROLE_SGSN: int = 1; // // pub const IFLA_GTP_UNSPEC: int = 0; // pub const IFLA_GTP_FD0: int = 1; // pub const IFLA_GTP_FD1: int = 2; // pub const IFLA_GTP_PDP_HASHSIZE: int = 3; // pub const IFLA_GTP_ROLE: int = 4; pub const IFLA_BOND_UNSPEC: u16 = 0; pub const IFLA_BOND_MODE: u16 = 1; pub const IFLA_BOND_ACTIVE_SLAVE: u16 = 2; pub const IFLA_BOND_MIIMON: u16 = 3; pub const IFLA_BOND_UPDELAY: u16 = 4; pub const IFLA_BOND_DOWNDELAY: u16 = 5; pub const IFLA_BOND_USE_CARRIER: u16 = 6; pub const IFLA_BOND_ARP_INTERVAL: u16 = 7; pub const IFLA_BOND_ARP_IP_TARGET: u16 = 8; pub const IFLA_BOND_ARP_VALIDATE: u16 = 9; pub const IFLA_BOND_ARP_ALL_TARGETS: u16 = 10; pub const IFLA_BOND_PRIMARY: u16 = 11; pub const IFLA_BOND_PRIMARY_RESELECT: u16 = 12; pub const IFLA_BOND_FAIL_OVER_MAC: u16 = 13; pub const IFLA_BOND_XMIT_HASH_POLICY: u16 = 14; pub const IFLA_BOND_RESEND_IGMP: u16 = 15; pub const IFLA_BOND_NUM_PEER_NOTIF: u16 = 16; pub const IFLA_BOND_ALL_SLAVES_ACTIVE: u16 = 17; pub const IFLA_BOND_MIN_LINKS: u16 = 18; pub const IFLA_BOND_LP_INTERVAL: u16 = 19; pub const IFLA_BOND_PACKETS_PER_SLAVE: u16 = 20; pub const IFLA_BOND_AD_LACP_RATE: u16 = 21; pub const IFLA_BOND_AD_SELECT: u16 = 22; pub const IFLA_BOND_AD_INFO: u16 = 23; pub const IFLA_BOND_AD_ACTOR_SYS_PRIO: u16 = 24; pub const IFLA_BOND_AD_USER_PORT_KEY: u16 = 25; pub const IFLA_BOND_AD_ACTOR_SYSTEM: u16 = 26; pub const IFLA_BOND_TLB_DYNAMIC_LB: u16 = 27; pub const IFLA_BOND_PEER_NOTIF_DELAY: u16 = 28; pub const IFLA_BOND_AD_LACP_ACTIVE: u16 = 29; pub const IFLA_BOND_MISSED_MAX: u16 = 30; pub const IFLA_BOND_NS_IP6_TARGET: u16 = 31; pub const IFLA_BOND_AD_INFO_UNSPEC: u16 = 0; pub const IFLA_BOND_AD_INFO_AGGREGATOR: u16 = 1; pub const IFLA_BOND_AD_INFO_NUM_PORTS: u16 = 2; pub const IFLA_BOND_AD_INFO_ACTOR_KEY: u16 = 3; pub const IFLA_BOND_AD_INFO_PARTNER_KEY: u16 = 4; pub const IFLA_BOND_AD_INFO_PARTNER_MAC: u16 = 5; // pub const IFLA_BOND_SLAVE_UNSPEC: int = 0; // pub const IFLA_BOND_SLAVE_STATE: int = 1; // pub const IFLA_BOND_SLAVE_MII_STATUS: int = 2; // pub const IFLA_BOND_SLAVE_LINK_FAILURE_COUNT: int = 3; // pub const IFLA_BOND_SLAVE_PERM_HWADDR: int = 4; // pub const IFLA_BOND_SLAVE_QUEUE_ID: int = 5; // pub const IFLA_BOND_SLAVE_AD_AGGREGATOR_ID: int = 6; // pub const IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE: int = 7; // pub const IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE: int = 8; // // pub const IFLA_VF_INFO_UNSPEC: int = 0; // pub const IFLA_VF_INFO: int = 1; // // pub const IFLA_VF_UNSPEC: int = 0; // pub const IFLA_VF_MAC: int = 1; // pub const IFLA_VF_VLAN: int = 2; // pub const IFLA_VF_TX_RATE: int = 3; // pub const IFLA_VF_SPOOFCHK: int = 4; // pub const IFLA_VF_LINK_STATE: int = 5; // pub const IFLA_VF_RATE: int = 6; // pub const IFLA_VF_RSS_QUERY_EN: int = 7; // pub const IFLA_VF_STATS: int = 8; // pub const IFLA_VF_TRUST: int = 9; // pub const IFLA_VF_IB_NODE_GUID: int = 10; // pub const IFLA_VF_IB_PORT_GUID: int = 11; // pub const IFLA_VF_VLAN_LIST: int = 12; // // pub const IFLA_VF_VLAN_INFO_UNSPEC: int = 0; // pub const IFLA_VF_VLAN_INFO: int = 1; // // pub const NDUSEROPT_UNSPEC: int = 0; // pub const NDUSEROPT_SRCADDR: int = 1; // pub const RTNLGRP_NONE: u32 = 0; pub const RTNLGRP_LINK: u32 = 1; pub const RTNLGRP_NOTIFY: u32 = 2; pub const RTNLGRP_NEIGH: u32 = 3; pub const RTNLGRP_TC: u32 = 4; pub const RTNLGRP_IPV4_IFADDR: u32 = 5; pub const RTNLGRP_IPV4_MROUTE: u32 = 6; pub const RTNLGRP_IPV4_ROUTE: u32 = 7; pub const RTNLGRP_IPV4_RULE: u32 = 8; pub const RTNLGRP_IPV6_IFADDR: u32 = 9; pub const RTNLGRP_IPV6_MROUTE: u32 = 10; pub const RTNLGRP_IPV6_ROUTE: u32 = 11; pub const RTNLGRP_IPV6_IFINFO: u32 = 12; pub const RTNLGRP_DECNET_IFADDR: u32 = 13; pub const RTNLGRP_NOP2: u32 = 14; pub const RTNLGRP_DECNET_ROUTE: u32 = 15; pub const RTNLGRP_DECNET_RULE: u32 = 16; pub const RTNLGRP_NOP4: u32 = 17; pub const RTNLGRP_IPV6_PREFIX: u32 = 18; pub const RTNLGRP_IPV6_RULE: u32 = 19; pub const RTNLGRP_ND_USEROPT: u32 = 20; pub const RTNLGRP_PHONET_IFADDR: u32 = 21; pub const RTNLGRP_PHONET_ROUTE: u32 = 22; pub const RTNLGRP_DCB: u32 = 23; pub const RTNLGRP_IPV4_NETCONF: u32 = 24; pub const RTNLGRP_IPV6_NETCONF: u32 = 25; pub const RTNLGRP_MDB: u32 = 26; pub const RTNLGRP_MPLS_ROUTE: u32 = 27; pub const RTNLGRP_NSID: u32 = 28; pub const RTNLGRP_MPLS_NETCONF: u32 = 29; pub const RTNLGRP_IPV4_MROUTE_R: u32 = 30; pub const RTNLGRP_IPV6_MROUTE_R: u32 = 31; // // pub const IFLA_VF_LINK_STATE_AUTO: int = 0; // pub const IFLA_VF_LINK_STATE_ENABLE: int = 1; // pub const IFLA_VF_LINK_STATE_DISABLE: int = 2; // // pub const IFLA_VF_STATS_RX_PACKETS: int = 0; // pub const IFLA_VF_STATS_TX_PACKETS: int = 1; // pub const IFLA_VF_STATS_RX_BYTES: int = 2; // pub const IFLA_VF_STATS_TX_BYTES: int = 3; // pub const IFLA_VF_STATS_BROADCAST: int = 4; // pub const IFLA_VF_STATS_MULTICAST: int = 5; // pub const IFLA_VF_STATS_PAD: int = 6; // pub const IFLA_VF_STATS_RX_DROPPED: int = 7; // pub const IFLA_VF_STATS_TX_DROPPED: int = 8; // // pub const IFLA_VF_PORT_UNSPEC: int = 0; // pub const IFLA_VF_PORT: int = 1; // // pub const IFLA_PORT_UNSPEC: int = 0; // pub const IFLA_PORT_VF: int = 1; // pub const IFLA_PORT_PROFILE: int = 2; // pub const IFLA_PORT_VSI_TYPE: int = 3; // pub const IFLA_PORT_INSTANCE_UUID: int = 4; // pub const IFLA_PORT_HOST_UUID: int = 5; // pub const IFLA_PORT_REQUEST: int = 6; // pub const IFLA_PORT_RESPONSE: int = 7; // // pub const PORT_REQUEST_PREASSOCIATE: int = 0; // pub const PORT_REQUEST_PREASSOCIATE_RR: int = 1; // pub const PORT_REQUEST_ASSOCIATE: int = 2; // pub const PORT_REQUEST_DISASSOCIATE: int = 3; // // pub const PORT_VDP_RESPONSE_SUCCESS: int = 0; // pub const PORT_VDP_RESPONSE_INVALID_FORMAT: int = 1; // pub const PORT_VDP_RESPONSE_INSUFFICIENT_RESOURCES: int = 2; // pub const PORT_VDP_RESPONSE_UNUSED_VTID: int = 3; // pub const PORT_VDP_RESPONSE_VTID_VIOLATION: int = 4; // pub const PORT_VDP_RESPONSE_VTID_VERSION_VIOALTION: int = 5; // pub const PORT_VDP_RESPONSE_OUT_OF_SYNC: int = 6; // pub const PORT_PROFILE_RESPONSE_SUCCESS: int = 256; // pub const PORT_PROFILE_RESPONSE_INPROGRESS: int = 257; // pub const PORT_PROFILE_RESPONSE_INVALID: int = 258; // pub const PORT_PROFILE_RESPONSE_BADSTATE: int = 259; // pub const PORT_PROFILE_RESPONSE_INSUFFICIENT_RESOURCES: int = 260; // pub const PORT_PROFILE_RESPONSE_ERROR: int = 261; // // pub const IFLA_IPOIB_UNSPEC: int = 0; // pub const IFLA_IPOIB_PKEY: int = 1; // pub const IFLA_IPOIB_MODE: int = 2; // pub const IFLA_IPOIB_UMCAST: int = 3; // // pub const IPOIB_MODE_DATAGRAM: int = 0; // pub const IPOIB_MODE_CONNECTED: int = 1; // // pub const IFLA_HSR_UNSPEC: int = 0; // pub const IFLA_HSR_SLAVE1: int = 1; // pub const IFLA_HSR_SLAVE2: int = 2; // pub const IFLA_HSR_MULTICAST_SPEC: int = 3; // pub const IFLA_HSR_SUPERVISION_ADDR: int = 4; // pub const IFLA_HSR_SEQ_NR: int = 5; // pub const IFLA_HSR_VERSION: int = 6; // // pub const IFLA_STATS_UNSPEC: int = 0; // pub const IFLA_STATS_LINK_64: int = 1; // pub const IFLA_STATS_LINK_XSTATS: int = 2; // pub const IFLA_STATS_LINK_XSTATS_SLAVE: int = 3; // pub const IFLA_STATS_LINK_OFFLOAD_XSTATS: int = 4; // pub const IFLA_STATS_AF_SPEC: int = 5; // // pub const LINK_XSTATS_TYPE_UNSPEC: int = 0; // pub const LINK_XSTATS_TYPE_BRIDGE: int = 1; // // pub const IFLA_OFFLOAD_XSTATS_UNSPEC: int = 0; // pub const IFLA_OFFLOAD_XSTATS_CPU_HIT: int = 1; // // pub const XDP_ATTACHED_NONE: int = 0; // pub const XDP_ATTACHED_DRV: int = 1; // pub const XDP_ATTACHED_SKB: int = 2; // pub const XDP_ATTACHED_HW: int = 3; pub const IFLA_XDP_UNSPEC: u32 = 0; pub const IFLA_XDP_FD: u32 = 1; pub const IFLA_XDP_ATTACHED: u32 = 2; pub const IFLA_XDP_FLAGS: u32 = 3; pub const IFLA_XDP_PROG_ID: u32 = 4; // pub const IFLA_EVENT_NONE: int = 0; // pub const IFLA_EVENT_REBOOT: int = 1; // pub const IFLA_EVENT_FEATURES: int = 2; // pub const IFLA_EVENT_BONDING_FAILOVER: int = 3; // pub const IFLA_EVENT_NOTIFY_PEERS: int = 4; // pub const IFLA_EVENT_IGMP_RESEND: int = 5; // pub const IFLA_EVENT_BONDING_OPTIONS: int = 6; // // pub const NDTPA_UNSPEC: int = 0; // pub const NDTPA_IFINDEX: int = 1; // pub const NDTPA_REFCNT: int = 2; // pub const NDTPA_REACHABLE_TIME: int = 3; // pub const NDTPA_BASE_REACHABLE_TIME: int = 4; // pub const NDTPA_RETRANS_TIME: int = 5; // pub const NDTPA_GC_STALETIME: int = 6; // pub const NDTPA_DELAY_PROBE_TIME: int = 7; // pub const NDTPA_QUEUE_LEN: int = 8; // pub const NDTPA_APP_PROBES: int = 9; // pub const NDTPA_UCAST_PROBES: int = 10; // pub const NDTPA_MCAST_PROBES: int = 11; // pub const NDTPA_ANYCAST_DELAY: int = 12; // pub const NDTPA_PROXY_DELAY: int = 13; // pub const NDTPA_PROXY_QLEN: int = 14; // pub const NDTPA_LOCKTIME: int = 15; // pub const NDTPA_QUEUE_LENBYTES: int = 16; // pub const NDTPA_MCAST_REPROBES: int = 17; // pub const NDTPA_PAD: int = 18; // // #[allow(overflowing_literals)] // pub const RT_TABLE_MAX: int = 0xffff_ffff; // // pub const PREFIX_UNSPEC: int = 0; // pub const PREFIX_ADDRESS: int = 1; // pub const PREFIX_CACHEINFO: int = 2; pub const LWTUNNEL_ENCAP_NONE: u16 = 0; pub const LWTUNNEL_ENCAP_MPLS: u16 = 1; pub const LWTUNNEL_ENCAP_IP: u16 = 2; pub const LWTUNNEL_ENCAP_ILA: u16 = 3; pub const LWTUNNEL_ENCAP_IP6: u16 = 4; pub const LWTUNNEL_ENCAP_SEG6: u16 = 5; pub const LWTUNNEL_ENCAP_BPF: u16 = 6; pub const LWTUNNEL_ENCAP_SEG6_LOCAL: u16 = 7; pub const LWTUNNEL_ENCAP_RPL: u16 = 8; pub const MPLS_IPTUNNEL_UNSPEC: u16 = 0; pub const MPLS_IPTUNNEL_DST: u16 = 1; pub const MPLS_IPTUNNEL_TTL: u16 = 2; // Available MACVTAP MODES pub const MACVTAP_MODE_PRIVATE: u32 = 1; pub const MACVTAP_MODE_VEPA: u32 = 2; pub const MACVTAP_MODE_BRIDGE: u32 = 4; pub const MACVTAP_MODE_PASSTHRU: u32 = 8; pub const MACVTAP_MODE_SOURCE: u32 = 16; ================================================ FILE: netlink-packet-route/src/rtnl/link/buffer.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ nlas::{NlaBuffer, NlasIterator}, DecodeError, }; pub const LINK_HEADER_LEN: usize = 16; buffer!(LinkMessageBuffer(LINK_HEADER_LEN) { interface_family: (u8, 0), reserved_1: (u8, 1), link_layer_type: (u16, 2..4), link_index: (u32, 4..8), flags: (u32, 8..12), change_mask: (u32, 12..LINK_HEADER_LEN), payload: (slice, LINK_HEADER_LEN..), }); impl<'a, T: AsRef<[u8]> + ?Sized> LinkMessageBuffer<&'a T> { pub fn nlas(&self) -> impl Iterator, DecodeError>> { NlasIterator::new(self.payload()) } } ================================================ FILE: netlink-packet-route/src/rtnl/link/header.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, LinkMessageBuffer, LINK_HEADER_LEN, }; /// High level representation of `RTM_GETLINK`, `RTM_SETLINK`, `RTM_NEWLINK` and `RTM_DELLINK` /// messages headers. /// /// These headers have the following structure: /// /// ```no_rust /// 0 8 16 24 32 /// +----------------+----------------+----------------+----------------+ /// |interface family| reserved | link layer type | /// +----------------+----------------+----------------+----------------+ /// | link index | /// +----------------+----------------+----------------+----------------+ /// | flags | /// +----------------+----------------+----------------+----------------+ /// | change mask | /// +----------------+----------------+----------------+----------------+ /// ``` /// /// `LinkHeader` exposes all these fields except for the "reserved" one. #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct LinkHeader { /// Address family: one of the `AF_*` constants. pub interface_family: u8, /// Link index. pub index: u32, /// Link type. It should be set to one of the `ARPHRD_*` /// constants. The most common value is `ARPHRD_ETHER` for /// Ethernet. pub link_layer_type: u16, /// State of the link, described by a combinations of `IFF_*` /// constants, for instance `IFF_UP | IFF_LOWER_UP`. pub flags: u32, /// Change mask for the `flags` field. Reserved, it should be set /// to `0xffff_ffff`. pub change_mask: u32, } impl Emitable for LinkHeader { fn buffer_len(&self) -> usize { LINK_HEADER_LEN } fn emit(&self, buffer: &mut [u8]) { let mut packet = LinkMessageBuffer::new(buffer); packet.set_interface_family(self.interface_family); packet.set_link_index(self.index); packet.set_change_mask(self.change_mask); packet.set_link_layer_type(self.link_layer_type); packet.set_flags(self.flags); } } impl> Parseable> for LinkHeader { fn parse(buf: &LinkMessageBuffer) -> Result { Ok(Self { interface_family: buf.interface_family(), link_layer_type: buf.link_layer_type(), index: buf.link_index(), change_mask: buf.change_mask(), flags: buf.flags(), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/link/message.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use crate::{ nlas::link::Nla, traits::{Emitable, Parseable, ParseableParametrized}, DecodeError, LinkHeader, LinkMessageBuffer, }; #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct LinkMessage { pub header: LinkHeader, pub nlas: Vec, } impl Emitable for LinkMessage { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); self.nlas .as_slice() .emit(&mut buffer[self.header.buffer_len()..]); } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for LinkMessage { fn parse(buf: &LinkMessageBuffer<&'a T>) -> Result { let header = LinkHeader::parse(buf).context("failed to parse link message header")?; let interface_family = header.interface_family; let nlas = Vec::::parse_with_param(buf, interface_family) .context("failed to parse link message NLAs")?; Ok(LinkMessage { header, nlas }) } } impl<'a, T: AsRef<[u8]> + 'a> ParseableParametrized, u16> for Vec { fn parse_with_param(buf: &LinkMessageBuffer<&'a T>, family: u16) -> Result { let mut nlas = vec![]; for nla_buf in buf.nlas() { nlas.push(Nla::parse_with_param(&nla_buf?, family)?); } Ok(nlas) } } impl<'a, T: AsRef<[u8]> + 'a> ParseableParametrized, u8> for Vec { fn parse_with_param(buf: &LinkMessageBuffer<&'a T>, family: u8) -> Result { Vec::::parse_with_param(buf, u16::from(family)) } } #[cfg(test)] mod test { use crate::{ constants::*, nlas::link::{Nla, State}, traits::{Emitable, ParseableParametrized}, LinkHeader, LinkMessage, LinkMessageBuffer, }; #[rustfmt::skip] static HEADER: [u8; 96] = [ 0x00, // interface family 0x00, // reserved 0x04, 0x03, // link layer type 772 = loopback 0x01, 0x00, 0x00, 0x00, // interface index = 1 // Note: in the wireshark capture, the thrid byte is 0x01 // but that does not correpond to any of the IFF_ flags... 0x49, 0x00, 0x00, 0x00, // device flags: UP, LOOPBACK, RUNNING, LOWERUP 0x00, 0x00, 0x00, 0x00, // reserved 2 (aka device change flag) // nlas 0x07, 0x00, 0x03, 0x00, 0x6c, 0x6f, 0x00, // device name L=7,T=3,V=lo 0x00, // padding 0x08, 0x00, 0x0d, 0x00, 0xe8, 0x03, 0x00, 0x00, // TxQueue length L=8,T=13,V=1000 0x05, 0x00, 0x10, 0x00, 0x00, // OperState L=5,T=16,V=0 (unknown) 0x00, 0x00, 0x00, // padding 0x05, 0x00, 0x11, 0x00, 0x00, // Link mode L=5,T=17,V=0 0x00, 0x00, 0x00, // padding 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, // MTU L=8,T=4,V=65536 0x08, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, // Group L=8,T=27,V=9 0x08, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, // Promiscuity L=8,T=30,V=0 0x08, 0x00, 0x1f, 0x00, 0x01, 0x00, 0x00, 0x00, // Number of Tx Queues L=8,T=31,V=1 0x08, 0x00, 0x28, 0x00, 0xff, 0xff, 0x00, 0x00, // Maximum GSO segment count L=8,T=40,V=65536 0x08, 0x00, 0x29, 0x00, 0x00, 0x00, 0x01, 0x00, // Maximum GSO size L=8,T=41,V=65536 ]; #[test] fn packet_header_read() { let packet = LinkMessageBuffer::new(&HEADER[0..16]); assert_eq!(packet.interface_family(), 0); assert_eq!(packet.reserved_1(), 0); assert_eq!(packet.link_layer_type(), ARPHRD_LOOPBACK); assert_eq!(packet.link_index(), 1); assert_eq!(packet.flags(), IFF_UP | IFF_LOOPBACK | IFF_RUNNING); assert_eq!(packet.change_mask(), 0); } #[test] fn packet_header_build() { let mut buf = vec![0xff; 16]; { let mut packet = LinkMessageBuffer::new(&mut buf); packet.set_interface_family(0); packet.set_reserved_1(0); packet.set_link_layer_type(ARPHRD_LOOPBACK); packet.set_link_index(1); packet.set_flags(IFF_UP | IFF_LOOPBACK | IFF_RUNNING); packet.set_change_mask(0); } assert_eq!(&buf[..], &HEADER[0..16]); } #[test] fn packet_nlas_read() { let packet = LinkMessageBuffer::new(&HEADER[..]); assert_eq!(packet.nlas().count(), 10); let mut nlas = packet.nlas(); // device name L=7,T=3,V=lo let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 7); assert_eq!(nla.kind(), 3); assert_eq!(nla.value(), &[0x6c, 0x6f, 0x00]); let parsed = Nla::parse_with_param(&nla, AF_INET).unwrap(); assert_eq!(parsed, Nla::IfName(String::from("lo"))); // TxQueue length L=8,T=13,V=1000 let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 8); assert_eq!(nla.kind(), 13); assert_eq!(nla.value(), &[0xe8, 0x03, 0x00, 0x00]); let parsed = Nla::parse_with_param(&nla, AF_INET).unwrap(); assert_eq!(parsed, Nla::TxQueueLen(1000)); // OperState L=5,T=16,V=0 (unknown) let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 5); assert_eq!(nla.kind(), 16); assert_eq!(nla.value(), &[0x00]); let parsed = Nla::parse_with_param(&nla, AF_INET).unwrap(); assert_eq!(parsed, Nla::OperState(State::Unknown)); // Link mode L=5,T=17,V=0 let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 5); assert_eq!(nla.kind(), 17); assert_eq!(nla.value(), &[0x00]); let parsed = Nla::parse_with_param(&nla, AF_INET).unwrap(); assert_eq!(parsed, Nla::Mode(0)); // MTU L=8,T=4,V=65536 let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 8); assert_eq!(nla.kind(), 4); assert_eq!(nla.value(), &[0x00, 0x00, 0x01, 0x00]); let parsed = Nla::parse_with_param(&nla, AF_INET).unwrap(); assert_eq!(parsed, Nla::Mtu(65_536)); // 0x00, 0x00, 0x00, 0x00, // Group L=8,T=27,V=9 let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 8); assert_eq!(nla.kind(), 27); assert_eq!(nla.value(), &[0x00, 0x00, 0x00, 0x00]); let parsed = Nla::parse_with_param(&nla, AF_INET).unwrap(); assert_eq!(parsed, Nla::Group(0)); // Promiscuity L=8,T=30,V=0 let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 8); assert_eq!(nla.kind(), 30); assert_eq!(nla.value(), &[0x00, 0x00, 0x00, 0x00]); let parsed = Nla::parse_with_param(&nla, AF_INET).unwrap(); assert_eq!(parsed, Nla::Promiscuity(0)); // Number of Tx Queues L=8,T=31,V=1 // 0x01, 0x00, 0x00, 0x00 let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 8); assert_eq!(nla.kind(), 31); assert_eq!(nla.value(), &[0x01, 0x00, 0x00, 0x00]); let parsed = Nla::parse_with_param(&nla, AF_INET).unwrap(); assert_eq!(parsed, Nla::NumTxQueues(1)); } #[test] fn emit() { let header = LinkHeader { link_layer_type: ARPHRD_LOOPBACK, index: 1, flags: IFF_UP | IFF_LOOPBACK | IFF_RUNNING | IFF_LOWER_UP, ..Default::default() }; let nlas = vec![ Nla::IfName("lo".into()), Nla::TxQueueLen(1000), Nla::OperState(State::Unknown), Nla::Mode(0), Nla::Mtu(0x1_0000), Nla::Group(0), Nla::Promiscuity(0), Nla::NumTxQueues(1), Nla::GsoMaxSegs(0xffff), Nla::GsoMaxSize(0x1_0000), ]; let packet = LinkMessage { header, nlas }; let mut buf = vec![0; 96]; assert_eq!(packet.buffer_len(), 96); packet.emit(&mut buf[..]); } } ================================================ FILE: netlink-packet-route/src/rtnl/link/mod.rs ================================================ // SPDX-License-Identifier: MIT mod buffer; mod header; mod message; pub mod nlas; pub use self::{buffer::*, header::*, message::*}; ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/af_spec_bridge.rs ================================================ // SPDX-License-Identifier: MIT use std::convert::TryFrom; use anyhow::Context; use crate::{ constants::*, nlas::{self, DefaultNla, NlaBuffer}, parsers::parse_u16, traits::Parseable, DecodeError, }; use byteorder::{ByteOrder, NativeEndian}; #[derive(Clone, Eq, PartialEq, Debug)] pub enum AfSpecBridge { Flags(u16), VlanInfo(BridgeVlanInfo), Other(DefaultNla), } impl nlas::Nla for AfSpecBridge { fn value_len(&self) -> usize { use self::AfSpecBridge::*; match *self { VlanInfo(_) => 4, Flags(_) => 2, Other(ref nla) => nla.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::AfSpecBridge::*; match *self { Flags(value) => NativeEndian::write_u16(buffer, value), VlanInfo(ref info) => { (&mut buffer[..4]).copy_from_slice(<[u8; 4]>::from(info).as_slice()) } Other(ref nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::AfSpecBridge::*; match *self { Flags(_) => IFLA_BRIDGE_FLAGS, VlanInfo(_) => IFLA_BRIDGE_VLAN_INFO, Other(ref nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for AfSpecBridge { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::AfSpecBridge::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_BRIDGE_VLAN_INFO => VlanInfo( BridgeVlanInfo::try_from(payload).context("Invalid IFLA_BRIDGE_VLAN_INFO value")?, ), IFLA_BRIDGE_FLAGS => { Flags(parse_u16(payload).context("invalid IFLA_BRIDGE_FLAGS value")?) } kind => Other(DefaultNla::parse(buf).context(format!("Unknown NLA type {}", kind))?), }) } } #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] pub struct BridgeVlanInfo { pub flags: u16, pub vid: u16, } impl From<&BridgeVlanInfo> for [u8; 4] { fn from(d: &BridgeVlanInfo) -> Self { let mut ret = [0u8; 4]; NativeEndian::write_u16(&mut ret[0..2], d.flags); NativeEndian::write_u16(&mut ret[2..4], d.vid); ret } } impl TryFrom<&[u8]> for BridgeVlanInfo { type Error = DecodeError; fn try_from(raw: &[u8]) -> Result { if raw.len() == 4 { Ok(Self { flags: parse_u16(&raw[0..2]) .context(format!("Invalid IFLA_BRIDGE_VLAN_INFO value: {:?}", raw))?, vid: parse_u16(&raw[2..4]) .context(format!("Invalid IFLA_BRIDGE_VLAN_INFO value: {:?}", raw))?, }) } else { Err(DecodeError::from(format!( "Invalid IFLA_BRIDGE_VLAN_INFO value, expecting [u8;4], but got {:?}", raw ))) } } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/af_spec_inet.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use super::{inet, inet6}; use crate::{ constants::*, nlas::{self, DefaultNla, NlaBuffer, NlasIterator}, traits::{Emitable, Parseable}, DecodeError, }; #[derive(Clone, Eq, PartialEq, Debug)] pub enum AfSpecInet { Unspec(Vec), Unix(Vec), Ax25(Vec), Ipx(Vec), AppleTalk(Vec), Netrom(Vec), Bridge(Vec), AtmPvc(Vec), X25(Vec), Inet(Vec), Inet6(Vec), Rose(Vec), DecNet(Vec), NetbEui(Vec), Security(Vec), Key(Vec), Netlink(Vec), Packet(Vec), Ash(Vec), EcoNet(Vec), AtmSvc(Vec), Rds(Vec), Sna(Vec), Irda(Vec), Pppox(Vec), WanPipe(Vec), Llc(Vec), Can(Vec), Tipc(Vec), Bluetooth(Vec), Iucv(Vec), RxRpc(Vec), Isdn(Vec), Phonet(Vec), Ieee802154(Vec), Caif(Vec), Alg(Vec), Other(DefaultNla), } impl nlas::Nla for AfSpecInet { #[rustfmt::skip] fn value_len(&self) -> usize { use self::AfSpecInet::*; match *self { Unspec(ref bytes) | Unix(ref bytes) | Ax25(ref bytes) | Ipx(ref bytes) | AppleTalk(ref bytes) | Netrom(ref bytes) | Bridge(ref bytes) | AtmPvc(ref bytes) | X25(ref bytes) | Rose(ref bytes) | DecNet(ref bytes) | NetbEui(ref bytes) | Security(ref bytes) | Key(ref bytes) | Netlink(ref bytes) | Packet(ref bytes) | Ash(ref bytes) | EcoNet(ref bytes) | AtmSvc(ref bytes) | Rds(ref bytes) | Sna(ref bytes) | Irda(ref bytes) | Pppox(ref bytes) | WanPipe(ref bytes) | Llc(ref bytes) | Can(ref bytes) | Tipc(ref bytes) | Bluetooth(ref bytes) | Iucv(ref bytes) | RxRpc(ref bytes) | Isdn(ref bytes) | Phonet(ref bytes) | Ieee802154(ref bytes) | Caif(ref bytes) | Alg(ref bytes) => bytes.len(), Inet6(ref nlas) => nlas.as_slice().buffer_len(), Inet(ref nlas) => nlas.as_slice().buffer_len(), Other(ref nla) => nla.value_len(), } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::AfSpecInet::*; match *self { Unspec(ref bytes) | Unix(ref bytes) | Ax25(ref bytes) | Ipx(ref bytes) | AppleTalk(ref bytes) | Netrom(ref bytes) | Bridge(ref bytes) | AtmPvc(ref bytes) | X25(ref bytes) | Rose(ref bytes) | DecNet(ref bytes) | NetbEui(ref bytes) | Security(ref bytes) | Key(ref bytes) | Netlink(ref bytes) | Packet(ref bytes) | Ash(ref bytes) | EcoNet(ref bytes) | AtmSvc(ref bytes) | Rds(ref bytes) | Sna(ref bytes) | Irda(ref bytes) | Pppox(ref bytes) | WanPipe(ref bytes) | Llc(ref bytes) | Can(ref bytes) | Tipc(ref bytes) | Bluetooth(ref bytes) | Iucv(ref bytes) | RxRpc(ref bytes) | Isdn(ref bytes) | Phonet(ref bytes) | Ieee802154(ref bytes) | Caif(ref bytes) | Alg(ref bytes) => buffer[..bytes.len()].copy_from_slice(bytes.as_slice()), Inet6(ref nlas) => nlas.as_slice().emit(buffer), Inet(ref nlas) => nlas.as_slice().emit(buffer), Other(ref nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::AfSpecInet::*; match *self { Inet(_) => AF_INET, Unspec(_) => AF_UNSPEC, Unix(_) => AF_UNIX, Ax25(_) => AF_AX25, Ipx(_) => AF_IPX, AppleTalk(_) => AF_APPLETALK, Netrom(_) => AF_NETROM, Bridge(_) => AF_BRIDGE, AtmPvc(_) => AF_ATMPVC, X25(_) => AF_X25, Inet6(_) => AF_INET6, Rose(_) => AF_ROSE, DecNet(_) => AF_DECNET, NetbEui(_) => AF_NETBEUI, Security(_) => AF_SECURITY, Key(_) => AF_KEY, Netlink(_) => AF_NETLINK, Packet(_) => AF_PACKET, Ash(_) => AF_ASH, EcoNet(_) => AF_ECONET, AtmSvc(_) => AF_ATMSVC, Rds(_) => AF_RDS, Sna(_) => AF_SNA, Irda(_) => AF_IRDA, Pppox(_) => AF_PPPOX, WanPipe(_) => AF_WANPIPE, Llc(_) => AF_LLC, Can(_) => AF_CAN, Tipc(_) => AF_TIPC, Bluetooth(_) => AF_BLUETOOTH, Iucv(_) => AF_IUCV, RxRpc(_) => AF_RXRPC, Isdn(_) => AF_ISDN, Phonet(_) => AF_PHONET, Ieee802154(_) => AF_IEEE802154, Caif(_) => AF_CAIF, Alg(_) => AF_ALG, Other(ref nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for AfSpecInet { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::AfSpecInet::*; let payload = buf.value(); Ok(match buf.kind() { AF_UNSPEC => Unspec(payload.to_vec()), AF_INET => { let mut nlas = vec![]; for nla in NlasIterator::new(payload) { let nla = nla.context("invalid AF_INET value")?; nlas.push(inet::Inet::parse(&nla).context("invalid AF_INET value")?); } Inet(nlas) } AF_INET6 => { let mut nlas = vec![]; for nla in NlasIterator::new(payload) { let nla = nla.context("invalid AF_INET6 value")?; nlas.push(inet6::Inet6::parse(&nla).context("invalid AF_INET6 value")?); } Inet6(nlas) } AF_UNIX => Unix(payload.to_vec()), AF_AX25 => Ax25(payload.to_vec()), AF_IPX => Ipx(payload.to_vec()), AF_APPLETALK => AppleTalk(payload.to_vec()), AF_NETROM => Netrom(payload.to_vec()), AF_BRIDGE => Bridge(payload.to_vec()), AF_ATMPVC => AtmPvc(payload.to_vec()), AF_X25 => X25(payload.to_vec()), AF_ROSE => Rose(payload.to_vec()), AF_DECNET => DecNet(payload.to_vec()), AF_NETBEUI => NetbEui(payload.to_vec()), AF_SECURITY => Security(payload.to_vec()), AF_KEY => Key(payload.to_vec()), AF_NETLINK => Netlink(payload.to_vec()), AF_PACKET => Packet(payload.to_vec()), AF_ASH => Ash(payload.to_vec()), AF_ECONET => EcoNet(payload.to_vec()), AF_ATMSVC => AtmSvc(payload.to_vec()), AF_RDS => Rds(payload.to_vec()), AF_SNA => Sna(payload.to_vec()), AF_IRDA => Irda(payload.to_vec()), AF_PPPOX => Pppox(payload.to_vec()), AF_WANPIPE => WanPipe(payload.to_vec()), AF_LLC => Llc(payload.to_vec()), AF_CAN => Can(payload.to_vec()), AF_TIPC => Tipc(payload.to_vec()), AF_BLUETOOTH => Bluetooth(payload.to_vec()), AF_IUCV => Iucv(payload.to_vec()), AF_RXRPC => RxRpc(payload.to_vec()), AF_ISDN => Isdn(payload.to_vec()), AF_PHONET => Phonet(payload.to_vec()), AF_IEEE802154 => Ieee802154(payload.to_vec()), AF_CAIF => Caif(payload.to_vec()), AF_ALG => Alg(payload.to_vec()), kind => Other(DefaultNla::parse(buf).context(format!("Unknown NLA type {}", kind))?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/bond.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ constants::*, nlas::{Nla, NlaBuffer, NlasIterator}, parsers::{parse_ip, parse_mac, parse_u16, parse_u32, parse_u8}, traits::{Emitable, Parseable}, DecodeError, }; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, ops::Deref, }; #[derive(Debug, Clone, Eq, PartialEq)] pub enum BondAdInfo { Aggregator(u16), NumPorts(u16), ActorKey(u16), PartnerKey(u16), PartnerMac([u8; 6]), } impl Nla for BondAdInfo { fn value_len(&self) -> usize { use self::BondAdInfo::*; match self { Aggregator(_) | NumPorts(_) | ActorKey(_) | PartnerKey(_) => 2, PartnerMac(_) => 6, } } fn kind(&self) -> u16 { use self::BondAdInfo::*; match self { Aggregator(_) => IFLA_BOND_AD_INFO_AGGREGATOR, NumPorts(_) => IFLA_BOND_AD_INFO_NUM_PORTS, ActorKey(_) => IFLA_BOND_AD_INFO_ACTOR_KEY, PartnerKey(_) => IFLA_BOND_AD_INFO_PARTNER_KEY, PartnerMac(_) => IFLA_BOND_AD_INFO_PARTNER_MAC, } } fn emit_value(&self, buffer: &mut [u8]) { use self::BondAdInfo::*; match self { Aggregator(d) | NumPorts(d) | ActorKey(d) | PartnerKey(d) => { NativeEndian::write_u16(buffer, *d) } PartnerMac(mac) => buffer.copy_from_slice(mac), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for BondAdInfo { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::BondAdInfo::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_BOND_AD_INFO_AGGREGATOR => Aggregator( parse_u16(payload).context("invalid IFLA_BOND_AD_INFO_AGGREGATOR value")?, ), IFLA_BOND_AD_INFO_NUM_PORTS => { NumPorts(parse_u16(payload).context("invalid IFLA_BOND_AD_INFO_NUM_PORTS value")?) } IFLA_BOND_AD_INFO_ACTOR_KEY => { ActorKey(parse_u16(payload).context("invalid IFLA_BOND_AD_INFO_ACTOR_KEY value")?) } IFLA_BOND_AD_INFO_PARTNER_KEY => PartnerKey( parse_u16(payload).context("invalid IFLA_BOND_AD_INFO_PARTNER_KEY value")?, ), IFLA_BOND_AD_INFO_PARTNER_MAC => PartnerMac( parse_mac(payload).context("invalid IFLA_BOND_AD_INFO_PARTNER_MAC value")?, ), _ => return Err(format!("unknown NLA type {}", buf.kind()).into()), }) } } // Some attributes (ARP_IP_TARGET, NS_IP6_TARGET) contain a nested // list of IP addresses, where each element uses the index as NLA kind // and the address as value. InfoBond exposes vectors of IP addresses, // and we use this struct for serialization. struct BondIpAddrNla { index: u16, addr: IpAddr, } struct BondIpAddrNlaList(Vec); impl Deref for BondIpAddrNlaList { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } impl From<&Vec> for BondIpAddrNlaList { fn from(addrs: &Vec) -> Self { let mut nlas = Vec::new(); for (i, addr) in addrs.iter().enumerate() { let nla = BondIpAddrNla { index: i as u16, addr: IpAddr::V4(*addr), }; nlas.push(nla); } BondIpAddrNlaList(nlas) } } impl From<&Vec> for BondIpAddrNlaList { fn from(addrs: &Vec) -> Self { let mut nlas = Vec::new(); for (i, addr) in addrs.iter().enumerate() { let nla = BondIpAddrNla { index: i as u16, addr: IpAddr::V6(*addr), }; nlas.push(nla); } BondIpAddrNlaList(nlas) } } impl Nla for BondIpAddrNla { fn value_len(&self) -> usize { if self.addr.is_ipv4() { 4 } else { 16 } } fn emit_value(&self, buffer: &mut [u8]) { match self.addr { IpAddr::V4(addr) => buffer.copy_from_slice(&addr.octets()), IpAddr::V6(addr) => buffer.copy_from_slice(&addr.octets()), } } fn kind(&self) -> u16 { self.index } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum InfoBond { Mode(u8), ActiveSlave(u32), MiiMon(u32), UpDelay(u32), DownDelay(u32), UseCarrier(u8), ArpInterval(u32), ArpIpTarget(Vec), ArpValidate(u32), ArpAllTargets(u32), Primary(u32), PrimaryReselect(u8), FailOverMac(u8), XmitHashPolicy(u8), ResendIgmp(u32), NumPeerNotif(u8), AllSlavesActive(u8), MinLinks(u32), LpInterval(u32), PacketsPerSlave(u32), AdLacpRate(u8), AdSelect(u8), AdInfo(Vec), AdActorSysPrio(u16), AdUserPortKey(u16), AdActorSystem([u8; 6]), TlbDynamicLb(u8), PeerNotifDelay(u32), AdLacpActive(u8), MissedMax(u8), NsIp6Target(Vec), } impl Nla for InfoBond { #[rustfmt::skip] fn value_len(&self) -> usize { use self::InfoBond::*; match *self { Mode(_) | UseCarrier(_) | PrimaryReselect(_) | FailOverMac(_) | XmitHashPolicy(_) | NumPeerNotif(_) | AllSlavesActive(_) | AdLacpActive(_) | AdLacpRate(_) | AdSelect(_) | TlbDynamicLb(_) | MissedMax(_) => 1, AdActorSysPrio(_) | AdUserPortKey(_) => 2, ActiveSlave(_) | MiiMon(_) | UpDelay(_) | DownDelay(_) | ArpInterval(_) | ArpValidate(_) | ArpAllTargets(_) | Primary(_) | ResendIgmp(_) | MinLinks(_) | LpInterval(_) | PacketsPerSlave(_) | PeerNotifDelay(_) => 4, ArpIpTarget(ref addrs) => { BondIpAddrNlaList::from(addrs).as_slice().buffer_len() }, NsIp6Target(ref addrs) => { BondIpAddrNlaList::from(addrs).as_slice().buffer_len() }, AdActorSystem(_) => 6, AdInfo(ref infos) => infos.as_slice().buffer_len(), } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::InfoBond::*; match self { Mode(value) | UseCarrier(value) | PrimaryReselect(value) | FailOverMac(value) | XmitHashPolicy(value) | NumPeerNotif(value) | AllSlavesActive(value) | AdLacpActive(value) | AdLacpRate(value) | AdSelect(value) | TlbDynamicLb(value) | MissedMax(value) => buffer[0] = *value, AdActorSysPrio(value) | AdUserPortKey(value) => NativeEndian::write_u16(buffer, *value), ActiveSlave(value) | MiiMon(value) | UpDelay(value) | DownDelay(value) | ArpInterval(value) | ArpValidate(value) | ArpAllTargets(value) | Primary(value) | ResendIgmp(value) | MinLinks(value) | LpInterval(value) | PacketsPerSlave(value) | PeerNotifDelay(value) => NativeEndian::write_u32(buffer, *value), AdActorSystem(bytes) => buffer.copy_from_slice(bytes), ArpIpTarget(addrs) => { BondIpAddrNlaList::from(addrs).as_slice().emit(buffer) }, NsIp6Target(addrs) => { BondIpAddrNlaList::from(addrs).as_slice().emit(buffer) }, AdInfo(infos) => infos.as_slice().emit(buffer), } } fn kind(&self) -> u16 { use self::InfoBond::*; match self { Mode(_) => IFLA_BOND_MODE, ActiveSlave(_) => IFLA_BOND_ACTIVE_SLAVE, MiiMon(_) => IFLA_BOND_MIIMON, UpDelay(_) => IFLA_BOND_UPDELAY, DownDelay(_) => IFLA_BOND_DOWNDELAY, UseCarrier(_) => IFLA_BOND_USE_CARRIER, ArpInterval(_) => IFLA_BOND_ARP_INTERVAL, ArpIpTarget(_) => IFLA_BOND_ARP_IP_TARGET, ArpValidate(_) => IFLA_BOND_ARP_VALIDATE, ArpAllTargets(_) => IFLA_BOND_ARP_ALL_TARGETS, Primary(_) => IFLA_BOND_PRIMARY, PrimaryReselect(_) => IFLA_BOND_PRIMARY_RESELECT, FailOverMac(_) => IFLA_BOND_FAIL_OVER_MAC, XmitHashPolicy(_) => IFLA_BOND_XMIT_HASH_POLICY, ResendIgmp(_) => IFLA_BOND_RESEND_IGMP, NumPeerNotif(_) => IFLA_BOND_NUM_PEER_NOTIF, AllSlavesActive(_) => IFLA_BOND_ALL_SLAVES_ACTIVE, MinLinks(_) => IFLA_BOND_MIN_LINKS, LpInterval(_) => IFLA_BOND_LP_INTERVAL, PacketsPerSlave(_) => IFLA_BOND_PACKETS_PER_SLAVE, AdLacpRate(_) => IFLA_BOND_AD_LACP_RATE, AdSelect(_) => IFLA_BOND_AD_SELECT, AdInfo(_) => IFLA_BOND_AD_INFO, AdActorSysPrio(_) => IFLA_BOND_AD_ACTOR_SYS_PRIO, AdUserPortKey(_) => IFLA_BOND_AD_USER_PORT_KEY, AdActorSystem(_) => IFLA_BOND_AD_ACTOR_SYSTEM, TlbDynamicLb(_) => IFLA_BOND_TLB_DYNAMIC_LB, PeerNotifDelay(_) => IFLA_BOND_PEER_NOTIF_DELAY, AdLacpActive(_) => IFLA_BOND_AD_LACP_ACTIVE, MissedMax(_) => IFLA_BOND_MISSED_MAX, NsIp6Target(_) => IFLA_BOND_NS_IP6_TARGET, } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoBond { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::InfoBond::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_BOND_MODE => Mode(parse_u8(payload).context("invalid IFLA_BOND_MODE value")?), IFLA_BOND_ACTIVE_SLAVE => { ActiveSlave(parse_u32(payload).context("invalid IFLA_BOND_ACTIVE_SLAVE value")?) } IFLA_BOND_MIIMON => { MiiMon(parse_u32(payload).context("invalid IFLA_BOND_MIIMON value")?) } IFLA_BOND_UPDELAY => { UpDelay(parse_u32(payload).context("invalid IFLA_BOND_UPDELAY value")?) } IFLA_BOND_DOWNDELAY => { DownDelay(parse_u32(payload).context("invalid IFLA_BOND_DOWNDELAY value")?) } IFLA_BOND_USE_CARRIER => { UseCarrier(parse_u8(payload).context("invalid IFLA_BOND_USE_CARRIER value")?) } IFLA_BOND_ARP_INTERVAL => { ArpInterval(parse_u32(payload).context("invalid IFLA_BOND_ARP_INTERVAL value")?) } IFLA_BOND_ARP_IP_TARGET => { let mut addrs = Vec::::new(); for nla in NlasIterator::new(payload) { let nla = &nla.context("invalid IFLA_BOND_ARP_IP_TARGET value")?; if let Ok(IpAddr::V4(addr)) = parse_ip(nla.value()) { addrs.push(addr); } } ArpIpTarget(addrs) } IFLA_BOND_ARP_VALIDATE => { ArpValidate(parse_u32(payload).context("invalid IFLA_BOND_ARP_VALIDATE value")?) } IFLA_BOND_ARP_ALL_TARGETS => ArpAllTargets( parse_u32(payload).context("invalid IFLA_BOND_ARP_ALL_TARGETS value")?, ), IFLA_BOND_PRIMARY => { Primary(parse_u32(payload).context("invalid IFLA_BOND_PRIMARY value")?) } IFLA_BOND_PRIMARY_RESELECT => PrimaryReselect( parse_u8(payload).context("invalid IFLA_BOND_PRIMARY_RESELECT value")?, ), IFLA_BOND_FAIL_OVER_MAC => { FailOverMac(parse_u8(payload).context("invalid IFLA_BOND_FAIL_OVER_MAC value")?) } IFLA_BOND_XMIT_HASH_POLICY => XmitHashPolicy( parse_u8(payload).context("invalid IFLA_BOND_XMIT_HASH_POLICY value")?, ), IFLA_BOND_RESEND_IGMP => { ResendIgmp(parse_u32(payload).context("invalid IFLA_BOND_RESEND_IGMP value")?) } IFLA_BOND_NUM_PEER_NOTIF => { NumPeerNotif(parse_u8(payload).context("invalid IFLA_BOND_NUM_PEER_NOTIF value")?) } IFLA_BOND_ALL_SLAVES_ACTIVE => AllSlavesActive( parse_u8(payload).context("invalid IFLA_BOND_ALL_SLAVES_ACTIVE value")?, ), IFLA_BOND_MIN_LINKS => { MinLinks(parse_u32(payload).context("invalid IFLA_BOND_MIN_LINKS value")?) } IFLA_BOND_LP_INTERVAL => { LpInterval(parse_u32(payload).context("invalid IFLA_BOND_LP_INTERVAL value")?) } IFLA_BOND_PACKETS_PER_SLAVE => PacketsPerSlave( parse_u32(payload).context("invalid IFLA_BOND_PACKETS_PER_SLAVE value")?, ), IFLA_BOND_AD_LACP_RATE => { AdLacpRate(parse_u8(payload).context("invalid IFLA_BOND_AD_LACP_RATE value")?) } IFLA_BOND_AD_SELECT => { AdSelect(parse_u8(payload).context("invalid IFLA_BOND_AD_SELECT value")?) } IFLA_BOND_AD_INFO => { let mut infos = Vec::new(); let err = "failed to parse IFLA_BOND_AD_INFO"; for nla in NlasIterator::new(payload) { let nla = &nla.context(err)?; let info = BondAdInfo::parse(nla).context(err)?; infos.push(info); } AdInfo(infos) } IFLA_BOND_AD_ACTOR_SYS_PRIO => AdActorSysPrio( parse_u16(payload).context("invalid IFLA_BOND_AD_ACTOR_SYS_PRIO value")?, ), IFLA_BOND_AD_USER_PORT_KEY => AdUserPortKey( parse_u16(payload).context("invalid IFLA_BOND_AD_USER_PORT_KEY value")?, ), IFLA_BOND_AD_ACTOR_SYSTEM => AdActorSystem( parse_mac(payload).context("invalid IFLA_BOND_AD_ACTOR_SYSTEM value")?, ), IFLA_BOND_TLB_DYNAMIC_LB => { TlbDynamicLb(parse_u8(payload).context("invalid IFLA_BOND_TLB_DYNAMIC_LB value")?) } IFLA_BOND_PEER_NOTIF_DELAY => PeerNotifDelay( parse_u32(payload).context("invalid IFLA_BOND_PEER_NOTIF_DELAY value")?, ), IFLA_BOND_AD_LACP_ACTIVE => { AdLacpActive(parse_u8(payload).context("invalid IFLA_BOND_AD_LACP_ACTIVE value")?) } IFLA_BOND_MISSED_MAX => { MissedMax(parse_u8(payload).context("invalid IFLA_BOND_MISSED_MAX value")?) } IFLA_BOND_NS_IP6_TARGET => { let mut addrs = Vec::::new(); for nla in NlasIterator::new(payload) { let nla = &nla.context("invalid IFLA_BOND_NS_IP6_TARGET value")?; if let Ok(IpAddr::V6(addr)) = parse_ip(nla.value()) { addrs.push(addr); } } NsIp6Target(addrs) } _ => return Err(format!("unknown NLA type {}", buf.kind()).into()), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/inet/dev_conf.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; pub const DEV_CONF_LEN: usize = 124; buffer!(InetDevConfBuffer(DEV_CONF_LEN) { forwarding: (i32, 0..4), mc_forwarding: (i32, 4..8), proxy_arp: (i32, 8..12), accept_redirects: (i32, 12..16), secure_redirects: (i32, 16..20), send_redirects: (i32, 20..24), shared_media: (i32, 24..28), rp_filter: (i32, 28..32), accept_source_route: (i32, 32..36), bootp_relay: (i32, 36..40), log_martians: (i32, 40..44), tag: (i32, 44..48), arpfilter: (i32, 48..52), medium_id: (i32, 52..56), noxfrm: (i32, 56..60), nopolicy: (i32, 60..64), force_igmp_version: (i32, 64..68), arp_announce: (i32, 68..72), arp_ignore: (i32, 72..76), promote_secondaries: (i32, 76..80), arp_accept: (i32, 80..84), arp_notify: (i32, 84..88), accept_local: (i32, 88..92), src_vmark: (i32, 92..96), proxy_arp_pvlan: (i32, 96..100), route_localnet: (i32, 100..104), igmpv2_unsolicited_report_interval: (i32, 104..108), igmpv3_unsolicited_report_interval: (i32, 108..112), ignore_routes_with_linkdown: (i32, 112..116), drop_unicast_in_l2_multicast: (i32, 116..120), drop_gratuitous_arp: (i32, 120..124), }); #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct InetDevConf { pub forwarding: i32, pub mc_forwarding: i32, pub proxy_arp: i32, pub accept_redirects: i32, pub secure_redirects: i32, pub send_redirects: i32, pub shared_media: i32, pub rp_filter: i32, pub accept_source_route: i32, pub bootp_relay: i32, pub log_martians: i32, pub tag: i32, pub arpfilter: i32, pub medium_id: i32, pub noxfrm: i32, pub nopolicy: i32, pub force_igmp_version: i32, pub arp_announce: i32, pub arp_ignore: i32, pub promote_secondaries: i32, pub arp_accept: i32, pub arp_notify: i32, pub accept_local: i32, pub src_vmark: i32, pub proxy_arp_pvlan: i32, pub route_localnet: i32, pub igmpv2_unsolicited_report_interval: i32, pub igmpv3_unsolicited_report_interval: i32, pub ignore_routes_with_linkdown: i32, pub drop_unicast_in_l2_multicast: i32, pub drop_gratuitous_arp: i32, } impl> Parseable> for InetDevConf { fn parse(buf: &InetDevConfBuffer) -> Result { Ok(Self { forwarding: buf.forwarding(), mc_forwarding: buf.mc_forwarding(), proxy_arp: buf.proxy_arp(), accept_redirects: buf.accept_redirects(), secure_redirects: buf.secure_redirects(), send_redirects: buf.send_redirects(), shared_media: buf.shared_media(), rp_filter: buf.rp_filter(), accept_source_route: buf.accept_source_route(), bootp_relay: buf.bootp_relay(), log_martians: buf.log_martians(), tag: buf.tag(), arpfilter: buf.arpfilter(), medium_id: buf.medium_id(), noxfrm: buf.noxfrm(), nopolicy: buf.nopolicy(), force_igmp_version: buf.force_igmp_version(), arp_announce: buf.arp_announce(), arp_ignore: buf.arp_ignore(), promote_secondaries: buf.promote_secondaries(), arp_accept: buf.arp_accept(), arp_notify: buf.arp_notify(), accept_local: buf.accept_local(), src_vmark: buf.src_vmark(), proxy_arp_pvlan: buf.proxy_arp_pvlan(), route_localnet: buf.route_localnet(), igmpv2_unsolicited_report_interval: buf.igmpv2_unsolicited_report_interval(), igmpv3_unsolicited_report_interval: buf.igmpv3_unsolicited_report_interval(), ignore_routes_with_linkdown: buf.ignore_routes_with_linkdown(), drop_unicast_in_l2_multicast: buf.drop_unicast_in_l2_multicast(), drop_gratuitous_arp: buf.drop_gratuitous_arp(), }) } } impl Emitable for InetDevConf { fn buffer_len(&self) -> usize { DEV_CONF_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = InetDevConfBuffer::new(buffer); buffer.set_forwarding(self.forwarding); buffer.set_mc_forwarding(self.mc_forwarding); buffer.set_proxy_arp(self.proxy_arp); buffer.set_accept_redirects(self.accept_redirects); buffer.set_secure_redirects(self.secure_redirects); buffer.set_send_redirects(self.send_redirects); buffer.set_shared_media(self.shared_media); buffer.set_rp_filter(self.rp_filter); buffer.set_accept_source_route(self.accept_source_route); buffer.set_bootp_relay(self.bootp_relay); buffer.set_log_martians(self.log_martians); buffer.set_tag(self.tag); buffer.set_arpfilter(self.arpfilter); buffer.set_medium_id(self.medium_id); buffer.set_noxfrm(self.noxfrm); buffer.set_nopolicy(self.nopolicy); buffer.set_force_igmp_version(self.force_igmp_version); buffer.set_arp_announce(self.arp_announce); buffer.set_arp_ignore(self.arp_ignore); buffer.set_promote_secondaries(self.promote_secondaries); buffer.set_arp_accept(self.arp_accept); buffer.set_arp_notify(self.arp_notify); buffer.set_accept_local(self.accept_local); buffer.set_src_vmark(self.src_vmark); buffer.set_proxy_arp_pvlan(self.proxy_arp_pvlan); buffer.set_route_localnet(self.route_localnet); buffer.set_igmpv2_unsolicited_report_interval(self.igmpv2_unsolicited_report_interval); buffer.set_igmpv3_unsolicited_report_interval(self.igmpv3_unsolicited_report_interval); buffer.set_ignore_routes_with_linkdown(self.ignore_routes_with_linkdown); buffer.set_drop_unicast_in_l2_multicast(self.drop_unicast_in_l2_multicast); buffer.set_drop_gratuitous_arp(self.drop_gratuitous_arp); } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/inet/mod.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use crate::{ constants::{IFLA_INET_CONF, IFLA_INET_UNSPEC}, nlas::{DefaultNla, Nla, NlaBuffer}, traits::Parseable, DecodeError, }; mod dev_conf; pub use self::dev_conf::*; #[derive(Clone, Eq, PartialEq, Debug)] pub enum Inet { DevConf(Vec), Unspec(Vec), Other(DefaultNla), } impl Nla for Inet { fn value_len(&self) -> usize { use self::Inet::*; match *self { Unspec(ref bytes) => bytes.len(), DevConf(_) => DEV_CONF_LEN, Other(ref nla) => nla.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::Inet::*; match *self { Unspec(ref bytes) => buffer[..bytes.len()].copy_from_slice(bytes.as_slice()), DevConf(ref dev_conf) => buffer[..dev_conf.len()].copy_from_slice(dev_conf.as_slice()), Other(ref nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Inet::*; match *self { Unspec(_) => IFLA_INET_UNSPEC, DevConf(_) => IFLA_INET_CONF, Other(ref nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Inet { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::Inet::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_INET_UNSPEC => Unspec(payload.to_vec()), IFLA_INET_CONF => DevConf(payload.to_vec()), kind => Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/inet6/cache.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct Inet6CacheInfo { pub max_reasm_len: i32, pub tstamp: i32, pub reachable_time: i32, pub retrans_time: i32, } pub const LINK_INET6_CACHE_INFO_LEN: usize = 16; buffer!(Inet6CacheInfoBuffer(LINK_INET6_CACHE_INFO_LEN) { max_reasm_len: (i32, 0..4), tstamp: (i32, 4..8), reachable_time: (i32, 8..12), retrans_time: (i32, 12..16), }); impl> Parseable> for Inet6CacheInfo { fn parse(buf: &Inet6CacheInfoBuffer) -> Result { Ok(Self { max_reasm_len: buf.max_reasm_len(), tstamp: buf.tstamp(), reachable_time: buf.reachable_time(), retrans_time: buf.retrans_time(), }) } } impl Emitable for Inet6CacheInfo { fn buffer_len(&self) -> usize { LINK_INET6_CACHE_INFO_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = Inet6CacheInfoBuffer::new(buffer); buffer.set_max_reasm_len(self.max_reasm_len); buffer.set_tstamp(self.tstamp); buffer.set_reachable_time(self.reachable_time); buffer.set_retrans_time(self.retrans_time); } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/inet6/dev_conf.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; pub const LINK_INET6_DEV_CONF_LEN: usize = 204; buffer!(Inet6DevConfBuffer(LINK_INET6_DEV_CONF_LEN) { forwarding: (i32, 0..4), hoplimit: (i32, 4..8), mtu6: (i32, 8..12), accept_ra: (i32, 12..16), accept_redirects: (i32, 16..20), autoconf: (i32, 20..24), dad_transmits: (i32, 24..28), rtr_solicits: (i32, 28..32), rtr_solicit_interval: (i32, 32..36), rtr_solicit_delay: (i32, 36..40), use_tempaddr: (i32, 40..44), temp_valid_lft: (i32, 44..48), temp_prefered_lft: (i32, 48..52), regen_max_retry: (i32, 52..56), max_desync_factor: (i32, 56..60), max_addresses: (i32, 60..64), force_mld_version: (i32, 64..68), accept_ra_defrtr: (i32, 68..72), accept_ra_pinfo: (i32, 72..76), accept_ra_rtr_pref: (i32, 76..80), rtr_probe_interval: (i32, 80..84), accept_ra_rt_info_max_plen: (i32, 84..88), proxy_ndp: (i32, 88..92), optimistic_dad: (i32, 92..96), accept_source_route: (i32, 96..100), mc_forwarding: (i32, 100..104), disable_ipv6: (i32, 104..108), accept_dad: (i32, 108..112), force_tllao: (i32, 112..116), ndisc_notify: (i32, 116..120), mldv1_unsolicited_report_interval: (i32, 120..124), mldv2_unsolicited_report_interval: (i32, 124..128), suppress_frag_ndisc: (i32, 128..132), accept_ra_from_local: (i32, 132..136), use_optimistic: (i32, 136..140), accept_ra_mtu: (i32, 140..144), stable_secret: (i32, 144..148), use_oif_addrs_only: (i32, 148..152), accept_ra_min_hop_limit: (i32, 152..156), ignore_routes_with_linkdown: (i32, 156..160), drop_unicast_in_l2_multicast: (i32, 160..164), drop_unsolicited_na: (i32, 164..168), keep_addr_on_down: (i32, 168..172), rtr_solicit_max_interval: (i32, 172..176), seg6_enabled: (i32, 176..180), seg6_require_hmac: (i32, 180..184), enhanced_dad: (i32, 184..188), addr_gen_mode: (i32, 188..192), disable_policy: (i32, 192..196), accept_ra_rt_info_min_plen: (i32, 196..200), ndisc_tclass: (i32, 200..204), }); impl> Parseable> for Inet6DevConf { fn parse(buf: &Inet6DevConfBuffer) -> Result { Ok(Self { forwarding: buf.forwarding(), hoplimit: buf.hoplimit(), mtu6: buf.mtu6(), accept_ra: buf.accept_ra(), accept_redirects: buf.accept_redirects(), autoconf: buf.autoconf(), dad_transmits: buf.dad_transmits(), rtr_solicits: buf.rtr_solicits(), rtr_solicit_interval: buf.rtr_solicit_interval(), rtr_solicit_delay: buf.rtr_solicit_delay(), use_tempaddr: buf.use_tempaddr(), temp_valid_lft: buf.temp_valid_lft(), temp_prefered_lft: buf.temp_prefered_lft(), regen_max_retry: buf.regen_max_retry(), max_desync_factor: buf.max_desync_factor(), max_addresses: buf.max_addresses(), force_mld_version: buf.force_mld_version(), accept_ra_defrtr: buf.accept_ra_defrtr(), accept_ra_pinfo: buf.accept_ra_pinfo(), accept_ra_rtr_pref: buf.accept_ra_rtr_pref(), rtr_probe_interval: buf.rtr_probe_interval(), accept_ra_rt_info_max_plen: buf.accept_ra_rt_info_max_plen(), proxy_ndp: buf.proxy_ndp(), optimistic_dad: buf.optimistic_dad(), accept_source_route: buf.accept_source_route(), mc_forwarding: buf.mc_forwarding(), disable_ipv6: buf.disable_ipv6(), accept_dad: buf.accept_dad(), force_tllao: buf.force_tllao(), ndisc_notify: buf.ndisc_notify(), mldv1_unsolicited_report_interval: buf.mldv1_unsolicited_report_interval(), mldv2_unsolicited_report_interval: buf.mldv2_unsolicited_report_interval(), suppress_frag_ndisc: buf.suppress_frag_ndisc(), accept_ra_from_local: buf.accept_ra_from_local(), use_optimistic: buf.use_optimistic(), accept_ra_mtu: buf.accept_ra_mtu(), stable_secret: buf.stable_secret(), use_oif_addrs_only: buf.use_oif_addrs_only(), accept_ra_min_hop_limit: buf.accept_ra_min_hop_limit(), ignore_routes_with_linkdown: buf.ignore_routes_with_linkdown(), drop_unicast_in_l2_multicast: buf.drop_unicast_in_l2_multicast(), drop_unsolicited_na: buf.drop_unsolicited_na(), keep_addr_on_down: buf.keep_addr_on_down(), rtr_solicit_max_interval: buf.rtr_solicit_max_interval(), seg6_enabled: buf.seg6_enabled(), seg6_require_hmac: buf.seg6_require_hmac(), enhanced_dad: buf.enhanced_dad(), addr_gen_mode: buf.addr_gen_mode(), disable_policy: buf.disable_policy(), accept_ra_rt_info_min_plen: buf.accept_ra_rt_info_min_plen(), ndisc_tclass: buf.ndisc_tclass(), }) } } impl Emitable for Inet6DevConf { fn buffer_len(&self) -> usize { LINK_INET6_DEV_CONF_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = Inet6DevConfBuffer::new(buffer); buffer.set_forwarding(self.forwarding); buffer.set_hoplimit(self.hoplimit); buffer.set_mtu6(self.mtu6); buffer.set_accept_ra(self.accept_ra); buffer.set_accept_redirects(self.accept_redirects); buffer.set_autoconf(self.autoconf); buffer.set_dad_transmits(self.dad_transmits); buffer.set_rtr_solicits(self.rtr_solicits); buffer.set_rtr_solicit_interval(self.rtr_solicit_interval); buffer.set_rtr_solicit_delay(self.rtr_solicit_delay); buffer.set_use_tempaddr(self.use_tempaddr); buffer.set_temp_valid_lft(self.temp_valid_lft); buffer.set_temp_prefered_lft(self.temp_prefered_lft); buffer.set_regen_max_retry(self.regen_max_retry); buffer.set_max_desync_factor(self.max_desync_factor); buffer.set_max_addresses(self.max_addresses); buffer.set_force_mld_version(self.force_mld_version); buffer.set_accept_ra_defrtr(self.accept_ra_defrtr); buffer.set_accept_ra_pinfo(self.accept_ra_pinfo); buffer.set_accept_ra_rtr_pref(self.accept_ra_rtr_pref); buffer.set_rtr_probe_interval(self.rtr_probe_interval); buffer.set_accept_ra_rt_info_max_plen(self.accept_ra_rt_info_max_plen); buffer.set_proxy_ndp(self.proxy_ndp); buffer.set_optimistic_dad(self.optimistic_dad); buffer.set_accept_source_route(self.accept_source_route); buffer.set_mc_forwarding(self.mc_forwarding); buffer.set_disable_ipv6(self.disable_ipv6); buffer.set_accept_dad(self.accept_dad); buffer.set_force_tllao(self.force_tllao); buffer.set_ndisc_notify(self.ndisc_notify); buffer.set_mldv1_unsolicited_report_interval(self.mldv1_unsolicited_report_interval); buffer.set_mldv2_unsolicited_report_interval(self.mldv2_unsolicited_report_interval); buffer.set_suppress_frag_ndisc(self.suppress_frag_ndisc); buffer.set_accept_ra_from_local(self.accept_ra_from_local); buffer.set_use_optimistic(self.use_optimistic); buffer.set_accept_ra_mtu(self.accept_ra_mtu); buffer.set_stable_secret(self.stable_secret); buffer.set_use_oif_addrs_only(self.use_oif_addrs_only); buffer.set_accept_ra_min_hop_limit(self.accept_ra_min_hop_limit); buffer.set_ignore_routes_with_linkdown(self.ignore_routes_with_linkdown); buffer.set_drop_unicast_in_l2_multicast(self.drop_unicast_in_l2_multicast); buffer.set_drop_unsolicited_na(self.drop_unsolicited_na); buffer.set_keep_addr_on_down(self.keep_addr_on_down); buffer.set_rtr_solicit_max_interval(self.rtr_solicit_max_interval); buffer.set_seg6_enabled(self.seg6_enabled); buffer.set_seg6_require_hmac(self.seg6_require_hmac); buffer.set_enhanced_dad(self.enhanced_dad); buffer.set_addr_gen_mode(self.addr_gen_mode); buffer.set_disable_policy(self.disable_policy); buffer.set_accept_ra_rt_info_min_plen(self.accept_ra_rt_info_min_plen); buffer.set_ndisc_tclass(self.ndisc_tclass); } } #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct Inet6DevConf { pub forwarding: i32, pub hoplimit: i32, pub mtu6: i32, pub accept_ra: i32, pub accept_redirects: i32, pub autoconf: i32, pub dad_transmits: i32, pub rtr_solicits: i32, pub rtr_solicit_interval: i32, pub rtr_solicit_delay: i32, pub use_tempaddr: i32, pub temp_valid_lft: i32, pub temp_prefered_lft: i32, pub regen_max_retry: i32, pub max_desync_factor: i32, pub max_addresses: i32, pub force_mld_version: i32, pub accept_ra_defrtr: i32, pub accept_ra_pinfo: i32, pub accept_ra_rtr_pref: i32, pub rtr_probe_interval: i32, pub accept_ra_rt_info_max_plen: i32, pub proxy_ndp: i32, pub optimistic_dad: i32, pub accept_source_route: i32, pub mc_forwarding: i32, pub disable_ipv6: i32, pub accept_dad: i32, pub force_tllao: i32, pub ndisc_notify: i32, pub mldv1_unsolicited_report_interval: i32, pub mldv2_unsolicited_report_interval: i32, pub suppress_frag_ndisc: i32, pub accept_ra_from_local: i32, pub use_optimistic: i32, pub accept_ra_mtu: i32, pub stable_secret: i32, pub use_oif_addrs_only: i32, pub accept_ra_min_hop_limit: i32, pub ignore_routes_with_linkdown: i32, pub drop_unicast_in_l2_multicast: i32, pub drop_unsolicited_na: i32, pub keep_addr_on_down: i32, pub rtr_solicit_max_interval: i32, pub seg6_enabled: i32, pub seg6_require_hmac: i32, pub enhanced_dad: i32, pub addr_gen_mode: i32, pub disable_policy: i32, pub accept_ra_rt_info_min_plen: i32, pub ndisc_tclass: i32, } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/inet6/icmp6_stats.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct Icmp6Stats { pub num: i64, pub in_msgs: i64, pub in_errors: i64, pub out_msgs: i64, pub out_errors: i64, pub csum_errors: i64, } pub const ICMP6_STATS_LEN: usize = 48; buffer!(Icmp6StatsBuffer(ICMP6_STATS_LEN) { num: (i64, 0..8), in_msgs: (i64, 8..16), in_errors: (i64, 16..24), out_msgs: (i64, 24..32), out_errors: (i64, 32..40), csum_errors: (i64, 40..48), }); impl> Parseable> for Icmp6Stats { fn parse(buf: &Icmp6StatsBuffer) -> Result { Ok(Self { num: buf.num(), in_msgs: buf.in_msgs(), in_errors: buf.in_errors(), out_msgs: buf.out_msgs(), out_errors: buf.out_errors(), csum_errors: buf.csum_errors(), }) } } impl Emitable for Icmp6Stats { fn buffer_len(&self) -> usize { ICMP6_STATS_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = Icmp6StatsBuffer::new(buffer); buffer.set_num(self.num); buffer.set_in_msgs(self.in_msgs); buffer.set_in_errors(self.in_errors); buffer.set_out_msgs(self.out_msgs); buffer.set_out_errors(self.out_errors); buffer.set_csum_errors(self.csum_errors); } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/inet6/mod.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use crate::{ constants::*, nlas::{DefaultNla, Nla, NlaBuffer}, parsers::{parse_ipv6, parse_u32, parse_u8}, traits::Parseable, DecodeError, }; mod cache; pub use self::cache::*; mod dev_conf; pub use self::dev_conf::*; mod icmp6_stats; pub use self::icmp6_stats::*; mod stats; pub use self::stats::*; #[derive(Clone, Eq, PartialEq, Debug)] pub enum Inet6 { Flags(u32), CacheInfo(Vec), DevConf(Vec), Unspec(Vec), Stats(Vec), IcmpStats(Vec), Token([u8; 16]), AddrGenMode(u8), Other(DefaultNla), } impl Nla for Inet6 { fn value_len(&self) -> usize { use self::Inet6::*; match *self { Unspec(ref bytes) => bytes.len(), CacheInfo(ref cache_info) => cache_info.len(), DevConf(ref dev_conf) => dev_conf.len(), Stats(ref stats) => stats.len(), IcmpStats(ref icmp_stats) => icmp_stats.len(), Flags(_) => 4, Token(_) => 16, AddrGenMode(_) => 1, Other(ref nla) => nla.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::Inet6::*; match *self { Unspec(ref bytes) => buffer.copy_from_slice(bytes.as_slice()), Flags(ref value) => NativeEndian::write_u32(buffer, *value), CacheInfo(ref cache_info) => buffer.copy_from_slice(cache_info.as_slice()), DevConf(ref bytes) => buffer.copy_from_slice(bytes.as_slice()), Stats(ref inet6_stats) => buffer.copy_from_slice(inet6_stats.as_slice()), IcmpStats(ref icmp6_stats) => buffer.copy_from_slice(icmp6_stats.as_slice()), Token(ref ipv6) => buffer.copy_from_slice(&ipv6[..]), AddrGenMode(value) => buffer[0] = value, Other(ref nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Inet6::*; match *self { Unspec(_) => IFLA_INET6_UNSPEC, Flags(_) => IFLA_INET6_FLAGS, CacheInfo(_) => IFLA_INET6_CACHEINFO, DevConf(_) => IFLA_INET6_CONF, Stats(_) => IFLA_INET6_STATS, IcmpStats(_) => IFLA_INET6_ICMP6STATS, Token(_) => IFLA_INET6_TOKEN, AddrGenMode(_) => IFLA_INET6_ADDR_GEN_MODE, Other(ref nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Inet6 { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::Inet6::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_INET6_UNSPEC => Unspec(payload.to_vec()), IFLA_INET6_FLAGS => { Flags(parse_u32(payload).context("invalid IFLA_INET6_FLAGS value")?) } IFLA_INET6_CACHEINFO => CacheInfo(payload.to_vec()), IFLA_INET6_CONF => DevConf(payload.to_vec()), IFLA_INET6_STATS => Stats(payload.to_vec()), IFLA_INET6_ICMP6STATS => IcmpStats(payload.to_vec()), IFLA_INET6_TOKEN => { Token(parse_ipv6(payload).context("invalid IFLA_INET6_TOKEN value")?) } IFLA_INET6_ADDR_GEN_MODE => { AddrGenMode(parse_u8(payload).context("invalid IFLA_INET6_ADDR_GEN_MODE value")?) } kind => Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/inet6/stats.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; pub const INET6_STATS_LEN: usize = 288; buffer!(Inet6StatsBuffer(INET6_STATS_LEN) { num: (i64, 0..8), in_pkts: (i64, 8..16), in_octets: (i64, 16..24), in_delivers: (i64, 24..32), out_forw_datagrams: (i64, 32..40), out_pkts: (i64, 40..48), out_octets: (i64, 48..56), in_hdr_errors: (i64, 56..64), in_too_big_errors: (i64, 64..72), in_no_routes: (i64, 72..80), in_addr_errors: (i64, 80..88), in_unknown_protos: (i64, 88..96), in_truncated_pkts: (i64, 96..104), in_discards: (i64, 104..112), out_discards: (i64, 112..120), out_no_routes: (i64, 120..128), reasm_timeout: (i64, 128..136), reasm_reqds: (i64, 136..144), reasm_oks: (i64, 144..152), reasm_fails: (i64, 152..160), frag_oks: (i64, 160..168), frag_fails: (i64, 168..176), frag_creates: (i64, 176..184), in_mcast_pkts: (i64, 184..192), out_mcast_pkts: (i64, 192..200), in_bcast_pkts: (i64, 200..208), out_bcast_pkts: (i64, 208..216), in_mcast_octets: (i64, 216..224), out_mcast_octets: (i64, 224..232), in_bcast_octets: (i64, 232..240), out_bcast_octets: (i64, 240..248), in_csum_errors: (i64, 248..256), in_no_ect_pkts: (i64, 256..264), in_ect1_pkts: (i64, 264..272), in_ect0_pkts: (i64, 272..280), in_ce_pkts: (i64, 280..288), }); #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct Inet6Stats { pub num: i64, pub in_pkts: i64, pub in_octets: i64, pub in_delivers: i64, pub out_forw_datagrams: i64, pub out_pkts: i64, pub out_octets: i64, pub in_hdr_errors: i64, pub in_too_big_errors: i64, pub in_no_routes: i64, pub in_addr_errors: i64, pub in_unknown_protos: i64, pub in_truncated_pkts: i64, pub in_discards: i64, pub out_discards: i64, pub out_no_routes: i64, pub reasm_timeout: i64, pub reasm_reqds: i64, pub reasm_oks: i64, pub reasm_fails: i64, pub frag_oks: i64, pub frag_fails: i64, pub frag_creates: i64, pub in_mcast_pkts: i64, pub out_mcast_pkts: i64, pub in_bcast_pkts: i64, pub out_bcast_pkts: i64, pub in_mcast_octets: i64, pub out_mcast_octets: i64, pub in_bcast_octets: i64, pub out_bcast_octets: i64, pub in_csum_errors: i64, pub in_no_ect_pkts: i64, pub in_ect1_pkts: i64, pub in_ect0_pkts: i64, pub in_ce_pkts: i64, } impl> Parseable> for Inet6Stats { fn parse(buf: &Inet6StatsBuffer) -> Result { Ok(Self { num: buf.num(), in_pkts: buf.in_pkts(), in_octets: buf.in_octets(), in_delivers: buf.in_delivers(), out_forw_datagrams: buf.out_forw_datagrams(), out_pkts: buf.out_pkts(), out_octets: buf.out_octets(), in_hdr_errors: buf.in_hdr_errors(), in_too_big_errors: buf.in_too_big_errors(), in_no_routes: buf.in_no_routes(), in_addr_errors: buf.in_addr_errors(), in_unknown_protos: buf.in_unknown_protos(), in_truncated_pkts: buf.in_truncated_pkts(), in_discards: buf.in_discards(), out_discards: buf.out_discards(), out_no_routes: buf.out_no_routes(), reasm_timeout: buf.reasm_timeout(), reasm_reqds: buf.reasm_reqds(), reasm_oks: buf.reasm_oks(), reasm_fails: buf.reasm_fails(), frag_oks: buf.frag_oks(), frag_fails: buf.frag_fails(), frag_creates: buf.frag_creates(), in_mcast_pkts: buf.in_mcast_pkts(), out_mcast_pkts: buf.out_mcast_pkts(), in_bcast_pkts: buf.in_bcast_pkts(), out_bcast_pkts: buf.out_bcast_pkts(), in_mcast_octets: buf.in_mcast_octets(), out_mcast_octets: buf.out_mcast_octets(), in_bcast_octets: buf.in_bcast_octets(), out_bcast_octets: buf.out_bcast_octets(), in_csum_errors: buf.in_csum_errors(), in_no_ect_pkts: buf.in_no_ect_pkts(), in_ect1_pkts: buf.in_ect1_pkts(), in_ect0_pkts: buf.in_ect0_pkts(), in_ce_pkts: buf.in_ce_pkts(), }) } } impl Emitable for Inet6Stats { fn buffer_len(&self) -> usize { INET6_STATS_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = Inet6StatsBuffer::new(buffer); buffer.set_num(self.num); buffer.set_in_pkts(self.in_pkts); buffer.set_in_octets(self.in_octets); buffer.set_in_delivers(self.in_delivers); buffer.set_out_forw_datagrams(self.out_forw_datagrams); buffer.set_out_pkts(self.out_pkts); buffer.set_out_octets(self.out_octets); buffer.set_in_hdr_errors(self.in_hdr_errors); buffer.set_in_too_big_errors(self.in_too_big_errors); buffer.set_in_no_routes(self.in_no_routes); buffer.set_in_addr_errors(self.in_addr_errors); buffer.set_in_unknown_protos(self.in_unknown_protos); buffer.set_in_truncated_pkts(self.in_truncated_pkts); buffer.set_in_discards(self.in_discards); buffer.set_out_discards(self.out_discards); buffer.set_out_no_routes(self.out_no_routes); buffer.set_reasm_timeout(self.reasm_timeout); buffer.set_reasm_reqds(self.reasm_reqds); buffer.set_reasm_oks(self.reasm_oks); buffer.set_reasm_fails(self.reasm_fails); buffer.set_frag_oks(self.frag_oks); buffer.set_frag_fails(self.frag_fails); buffer.set_frag_creates(self.frag_creates); buffer.set_in_mcast_pkts(self.in_mcast_pkts); buffer.set_out_mcast_pkts(self.out_mcast_pkts); buffer.set_in_bcast_pkts(self.in_bcast_pkts); buffer.set_out_bcast_pkts(self.out_bcast_pkts); buffer.set_in_mcast_octets(self.in_mcast_octets); buffer.set_out_mcast_octets(self.out_mcast_octets); buffer.set_in_bcast_octets(self.in_bcast_octets); buffer.set_out_bcast_octets(self.out_bcast_octets); buffer.set_in_csum_errors(self.in_csum_errors); buffer.set_in_no_ect_pkts(self.in_no_ect_pkts); buffer.set_in_ect1_pkts(self.in_ect1_pkts); buffer.set_in_ect0_pkts(self.in_ect0_pkts); buffer.set_in_ce_pkts(self.in_ce_pkts); } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/link_infos.rs ================================================ // SPDX-License-Identifier: MIT use super::bond::InfoBond; use crate::{ constants::*, nlas::{DefaultNla, Nla, NlaBuffer, NlasIterator}, parsers::{parse_mac, parse_string, parse_u16, parse_u16_be, parse_u32, parse_u64, parse_u8}, traits::{Emitable, Parseable}, DecodeError, LinkMessage, LinkMessageBuffer, }; use anyhow::Context; use byteorder::{BigEndian, ByteOrder, NativeEndian}; const DUMMY: &str = "dummy"; const IFB: &str = "ifb"; const BRIDGE: &str = "bridge"; const TUN: &str = "tun"; const NLMON: &str = "nlmon"; const VLAN: &str = "vlan"; const VETH: &str = "veth"; const VXLAN: &str = "vxlan"; const BOND: &str = "bond"; const IPVLAN: &str = "ipvlan"; const MACVLAN: &str = "macvlan"; const MACVTAP: &str = "macvtap"; const GRETAP: &str = "gretap"; const IP6GRETAP: &str = "ip6gretap"; const IPIP: &str = "ipip"; const SIT: &str = "sit"; const GRE: &str = "gre"; const IP6GRE: &str = "ip6gre"; const VTI: &str = "vti"; const VRF: &str = "vrf"; const GTP: &str = "gtp"; const IPOIB: &str = "ipoib"; const WIREGUARD: &str = "wireguard"; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Info { Unspec(Vec), Xstats(Vec), Kind(InfoKind), Data(InfoData), SlaveKind(Vec), SlaveData(Vec), } impl Nla for Info { #[rustfmt::skip] fn value_len(&self) -> usize { use self::Info::*; match self { Unspec(ref bytes) | Xstats(ref bytes) | SlaveKind(ref bytes) | SlaveData(ref bytes) => bytes.len(), Kind(ref nla) => nla.value_len(), Data(ref nla) => nla.value_len(), } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::Info::*; match self { Unspec(ref bytes) | Xstats(ref bytes) | SlaveKind(ref bytes) | SlaveData(ref bytes) => buffer.copy_from_slice(bytes), Kind(ref nla) => nla.emit_value(buffer), Data(ref nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Info::*; match self { Unspec(_) => IFLA_INFO_UNSPEC, Xstats(_) => IFLA_INFO_XSTATS, SlaveKind(_) => IFLA_INFO_SLAVE_KIND, SlaveData(_) => IFLA_INFO_DATA, Kind(_) => IFLA_INFO_KIND, Data(_) => IFLA_INFO_DATA, } } } pub(crate) struct VecInfo(pub(crate) Vec); // We cannot `impl Parseable<_> for Info` because some attributes // depend on each other. To parse IFLA_INFO_DATA we first need to // parse the preceding IFLA_INFO_KIND for example. // // Moreover, with cannot `impl Parseable for Vec` due to the // orphan rule: `Parseable` and `Vec<_>` are both defined outside of // this crate. Thus, we create this internal VecInfo struct that wraps // `Vec` and allows us to circumvent the orphan rule. // // The downside is that this impl will not be exposed. impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for VecInfo { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let mut res = Vec::new(); let nlas = NlasIterator::new(buf.into_inner()); let mut link_info_kind: Option = None; for nla in nlas { let nla = nla?; match nla.kind() { IFLA_INFO_UNSPEC => res.push(Info::Unspec(nla.value().to_vec())), IFLA_INFO_XSTATS => res.push(Info::Xstats(nla.value().to_vec())), IFLA_INFO_SLAVE_KIND => res.push(Info::SlaveKind(nla.value().to_vec())), IFLA_INFO_SLAVE_DATA => res.push(Info::SlaveData(nla.value().to_vec())), IFLA_INFO_KIND => { let parsed = InfoKind::parse(&nla)?; res.push(Info::Kind(parsed.clone())); link_info_kind = Some(parsed); } IFLA_INFO_DATA => { if let Some(link_info_kind) = link_info_kind { let payload = nla.value(); let info_data = match link_info_kind { InfoKind::Dummy => InfoData::Dummy(payload.to_vec()), InfoKind::Ifb => InfoData::Ifb(payload.to_vec()), InfoKind::Bridge => { let mut v = Vec::new(); let err = "failed to parse IFLA_INFO_DATA (IFLA_INFO_KIND is 'bridge')"; for nla in NlasIterator::new(payload) { let nla = &nla.context(err)?; let parsed = InfoBridge::parse(nla).context(err)?; v.push(parsed); } InfoData::Bridge(v) } InfoKind::Vlan => { let mut v = Vec::new(); let err = "failed to parse IFLA_INFO_DATA (IFLA_INFO_KIND is 'vlan')"; for nla in NlasIterator::new(payload) { let nla = &nla.context(err)?; let parsed = InfoVlan::parse(nla).context(err)?; v.push(parsed); } InfoData::Vlan(v) } InfoKind::Tun => InfoData::Tun(payload.to_vec()), InfoKind::Nlmon => InfoData::Nlmon(payload.to_vec()), InfoKind::Veth => { let err = "failed to parse IFLA_INFO_DATA (IFLA_INFO_KIND is 'veth')"; let nla_buf = NlaBuffer::new_checked(&payload).context(err)?; let parsed = VethInfo::parse(&nla_buf).context(err)?; InfoData::Veth(parsed) } InfoKind::Vxlan => { let mut v = Vec::new(); let err = "failed to parse IFLA_INFO_DATA (IFLA_INFO_KIND is 'vxlan')"; for nla in NlasIterator::new(payload) { let nla = &nla.context(err)?; let parsed = InfoVxlan::parse(nla).context(err)?; v.push(parsed); } InfoData::Vxlan(v) } InfoKind::Bond => { let mut v = Vec::new(); let err = "failed to parse IFLA_INFO_DATA (IFLA_INFO_KIND is 'bond')"; for nla in NlasIterator::new(payload) { let nla = &nla.context(err)?; let parsed = InfoBond::parse(nla).context(err)?; v.push(parsed); } InfoData::Bond(v) } InfoKind::IpVlan => { let mut v = Vec::new(); let err = "failed to parse IFLA_INFO_DATA (IFLA_INFO_KIND is 'ipvlan')"; for nla in NlasIterator::new(payload) { let nla = &nla.context(err)?; let parsed = InfoIpVlan::parse(nla).context(err)?; v.push(parsed); } InfoData::IpVlan(v) } InfoKind::MacVlan => { let mut v = Vec::new(); let err = "failed to parse IFLA_INFO_DATA (IFLA_INFO_KIND is 'macvlan')"; for nla in NlasIterator::new(payload) { let nla = &nla.context(err)?; let parsed = InfoMacVlan::parse(nla).context(err)?; v.push(parsed); } InfoData::MacVlan(v) } InfoKind::MacVtap => { let mut v = Vec::new(); let err = "failed to parse IFLA_INFO_DATA (IFLA_INFO_KIND is 'macvtap')"; for nla in NlasIterator::new(payload) { let nla = &nla.context(err)?; let parsed = InfoMacVtap::parse(nla).context(err)?; v.push(parsed); } InfoData::MacVtap(v) } InfoKind::GreTap => InfoData::GreTap(payload.to_vec()), InfoKind::GreTap6 => InfoData::GreTap6(payload.to_vec()), InfoKind::IpTun => InfoData::IpTun(payload.to_vec()), InfoKind::SitTun => InfoData::SitTun(payload.to_vec()), InfoKind::GreTun => InfoData::GreTun(payload.to_vec()), InfoKind::GreTun6 => InfoData::GreTun6(payload.to_vec()), InfoKind::Vti => InfoData::Vti(payload.to_vec()), InfoKind::Vrf => { let mut v = Vec::new(); let err = "failed to parse IFLA_INFO_DATA (IFLA_INFO_KIND is 'vrf')"; for nla in NlasIterator::new(payload) { let nla = &nla.context(err)?; let parsed = InfoVrf::parse(nla).context(err)?; v.push(parsed); } InfoData::Vrf(v) } InfoKind::Gtp => InfoData::Gtp(payload.to_vec()), InfoKind::Ipoib => { let mut v = Vec::new(); let err = "failed to parse IFLA_INFO_DATA (IFLA_INFO_KIND is 'ipoib')"; for nla in NlasIterator::new(payload) { let nla = &nla.context(err)?; let parsed = InfoIpoib::parse(nla).context(err)?; v.push(parsed); } InfoData::Ipoib(v) } InfoKind::Wireguard => InfoData::Wireguard(payload.to_vec()), InfoKind::Other(_) => InfoData::Other(payload.to_vec()), }; res.push(Info::Data(info_data)); } else { return Err("IFLA_INFO_DATA is not preceded by an IFLA_INFO_KIND".into()); } link_info_kind = None; } _ => return Err(format!("unknown NLA type {}", nla.kind()).into()), } } Ok(VecInfo(res)) } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum InfoData { Bridge(Vec), Tun(Vec), Nlmon(Vec), Vlan(Vec), Dummy(Vec), Ifb(Vec), Veth(VethInfo), Vxlan(Vec), Bond(Vec), IpVlan(Vec), MacVlan(Vec), MacVtap(Vec), GreTap(Vec), GreTap6(Vec), IpTun(Vec), SitTun(Vec), GreTun(Vec), GreTun6(Vec), Vti(Vec), Vrf(Vec), Gtp(Vec), Ipoib(Vec), Wireguard(Vec), Other(Vec), } impl Nla for InfoData { #[rustfmt::skip] fn value_len(&self) -> usize { use self::InfoData::*; match self { Bond(ref nlas) => nlas.as_slice().buffer_len(), Bridge(ref nlas) => nlas.as_slice().buffer_len(), Vlan(ref nlas) => nlas.as_slice().buffer_len(), Veth(ref msg) => msg.buffer_len(), IpVlan(ref nlas) => nlas.as_slice().buffer_len(), Ipoib(ref nlas) => nlas.as_slice().buffer_len(), MacVlan(ref nlas) => nlas.as_slice().buffer_len(), MacVtap(ref nlas) => nlas.as_slice().buffer_len(), Vrf(ref nlas) => nlas.as_slice().buffer_len(), Vxlan(ref nlas) => nlas.as_slice().buffer_len(), Dummy(ref bytes) | Tun(ref bytes) | Nlmon(ref bytes) | Ifb(ref bytes) | GreTap(ref bytes) | GreTap6(ref bytes) | IpTun(ref bytes) | SitTun(ref bytes) | GreTun(ref bytes) | GreTun6(ref bytes) | Vti(ref bytes) | Gtp(ref bytes) | Wireguard(ref bytes) | Other(ref bytes) => bytes.len(), } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::InfoData::*; match self { Bond(ref nlas) => nlas.as_slice().emit(buffer), Bridge(ref nlas) => nlas.as_slice().emit(buffer), Vlan(ref nlas) => nlas.as_slice().emit(buffer), Veth(ref msg) => msg.emit(buffer), IpVlan(ref nlas) => nlas.as_slice().emit(buffer), Ipoib(ref nlas) => nlas.as_slice().emit(buffer), MacVlan(ref nlas) => nlas.as_slice().emit(buffer), MacVtap(ref nlas) => nlas.as_slice().emit(buffer), Vrf(ref nlas) => nlas.as_slice().emit(buffer), Vxlan(ref nlas) => nlas.as_slice().emit(buffer), Dummy(ref bytes) | Tun(ref bytes) | Nlmon(ref bytes) | Ifb(ref bytes) | GreTap(ref bytes) | GreTap6(ref bytes) | IpTun(ref bytes) | SitTun(ref bytes) | GreTun(ref bytes) | GreTun6(ref bytes) | Vti(ref bytes) | Gtp(ref bytes) | Wireguard(ref bytes) | Other(ref bytes) => buffer.copy_from_slice(bytes), } } fn kind(&self) -> u16 { IFLA_INFO_DATA } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum InfoKind { Dummy, Ifb, Bridge, Tun, Nlmon, Vlan, Veth, Vxlan, Bond, IpVlan, MacVlan, MacVtap, GreTap, GreTap6, IpTun, SitTun, GreTun, GreTun6, Vti, Vrf, Gtp, Ipoib, Wireguard, Other(String), } impl Nla for InfoKind { fn value_len(&self) -> usize { use self::InfoKind::*; let len = match *self { Dummy => DUMMY.len(), Ifb => IFB.len(), Bridge => BRIDGE.len(), Tun => TUN.len(), Nlmon => NLMON.len(), Vlan => VLAN.len(), Veth => VETH.len(), Vxlan => VXLAN.len(), Bond => BOND.len(), IpVlan => IPVLAN.len(), MacVlan => MACVLAN.len(), MacVtap => MACVTAP.len(), GreTap => GRETAP.len(), GreTap6 => IP6GRETAP.len(), IpTun => IPIP.len(), SitTun => SIT.len(), GreTun => GRE.len(), GreTun6 => IP6GRE.len(), Vti => VTI.len(), Vrf => VRF.len(), Gtp => GTP.len(), Ipoib => IPOIB.len(), Wireguard => WIREGUARD.len(), Other(ref s) => s.len(), }; len + 1 } fn emit_value(&self, buffer: &mut [u8]) { use self::InfoKind::*; let s = match *self { Dummy => DUMMY, Ifb => IFB, Bridge => BRIDGE, Tun => TUN, Nlmon => NLMON, Vlan => VLAN, Veth => VETH, Vxlan => VXLAN, Bond => BOND, IpVlan => IPVLAN, MacVlan => MACVLAN, MacVtap => MACVTAP, GreTap => GRETAP, GreTap6 => IP6GRETAP, IpTun => IPIP, SitTun => SIT, GreTun => GRE, GreTun6 => IP6GRE, Vti => VTI, Vrf => VRF, Gtp => GTP, Ipoib => IPOIB, Wireguard => WIREGUARD, Other(ref s) => s.as_str(), }; buffer[..s.len()].copy_from_slice(s.as_bytes()); buffer[s.len()] = 0; } fn kind(&self) -> u16 { IFLA_INFO_KIND } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoKind { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::InfoKind::*; if buf.kind() != IFLA_INFO_KIND { return Err( format!("failed to parse IFLA_INFO_KIND: NLA type is {}", buf.kind()).into(), ); } let s = parse_string(buf.value()).context("invalid IFLA_INFO_KIND value")?; Ok(match s.as_str() { DUMMY => Dummy, IFB => Ifb, BRIDGE => Bridge, TUN => Tun, NLMON => Nlmon, VLAN => Vlan, VETH => Veth, VXLAN => Vxlan, BOND => Bond, IPVLAN => IpVlan, MACVLAN => MacVlan, MACVTAP => MacVtap, GRETAP => GreTap, IP6GRETAP => GreTap6, IPIP => IpTun, SIT => SitTun, GRE => GreTun, IP6GRE => GreTun6, VTI => Vti, VRF => Vrf, GTP => Gtp, IPOIB => Ipoib, WIREGUARD => Wireguard, _ => Other(s), }) } } // https://elixir.bootlin.com/linux/v5.9.8/source/drivers/net/vxlan.c#L3332 #[derive(Debug, PartialEq, Eq, Clone)] pub enum InfoVxlan { Unspec(Vec), Id(u32), Group(Vec), Group6(Vec), Link(u32), Local(Vec), Local6(Vec), Tos(u8), Ttl(u8), Label(u32), Learning(u8), Ageing(u32), Limit(u32), PortRange((u16, u16)), Proxy(u8), Rsc(u8), L2Miss(u8), L3Miss(u8), CollectMetadata(u8), Port(u16), UDPCsum(u8), UDPZeroCsumTX(u8), UDPZeroCsumRX(u8), RemCsumTX(u8), RemCsumRX(u8), Gbp(u8), Gpe(u8), RemCsumNoPartial(u8), TtlInherit(u8), Df(u8), } impl Nla for InfoVxlan { #[rustfmt::skip] fn value_len(&self) -> usize { use self::InfoVxlan::*; match *self { Tos(_) | Ttl(_) | Learning(_) | Proxy(_) | Rsc(_) | L2Miss(_) | L3Miss(_) | CollectMetadata(_) | UDPCsum(_) | UDPZeroCsumTX(_) | UDPZeroCsumRX(_) | RemCsumTX(_) | RemCsumRX(_) | Gbp(_) | Gpe(_) | RemCsumNoPartial(_) | TtlInherit(_) | Df(_) => 1, Port(_) => 2, Id(_) | Label(_) | Link(_) | Ageing(_) | Limit(_) | PortRange(_) => 4, Local(ref bytes) | Local6(ref bytes) | Group(ref bytes) | Group6(ref bytes) | Unspec(ref bytes) => bytes.len(), } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::InfoVxlan::*; match self { Unspec(ref bytes) => buffer.copy_from_slice(bytes), Id(ref value) | Label(ref value) | Link(ref value) | Ageing(ref value) | Limit(ref value) => NativeEndian::write_u32(buffer, *value), Tos(ref value) | Ttl(ref value) | Learning (ref value) | Proxy(ref value) | Rsc(ref value) | L2Miss(ref value) | L3Miss(ref value) | CollectMetadata(ref value) | UDPCsum(ref value) | UDPZeroCsumTX(ref value) | UDPZeroCsumRX(ref value) | RemCsumTX(ref value) | RemCsumRX(ref value) | Gbp(ref value) | Gpe(ref value) | RemCsumNoPartial(ref value) | TtlInherit(ref value) | Df(ref value) => buffer[0] = *value, Local(ref value) | Group(ref value) | Group6(ref value) | Local6(ref value) => buffer.copy_from_slice(value.as_slice()), Port(ref value) => NativeEndian::write_u16(buffer, *value), PortRange(ref range) => { NativeEndian::write_u16(buffer, range.0); NativeEndian::write_u16(buffer, range.1) } } } fn kind(&self) -> u16 { use self::InfoVxlan::*; match self { Id(_) => IFLA_VXLAN_ID, Group(_) => IFLA_VXLAN_GROUP, Group6(_) => IFLA_VXLAN_GROUP6, Link(_) => IFLA_VXLAN_LINK, Local(_) => IFLA_VXLAN_LOCAL, Local6(_) => IFLA_VXLAN_LOCAL6, Tos(_) => IFLA_VXLAN_TOS, Ttl(_) => IFLA_VXLAN_TTL, Label(_) => IFLA_VXLAN_LABEL, Learning(_) => IFLA_VXLAN_LEARNING, Ageing(_) => IFLA_VXLAN_AGEING, Limit(_) => IFLA_VXLAN_LIMIT, PortRange(_) => IFLA_VXLAN_PORT_RANGE, Proxy(_) => IFLA_VXLAN_PROXY, Rsc(_) => IFLA_VXLAN_RSC, L2Miss(_) => IFLA_VXLAN_L2MISS, L3Miss(_) => IFLA_VXLAN_L3MISS, CollectMetadata(_) => IFLA_VXLAN_COLLECT_METADATA, Port(_) => IFLA_VXLAN_PORT, UDPCsum(_) => IFLA_VXLAN_UDP_CSUM, UDPZeroCsumTX(_) => IFLA_VXLAN_UDP_ZERO_CSUM6_TX, UDPZeroCsumRX(_) => IFLA_VXLAN_UDP_ZERO_CSUM6_RX, RemCsumTX(_) => IFLA_VXLAN_REMCSUM_TX, RemCsumRX(_) => IFLA_VXLAN_REMCSUM_RX, Gbp(_) => IFLA_VXLAN_GBP, Gpe(_) => IFLA_VXLAN_GPE, RemCsumNoPartial(_) => IFLA_VXLAN_REMCSUM_NOPARTIAL, TtlInherit(_) => IFLA_VXLAN_TTL_INHERIT, Df(_) => IFLA_VXLAN_DF, Unspec(_) => IFLA_VXLAN_UNSPEC, } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoVxlan { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::InfoVxlan::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_VLAN_UNSPEC => Unspec(payload.to_vec()), IFLA_VXLAN_ID => Id(parse_u32(payload).context("invalid IFLA_VXLAN_ID value")?), IFLA_VXLAN_GROUP => Group(payload.to_vec()), IFLA_VXLAN_GROUP6 => Group6(payload.to_vec()), IFLA_VXLAN_LINK => Link(parse_u32(payload).context("invalid IFLA_VXLAN_LINK value")?), IFLA_VXLAN_LOCAL => Local(payload.to_vec()), IFLA_VXLAN_LOCAL6 => Local6(payload.to_vec()), IFLA_VXLAN_TOS => Tos(parse_u8(payload).context("invalid IFLA_VXLAN_TOS value")?), IFLA_VXLAN_TTL => Ttl(parse_u8(payload).context("invalid IFLA_VXLAN_TTL value")?), IFLA_VXLAN_LABEL => { Label(parse_u32(payload).context("invalid IFLA_VXLAN_LABEL value")?) } IFLA_VXLAN_LEARNING => { Learning(parse_u8(payload).context("invalid IFLA_VXLAN_LEARNING value")?) } IFLA_VXLAN_AGEING => { Ageing(parse_u32(payload).context("invalid IFLA_VXLAN_AGEING value")?) } IFLA_VXLAN_LIMIT => { Limit(parse_u32(payload).context("invalid IFLA_VXLAN_LIMIT value")?) } IFLA_VXLAN_PROXY => Proxy(parse_u8(payload).context("invalid IFLA_VXLAN_PROXY value")?), IFLA_VXLAN_RSC => Rsc(parse_u8(payload).context("invalid IFLA_VXLAN_RSC value")?), IFLA_VXLAN_L2MISS => { L2Miss(parse_u8(payload).context("invalid IFLA_VXLAN_L2MISS value")?) } IFLA_VXLAN_L3MISS => { L3Miss(parse_u8(payload).context("invalid IFLA_VXLAN_L3MISS value")?) } IFLA_VXLAN_COLLECT_METADATA => CollectMetadata( parse_u8(payload).context("invalid IFLA_VXLAN_COLLECT_METADATA value")?, ), IFLA_VXLAN_PORT_RANGE => { let err = "invalid IFLA_VXLAN_PORT value"; if payload.len() != 4 { return Err(err.into()); } let low = parse_u16(&payload[0..2]).context(err)?; let high = parse_u16(&payload[2..]).context(err)?; PortRange((low, high)) } IFLA_VXLAN_PORT => { Port(parse_u16_be(payload).context("invalid IFLA_VXLAN_PORT value")?) } IFLA_VXLAN_UDP_CSUM => { UDPCsum(parse_u8(payload).context("invalid IFLA_VXLAN_UDP_CSUM value")?) } IFLA_VXLAN_UDP_ZERO_CSUM6_TX => UDPZeroCsumTX( parse_u8(payload).context("invalid IFLA_VXLAN_UDP_ZERO_CSUM6_TX value")?, ), IFLA_VXLAN_UDP_ZERO_CSUM6_RX => UDPZeroCsumRX( parse_u8(payload).context("invalid IFLA_VXLAN_UDP_ZERO_CSUM6_RX value")?, ), IFLA_VXLAN_REMCSUM_TX => { RemCsumTX(parse_u8(payload).context("invalid IFLA_VXLAN_REMCSUM_TX value")?) } IFLA_VXLAN_REMCSUM_RX => { RemCsumRX(parse_u8(payload).context("invalid IFLA_VXLAN_REMCSUM_RX value")?) } IFLA_VXLAN_DF => Df(parse_u8(payload).context("invalid IFLA_VXLAN_DF value")?), IFLA_VXLAN_GBP => Gbp(parse_u8(payload).context("invalid IFLA_VXLAN_GBP value")?), IFLA_VXLAN_GPE => Gpe(parse_u8(payload).context("invalid IFLA_VXLAN_GPE value")?), IFLA_VXLAN_REMCSUM_NOPARTIAL => RemCsumNoPartial( parse_u8(payload).context("invalid IFLA_VXLAN_REMCSUM_NO_PARTIAL")?, ), IFLA_VXLAN_TTL_INHERIT => { TtlInherit(parse_u8(payload).context("invalid IFLA_VXLAN_TTL_INHERIT value")?) } __IFLA_VXLAN_MAX => Unspec(payload.to_vec()), _ => return Err(format!("unknown NLA type {}", buf.kind()).into()), }) } } // https://elixir.bootlin.com/linux/latest/source/net/8021q/vlan_netlink.c#L21 #[derive(Debug, PartialEq, Eq, Clone)] pub enum InfoVlan { Unspec(Vec), Id(u16), Flags((u32, u32)), EgressQos(Vec), IngressQos(Vec), Protocol(u16), } impl Nla for InfoVlan { #[rustfmt::skip] fn value_len(&self) -> usize { use self::InfoVlan::*; match self { Id(_) | Protocol(_) => 2, Flags(_) => 8, Unspec(bytes) | EgressQos(bytes) | IngressQos(bytes) => bytes.len(), } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::InfoVlan::*; match self { Unspec(ref bytes) | EgressQos(ref bytes) | IngressQos(ref bytes) => buffer.copy_from_slice(bytes), Id(ref value) | Protocol(ref value) => NativeEndian::write_u16(buffer, *value), Flags(ref flags) => { NativeEndian::write_u32(buffer, flags.0); NativeEndian::write_u32(buffer, flags.1) } } } fn kind(&self) -> u16 { use self::InfoVlan::*; match self { Unspec(_) => IFLA_VLAN_UNSPEC, Id(_) => IFLA_VLAN_ID, Flags(_) => IFLA_VLAN_FLAGS, EgressQos(_) => IFLA_VLAN_EGRESS_QOS, IngressQos(_) => IFLA_VLAN_INGRESS_QOS, Protocol(_) => IFLA_VLAN_PROTOCOL, } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoVlan { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::InfoVlan::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_VLAN_UNSPEC => Unspec(payload.to_vec()), IFLA_VLAN_ID => Id(parse_u16(payload).context("invalid IFLA_VLAN_ID value")?), IFLA_VLAN_FLAGS => { let err = "invalid IFLA_VLAN_FLAGS value"; if payload.len() != 8 { return Err(err.into()); } let flags = parse_u32(&payload[0..4]).context(err)?; let mask = parse_u32(&payload[4..]).context(err)?; Flags((flags, mask)) } IFLA_VLAN_EGRESS_QOS => EgressQos(payload.to_vec()), IFLA_VLAN_INGRESS_QOS => IngressQos(payload.to_vec()), IFLA_VLAN_PROTOCOL => { Protocol(parse_u16_be(payload).context("invalid IFLA_VLAN_PROTOCOL value")?) } _ => return Err(format!("unknown NLA type {}", buf.kind()).into()), }) } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum InfoBridge { Unspec(Vec), GroupAddr([u8; 6]), // FIXME: what type is this? putting Vec for now but it might // be a boolean actually FdbFlush(Vec), Pad(Vec), HelloTimer(u64), TcnTimer(u64), TopologyChangeTimer(u64), GcTimer(u64), MulticastMembershipInterval(u64), MulticastQuerierInterval(u64), MulticastQueryInterval(u64), MulticastQueryResponseInterval(u64), MulticastLastMemberInterval(u64), MulticastStartupQueryInterval(u64), ForwardDelay(u32), HelloTime(u32), MaxAge(u32), AgeingTime(u32), StpState(u32), MulticastHashElasticity(u32), MulticastHashMax(u32), MulticastLastMemberCount(u32), MulticastStartupQueryCount(u32), RootPathCost(u32), Priority(u16), VlanProtocol(u16), GroupFwdMask(u16), RootId((u16, [u8; 6])), BridgeId((u16, [u8; 6])), RootPort(u16), VlanDefaultPvid(u16), VlanFiltering(u8), TopologyChange(u8), TopologyChangeDetected(u8), MulticastRouter(u8), MulticastSnooping(u8), MulticastQueryUseIfaddr(u8), MulticastQuerier(u8), NfCallIpTables(u8), NfCallIp6Tables(u8), NfCallArpTables(u8), VlanStatsEnabled(u8), MulticastStatsEnabled(u8), MulticastIgmpVersion(u8), MulticastMldVersion(u8), VlanStatsPerHost(u8), MultiBoolOpt(u64), Other(DefaultNla), } impl Nla for InfoBridge { #[rustfmt::skip] fn value_len(&self) -> usize { use self::InfoBridge::*; match self { Unspec(bytes) | FdbFlush(bytes) | Pad(bytes) => bytes.len(), HelloTimer(_) | TcnTimer(_) | TopologyChangeTimer(_) | GcTimer(_) | MulticastMembershipInterval(_) | MulticastQuerierInterval(_) | MulticastQueryInterval(_) | MulticastQueryResponseInterval(_) | MulticastLastMemberInterval(_) | MulticastStartupQueryInterval(_) => 8, ForwardDelay(_) | HelloTime(_) | MaxAge(_) | AgeingTime(_) | StpState(_) | MulticastHashElasticity(_) | MulticastHashMax(_) | MulticastLastMemberCount(_) | MulticastStartupQueryCount(_) | RootPathCost(_) => 4, Priority(_) | VlanProtocol(_) | GroupFwdMask(_) | RootPort(_) | VlanDefaultPvid(_) => 2, RootId(_) | BridgeId(_) | MultiBoolOpt(_) => 8, GroupAddr(_) => 6, VlanFiltering(_) | TopologyChange(_) | TopologyChangeDetected(_) | MulticastRouter(_) | MulticastSnooping(_) | MulticastQueryUseIfaddr(_) | MulticastQuerier(_) | NfCallIpTables(_) | NfCallIp6Tables(_) | NfCallArpTables(_) | VlanStatsEnabled(_) | MulticastStatsEnabled(_) | MulticastIgmpVersion(_) | MulticastMldVersion(_) | VlanStatsPerHost(_) => 1, Other(nla) => nla.value_len(), } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::InfoBridge::*; match self { Unspec(ref bytes) | FdbFlush(ref bytes) | Pad(ref bytes) => buffer.copy_from_slice(bytes), HelloTimer(ref value) | TcnTimer(ref value) | TopologyChangeTimer(ref value) | GcTimer(ref value) | MulticastMembershipInterval(ref value) | MulticastQuerierInterval(ref value) | MulticastQueryInterval(ref value) | MulticastQueryResponseInterval(ref value) | MulticastLastMemberInterval(ref value) | MulticastStartupQueryInterval(ref value) | MultiBoolOpt(ref value) => NativeEndian::write_u64(buffer, *value), ForwardDelay(ref value) | HelloTime(ref value) | MaxAge(ref value) | AgeingTime(ref value) | StpState(ref value) | MulticastHashElasticity(ref value) | MulticastHashMax(ref value) | MulticastLastMemberCount(ref value) | MulticastStartupQueryCount(ref value) | RootPathCost(ref value) => NativeEndian::write_u32(buffer, *value), Priority(ref value) | GroupFwdMask(ref value) | RootPort(ref value) | VlanDefaultPvid(ref value) => NativeEndian::write_u16(buffer, *value), VlanProtocol(ref value) => BigEndian::write_u16(buffer, *value), RootId((ref priority, ref address)) | BridgeId((ref priority, ref address)) => { NativeEndian::write_u16(buffer, *priority); buffer[2..].copy_from_slice(&address[..]); } GroupAddr(ref value) => buffer.copy_from_slice(&value[..]), VlanFiltering(ref value) | TopologyChange(ref value) | TopologyChangeDetected(ref value) | MulticastRouter(ref value) | MulticastSnooping(ref value) | MulticastQueryUseIfaddr(ref value) | MulticastQuerier(ref value) | NfCallIpTables(ref value) | NfCallIp6Tables(ref value) | NfCallArpTables(ref value) | VlanStatsEnabled(ref value) | MulticastStatsEnabled(ref value) | MulticastIgmpVersion(ref value) | MulticastMldVersion(ref value) | VlanStatsPerHost(ref value) => buffer[0] = *value, Other(nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::InfoBridge::*; match self { Unspec(_) => IFLA_BR_UNSPEC, GroupAddr(_) => IFLA_BR_GROUP_ADDR, FdbFlush(_) => IFLA_BR_FDB_FLUSH, Pad(_) => IFLA_BR_PAD, HelloTimer(_) => IFLA_BR_HELLO_TIMER, TcnTimer(_) => IFLA_BR_TCN_TIMER, TopologyChangeTimer(_) => IFLA_BR_TOPOLOGY_CHANGE_TIMER, GcTimer(_) => IFLA_BR_GC_TIMER, MulticastMembershipInterval(_) => IFLA_BR_MCAST_MEMBERSHIP_INTVL, MulticastQuerierInterval(_) => IFLA_BR_MCAST_QUERIER_INTVL, MulticastQueryInterval(_) => IFLA_BR_MCAST_QUERY_INTVL, MulticastQueryResponseInterval(_) => IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, ForwardDelay(_) => IFLA_BR_FORWARD_DELAY, HelloTime(_) => IFLA_BR_HELLO_TIME, MaxAge(_) => IFLA_BR_MAX_AGE, AgeingTime(_) => IFLA_BR_AGEING_TIME, StpState(_) => IFLA_BR_STP_STATE, MulticastHashElasticity(_) => IFLA_BR_MCAST_HASH_ELASTICITY, MulticastHashMax(_) => IFLA_BR_MCAST_HASH_MAX, MulticastLastMemberCount(_) => IFLA_BR_MCAST_LAST_MEMBER_CNT, MulticastStartupQueryCount(_) => IFLA_BR_MCAST_STARTUP_QUERY_CNT, MulticastLastMemberInterval(_) => IFLA_BR_MCAST_LAST_MEMBER_INTVL, MulticastStartupQueryInterval(_) => IFLA_BR_MCAST_STARTUP_QUERY_INTVL, RootPathCost(_) => IFLA_BR_ROOT_PATH_COST, Priority(_) => IFLA_BR_PRIORITY, VlanProtocol(_) => IFLA_BR_VLAN_PROTOCOL, GroupFwdMask(_) => IFLA_BR_GROUP_FWD_MASK, RootId(_) => IFLA_BR_ROOT_ID, BridgeId(_) => IFLA_BR_BRIDGE_ID, RootPort(_) => IFLA_BR_ROOT_PORT, VlanDefaultPvid(_) => IFLA_BR_VLAN_DEFAULT_PVID, VlanFiltering(_) => IFLA_BR_VLAN_FILTERING, TopologyChange(_) => IFLA_BR_TOPOLOGY_CHANGE, TopologyChangeDetected(_) => IFLA_BR_TOPOLOGY_CHANGE_DETECTED, MulticastRouter(_) => IFLA_BR_MCAST_ROUTER, MulticastSnooping(_) => IFLA_BR_MCAST_SNOOPING, MulticastQueryUseIfaddr(_) => IFLA_BR_MCAST_QUERY_USE_IFADDR, MulticastQuerier(_) => IFLA_BR_MCAST_QUERIER, NfCallIpTables(_) => IFLA_BR_NF_CALL_IPTABLES, NfCallIp6Tables(_) => IFLA_BR_NF_CALL_IP6TABLES, NfCallArpTables(_) => IFLA_BR_NF_CALL_ARPTABLES, VlanStatsEnabled(_) => IFLA_BR_VLAN_STATS_ENABLED, MulticastStatsEnabled(_) => IFLA_BR_MCAST_STATS_ENABLED, MulticastIgmpVersion(_) => IFLA_BR_MCAST_IGMP_VERSION, MulticastMldVersion(_) => IFLA_BR_MCAST_MLD_VERSION, VlanStatsPerHost(_) => IFLA_BR_VLAN_STATS_PER_PORT, MultiBoolOpt(_) => IFLA_BR_MULTI_BOOLOPT, Other(nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoBridge { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::InfoBridge::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_BR_UNSPEC => Unspec(payload.to_vec()), IFLA_BR_FDB_FLUSH => FdbFlush(payload.to_vec()), IFLA_BR_PAD => Pad(payload.to_vec()), IFLA_BR_HELLO_TIMER => { HelloTimer(parse_u64(payload).context("invalid IFLA_BR_HELLO_TIMER value")?) } IFLA_BR_TCN_TIMER => { TcnTimer(parse_u64(payload).context("invalid IFLA_BR_TCN_TIMER value")?) } IFLA_BR_TOPOLOGY_CHANGE_TIMER => TopologyChangeTimer( parse_u64(payload).context("invalid IFLA_BR_TOPOLOGY_CHANGE_TIMER value")?, ), IFLA_BR_GC_TIMER => { GcTimer(parse_u64(payload).context("invalid IFLA_BR_GC_TIMER value")?) } IFLA_BR_MCAST_LAST_MEMBER_INTVL => MulticastLastMemberInterval( parse_u64(payload).context("invalid IFLA_BR_MCAST_LAST_MEMBER_INTVL value")?, ), IFLA_BR_MCAST_MEMBERSHIP_INTVL => MulticastMembershipInterval( parse_u64(payload).context("invalid IFLA_BR_MCAST_MEMBERSHIP_INTVL value")?, ), IFLA_BR_MCAST_QUERIER_INTVL => MulticastQuerierInterval( parse_u64(payload).context("invalid IFLA_BR_MCAST_QUERIER_INTVL value")?, ), IFLA_BR_MCAST_QUERY_INTVL => MulticastQueryInterval( parse_u64(payload).context("invalid IFLA_BR_MCAST_QUERY_INTVL value")?, ), IFLA_BR_MCAST_QUERY_RESPONSE_INTVL => MulticastQueryResponseInterval( parse_u64(payload).context("invalid IFLA_BR_MCAST_QUERY_RESPONSE_INTVL value")?, ), IFLA_BR_MCAST_STARTUP_QUERY_INTVL => MulticastStartupQueryInterval( parse_u64(payload).context("invalid IFLA_BR_MCAST_STARTUP_QUERY_INTVL value")?, ), IFLA_BR_FORWARD_DELAY => { ForwardDelay(parse_u32(payload).context("invalid IFLA_BR_FORWARD_DELAY value")?) } IFLA_BR_HELLO_TIME => { HelloTime(parse_u32(payload).context("invalid IFLA_BR_HELLO_TIME value")?) } IFLA_BR_MAX_AGE => MaxAge(parse_u32(payload).context("invalid IFLA_BR_MAX_AGE value")?), IFLA_BR_AGEING_TIME => { AgeingTime(parse_u32(payload).context("invalid IFLA_BR_AGEING_TIME value")?) } IFLA_BR_STP_STATE => { StpState(parse_u32(payload).context("invalid IFLA_BR_STP_STATE value")?) } IFLA_BR_MCAST_HASH_ELASTICITY => MulticastHashElasticity( parse_u32(payload).context("invalid IFLA_BR_MCAST_HASH_ELASTICITY value")?, ), IFLA_BR_MCAST_HASH_MAX => MulticastHashMax( parse_u32(payload).context("invalid IFLA_BR_MCAST_HASH_MAX value")?, ), IFLA_BR_MCAST_LAST_MEMBER_CNT => MulticastLastMemberCount( parse_u32(payload).context("invalid IFLA_BR_MCAST_LAST_MEMBER_CNT value")?, ), IFLA_BR_MCAST_STARTUP_QUERY_CNT => MulticastStartupQueryCount( parse_u32(payload).context("invalid IFLA_BR_MCAST_STARTUP_QUERY_CNT value")?, ), IFLA_BR_ROOT_PATH_COST => { RootPathCost(parse_u32(payload).context("invalid IFLA_BR_ROOT_PATH_COST value")?) } IFLA_BR_PRIORITY => { Priority(parse_u16(payload).context("invalid IFLA_BR_PRIORITY value")?) } IFLA_BR_VLAN_PROTOCOL => { VlanProtocol(parse_u16_be(payload).context("invalid IFLA_BR_VLAN_PROTOCOL value")?) } IFLA_BR_GROUP_FWD_MASK => { GroupFwdMask(parse_u16(payload).context("invalid IFLA_BR_GROUP_FWD_MASK value")?) } IFLA_BR_ROOT_ID | IFLA_BR_BRIDGE_ID => { if payload.len() != 8 { return Err("invalid IFLA_BR_ROOT_ID or IFLA_BR_BRIDGE_ID value".into()); } let priority = NativeEndian::read_u16(&payload[..2]); let address = parse_mac(&payload[2..]) .context("invalid IFLA_BR_ROOT_ID or IFLA_BR_BRIDGE_ID value")?; match buf.kind() { IFLA_BR_ROOT_ID => RootId((priority, address)), IFLA_BR_BRIDGE_ID => BridgeId((priority, address)), _ => unreachable!(), } } IFLA_BR_GROUP_ADDR => { GroupAddr(parse_mac(payload).context("invalid IFLA_BR_GROUP_ADDR value")?) } IFLA_BR_ROOT_PORT => { RootPort(parse_u16(payload).context("invalid IFLA_BR_ROOT_PORT value")?) } IFLA_BR_VLAN_DEFAULT_PVID => VlanDefaultPvid( parse_u16(payload).context("invalid IFLA_BR_VLAN_DEFAULT_PVID value")?, ), IFLA_BR_VLAN_FILTERING => { VlanFiltering(parse_u8(payload).context("invalid IFLA_BR_VLAN_FILTERING value")?) } IFLA_BR_TOPOLOGY_CHANGE => { TopologyChange(parse_u8(payload).context("invalid IFLA_BR_TOPOLOGY_CHANGE value")?) } IFLA_BR_TOPOLOGY_CHANGE_DETECTED => TopologyChangeDetected( parse_u8(payload).context("invalid IFLA_BR_TOPOLOGY_CHANGE_DETECTED value")?, ), IFLA_BR_MCAST_ROUTER => { MulticastRouter(parse_u8(payload).context("invalid IFLA_BR_MCAST_ROUTER value")?) } IFLA_BR_MCAST_SNOOPING => MulticastSnooping( parse_u8(payload).context("invalid IFLA_BR_MCAST_SNOOPING value")?, ), IFLA_BR_MCAST_QUERY_USE_IFADDR => MulticastQueryUseIfaddr( parse_u8(payload).context("invalid IFLA_BR_MCAST_QUERY_USE_IFADDR value")?, ), IFLA_BR_MCAST_QUERIER => { MulticastQuerier(parse_u8(payload).context("invalid IFLA_BR_MCAST_QUERIER value")?) } IFLA_BR_NF_CALL_IPTABLES => { NfCallIpTables(parse_u8(payload).context("invalid IFLA_BR_NF_CALL_IPTABLES value")?) } IFLA_BR_NF_CALL_IP6TABLES => NfCallIp6Tables( parse_u8(payload).context("invalid IFLA_BR_NF_CALL_IP6TABLES value")?, ), IFLA_BR_NF_CALL_ARPTABLES => NfCallArpTables( parse_u8(payload).context("invalid IFLA_BR_NF_CALL_ARPTABLES value")?, ), IFLA_BR_VLAN_STATS_ENABLED => VlanStatsEnabled( parse_u8(payload).context("invalid IFLA_BR_VLAN_STATS_ENABLED value")?, ), IFLA_BR_MCAST_STATS_ENABLED => MulticastStatsEnabled( parse_u8(payload).context("invalid IFLA_BR_MCAST_STATS_ENABLED value")?, ), IFLA_BR_MCAST_IGMP_VERSION => MulticastIgmpVersion( parse_u8(payload).context("invalid IFLA_BR_MCAST_IGMP_VERSION value")?, ), IFLA_BR_MCAST_MLD_VERSION => MulticastMldVersion( parse_u8(payload).context("invalid IFLA_BR_MCAST_MLD_VERSION value")?, ), IFLA_BR_VLAN_STATS_PER_PORT => VlanStatsPerHost( parse_u8(payload).context("invalid IFLA_BR_VLAN_STATS_PER_PORT value")?, ), IFLA_BR_MULTI_BOOLOPT => { MultiBoolOpt(parse_u64(payload).context("invalid IFLA_BR_MULTI_BOOLOPT value")?) } _ => Other( DefaultNla::parse(buf) .context("invalid link info bridge NLA value (unknown type)")?, ), }) } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum InfoIpoib { Unspec(Vec), Pkey(u16), Mode(u16), UmCast(u16), Other(DefaultNla), } impl Nla for InfoIpoib { fn value_len(&self) -> usize { use self::InfoIpoib::*; match self { Unspec(bytes) => bytes.len(), Pkey(_) | Mode(_) | UmCast(_) => 2, Other(nla) => nla.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::InfoIpoib::*; match self { Unspec(bytes) => buffer.copy_from_slice(bytes.as_slice()), Pkey(value) => NativeEndian::write_u16(buffer, *value), Mode(value) => NativeEndian::write_u16(buffer, *value), UmCast(value) => NativeEndian::write_u16(buffer, *value), Other(nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::InfoIpoib::*; match self { Unspec(_) => IFLA_IPOIB_UNSPEC, Pkey(_) => IFLA_IPOIB_PKEY, Mode(_) => IFLA_IPOIB_MODE, UmCast(_) => IFLA_IPOIB_UMCAST, Other(nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoIpoib { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::InfoIpoib::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_IPOIB_UNSPEC => Unspec(payload.to_vec()), IFLA_IPOIB_PKEY => Pkey(parse_u16(payload).context("invalid IFLA_IPOIB_PKEY value")?), IFLA_IPOIB_MODE => Mode(parse_u16(payload).context("invalid IFLA_IPOIB_MODE value")?), IFLA_IPOIB_UMCAST => { UmCast(parse_u16(payload).context("invalid IFLA_IPOIB_UMCAST value")?) } kind => Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?), }) } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum VethInfo { Unspec(Vec), Peer(LinkMessage), Other(DefaultNla), } impl Nla for VethInfo { fn value_len(&self) -> usize { use self::VethInfo::*; match *self { Unspec(ref bytes) => bytes.len(), Peer(ref message) => message.buffer_len(), Other(ref attr) => attr.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::VethInfo::*; match *self { Unspec(ref bytes) => buffer.copy_from_slice(bytes.as_slice()), Peer(ref message) => message.emit(buffer), Other(ref attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::VethInfo::*; match *self { Unspec(_) => VETH_INFO_UNSPEC, Peer(_) => VETH_INFO_PEER, Other(ref attr) => attr.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for VethInfo { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::VethInfo::*; let payload = buf.value(); Ok(match buf.kind() { VETH_INFO_UNSPEC => Unspec(payload.to_vec()), VETH_INFO_PEER => { let err = "failed to parse veth link info"; let buffer = LinkMessageBuffer::new_checked(&payload).context(err)?; Peer(LinkMessage::parse(&buffer).context(err)?) } kind => Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?), }) } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum InfoIpVlan { Unspec(Vec), Mode(u16), Flags(u16), Other(DefaultNla), } impl Nla for InfoIpVlan { fn value_len(&self) -> usize { use self::InfoIpVlan::*; match self { Unspec(bytes) => bytes.len(), Mode(_) | Flags(_) => 2, Other(nla) => nla.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::InfoIpVlan::*; match self { Unspec(bytes) => buffer.copy_from_slice(bytes.as_slice()), Mode(value) => NativeEndian::write_u16(buffer, *value), Flags(value) => NativeEndian::write_u16(buffer, *value), Other(nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::InfoIpVlan::*; match self { Unspec(_) => IFLA_IPVLAN_UNSPEC, Mode(_) => IFLA_IPVLAN_MODE, Flags(_) => IFLA_IPVLAN_FLAGS, Other(nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoIpVlan { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::InfoIpVlan::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_IPVLAN_UNSPEC => Unspec(payload.to_vec()), IFLA_IPVLAN_MODE => Mode(parse_u16(payload).context("invalid IFLA_IPVLAN_MODE value")?), IFLA_IPVLAN_FLAGS => { Flags(parse_u16(payload).context("invalid IFLA_IPVLAN_FLAGS value")?) } kind => Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?), }) } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum InfoVrf { TableId(u32), Other(DefaultNla), } impl Nla for InfoVrf { fn value_len(&self) -> usize { use self::InfoVrf::*; match self { TableId(_) => 4, Other(nla) => nla.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::InfoVrf::*; match self { TableId(value) => NativeEndian::write_u32(buffer, *value), Other(nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::InfoVrf::*; match self { TableId(_) => IFLA_VRF_TABLE, Other(nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoVrf { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::InfoVrf::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_VRF_TABLE => TableId(parse_u32(payload).context("invalid IFLA_VRF_TABLE value")?), kind => Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?), }) } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum InfoMacVlan { Unspec(Vec), Mode(u32), Flags(u16), MacAddrMode(u32), MacAddr([u8; 6]), MacAddrData(Vec), MacAddrCount(u32), Other(DefaultNla), } impl Nla for InfoMacVlan { fn value_len(&self) -> usize { use self::InfoMacVlan::*; match self { Unspec(bytes) => bytes.len(), Mode(_) => 4, Flags(_) => 2, MacAddrMode(_) => 4, MacAddr(_) => 6, MacAddrData(ref nlas) => nlas.as_slice().buffer_len(), MacAddrCount(_) => 4, Other(nla) => nla.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::InfoMacVlan::*; match self { Unspec(bytes) => buffer.copy_from_slice(bytes.as_slice()), Mode(value) => NativeEndian::write_u32(buffer, *value), Flags(value) => NativeEndian::write_u16(buffer, *value), MacAddrMode(value) => NativeEndian::write_u32(buffer, *value), MacAddr(bytes) => buffer.copy_from_slice(bytes), MacAddrData(ref nlas) => nlas.as_slice().emit(buffer), MacAddrCount(value) => NativeEndian::write_u32(buffer, *value), Other(nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::InfoMacVlan::*; match self { Unspec(_) => IFLA_MACVLAN_UNSPEC, Mode(_) => IFLA_MACVLAN_MODE, Flags(_) => IFLA_MACVLAN_FLAGS, MacAddrMode(_) => IFLA_MACVLAN_MACADDR_MODE, MacAddr(_) => IFLA_MACVLAN_MACADDR, MacAddrData(_) => IFLA_MACVLAN_MACADDR_DATA, MacAddrCount(_) => IFLA_MACVLAN_MACADDR_COUNT, Other(nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoMacVlan { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::InfoMacVlan::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_MACVLAN_UNSPEC => Unspec(payload.to_vec()), IFLA_MACVLAN_MODE => { Mode(parse_u32(payload).context("invalid IFLA_MACVLAN_MODE value")?) } IFLA_MACVLAN_FLAGS => { Flags(parse_u16(payload).context("invalid IFLA_MACVLAN_FLAGS value")?) } IFLA_MACVLAN_MACADDR_MODE => { MacAddrMode(parse_u32(payload).context("invalid IFLA_MACVLAN_MACADDR_MODE value")?) } IFLA_MACVLAN_MACADDR => { MacAddr(parse_mac(payload).context("invalid IFLA_MACVLAN_MACADDR value")?) } IFLA_MACVLAN_MACADDR_DATA => { let mut mac_data = Vec::new(); let err = "failed to parse IFLA_MACVLAN_MACADDR_DATA"; for nla in NlasIterator::new(payload) { let nla = &nla.context(err)?; let parsed = InfoMacVlan::parse(nla).context(err)?; mac_data.push(parsed); } MacAddrData(mac_data) } IFLA_MACVLAN_MACADDR_COUNT => MacAddrCount( parse_u32(payload).context("invalid IFLA_MACVLAN_MACADDR_COUNT value")?, ), kind => Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?), }) } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum InfoMacVtap { Unspec(Vec), Mode(u32), Flags(u16), MacAddrMode(u32), MacAddr([u8; 6]), MacAddrData(Vec), MacAddrCount(u32), Other(DefaultNla), } impl Nla for InfoMacVtap { fn value_len(&self) -> usize { use self::InfoMacVtap::*; match self { Unspec(bytes) => bytes.len(), Mode(_) => 4, Flags(_) => 2, MacAddrMode(_) => 4, MacAddr(_) => 6, MacAddrData(ref nlas) => nlas.as_slice().buffer_len(), MacAddrCount(_) => 4, Other(nla) => nla.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::InfoMacVtap::*; match self { Unspec(bytes) => buffer.copy_from_slice(bytes.as_slice()), Mode(value) => NativeEndian::write_u32(buffer, *value), Flags(value) => NativeEndian::write_u16(buffer, *value), MacAddrMode(value) => NativeEndian::write_u32(buffer, *value), MacAddr(bytes) => buffer.copy_from_slice(bytes), MacAddrData(ref nlas) => nlas.as_slice().emit(buffer), MacAddrCount(value) => NativeEndian::write_u32(buffer, *value), Other(nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::InfoMacVtap::*; match self { Unspec(_) => IFLA_MACVLAN_UNSPEC, Mode(_) => IFLA_MACVLAN_MODE, Flags(_) => IFLA_MACVLAN_FLAGS, MacAddrMode(_) => IFLA_MACVLAN_MACADDR_MODE, MacAddr(_) => IFLA_MACVLAN_MACADDR, MacAddrData(_) => IFLA_MACVLAN_MACADDR_DATA, MacAddrCount(_) => IFLA_MACVLAN_MACADDR_COUNT, Other(nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoMacVtap { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::InfoMacVtap::*; let payload = buf.value(); Ok(match buf.kind() { IFLA_MACVLAN_UNSPEC => Unspec(payload.to_vec()), IFLA_MACVLAN_MODE => { Mode(parse_u32(payload).context("invalid IFLA_MACVLAN_MODE value")?) } IFLA_MACVLAN_FLAGS => { Flags(parse_u16(payload).context("invalid IFLA_MACVLAN_FLAGS value")?) } IFLA_MACVLAN_MACADDR_MODE => { MacAddrMode(parse_u32(payload).context("invalid IFLA_MACVLAN_MACADDR_MODE value")?) } IFLA_MACVLAN_MACADDR => { MacAddr(parse_mac(payload).context("invalid IFLA_MACVLAN_MACADDR value")?) } IFLA_MACVLAN_MACADDR_DATA => { let mut mac_data = Vec::new(); let err = "failed to parse IFLA_MACVLAN_MACADDR_DATA"; for nla in NlasIterator::new(payload) { let nla = &nla.context(err)?; let parsed = InfoMacVtap::parse(nla).context(err)?; mac_data.push(parsed); } MacAddrData(mac_data) } IFLA_MACVLAN_MACADDR_COUNT => MacAddrCount( parse_u32(payload).context("invalid IFLA_MACVLAN_MACADDR_COUNT value")?, ), kind => Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?), }) } } #[cfg(test)] mod tests { use super::*; use crate::{ nlas::link::{bond::*, Nla}, traits::Emitable, LinkHeader, LinkMessage, }; use std::net::{Ipv4Addr, Ipv6Addr}; #[rustfmt::skip] static BRIDGE: [u8; 424] = [ 0x0b, 0x00, // L = 11 0x01, 0x00, // T = 1 (IFLA_INFO_KIND) 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x00, // V = "bridge" 0x00, // padding 0x9c, 0x01, // L = 412 0x02, 0x00, // T = 2 (IFLA_INFO_DATA) 0x0c, 0x00, // L = 12 0x10, 0x00, // T = 16 (IFLA_BR_HELLO_TIMER) 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // V = 35 0x0c, 0x00, // L = 12 0x11, 0x00, // T = 17 (IFLA_BR_TCN_TIMER) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // V = 0 0x0c, 0x00, // L = 12 0x12, 0x00, // T = 18 (IFLA_BR_TOPOLOGY_CHANGE_TIMER) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // V = 0 0x0c, 0x00, // L = 12 0x13, 0x00, // T = 19 (IFLA_BR_GC_TIMER) 0xb5, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // V = 14261 (0x37b5) 0x08, 0x00, // L = 8 0x01, 0x00, // T = 1 (IFLA_BR_FORWARD_DELAY) 0xc7, 0x00, 0x00, 0x00, // V = 199 0x08, 0x00, // L = 8 0x02, 0x00, // T = 2 (IFLA_BR_HELLO_TIME) 0xc7, 0x00, 0x00, 0x00, // V = 199 0x08, 0x00, // L = 8 0x03, 0x00, // T = 3 (IFLA_BR_MAX_AGE) 0xcf, 0x07, 0x00, 0x00, // V = 1999 (0x07cf) 0x08, 0x00, // L = 8 0x04, 0x00, // T = 4 (IFLA_BR_AGEING_TIME) 0x2f, 0x75, 0x00, 0x00, // V = 29999 (0x752f) 0x08, 0x00, // L = 8 0x05, 0x00, // T = 5 (IFLA_BR_STP_STATE) 0x01, 0x00, 0x00, 0x00, // V = 1 0x06, 0x00, // L = 6 0x06, 0x00, // T = 6 (IFLA_BR_PRIORITY) 0x00, 0x80, // V = 32768 (0x8000) 0x00, 0x00, // Padding 0x05, 0x00, // L = 5 0x07, 0x00, // T = 7 (IFLA_BR_VLAN_FILTERING) 0x00, // V = 0 0x00, 0x00, 0x00, // Padding 0x06, 0x00, // L = 6 0x09, 0x00, // T = 9 (IFLA_BR_GROUP_FWD_MASK) 0x00, 0x00, // V = 0 0x00, 0x00, // Padding 0x0c, 0x00, // L = 12 0x0b, 0x00, // T = 11 (IFLA_BR_BRIDGE_ID) 0x80, 0x00, // V (priority) = 128 (0x80) 0x52, 0x54, 0x00, 0xd7, 0x19, 0x3e, // V (address) = 52:54:00:d7:19:3e 0x0c, 0x00, // L = 12 0x0a, 0x00, // T = 10 (IFLA_BR_ROOT_ID) 0x80, 0x00, // V (priority) = 128 (0x80) 0x52, 0x54, 0x00, 0xd7, 0x19, 0x3e, // V (address) = 52:54:00:d7:19:3e 0x06, 0x00, // L = 6 0x0c, 0x00, // T = 12 (IFLA_BR_ROOT_PORT) 0x00, 0x00, // V = 0 0x00, 0x00, // Padding 0x08, 0x00, // L = 8 0x0d, 0x00, // T = 13 (IFLA_BR_ROOT_PATH_COST) 0x00, 0x00, 0x00, 0x00, // V = 0 0x05, 0x00, // L = 5 0x0e, 0x00, // T = 14 (IFLA_BR_TOPOLOGY_CHANGE) 0x00, // V = 0 0x00, 0x00, 0x00, // Padding 0x05, 0x00, // L = 5 0x0f, 0x00, // T = 15 (IFLA_BR_TOPOLOGY_CHANGE_DETECTED) 0x00, // V = 0 0x00, 0x00, 0x00, // Padding 0x0a, 0x00, // L = 10 0x14, 0x00, // T = 20 (IFLA_BR_GROUP_ADDR) 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00, // V = 01:80:c2:00:00:00 0x00, 0x00, // Padding 0x06, 0x00, // L = 6 0x08, 0x00, // T = 8 (IFLA_BR_VLAN_PROTOCOL) 0x81, 0x00, // V = 33024 (big-endian) 0x00, 0x00, // Padding 0x06, 0x00, // L = 6 0x27, 0x00, // T = 39 (IFLA_BR_VLAN_DEFAULT_PVID) 0x01, 0x00, // V = 1 0x00, 0x00, // Padding 0x05, 0x00, // L = 5 0x29, 0x00, // T = 41 (IFLA_BR_VLAN_STATS_ENABLED) 0x00, // V = 0 0x00, 0x00, 0x00, // Padding 0x05, 0x00, // L = 5 0x16, 0x00, // T = 22 (IFLA_BR_MCAST_ROUTER) 0x01, // V = 1 0x00, 0x00, 0x00, // Padding 0x05, 0x00, // L = 5 0x17, 0x00, // T = 23 (IFLA_BR_MCAST_SNOOPING) 0x01, // V = 1 0x00, 0x00, 0x00, // Padding 0x05, 0x00, // L = 5 0x18, 0x00, // T = 24 (IFLA_BR_MCAST_QUERY_USE_IFADDR) 0x00, // V = 0 0x00, 0x00, 0x00, // Padding 0x05, 0x00, // L = 5 0x19, 0x00, // T = 25 (IFLA_BR_MCAST_QUERIER) 0x00, // V = 0 0x00, 0x00, 0x00, // Padding 0x05, 0x00, // L = 5 0x2a, 0x00, // T = 42 (IFLA_BR_MCAST_STATS_ENABLED) 0x00, // V = 0 0x00, 0x00, 0x00, // Padding 0x08, 0x00, // L = 8 0x1a, 0x00, // T = 26 (IFLA_BR_MCAST_HASH_ELASTICITY) 0x04, 0x00, 0x00, 0x00, // V = 4 0x08, 0x00, // L = 8 0x1b, 0x00, // T = 27 (IFLA_BR_MCAST_HASH_MAX) 0x00, 0x02, 0x00, 0x00, // V = 512 (0x0200) 0x08, 0x00, // L = 8 0x1c, 0x00, // T = 28 (IFLA_BR_MCAST_LAST_MEMBER_CNT) 0x02, 0x00, 0x00, 0x00, // V = 2 0x08, 0x00, // L = 8 0x1d, 0x00, // T = 29 (IFLA_BR_MCAST_STARTUP_QUERY_CNT) 0x02, 0x00, 0x00, 0x00, // V = 2 0x05, 0x00, // L = 5 0x2b, 0x00, // T = 43 (IFLA_BR_MCAST_IGMP_VERSION) 0x02, // V = 2 0x00, 0x00, 0x00, // Padding 0x05, 0x00, // L = 5 0x2c, 0x00, // T = 44 (IFLA_BR_MCAST_MLD_VERSION) 0x01, // V = 1 0x00, 0x00, 0x00, // Padding 0x0c, 0x00, // L = 12 0x1e, 0x00, // T = 30 (IFLA_BR_MCAST_LAST_MEMBER_INTVL) 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // V = 99 0x0c, 0x00, // L = 12 0x1f, 0x00, // T = 31 (IFLA_BR_MCAST_MEMBERSHIP_INTVL) 0x8f, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // V = 25999 (0x658f) 0x0c, 0x00, // L = 12 0x20, 0x00, // T = 32 (IFLA_BR_MCAST_QUERIER_INTVL) 0x9b, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // V = 25499 (0x639b) 0x0c, 0x00, // L = 12 0x21, 0x00, // T = 33 (IFLA_BR_MCAST_QUERY_INTVL) 0xd3, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // V = 12499 (0x30d3) 0x0c, 0x00, // L = 12 0x22, 0x00, // T = 34 (IFLA_BR_MCAST_QUERY_RESPONSE_INTVL) 0xe7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // V = 999 (0x03e7) 0x0c, 0x00, // L = 12 0x23, 0x00, // T = 35 (IFLA_BR_MCAST_STARTUP_QUERY_INTVL) 0x34, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // V = 3124 (0x0c34) 0x05, 0x00, // L = 5 0x24, 0x00, // T = 36 (IFLA_BR_NF_CALL_IPTABLES) 0x00, // V = 0 0x00, 0x00, 0x00, // Padding 0x05, 0x00, // L = 5 0x25, 0x00, // T = 37 (IFLA_BR_NF_CALL_IP6TABLES) 0x00, // V = 0 0x00, 0x00, 0x00, // Padding 0x05, 0x00, // L = 5 0x26, 0x00, // T = 38 (IFLA_BR_NF_CALL_ARPTABLES) 0x00, // V = 0 0x00, 0x00, 0x00, // Padding 0x05, 0x00, // L = 5 0x2d, 0x00, // T = 45 (IFLA_BR_VLAN_STATS_PER_PORT) 0x01, // V = 1 0x00, 0x00, 0x00, // Padding 0x0c, 0x00, // L = 12 0x2e, 0x00, // T = 46 (IFLA_BR_MULTI_BOOLOPT) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // V = 0 ]; lazy_static! { static ref BRIDGE_INFO: Vec = vec![ InfoBridge::HelloTimer(35), InfoBridge::TcnTimer(0), InfoBridge::TopologyChangeTimer(0), InfoBridge::GcTimer(14261), InfoBridge::ForwardDelay(199), InfoBridge::HelloTime(199), InfoBridge::MaxAge(1999), InfoBridge::AgeingTime(29999), InfoBridge::StpState(1), InfoBridge::Priority(0x8000), InfoBridge::VlanFiltering(0), InfoBridge::GroupFwdMask(0), InfoBridge::BridgeId((128, [0x52, 0x54, 0x00, 0xd7, 0x19, 0x3e])), InfoBridge::RootId((128, [0x52, 0x54, 0x00, 0xd7, 0x19, 0x3e])), InfoBridge::RootPort(0), InfoBridge::RootPathCost(0), InfoBridge::TopologyChange(0), InfoBridge::TopologyChangeDetected(0), InfoBridge::GroupAddr([0x01, 0x80, 0xc2, 0x00, 0x00, 0x00]), InfoBridge::VlanProtocol(33024), InfoBridge::VlanDefaultPvid(1), InfoBridge::VlanStatsEnabled(0), InfoBridge::MulticastRouter(1), InfoBridge::MulticastSnooping(1), InfoBridge::MulticastQueryUseIfaddr(0), InfoBridge::MulticastQuerier(0), InfoBridge::MulticastStatsEnabled(0), InfoBridge::MulticastHashElasticity(4), InfoBridge::MulticastHashMax(512), InfoBridge::MulticastLastMemberCount(2), InfoBridge::MulticastStartupQueryCount(2), InfoBridge::MulticastIgmpVersion(2), InfoBridge::MulticastMldVersion(1), InfoBridge::MulticastLastMemberInterval(99), InfoBridge::MulticastMembershipInterval(25999), InfoBridge::MulticastQuerierInterval(25499), InfoBridge::MulticastQueryInterval(12499), InfoBridge::MulticastQueryResponseInterval(999), InfoBridge::MulticastStartupQueryInterval(3124), InfoBridge::NfCallIpTables(0), InfoBridge::NfCallIp6Tables(0), InfoBridge::NfCallArpTables(0), InfoBridge::VlanStatsPerHost(1), InfoBridge::MultiBoolOpt(0), ]; } #[test] fn parse_info_kind() { let info_kind_nla = NlaBuffer::new_checked(&BRIDGE[..12]).unwrap(); let parsed = InfoKind::parse(&info_kind_nla).unwrap(); assert_eq!(parsed, InfoKind::Bridge); } #[test] fn parse_info_bridge() { let nlas = NlasIterator::new(&BRIDGE[16..]); for nla in nlas.map(|nla| nla.unwrap()) { InfoBridge::parse(&nla).unwrap(); } } #[rustfmt::skip] #[test] fn parse_veth_info() { let data = vec![ 0x08, 0x00, // length = 8 0x01, 0x00, // type = 1 = IFLA_INFO_KIND 0x76, 0x65, 0x74, 0x68, // VETH 0x30, 0x00, // length = 48 0x02, 0x00, // type = IFLA_INFO_DATA 0x2c, 0x00, // length = 44 0x01, 0x00, // type = VETH_INFO_PEER // The data a NEWLINK message 0x00, // interface family 0x00, // padding 0x00, 0x00, // link layer type 0x00, 0x00, 0x00, 0x00, // link index 0x00, 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, // flags change mask // NLA 0x10, 0x00, // length = 16 0x03, 0x00, // type = IFLA_IFNAME 0x76, 0x65, 0x74, 0x68, 0x63, 0x30, 0x65, 0x36, 0x30, 0x64, 0x36, 0x00, // NLA 0x08, 0x00, // length = 8 0x0d, 0x00, // type = IFLA_TXQLEN 0x00, 0x00, 0x00, 0x00, ]; let nla = NlaBuffer::new_checked(&data[..]).unwrap(); let parsed = VecInfo::parse(&nla).unwrap().0; let expected = vec![ Info::Kind(InfoKind::Veth), Info::Data(InfoData::Veth(VethInfo::Peer(LinkMessage { header: LinkHeader { interface_family: 0, index: 0, link_layer_type: ARPHRD_NETROM, flags: 0, change_mask: 0, }, nlas: vec![ Nla::IfName("vethc0e60d6".to_string()), Nla::TxQueueLen(0), ], }))), ]; assert_eq!(expected, parsed); } #[rustfmt::skip] #[test] fn parse_info_bond() { let data = vec![ 0x08, 0x00, // length 0x01, 0x00, // IFLA_INFO_KIND 0x62, 0x6f, 0x6e, 0x64, // "bond" 0x80, 0x00, // length 0x02, 0x00, // IFLA_INFO_DATA 0x05, 0x00, // length 0x01, 0x00, // IFLA_BOND_MODE 0x04, // 4 (802.3ad) 0x00, 0x00, 0x00, // padding 0x08, 0x00, // length 0x03, 0x00, // IFLA_BOND_MIIMON 0x32, 0x00, 0x00, 0x00, // 50 0x08, 0x00, // length 0x04, 0x00, // IFLA_BOND_UPDELAY 0x64, 0x00, 0x00, 0x00, // 100 0x08, 0x00, // length 0x05, 0x00, // IFLA_BOND_DOWNDELAY 0x64, 0x00, 0x00, 0x00, // 100 0x14, 0x00, // length 0x08, 0x00, // IFLA_BOND_ARP_IP_TARGET 0x08, 0x00, // length 0x00, 0x00, // entry #0 0x01, 0x02, 0x03, 0x04, // 1.2.3.4 0x08, 0x00, // length 0x01, 0x00, // entry #1 0x09, 0x09, 0x09, 0x09, // 9.9.9.9 0x18, 0x00, // length 0x1f, 0x00, // IFLA_BOND_NS_IP6_TARGET 0x14, 0x00, // length 0x00, 0x00, // entry #0 0xfd, 0x01, 0x00, 0x00, // fd01::1 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, // length 0x1c, 0x00, // IFLA_BOND_PEER_NOTIF_DELAY 0xc8, 0x00, 0x00, 0x00, // 200 0x08, 0x00, // length 0x12, 0x00, // IFLA_BOND_MIN_LINKS 0x03, 0x00, 0x00, 0x00, // 3 0x20, 0x00, // length 0x17, 0x00, // IFLA_BOND_AD_INFO 0x06, 0x00, // length 0x01, 0x00, // IFLA_BOND_AD_INFO_AGGREGATOR 0x10, 0x00, // 16 0x00, 0x00, // padding 0x06, 0x00, // length 0x02, 0x00, // IFLA_BOND_AD_INFO_NUM_PORTS 0x02, 0x00, // 2 0x00, 0x00, // padding 0x0a, 0x00, // length 0x05, 0x00, // IFLA_BOND_AD_INFO_PARTNER_MAC 0x00, 0x11, 0x22, // 00:11:22:33:44:55 0x33, 0x44, 0x55, 0x00, 0x00, // padding ]; let nla = NlaBuffer::new_checked(&data[..]).unwrap(); let parsed = VecInfo::parse(&nla).unwrap().0; let expected = vec![ Info::Kind(InfoKind::Bond), Info::Data(InfoData::Bond(vec![InfoBond::Mode(4), InfoBond::MiiMon(50), InfoBond::UpDelay(100), InfoBond::DownDelay(100), InfoBond::ArpIpTarget(vec!(Ipv4Addr::new(1, 2, 3, 4), Ipv4Addr::new(9, 9, 9, 9))), InfoBond::NsIp6Target(vec!(Ipv6Addr::new(0xfd01, 0, 0, 0, 0, 0, 0, 1))), InfoBond::PeerNotifDelay(200), InfoBond::MinLinks(3), InfoBond::AdInfo(vec!(BondAdInfo::Aggregator(16), BondAdInfo::NumPorts(2), BondAdInfo::PartnerMac([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]))), ])), ]; assert_eq!(expected, parsed); } #[rustfmt::skip] static IPVLAN: [u8; 32] = [ 0x0b, 0x00, // length = 11 0x01, 0x00, // type = 1 = IFLA_INFO_KIND 0x69, 0x70, 0x76, 0x6c, 0x61, 0x6e, 0x00, // V = "ipvlan\0" 0x00, // padding 0x14, 0x00, // length = 20 0x02, 0x00, // type = 2 = IFLA_INFO_DATA 0x06, 0x00, // length = 6 0x01, 0x00, // type = 1 = IFLA_IPVLAN_MODE 0x01, 0x00, // l3 0x00, 0x00, // padding 0x06, 0x00, // length = 6 0x02, 0x00, // type = 2 = IFLA_IPVLAN_FLAGS 0x02, 0x00, // vepa flag 0x00, 0x00, // padding ]; lazy_static! { static ref IPVLAN_INFO: Vec = vec![ InfoIpVlan::Mode(1), // L3 InfoIpVlan::Flags(2), // vepa flag ]; } #[test] fn parse_info_ipvlan() { let nla = NlaBuffer::new_checked(&IPVLAN[..]).unwrap(); let parsed = VecInfo::parse(&nla).unwrap().0; let expected = vec![ Info::Kind(InfoKind::IpVlan), Info::Data(InfoData::IpVlan(IPVLAN_INFO.clone())), ]; assert_eq!(expected, parsed); } #[test] fn emit_info_ipvlan() { let nlas = vec![ Info::Kind(InfoKind::IpVlan), Info::Data(InfoData::IpVlan(IPVLAN_INFO.clone())), ]; assert_eq!(nlas.as_slice().buffer_len(), 32); let mut vec = vec![0xff; 32]; nlas.as_slice().emit(&mut vec); assert_eq!(&vec[..], &IPVLAN[..]); } #[rustfmt::skip] static MACVLAN: [u8; 24] = [ 0x0c, 0x00, // length = 12 0x01, 0x00, // type = 1 = IFLA_INFO_KIND 0x6d, 0x61, 0x63, 0x76, 0x6c, 0x61, 0x6e, 0x00, // V = "macvlan\0" 0x0c, 0x00, // length = 12 0x02, 0x00, // type = 2 = IFLA_INFO_DATA 0x08, 0x00, // length = 8 0x01, 0x00, // type = IFLA_MACVLAN_MODE 0x04, 0x00, 0x00, 0x00, // V = 4 = bridge ]; lazy_static! { static ref MACVLAN_INFO: Vec = vec![ InfoMacVlan::Mode(4), // bridge ]; } #[test] fn parse_info_macvlan() { let nla = NlaBuffer::new_checked(&MACVLAN[..]).unwrap(); let parsed = VecInfo::parse(&nla).unwrap().0; let expected = vec![ Info::Kind(InfoKind::MacVlan), Info::Data(InfoData::MacVlan(MACVLAN_INFO.clone())), ]; assert_eq!(expected, parsed); } #[test] fn emit_info_macvlan() { let nlas = vec![ Info::Kind(InfoKind::MacVlan), Info::Data(InfoData::MacVlan(MACVLAN_INFO.clone())), ]; assert_eq!(nlas.as_slice().buffer_len(), 24); let mut vec = vec![0xff; 24]; nlas.as_slice().emit(&mut vec); assert_eq!(&vec[..], &MACVLAN[..]); } #[rustfmt::skip] static MACVLAN_SOURCE_SET: [u8; 84] = [ 0x0c, 0x00, // length = 12 0x01, 0x00, // type = 1 = IFLA_INFO_KIND 0x6d, 0x61, 0x63, 0x76, 0x6c, 0x61, 0x6e, 0x00, // V = "macvlan\0" 0x48, 0x00, // length = 72 0x02, 0x00, // type = 2 = IFLA_INFO_DATA 0x08, 0x00, // length = 8 0x03, 0x00, // type = 3 = IFLA_MACVLAN_MACADDR_MODE 0x03, 0x00, 0x00, 0x00, // V = 3 = set 0x34, 0x00, // length = 52 0x05, 0x00, // type = 5 = IFLA_MACVLAN_MACADDR_DATA 0x0a, 0x00, // length = 10 0x04, 0x00, // type = 4 = IFLA_MACVLAN_MACADDR 0x22, 0xf5, 0x54, 0x09, 0x88, 0xd7, // V = mac address 0x00, 0x00, // padding 0x0a, 0x00, // length = 10 0x04, 0x00, // type = 4 = IFLA_MACVLAN_MACADDR 0x22, 0xf5, 0x54, 0x09, 0x99, 0x32, // V = mac address 0x00, 0x00, // padding 0x0a, 0x00, // length = 10 0x04, 0x00, // type = 4 = IFLA_MACVLAN_MACADDR 0x22, 0xf5, 0x54, 0x09, 0x87, 0x45, // V = mac address 0x00, 0x00, // padding 0x0a, 0x00, // length = 10 0x04, 0x00, // type = 4 = IFLA_MACVLAN_MACADDR 0x22, 0xf5, 0x54, 0x09, 0x11, 0x45, // V = mac address 0x00, 0x00, // padding 0x08, 0x00, // length = 8 0x01, 0x00, // Type = 1 = IFLA_MACVLAN_MODE 0x10, 0x00, 0x00, 0x00, // V = 16 = source ]; lazy_static! { static ref MACVLAN_SOURCE_SET_INFO: Vec = vec![ InfoMacVlan::MacAddrMode(3), // set InfoMacVlan::MacAddrData(vec![ InfoMacVlan::MacAddr([0x22, 0xf5, 0x54, 0x09, 0x88, 0xd7,]), InfoMacVlan::MacAddr([0x22, 0xf5, 0x54, 0x09, 0x99, 0x32,]), InfoMacVlan::MacAddr([0x22, 0xf5, 0x54, 0x09, 0x87, 0x45,]), InfoMacVlan::MacAddr([0x22, 0xf5, 0x54, 0x09, 0x11, 0x45,]), ]), InfoMacVlan::Mode(16), // source ]; } #[test] fn parse_info_macvlan_source_set() { let nla = NlaBuffer::new_checked(&MACVLAN_SOURCE_SET[..]).unwrap(); let parsed = VecInfo::parse(&nla).unwrap().0; let expected = vec![ Info::Kind(InfoKind::MacVlan), Info::Data(InfoData::MacVlan(MACVLAN_SOURCE_SET_INFO.clone())), ]; assert_eq!(expected, parsed); } #[test] fn emit_info_macvlan_source_set() { let nlas = vec![ Info::Kind(InfoKind::MacVlan), Info::Data(InfoData::MacVlan(MACVLAN_SOURCE_SET_INFO.clone())), ]; assert_eq!(nlas.as_slice().buffer_len(), 84); let mut vec = vec![0xff; 84]; nlas.as_slice().emit(&mut vec); assert_eq!(&vec[..], &MACVLAN_SOURCE_SET[..]); } #[test] fn parse() { let nla = NlaBuffer::new_checked(&BRIDGE[..]).unwrap(); let parsed = VecInfo::parse(&nla).unwrap().0; assert_eq!(parsed.len(), 2); assert_eq!(parsed[0], Info::Kind(InfoKind::Bridge)); if let Info::Data(InfoData::Bridge(nlas)) = parsed[1].clone() { assert_eq!(nlas.len(), BRIDGE_INFO.len()); for (expected, parsed) in BRIDGE_INFO.iter().zip(nlas) { assert_eq!(*expected, parsed); } } else { panic!( "expected Info::Data(InfoData::Bridge(_) got {:?}", parsed[1] ) } } #[test] fn emit() { let nlas = vec![ Info::Kind(InfoKind::Bridge), Info::Data(InfoData::Bridge(BRIDGE_INFO.clone())), ]; assert_eq!(nlas.as_slice().buffer_len(), 424); let mut vec = vec![0xff; 424]; nlas.as_slice().emit(&mut vec); assert_eq!(&vec[..], &BRIDGE[..]); } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/link_state.rs ================================================ // SPDX-License-Identifier: MIT use crate::constants::*; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum State { /// Status can't be determined Unknown, /// Some component is missing NotPresent, /// Down Down, /// Down due to state of lower layer LowerLayerDown, /// In some test mode Testing, /// Not up but pending an external event Dormant, /// Up, ready to send packets Up, /// Unrecognized value. This should go away when `TryFrom` is stable in Rust // FIXME: there's not point in having this. When TryFrom is stable we'll remove it Other(u8), } impl From for State { fn from(value: u8) -> Self { use self::State::*; match value { IF_OPER_UNKNOWN => Unknown, IF_OPER_NOTPRESENT => NotPresent, IF_OPER_DOWN => Down, IF_OPER_LOWERLAYERDOWN => LowerLayerDown, IF_OPER_TESTING => Testing, IF_OPER_DORMANT => Dormant, IF_OPER_UP => Up, _ => Other(value), } } } impl From for u8 { fn from(value: State) -> Self { use self::State::*; match value { Unknown => IF_OPER_UNKNOWN, NotPresent => IF_OPER_NOTPRESENT, Down => IF_OPER_DOWN, LowerLayerDown => IF_OPER_LOWERLAYERDOWN, Testing => IF_OPER_TESTING, Dormant => IF_OPER_DORMANT, Up => IF_OPER_UP, Other(other) => other, } } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/map.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; pub const LINK_MAP_LEN: usize = 28; buffer!(MapBuffer(LINK_MAP_LEN) { memory_start: (u64, 0..8), memory_end: (u64, 8..16), base_address: (u64, 16..24), irq: (u16, 24..26), dma: (u8, 26), port: (u8, 27), }); #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct Map { pub memory_start: u64, pub memory_end: u64, pub base_address: u64, pub irq: u16, pub dma: u8, pub port: u8, } impl> Parseable> for Map { fn parse(buf: &MapBuffer) -> Result { Ok(Self { memory_start: buf.memory_start(), memory_end: buf.memory_end(), base_address: buf.base_address(), irq: buf.irq(), dma: buf.dma(), port: buf.port(), }) } } impl Emitable for Map { fn buffer_len(&self) -> usize { LINK_MAP_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = MapBuffer::new(buffer); buffer.set_memory_start(self.memory_start); buffer.set_memory_end(self.memory_end); buffer.set_base_address(self.base_address); buffer.set_irq(self.irq); buffer.set_dma(self.dma); buffer.set_port(self.port); } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/mod.rs ================================================ // SPDX-License-Identifier: MIT mod inet; pub use self::inet::*; mod inet6; pub use self::inet6::*; mod af_spec_inet; pub use self::af_spec_inet::*; mod af_spec_bridge; pub use self::af_spec_bridge::*; mod link_infos; pub use self::link_infos::*; mod bond; pub use self::bond::*; mod prop_list; pub use self::prop_list::*; mod map; pub use self::map::*; mod stats; pub use self::stats::*; mod stats64; pub use self::stats64::*; mod link_state; pub use self::link_state::*; #[cfg(test)] mod tests; use std::os::unix::io::RawFd; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use crate::{ constants::*, nlas::{self, DefaultNla, NlaBuffer, NlasIterator, NLA_F_NESTED}, parsers::{parse_i32, parse_string, parse_u32, parse_u8}, traits::{Emitable, Parseable, ParseableParametrized}, DecodeError, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Nla { // Vec Unspec(Vec), Cost(Vec), Priority(Vec), Weight(Vec), VfInfoList(Vec), VfPorts(Vec), PortSelf(Vec), PhysPortId(Vec), PhysSwitchId(Vec), Pad(Vec), Xdp(Vec), Event(Vec), NewNetnsId(Vec), IfNetnsId(Vec), CarrierUpCount(Vec), CarrierDownCount(Vec), NewIfIndex(Vec), Info(Vec), Wireless(Vec), ProtoInfo(Vec), /// A list of properties for the device. For additional context see the related linux kernel /// threads[1][1],[2][2]. In particular see [this message][defining message] from /// the first thread describing the design. /// /// [1]: https://lwn.net/ml/netdev/20190719110029.29466-1-jiri@resnulli.us/ /// [2]: https://lwn.net/ml/netdev/20190930094820.11281-1-jiri@resnulli.us/ /// [defining message]: https://lwn.net/ml/netdev/20190913145012.GB2276@nanopsycho.orion/ PropList(Vec), /// `protodown` is a mechanism that allows protocols to hold an interface down. /// This field is used to specify the reason why it is held down. /// For additional context see the related linux kernel threads[1][1],[2][2]. /// /// [1]: https://lwn.net/ml/netdev/1595877677-45849-1-git-send-email-roopa%40cumulusnetworks.com/ /// [2]: https://lwn.net/ml/netdev/1596242041-14347-1-git-send-email-roopa%40cumulusnetworks.com/ ProtoDownReason(Vec), // mac address (use to be [u8; 6] but it turns out MAC != HW address, for instance for IP over // GRE where it's an IPv4!) Address(Vec), Broadcast(Vec), /// Permanent hardware address of the device. The provides the same information /// as the ethtool ioctl interface. PermAddress(Vec), // string // FIXME: for empty string, should we encode the NLA as \0 or should we not set a payload? It // seems that for certain attriutes, this matter: // https://elixir.bootlin.com/linux/v4.17-rc5/source/net/core/rtnetlink.c#L1660 IfName(String), Qdisc(String), IfAlias(String), PhysPortName(String), /// Alternate name for the device. /// For additional context see the related linux kernel threads[1][1],[2][2]. /// /// [1]: https://lwn.net/ml/netdev/20190719110029.29466-1-jiri@resnulli.us/ /// [2]: https://lwn.net/ml/netdev/20190930094820.11281-1-jiri@resnulli.us/ AltIfName(String), // byte Mode(u8), Carrier(u8), ProtoDown(u8), // u32 Mtu(u32), Link(u32), Master(u32), TxQueueLen(u32), NetNsPid(u32), NumVf(u32), Group(u32), NetNsFd(RawFd), ExtMask(u32), Promiscuity(u32), NumTxQueues(u32), NumRxQueues(u32), CarrierChanges(u32), GsoMaxSegs(u32), GsoMaxSize(u32), /// The minimum MTU for the device. /// For additional context see the related [linux kernel message][1]. /// /// [1]: https://lwn.net/ml/netdev/20180727204323.19408-3-sthemmin%40microsoft.com/ MinMtu(u32), /// The maximum MTU for the device. /// For additional context see the related [linux kernel message][1]. /// /// [1]: https://lwn.net/ml/netdev/20180727204323.19408-3-sthemmin%40microsoft.com/ MaxMtu(u32), // i32 NetnsId(i32), // custom OperState(State), Stats(Vec), Stats64(Vec), Map(Vec), // AF_SPEC (the type of af_spec depends on the interface family of the message) AfSpecInet(Vec), AfSpecBridge(Vec), //AfSpecBridge(Vec), AfSpecUnknown(Vec), Other(DefaultNla), } impl nlas::Nla for Nla { #[rustfmt::skip] fn value_len(&self) -> usize { use self::Nla::*; match *self { // Vec Unspec(ref bytes) | Cost(ref bytes) | Priority(ref bytes) | Weight(ref bytes) | VfInfoList(ref bytes) | VfPorts(ref bytes) | PortSelf(ref bytes) | PhysPortId(ref bytes) | PhysSwitchId(ref bytes) | Pad(ref bytes) | Xdp(ref bytes) | Event(ref bytes) | NewNetnsId(ref bytes) | IfNetnsId(ref bytes) | Wireless(ref bytes) | ProtoInfo(ref bytes) | CarrierUpCount(ref bytes) | CarrierDownCount(ref bytes) | NewIfIndex(ref bytes) | Address(ref bytes) | Broadcast(ref bytes) | PermAddress(ref bytes) | AfSpecUnknown(ref bytes) | Map(ref bytes) | ProtoDownReason(ref bytes) => bytes.len(), // strings: +1 because we need to append a nul byte IfName(ref string) | Qdisc(ref string) | IfAlias(ref string) | PhysPortName(ref string) | AltIfName(ref string) => string.as_bytes().len() + 1, // u8 Mode(_) | Carrier(_) | ProtoDown(_) => 1, // u32 and i32 Mtu(_) | Link(_) | Master(_) | TxQueueLen(_) | NetNsPid(_) | NumVf(_) | Group(_) | NetNsFd(_) | ExtMask(_) | Promiscuity(_) | NumTxQueues(_) | NumRxQueues(_) | CarrierChanges(_) | GsoMaxSegs(_) | GsoMaxSize(_) | NetnsId(_) | MinMtu(_) | MaxMtu(_) => 4, // Defaults OperState(_) => 1, Stats(_) => LINK_STATS_LEN, Stats64(_) => LINK_STATS64_LEN, Info(ref nlas) => nlas.as_slice().buffer_len(), PropList(ref nlas) => nlas.as_slice().buffer_len(), AfSpecInet(ref nlas) => nlas.as_slice().buffer_len(), AfSpecBridge(ref nlas) => nlas.as_slice().buffer_len(), Other(ref attr) => attr.value_len(), } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::Nla::*; match *self { // Vec Unspec(ref bytes) | Cost(ref bytes) | Priority(ref bytes) | Weight(ref bytes) | VfInfoList(ref bytes) | VfPorts(ref bytes) | PortSelf(ref bytes) | PhysPortId(ref bytes) | PhysSwitchId(ref bytes) | Wireless(ref bytes) | ProtoInfo(ref bytes) | Pad(ref bytes) | Xdp(ref bytes) | Event(ref bytes) | NewNetnsId(ref bytes) | IfNetnsId(ref bytes) | CarrierUpCount(ref bytes) | CarrierDownCount(ref bytes) | NewIfIndex(ref bytes) // mac address (could be [u8; 6] or [u8; 4] for example. Not sure if we should have // a separate type for them | Address(ref bytes) | Broadcast(ref bytes) | PermAddress(ref bytes) | AfSpecUnknown(ref bytes) | Stats(ref bytes) | Stats64(ref bytes) | Map(ref bytes) | ProtoDownReason(ref bytes) => buffer.copy_from_slice(bytes.as_slice()), // String IfName(ref string) | Qdisc(ref string) | IfAlias(ref string) | PhysPortName(ref string) | AltIfName(ref string) => { buffer[..string.len()].copy_from_slice(string.as_bytes()); buffer[string.len()] = 0; } // u8 Mode(ref val) | Carrier(ref val) | ProtoDown(ref val) => buffer[0] = *val, // u32 Mtu(ref value) | Link(ref value) | Master(ref value) | TxQueueLen(ref value) | NetNsPid(ref value) | NumVf(ref value) | Group(ref value) | ExtMask(ref value) | Promiscuity(ref value) | NumTxQueues(ref value) | NumRxQueues(ref value) | CarrierChanges(ref value) | GsoMaxSegs(ref value) | GsoMaxSize(ref value) | MinMtu(ref value) | MaxMtu(ref value) => NativeEndian::write_u32(buffer, *value), NetnsId(ref value) | NetNsFd(ref value) => NativeEndian::write_i32(buffer, *value), OperState(state) => buffer[0] = state.into(), Info(ref nlas) => nlas.as_slice().emit(buffer), PropList(ref nlas) => nlas.as_slice().emit(buffer), AfSpecInet(ref nlas) => nlas.as_slice().emit(buffer), AfSpecBridge(ref nlas) => nlas.as_slice().emit(buffer), // default nlas Other(ref attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Nla::*; match *self { // Vec Unspec(_) => IFLA_UNSPEC, Cost(_) => IFLA_COST, Priority(_) => IFLA_PRIORITY, Weight(_) => IFLA_WEIGHT, VfInfoList(_) => IFLA_VFINFO_LIST, VfPorts(_) => IFLA_VF_PORTS, PortSelf(_) => IFLA_PORT_SELF, PhysPortId(_) => IFLA_PHYS_PORT_ID, PhysSwitchId(_) => IFLA_PHYS_SWITCH_ID, Info(_) => IFLA_LINKINFO, Wireless(_) => IFLA_WIRELESS, ProtoInfo(_) => IFLA_PROTINFO, Pad(_) => IFLA_PAD, Xdp(_) => IFLA_XDP, Event(_) => IFLA_EVENT, NewNetnsId(_) => IFLA_NEW_NETNSID, IfNetnsId(_) => IFLA_IF_NETNSID, CarrierUpCount(_) => IFLA_CARRIER_UP_COUNT, CarrierDownCount(_) => IFLA_CARRIER_DOWN_COUNT, NewIfIndex(_) => IFLA_NEW_IFINDEX, PropList(_) => IFLA_PROP_LIST | NLA_F_NESTED, ProtoDownReason(_) => IFLA_PROTO_DOWN_REASON, // Mac address Address(_) => IFLA_ADDRESS, Broadcast(_) => IFLA_BROADCAST, PermAddress(_) => IFLA_PERM_ADDRESS, // String IfName(_) => IFLA_IFNAME, Qdisc(_) => IFLA_QDISC, IfAlias(_) => IFLA_IFALIAS, PhysPortName(_) => IFLA_PHYS_PORT_NAME, AltIfName(_) => IFLA_ALT_IFNAME, // u8 Mode(_) => IFLA_LINKMODE, Carrier(_) => IFLA_CARRIER, ProtoDown(_) => IFLA_PROTO_DOWN, // u32 Mtu(_) => IFLA_MTU, Link(_) => IFLA_LINK, Master(_) => IFLA_MASTER, TxQueueLen(_) => IFLA_TXQLEN, NetNsPid(_) => IFLA_NET_NS_PID, NumVf(_) => IFLA_NUM_VF, Group(_) => IFLA_GROUP, NetNsFd(_) => IFLA_NET_NS_FD, ExtMask(_) => IFLA_EXT_MASK, Promiscuity(_) => IFLA_PROMISCUITY, NumTxQueues(_) => IFLA_NUM_TX_QUEUES, NumRxQueues(_) => IFLA_NUM_RX_QUEUES, CarrierChanges(_) => IFLA_CARRIER_CHANGES, GsoMaxSegs(_) => IFLA_GSO_MAX_SEGS, GsoMaxSize(_) => IFLA_GSO_MAX_SIZE, MinMtu(_) => IFLA_MIN_MTU, MaxMtu(_) => IFLA_MAX_MTU, // i32 NetnsId(_) => IFLA_LINK_NETNSID, // custom OperState(_) => IFLA_OPERSTATE, Map(_) => IFLA_MAP, Stats(_) => IFLA_STATS, Stats64(_) => IFLA_STATS64, AfSpecInet(_) | AfSpecBridge(_) | AfSpecUnknown(_) => IFLA_AF_SPEC, Other(ref attr) => attr.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> ParseableParametrized, u16> for Nla { fn parse_with_param( buf: &NlaBuffer<&'a T>, interface_family: u16, ) -> Result { use Nla::*; let payload = buf.value(); Ok(match buf.kind() { // Vec IFLA_UNSPEC => Unspec(payload.to_vec()), IFLA_COST => Cost(payload.to_vec()), IFLA_PRIORITY => Priority(payload.to_vec()), IFLA_WEIGHT => Weight(payload.to_vec()), IFLA_VFINFO_LIST => VfInfoList(payload.to_vec()), IFLA_VF_PORTS => VfPorts(payload.to_vec()), IFLA_PORT_SELF => PortSelf(payload.to_vec()), IFLA_PHYS_PORT_ID => PhysPortId(payload.to_vec()), IFLA_PHYS_SWITCH_ID => PhysSwitchId(payload.to_vec()), IFLA_WIRELESS => Wireless(payload.to_vec()), IFLA_PROTINFO => ProtoInfo(payload.to_vec()), IFLA_PAD => Pad(payload.to_vec()), IFLA_XDP => Xdp(payload.to_vec()), IFLA_EVENT => Event(payload.to_vec()), IFLA_NEW_NETNSID => NewNetnsId(payload.to_vec()), IFLA_IF_NETNSID => IfNetnsId(payload.to_vec()), IFLA_CARRIER_UP_COUNT => CarrierUpCount(payload.to_vec()), IFLA_CARRIER_DOWN_COUNT => CarrierDownCount(payload.to_vec()), IFLA_NEW_IFINDEX => NewIfIndex(payload.to_vec()), IFLA_PROP_LIST => { let error_msg = "invalid IFLA_PROP_LIST value"; let mut nlas = vec![]; for nla in NlasIterator::new(payload) { let nla = &nla.context(error_msg)?; let parsed = Prop::parse(nla).context(error_msg)?; nlas.push(parsed); } PropList(nlas) } IFLA_PROTO_DOWN_REASON => ProtoDownReason(payload.to_vec()), // HW address (we parse them as Vec for now, because for IP over GRE, the HW address is // an IP instead of a MAC for example IFLA_ADDRESS => Address(payload.to_vec()), IFLA_BROADCAST => Broadcast(payload.to_vec()), IFLA_PERM_ADDRESS => PermAddress(payload.to_vec()), // String IFLA_IFNAME => IfName(parse_string(payload).context("invalid IFLA_IFNAME value")?), IFLA_QDISC => Qdisc(parse_string(payload).context("invalid IFLA_QDISC value")?), IFLA_IFALIAS => IfAlias(parse_string(payload).context("invalid IFLA_IFALIAS value")?), IFLA_PHYS_PORT_NAME => { PhysPortName(parse_string(payload).context("invalid IFLA_PHYS_PORT_NAME value")?) } IFLA_ALT_IFNAME => { AltIfName(parse_string(payload).context("invalid IFLA_ALT_IFNAME value")?) } // u8 IFLA_LINKMODE => Mode(parse_u8(payload).context("invalid IFLA_LINKMODE value")?), IFLA_CARRIER => Carrier(parse_u8(payload).context("invalid IFLA_CARRIER value")?), IFLA_PROTO_DOWN => { ProtoDown(parse_u8(payload).context("invalid IFLA_PROTO_DOWN value")?) } IFLA_MTU => Mtu(parse_u32(payload).context("invalid IFLA_MTU value")?), IFLA_LINK => Link(parse_u32(payload).context("invalid IFLA_LINK value")?), IFLA_MASTER => Master(parse_u32(payload).context("invalid IFLA_MASTER value")?), IFLA_TXQLEN => TxQueueLen(parse_u32(payload).context("invalid IFLA_TXQLEN value")?), IFLA_NET_NS_PID => { NetNsPid(parse_u32(payload).context("invalid IFLA_NET_NS_PID value")?) } IFLA_NUM_VF => NumVf(parse_u32(payload).context("invalid IFLA_NUM_VF value")?), IFLA_GROUP => Group(parse_u32(payload).context("invalid IFLA_GROUP value")?), IFLA_NET_NS_FD => NetNsFd(parse_i32(payload).context("invalid IFLA_NET_NS_FD value")?), IFLA_EXT_MASK => ExtMask(parse_u32(payload).context("invalid IFLA_EXT_MASK value")?), IFLA_PROMISCUITY => { Promiscuity(parse_u32(payload).context("invalid IFLA_PROMISCUITY value")?) } IFLA_NUM_TX_QUEUES => { NumTxQueues(parse_u32(payload).context("invalid IFLA_NUM_TX_QUEUES value")?) } IFLA_NUM_RX_QUEUES => { NumRxQueues(parse_u32(payload).context("invalid IFLA_NUM_RX_QUEUES value")?) } IFLA_CARRIER_CHANGES => { CarrierChanges(parse_u32(payload).context("invalid IFLA_CARRIER_CHANGES value")?) } IFLA_GSO_MAX_SEGS => { GsoMaxSegs(parse_u32(payload).context("invalid IFLA_GSO_MAX_SEGS value")?) } IFLA_GSO_MAX_SIZE => { GsoMaxSize(parse_u32(payload).context("invalid IFLA_GSO_MAX_SIZE value")?) } IFLA_MIN_MTU => MinMtu(parse_u32(payload).context("invalid IFLA_MIN_MTU value")?), IFLA_MAX_MTU => MaxMtu(parse_u32(payload).context("invalid IFLA_MAX_MTU value")?), IFLA_LINK_NETNSID => { NetnsId(parse_i32(payload).context("invalid IFLA_LINK_NETNSID value")?) } IFLA_OPERSTATE => OperState( parse_u8(payload) .context("invalid IFLA_OPERSTATE value")? .into(), ), IFLA_MAP => Map(payload.to_vec()), IFLA_STATS => Stats(payload.to_vec()), IFLA_STATS64 => Stats64(payload.to_vec()), IFLA_AF_SPEC => match interface_family as u16 { AF_INET | AF_INET6 | AF_UNSPEC => { let mut nlas = vec![]; let err = "invalid IFLA_AF_SPEC value"; for nla in NlasIterator::new(payload) { let nla = nla.context(err)?; nlas.push(af_spec_inet::AfSpecInet::parse(&nla).context(err)?); } AfSpecInet(nlas) } AF_BRIDGE => { let mut nlas = vec![]; let err = "invalid IFLA_AF_SPEC value for AF_BRIDGE"; for nla in NlasIterator::new(payload) { let nla = nla.context(err)?; nlas.push(af_spec_bridge::AfSpecBridge::parse(&nla).context(err)?); } AfSpecBridge(nlas) } _ => AfSpecUnknown(payload.to_vec()), }, IFLA_LINKINFO => { let err = "invalid IFLA_LINKINFO value"; let buf = NlaBuffer::new_checked(payload).context(err)?; Info(VecInfo::parse(&buf).context(err)?.0) } kind => Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/prop_list.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ constants::*, nlas::{DefaultNla, Nla, NlaBuffer}, parsers::parse_string, traits::Parseable, DecodeError, }; use anyhow::Context; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Prop { AltIfName(String), Other(DefaultNla), } impl Nla for Prop { #[rustfmt::skip] fn value_len(&self) -> usize { use self::Prop::*; match self { AltIfName(ref string) => string.as_bytes().len() + 1, Other(nla) => nla.value_len() } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::Prop::*; match self { AltIfName(ref string) => { buffer[..string.len()].copy_from_slice(string.as_bytes()); buffer[string.len()] = 0; }, Other(nla) => nla.emit_value(buffer) } } fn kind(&self) -> u16 { use self::Prop::*; match self { AltIfName(_) => IFLA_ALT_IFNAME, Other(nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Prop { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { IFLA_ALT_IFNAME => { Prop::AltIfName(parse_string(payload).context("invalid IFLA_ALT_IFNAME value")?) } kind => { Prop::Other(DefaultNla::parse(buf).context(format!("Unknown NLA type {}", kind))?) } }) } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/stats.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct Stats { /// total packets received pub rx_packets: u32, /// total packets transmitted pub tx_packets: u32, /// total bytes received pub rx_bytes: u32, /// total bytes transmitted pub tx_bytes: u32, /// bad packets received pub rx_errors: u32, /// packet transmit problems pub tx_errors: u32, /// no space in linux buffers pub rx_dropped: u32, /// no space available in linux pub tx_dropped: u32, /// multicast packets received pub multicast: u32, pub collisions: u32, // detailed rx_errors pub rx_length_errors: u32, /// receiver ring buff overflow pub rx_over_errors: u32, /// received packets with crc error pub rx_crc_errors: u32, /// received frame alignment errors pub rx_frame_errors: u32, /// recv'r fifo overrun pub rx_fifo_errors: u32, /// receiver missed packet pub rx_missed_errors: u32, // detailed tx_errors pub tx_aborted_errors: u32, pub tx_carrier_errors: u32, pub tx_fifo_errors: u32, pub tx_heartbeat_errors: u32, pub tx_window_errors: u32, // for cslip etc pub rx_compressed: u32, pub tx_compressed: u32, /// dropped, no handler found pub rx_nohandler: u32, } pub const LINK_STATS_LEN: usize = 96; buffer!(StatsBuffer(LINK_STATS_LEN) { rx_packets: (u32, 0..4), tx_packets: (u32, 4..8), rx_bytes: (u32, 8..12), tx_bytes: (u32, 12..16), rx_errors: (u32, 16..20), tx_errors: (u32, 20..24), rx_dropped: (u32, 24..28), tx_dropped: (u32, 28..32), multicast: (u32, 32..36), collisions: (u32, 36..40), rx_length_errors: (u32, 40..44), rx_over_errors: (u32, 44..48), rx_crc_errors: (u32, 48..52), rx_frame_errors: (u32, 52..56), rx_fifo_errors: (u32, 56..60), rx_missed_errors: (u32, 60..64), tx_aborted_errors: (u32, 64..68), tx_carrier_errors: (u32, 68..72), tx_fifo_errors: (u32, 72..76), tx_heartbeat_errors: (u32, 76..80), tx_window_errors: (u32, 80..84), rx_compressed: (u32, 84..88), tx_compressed: (u32, 88..92), rx_nohandler: (u32, 92..96), }); impl> Parseable> for Stats { fn parse(buf: &StatsBuffer) -> Result { Ok(Self { rx_packets: buf.rx_packets(), tx_packets: buf.tx_packets(), rx_bytes: buf.rx_bytes(), tx_bytes: buf.tx_bytes(), rx_errors: buf.rx_errors(), tx_errors: buf.tx_errors(), rx_dropped: buf.rx_dropped(), tx_dropped: buf.tx_dropped(), multicast: buf.multicast(), collisions: buf.collisions(), rx_length_errors: buf.rx_length_errors(), rx_over_errors: buf.rx_over_errors(), rx_crc_errors: buf.rx_crc_errors(), rx_frame_errors: buf.rx_frame_errors(), rx_fifo_errors: buf.rx_fifo_errors(), rx_missed_errors: buf.rx_missed_errors(), tx_aborted_errors: buf.tx_aborted_errors(), tx_carrier_errors: buf.tx_carrier_errors(), tx_fifo_errors: buf.tx_fifo_errors(), tx_heartbeat_errors: buf.tx_heartbeat_errors(), tx_window_errors: buf.tx_window_errors(), rx_compressed: buf.rx_compressed(), tx_compressed: buf.tx_compressed(), rx_nohandler: buf.rx_nohandler(), }) } } impl Emitable for Stats { fn buffer_len(&self) -> usize { LINK_STATS_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = StatsBuffer::new(buffer); buffer.set_rx_packets(self.rx_packets); buffer.set_tx_packets(self.tx_packets); buffer.set_rx_bytes(self.rx_bytes); buffer.set_tx_bytes(self.tx_bytes); buffer.set_rx_errors(self.rx_errors); buffer.set_tx_errors(self.tx_errors); buffer.set_rx_dropped(self.rx_dropped); buffer.set_tx_dropped(self.tx_dropped); buffer.set_multicast(self.multicast); buffer.set_collisions(self.collisions); buffer.set_rx_length_errors(self.rx_length_errors); buffer.set_rx_over_errors(self.rx_over_errors); buffer.set_rx_crc_errors(self.rx_crc_errors); buffer.set_rx_frame_errors(self.rx_frame_errors); buffer.set_rx_fifo_errors(self.rx_fifo_errors); buffer.set_rx_missed_errors(self.rx_missed_errors); buffer.set_tx_aborted_errors(self.tx_aborted_errors); buffer.set_tx_carrier_errors(self.tx_carrier_errors); buffer.set_tx_fifo_errors(self.tx_fifo_errors); buffer.set_tx_heartbeat_errors(self.tx_heartbeat_errors); buffer.set_tx_window_errors(self.tx_window_errors); buffer.set_rx_compressed(self.rx_compressed); buffer.set_tx_compressed(self.tx_compressed); buffer.set_rx_nohandler(self.rx_nohandler); } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/stats64.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; pub const LINK_STATS64_LEN: usize = 192; buffer!(Stats64Buffer(LINK_STATS64_LEN) { rx_packets: (u64, 0..8), tx_packets: (u64, 8..16), rx_bytes: (u64, 16..24), tx_bytes: (u64, 24..32), rx_errors: (u64, 32..40), tx_errors: (u64, 40..48), rx_dropped: (u64, 48..56), tx_dropped: (u64, 56..64), multicast: (u64, 64..72), collisions: (u64, 72..80), rx_length_errors: (u64, 80..88), rx_over_errors: (u64, 88..96), rx_crc_errors: (u64, 96..104), rx_frame_errors: (u64, 104..112), rx_fifo_errors: (u64, 112..120), rx_missed_errors: (u64, 120..128), tx_aborted_errors: (u64, 128..136), tx_carrier_errors: (u64, 136..144), tx_fifo_errors: (u64, 144..152), tx_heartbeat_errors: (u64, 152..160), tx_window_errors: (u64, 160..168), rx_compressed: (u64, 168..176), tx_compressed: (u64, 176..184), rx_nohandler: (u64, 184..192), }); #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct Stats64 { /// total packets received pub rx_packets: u64, /// total packets transmitted pub tx_packets: u64, /// total bytes received pub rx_bytes: u64, /// total bytes transmitted pub tx_bytes: u64, /// bad packets received pub rx_errors: u64, /// packet transmit problems pub tx_errors: u64, /// no space in linux buffers pub rx_dropped: u64, /// no space available in linux pub tx_dropped: u64, /// multicast packets received pub multicast: u64, pub collisions: u64, // detailed rx_errors pub rx_length_errors: u64, /// receiver ring buff overflow pub rx_over_errors: u64, /// received packets with crc error pub rx_crc_errors: u64, /// received frame alignment errors pub rx_frame_errors: u64, /// recv'r fifo overrun pub rx_fifo_errors: u64, /// receiver missed packet pub rx_missed_errors: u64, // detailed tx_errors pub tx_aborted_errors: u64, pub tx_carrier_errors: u64, pub tx_fifo_errors: u64, pub tx_heartbeat_errors: u64, pub tx_window_errors: u64, // for cslip etc pub rx_compressed: u64, pub tx_compressed: u64, /// dropped, no handler found pub rx_nohandler: u64, } impl> Parseable> for Stats64 { fn parse(buf: &Stats64Buffer) -> Result { Ok(Self { rx_packets: buf.rx_packets(), tx_packets: buf.tx_packets(), rx_bytes: buf.rx_bytes(), tx_bytes: buf.tx_bytes(), rx_errors: buf.rx_errors(), tx_errors: buf.tx_errors(), rx_dropped: buf.rx_dropped(), tx_dropped: buf.tx_dropped(), multicast: buf.multicast(), collisions: buf.collisions(), rx_length_errors: buf.rx_length_errors(), rx_over_errors: buf.rx_over_errors(), rx_crc_errors: buf.rx_crc_errors(), rx_frame_errors: buf.rx_frame_errors(), rx_fifo_errors: buf.rx_fifo_errors(), rx_missed_errors: buf.rx_missed_errors(), tx_aborted_errors: buf.tx_aborted_errors(), tx_carrier_errors: buf.tx_carrier_errors(), tx_fifo_errors: buf.tx_fifo_errors(), tx_heartbeat_errors: buf.tx_heartbeat_errors(), tx_window_errors: buf.tx_window_errors(), rx_compressed: buf.rx_compressed(), tx_compressed: buf.tx_compressed(), rx_nohandler: buf.rx_nohandler(), }) } } impl Emitable for Stats64 { fn buffer_len(&self) -> usize { LINK_STATS64_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = Stats64Buffer::new(buffer); buffer.set_rx_packets(self.rx_packets); buffer.set_tx_packets(self.tx_packets); buffer.set_rx_bytes(self.rx_bytes); buffer.set_tx_bytes(self.tx_bytes); buffer.set_rx_errors(self.rx_errors); buffer.set_tx_errors(self.tx_errors); buffer.set_rx_dropped(self.rx_dropped); buffer.set_tx_dropped(self.tx_dropped); buffer.set_multicast(self.multicast); buffer.set_collisions(self.collisions); buffer.set_rx_length_errors(self.rx_length_errors); buffer.set_rx_over_errors(self.rx_over_errors); buffer.set_rx_crc_errors(self.rx_crc_errors); buffer.set_rx_frame_errors(self.rx_frame_errors); buffer.set_rx_fifo_errors(self.rx_fifo_errors); buffer.set_rx_missed_errors(self.rx_missed_errors); buffer.set_tx_aborted_errors(self.tx_aborted_errors); buffer.set_tx_carrier_errors(self.tx_carrier_errors); buffer.set_tx_fifo_errors(self.tx_fifo_errors); buffer.set_tx_heartbeat_errors(self.tx_heartbeat_errors); buffer.set_tx_window_errors(self.tx_window_errors); buffer.set_rx_compressed(self.rx_compressed); buffer.set_tx_compressed(self.tx_compressed); buffer.set_rx_nohandler(self.rx_nohandler); } } ================================================ FILE: netlink-packet-route/src/rtnl/link/nlas/tests.rs ================================================ // SPDX-License-Identifier: MIT use crate::{utils::nla::Nla, DecodeError}; use super::*; // https://lists.infradead.org/pipermail/libnl/2015-November/002034.html // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/if_link.h#L89 #[rustfmt::skip] static BYTES: [u8; 748] = [ // AF_SPEC (L=748, T=26) 0xec, 0x02, 0x1a, 0x00, // AF_INET (L=132, T=2) 0x84, 0x00, 0x02, 0x00, // IFLA_INET_CONF (L=128, T=1) 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, // 1 forwarding 0x00, 0x00, 0x00, 0x00, // 2 mc_forwarding 0x00, 0x00, 0x00, 0x00, // 3 proxy_arp 0x01, 0x00, 0x00, 0x00, // 4 accept_redirects 0x01, 0x00, 0x00, 0x00, // 5 secure_redirects 0x01, 0x00, 0x00, 0x00, // 6 send_redirects 0x01, 0x00, 0x00, 0x00, // 7 shared_media 0x00, 0x00, 0x00, 0x00, // 8 rp_filter 0x01, 0x00, 0x00, 0x00, // 9 accept_source_route 0x00, 0x00, 0x00, 0x00, // 10 bootp_relay (40 bytes) 0x00, 0x00, 0x00, 0x00, // 11 log_martians 0x00, 0x00, 0x00, 0x00, // 12 tag 0x00, 0x00, 0x00, 0x00, // 13 arpfilter 0x00, 0x00, 0x00, 0x00, // 14 medium_id 0x01, 0x00, 0x00, 0x00, // 15 noxfrm 0x01, 0x00, 0x00, 0x00, // 16 nopolicy 0x00, 0x00, 0x00, 0x00, // 17 force_igmp_version 0x00, 0x00, 0x00, 0x00, // 18 arp_announce 0x00, 0x00, 0x00, 0x00, // 19 arp_ignore 0x00, 0x00, 0x00, 0x00, // 20 promote_secondaries (80 bytes) 0x00, 0x00, 0x00, 0x00, // 21 arp_accept 0x00, 0x00, 0x00, 0x00, // 22 arp_notify 0x00, 0x00, 0x00, 0x00, // 23 accept_local 0x00, 0x00, 0x00, 0x00, // 24 src_vmark 0x00, 0x00, 0x00, 0x00, // 25 proxy_arp_pvlan 0x00, 0x00, 0x00, 0x00, // 26 route_localnet 0x10, 0x27, 0x00, 0x00, // 27 igmpv2_unsolicited_report_interval 0xe8, 0x03, 0x00, 0x00, // 28 igmpv3_unsolicited_report_interval 0x00, 0x00, 0x00, 0x00, // 29 ignore_routes_with_linkdown 0x00, 0x00, 0x00, 0x00, // 30 drop_unicast_in_l2_multicast (120 bytes) 0x00, 0x00, 0x00, 0x00, // 31 drop_gratuitous_arp // AF_INET6 (L=612, T=10) 0x64, 0x02, 0x0a, 0x00, // IFLA_INET6_FLAGS (L=8,T=1) 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, // IFLA_INET6_CACHEINFO (L=20, T=5) 0x14, 0x00, 0x05, 0x00, 0xff, 0xff, 0x00, 0x00, // max_reasm_len 0xaf, 0x00, 0x00, 0x00, // tstamp 0x82, 0x64, 0x00, 0x00, // reachable_time 0xe8, 0x03, 0x00, 0x00, // retrans_time // IFLA_INET6_CONF (L=208, T=2) 0xd0, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // forwarding 0x40, 0x00, 0x00, 0x00, // hoplimit 0x00, 0x00, 0x01, 0x00, // mtu6 0x01, 0x00, 0x00, 0x00, // accept_ra 0x01, 0x00, 0x00, 0x00, // accept_redirects 0x01, 0x00, 0x00, 0x00, // autoconf 0x01, 0x00, 0x00, 0x00, // dad_transmits 0xff, 0xff, 0xff, 0xff, // rtr_solicits 0xa0, 0x0f, 0x00, 0x00, // rtr_solicit_interval 0xe8, 0x03, 0x00, 0x00, // rtr_solicit_delay 0xff, 0xff, 0xff, 0xff, // use_tempaddr 0x80, 0x3a, 0x09, 0x00, // temp_valid_lft 0x80, 0x51, 0x01, 0x00, // temp_prefered_lft 0x03, 0x00, 0x00, 0x00, // regen_max_retry 0x58, 0x02, 0x00, 0x00, // max_desync_factor 0x10, 0x00, 0x00, 0x00, // max_addresses 0x00, 0x00, 0x00, 0x00, // force_mld_version 0x01, 0x00, 0x00, 0x00, // accept_ra_defrtr 0x01, 0x00, 0x00, 0x00, // accept_ra_pinfo 0x01, 0x00, 0x00, 0x00, // accept_ra_rtr_pref 0x60, 0xea, 0x00, 0x00, // rtr_probe_interval 0x00, 0x00, 0x00, 0x00, // accept_ra_rt_info_max_plen 0x00, 0x00, 0x00, 0x00, // proxy_ndp 0x00, 0x00, 0x00, 0x00, // optimistic_dad 0x00, 0x00, 0x00, 0x00, // accept_source_route 0x00, 0x00, 0x00, 0x00, // mc_forwarding 0x00, 0x00, 0x00, 0x00, // disable_ipv6 0xff, 0xff, 0xff, 0xff, // accept_dad 0x00, 0x00, 0x00, 0x00, // force_tllao 0x00, 0x00, 0x00, 0x00, // ndisc_notify 0x10, 0x27, 0x00, 0x00, // mldv1_unsolicited_report_interval 0xe8, 0x03, 0x00, 0x00, // mldv2_unsolicited_report_interval 0x01, 0x00, 0x00, 0x00, // suppress_frag_ndisc 0x00, 0x00, 0x00, 0x00, // accept_ra_from_local 0x00, 0x00, 0x00, 0x00, // use_optimistic 0x01, 0x00, 0x00, 0x00, // accept_ra_mtu 0x00, 0x00, 0x00, 0x00, // stable_secret 0x00, 0x00, 0x00, 0x00, // use_oif_addrs_only 0x01, 0x00, 0x00, 0x00, // accept_ra_min_hop_limit 0x00, 0x00, 0x00, 0x00, // ignore_routes_with_linkdown 0x00, 0x00, 0x00, 0x00, // drop_unicast_in_l2_multicast 0x00, 0x00, 0x00, 0x00, // drop_unsolicited_na 0x00, 0x00, 0x00, 0x00, // keep_addr_on_down 0x80, 0xee, 0x36, 0x00, // rtr_solicit_max_interval 0x00, 0x00, 0x00, 0x00, // seg6_enabled 0x00, 0x00, 0x00, 0x00, // seg6_require_hmac 0x01, 0x00, 0x00, 0x00, // enhanced_dad 0x00, 0x00, 0x00, 0x00, // addr_gen_mode 0x00, 0x00, 0x00, 0x00, // disable_policy 0x00, 0x00, 0x00, 0x00, // accept_ra_rt_info_min_plen 0x00, 0x00, 0x00, 0x00, // ndisc_tclass // IFLA_INET6_STATS (L=292, T=3) 0x24, 0x01, 0x03, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1 num 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2 in_pkts 0xa4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 3 in_octets 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 4 in_delivers 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 5 out_forw_datagrams 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 6 out_pkts 0xa4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 7 out_octets 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8 in_hdr_errors 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9 in_too_big_errors 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 10 in_no_routes (40 bytes) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 11 in_addr_errors 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 12 in_unknown_protos 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 13 in_truncated_pkts 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 14 in_discards 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 15 out_discards 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 16 out_no_routes 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 17 reasm_timeout 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 18 reasm_reqds 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 19 reasm_oks 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20 reasm_fails (80 bytes) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 21 frag_oks 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 22 frag_fails 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 23 frag_creates 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 24 in_mcast_pkts 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 25 out_mcast_pkts 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 26 in_bcast_pkts 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 27 out_bcast_pkts 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 28 in_mcast_octets 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 29 out_mcast_octets 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 30 in_bcast_octets (120 bytes) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 31 out_bcast_octets 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 32 in_csum_errors 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 33 in_no_ect_pkts 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 34 in_ect1_pkts 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 35 in_ect0_pkts 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 36 in_ce_pkts // IFLA_INET6_ICMP6STATS (L=52, T=6) 0x34, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // num 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // in_msgs 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // in_errors 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // out_msgs 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // out_errors 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // csum_errors // IFLA_INET6_TOKEN (L=20, T=7) 0x14, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IFLA_INET6_ADDR_GEN_MODE (L=5, T=8) 0x05, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00]; lazy_static! { static ref BUFFER: NlaBuffer<&'static [u8]> = NlaBuffer::new_checked(&BYTES[..]).unwrap(); } fn get_nlas() -> impl Iterator, DecodeError>> { NlasIterator::new(&*BUFFER.value()) } fn get_byte_buffer(nla: &dyn Emitable) -> Vec { let mut buf = vec![0u8; nla.buffer_len()]; nla.emit(&mut buf); buf } lazy_static! { static ref PARSED_AF_INET6: AfSpecInet = AfSpecInet::Inet6(vec![ Inet6::Flags(2147483648), Inet6::CacheInfo(get_byte_buffer(&Inet6CacheInfo { max_reasm_len: 65535, tstamp: 175, reachable_time: 25730, retrans_time: 1000, })), Inet6::DevConf(get_byte_buffer(&Inet6DevConf { forwarding: 0, hoplimit: 64, mtu6: 65536, accept_ra: 1, accept_redirects: 1, autoconf: 1, dad_transmits: 1, rtr_solicits: -1, rtr_solicit_interval: 4000, rtr_solicit_delay: 1000, use_tempaddr: -1, temp_valid_lft: 604800, temp_prefered_lft: 86400, regen_max_retry: 3, max_desync_factor: 600, max_addresses: 16, force_mld_version: 0, accept_ra_defrtr: 1, accept_ra_pinfo: 1, accept_ra_rtr_pref: 1, rtr_probe_interval: 60000, accept_ra_rt_info_max_plen: 0, proxy_ndp: 0, optimistic_dad: 0, accept_source_route: 0, mc_forwarding: 0, disable_ipv6: 0, accept_dad: -1, force_tllao: 0, ndisc_notify: 0, mldv1_unsolicited_report_interval: 10000, mldv2_unsolicited_report_interval: 1000, suppress_frag_ndisc: 1, accept_ra_from_local: 0, use_optimistic: 0, accept_ra_mtu: 1, stable_secret: 0, use_oif_addrs_only: 0, accept_ra_min_hop_limit: 1, ignore_routes_with_linkdown: 0, drop_unicast_in_l2_multicast: 0, drop_unsolicited_na: 0, keep_addr_on_down: 0, rtr_solicit_max_interval: 3600000, seg6_enabled: 0, seg6_require_hmac: 0, enhanced_dad: 1, addr_gen_mode: 0, disable_policy: 0, accept_ra_rt_info_min_plen: 0, ndisc_tclass: 0, })), Inet6::Stats(get_byte_buffer(&Inet6Stats { num: 36, in_pkts: 6, in_octets: 420, in_delivers: 6, out_forw_datagrams: 0, out_pkts: 6, out_octets: 420, in_hdr_errors: 0, in_too_big_errors: 0, in_no_routes: 2, in_addr_errors: 0, in_unknown_protos: 0, in_truncated_pkts: 0, in_discards: 0, out_discards: 0, out_no_routes: 0, reasm_timeout: 0, reasm_reqds: 0, reasm_oks: 0, reasm_fails: 0, frag_oks: 0, frag_fails: 0, frag_creates: 0, in_mcast_pkts: 0, out_mcast_pkts: 0, in_bcast_pkts: 0, out_bcast_pkts: 0, in_mcast_octets: 0, out_mcast_octets: 0, in_bcast_octets: 0, out_bcast_octets: 0, in_csum_errors: 0, in_no_ect_pkts: 6, in_ect1_pkts: 0, in_ect0_pkts: 0, in_ce_pkts: 0, })), Inet6::IcmpStats(get_byte_buffer(&Icmp6Stats { num: 6, in_msgs: 0, in_errors: 0, out_msgs: 0, out_errors: 0, csum_errors: 0, })), Inet6::Token([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), Inet6::AddrGenMode(0), ]); } lazy_static! { static ref PARSED_AF_INET: AfSpecInet = AfSpecInet::Inet(vec![Inet::DevConf(get_byte_buffer(&InetDevConf { forwarding: 1, mc_forwarding: 0, proxy_arp: 0, accept_redirects: 1, secure_redirects: 1, send_redirects: 1, shared_media: 1, rp_filter: 0, accept_source_route: 1, bootp_relay: 0, log_martians: 0, tag: 0, arpfilter: 0, medium_id: 0, noxfrm: 1, nopolicy: 1, force_igmp_version: 0, arp_announce: 0, arp_ignore: 0, promote_secondaries: 0, arp_accept: 0, arp_notify: 0, accept_local: 0, src_vmark: 0, proxy_arp_pvlan: 0, route_localnet: 0, igmpv2_unsolicited_report_interval: 10000, igmpv3_unsolicited_report_interval: 1000, ignore_routes_with_linkdown: 0, drop_unicast_in_l2_multicast: 0, drop_gratuitous_arp: 0, }))]); } #[test] fn af_spec_header() { assert_eq!(BUFFER.length(), 748); assert_eq!(BUFFER.kind(), IFLA_AF_SPEC as u16); } #[test] fn parse_af_inet() { let mut nlas = get_nlas(); // take the first nla let inet_buf = nlas.next().unwrap().unwrap(); // buffer checks assert_eq!(inet_buf.length(), 132); assert_eq!(inet_buf.kind(), AF_INET); assert_eq!(inet_buf.value().len(), 128); // parsing check let parsed = AfSpecInet::parse(&inet_buf).unwrap(); assert_eq!(parsed, *PARSED_AF_INET); } #[test] fn emit_af_inet() { let mut bytes = vec![0xff; 132]; // Note: the value is a Vec of nlas, so the padding is automatically added for each nla. assert_eq!(PARSED_AF_INET.value_len(), 128); assert_eq!(PARSED_AF_INET.buffer_len(), 128 + 4); PARSED_AF_INET.emit(&mut bytes[..]); let buf = NlaBuffer::new_checked(&bytes[..]).unwrap(); let mut nlas = get_nlas(); let expected_buf = nlas.next().unwrap().unwrap(); assert_eq!(expected_buf.kind(), buf.kind()); assert_eq!(expected_buf.length(), buf.length()); assert_eq!(expected_buf.value(), buf.value()); } #[test] fn emit_af_inet6() { let mut bytes = vec![0xff; 612]; // Note: the value is a Vec of nlas, so the padding is automatically added for each nla. assert_eq!(PARSED_AF_INET6.value_len(), 608); assert_eq!(PARSED_AF_INET6.buffer_len(), 608 + 4); PARSED_AF_INET6.emit(&mut bytes[..]); let buf = NlaBuffer::new_checked(&bytes[..]).unwrap(); let mut nlas = get_nlas(); let _ = nlas.next(); let expected_buf = nlas.next().unwrap().unwrap(); assert_eq!(expected_buf.kind(), buf.kind()); assert_eq!(expected_buf.length(), buf.length()); assert_eq!(expected_buf.value(), buf.value()); } #[test] fn parse_af_inet6() { let mut nlas = get_nlas(); // take the first nla let _ = nlas.next().unwrap(); let inet6_buf = nlas.next().unwrap().unwrap(); assert_eq!(inet6_buf.length(), 612); assert_eq!(inet6_buf.kind(), AF_INET6); assert_eq!(inet6_buf.value().len(), 608); let parsed = AfSpecInet::parse(&inet6_buf).unwrap(); assert_eq!(parsed, *PARSED_AF_INET6); // Normally this is the end of the nla iterator assert!(nlas.next().is_none()); } ================================================ FILE: netlink-packet-route/src/rtnl/message.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ constants::*, traits::{Emitable, ParseableParametrized}, AddressMessage, DecodeError, LinkMessage, NeighbourMessage, NeighbourTableMessage, NetlinkDeserializable, NetlinkHeader, NetlinkPayload, NetlinkSerializable, NsidMessage, RouteMessage, RtnlMessageBuffer, RuleMessage, TcMessage, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum RtnlMessage { NewLink(LinkMessage), DelLink(LinkMessage), GetLink(LinkMessage), SetLink(LinkMessage), NewLinkProp(LinkMessage), DelLinkProp(LinkMessage), NewAddress(AddressMessage), DelAddress(AddressMessage), GetAddress(AddressMessage), NewNeighbour(NeighbourMessage), GetNeighbour(NeighbourMessage), DelNeighbour(NeighbourMessage), NewNeighbourTable(NeighbourTableMessage), GetNeighbourTable(NeighbourTableMessage), SetNeighbourTable(NeighbourTableMessage), NewRoute(RouteMessage), DelRoute(RouteMessage), GetRoute(RouteMessage), NewQueueDiscipline(TcMessage), DelQueueDiscipline(TcMessage), GetQueueDiscipline(TcMessage), NewTrafficClass(TcMessage), DelTrafficClass(TcMessage), GetTrafficClass(TcMessage), NewTrafficFilter(TcMessage), DelTrafficFilter(TcMessage), GetTrafficFilter(TcMessage), NewTrafficChain(TcMessage), DelTrafficChain(TcMessage), GetTrafficChain(TcMessage), NewNsId(NsidMessage), DelNsId(NsidMessage), GetNsId(NsidMessage), NewRule(RuleMessage), DelRule(RuleMessage), GetRule(RuleMessage), } impl RtnlMessage { pub fn is_new_link(&self) -> bool { matches!(self, RtnlMessage::NewLink(_)) } pub fn is_del_link(&self) -> bool { matches!(self, RtnlMessage::DelLink(_)) } pub fn is_get_link(&self) -> bool { matches!(self, RtnlMessage::GetLink(_)) } pub fn is_set_link(&self) -> bool { matches!(self, RtnlMessage::SetLink(_)) } pub fn is_new_address(&self) -> bool { matches!(self, RtnlMessage::NewAddress(_)) } pub fn is_del_address(&self) -> bool { matches!(self, RtnlMessage::DelAddress(_)) } pub fn is_get_address(&self) -> bool { matches!(self, RtnlMessage::GetAddress(_)) } pub fn is_get_neighbour(&self) -> bool { matches!(self, RtnlMessage::GetNeighbour(_)) } pub fn is_new_route(&self) -> bool { matches!(self, RtnlMessage::NewRoute(_)) } pub fn is_new_neighbour(&self) -> bool { matches!(self, RtnlMessage::NewNeighbour(_)) } pub fn is_get_route(&self) -> bool { matches!(self, RtnlMessage::GetRoute(_)) } pub fn is_del_neighbour(&self) -> bool { matches!(self, RtnlMessage::DelNeighbour(_)) } pub fn is_new_neighbour_table(&self) -> bool { matches!(self, RtnlMessage::NewNeighbourTable(_)) } pub fn is_get_neighbour_table(&self) -> bool { matches!(self, RtnlMessage::GetNeighbourTable(_)) } pub fn is_set_neighbour_table(&self) -> bool { matches!(self, RtnlMessage::SetNeighbourTable(_)) } pub fn is_del_route(&self) -> bool { matches!(self, RtnlMessage::DelRoute(_)) } pub fn is_new_qdisc(&self) -> bool { matches!(self, RtnlMessage::NewQueueDiscipline(_)) } pub fn is_del_qdisc(&self) -> bool { matches!(self, RtnlMessage::DelQueueDiscipline(_)) } pub fn is_get_qdisc(&self) -> bool { matches!(self, RtnlMessage::GetQueueDiscipline(_)) } pub fn is_new_class(&self) -> bool { matches!(self, RtnlMessage::NewTrafficClass(_)) } pub fn is_del_class(&self) -> bool { matches!(self, RtnlMessage::DelTrafficClass(_)) } pub fn is_get_class(&self) -> bool { matches!(self, RtnlMessage::GetTrafficClass(_)) } pub fn is_new_filter(&self) -> bool { matches!(self, RtnlMessage::NewTrafficFilter(_)) } pub fn is_del_filter(&self) -> bool { matches!(self, RtnlMessage::DelTrafficFilter(_)) } pub fn is_get_filter(&self) -> bool { matches!(self, RtnlMessage::GetTrafficFilter(_)) } pub fn is_new_chain(&self) -> bool { matches!(self, RtnlMessage::NewTrafficChain(_)) } pub fn is_del_chain(&self) -> bool { matches!(self, RtnlMessage::DelTrafficChain(_)) } pub fn is_get_chain(&self) -> bool { matches!(self, RtnlMessage::GetTrafficChain(_)) } pub fn is_new_nsid(&self) -> bool { matches!(self, RtnlMessage::NewNsId(_)) } pub fn is_get_nsid(&self) -> bool { matches!(self, RtnlMessage::GetNsId(_)) } pub fn is_del_nsid(&self) -> bool { matches!(self, RtnlMessage::DelNsId(_)) } pub fn is_get_rule(&self) -> bool { matches!(self, RtnlMessage::GetRule(_)) } pub fn is_new_rule(&self) -> bool { matches!(self, RtnlMessage::NewRule(_)) } pub fn is_del_rule(&self) -> bool { matches!(self, RtnlMessage::DelRule(_)) } pub fn message_type(&self) -> u16 { use self::RtnlMessage::*; match self { NewLink(_) => RTM_NEWLINK, DelLink(_) => RTM_DELLINK, GetLink(_) => RTM_GETLINK, SetLink(_) => RTM_SETLINK, NewLinkProp(_) => RTM_NEWLINKPROP, DelLinkProp(_) => RTM_DELLINKPROP, NewAddress(_) => RTM_NEWADDR, DelAddress(_) => RTM_DELADDR, GetAddress(_) => RTM_GETADDR, GetNeighbour(_) => RTM_GETNEIGH, NewNeighbour(_) => RTM_NEWNEIGH, DelNeighbour(_) => RTM_DELNEIGH, GetNeighbourTable(_) => RTM_GETNEIGHTBL, NewNeighbourTable(_) => RTM_NEWNEIGHTBL, SetNeighbourTable(_) => RTM_SETNEIGHTBL, NewRoute(_) => RTM_NEWROUTE, DelRoute(_) => RTM_DELROUTE, GetRoute(_) => RTM_GETROUTE, NewQueueDiscipline(_) => RTM_NEWQDISC, DelQueueDiscipline(_) => RTM_DELQDISC, GetQueueDiscipline(_) => RTM_GETQDISC, NewTrafficClass(_) => RTM_NEWTCLASS, DelTrafficClass(_) => RTM_DELTCLASS, GetTrafficClass(_) => RTM_GETTCLASS, NewTrafficFilter(_) => RTM_NEWTFILTER, DelTrafficFilter(_) => RTM_DELTFILTER, GetTrafficFilter(_) => RTM_GETTFILTER, NewTrafficChain(_) => RTM_NEWCHAIN, DelTrafficChain(_) => RTM_DELCHAIN, GetTrafficChain(_) => RTM_GETCHAIN, GetNsId(_) => RTM_GETNSID, NewNsId(_) => RTM_NEWNSID, DelNsId(_) => RTM_DELNSID, GetRule(_) => RTM_GETRULE, NewRule(_) => RTM_NEWRULE, DelRule(_) => RTM_DELRULE, } } } impl Emitable for RtnlMessage { #[rustfmt::skip] fn buffer_len(&self) -> usize { use self::RtnlMessage::*; match self { | NewLink(ref msg) | DelLink(ref msg) | GetLink(ref msg) | SetLink(ref msg) | NewLinkProp(ref msg) | DelLinkProp(ref msg) => msg.buffer_len(), | NewAddress(ref msg) | DelAddress(ref msg) | GetAddress(ref msg) => msg.buffer_len(), | NewNeighbour(ref msg) | GetNeighbour(ref msg) | DelNeighbour(ref msg) => msg.buffer_len(), | NewNeighbourTable(ref msg) | GetNeighbourTable(ref msg) | SetNeighbourTable(ref msg) => msg.buffer_len(), | NewRoute(ref msg) | DelRoute(ref msg) | GetRoute(ref msg) => msg.buffer_len(), | NewQueueDiscipline(ref msg) | DelQueueDiscipline(ref msg) | GetQueueDiscipline(ref msg) | NewTrafficClass(ref msg) | DelTrafficClass(ref msg) | GetTrafficClass(ref msg) | NewTrafficFilter(ref msg) | DelTrafficFilter(ref msg) | GetTrafficFilter(ref msg) | NewTrafficChain(ref msg) | DelTrafficChain(ref msg) | GetTrafficChain(ref msg) => msg.buffer_len(), | NewNsId(ref msg) | DelNsId(ref msg) | GetNsId(ref msg) => msg.buffer_len(), | NewRule(ref msg) | DelRule(ref msg) | GetRule(ref msg) => msg.buffer_len() } } #[rustfmt::skip] fn emit(&self, buffer: &mut [u8]) { use self::RtnlMessage::*; match self { | NewLink(ref msg) | DelLink(ref msg) | GetLink(ref msg) | SetLink(ref msg) | NewLinkProp(ref msg) | DelLinkProp(ref msg) => msg.emit(buffer), | NewAddress(ref msg) | DelAddress(ref msg) | GetAddress(ref msg) => msg.emit(buffer), | GetNeighbour(ref msg) | NewNeighbour(ref msg) | DelNeighbour(ref msg) => msg.emit(buffer), | GetNeighbourTable(ref msg) | NewNeighbourTable(ref msg) | SetNeighbourTable(ref msg) => msg.emit(buffer), | NewRoute(ref msg) | DelRoute(ref msg) | GetRoute(ref msg) => msg.emit(buffer), | NewQueueDiscipline(ref msg) | DelQueueDiscipline(ref msg) | GetQueueDiscipline(ref msg) | NewTrafficClass(ref msg) | DelTrafficClass(ref msg) | GetTrafficClass(ref msg) | NewTrafficFilter(ref msg) | DelTrafficFilter(ref msg) | GetTrafficFilter(ref msg) | NewTrafficChain(ref msg) | DelTrafficChain(ref msg) | GetTrafficChain(ref msg) => msg.emit(buffer), | NewNsId(ref msg) | DelNsId(ref msg) | GetNsId(ref msg) => msg.emit(buffer), | NewRule(ref msg) | DelRule(ref msg) | GetRule(ref msg) => msg.emit(buffer) } } } impl NetlinkSerializable for RtnlMessage { fn message_type(&self) -> u16 { self.message_type() } fn buffer_len(&self) -> usize { ::buffer_len(self) } fn serialize(&self, buffer: &mut [u8]) { self.emit(buffer) } } impl NetlinkDeserializable for RtnlMessage { type Error = DecodeError; fn deserialize(header: &NetlinkHeader, payload: &[u8]) -> Result { let buf = RtnlMessageBuffer::new(payload); match RtnlMessage::parse_with_param(&buf, header.message_type) { Err(e) => Err(e), Ok(message) => Ok(message), } } } impl From for NetlinkPayload { fn from(message: RtnlMessage) -> Self { NetlinkPayload::InnerMessage(message) } } ================================================ FILE: netlink-packet-route/src/rtnl/mod.rs ================================================ // SPDX-License-Identifier: MIT pub mod address; pub use address::{AddressHeader, AddressMessage, AddressMessageBuffer, ADDRESS_HEADER_LEN}; pub mod link; pub use link::{LinkHeader, LinkMessage, LinkMessageBuffer, LINK_HEADER_LEN}; pub mod neighbour; pub use neighbour::{ NeighbourHeader, NeighbourMessage, NeighbourMessageBuffer, NEIGHBOUR_HEADER_LEN, }; pub mod neighbour_table; pub use neighbour_table::{ NeighbourTableHeader, NeighbourTableMessage, NeighbourTableMessageBuffer, NEIGHBOUR_TABLE_HEADER_LEN, }; pub mod nsid; pub use nsid::{NsidHeader, NsidMessage, NsidMessageBuffer, NSID_HEADER_LEN}; pub mod route; pub use route::{RouteFlags, RouteHeader, RouteMessage, RouteMessageBuffer, ROUTE_HEADER_LEN}; pub mod rule; pub use rule::{RuleHeader, RuleMessage, RuleMessageBuffer, RULE_HEADER_LEN}; pub mod tc; pub use tc::{TcHeader, TcMessage, TcMessageBuffer, TC_HEADER_LEN}; pub mod constants; pub use self::constants::*; mod buffer; pub use self::buffer::*; mod message; pub use self::message::*; pub mod nlas { pub use super::{ address::nlas as address, link::nlas as link, neighbour::nlas as neighbour, neighbour_table::nlas as neighbour_table, nsid::nlas as nsid, route::nlas as route, rule::nlas as rule, tc::nlas as tc, }; pub use crate::utils::nla::*; } #[cfg(test)] mod test; ================================================ FILE: netlink-packet-route/src/rtnl/neighbour/buffer.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ nlas::{NlaBuffer, NlasIterator}, DecodeError, }; pub const NEIGHBOUR_HEADER_LEN: usize = 12; buffer!(NeighbourMessageBuffer(NEIGHBOUR_HEADER_LEN) { family: (u8, 0), ifindex: (u32, 4..8), state: (u16, 8..10), flags: (u8, 10), ntype: (u8, 11), payload:(slice, NEIGHBOUR_HEADER_LEN..), }); impl<'a, T: AsRef<[u8]> + ?Sized> NeighbourMessageBuffer<&'a T> { pub fn nlas(&self) -> impl Iterator, DecodeError>> { NlasIterator::new(self.payload()) } } ================================================ FILE: netlink-packet-route/src/rtnl/neighbour/header.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, NeighbourMessageBuffer, NEIGHBOUR_HEADER_LEN, }; /// Neighbour headers have the following structure: /// /// ```no_rust /// 0 8 16 24 32 /// +----------------+----------------+----------------+----------------+ /// | family | padding | /// +----------------+----------------+----------------+----------------+ /// | link index | /// +----------------+----------------+----------------+----------------+ /// | state | flags | ntype | /// +----------------+----------------+----------------+----------------+ /// ``` /// /// `NeighbourHeader` exposes all these fields. #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct NeighbourHeader { pub family: u8, pub ifindex: u32, /// Neighbour cache entry state. It should be set to one of the /// `NUD_*` constants pub state: u16, /// Neighbour cache entry flags. It should be set to a combination /// of the `NTF_*` constants pub flags: u8, /// Neighbour cache entry type. It should be set to one of the /// `NDA_*` constants. pub ntype: u8, } impl> Parseable> for NeighbourHeader { fn parse(buf: &NeighbourMessageBuffer) -> Result { Ok(Self { family: buf.family(), ifindex: buf.ifindex(), state: buf.state(), flags: buf.flags(), ntype: buf.ntype(), }) } } impl Emitable for NeighbourHeader { fn buffer_len(&self) -> usize { NEIGHBOUR_HEADER_LEN } fn emit(&self, buffer: &mut [u8]) { let mut packet = NeighbourMessageBuffer::new(buffer); packet.set_family(self.family); packet.set_ifindex(self.ifindex); packet.set_state(self.state); packet.set_flags(self.flags); packet.set_ntype(self.ntype); } } ================================================ FILE: netlink-packet-route/src/rtnl/neighbour/message.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use crate::{ nlas::neighbour::Nla, traits::{Emitable, Parseable}, DecodeError, NeighbourHeader, NeighbourMessageBuffer, }; #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct NeighbourMessage { pub header: NeighbourHeader, pub nlas: Vec, } impl Emitable for NeighbourMessage { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); self.nlas .as_slice() .emit(&mut buffer[self.header.buffer_len()..]); } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for NeighbourMessage { fn parse(buf: &NeighbourMessageBuffer<&'a T>) -> Result { Ok(NeighbourMessage { header: NeighbourHeader::parse(buf) .context("failed to parse neighbour message header")?, nlas: Vec::::parse(buf).context("failed to parse neighbour message NLAs")?, }) } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for Vec { fn parse(buf: &NeighbourMessageBuffer<&'a T>) -> Result { let mut nlas = vec![]; for nla_buf in buf.nlas() { nlas.push(Nla::parse(&nla_buf?)?); } Ok(nlas) } } #[cfg(test)] mod test { use crate::{ constants::*, traits::Emitable, NeighbourHeader, NeighbourMessage, NeighbourMessageBuffer, }; // 0020 0a 00 00 00 02 00 00 00 02 00 80 01 14 00 01 00 // 0030 2a 02 80 10 66 d5 00 00 f6 90 ea ff fe 00 2d 83 // 0040 0a 00 02 00 f4 90 ea 00 2d 83 00 00 08 00 04 00 // 0050 01 00 00 00 14 00 03 00 00 00 00 00 00 00 00 00 // 0060 00 00 00 00 02 00 00 00 #[rustfmt::skip] static HEADER: [u8; 12] = [ 0x0a, // interface family (inet6) 0xff, 0xff, 0xff, // padding 0x01, 0x00, 0x00, 0x00, // interface index = 1 0x02, 0x00, // state NUD_REACHABLE 0x80, // flags NTF_PROXY 0x01 // ntype // nlas // will add some once I've got them parsed out. ]; #[test] fn packet_header_read() { let packet = NeighbourMessageBuffer::new(&HEADER[0..12]); assert_eq!(packet.family(), AF_INET6 as u8); assert_eq!(packet.ifindex(), 1); assert_eq!(packet.state(), NUD_REACHABLE); assert_eq!(packet.flags(), NTF_ROUTER); assert_eq!(packet.ntype(), NDA_DST as u8); } #[test] fn packet_header_build() { let mut buf = vec![0xff; 12]; { let mut packet = NeighbourMessageBuffer::new(&mut buf); packet.set_family(AF_INET6 as u8); packet.set_ifindex(1); packet.set_state(NUD_REACHABLE); packet.set_flags(NTF_ROUTER); packet.set_ntype(NDA_DST as u8); } assert_eq!(&buf[..], &HEADER[0..12]); } #[test] fn emit() { let header = NeighbourHeader { family: AF_INET6 as u8, ifindex: 1, state: NUD_REACHABLE, flags: NTF_ROUTER, ntype: NDA_DST as u8, }; let nlas = vec![]; let packet = NeighbourMessage { header, nlas }; let mut buf = vec![0; 12]; assert_eq!(packet.buffer_len(), 12); packet.emit(&mut buf[..]); } } ================================================ FILE: netlink-packet-route/src/rtnl/neighbour/mod.rs ================================================ // SPDX-License-Identifier: MIT mod buffer; mod header; mod message; pub mod nlas; pub use self::{buffer::*, header::*, message::*, nlas::*}; ================================================ FILE: netlink-packet-route/src/rtnl/neighbour/nlas/cache_info.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct CacheInfo { pub confirmed: u32, pub used: u32, pub updated: u32, pub refcnt: u32, } pub const NEIGHBOUR_CACHE_INFO_LEN: usize = 16; buffer!(CacheInfoBuffer(NEIGHBOUR_CACHE_INFO_LEN) { confirmed: (u32, 0..4), used: (u32, 4..8), updated: (u32, 8..12), refcnt: (u32, 12..16), }); impl> Parseable> for CacheInfo { fn parse(buf: &CacheInfoBuffer) -> Result { Ok(Self { confirmed: buf.confirmed(), used: buf.used(), updated: buf.updated(), refcnt: buf.refcnt(), }) } } impl Emitable for CacheInfo { fn buffer_len(&self) -> usize { NEIGHBOUR_CACHE_INFO_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = CacheInfoBuffer::new(buffer); buffer.set_confirmed(self.confirmed); buffer.set_used(self.used); buffer.set_updated(self.updated); buffer.set_refcnt(self.refcnt); } } ================================================ FILE: netlink-packet-route/src/rtnl/neighbour/nlas/mod.rs ================================================ // SPDX-License-Identifier: MIT mod cache_info; pub use self::cache_info::*; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use crate::{ constants::*, nlas::{self, DefaultNla, NlaBuffer}, parsers::{parse_u16, parse_u32}, traits::Parseable, DecodeError, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Nla { Unspec(Vec), Destination(Vec), LinkLocalAddress(Vec), CacheInfo(Vec), Probes(Vec), Vlan(u16), Port(Vec), Vni(u32), IfIndex(u32), Master(Vec), LinkNetNsId(Vec), SourceVni(u32), Other(DefaultNla), } impl nlas::Nla for Nla { #[rustfmt::skip] fn value_len(&self) -> usize { use self::Nla::*; match *self { Unspec(ref bytes) | Destination(ref bytes) | LinkLocalAddress(ref bytes) | Probes(ref bytes) | Port(ref bytes) | Master(ref bytes) | CacheInfo(ref bytes) | LinkNetNsId(ref bytes) => bytes.len(), Vlan(_) => 2, Vni(_) | IfIndex(_) | SourceVni(_) => 4, Other(ref attr) => attr.value_len(), } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::Nla::*; match *self { Unspec(ref bytes) | Destination(ref bytes) | LinkLocalAddress(ref bytes) | Probes(ref bytes) | Port(ref bytes) | Master(ref bytes) | CacheInfo(ref bytes) | LinkNetNsId(ref bytes) => buffer.copy_from_slice(bytes.as_slice()), Vlan(ref value) => NativeEndian::write_u16(buffer, *value), Vni(ref value) | IfIndex(ref value) | SourceVni(ref value) => NativeEndian::write_u32(buffer, *value), Other(ref attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Nla::*; match *self { Unspec(_) => NDA_UNSPEC, Destination(_) => NDA_DST, LinkLocalAddress(_) => NDA_LLADDR, CacheInfo(_) => NDA_CACHEINFO, Probes(_) => NDA_PROBES, Vlan(_) => NDA_VLAN, Port(_) => NDA_PORT, Vni(_) => NDA_VNI, IfIndex(_) => NDA_IFINDEX, Master(_) => NDA_MASTER, LinkNetNsId(_) => NDA_LINK_NETNSID, SourceVni(_) => NDA_SRC_VNI, Other(ref nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nla { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::Nla::*; let payload = buf.value(); Ok(match buf.kind() { NDA_UNSPEC => Unspec(payload.to_vec()), NDA_DST => Destination(payload.to_vec()), NDA_LLADDR => LinkLocalAddress(payload.to_vec()), NDA_CACHEINFO => CacheInfo(payload.to_vec()), NDA_PROBES => Probes(payload.to_vec()), NDA_VLAN => Vlan(parse_u16(payload)?), NDA_PORT => Port(payload.to_vec()), NDA_VNI => Vni(parse_u32(payload)?), NDA_IFINDEX => IfIndex(parse_u32(payload)?), NDA_MASTER => Master(payload.to_vec()), NDA_LINK_NETNSID => LinkNetNsId(payload.to_vec()), NDA_SRC_VNI => SourceVni(parse_u32(payload)?), _ => Other(DefaultNla::parse(buf).context("invalid link NLA value (unknown type)")?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/neighbour_table/buffer.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ nlas::{NlaBuffer, NlasIterator}, DecodeError, }; pub const NEIGHBOUR_TABLE_HEADER_LEN: usize = 4; buffer!(NeighbourTableMessageBuffer(NEIGHBOUR_TABLE_HEADER_LEN) { family: (u8, 0), payload: (slice, NEIGHBOUR_TABLE_HEADER_LEN..), }); impl<'a, T: AsRef<[u8]> + ?Sized> NeighbourTableMessageBuffer<&'a T> { pub fn nlas(&self) -> impl Iterator, DecodeError>> { NlasIterator::new(self.payload()) } } ================================================ FILE: netlink-packet-route/src/rtnl/neighbour_table/header.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; use super::buffer::{NeighbourTableMessageBuffer, NEIGHBOUR_TABLE_HEADER_LEN}; #[derive(Debug, PartialEq, Eq, Clone)] pub struct NeighbourTableHeader { pub family: u8, } impl> Parseable> for NeighbourTableHeader { fn parse(buf: &NeighbourTableMessageBuffer) -> Result { Ok(Self { family: buf.family(), }) } } impl Emitable for NeighbourTableHeader { fn buffer_len(&self) -> usize { NEIGHBOUR_TABLE_HEADER_LEN } fn emit(&self, buffer: &mut [u8]) { let mut packet = NeighbourTableMessageBuffer::new(buffer); packet.set_family(self.family); } } ================================================ FILE: netlink-packet-route/src/rtnl/neighbour_table/message.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ nlas::neighbour_table::Nla, traits::{Emitable, Parseable}, DecodeError, NeighbourTableHeader, NeighbourTableMessageBuffer, }; use anyhow::Context; #[derive(Debug, PartialEq, Eq, Clone)] pub struct NeighbourTableMessage { pub header: NeighbourTableHeader, pub nlas: Vec, } impl Emitable for NeighbourTableMessage { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); self.nlas.as_slice().emit(buffer); } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for NeighbourTableMessage { fn parse(buf: &NeighbourTableMessageBuffer<&'a T>) -> Result { Ok(NeighbourTableMessage { header: NeighbourTableHeader::parse(buf) .context("failed to parse neighbour table message header")?, nlas: Vec::::parse(buf).context("failed to parse neighbour table message NLAs")?, }) } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for Vec { fn parse(buf: &NeighbourTableMessageBuffer<&'a T>) -> Result { let mut nlas = vec![]; for nla_buf in buf.nlas() { nlas.push(Nla::parse(&nla_buf?)?); } Ok(nlas) } } ================================================ FILE: netlink-packet-route/src/rtnl/neighbour_table/mod.rs ================================================ // SPDX-License-Identifier: MIT mod buffer; mod header; mod message; pub mod nlas; pub use self::{buffer::*, header::*, message::*, nlas::*}; ================================================ FILE: netlink-packet-route/src/rtnl/neighbour_table/nlas/config.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct Config { pub key_len: u16, pub entry_size: u16, pub entries: u32, pub last_flush: u32, pub last_rand: u32, pub hash_rand: u32, pub hash_mask: u32, pub hash_chain_gc: u32, pub proxy_qlen: u32, } pub const CONFIG_LEN: usize = 32; buffer!(ConfigBuffer(CONFIG_LEN) { key_len: (u16, 0..2), entry_size: (u16, 2..4), entries: (u32, 4..8), last_flush: (u32, 8..12), last_rand: (u32, 12..16), hash_rand: (u32, 16..20), hash_mask: (u32, 20..24), hash_chain_gc: (u32, 24..28), proxy_qlen: (u32, 28..32), }); impl> Parseable> for Config { fn parse(buf: &ConfigBuffer) -> Result { Ok(Self { key_len: buf.key_len(), entry_size: buf.entry_size(), entries: buf.entries(), last_flush: buf.last_flush(), last_rand: buf.last_rand(), hash_rand: buf.hash_rand(), hash_mask: buf.hash_mask(), hash_chain_gc: buf.hash_chain_gc(), proxy_qlen: buf.proxy_qlen(), }) } } impl Emitable for Config { fn buffer_len(&self) -> usize { CONFIG_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = ConfigBuffer::new(buffer); buffer.set_key_len(self.key_len); buffer.set_entry_size(self.entry_size); buffer.set_entries(self.entries); buffer.set_last_flush(self.last_flush); buffer.set_last_rand(self.last_rand); buffer.set_hash_rand(self.hash_rand); buffer.set_hash_mask(self.hash_mask); buffer.set_hash_chain_gc(self.hash_chain_gc); buffer.set_proxy_qlen(self.proxy_qlen); } } ================================================ FILE: netlink-packet-route/src/rtnl/neighbour_table/nlas/mod.rs ================================================ // SPDX-License-Identifier: MIT mod config; pub use config::*; mod stats; pub use stats::*; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use crate::{ constants::*, nlas::{self, DefaultNla, NlaBuffer}, parsers::{parse_string, parse_u32, parse_u64}, traits::Parseable, DecodeError, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Nla { Unspec(Vec), // FIXME: parse this nla Parms(Vec), Name(String), Threshold1(u32), Threshold2(u32), Threshold3(u32), Config(Vec), Stats(Vec), GcInterval(u64), Other(DefaultNla), } impl nlas::Nla for Nla { #[rustfmt::skip] fn value_len(&self) -> usize { use self::Nla::*; match *self { Unspec(ref bytes) | Parms(ref bytes) | Config(ref bytes) | Stats(ref bytes)=> bytes.len(), // strings: +1 because we need to append a nul byte Name(ref s) => s.len() + 1, Threshold1(_) | Threshold2(_) | Threshold3(_) => 4, GcInterval(_) => 8, Other(ref attr) => attr.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::Nla::*; match *self { Unspec(ref bytes) | Parms(ref bytes) | Config(ref bytes) | Stats(ref bytes) => { buffer.copy_from_slice(bytes.as_slice()) } Name(ref string) => { buffer[..string.len()].copy_from_slice(string.as_bytes()); buffer[string.len()] = 0; } GcInterval(ref value) => NativeEndian::write_u64(buffer, *value), Threshold1(ref value) | Threshold2(ref value) | Threshold3(ref value) => { NativeEndian::write_u32(buffer, *value) } Other(ref attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Nla::*; match *self { Unspec(_) => NDTA_UNSPEC, Name(_) => NDTA_NAME, Config(_) => NDTA_CONFIG, Stats(_) => NDTA_STATS, Parms(_) => NDTA_PARMS, GcInterval(_) => NDTA_GC_INTERVAL, Threshold1(_) => NDTA_THRESH1, Threshold2(_) => NDTA_THRESH2, Threshold3(_) => NDTA_THRESH3, Other(ref attr) => attr.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nla { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::Nla::*; let payload = buf.value(); Ok(match buf.kind() { NDTA_UNSPEC => Unspec(payload.to_vec()), NDTA_NAME => Name(parse_string(payload).context("invalid NDTA_NAME value")?), NDTA_CONFIG => Config(payload.to_vec()), NDTA_STATS => Stats(payload.to_vec()), NDTA_PARMS => Parms(payload.to_vec()), NDTA_GC_INTERVAL => { GcInterval(parse_u64(payload).context("invalid NDTA_GC_INTERVAL value")?) } NDTA_THRESH1 => Threshold1(parse_u32(payload).context("invalid NDTA_THRESH1 value")?), NDTA_THRESH2 => Threshold2(parse_u32(payload).context("invalid NDTA_THRESH2 value")?), NDTA_THRESH3 => Threshold3(parse_u32(payload).context("invalid NDTA_THRESH3 value")?), kind => Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/neighbour_table/nlas/stats.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct Stats { pub allocs: u64, pub destroys: u64, pub hash_grows: u64, pub res_failed: u64, pub lookups: u64, pub hits: u64, pub multicast_probes_received: u64, pub unicast_probes_received: u64, pub periodic_gc_runs: u64, pub forced_gc_runs: u64, } pub const STATS_LEN: usize = 80; buffer!(StatsBuffer(STATS_LEN) { allocs: (u64, 0..8), destroys: (u64, 8..16), hash_grows: (u64, 16..24), res_failed: (u64, 24..32), lookups: (u64, 32..40), hits: (u64, 40..48), multicast_probes_received: (u64, 48..56), unicast_probes_received: (u64, 56..64), periodic_gc_runs: (u64, 64..72), forced_gc_runs: (u64, 72..80), }); impl> Parseable> for Stats { fn parse(buf: &StatsBuffer) -> Result { Ok(Self { allocs: buf.allocs(), destroys: buf.destroys(), hash_grows: buf.hash_grows(), res_failed: buf.res_failed(), lookups: buf.lookups(), hits: buf.hits(), multicast_probes_received: buf.multicast_probes_received(), unicast_probes_received: buf.unicast_probes_received(), periodic_gc_runs: buf.periodic_gc_runs(), forced_gc_runs: buf.forced_gc_runs(), }) } } impl Emitable for Stats { fn buffer_len(&self) -> usize { STATS_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = StatsBuffer::new(buffer); buffer.set_allocs(self.allocs); buffer.set_destroys(self.destroys); buffer.set_hash_grows(self.hash_grows); buffer.set_res_failed(self.res_failed); buffer.set_lookups(self.lookups); buffer.set_hits(self.hits); buffer.set_multicast_probes_received(self.multicast_probes_received); buffer.set_unicast_probes_received(self.unicast_probes_received); buffer.set_periodic_gc_runs(self.periodic_gc_runs); buffer.set_forced_gc_runs(self.forced_gc_runs); } } ================================================ FILE: netlink-packet-route/src/rtnl/nsid/buffer.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ nlas::{NlaBuffer, NlasIterator}, DecodeError, }; pub const NSID_HEADER_LEN: usize = 4; buffer!(NsidMessageBuffer(NSID_HEADER_LEN) { rtgen_family: (u8, 0), payload: (slice, NSID_HEADER_LEN..), }); impl<'a, T: AsRef<[u8]> + ?Sized> NsidMessageBuffer<&'a T> { pub fn nlas(&self) -> impl Iterator, DecodeError>> { NlasIterator::new(self.payload()) } } ================================================ FILE: netlink-packet-route/src/rtnl/nsid/header.rs ================================================ // SPDX-License-Identifier: MIT use super::{NsidMessageBuffer, NSID_HEADER_LEN}; use crate::{ traits::{Emitable, Parseable}, DecodeError, }; #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct NsidHeader { pub rtgen_family: u8, } impl Emitable for NsidHeader { fn buffer_len(&self) -> usize { NSID_HEADER_LEN } fn emit(&self, buffer: &mut [u8]) { let mut packet = NsidMessageBuffer::new(buffer); packet.set_rtgen_family(self.rtgen_family); } } impl> Parseable> for NsidHeader { fn parse(buf: &NsidMessageBuffer) -> Result { Ok(NsidHeader { rtgen_family: buf.rtgen_family(), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/nsid/message.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use crate::{ nlas::nsid::Nla, traits::{Emitable, Parseable}, DecodeError, NsidHeader, NsidMessageBuffer, }; #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct NsidMessage { pub header: NsidHeader, pub nlas: Vec, } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for NsidMessage { fn parse(buf: &NsidMessageBuffer<&'a T>) -> Result { Ok(Self { header: NsidHeader::parse(buf).context("failed to parse nsid message header")?, nlas: Vec::::parse(buf).context("failed to parse nsid message NLAs")?, }) } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for Vec { fn parse(buf: &NsidMessageBuffer<&'a T>) -> Result { let mut nlas = vec![]; for nla_buf in buf.nlas() { nlas.push(Nla::parse(&nla_buf?)?); } Ok(nlas) } } impl Emitable for NsidMessage { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); self.nlas .as_slice() .emit(&mut buffer[self.header.buffer_len()..]); } } #[cfg(test)] mod test { use crate::{ nlas::nsid::Nla, traits::ParseableParametrized, NetlinkBuffer, NsidHeader, NsidMessage, RtnlMessage, RtnlMessageBuffer, NETNSA_NSID_NOT_ASSIGNED, RTM_GETNSID, RTM_NEWNSID, }; #[rustfmt::skip] #[test] fn get_ns_id_request() { let data = vec![ 0x1c, 0x00, 0x00, 0x00, // length = 28 0x5a, 0x00, // message type = 90 = RTM_GETNSID 0x01, 0x00, // flags 0x00, 0x00, 0x00, 0x00, // seq number 0x00, 0x00, 0x00, 0x00, // pid // GETNSID message 0x00, // rtgen family 0x00, 0x00, 0x00, // padding // NLA 0x08, 0x00, // length = 8 0x03, 0x00, // type = 3 (Fd) 0x04, 0x00, 0x00, 0x00 // 4 ]; let expected = RtnlMessage::GetNsId(NsidMessage { header: NsidHeader { rtgen_family: 0 }, nlas: vec![Nla::Fd(4)], }); let actual = RtnlMessage::parse_with_param(&RtnlMessageBuffer::new(&NetlinkBuffer::new(&data).payload()), RTM_GETNSID).unwrap(); assert_eq!(expected, actual); } #[rustfmt::skip] #[test] fn get_ns_id_response() { let data = vec![ 0x1c, 0x00, 0x00, 0x00, // length = 28 0x58, 0x00, // message type = RTM_NEWNSID 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, // seq number 0x76, 0x12, 0x00, 0x00, // pid // NETNSID message 0x00, // rtgen family 0x00, 0x00, 0x00, // padding // NLA 0x08, 0x00, // length 0x01, 0x00, // type = NETNSA_NSID 0xff, 0xff, 0xff, 0xff // -1 ]; let expected = RtnlMessage::NewNsId(NsidMessage { header: NsidHeader { rtgen_family: 0 }, nlas: vec![Nla::Id(NETNSA_NSID_NOT_ASSIGNED)], }); let nl_buffer = NetlinkBuffer::new(&data).payload(); let rtnl_buffer = RtnlMessageBuffer::new(&nl_buffer); let actual = RtnlMessage::parse_with_param(&rtnl_buffer, RTM_NEWNSID).unwrap(); assert_eq!(expected, actual); } } ================================================ FILE: netlink-packet-route/src/rtnl/nsid/mod.rs ================================================ // SPDX-License-Identifier: MIT mod buffer; mod header; mod message; pub mod nlas; pub use self::{buffer::*, header::*, message::*, nlas::*}; ================================================ FILE: netlink-packet-route/src/rtnl/nsid/nlas.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use crate::{ constants::*, nlas::{self, DefaultNla, NlaBuffer}, parsers::{parse_i32, parse_u32}, traits::Parseable, DecodeError, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Nla { Unspec(Vec), Id(i32), Pid(u32), Fd(u32), Other(DefaultNla), } impl nlas::Nla for Nla { fn value_len(&self) -> usize { use self::Nla::*; match *self { Unspec(ref bytes) => bytes.len(), Id(_) | Pid(_) | Fd(_) => 4, Other(ref attr) => attr.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::Nla::*; match *self { Unspec(ref bytes) => buffer.copy_from_slice(bytes.as_slice()), Fd(ref value) | Pid(ref value) => NativeEndian::write_u32(buffer, *value), Id(ref value) => NativeEndian::write_i32(buffer, *value), Other(ref attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Nla::*; match *self { Unspec(_) => NETNSA_NONE, Id(_) => NETNSA_NSID, Pid(_) => NETNSA_PID, Fd(_) => NETNSA_FD, Other(ref attr) => attr.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nla { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::Nla::*; let payload = buf.value(); Ok(match buf.kind() { NETNSA_NONE => Unspec(payload.to_vec()), NETNSA_NSID => Id(parse_i32(payload).context("invalid NETNSA_NSID")?), NETNSA_PID => Pid(parse_u32(payload).context("invalid NETNSA_PID")?), NETNSA_FD => Fd(parse_u32(payload).context("invalid NETNSA_FD")?), kind => Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/route/buffer.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ nlas::{NlaBuffer, NlasIterator}, DecodeError, }; pub const ROUTE_HEADER_LEN: usize = 12; buffer!(RouteMessageBuffer(ROUTE_HEADER_LEN) { address_family: (u8, 0), destination_prefix_length: (u8, 1), source_prefix_length: (u8, 2), tos: (u8, 3), table: (u8, 4), protocol: (u8, 5), scope: (u8, 6), kind: (u8, 7), flags: (u32, 8..ROUTE_HEADER_LEN), payload: (slice, ROUTE_HEADER_LEN..), }); impl<'a, T: AsRef<[u8]> + ?Sized> RouteMessageBuffer<&'a T> { pub fn nlas(&self) -> impl Iterator, DecodeError>> { NlasIterator::new(self.payload()) } } ================================================ FILE: netlink-packet-route/src/rtnl/route/header.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ constants::*, traits::{Emitable, Parseable}, DecodeError, RouteMessageBuffer, ROUTE_HEADER_LEN, }; bitflags! { /// Flags that can be set in a `RTM_GETROUTE` ([`RtnlMessage::GetRoute`]) message. pub struct RouteFlags: u32 { /// If the route changes, notify the user via rtnetlink const RTM_F_NOTIFY = RTM_F_NOTIFY; /// This route is cloned. Cloned routes are routes coming from the cache instead of the /// FIB. For IPv4, the cache was removed in Linux 3.6 (see [IPv4 route lookup on Linux] for /// more information about IPv4 routing) /// /// [IPv4 route lookup on Linux]: https://vincent.bernat.ch/en/blog/2017-ipv4-route-lookup-linux const RTM_F_CLONED = RTM_F_CLONED; /// Multipath equalizer (not yet implemented) const RTM_F_EQUALIZE = RTM_F_EQUALIZE; /// Prefix addresses const RTM_F_PREFIX = RTM_F_PREFIX; /// Show the table from which the lookup result comes. Note that before commit /// `c36ba6603a11`, Linux would always hardcode [`RouteMessageHeader.table`] (known as /// `rtmsg.rtm_table` in the kernel) to `RT_TABLE_MAIN`. /// /// [`RouteMessageHeader.table`]: ../struct.RouteMessageHeader.html#structfield.table const RTM_F_LOOKUP_TABLE = RTM_F_LOOKUP_TABLE; /// Return the full FIB lookup match (see commit `b61798130f1be5bff08712308126c2d7ebe390ef`) const RTM_F_FIB_MATCH = RTM_F_FIB_MATCH; } } impl Default for RouteFlags { fn default() -> Self { Self::empty() } } /// High level representation of `RTM_GETROUTE`, `RTM_ADDROUTE`, `RTM_DELROUTE` /// messages headers. /// /// These headers have the following structure: /// /// ```no_rust /// 0 8 16 24 32 /// +----------------+----------------+----------------+----------------+ /// | address family | dest. length | source length | tos | /// +----------------+----------------+----------------+----------------+ /// | table | protocol | scope | type (kind) | /// +----------------+----------------+----------------+----------------+ /// | flags | /// +----------------+----------------+----------------+----------------+ /// ``` /// /// # Example /// /// ```rust /// extern crate netlink_packet_route; /// use netlink_packet_route::{constants::*, RouteFlags, RouteHeader}; /// /// fn main() { /// let mut hdr = RouteHeader::default(); /// assert_eq!(hdr.address_family, 0u8); /// assert_eq!(hdr.destination_prefix_length, 0u8); /// assert_eq!(hdr.source_prefix_length, 0u8); /// assert_eq!(hdr.tos, 0u8); /// assert_eq!(hdr.table, RT_TABLE_UNSPEC); /// assert_eq!(hdr.protocol, RTPROT_UNSPEC); /// assert_eq!(hdr.scope, RT_SCOPE_UNIVERSE); /// assert_eq!(hdr.kind, RTN_UNSPEC); /// assert_eq!(hdr.flags.bits(), 0u32); /// /// // set some values /// hdr.destination_prefix_length = 8; /// hdr.table = RT_TABLE_MAIN; /// hdr.protocol = RTPROT_KERNEL; /// hdr.scope = RT_SCOPE_NOWHERE; /// /// // ... /// } /// ``` #[derive(Debug, PartialEq, Eq, Hash, Clone, Default)] pub struct RouteHeader { /// Address family of the route: either [`AF_INET`] for IPv4 prefixes, or [`AF_INET6`] for IPv6 /// prefixes. pub address_family: u8, /// Prefix length of the destination subnet. /// /// Note that setting pub destination_prefix_length: u8, /// Prefix length of the source address. /// /// There could be multiple addresses from which a certain network is reachable. To decide which /// source address to use to reach and address in that network, the kernel rely on the route's /// source address for this destination. /// /// For instance, interface `if1` could have two addresses `10.0.0.1/24` and `10.0.0.128/24`, /// and we could have the following routes: /// /// ```no_rust /// 10.0.0.10/32 dev if1 scope link src 10.0.0.1 /// 10.0.0.11/32 dev if1 scope link src 10.0.0.1 /// 10.0.0.12/32 dev if1 scope link src 10.0.0.1 /// 10.0.0.0/24 dev if1 scope link src 10.0.0.128 /// ``` /// /// It means that for `10.0.0.10`, `10.0.0.11` and `10.0.0.12` the packets will be sent with /// `10.0.0.1` as source address, while for the rest of the `10.0.0.0/24` subnet, the source /// address will be `10.0.0.128` pub source_prefix_length: u8, /// TOS filter pub tos: u8, /// Routing table ID. It can be one of the `RT_TABLE_*` constants or a custom table number /// between 1 and 251 (included). Note that Linux supports routing table with an ID greater than /// 255, in which case this attribute will be set to [`RT_TABLE_COMPAT`] and an [`Nla::Table`] /// netlink attribute will be present in the message. pub table: u8, /// Protocol from which the route was learnt. It should be set to one of the `RTPROT_*` /// constants. pub protocol: u8, /// The scope of the area where the addresses in the destination subnet are valid. Predefined /// scope values are the `RT_SCOPE_*` constants. pub scope: u8, /// Route type. It should be set to one of the `RTN_*` constants. pub kind: u8, /// Flags when querying the kernel with a `RTM_GETROUTE` message. See [`RouteFlags`]. pub flags: RouteFlags, } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for RouteHeader { fn parse(buf: &RouteMessageBuffer<&'a T>) -> Result { Ok(RouteHeader { address_family: buf.address_family(), destination_prefix_length: buf.destination_prefix_length(), source_prefix_length: buf.source_prefix_length(), tos: buf.tos(), table: buf.table(), protocol: buf.protocol(), scope: buf.scope(), kind: buf.kind(), flags: RouteFlags::from_bits_truncate(buf.flags()), }) } } impl Emitable for RouteHeader { fn buffer_len(&self) -> usize { ROUTE_HEADER_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = RouteMessageBuffer::new(buffer); buffer.set_address_family(self.address_family); buffer.set_destination_prefix_length(self.destination_prefix_length); buffer.set_source_prefix_length(self.source_prefix_length); buffer.set_tos(self.tos); buffer.set_table(self.table); buffer.set_protocol(self.protocol); buffer.set_scope(self.scope); buffer.set_kind(self.kind); buffer.set_flags(self.flags.bits()); } } ================================================ FILE: netlink-packet-route/src/rtnl/route/message.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ nlas::route::Nla, traits::{Emitable, Parseable}, DecodeError, RouteHeader, RouteMessageBuffer, }; use anyhow::Context; use std::net::IpAddr; #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct RouteMessage { pub header: RouteHeader, pub nlas: Vec, } impl Emitable for RouteMessage { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); self.nlas .as_slice() .emit(&mut buffer[self.header.buffer_len()..]); } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for RouteMessage { fn parse(buf: &RouteMessageBuffer<&'a T>) -> Result { Ok(RouteMessage { header: RouteHeader::parse(buf).context("failed to parse route message header")?, nlas: Vec::::parse(buf).context("failed to parse route message NLAs")?, }) } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for Vec { fn parse(buf: &RouteMessageBuffer<&'a T>) -> Result { let mut nlas = vec![]; for nla_buf in buf.nlas() { nlas.push(Nla::parse(&nla_buf?)?); } Ok(nlas) } } fn octets_to_addr(octets: &[u8]) -> Result { if octets.len() == 4 { let mut ary: [u8; 4] = Default::default(); ary.copy_from_slice(octets); Ok(IpAddr::from(ary)) } else if octets.len() == 16 { let mut ary: [u8; 16] = Default::default(); ary.copy_from_slice(octets); Ok(IpAddr::from(ary)) } else { Err(DecodeError::from("Cannot decode IP address")) } } impl RouteMessage { /// Returns the input interface index, if present. pub fn input_interface(&self) -> Option { self.nlas.iter().find_map(|nla| { if let Nla::Iif(v) = nla { Some(*v) } else { None } }) } /// Returns the output interface index, if present. pub fn output_interface(&self) -> Option { self.nlas.iter().find_map(|nla| { if let Nla::Oif(v) = nla { Some(*v) } else { None } }) } /// Returns the source address prefix, if present. pub fn source_prefix(&self) -> Option<(IpAddr, u8)> { self.nlas.iter().find_map(|nla| { if let Nla::Source(v) = nla { octets_to_addr(v) .ok() .map(|addr| (addr, self.header.source_prefix_length)) } else { None } }) } /// Returns the destination subnet prefix, if present. pub fn destination_prefix(&self) -> Option<(IpAddr, u8)> { self.nlas.iter().find_map(|nla| { if let Nla::Destination(v) = nla { octets_to_addr(v) .ok() .map(|addr| (addr, self.header.destination_prefix_length)) } else { None } }) } /// Returns the gateway address, if present. pub fn gateway(&self) -> Option { self.nlas.iter().find_map(|nla| { if let Nla::Gateway(v) = nla { octets_to_addr(v).ok() } else { None } }) } } ================================================ FILE: netlink-packet-route/src/rtnl/route/mod.rs ================================================ // SPDX-License-Identifier: MIT mod buffer; mod header; mod message; pub mod nlas; pub use self::{buffer::*, header::*, message::*, nlas::*}; #[cfg(test)] mod test; ================================================ FILE: netlink-packet-route/src/rtnl/route/nlas/cache_info.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct CacheInfo { pub clntref: u32, pub last_use: u32, pub expires: u32, pub error: u32, pub used: u32, pub id: u32, pub ts: u32, pub ts_age: u32, } pub const CACHE_INFO_LEN: usize = 32; buffer!(CacheInfoBuffer(CACHE_INFO_LEN) { clntref: (u32, 0..4), last_use: (u32, 4..8), expires: (u32, 8..12), error: (u32, 12..16), used: (u32, 16..20), id: (u32, 20..24), ts: (u32, 24..28), ts_age: (u32, 28..32), }); impl> Parseable> for CacheInfo { fn parse(buf: &CacheInfoBuffer) -> Result { Ok(Self { clntref: buf.clntref(), last_use: buf.last_use(), expires: buf.expires(), error: buf.error(), used: buf.used(), id: buf.id(), ts: buf.ts(), ts_age: buf.ts_age(), }) } } impl Emitable for CacheInfo { fn buffer_len(&self) -> usize { CACHE_INFO_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = CacheInfoBuffer::new(buffer); buffer.set_clntref(self.clntref); buffer.set_last_use(self.last_use); buffer.set_expires(self.expires); buffer.set_error(self.error); buffer.set_used(self.used); buffer.set_id(self.id); buffer.set_ts(self.ts); buffer.set_ts_age(self.ts_age); } } ================================================ FILE: netlink-packet-route/src/rtnl/route/nlas/metrics.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use std::mem::size_of; use crate::{ constants::*, nlas::{DefaultNla, Nla, NlaBuffer}, parsers::parse_u32, traits::Parseable, DecodeError, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Metrics { Unspec(Vec), Lock(u32), Mtu(u32), Window(u32), Rtt(u32), RttVar(u32), SsThresh(u32), Cwnd(u32), Advmss(u32), Reordering(u32), Hoplimit(u32), InitCwnd(u32), Features(u32), RtoMin(u32), InitRwnd(u32), QuickAck(u32), CcAlgo(u32), FastopenNoCookie(u32), Other(DefaultNla), } impl Nla for Metrics { #[rustfmt::skip] fn value_len(&self) -> usize { use self::Metrics::*; match *self { Unspec(ref bytes) => bytes.len(), Lock(_) | Mtu(_) | Window(_) | Rtt(_) | RttVar(_) | SsThresh(_) | Cwnd(_) | Advmss(_) | Reordering(_) | Hoplimit(_) | InitCwnd(_) | Features(_) | RtoMin(_) | InitRwnd(_) | QuickAck(_) | CcAlgo(_) | FastopenNoCookie(_) => size_of::(), Other(ref attr) => attr.value_len(), } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::Metrics::*; match *self { Unspec(ref bytes) => buffer.copy_from_slice(bytes.as_slice()), Lock(value) | Mtu(value) | Window(value) | Rtt(value) | RttVar(value) | SsThresh(value) | Cwnd(value) | Advmss(value) | Reordering(value) | Hoplimit(value) | InitCwnd(value) | Features(value) | RtoMin(value) | InitRwnd(value) | QuickAck(value) | CcAlgo(value) | FastopenNoCookie(value) => NativeEndian::write_u32(buffer, value), Other(ref attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Metrics::*; match *self { Unspec(_) => RTAX_UNSPEC, Lock(_) => RTAX_LOCK, Mtu(_) => RTAX_MTU, Window(_) => RTAX_WINDOW, Rtt(_) => RTAX_RTT, RttVar(_) => RTAX_RTTVAR, SsThresh(_) => RTAX_SSTHRESH, Cwnd(_) => RTAX_CWND, Advmss(_) => RTAX_ADVMSS, Reordering(_) => RTAX_REORDERING, Hoplimit(_) => RTAX_HOPLIMIT, InitCwnd(_) => RTAX_INITCWND, Features(_) => RTAX_FEATURES, RtoMin(_) => RTAX_RTO_MIN, InitRwnd(_) => RTAX_INITRWND, QuickAck(_) => RTAX_QUICKACK, CcAlgo(_) => RTAX_CC_ALGO, FastopenNoCookie(_) => RTAX_FASTOPEN_NO_COOKIE, Other(ref attr) => attr.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Metrics { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::Metrics::*; let payload = buf.value(); Ok(match buf.kind() { RTAX_UNSPEC => Unspec(payload.to_vec()), RTAX_LOCK => Lock(parse_u32(payload).context("invalid RTAX_LOCK value")?), RTAX_MTU => Mtu(parse_u32(payload).context("invalid RTAX_MTU value")?), RTAX_WINDOW => Window(parse_u32(payload).context("invalid RTAX_WINDOW value")?), RTAX_RTT => Rtt(parse_u32(payload).context("invalid RTAX_RTT value")?), RTAX_RTTVAR => RttVar(parse_u32(payload).context("invalid RTAX_RTTVAR value")?), RTAX_SSTHRESH => SsThresh(parse_u32(payload).context("invalid RTAX_SSTHRESH value")?), RTAX_CWND => Cwnd(parse_u32(payload).context("invalid RTAX_CWND value")?), RTAX_ADVMSS => Advmss(parse_u32(payload).context("invalid RTAX_ADVMSS value")?), RTAX_REORDERING => { Reordering(parse_u32(payload).context("invalid RTAX_REORDERING value")?) } RTAX_HOPLIMIT => Hoplimit(parse_u32(payload).context("invalid RTAX_HOPLIMIT value")?), RTAX_INITCWND => InitCwnd(parse_u32(payload).context("invalid RTAX_INITCWND value")?), RTAX_FEATURES => Features(parse_u32(payload).context("invalid RTAX_FEATURES value")?), RTAX_RTO_MIN => RtoMin(parse_u32(payload).context("invalid RTAX_RTO_MIN value")?), RTAX_INITRWND => InitRwnd(parse_u32(payload).context("invalid RTAX_INITRWND value")?), RTAX_QUICKACK => QuickAck(parse_u32(payload).context("invalid RTAX_QUICKACK value")?), RTAX_CC_ALGO => CcAlgo(parse_u32(payload).context("invalid RTAX_CC_ALGO value")?), RTAX_FASTOPEN_NO_COOKIE => FastopenNoCookie( parse_u32(payload).context("invalid RTAX_FASTOPEN_NO_COOKIE value")?, ), _ => Other(DefaultNla::parse(buf).context("invalid NLA value (unknown type) value")?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/route/nlas/mfc_stats.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct MfcStats { pub packets: u64, pub bytes: u64, pub wrong_if: u64, } pub const MFC_STATS_LEN: usize = 24; buffer!(MfcStatsBuffer(MFC_STATS_LEN) { packets: (u64, 0..8), bytes: (u64, 8..16), wrong_if: (u64, 16..24), }); impl> Parseable> for MfcStats { fn parse(buf: &MfcStatsBuffer) -> Result { Ok(MfcStats { packets: buf.packets(), bytes: buf.bytes(), wrong_if: buf.wrong_if(), }) } } impl Emitable for MfcStats { fn buffer_len(&self) -> usize { MFC_STATS_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = MfcStatsBuffer::new(buffer); buffer.set_packets(self.packets); buffer.set_bytes(self.bytes); buffer.set_wrong_if(self.wrong_if); } } ================================================ FILE: netlink-packet-route/src/rtnl/route/nlas/mod.rs ================================================ // SPDX-License-Identifier: MIT mod cache_info; pub use self::cache_info::*; mod metrics; pub use self::metrics::*; mod mfc_stats; pub use self::mfc_stats::*; mod mpls_ip_tunnel; pub use self::mpls_ip_tunnel::*; mod next_hops; pub use self::next_hops::*; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use crate::{ constants::*, nlas::{self, DefaultNla, NlaBuffer}, parsers::{parse_u16, parse_u32}, traits::Parseable, DecodeError, }; #[cfg(feature = "rich_nlas")] use crate::traits::Emitable; /// Netlink attributes for `RTM_NEWROUTE`, `RTM_DELROUTE`, /// `RTM_GETROUTE` messages. #[derive(Debug, PartialEq, Eq, Clone)] pub enum Nla { #[cfg(not(feature = "rich_nlas"))] Metrics(Vec), #[cfg(feature = "rich_nlas")] Metrics(Metrics), #[cfg(not(feature = "rich_nlas"))] MfcStats(Vec), #[cfg(feature = "rich_nlas")] MfcStats(MfcStats), #[cfg(not(feature = "rich_nlas"))] MultiPath(Vec), #[cfg(feature = "rich_nlas")] // See: https://codecave.cc/multipath-routing-in-linux-part-1.html MultiPath(Vec), #[cfg(not(feature = "rich_nlas"))] CacheInfo(Vec), #[cfg(feature = "rich_nlas")] CacheInfo(CacheInfo), Unspec(Vec), Destination(Vec), Source(Vec), Gateway(Vec), PrefSource(Vec), Session(Vec), MpAlgo(Vec), Via(Vec), NewDestination(Vec), Pref(Vec), Encap(Vec), Expires(Vec), Pad(Vec), Uid(Vec), TtlPropagate(Vec), EncapType(u16), Iif(u32), Oif(u32), Priority(u32), ProtocolInfo(u32), Flow(u32), Table(u32), Mark(u32), Other(DefaultNla), } impl nlas::Nla for Nla { #[rustfmt::skip] fn value_len(&self) -> usize { use self::Nla::*; match *self { Unspec(ref bytes) | Destination(ref bytes) | Source(ref bytes) | Gateway(ref bytes) | PrefSource(ref bytes) | Session(ref bytes) | MpAlgo(ref bytes) | Via(ref bytes) | NewDestination(ref bytes) | Pref(ref bytes) | Encap(ref bytes) | Expires(ref bytes) | Pad(ref bytes) | Uid(ref bytes) | TtlPropagate(ref bytes) => bytes.len(), #[cfg(not(feature = "rich_nlas"))] CacheInfo(ref bytes) | MfcStats(ref bytes) | Metrics(ref bytes) | MultiPath(ref bytes) => bytes.len(), #[cfg(feature = "rich_nlas")] CacheInfo(ref cache_info) => cache_info.buffer_len(), #[cfg(feature = "rich_nlas")] MfcStats(ref stats) => stats.buffer_len(), #[cfg(feature = "rich_nlas")] Metrics(ref metrics) => metrics.buffer_len(), #[cfg(feature = "rich_nlas")] MultiPath(ref next_hops) => next_hops.iter().map(|nh| nh.buffer_len()).sum(), EncapType(_) => 2, Iif(_) | Oif(_) | Priority(_) | ProtocolInfo(_) | Flow(_) | Table(_) | Mark(_) => 4, Other(ref attr) => attr.value_len(), } } #[rustfmt::skip] fn emit_value(&self, buffer: &mut [u8]) { use self::Nla::*; match *self { Unspec(ref bytes) | Destination(ref bytes) | Source(ref bytes) | Gateway(ref bytes) | PrefSource(ref bytes) | Session(ref bytes) | MpAlgo(ref bytes) | Via(ref bytes) | NewDestination(ref bytes) | Pref(ref bytes) | Encap(ref bytes) | Expires(ref bytes) | Pad(ref bytes) | Uid(ref bytes) | TtlPropagate(ref bytes) => buffer.copy_from_slice(bytes.as_slice()), #[cfg(not(feature = "rich_nlas"))] MultiPath(ref bytes) | CacheInfo(ref bytes) | MfcStats(ref bytes) | Metrics(ref bytes) => buffer.copy_from_slice(bytes.as_slice()), #[cfg(feature = "rich_nlas")] CacheInfo(ref cache_info) => cache_info.emit(buffer), #[cfg(feature = "rich_nlas")] MfcStats(ref stats) => stats.emit(buffer), #[cfg(feature = "rich_nlas")] Metrics(ref metrics) => metrics.emit(buffer), #[cfg(feature = "rich_nlas")] MultiPath(ref next_hops) => { let mut offset = 0; for nh in next_hops { let len = nh.buffer_len(); nh.emit(&mut buffer[offset..offset+len]); offset += len } } EncapType(value) => NativeEndian::write_u16(buffer, value), Iif(value) | Oif(value) | Priority(value) | ProtocolInfo(value) | Flow(value) | Table(value) | Mark(value) => NativeEndian::write_u32(buffer, value), Other(ref attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Nla::*; match *self { Unspec(_) => RTA_UNSPEC, Destination(_) => RTA_DST, Source(_) => RTA_SRC, Iif(_) => RTA_IIF, Oif(_) => RTA_OIF, Gateway(_) => RTA_GATEWAY, Priority(_) => RTA_PRIORITY, PrefSource(_) => RTA_PREFSRC, Metrics(_) => RTA_METRICS, MultiPath(_) => RTA_MULTIPATH, ProtocolInfo(_) => RTA_PROTOINFO, Flow(_) => RTA_FLOW, CacheInfo(_) => RTA_CACHEINFO, Session(_) => RTA_SESSION, MpAlgo(_) => RTA_MP_ALGO, Table(_) => RTA_TABLE, Mark(_) => RTA_MARK, MfcStats(_) => RTA_MFC_STATS, Via(_) => RTA_VIA, NewDestination(_) => RTA_NEWDST, Pref(_) => RTA_PREF, EncapType(_) => RTA_ENCAP_TYPE, Encap(_) => RTA_ENCAP, Expires(_) => RTA_EXPIRES, Pad(_) => RTA_PAD, Uid(_) => RTA_UID, TtlPropagate(_) => RTA_TTL_PROPAGATE, Other(ref attr) => attr.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nla { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::Nla::*; let payload = buf.value(); Ok(match buf.kind() { RTA_UNSPEC => Unspec(payload.to_vec()), RTA_DST => Destination(payload.to_vec()), RTA_SRC => Source(payload.to_vec()), RTA_GATEWAY => Gateway(payload.to_vec()), RTA_PREFSRC => PrefSource(payload.to_vec()), RTA_SESSION => Session(payload.to_vec()), RTA_MP_ALGO => MpAlgo(payload.to_vec()), RTA_VIA => Via(payload.to_vec()), RTA_NEWDST => NewDestination(payload.to_vec()), RTA_PREF => Pref(payload.to_vec()), RTA_ENCAP => Encap(payload.to_vec()), RTA_EXPIRES => Expires(payload.to_vec()), RTA_PAD => Pad(payload.to_vec()), RTA_UID => Uid(payload.to_vec()), RTA_TTL_PROPAGATE => TtlPropagate(payload.to_vec()), RTA_ENCAP_TYPE => { EncapType(parse_u16(payload).context("invalid RTA_ENCAP_TYPE value")?) } RTA_IIF => Iif(parse_u32(payload).context("invalid RTA_IIF value")?), RTA_OIF => Oif(parse_u32(payload).context("invalid RTA_OIF value")?), RTA_PRIORITY => Priority(parse_u32(payload).context("invalid RTA_PRIORITY value")?), RTA_PROTOINFO => { ProtocolInfo(parse_u32(payload).context("invalid RTA_PROTOINFO value")?) } RTA_FLOW => Flow(parse_u32(payload).context("invalid RTA_FLOW value")?), RTA_TABLE => Table(parse_u32(payload).context("invalid RTA_TABLE value")?), RTA_MARK => Mark(parse_u32(payload).context("invalid RTA_MARK value")?), #[cfg(not(feature = "rich_nlas"))] RTA_CACHEINFO => CacheInfo(payload.to_vec()), #[cfg(feature = "rich_nlas")] RTA_CACHEINFO => CacheInfo( cache_info::CacheInfo::parse( &CacheInfoBuffer::new_checked(payload) .context("invalid RTA_CACHEINFO value")?, ) .context("invalid RTA_CACHEINFO value")?, ), #[cfg(not(feature = "rich_nlas"))] RTA_MFC_STATS => MfcStats(payload.to_vec()), #[cfg(feature = "rich_nlas")] RTA_MFC_STATS => MfcStats( mfc_stats::MfcStats::parse( &MfcStatsBuffer::new_checked(payload).context("invalid RTA_MFC_STATS value")?, ) .context("invalid RTA_MFC_STATS value")?, ), #[cfg(not(feature = "rich_nlas"))] RTA_METRICS => Metrics(payload.to_vec()), #[cfg(feature = "rich_nlas")] RTA_METRICS => Metrics( metrics::Metrics::parse( &NlaBuffer::new_checked(payload).context("invalid RTA_METRICS value")?, ) .context("invalid RTA_METRICS value")?, ), #[cfg(not(feature = "rich_nlas"))] RTA_MULTIPATH => MultiPath(payload.to_vec()), #[cfg(feature = "rich_nlas")] RTA_MULTIPATH => { let mut next_hops = vec![]; let mut buf = payload; loop { let nh_buf = NextHopBuffer::new_checked(&buf).context("invalid RTA_MULTIPATH value")?; let len = nh_buf.length() as usize; let nh = NextHop::parse(&nh_buf).context("invalid RTA_MULTIPATH value")?; next_hops.push(nh); if buf.len() == len { break; } buf = &buf[len..]; } MultiPath(next_hops) } _ => Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/route/nlas/mpls_ip_tunnel.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use crate::{ constants::*, nlas::{DefaultNla, Nla, NlaBuffer}, parsers::parse_u8, traits::Parseable, DecodeError, }; /// Netlink attributes for `RTA_ENCAP` with `RTA_ENCAP_TYPE` set to `LWTUNNEL_ENCAP_MPLS`. pub enum MplsIpTunnel { Destination(Vec), Ttl(u8), Other(DefaultNla), } impl Nla for MplsIpTunnel { fn value_len(&self) -> usize { use self::MplsIpTunnel::*; match self { Destination(bytes) => bytes.len(), Ttl(_) => 1, Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { use self::MplsIpTunnel::*; match self { Destination(_) => MPLS_IPTUNNEL_DST, Ttl(_) => MPLS_IPTUNNEL_TTL, Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::MplsIpTunnel::*; match self { Destination(bytes) => buffer.copy_from_slice(bytes.as_slice()), Ttl(ttl) => buffer[0] = *ttl, Other(attr) => attr.emit_value(buffer), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for MplsIpTunnel { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::MplsIpTunnel::*; let payload = buf.value(); Ok(match buf.kind() { MPLS_IPTUNNEL_DST => Destination(payload.to_vec()), MPLS_IPTUNNEL_TTL => Ttl(parse_u8(payload).context("invalid MPLS_IPTUNNEL_TTL value")?), _ => Other(DefaultNla::parse(buf).context("invalid NLA value (unknown type) value")?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/route/nlas/next_hops.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use std::net::IpAddr; use crate::{ constants, nlas::{NlaBuffer, NlasIterator}, parsers::parse_ip, route::nlas::Nla, traits::{Emitable, Parseable}, DecodeError, }; bitflags! { pub struct NextHopFlags: u8 { const RTNH_F_EMPTY = 0; const RTNH_F_DEAD = constants::RTNH_F_DEAD as u8; const RTNH_F_PERVASIVE = constants::RTNH_F_PERVASIVE as u8; const RTNH_F_ONLINK = constants::RTNH_F_ONLINK as u8; const RTNH_F_OFFLOAD = constants::RTNH_F_OFFLOAD as u8; const RTNH_F_LINKDOWN = constants::RTNH_F_LINKDOWN as u8; const RTNH_F_UNRESOLVED = constants::RTNH_F_UNRESOLVED as u8; } } const PAYLOAD_OFFSET: usize = 8; buffer!(NextHopBuffer { length: (u16, 0..2), flags: (u8, 2), hops: (u8, 3), interface_id: (u32, 4..8), payload: (slice, PAYLOAD_OFFSET..), }); impl> NextHopBuffer { pub fn new_checked(buffer: T) -> Result { let packet = Self::new(buffer); packet.check_buffer_length()?; Ok(packet) } fn check_buffer_length(&self) -> Result<(), DecodeError> { let len = self.buffer.as_ref().len(); if len < PAYLOAD_OFFSET { return Err( format!("invalid NextHopBuffer: length {} < {}", len, PAYLOAD_OFFSET).into(), ); } if len < self.length() as usize { return Err(format!( "invalid NextHopBuffer: length {} < {}", len, 8 + self.length() ) .into()); } Ok(()) } } impl<'a, T: AsRef<[u8]> + ?Sized> NextHopBuffer<&'a T> { pub fn nlas(&self) -> impl Iterator, DecodeError>> { NlasIterator::new(&self.payload()[..(self.length() as usize - PAYLOAD_OFFSET)]) } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct NextHop { /// Next-hop flags (see [`NextHopFlags`]) pub flags: NextHopFlags, /// Next-hop priority pub hops: u8, /// Interface index for the next-hop pub interface_id: u32, /// Attributes pub nlas: Vec, } impl<'a, T: AsRef<[u8]>> Parseable> for NextHop { fn parse(buf: &NextHopBuffer<&T>) -> Result { let nlas = Vec::::parse( &NextHopBuffer::new_checked(buf.buffer) .context("cannot parse route attributes in next-hop")?, ) .context("cannot parse route attributes in next-hop")?; Ok(NextHop { flags: NextHopFlags::from_bits_truncate(buf.flags()), hops: buf.hops(), interface_id: buf.interface_id(), nlas, }) } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for Vec { fn parse(buf: &NextHopBuffer<&'a T>) -> Result { let mut nlas = vec![]; for nla_buf in buf.nlas() { nlas.push(Nla::parse(&nla_buf?)?); } Ok(nlas) } } impl Emitable for NextHop { fn buffer_len(&self) -> usize { // len, flags, hops and interface id fields PAYLOAD_OFFSET + self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { let mut nh_buffer = NextHopBuffer::new(buffer); nh_buffer.set_length(self.buffer_len() as u16); nh_buffer.set_flags(self.flags.bits()); nh_buffer.set_hops(self.hops); nh_buffer.set_interface_id(self.interface_id); self.nlas.as_slice().emit(nh_buffer.payload_mut()) } } impl NextHop { /// Gateway address (it is actually encoded as an `RTA_GATEWAY` nla) pub fn gateway(&self) -> Option { self.nlas.iter().find_map(|nla| { if let Nla::Gateway(ip) = nla { parse_ip(ip).ok() } else { None } }) } } ================================================ FILE: netlink-packet-route/src/rtnl/route/test.rs ================================================ // SPDX-License-Identifier: MIT #[cfg(feature = "rich_nlas")] mod test_rich_nlas { use crate::{ rtnl::route::{ nlas::{NextHop, NextHopFlags, Nla}, RouteFlags, RouteMessage, RouteMessageBuffer, }, utils::{Emitable, Parseable}, }; use std::net::Ipv6Addr; #[rustfmt::skip] static ROUTE_MSG: [u8; 100] = [ 0x0a, // address family 0x40, // length of destination 0x00, // length of source 0x00, // TOS 0xfe, // routing table id 0x03, // routing protocol (boot) 0x00, // route origin (global) 0x01, // gateway or direct route 0x00, 0x00, 0x00, 0x00, // Route destination address NLA 0x14, 0x00, // Length (20) 0x01, 0x00, // Type // Value 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // RTA_MULTIPATH attribute 0x44, 0x00, // Length (68) 0x09, 0x00, // Type // next-hop 1 0x1c, 0x00, // length (28) 0x00, // flags 0x00, // hops 0x00, 0x00, 0x00, 0x00, // interface ID // nested RTA_GATEWAY 0x14, 0x00, // Length (14) 0x05, 0x00, // Type // Value 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // next-hop 2 0x1c, 0x00, // length (28) 0x00, // flags 0x00, // hops 0x00, 0x00, 0x00, 0x00, // interface ID // nested RTA_GATEWAY 0x14, 0x00, // Length (14) 0x05, 0x00, // Type // Value 0xfc, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // next-hop 3 0x08, 0x00, // length (8) 0x00, // flags 0x00, // hops 0x02, 0x00, 0x00, 0x00, // interface ID ]; fn route_message() -> RouteMessage { let mut msg = RouteMessage::default(); msg.header.address_family = 0x0a; msg.header.destination_prefix_length = 0x40; msg.header.source_prefix_length = 0; msg.header.tos = 0; msg.header.table = 0xfe; msg.header.protocol = 0x03; msg.header.scope = 0x00; msg.header.kind = 0x01; msg.header.flags = RouteFlags::empty(); msg.nlas = vec![ Nla::Destination("1001::".parse::().unwrap().octets().to_vec()), Nla::MultiPath(vec![ NextHop { flags: NextHopFlags::empty(), hops: 0, interface_id: 0, nlas: vec![Nla::Gateway( "fc00::1".parse::().unwrap().octets().to_vec(), )], }, NextHop { flags: NextHopFlags::empty(), hops: 0, interface_id: 0, nlas: vec![Nla::Gateway( "fc01::1".parse::().unwrap().octets().to_vec(), )], }, NextHop { flags: NextHopFlags::empty(), hops: 0, interface_id: 2, nlas: vec![], }, ]), ]; msg } #[test] fn parse_message_with_multipath_nla() { let expected = route_message(); let actual = RouteMessage::parse(&RouteMessageBuffer::new_checked(&&ROUTE_MSG[..]).unwrap()) .unwrap(); assert_eq!(actual, expected); } #[test] fn emit_message_with_multipath_nla() { let msg = route_message(); let mut buf = vec![0; 100]; assert_eq!(msg.buffer_len(), 100); msg.emit(&mut buf[..]); assert_eq!(buf, ROUTE_MSG); } } ================================================ FILE: netlink-packet-route/src/rtnl/rule/buffer.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ nlas::{NlaBuffer, NlasIterator}, DecodeError, }; pub const RULE_HEADER_LEN: usize = 12; buffer!(RuleMessageBuffer(RULE_HEADER_LEN) { family: (u8, 0), dst_len: (u8, 1), src_len: (u8, 2), tos: (u8, 3), table: (u8, 4), reserve_1: (u8, 5), reserve_2: (u8, 6), action: (u8, 7), flags: (u32, 8..RULE_HEADER_LEN), payload: (slice, RULE_HEADER_LEN..), }); impl<'a, T: AsRef<[u8]> + ?Sized> RuleMessageBuffer<&'a T> { pub fn nlas(&self) -> impl Iterator, DecodeError>> { NlasIterator::new(self.payload()) } } ================================================ FILE: netlink-packet-route/src/rtnl/rule/header.rs ================================================ // SPDX-License-Identifier: MIT use super::{buffer::RuleMessageBuffer, RULE_HEADER_LEN}; use crate::{ constants::*, utils::{Emitable, Parseable}, DecodeError, }; bitflags! { pub struct RuleFlags: u32 { const FIB_RULE_PERMANENT = FIB_RULE_PERMANENT; const FIB_RULE_INVERT = FIB_RULE_INVERT; const FIB_RULE_UNRESOLVED = FIB_RULE_UNRESOLVED; const FIB_RULE_IIF_DETACHED = FIB_RULE_IIF_DETACHED; const FIB_RULE_DEV_DETACHED = FIB_RULE_DEV_DETACHED; const FIB_RULE_OIF_DETACHED = FIB_RULE_OIF_DETACHED; const FIB_RULE_FIND_SADDR = FIB_RULE_FIND_SADDR; } } impl Default for RuleFlags { fn default() -> Self { Self::empty() } } // see https://github.com/torvalds/linux/blob/master/include/uapi/linux/fib_rules.h // see https://github.com/torvalds/linux/blob/master/include/net/fib_rules.h #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct RuleHeader { /// Address family: one of the `AF_*` constants. pub family: u8, pub dst_len: u8, pub src_len: u8, pub tos: u8, /// RT_TABLE_* pub table: u8, /// FR_ACT_* pub action: u8, /// fib rule flags pub flags: u32, } impl Emitable for RuleHeader { fn buffer_len(&self) -> usize { RULE_HEADER_LEN } fn emit(&self, buffer: &mut [u8]) { let mut packet = RuleMessageBuffer::new(buffer); packet.set_family(self.family); packet.set_dst_len(self.dst_len); packet.set_src_len(self.src_len); packet.set_flags(self.flags); packet.set_table(self.table); packet.set_tos(self.tos); packet.set_action(self.action); } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for RuleHeader { fn parse(buf: &RuleMessageBuffer<&'a T>) -> Result { Ok(RuleHeader { family: buf.family(), dst_len: buf.dst_len(), src_len: buf.src_len(), tos: buf.tos(), table: buf.table(), action: buf.action(), flags: buf.flags(), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/rule/message.rs ================================================ // SPDX-License-Identifier: MIT use super::{buffer::RuleMessageBuffer, header::RuleHeader, nlas::Nla}; use crate::{ utils::{Emitable, Parseable}, DecodeError, }; use anyhow::Context; #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct RuleMessage { pub header: RuleHeader, pub nlas: Vec, } impl Emitable for RuleMessage { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); self.nlas .as_slice() .emit(&mut buffer[self.header.buffer_len()..]); } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for RuleMessage { fn parse(buf: &RuleMessageBuffer<&'a T>) -> Result { let header = RuleHeader::parse(buf).context("failed to parse link message header")?; let nlas = Vec::::parse(buf).context("failed to parse link message NLAs")?; Ok(RuleMessage { header, nlas }) } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for Vec { fn parse(buf: &RuleMessageBuffer<&'a T>) -> Result { let mut nlas = vec![]; for nla_buf in buf.nlas() { nlas.push(Nla::parse(&nla_buf?)?); } Ok(nlas) } } ================================================ FILE: netlink-packet-route/src/rtnl/rule/mod.rs ================================================ // SPDX-License-Identifier: MIT pub mod buffer; pub mod header; pub mod message; pub mod nlas; pub use buffer::*; pub use header::*; pub use message::*; pub use nlas::*; ================================================ FILE: netlink-packet-route/src/rtnl/rule/nlas/mod.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ nlas, nlas::DefaultNla, utils::{ byteorder::{ByteOrder, NativeEndian}, nla::NlaBuffer, parsers::{parse_string, parse_u32, parse_u8}, Parseable, }, DecodeError, FRA_DPORT_RANGE, FRA_DST, FRA_FLOW, FRA_FWMARK, FRA_FWMASK, FRA_GOTO, FRA_IIFNAME, FRA_IP_PROTO, FRA_L3MDEV, FRA_OIFNAME, FRA_PAD, FRA_PRIORITY, FRA_PROTOCOL, FRA_SPORT_RANGE, FRA_SRC, FRA_SUPPRESS_IFGROUP, FRA_SUPPRESS_PREFIXLEN, FRA_TABLE, FRA_TUN_ID, FRA_UID_RANGE, FRA_UNSPEC, }; use anyhow::Context; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Nla { Unspec(Vec), /// destination address Destination(Vec), /// source address Source(Vec), /// input interface name Iifname(String), /// target to jump to when used with rule action `FR_ACT_GOTO` Goto(u32), Priority(u32), FwMark(u32), FwMask(u32), /// flow class id, Flow(u32), TunId(u32), SuppressIfGroup(u32), SuppressPrefixLen(u32), Table(u32), /// output interface name OifName(String), Pad(Vec), /// iif or oif is l3mdev goto its table L3MDev(u8), UidRange(Vec), /// RTPROT_* Protocol(u8), /// AF_* IpProto(u8), SourcePortRange(Vec), DestinationPortRange(Vec), Other(DefaultNla), } impl nlas::Nla for Nla { fn value_len(&self) -> usize { use self::Nla::*; match self { Unspec(ref bytes) | Destination(ref bytes) | Source(ref bytes) | Pad(ref bytes) | UidRange(ref bytes) | SourcePortRange(ref bytes) | DestinationPortRange(ref bytes) => bytes.len(), Iifname(ref s) | OifName(ref s) => s.as_bytes().len() + 1, Priority(_) | FwMark(_) | FwMask(_) | Flow(_) | TunId(_) | Goto(_) | SuppressIfGroup(_) | SuppressPrefixLen(_) | Table(_) => 4, L3MDev(_) | Protocol(_) | IpProto(_) => 1, Other(attr) => attr.value_len(), } } fn kind(&self) -> u16 { use self::Nla::*; match self { Unspec(_) => FRA_UNSPEC, Destination(_) => FRA_DST, Source(_) => FRA_SRC, Iifname(_) => FRA_IIFNAME, Goto(_) => FRA_GOTO, Priority(_) => FRA_PRIORITY, FwMark(_) => FRA_FWMARK, FwMask(_) => FRA_FWMASK, Flow(_) => FRA_FLOW, TunId(_) => FRA_TUN_ID, SuppressIfGroup(_) => FRA_SUPPRESS_IFGROUP, SuppressPrefixLen(_) => FRA_SUPPRESS_PREFIXLEN, Table(_) => FRA_TABLE, OifName(_) => FRA_OIFNAME, Pad(_) => FRA_PAD, L3MDev(_) => FRA_L3MDEV, UidRange(_) => FRA_UID_RANGE, Protocol(_) => FRA_PROTOCOL, IpProto(_) => FRA_IP_PROTO, SourcePortRange(_) => FRA_SPORT_RANGE, DestinationPortRange(_) => FRA_DPORT_RANGE, Other(attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::Nla::*; match self { Unspec(ref bytes) | Destination(ref bytes) | Source(ref bytes) | Pad(ref bytes) | UidRange(ref bytes) | SourcePortRange(ref bytes) | DestinationPortRange(ref bytes) => buffer.copy_from_slice(bytes.as_slice()), Iifname(ref s) | OifName(ref s) => buffer[..s.len()].copy_from_slice(s.as_bytes()), Priority(value) | FwMark(value) | FwMask(value) | Flow(value) | TunId(value) | Goto(value) | SuppressIfGroup(value) | SuppressPrefixLen(value) | Table(value) => NativeEndian::write_u32(buffer, *value), L3MDev(value) | Protocol(value) | IpProto(value) => buffer[0] = *value, Other(attr) => attr.emit_value(buffer), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nla { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use Nla::*; let payload = buf.value(); Ok(match buf.kind() { FRA_UNSPEC => Unspec(payload.to_vec()), FRA_DST => Destination(payload.to_vec()), FRA_SRC => Source(payload.to_vec()), FRA_IIFNAME => Iifname(parse_string(payload).context("invalid FRA_IIFNAME value")?), FRA_GOTO => Goto(parse_u32(payload).context("invalid FRA_GOTO value")?), FRA_PRIORITY => Priority(parse_u32(payload).context("invalid FRA_PRIORITY value")?), FRA_FWMARK => FwMark(parse_u32(payload).context("invalid FRA_FWMARK value")?), FRA_FLOW => Flow(parse_u32(payload).context("invalid FRA_FLOW value")?), FRA_TUN_ID => TunId(parse_u32(payload).context("invalid FRA_TUN_ID value")?), FRA_SUPPRESS_IFGROUP => { SuppressIfGroup(parse_u32(payload).context("invalid FRA_SUPPRESS_IFGROUP value")?) } FRA_SUPPRESS_PREFIXLEN => SuppressPrefixLen( parse_u32(payload).context("invalid FRA_SUPPRESS_PREFIXLEN value")?, ), FRA_TABLE => Table(parse_u32(payload).context("invalid FRA_TABLE value")?), FRA_FWMASK => FwMask(parse_u32(payload).context("invalid FRA_FWMASK value")?), FRA_OIFNAME => OifName(parse_string(payload).context("invalid FRA_OIFNAME value")?), FRA_PAD => Pad(payload.to_vec()), FRA_L3MDEV => L3MDev(parse_u8(payload).context("invalid FRA_L3MDEV value")?), FRA_UID_RANGE => UidRange(payload.to_vec()), FRA_PROTOCOL => Protocol(parse_u8(payload).context("invalid FRA_PROTOCOL value")?), FRA_IP_PROTO => IpProto(parse_u8(payload).context("invalid FRA_IP_PROTO value")?), FRA_SPORT_RANGE => SourcePortRange(payload.to_vec()), FRA_DPORT_RANGE => DestinationPortRange(payload.to_vec()), _ => Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/tc/buffer.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ nlas::{NlaBuffer, NlasIterator}, DecodeError, }; pub const TC_HEADER_LEN: usize = 20; buffer!(TcMessageBuffer(TC_HEADER_LEN) { family: (u8, 0), pad1: (u8, 1), pad2: (u16, 2..4), index: (i32, 4..8), handle: (u32, 8..12), parent: (u32, 12..16), info: (u32, 16..TC_HEADER_LEN), payload: (slice, TC_HEADER_LEN..), }); impl<'a, T: AsRef<[u8]> + ?Sized> TcMessageBuffer<&'a T> { pub fn nlas(&self) -> impl Iterator, DecodeError>> { NlasIterator::new(self.payload()) } } ================================================ FILE: netlink-packet-route/src/rtnl/tc/constants.rs ================================================ // SPDX-License-Identifier: MIT /// Handles pub const TC_H_MAJ_MASK: u32 = 0xFFFF0000; pub const TC_H_MIN_MASK: u32 = 0x0000FFFF; #[macro_export] macro_rules! TC_H_MAKE { ($maj: expr, $min: expr) => { ($maj & TC_H_MAJ_MASK) | ($min & TC_H_MIN_MASK) }; } pub const TC_H_UNSPEC: u32 = 0; pub const TC_H_ROOT: u32 = 0xFFFFFFFF; pub const TC_H_INGRESS: u32 = 0xFFFFFFF1; pub const TC_H_CLSACT: u32 = TC_H_INGRESS; pub const TC_H_MIN_PRIORITY: u32 = 0xFFE0; pub const TC_H_MIN_INGRESS: u32 = 0xFFF2; pub const TC_H_MIN_EGRESS: u32 = 0xFFF3; /// U32 filters pub const TCA_U32_UNSPEC: u16 = 0; pub const TCA_U32_CLASSID: u16 = 1; pub const TCA_U32_HASH: u16 = 2; pub const TCA_U32_LINK: u16 = 3; pub const TCA_U32_DIVISOR: u16 = 4; pub const TCA_U32_SEL: u16 = 5; pub const TCA_U32_POLICE: u16 = 6; pub const TCA_U32_ACT: u16 = 7; pub const TCA_U32_INDEV: u16 = 8; pub const TCA_U32_PCNT: u16 = 9; pub const TCA_U32_MARK: u16 = 10; pub const TCA_U32_FLAGS: u16 = 11; pub const TCA_U32_PAD: u16 = 12; pub const TCA_U32_MAX: u16 = TCA_U32_PAD; /// U32 Flags pub const TC_U32_TERMINAL: u8 = 1; pub const TC_U32_OFFSET: u8 = 2; pub const TC_U32_VAROFFSET: u8 = 4; pub const TC_U32_EAT: u8 = 8; pub const TC_U32_MAXDEPTH: u8 = 8; /// Action attributes pub const TCA_ACT_UNSPEC: u16 = 0; pub const TCA_ACT_KIND: u16 = 1; pub const TCA_ACT_OPTIONS: u16 = 2; pub const TCA_ACT_INDEX: u16 = 3; pub const TCA_ACT_STATS: u16 = 4; pub const TCA_ACT_PAD: u16 = 5; pub const TCA_ACT_COOKIE: u16 = 6; //TODO(wllenyj): Why not subtract 1? See `linux/pkt_cls.h` for original definition. pub const TCA_ACT_MAX: u16 = 7; pub const TCA_OLD_COMPAT: u16 = TCA_ACT_MAX + 1; pub const TCA_ACT_MAX_PRIO: u16 = 32; pub const TCA_ACT_BIND: u16 = 1; pub const TCA_ACT_NOBIND: u16 = 0; pub const TCA_ACT_UNBIND: u16 = 1; pub const TCA_ACT_NOUNBIND: u16 = 0; pub const TCA_ACT_REPLACE: u16 = 1; pub const TCA_ACT_NOREPLACE: u16 = 0; pub const TC_ACT_UNSPEC: i32 = -1; pub const TC_ACT_OK: i32 = 0; pub const TC_ACT_RECLASSIFY: i32 = 1; pub const TC_ACT_SHOT: i32 = 2; pub const TC_ACT_PIPE: i32 = 3; pub const TC_ACT_STOLEN: i32 = 4; pub const TC_ACT_QUEUED: i32 = 5; pub const TC_ACT_REPEAT: i32 = 6; pub const TC_ACT_REDIRECT: i32 = 7; pub const TC_ACT_TRAP: i32 = 8; pub const TC_ACT_VALUE_MAX: i32 = TC_ACT_TRAP; pub const TC_ACT_JUMP: i32 = 0x10000000; pub const TCA_ACT_TAB: u16 = 1; // TCA_ROOT_TAB pub const TCAA_MAX: u16 = 1; /// Mirred action attr pub const TCA_MIRRED_UNSPEC: u16 = 0; pub const TCA_MIRRED_TM: u16 = 1; pub const TCA_MIRRED_PARMS: u16 = 2; pub const TCA_MIRRED_PAD: u16 = 3; pub const TCA_MIRRED_MAX: u16 = TCA_MIRRED_PAD; pub const TCA_EGRESS_REDIR: i32 = 1; /* packet redirect to EGRESS */ pub const TCA_EGRESS_MIRROR: i32 = 2; /* mirror packet to EGRESS */ pub const TCA_INGRESS_REDIR: i32 = 3; /* packet redirect to INGRESS */ pub const TCA_INGRESS_MIRROR: i32 = 4; /* mirror packet to INGRESS */ ================================================ FILE: netlink-packet-route/src/rtnl/tc/message.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use crate::{ constants::*, nlas::{ tc::{Nla, Stats, Stats2, StatsBuffer, TcOpt}, DefaultNla, NlasIterator, }, parsers::{parse_string, parse_u8}, traits::{Emitable, Parseable, ParseableParametrized}, DecodeError, TcMessageBuffer, TC_HEADER_LEN, }; #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct TcMessage { pub header: TcHeader, pub nlas: Vec, } impl TcMessage { pub fn into_parts(self) -> (TcHeader, Vec) { (self.header, self.nlas) } pub fn from_parts(header: TcHeader, nlas: Vec) -> Self { TcMessage { header, nlas } } /// Create a new `TcMessage` with the given index pub fn with_index(index: i32) -> Self { Self { header: TcHeader { index, ..Default::default() }, nlas: Vec::new(), } } } #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct TcHeader { pub family: u8, // Interface index pub index: i32, // Qdisc handle pub handle: u32, // Parent Qdisc pub parent: u32, pub info: u32, } impl Emitable for TcHeader { fn buffer_len(&self) -> usize { TC_HEADER_LEN } fn emit(&self, buffer: &mut [u8]) { let mut packet = TcMessageBuffer::new(buffer); packet.set_family(self.family); packet.set_index(self.index); packet.set_handle(self.handle); packet.set_parent(self.parent); packet.set_info(self.info); } } impl Emitable for TcMessage { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); self.nlas .as_slice() .emit(&mut buffer[self.header.buffer_len()..]); } } impl> Parseable> for TcHeader { fn parse(buf: &TcMessageBuffer) -> Result { Ok(Self { family: buf.family(), index: buf.index(), handle: buf.handle(), parent: buf.parent(), info: buf.info(), }) } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for TcMessage { fn parse(buf: &TcMessageBuffer<&'a T>) -> Result { Ok(Self { header: TcHeader::parse(buf).context("failed to parse tc message header")?, nlas: Vec::::parse(buf).context("failed to parse tc message NLAs")?, }) } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for Vec { fn parse(buf: &TcMessageBuffer<&'a T>) -> Result { let mut nlas = vec![]; let mut kind = String::new(); for nla_buf in buf.nlas() { let buf = nla_buf.context("invalid tc nla")?; let payload = buf.value(); let nla = match buf.kind() { TCA_UNSPEC => Nla::Unspec(payload.to_vec()), TCA_KIND => { kind = parse_string(payload).context("invalid TCA_KIND")?; Nla::Kind(kind.clone()) } TCA_OPTIONS => { let mut nlas = vec![]; for nla in NlasIterator::new(payload) { let nla = nla.context("invalid TCA_OPTIONS")?; nlas.push( TcOpt::parse_with_param(&nla, &kind) .context("failed to parse TCA_OPTIONS")?, ) } Nla::Options(nlas) } TCA_STATS => Nla::Stats( Stats::parse(&StatsBuffer::new_checked(payload).context("invalid TCA_STATS")?) .context("failed to parse TCA_STATS")?, ), TCA_XSTATS => Nla::XStats(payload.to_vec()), TCA_RATE => Nla::Rate(payload.to_vec()), TCA_FCNT => Nla::Fcnt(payload.to_vec()), TCA_STATS2 => { let mut nlas = vec![]; for nla in NlasIterator::new(payload) { let nla = nla.context("invalid TCA_STATS2")?; nlas.push(Stats2::parse(&nla).context("failed to parse TCA_STATS2")?); } Nla::Stats2(nlas) } TCA_STAB => Nla::Stab(payload.to_vec()), TCA_CHAIN => Nla::Chain(payload.to_vec()), TCA_HW_OFFLOAD => { Nla::HwOffload(parse_u8(payload).context("failed to parse TCA_HW_OFFLOAD")?) } _ => Nla::Other(DefaultNla::parse(&buf).context("failed to parse tc nla")?), }; nlas.push(nla); } Ok(nlas) } } ================================================ FILE: netlink-packet-route/src/rtnl/tc/mod.rs ================================================ // SPDX-License-Identifier: MIT mod buffer; pub mod constants; mod message; pub mod nlas; pub use self::{buffer::*, message::*, nlas::*}; #[cfg(test)] mod test; ================================================ FILE: netlink-packet-route/src/rtnl/tc/nlas/action/mirred.rs ================================================ // SPDX-License-Identifier: MIT /// Mirred action /// /// The mirred action allows packet mirroring (copying) or /// redirecting (stealing) the packet it receives. Mirroring is what /// is sometimes referred to as Switch Port Analyzer (SPAN) and is /// commonly used to analyze and/or debug flows. use crate::{ nlas::{self, DefaultNla, NlaBuffer}, tc::{constants::*, TC_GEN_BUF_LEN}, traits::{Emitable, Parseable}, DecodeError, }; pub const KIND: &str = "mirred"; pub const TC_MIRRED_BUF_LEN: usize = TC_GEN_BUF_LEN + 8; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Nla { Unspec(Vec), Tm(Vec), Parms(TcMirred), Other(DefaultNla), } impl nlas::Nla for Nla { fn value_len(&self) -> usize { use self::Nla::*; match self { Unspec(bytes) | Tm(bytes) => bytes.len(), Parms(_) => TC_MIRRED_BUF_LEN, Other(attr) => attr.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::Nla::*; match self { Unspec(bytes) | Tm(bytes) => buffer.copy_from_slice(bytes.as_slice()), Parms(p) => p.emit(buffer), Other(attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Nla::*; match self { Unspec(_) => TCA_MIRRED_UNSPEC, Tm(_) => TCA_MIRRED_TM, Parms(_) => TCA_MIRRED_PARMS, Other(nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nla { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::Nla::*; let payload = buf.value(); Ok(match buf.kind() { TCA_MIRRED_UNSPEC => Unspec(payload.to_vec()), TCA_MIRRED_TM => Tm(payload.to_vec()), TCA_MIRRED_PARMS => Parms(TcMirred::parse(&TcMirredBuffer::new_checked(payload)?)?), _ => Other(DefaultNla::parse(buf)?), }) } } #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct TcMirred { pub index: u32, pub capab: u32, pub action: i32, pub refcnt: i32, pub bindcnt: i32, pub eaction: i32, pub ifindex: u32, } buffer!(TcMirredBuffer(TC_MIRRED_BUF_LEN) { index: (u32, 0..4), capab: (u32, 4..8), action: (i32, 8..12), refcnt: (i32, 12..16), bindcnt: (i32, 16..20), eaction: (i32, TC_GEN_BUF_LEN..(TC_GEN_BUF_LEN + 4)), ifindex: (u32, (TC_GEN_BUF_LEN + 4)..TC_MIRRED_BUF_LEN), }); impl Emitable for TcMirred { fn buffer_len(&self) -> usize { TC_MIRRED_BUF_LEN } fn emit(&self, buffer: &mut [u8]) { let mut packet = TcMirredBuffer::new(buffer); packet.set_index(self.index); packet.set_capab(self.capab); packet.set_action(self.action); packet.set_refcnt(self.refcnt); packet.set_bindcnt(self.bindcnt); packet.set_eaction(self.eaction); packet.set_ifindex(self.ifindex); } } impl> Parseable> for TcMirred { fn parse(buf: &TcMirredBuffer) -> Result { Ok(Self { index: buf.index(), capab: buf.capab(), action: buf.action(), refcnt: buf.refcnt(), bindcnt: buf.bindcnt(), eaction: buf.eaction(), ifindex: buf.ifindex(), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/tc/nlas/action/mod.rs ================================================ // SPDX-License-Identifier: MIT pub mod mirred; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use crate::{ nlas::{self, DefaultNla, NlaBuffer, NlasIterator}, parsers::{parse_string, parse_u32}, tc::{constants::*, Stats2}, traits::{Emitable, Parseable, ParseableParametrized}, DecodeError, }; pub const TC_GEN_BUF_LEN: usize = 20; #[derive(Debug, PartialEq, Eq, Clone)] pub struct Action { pub tab: u16, pub nlas: Vec, } impl Default for Action { fn default() -> Self { Self { tab: TCA_ACT_TAB, nlas: Vec::new(), } } } impl nlas::Nla for Action { fn value_len(&self) -> usize { self.nlas.as_slice().buffer_len() } fn emit_value(&self, buffer: &mut [u8]) { self.nlas.as_slice().emit(buffer) } fn kind(&self) -> u16 { self.tab } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Action { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let mut nlas = vec![]; let mut kind = String::new(); for iter in NlasIterator::new(buf.value()) { let buf = iter.context("invalid action nla")?; let payload = buf.value(); nlas.push(match buf.kind() { TCA_ACT_UNSPEC => ActNla::Unspec(payload.to_vec()), TCA_ACT_KIND => { kind = parse_string(payload).context("failed to parse TCA_ACT_KIND")?; ActNla::Kind(kind.clone()) } TCA_ACT_OPTIONS => { let mut nlas = vec![]; for nla in NlasIterator::new(payload) { let nla = nla.context("invalid TCA_ACT_OPTIONS")?; nlas.push( ActOpt::parse_with_param(&nla, &kind) .context("failed to parse TCA_ACT_OPTIONS")?, ) } ActNla::Options(nlas) } TCA_ACT_INDEX => { ActNla::Index(parse_u32(payload).context("failed to parse TCA_ACT_INDEX")?) } TCA_ACT_STATS => { let mut nlas = vec![]; for nla in NlasIterator::new(payload) { let nla = nla.context("invalid TCA_ACT_STATS")?; nlas.push(Stats2::parse(&nla).context("failed to parse TCA_ACT_STATS")?); } ActNla::Stats(nlas) } TCA_ACT_COOKIE => ActNla::Cookie(payload.to_vec()), _ => ActNla::Other(DefaultNla::parse(&buf).context("failed to parse action nla")?), }); } Ok(Self { tab: buf.kind(), nlas, }) } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum ActNla { Unspec(Vec), Kind(String), Options(Vec), Index(u32), Stats(Vec), Cookie(Vec), Other(DefaultNla), } impl nlas::Nla for ActNla { fn value_len(&self) -> usize { use self::ActNla::*; match self { Unspec(bytes) | Cookie(bytes) => bytes.len(), Kind(k) => k.len() + 1, Options(opt) => opt.as_slice().buffer_len(), Index(_) => 4, Stats(s) => s.as_slice().buffer_len(), Other(attr) => attr.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::ActNla::*; match self { Unspec(bytes) | Cookie(bytes) => buffer.copy_from_slice(bytes.as_slice()), Kind(string) => { buffer[..string.as_bytes().len()].copy_from_slice(string.as_bytes()); buffer[string.as_bytes().len()] = 0; } Options(opt) => opt.as_slice().emit(buffer), Index(value) => NativeEndian::write_u32(buffer, *value), Stats(s) => s.as_slice().emit(buffer), Other(attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::ActNla::*; match self { Unspec(_) => TCA_ACT_UNSPEC, Kind(_) => TCA_ACT_KIND, Options(_) => TCA_ACT_OPTIONS, Index(_) => TCA_ACT_INDEX, Stats(_) => TCA_ACT_STATS, Cookie(_) => TCA_ACT_COOKIE, Other(nla) => nla.kind(), } } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum ActOpt { Mirred(mirred::Nla), // Other options Other(DefaultNla), } impl nlas::Nla for ActOpt { fn value_len(&self) -> usize { use self::ActOpt::*; match self { Mirred(nla) => nla.value_len(), Other(nla) => nla.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::ActOpt::*; match self { Mirred(nla) => nla.emit_value(buffer), Other(nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::ActOpt::*; match self { Mirred(nla) => nla.kind(), Other(nla) => nla.kind(), } } } impl<'a, T, S> ParseableParametrized, S> for ActOpt where T: AsRef<[u8]> + ?Sized, S: AsRef, { fn parse_with_param(buf: &NlaBuffer<&'a T>, kind: S) -> Result { Ok(match kind.as_ref() { mirred::KIND => { Self::Mirred(mirred::Nla::parse(buf).context("failed to parse mirred action")?) } _ => Self::Other(DefaultNla::parse(buf).context("failed to parse action options")?), }) } } #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct TcGen { pub index: u32, pub capab: u32, pub action: i32, pub refcnt: i32, pub bindcnt: i32, } buffer!(TcGenBuffer(TC_GEN_BUF_LEN) { index: (u32, 0..4), capab: (u32, 4..8), action: (i32, 8..12), refcnt: (i32, 12..16), bindcnt: (i32, 16..20), }); impl Emitable for TcGen { fn buffer_len(&self) -> usize { TC_GEN_BUF_LEN } fn emit(&self, buffer: &mut [u8]) { let mut packet = TcGenBuffer::new(buffer); packet.set_index(self.index); packet.set_capab(self.capab); packet.set_action(self.action); packet.set_refcnt(self.refcnt); packet.set_bindcnt(self.bindcnt); } } impl> Parseable> for TcGen { fn parse(buf: &TcGenBuffer) -> Result { Ok(Self { index: buf.index(), capab: buf.capab(), action: buf.action(), refcnt: buf.refcnt(), bindcnt: buf.bindcnt(), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/tc/nlas/filter/mod.rs ================================================ // SPDX-License-Identifier: MIT pub mod u32; ================================================ FILE: netlink-packet-route/src/rtnl/tc/nlas/filter/u32.rs ================================================ // SPDX-License-Identifier: MIT /// U32 filter /// /// In its simplest form the U32 filter is a list of records, each consisting /// of two fields: a selector and an action. The selectors, described below, /// are compared with the currently processed IP packet until the first match /// occurs, and then the associated action is performed. use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use crate::{ nlas::{self, DefaultNla, NlaBuffer, NlasIterator}, parsers::parse_u32, tc::{constants::*, Action}, traits::{Emitable, Parseable}, DecodeError, }; pub const KIND: &str = "u32"; const U32_SEL_BUF_LEN: usize = 16; const U32_KEY_BUF_LEN: usize = 16; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Nla { Unspec(Vec), ClassId(u32), Hash(u32), Link(u32), Divisor(u32), Sel(Sel), Police(Vec), Act(Vec), Indev(Vec), Pcnt(Vec), Mark(Vec), Flags(u32), Other(DefaultNla), } impl nlas::Nla for Nla { fn value_len(&self) -> usize { use self::Nla::*; match self { Unspec(b) | Police(b) | Indev(b) | Pcnt(b) | Mark(b) => b.len(), ClassId(_) | Hash(_) | Link(_) | Divisor(_) | Flags(_) => 4, Sel(s) => s.buffer_len(), Act(acts) => acts.as_slice().buffer_len(), Other(attr) => attr.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::Nla::*; match self { Unspec(b) | Police(b) | Indev(b) | Pcnt(b) | Mark(b) => { buffer.copy_from_slice(b.as_slice()) } ClassId(i) | Hash(i) | Link(i) | Divisor(i) | Flags(i) => { NativeEndian::write_u32(buffer, *i) } Sel(s) => s.emit(buffer), Act(acts) => acts.as_slice().emit(buffer), Other(attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Nla::*; match self { Unspec(_) => TCA_U32_UNSPEC, ClassId(_) => TCA_U32_CLASSID, Hash(_) => TCA_U32_HASH, Link(_) => TCA_U32_LINK, Divisor(_) => TCA_U32_DIVISOR, Sel(_) => TCA_U32_SEL, Police(_) => TCA_U32_POLICE, Act(_) => TCA_U32_ACT, Indev(_) => TCA_U32_INDEV, Pcnt(_) => TCA_U32_PCNT, Mark(_) => TCA_U32_MARK, Flags(_) => TCA_U32_FLAGS, Other(attr) => attr.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nla { fn parse(buf: &NlaBuffer<&'a T>) -> Result { use self::Nla::*; let payload = buf.value(); Ok(match buf.kind() { TCA_U32_UNSPEC => Unspec(payload.to_vec()), TCA_U32_CLASSID => { ClassId(parse_u32(payload).context("failed to parse TCA_U32_UNSPEC")?) } TCA_U32_HASH => Hash(parse_u32(payload).context("failed to parse TCA_U32_HASH")?), TCA_U32_LINK => Link(parse_u32(payload).context("failed to parse TCA_U32_LINK")?), TCA_U32_DIVISOR => { Divisor(parse_u32(payload).context("failed to parse TCA_U32_DIVISOR")?) } TCA_U32_SEL => Sel(self::Sel::parse( &SelBuffer::new_checked(payload).context("invalid TCA_U32_SEL")?, ) .context("failed to parse TCA_U32_SEL")?), TCA_U32_POLICE => Police(payload.to_vec()), TCA_U32_ACT => { let mut acts = vec![]; for act in NlasIterator::new(payload) { let act = act.context("invalid TCA_U32_ACT")?; acts.push(Action::parse(&act).context("failed to parse TCA_U32_ACT")?); } Act(acts) } TCA_U32_INDEV => Indev(payload.to_vec()), TCA_U32_PCNT => Pcnt(payload.to_vec()), TCA_U32_MARK => Mark(payload.to_vec()), TCA_U32_FLAGS => Flags(parse_u32(payload).context("failed to parse TCA_U32_FLAGS")?), _ => Other(DefaultNla::parse(buf).context("failed to parse u32 nla")?), }) } } #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct Sel { pub flags: u8, pub offshift: u8, pub nkeys: u8, pub offmask: u16, pub off: u16, pub offoff: u16, pub hoff: u16, pub hmask: u32, pub keys: Vec, } buffer!(SelBuffer(U32_SEL_BUF_LEN) { flags: (u8, 0), offshift: (u8, 1), nkeys: (u8, 2), //pad: (u8, 3), offmask: (u16, 4..6), off: (u16, 6..8), offoff: (u16, 8..10), hoff: (u16, 10..12), hmask: (u32, 12..U32_SEL_BUF_LEN), keys: (slice, U32_SEL_BUF_LEN..), }); impl Emitable for Sel { fn buffer_len(&self) -> usize { U32_SEL_BUF_LEN + (self.nkeys as usize * U32_KEY_BUF_LEN) } fn emit(&self, buffer: &mut [u8]) { let mut packet = SelBuffer::new(buffer); packet.set_flags(self.flags); packet.set_offshift(self.offshift); packet.set_offmask(self.offmask); packet.set_off(self.off); packet.set_offoff(self.offoff); packet.set_hoff(self.hoff); packet.set_hmask(self.hmask); packet.set_nkeys(self.nkeys); assert_eq!(self.nkeys as usize, self.keys.len()); let key_buf = packet.keys_mut(); for (i, k) in self.keys.iter().enumerate() { k.emit(&mut key_buf[(i * U32_KEY_BUF_LEN)..((i + 1) * U32_KEY_BUF_LEN)]); } } } impl + ?Sized> Parseable> for Sel { fn parse(buf: &SelBuffer<&T>) -> Result { let nkeys = buf.nkeys(); let mut keys = Vec::::with_capacity(nkeys.into()); let key_payload = buf.keys(); for i in 0..nkeys { let i = i as usize; let keybuf = KeyBuffer::new_checked( &key_payload[(i * U32_KEY_BUF_LEN)..(i + 1) * U32_KEY_BUF_LEN], ) .context("invalid u32 key")?; keys.push(Key::parse(&keybuf).context("failed to parse u32 key")?); } Ok(Self { flags: buf.flags(), offshift: buf.offshift(), nkeys, offmask: buf.offmask(), off: buf.off(), offoff: buf.offoff(), hoff: buf.hoff(), hmask: buf.hmask(), keys, }) } } #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct Key { pub mask: u32, pub val: u32, pub off: i32, pub offmask: i32, } buffer!(KeyBuffer(U32_KEY_BUF_LEN) { mask: (u32, 0..4), val: (u32, 4..8), off: (i32, 8..12), offmask: (i32, 12..U32_KEY_BUF_LEN), }); impl Emitable for Key { fn buffer_len(&self) -> usize { U32_KEY_BUF_LEN } fn emit(&self, buffer: &mut [u8]) { let mut packet = KeyBuffer::new(buffer); packet.set_mask(self.mask); packet.set_val(self.val); packet.set_off(self.off); packet.set_offmask(self.offmask); } } impl> Parseable> for Key { fn parse(buf: &KeyBuffer) -> Result { Ok(Self { mask: buf.mask(), val: buf.val(), off: buf.off(), offmask: buf.offmask(), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/tc/nlas/mod.rs ================================================ // SPDX-License-Identifier: MIT mod stats; pub use self::stats::*; mod stats_queue; pub use self::stats_queue::*; mod stats_basic; pub use self::stats_basic::*; mod options; pub use self::options::*; mod qdisc; pub use self::qdisc::*; mod filter; pub use self::filter::*; mod action; pub use self::action::*; #[cfg(test)] mod test; use crate::{ constants::*, nlas::{self, DefaultNla, NlaBuffer}, traits::{Emitable, Parseable}, DecodeError, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Nla { /// Unspecified Unspec(Vec), /// Name of queueing discipline Kind(String), /// Options follow Options(Vec), /// Statistics Stats(Stats), /// Module-specific statistics XStats(Vec), /// Rate limit Rate(Vec), Fcnt(Vec), Stats2(Vec), Stab(Vec), Chain(Vec), HwOffload(u8), Other(DefaultNla), } impl nlas::Nla for Nla { #[rustfmt::skip] fn value_len(&self) -> usize { use self::Nla::*; match *self { // Vec Unspec(ref bytes) | XStats(ref bytes) | Rate(ref bytes) | Fcnt(ref bytes) | Stab(ref bytes) | Chain(ref bytes) => bytes.len(), HwOffload(_) => 1, Stats2(ref thing) => thing.as_slice().buffer_len(), Stats(_) => STATS_LEN, Kind(ref string) => string.as_bytes().len() + 1, Options(ref opt) => opt.as_slice().buffer_len(), // Defaults Other(ref attr) => attr.value_len(), } } #[cfg_attr(nightly, rustfmt::skip)] fn emit_value(&self, buffer: &mut [u8]) { use self::Nla::*; match *self { // Vec Unspec(ref bytes) | XStats(ref bytes) | Rate(ref bytes) | Fcnt(ref bytes) | Stab(ref bytes) | Chain(ref bytes) => buffer.copy_from_slice(bytes.as_slice()), HwOffload(ref val) => buffer[0] = *val, Stats2(ref stats) => stats.as_slice().emit(buffer), Stats(ref stats) => stats.emit(buffer), Kind(ref string) => { buffer[..string.as_bytes().len()].copy_from_slice(string.as_bytes()); buffer[string.as_bytes().len()] = 0; } Options(ref opt) => opt.as_slice().emit(buffer), // Default Other(ref attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Nla::*; match *self { Unspec(_) => TCA_UNSPEC, Kind(_) => TCA_KIND, Options(_) => TCA_OPTIONS, Stats(_) => TCA_STATS, XStats(_) => TCA_XSTATS, Rate(_) => TCA_RATE, Fcnt(_) => TCA_FCNT, Stats2(_) => TCA_STATS2, Stab(_) => TCA_STAB, Chain(_) => TCA_CHAIN, HwOffload(_) => TCA_HW_OFFLOAD, Other(ref nla) => nla.kind(), } } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum Stats2 { StatsApp(Vec), StatsBasic(Vec), StatsQueue(Vec), Other(DefaultNla), } impl nlas::Nla for Stats2 { fn value_len(&self) -> usize { use self::Stats2::*; match *self { StatsBasic(ref bytes) | StatsQueue(ref bytes) | StatsApp(ref bytes) => bytes.len(), Other(ref nla) => nla.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::Stats2::*; match *self { StatsBasic(ref bytes) | StatsQueue(ref bytes) | StatsApp(ref bytes) => { buffer.copy_from_slice(bytes.as_slice()) } Other(ref nla) => nla.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Stats2::*; match *self { StatsApp(_) => TCA_STATS_APP, StatsBasic(_) => TCA_STATS_BASIC, StatsQueue(_) => TCA_STATS_QUEUE, Other(ref nla) => nla.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Stats2 { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { TCA_STATS_APP => Self::StatsApp(payload.to_vec()), TCA_STATS_BASIC => Self::StatsBasic(payload.to_vec()), TCA_STATS_QUEUE => Self::StatsQueue(payload.to_vec()), _ => Self::Other(DefaultNla::parse(buf)?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/tc/nlas/options.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use crate::{ nlas::{self, DefaultNla, NlaBuffer}, tc::{ingress, u32}, traits::{Parseable, ParseableParametrized}, DecodeError, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum TcOpt { // Qdisc specific options Ingress, // Filter specific options U32(u32::Nla), // Other options Other(DefaultNla), } impl nlas::Nla for TcOpt { fn value_len(&self) -> usize { match self { Self::Ingress => 0, Self::U32(u) => u.value_len(), Self::Other(o) => o.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { match self { Self::Ingress => unreachable!(), Self::U32(u) => u.emit_value(buffer), Self::Other(o) => o.emit_value(buffer), } } fn kind(&self) -> u16 { match self { Self::Ingress => unreachable!(), Self::U32(u) => u.kind(), Self::Other(o) => o.kind(), } } } impl<'a, T, S> ParseableParametrized, S> for TcOpt where T: AsRef<[u8]> + ?Sized, S: AsRef, { fn parse_with_param(buf: &NlaBuffer<&'a T>, kind: S) -> Result { Ok(match kind.as_ref() { ingress::KIND => TcOpt::Ingress, u32::KIND => Self::U32(u32::Nla::parse(buf).context("failed to parse u32 nlas")?), _ => Self::Other(DefaultNla::parse(buf)?), }) } } ================================================ FILE: netlink-packet-route/src/rtnl/tc/nlas/qdisc/mod.rs ================================================ // SPDX-License-Identifier: MIT pub mod ingress { pub const KIND: &str = "ingress"; } ================================================ FILE: netlink-packet-route/src/rtnl/tc/nlas/stats.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; /// Generic queue statistics #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Stats { /// Number of enqueued bytes pub bytes: u64, /// Number of enqueued packets pub packets: u32, /// Packets dropped because of lack of resources pub drops: u32, /// Number of throttle events when this flow goes out of allocated bandwidth pub overlimits: u32, /// Current flow byte rate pub bps: u32, /// Current flow packet rate pub pps: u32, pub qlen: u32, pub backlog: u32, } pub const STATS_LEN: usize = 36; buffer!(StatsBuffer(STATS_LEN) { bytes: (u64, 0..8), packets: (u32, 8..12), drops: (u32, 12..16), overlimits: (u32, 16..20), bps: (u32, 20..24), pps: (u32, 24..28), qlen: (u32, 28..32), backlog: (u32, 32..36), }); impl> Parseable> for Stats { fn parse(buf: &StatsBuffer) -> Result { Ok(Self { bytes: buf.bytes(), packets: buf.packets(), drops: buf.drops(), overlimits: buf.overlimits(), bps: buf.bps(), pps: buf.pps(), qlen: buf.qlen(), backlog: buf.backlog(), }) } } impl Emitable for Stats { fn buffer_len(&self) -> usize { STATS_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = StatsBuffer::new(buffer); buffer.set_bytes(self.bytes); buffer.set_packets(self.packets); buffer.set_drops(self.drops); buffer.set_overlimits(self.overlimits); buffer.set_bps(self.bps); buffer.set_pps(self.pps); buffer.set_qlen(self.qlen); buffer.set_backlog(self.backlog); } } ================================================ FILE: netlink-packet-route/src/rtnl/tc/nlas/stats_basic.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; /// Byte/Packet throughput statistics #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct StatsBasic { /// number of seen bytes pub bytes: u64, /// number of seen packets pub packets: u32, } pub const STATS_BASIC_LEN: usize = 12; buffer!(StatsBasicBuffer(STATS_BASIC_LEN) { bytes: (u64, 0..8), packets: (u32, 8..12), }); impl> Parseable> for StatsBasic { fn parse(buf: &StatsBasicBuffer) -> Result { Ok(StatsBasic { bytes: buf.bytes(), packets: buf.packets(), }) } } impl Emitable for StatsBasic { fn buffer_len(&self) -> usize { STATS_BASIC_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = StatsBasicBuffer::new(buffer); buffer.set_bytes(self.bytes); buffer.set_packets(self.packets); } } ================================================ FILE: netlink-packet-route/src/rtnl/tc/nlas/stats_queue.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ traits::{Emitable, Parseable}, DecodeError, }; /// Queuing statistics #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct StatsQueue { /// queue length pub qlen: u32, /// backlog size of queue pub backlog: u32, /// number of dropped packets pub drops: u32, /// number of requeues pub requeues: u32, /// number of enqueues over the limit pub overlimits: u32, } pub const STATS_QUEUE_LEN: usize = 20; buffer!(StatsQueueBuffer( STATS_QUEUE_LEN) { qlen: (u32, 0..4), backlog: (u32, 4..8), drops: (u32, 8..12), requeues: (u32, 12..16), overlimits: (u32, 16..20), }); impl> Parseable> for StatsQueue { fn parse(buf: &StatsQueueBuffer) -> Result { Ok(Self { qlen: buf.qlen(), backlog: buf.backlog(), drops: buf.drops(), requeues: buf.requeues(), overlimits: buf.overlimits(), }) } } impl Emitable for StatsQueue { fn buffer_len(&self) -> usize { STATS_QUEUE_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = StatsQueueBuffer::new(buffer); buffer.set_qlen(self.qlen); buffer.set_backlog(self.backlog); buffer.set_drops(self.drops); buffer.set_requeues(self.requeues); buffer.set_overlimits(self.overlimits); } } ================================================ FILE: netlink-packet-route/src/rtnl/tc/nlas/test.rs ================================================ // SPDX-License-Identifier: MIT #![cfg(test)] use crate::{ constants::*, nlas::Nla, parsers::parse_u32, tc::{self, constants::*, mirred, u32, ActNla, ActOpt, Action, Stats2, TcOpt}, traits::{Emitable, Parseable}, TcHeader, TcMessage, TcMessageBuffer, }; #[rustfmt::skip] static FILTER_U32_ACTION_PACKET: [u8; 260] = [ 0, 0, 0, 0, // family, pad1, pad2 3, 0, 0, 0, // Interface index 0, 8, 0, 128, // handle: 0x800_00_800 => htid | hash | nodeid 255, 255, 255, 255, // parent: TC_H_ROOT 0, 3, 0, 192, // info: 0xc000_0300 => pref | protocol // nlas 8, 0, // length 1, 0, // type: TCA_KIND 117, 51, 50, 0, // u32\0 8, 0, 11, 0, // type: TCA_CHAIN 0, 0, 0, 0, 224, 0, 2, 0, // type: TCA_OPTIONS 36, 0, 5, 0, // type: TCA_U32_SEL 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 2, 0, // TCA_U32_HASH 0, 0, 0, 128, 8, 0, 11, 0, // TCA_U32_FLAGS 8, 0, 0, 0, 140, 0, 7, 0, // TCA_U32_ACT 136, 0, 1, 0, // TCA_ACT_TAB 11, 0, 1, 0, // TCA_ACT_KIND 109, 105, 114, 114, 101, 100, 0, 0, // "mirred\0" 48, 0, 4, 0, // TCA_ACT_STATS 20, 0, 1, 0, // TCA_STATS_BASIC 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 3, 0, // TCA_STATS_QUEUE 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 0, 2, 0, // TCA_ACT_OPTIONS 32, 0, 2, 0, // TCA_MIRRED_PARMS 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 36, 0, 1, 0, // TCA_MIRRED_TM 189, 117, 195, 9, 0, 0, 0, 0, 189, 117, 195, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 226, 238, 72, 0, 0, 0, 0, 28, 0, 9, 0, // TCA_U32_PCNT 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; #[test] #[allow(clippy::unusual_byte_groupings)] fn tc_filter_u32_read() { let packet = TcMessageBuffer::new(&FILTER_U32_ACTION_PACKET); assert_eq!(packet.family(), 0); assert_eq!(packet.index(), 3); assert_eq!(packet.handle(), 0x800_00_800); assert_eq!(packet.parent(), 0xffffffff); assert_eq!(packet.info(), 0xc000_0300); assert_eq!(packet.nlas().count(), 3); let mut nlas = packet.nlas(); let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 8); assert_eq!(nla.kind(), TCA_KIND); assert_eq!(nla.value(), "u32\0".as_bytes()); let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 8); assert_eq!(nla.kind(), TCA_CHAIN); assert_eq!(parse_u32(nla.value()).unwrap(), 0); let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 224); assert_eq!(nla.kind(), TCA_OPTIONS); } #[test] fn tc_filter_u32_parse() { let packet = TcMessageBuffer::new_checked(&FILTER_U32_ACTION_PACKET).unwrap(); // TcMessage let msg = TcMessage::parse(&packet).unwrap(); assert_eq!(msg.header.index, 3); assert_eq!(msg.header.info, 0xc000_0300); assert_eq!(msg.nlas.len(), 3); // Nlas let mut iter = msg.nlas.iter(); // TCA_KIND assert_eq!( iter.next().unwrap(), &tc::Nla::Kind(String::from(u32::KIND)) ); // TCA_CHAIN assert!(matches!(iter.next().unwrap(), &tc::Nla::Chain(_))); // TCA_OPTIONS let nla = iter.next().unwrap(); let filter = if let tc::Nla::Options(f) = nla { assert_eq!(f.len(), 5); f } else { panic!("expect options nla"); }; // u32 option let mut fi = filter.iter(); let fa = fi.next().unwrap(); let ua = if let TcOpt::U32(u) = fa { u } else { panic!("expect u32 nla"); }; // TCA_U32_SEL let sel = if let u32::Nla::Sel(s) = ua { s } else { panic!("expect sel nla"); }; assert_eq!(sel.flags, TC_U32_TERMINAL); assert_eq!(sel.nkeys, 1); assert_eq!(sel.keys.len(), 1); assert_eq!(sel.keys[0], u32::Key::default()); // TCA_U32_HASH assert_eq!(fi.next().unwrap(), &TcOpt::U32(u32::Nla::Hash(0x80000000))); // TCA_U32_FLAGS assert_eq!(fi.next().unwrap(), &TcOpt::U32(u32::Nla::Flags(0x00000008))); // TCA_U32_ACT let fa = fi.next().unwrap(); let acts = if let TcOpt::U32(u) = fa { if let u32::Nla::Act(a) = u { a } else { panic!("expect u32 action"); } } else { panic!("expect u32 nla"); }; // TCA_ACT_TAB let mut act_iter = acts.iter(); let act = act_iter.next().unwrap(); assert_eq!(act.kind(), 1); // TCA_ACT_TAB assert_eq!(act.buffer_len(), 136); // TCA_ACT_TAB assert_eq!(act.tab, 1); let mut act_nlas_iter = act.nlas.iter(); // TCA_ACT_KIND assert_eq!( act_nlas_iter.next().unwrap(), &ActNla::Kind("mirred".to_string()) ); // TCA_ACT_STATS assert!(matches!(act_nlas_iter.next().unwrap(), ActNla::Stats(_))); // TCA_ACT_OPTIONS let act_nla = act_nlas_iter.next().unwrap(); let act_opts = if let ActNla::Options(opts) = act_nla { opts } else { panic!("expect action options"); }; let mut act_opts_iter = act_opts.iter(); // TCA_MIRRED_PARMS let act_opt = act_opts_iter.next().unwrap(); if let ActOpt::Mirred(mirred::Nla::Parms(p)) = act_opt { assert_eq!(p.index, 1); assert_eq!(p.capab, 0); assert_eq!(p.action, 4); assert_eq!(p.refcnt, 1); assert_eq!(p.bindcnt, 1); assert_eq!(p.eaction, 1); assert_eq!(p.ifindex, 3); } else { panic!("expect action mirred"); } // TCA_MIRRED_TM let act_opt = act_opts_iter.next().unwrap(); assert_eq!(act_opt.kind(), TCA_MIRRED_TM); assert_eq!(act_opt.buffer_len(), 36); // TCA_U32_PCNT let fa = fi.next().unwrap(); assert_eq!(fa.kind(), TCA_U32_PCNT); assert_eq!(fa.buffer_len(), 28); } #[test] #[allow(clippy::unusual_byte_groupings)] fn tc_filter_u32_emit() { // TcHeader let header = TcHeader { index: 3, handle: 0x800_00_800, parent: 0xffffffff, info: 0xc000_0300, ..Default::default() }; // Tc Nlas let nlas = vec![ tc::Nla::Kind(u32::KIND.to_string()), tc::Nla::Chain(vec![0, 0, 0, 0]), tc::Nla::Options(vec![ TcOpt::U32(u32::Nla::Sel(u32::Sel { flags: TC_U32_TERMINAL, offshift: 0, nkeys: 1, offmask: 0, off: 0, offoff: 0, hoff: 0, hmask: 0, keys: vec![u32::Key::default()], })), TcOpt::U32(u32::Nla::Hash(0x80000000)), TcOpt::U32(u32::Nla::Flags(0x00000008)), TcOpt::U32(u32::Nla::Act(vec![Action { tab: TCA_ACT_TAB, nlas: vec![ ActNla::Kind(mirred::KIND.to_string()), ActNla::Stats(vec![ Stats2::StatsBasic(vec![0u8; 16]), Stats2::StatsQueue(vec![0u8; 20]), ]), ActNla::Options(vec![ ActOpt::Mirred(mirred::Nla::Parms(mirred::TcMirred { index: 1, capab: 0, action: 4, refcnt: 1, bindcnt: 1, eaction: 1, ifindex: 3, })), ActOpt::Mirred(mirred::Nla::Tm( FILTER_U32_ACTION_PACKET[200..232].to_vec(), )), ]), ], }])), TcOpt::U32(u32::Nla::Pcnt(vec![0u8; 24])), ]), ]; let msg = TcMessage::from_parts(header, nlas); let mut buf = vec![0; 260]; assert_eq!(msg.buffer_len(), 260); msg.emit(&mut buf[..]); assert_eq!(&buf, &FILTER_U32_ACTION_PACKET); } ================================================ FILE: netlink-packet-route/src/rtnl/tc/test.rs ================================================ // SPDX-License-Identifier: MIT #![cfg(test)] use crate::{ constants::*, nlas::NlasIterator, tc::{ingress, Nla, Stats, Stats2, StatsBuffer, TC_HEADER_LEN}, traits::{Emitable, Parseable}, TcHeader, TcMessage, TcMessageBuffer, }; #[rustfmt::skip] static QDISC_INGRESS_PACKET: [u8; 136] = [ 0, // family 0, 0, 0, // pad1 + pad2 84, 0, 0, 0, // Interface index = 84 0, 0, 255, 255, // handle: 0xffff0000 241, 255, 255, 255, // parent: 0xfffffff1 1, 0, 0, 0, // info: refcnt: 1 // nlas 12, 0, // length 1, 0, // type: TCA_KIND 105, 110, 103, 114, 101, 115, 115, 0, // ingress\0 4, 0, // length 2, 0, // type: TCA_OPTIONS 5, 0, // length 12, 0,// type: TCA_HW_OFFLOAD 0, // data: 0 0, 0, 0,// padding 48, 0, // length 7, 0, // type: TCA_STATS2 20, 0, // length 1, 0, // type: TCA_STATS_BASIC 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 3, 0, // type: TCA_STATS_QUEUE 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, // length 3, 0, // type: TCA_STATS 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; #[test] fn tc_packet_header_read() { let packet = TcMessageBuffer::new(QDISC_INGRESS_PACKET); assert_eq!(packet.family(), 0); assert_eq!(packet.index(), 84); assert_eq!(packet.handle(), 0xffff0000); assert_eq!(packet.parent(), 0xfffffff1); assert_eq!(packet.info(), 1); } #[test] fn tc_packet_header_build() { let mut buf = vec![0xff; TC_HEADER_LEN]; { let mut packet = TcMessageBuffer::new(&mut buf); packet.set_family(0); packet.set_pad1(0); packet.set_pad2(0); packet.set_index(84); packet.set_handle(0xffff0000); packet.set_parent(0xfffffff1); packet.set_info(1); } assert_eq!(&buf[..], &QDISC_INGRESS_PACKET[0..TC_HEADER_LEN]); } #[test] fn tc_packet_nlas_read() { let packet = TcMessageBuffer::new(&QDISC_INGRESS_PACKET[..]); assert_eq!(packet.nlas().count(), 5); let mut nlas = packet.nlas(); let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 12); assert_eq!(nla.kind(), TCA_KIND); assert_eq!(nla.value(), "ingress\0".as_bytes()); let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 4); assert_eq!(nla.kind(), TCA_OPTIONS); assert_eq!(nla.value(), []); let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 5); assert_eq!(nla.kind(), TCA_HW_OFFLOAD); assert_eq!(nla.value(), [0]); let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 48); assert_eq!(nla.kind(), TCA_STATS2); let mut stats2_iter = NlasIterator::new(nla.value()); let stats2_nla = stats2_iter.next().unwrap().unwrap(); stats2_nla.check_buffer_length().unwrap(); assert_eq!(stats2_nla.length(), 20); assert_eq!(stats2_nla.kind(), TCA_STATS_BASIC); assert_eq!(stats2_nla.value(), [0; 16]); let s2 = Stats2::parse(&stats2_nla).unwrap(); assert!(matches!(s2, Stats2::StatsBasic(_))); let stats2_nla = stats2_iter.next().unwrap().unwrap(); stats2_nla.check_buffer_length().unwrap(); assert_eq!(stats2_nla.length(), 24); assert_eq!(stats2_nla.kind(), TCA_STATS_QUEUE); assert_eq!(stats2_nla.value(), [0; 20]); let s2 = Stats2::parse(&stats2_nla).unwrap(); assert!(matches!(s2, Stats2::StatsQueue(_))); let nla = nlas.next().unwrap().unwrap(); nla.check_buffer_length().unwrap(); assert_eq!(nla.length(), 44); assert_eq!(nla.kind(), TCA_STATS); assert_eq!(nla.value(), [0; 40]); let s = Stats::parse(&StatsBuffer::new(nla.value())).unwrap(); assert_eq!(s.packets, 0); assert_eq!(s.backlog, 0); } #[test] fn tc_qdisc_ingress_emit() { let header = TcHeader { index: 84, handle: 0xffff0000, parent: 0xfffffff1, info: 1, ..Default::default() }; let nlas = vec![Nla::Kind(ingress::KIND.into()), Nla::Options(vec![])]; let msg = TcMessage::from_parts(header, nlas); let mut buf = vec![0; 36]; assert_eq!(msg.buffer_len(), 36); msg.emit(&mut buf[..]); assert_eq!(&buf, &QDISC_INGRESS_PACKET[..36]); } #[test] fn tc_qdisc_ingress_read() { let packet = TcMessageBuffer::new_checked(&QDISC_INGRESS_PACKET).unwrap(); let msg = TcMessage::parse(&packet).unwrap(); assert_eq!(msg.header.index, 84); assert_eq!(msg.nlas.len(), 5); let mut iter = msg.nlas.iter(); let nla = iter.next().unwrap(); assert_eq!(nla, &Nla::Kind(String::from(ingress::KIND))); let nla = iter.next().unwrap(); assert_eq!(nla, &Nla::Options(vec![])); let nla = iter.next().unwrap(); assert_eq!(nla, &Nla::HwOffload(0)); } ================================================ FILE: netlink-packet-route/src/rtnl/test.rs ================================================ // SPDX-License-Identifier: MIT #![cfg(test)] use crate::{ nlas::link::{Info, InfoKind, Nla}, traits::ParseableParametrized, LinkHeader, LinkMessage, NetlinkBuffer, RtnlMessage, RtnlMessageBuffer, RTM_NEWLINK, }; // This test was added because one of the NLA's payload is a string that is not null // terminated. I'm not sure if we missed something in the IFLA_LINK_INFO spec, or if // linux/iproute2 is being a bit inconsistent here. // // This message was created using `ip link add qemu-br1 type bridge`. #[rustfmt::skip] #[test] fn test_non_null_terminated_string() { let data = vec![ 0x40, 0x00, 0x00, 0x00, // length = 64 0x10, 0x00, // message type = 16 = (create network interface) 0x05, 0x06, // flags 0x81, 0x74, 0x57, 0x5c, // seq id 0x00, 0x00, 0x00, 0x00, // pid 0x00, // interface family 0x00, // padding 0x00, 0x00, // device type (NET/ROM pseudo) 0x00, 0x00, 0x00, 0x00, // interface index 0x00, 0x00, 0x00, 0x00, // device flags 0x00, 0x00, 0x00, 0x00, // device change flags // NLA: device name 0x0d, 0x00, // length = 13 0x03, 0x00, // type = 3 // value=qemu-br1 NOTE THAT THIS IS NULL-TERMINATED 0x71, 0x65, 0x6d, 0x75, 0x2d, 0x62, 0x72, 0x31, 0x00, 0x00, 0x00, 0x00, // padding // NLA: Link info 0x10, 0x00, // length = 16 0x12, 0x00, // type = link info // nested NLA: 0x0a, 0x00, // length = 10 0x01, 0x00, // type = 1 = IFLA_INFO_KIND // "bridge" NOTE THAT THIS IS NOT NULL-TERMINATED! 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x00, 0x00, // padding ]; let expected = RtnlMessage::NewLink(LinkMessage { header: LinkHeader::default(), nlas: vec![ Nla::IfName(String::from("qemu-br1")), Nla::Info(vec![Info::Kind(InfoKind::Bridge)]), ], }); let nl_buffer = NetlinkBuffer::new(&data).payload(); let rtnl_buffer = RtnlMessageBuffer::new(&nl_buffer); let actual = RtnlMessage::parse_with_param(&rtnl_buffer, RTM_NEWLINK).unwrap(); assert_eq!(expected, actual); } #[rustfmt::skip] #[test] fn test_attach_to_bridge() { use crate::*; let data = vec![ 0x28, 0x00, 0x00, 0x00, // length 0x10, 0x00, // type 0x05, 0x00, // flags 0x9c, 0x9d, 0x57, 0x5c, // seq id 0x00, 0x00, 0x00, 0x00, // pid 0x00, // interface family 0x00, // padding 0x00, 0x00, // device type 0x06, 0x00, 0x00, 0x00, // interface index 0x00, 0x00, 0x00, 0x00, // device flags 0x00, 0x00, 0x00, 0x00, // device change flags // NLA (set master) 0x08, 0x00, // length 0x0a, 0x00, // type 0x05, 0x00, 0x00, 0x00 // index of the master interface ]; let nl_buffer = NetlinkBuffer::new(&data).payload(); let rtnl_buffer = RtnlMessageBuffer::new(&nl_buffer); let actual = RtnlMessage::parse_with_param(&rtnl_buffer, RTM_NEWLINK).unwrap(); let expected = RtnlMessage::NewLink(LinkMessage { header: LinkHeader { interface_family: 0, index: 6, link_layer_type: 0, flags: 0, change_mask: 0, }, nlas: vec![Nla::Master(5)], }); assert_eq!(expected, actual); } ================================================ FILE: netlink-packet-sock-diag/Cargo.toml ================================================ [package] authors = ["Flier Lu ", "Corentin Henry "] name = "netlink-packet-sock-diag" version = "0.3.1" edition = "2018" homepage = "https://github.com/little-dude/netlink" keywords = ["netlink", "linux", "sock_diag"] license = "MIT" readme = "../README.md" repository = "https://github.com/little-dude/netlink" description = "netlink packet types for the sock_diag subprotocol" [features] rich_nlas = [] [dependencies] anyhow = "1.0.32" byteorder = "1.3.4" netlink-packet-core = { version = "0.4.2", path = "../netlink-packet-core" } netlink-packet-utils = { version = "0.5.1", path = "../netlink-packet-utils" } bitflags = "1.2.1" libc = "0.2.77" smallvec = "1.4.2" [dev-dependencies] lazy_static = "1.4.0" netlink-sys = { version = "0.8.3", path = "../netlink-sys" } ================================================ FILE: netlink-packet-sock-diag/examples/dump_ipv4.rs ================================================ // SPDX-License-Identifier: MIT use netlink_packet_sock_diag::{ constants::*, inet::{ExtensionFlags, InetRequest, SocketId, StateFlags}, NetlinkHeader, NetlinkMessage, NetlinkPayload, SockDiagMessage, }; use netlink_sys::{protocols::NETLINK_SOCK_DIAG, Socket, SocketAddr}; fn main() { let mut socket = Socket::new(NETLINK_SOCK_DIAG).unwrap(); let _port_number = socket.bind_auto().unwrap().port_number(); socket.connect(&SocketAddr::new(0, 0)).unwrap(); let mut packet = NetlinkMessage { header: NetlinkHeader { flags: NLM_F_REQUEST | NLM_F_DUMP, ..Default::default() }, payload: SockDiagMessage::InetRequest(InetRequest { family: AF_INET, protocol: IPPROTO_TCP, extensions: ExtensionFlags::empty(), states: StateFlags::all(), socket_id: SocketId::new_v4(), }) .into(), }; packet.finalize(); let mut buf = vec![0; packet.header.length as usize]; // Before calling serialize, it is important to check that the buffer in which we're emitting is big // enough for the packet, other `serialize()` panics. assert_eq!(buf.len(), packet.buffer_len()); packet.serialize(&mut buf[..]); println!(">>> {:?}", packet); if let Err(e) = socket.send(&buf[..], 0) { println!("SEND ERROR {}", e); return; } let mut receive_buffer = vec![0; 4096]; let mut offset = 0; while let Ok(size) = socket.recv(&mut &mut receive_buffer[..], 0) { loop { let bytes = &receive_buffer[offset..]; let rx_packet = >::deserialize(bytes).unwrap(); println!("<<< {:?}", rx_packet); match rx_packet.payload { NetlinkPayload::Noop | NetlinkPayload::Ack(_) => {} NetlinkPayload::InnerMessage(SockDiagMessage::InetResponse(response)) => { println!("{:#?}", response); } NetlinkPayload::Done => { println!("Done!"); return; } NetlinkPayload::Error(_) | NetlinkPayload::Overrun(_) | _ => return, } offset += rx_packet.header.length as usize; if offset == size || rx_packet.header.length == 0 { offset = 0; break; } } } } ================================================ FILE: netlink-packet-sock-diag/src/buffer.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ constants::*, inet, traits::{Parseable, ParseableParametrized}, unix, DecodeError, SockDiagMessage, }; use anyhow::Context; const BUF_MIN_LEN: usize = 2; pub struct SockDiagBuffer { buffer: T, } impl> SockDiagBuffer { pub fn new(buffer: T) -> SockDiagBuffer { SockDiagBuffer { buffer } } pub fn length(&self) -> usize { self.buffer.as_ref().len() } pub fn new_checked(buffer: T) -> Result { let packet = Self::new(buffer); packet.check_len()?; Ok(packet) } pub(crate) fn check_len(&self) -> Result<(), DecodeError> { let len = self.buffer.as_ref().len(); if len < BUF_MIN_LEN { return Err(format!( "invalid buffer: length is {} but packets are at least {} bytes", len, BUF_MIN_LEN ) .into()); } Ok(()) } pub(crate) fn family(&self) -> u8 { self.buffer.as_ref()[0] } } impl<'a, T: AsRef<[u8]> + ?Sized> SockDiagBuffer<&'a T> { pub fn inner(&self) -> &'a [u8] { self.buffer.as_ref() } } impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> SockDiagBuffer<&'a mut T> { pub fn inner_mut(&mut self) -> &mut [u8] { self.buffer.as_mut() } } impl<'a, T: AsRef<[u8]>> ParseableParametrized, u16> for SockDiagMessage { fn parse_with_param( buf: &SockDiagBuffer<&'a T>, message_type: u16, ) -> Result { use self::SockDiagMessage::*; buf.check_len()?; let message = match (message_type, buf.family()) { (SOCK_DIAG_BY_FAMILY, AF_INET) => { let err = "invalid AF_INET response"; let buf = inet::InetResponseBuffer::new_checked(buf.inner()).context(err)?; InetResponse(Box::new(inet::InetResponse::parse(&buf).context(err)?)) } (SOCK_DIAG_BY_FAMILY, AF_INET6) => { let err = "invalid AF_INET6 response"; let buf = inet::InetResponseBuffer::new_checked(buf.inner()).context(err)?; InetResponse(Box::new(inet::InetResponse::parse(&buf).context(err)?)) } (SOCK_DIAG_BY_FAMILY, AF_UNIX) => { let err = "invalid AF_UNIX response"; let buf = unix::UnixResponseBuffer::new_checked(buf.inner()).context(err)?; UnixResponse(Box::new(unix::UnixResponse::parse(&buf).context(err)?)) } (SOCK_DIAG_BY_FAMILY, af) => { return Err(format!("unknown address family {}", af).into()) } _ => return Err(format!("unknown message type {}", message_type).into()), }; Ok(message) } } ================================================ FILE: netlink-packet-sock-diag/src/constants.rs ================================================ // SPDX-License-Identifier: MIT pub use netlink_packet_core::constants::*; pub const SOCK_DIAG_BY_FAMILY: u16 = 20; pub const SOCK_DESTROY: u16 = 21; pub const AF_UNSPEC: u8 = libc::AF_UNSPEC as u8; pub const AF_UNIX: u8 = libc::AF_UNIX as u8; // pub const AF_LOCAL: u8 = libc::AF_LOCAL as u8; pub const AF_INET: u8 = libc::AF_INET as u8; pub const AF_AX25: u8 = libc::AF_AX25 as u8; pub const AF_IPX: u8 = libc::AF_IPX as u8; pub const AF_APPLETALK: u8 = libc::AF_APPLETALK as u8; pub const AF_NETROM: u8 = libc::AF_NETROM as u8; pub const AF_BRIDGE: u8 = libc::AF_BRIDGE as u8; pub const AF_ATMPVC: u8 = libc::AF_ATMPVC as u8; pub const AF_X25: u8 = libc::AF_X25 as u8; pub const AF_INET6: u8 = libc::AF_INET6 as u8; pub const AF_ROSE: u8 = libc::AF_ROSE as u8; pub const AF_DECNET: u8 = libc::AF_DECnet as u8; pub const AF_NETBEUI: u8 = libc::AF_NETBEUI as u8; pub const AF_SECURITY: u8 = libc::AF_SECURITY as u8; pub const AF_KEY: u8 = libc::AF_KEY as u8; pub const AF_NETLINK: u8 = libc::AF_NETLINK as u8; // pub const AF_ROUTE: u8 = libc::AF_ROUTE as u8; pub const AF_PACKET: u8 = libc::AF_PACKET as u8; pub const AF_ASH: u8 = libc::AF_ASH as u8; pub const AF_ECONET: u8 = libc::AF_ECONET as u8; pub const AF_ATMSVC: u8 = libc::AF_ATMSVC as u8; pub const AF_RDS: u8 = libc::AF_RDS as u8; pub const AF_SNA: u8 = libc::AF_SNA as u8; pub const AF_IRDA: u8 = libc::AF_IRDA as u8; pub const AF_PPPOX: u8 = libc::AF_PPPOX as u8; pub const AF_WANPIPE: u8 = libc::AF_WANPIPE as u8; pub const AF_LLC: u8 = libc::AF_LLC as u8; pub const AF_CAN: u8 = libc::AF_CAN as u8; pub const AF_TIPC: u8 = libc::AF_TIPC as u8; pub const AF_BLUETOOTH: u8 = libc::AF_BLUETOOTH as u8; pub const AF_IUCV: u8 = libc::AF_IUCV as u8; pub const AF_RXRPC: u8 = libc::AF_RXRPC as u8; pub const AF_ISDN: u8 = libc::AF_ISDN as u8; pub const AF_PHONET: u8 = libc::AF_PHONET as u8; pub const AF_IEEE802154: u8 = libc::AF_IEEE802154 as u8; pub const AF_CAIF: u8 = libc::AF_CAIF as u8; pub const AF_ALG: u8 = libc::AF_ALG as u8; /// Dummy protocol for TCP pub const IPPROTO_IP: u8 = 0; /// Internet Control Message Protocol pub const IPPROTO_ICMP: u8 = 1; /// Internet Group Management Protocol pub const IPPROTO_IGMP: u8 = 2; /// IPIP tunnels (older KA9Q tunnels use 94) pub const IPPROTO_IPIP: u8 = 4; /// Transmission Control Protocol pub const IPPROTO_TCP: u8 = 6; /// Exterior Gateway Protocol pub const IPPROTO_EGP: u8 = 8; /// PUP protocol pub const IPPROTO_PUP: u8 = 12; /// User Datagram Protocol pub const IPPROTO_UDP: u8 = 17; /// XNS IDP protocol pub const IPPROTO_IDP: u8 = 22; /// SO Transport Protocol Class 4 pub const IPPROTO_TP: u8 = 29; /// Datagram Congestion Control Protocol pub const IPPROTO_DCCP: u8 = 33; /// IPv6 header pub const IPPROTO_IPV6: u8 = 41; /// Reservation Protocol pub const IPPROTO_RSVP: u8 = 46; /// General Routing Encapsulation pub const IPPROTO_GRE: u8 = 47; /// encapsulating security payload pub const IPPROTO_ESP: u8 = 50; /// authentication header pub const IPPROTO_AH: u8 = 51; /// Multicast Transport Protocol pub const IPPROTO_MTP: u8 = 92; /// IP option pseudo header for BEET pub const IPPROTO_BEETPH: u8 = 94; /// Encapsulation Header pub const IPPROTO_ENCAP: u8 = 98; /// Protocol Independent Multicast pub const IPPROTO_PIM: u8 = 103; /// Compression Header Protocol pub const IPPROTO_COMP: u8 = 108; /// Stream Control Transmission Protocol pub const IPPROTO_SCTP: u8 = 132; /// UDP-Lite protocol pub const IPPROTO_UDPLITE: u8 = 136; /// MPLS in IP pub const IPPROTO_MPLS: u8 = 137; /// Raw IP packets pub const IPPROTO_RAW: u8 = 255; /// IPv6 Hop-by-Hop options pub const IPPROTO_HOPOPTS: u8 = 0; /// IPv6 routing header pub const IPPROTO_ROUTING: u8 = 43; /// IPv6 fragmentation header pub const IPPROTO_FRAGMENT: u8 = 44; /// ICMPv6 pub const IPPROTO_ICMPV6: u8 = 58; /// IPv6 no next header pub const IPPROTO_NONE: u8 = 59; /// IPv6 destination options pub const IPPROTO_DSTOPTS: u8 = 60; /// IPv6 mobility header pub const IPPROTO_MH: u8 = 135; // Extensions for inet pub const INET_DIAG_NONE: u16 = 0; pub const INET_DIAG_MEMINFO: u16 = 1; pub const INET_DIAG_INFO: u16 = 2; pub const INET_DIAG_VEGASINFO: u16 = 3; pub const INET_DIAG_CONG: u16 = 4; pub const INET_DIAG_TOS: u16 = 5; pub const INET_DIAG_TCLASS: u16 = 6; pub const INET_DIAG_SKMEMINFO: u16 = 7; pub const INET_DIAG_SHUTDOWN: u16 = 8; pub const INET_DIAG_DCTCPINFO: u16 = 9; pub const INET_DIAG_PROTOCOL: u16 = 10; pub const INET_DIAG_SKV6ONLY: u16 = 11; pub const INET_DIAG_LOCALS: u16 = 12; pub const INET_DIAG_PEERS: u16 = 13; pub const INET_DIAG_PAD: u16 = 14; pub const INET_DIAG_MARK: u16 = 15; pub const INET_DIAG_BBRINFO: u16 = 16; pub const INET_DIAG_CLASS_ID: u16 = 17; pub const INET_DIAG_MD5SIG: u16 = 18; /// (both server and client) represents an open connection, data /// received can be delivered to the user. The normal state for the /// data transfer phase of the connection. pub const TCP_ESTABLISHED: u8 = 1; /// (client) represents waiting for a matching connection request /// after having sent a connection request. pub const TCP_SYN_SENT: u8 = 2; /// (server) represents waiting for a confirming connection request /// acknowledgment after having both received and sent a connection /// request. pub const TCP_SYN_RECV: u8 = 3; /// (both server and client) represents waiting for a connection /// termination request from the remote TCP, or an acknowledgment of /// the connection termination request previously sent. pub const TCP_FIN_WAIT1: u8 = 4; /// (both server and client) represents waiting for a connection /// termination request from the remote TCP. pub const TCP_FIN_WAIT2: u8 = 5; /// (either server or client) represents waiting for enough time to /// pass to be sure the remote TCP received the acknowledgment of its /// connection termination request. pub const TCP_TIME_WAIT: u8 = 6; /// (both server and client) represents no connection state at all. pub const TCP_CLOSE: u8 = 7; /// (both server and client) represents waiting for a connection /// termination request from the local user. pub const TCP_CLOSE_WAIT: u8 = 8; /// (both server and client) represents waiting for an acknowledgment /// of the connection termination request previously sent to the /// remote TCP (which includes an acknowledgment of its connection /// termination request). pub const TCP_LAST_ACK: u8 = 9; /// (server) represents waiting for a connection request from any /// remote TCP and port. pub const TCP_LISTEN: u8 = 10; /// (both server and client) represents waiting for a connection termination request acknowledgment from the remote TCP. pub const TCP_CLOSING: u8 = 11; /// The attribute reported in answer to this request is /// `UNIX_DIAG_NAME`. The payload associated with this attribute is /// the pathname to which the socket was bound (a se quence of bytes /// up to `UNIX_PATH_MAX` length). pub const UDIAG_SHOW_NAME: u32 = 1 << UNIX_DIAG_NAME; /// The attribute reported in answer to this request is /// `UNIX_DIAG_VFS`, which returns VFS information associated to the /// inode. pub const UDIAG_SHOW_VFS: u32 = 1 << UNIX_DIAG_VFS; /// The attribute reported in answer to this request is /// `UNIX_DIAG_PEER`, which carries the peer's inode number. This /// attribute is reported for connected sockets only. pub const UDIAG_SHOW_PEER: u32 = 1 << UNIX_DIAG_PEER; /// The attribute reported in answer to this request is /// `UNIX_DIAG_ICONS`, which information about pending /// connections. Specifically, it contains the inode numbers of the /// sockets that have passed the `connect(2)` call, but hasn't been /// processed with `accept(2) yet`. This attribute is reported for /// listening sockets only. pub const UDIAG_SHOW_ICONS: u32 = 1 << UNIX_DIAG_ICONS; /// The attribute reported in answer to this request is /// `UNIX_DIAG_RQLEN`, which reports: /// /// - for listening socket: the number of pending connections, and the /// backlog length (which equals to the value passed as the second /// argument to `listen(2)`). /// - for established sockets: the amount of data in incoming queue, /// and the amount of memory available for sending pub const UDIAG_SHOW_RQLEN: u32 = 1 << UNIX_DIAG_RQLEN; /// The attribute reported in answer to this request is /// `UNIX_DIAG_MEMINFO` which shows memory information about the /// socket pub const UDIAG_SHOW_MEMINFO: u32 = 1 << UNIX_DIAG_MEMINFO; pub const UNIX_DIAG_NAME: u16 = 0; pub const UNIX_DIAG_VFS: u16 = 1; pub const UNIX_DIAG_PEER: u16 = 2; pub const UNIX_DIAG_ICONS: u16 = 3; pub const UNIX_DIAG_RQLEN: u16 = 4; pub const UNIX_DIAG_MEMINFO: u16 = 5; pub const UNIX_DIAG_SHUTDOWN: u16 = 6; /// Provides sequenced, reliable, two-way, connection-based byte /// streams. An out-of-band data transmission mechanism may be /// supported. pub const SOCK_STREAM: u8 = libc::SOCK_STREAM as u8; /// Supports datagrams (connectionless, unreliable messages of a fixed /// maximum length). pub const SOCK_DGRAM: u8 = libc::SOCK_DGRAM as u8; /// Provides a sequenced, reliable, two-way connection-based data /// transmission path for datagrams of fixed maximum length; a /// consumer is required to read an entire packet with each input /// system call. pub const SOCK_SEQPACKET: u8 = libc::SOCK_SEQPACKET as u8; /// Provides raw network protocol access. pub const SOCK_RAW: u8 = libc::SOCK_RAW as u8; /// Provides a reliable datagram layer that does not guarantee /// ordering. pub const SOCK_RDM: u8 = libc::SOCK_RDM as u8; /// Obsolete and should not be used in new programs; see `packet(7)`. pub const SOCK_PACKET: u8 = libc::SOCK_PACKET as u8; /// Nothing bad has been observed recently. No apparent reordering, packet loss, or ECN marks. pub const TCP_CA_OPEN: u8 = 0; pub const TCPF_CA_OPEN: u32 = 1 << TCP_CA_OPEN; /// The sender enters disordered state when it has received DUPACKs or /// SACKs in the last round of packets sent. This could be due to /// packet loss or reordering but needs further information to confirm /// packets have been lost. pub const TCP_CA_DISORDER: u8 = 1; pub const TCPF_CA_DISORDER: u32 = 1 << TCP_CA_DISORDER; /// The sender enters Congestion Window Reduction (CWR) state when it /// has received ACKs with ECN-ECE marks, or has experienced /// congestion or packet discard on the sender host (e.g. qdisc). pub const TCP_CA_CWR: u8 = 2; pub const TCPF_CA_CWR: u32 = 1 << TCP_CA_CWR; /// The sender is in fast recovery and retransmitting lost packets, typically triggered by ACK events. pub const TCP_CA_RECOVERY: u8 = 3; pub const TCPF_CA_RECOVERY: u32 = 1 << TCP_CA_RECOVERY; /// The sender is in loss recovery triggered by retransmission timeout. pub const TCP_CA_LOSS: u8 = 4; pub const TCPF_CA_LOSS: u32 = 1 << TCP_CA_LOSS; pub const TCPI_OPT_TIMESTAMPS: u8 = 1; pub const TCPI_OPT_SACK: u8 = 2; pub const TCPI_OPT_WSCALE: u8 = 4; /// ECN was negociated at TCP session init pub const TCPI_OPT_ECN: u8 = 8; /// We received at least one packet with ECT pub const TCPI_OPT_ECN_SEEN: u8 = 16; /// SYN-ACK acked data in SYN sent or rcvd pub const TCPI_OPT_SYN_DATA: u8 = 32; /// Shutdown state of a socket. A socket shut down with `SHUT_RD` can /// no longer receive data. See also `man 2 shutdown`. pub const SHUT_RD: u8 = 0; /// Shutdown state of a socket. A socket shut down with `SHUT_WR` can /// no longer send data. See also `man 2 shutdown`. pub const SHUT_WR: u8 = 1; /// Shutdown state of a socket. A socket shut down with `SHUT_RDWR` /// can no longer receive nor send data. See also `man 2 shutdown`. pub const SHUT_RDWR: u8 = 2; ================================================ FILE: netlink-packet-sock-diag/src/inet/mod.rs ================================================ // SPDX-License-Identifier: MIT mod socket_id; pub use self::socket_id::*; mod request; pub use self::request::*; mod response; pub use self::response::*; pub mod nlas; #[cfg(test)] mod tests; ================================================ FILE: netlink-packet-sock-diag/src/inet/nlas.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; pub use crate::utils::nla::{DefaultNla, NlaBuffer, NlasIterator}; use crate::{ constants::*, parsers::{parse_string, parse_u32, parse_u8}, traits::{Emitable, Parseable}, DecodeError, }; pub const LEGACY_MEM_INFO_LEN: usize = 16; buffer!(LegacyMemInfoBuffer(LEGACY_MEM_INFO_LEN) { receive_queue: (u32, 0..4), bottom_send_queue: (u32, 4..8), cache: (u32, 8..12), send_queue: (u32, 12..16) }); /// In recent Linux kernels, this NLA is not used anymore to report /// AF_INET and AF_INET6 sockets memory information. See [`MemInfo`] /// instead. #[derive(Debug, Clone, PartialEq, Eq)] pub struct LegacyMemInfo { /// Amount of data in the receive queue. pub receive_queue: u32, /// Amount of data that is queued by TCP but not yet sent. pub bottom_send_queue: u32, /// Amount of memory scheduled for future use (TCP only). pub cache: u32, /// Amount of data in the send queue. pub send_queue: u32, } impl> Parseable> for LegacyMemInfo { fn parse(buf: &LegacyMemInfoBuffer) -> Result { Ok(Self { receive_queue: buf.receive_queue(), bottom_send_queue: buf.bottom_send_queue(), cache: buf.cache(), send_queue: buf.send_queue(), }) } } impl Emitable for LegacyMemInfo { fn buffer_len(&self) -> usize { LEGACY_MEM_INFO_LEN } fn emit(&self, buf: &mut [u8]) { let mut buf = LegacyMemInfoBuffer::new(buf); buf.set_receive_queue(self.receive_queue); buf.set_bottom_send_queue(self.bottom_send_queue); buf.set_cache(self.cache); buf.set_send_queue(self.send_queue); } } pub const MEM_INFO_LEN: usize = 36; // FIXME: the last 2 fields are not present on old linux kernels. We // should support optional fields in the `buffer!` macro. buffer!(MemInfoBuffer(MEM_INFO_LEN) { receive_queue: (u32, 0..4), receive_queue_max: (u32, 4..8), bottom_send_queues: (u32, 8..12), send_queue_max: (u32, 12..16), cache: (u32, 16..20), send_queue: (u32, 20..24), options: (u32, 24..28), backlog_queue_length: (u32, 28..32), drops: (u32, 32..36), }); /// Socket memory information. To understand this information, one /// must understand how the memory allocated for the send and receive /// queues of a socket is managed. /// /// # Warning /// /// This data structure is not well documented. The explanations given /// here are the results of my personal research on this topic, but I /// am by no mean an expert in Linux networking, so take this /// documentation with a huge grain of salt. Please report any error /// you may notice. Here are the references I used: /// /// - [https://wiki.linuxfoundation.org/networking/sk_buff](a short introduction to `sk_buff`, the struct used in the kernel to store packets) /// - [vger.kernel.org has a lot of documentation about the low level network stack APIs](http://vger.kernel.org/~davem/skb_data.html) /// - [thorough high level explanation of buffering in the network stack](https://www.coverfire.com/articles/queueing-in-the-linux-network-stack/) /// - [understanding the backlog queue](http://veithen.io/2014/01/01/how-tcp-backlog-works-in-linux.html) /// - [high level explanation of packet reception](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/performance_tuning_guide/s-network-packet-reception) /// - [a StackExchange question about the different send queues used by a socket](https://unix.stackexchange.com/questions/551444/what-is-the-difference-between-sock-sk-wmem-alloc-and-sock-sk-wmem-queued) /// - other useful resources: [here](https://www.cl.cam.ac.uk/~pes20/Netsem/linuxnet.pdf) and [here](https://people.cs.clemson.edu/~westall/853/notes/skbuff.pdf) /// - [explanation of the socket backlog queue](https://medium.com/@c0ngwang/the-design-of-lock-sock-in-linux-kernel-69c3406e504b) /// /// # Linux networking in a nutshell /// /// The network stack uses multiple queues, both for sending an /// receiving data. Let's start with the simplest case: packet /// receptions. /// /// When data is received, it is first handled by the device driver /// and put in the device driver queue. The kernel then move the /// packet to the socket receive queue (also called _receive /// buffer_). Finally, this application reads it (with `recv`, `read` /// or `recvfrom`) and the packet is dequeued. /// /// Sending packet it slightly more complicated and the exact workflow /// may differ from one protocol to the other so we'll just give a /// high level overview. When an application sends data, a packet is /// created and stored in the socket send queue (also called _send /// buffer_). It is then passed down to the QDisc (Queuing /// Disciplines) queue. The QDisc facility enables quality of service: /// if some data is more urgent to transmit than other, QDisc will /// make sure it is sent in priority. Finally, the data is put on the /// device driver queue to be sent out. #[derive(Debug, PartialEq, Eq, Clone)] pub struct MemInfo { /// Memory currently allocated for the socket's receive /// queue. This attribute is known as `sk_rmem_alloc` in the /// kernel. pub receive_queue: u32, /// Maximum amount of memory that can be allocated for the /// socket's receive queue. This is set by `SO_RCVBUF`. This is /// _not_ the amount of memory currently allocated. This attribute /// is known as `sk_rcvbuf` in the kernel. pub receive_queue_max: u32, /// Memory currently allocated for the socket send queue. This /// attribute is known as `sk_wmem_queued` in the kernel. This /// does does not account for data that have been passed down the /// network stack (i.e. to the QDisc and device driver queues), /// which is reported by the `bottow_send_queue` (known as /// `sk_wmem_alloc` in the kernel). /// /// For a TCP socket, if the congestion window is small, the /// kernel will move the data fron the socket send queue to the /// QDisc queues more slowly. Thus, if the process sends of lot of /// data, the socket send queue (which memory is tracked by /// `sk_wmem_queued`) will grow while `sk_wmem_alloc` will remain /// small. pub send_queue: u32, /// Maximum amount of memory (in bytes) that can be allocated for /// this socket's send queue. This is set by `SO_SNDBUF`. This is /// _not_ the amount of memory currently allocated. This attribute /// is known as `sk_sndbuf` in the kernel. pub send_queue_max: u32, /// Memory used for packets that have been passed down the network /// stack, i.e. that are either in the QDisc or device driver /// queues. This attribute is known as `sk_wmem_alloc` in the /// kernel. See also [`send_queue`]. pub bottom_send_queues: u32, /// The amount of memory already allocated for this socket but /// currently unused. When more memory is needed either for /// sending or for receiving data, it will be taken from this /// pool. This attribute is known as `sk_fwd_alloc` in the kernel. pub cache: u32, /// The amount of memory allocated for storing socket options, for /// instance the key for TCP MD5 signature. This attribute is /// known as `sk_optmem` in the kernel. pub options: u32, /// The length of the backlog queue. When the process is using the /// socket, the socket is locked so the kernel cannot enqueue new /// packets in the receive queue. To avoid blocking the bottom /// half of network stack waiting for the process to release the /// socket, the packets are enqueued in the backlog queue. Upon /// releasing the socket, those packets are processed and put in /// the regular receive queue. // FIXME: this should be an Option because it's not present on old // linux kernels. pub backlog_queue_length: u32, /// The amount of packets dropped. Depending on the kernel /// version, this field may not be present. // FIXME: this should be an Option because it's not present on old // linux kernels. pub drops: u32, } impl> Parseable> for MemInfo { fn parse(buf: &MemInfoBuffer) -> Result { Ok(Self { receive_queue: buf.receive_queue(), receive_queue_max: buf.receive_queue_max(), bottom_send_queues: buf.bottom_send_queues(), send_queue_max: buf.send_queue_max(), cache: buf.cache(), send_queue: buf.send_queue(), options: buf.options(), backlog_queue_length: buf.backlog_queue_length(), drops: buf.drops(), }) } } impl Emitable for MemInfo { fn buffer_len(&self) -> usize { MEM_INFO_LEN } fn emit(&self, buf: &mut [u8]) { let mut buf = MemInfoBuffer::new(buf); buf.set_receive_queue(self.receive_queue); buf.set_receive_queue_max(self.receive_queue_max); buf.set_bottom_send_queues(self.bottom_send_queues); buf.set_send_queue_max(self.send_queue_max); buf.set_cache(self.cache); buf.set_send_queue(self.send_queue); buf.set_options(self.options); buf.set_backlog_queue_length(self.backlog_queue_length); buf.set_drops(self.drops); } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum Nla { /// The memory information of the socket. This attribute is /// similar to `Nla::MemInfo` but provides less information. On /// recent kernels, `Nla::MemInfo` is used instead. // ref: https://patchwork.ozlabs.org/patch/154816/ LegacyMemInfo(LegacyMemInfo), /// the TCP information #[cfg(feature = "rich_nlas")] TcpInfo(TcpInfo), #[cfg(not(feature = "rich_nlas"))] TcpInfo(Vec), /// the congestion control algorithm used Congestion(String), /// the TOS of the socket. Tos(u8), /// the traffic class of the socket. Tc(u8), /// The memory information of the socket MemInfo(MemInfo), /// Shutown state: one of [`SHUT_RD`], [`SHUT_WR`] or [`SHUT_RDWR`] Shutdown(u8), /// The protocol Protocol(u8), /// Whether the socket is IPv6 only SkV6Only(bool), /// The mark of the socket. Mark(u32), /// The class ID of the socket. ClassId(u32), /// other attribute Other(DefaultNla), } impl crate::utils::nla::Nla for Nla { fn value_len(&self) -> usize { use self::Nla::*; match *self { LegacyMemInfo(_) => LEGACY_MEM_INFO_LEN, #[cfg(feature = "rich_nlas")] TcpInfo(_) => TCP_INFO_LEN, #[cfg(not(feature = "rich_nlas"))] TcpInfo(ref bytes) => bytes.len(), // +1 because we need to append a null byte Congestion(ref s) => s.as_bytes().len() + 1, Tos(_) | Tc(_) | Shutdown(_) | Protocol(_) | SkV6Only(_) => 1, MemInfo(_) => MEM_INFO_LEN, Mark(_) | ClassId(_) => 4, Other(ref attr) => attr.value_len(), } } fn kind(&self) -> u16 { use self::Nla::*; match *self { LegacyMemInfo(_) => INET_DIAG_MEMINFO, TcpInfo(_) => INET_DIAG_INFO, Congestion(_) => INET_DIAG_CONG, Tos(_) => INET_DIAG_TOS, Tc(_) => INET_DIAG_TCLASS, MemInfo(_) => INET_DIAG_SKMEMINFO, Shutdown(_) => INET_DIAG_SHUTDOWN, Protocol(_) => INET_DIAG_PROTOCOL, SkV6Only(_) => INET_DIAG_SKV6ONLY, Mark(_) => INET_DIAG_MARK, ClassId(_) => INET_DIAG_CLASS_ID, Other(ref attr) => attr.kind(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::Nla::*; match *self { LegacyMemInfo(ref value) => value.emit(buffer), #[cfg(feature = "rich_nlas")] TcpInfo(ref value) => value.emit(buffer), #[cfg(not(feature = "rich_nlas"))] TcpInfo(ref bytes) => buffer[..bytes.len()].copy_from_slice(&bytes[..]), Congestion(ref s) => { buffer[..s.len()].copy_from_slice(s.as_bytes()); buffer[s.len()] = 0; } Tos(b) | Tc(b) | Shutdown(b) | Protocol(b) => buffer[0] = b, SkV6Only(value) => buffer[0] = value.into(), MemInfo(ref value) => value.emit(buffer), Mark(value) | ClassId(value) => NativeEndian::write_u32(buffer, value), Other(ref attr) => attr.emit_value(buffer), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nla { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { INET_DIAG_MEMINFO => { let err = "invalid INET_DIAG_MEMINFO value"; let buf = LegacyMemInfoBuffer::new_checked(payload).context(err)?; Self::LegacyMemInfo(LegacyMemInfo::parse(&buf).context(err)?) } #[cfg(feature = "rich_nlas")] INET_DIAG_INFO => { let err = "invalid INET_DIAG_INFO value"; let buf = TcpInfoBuffer::new_checked(payload).context(err)?; Self::TcpInfo(TcpInfo::parse(&buf).context(err)?) } #[cfg(not(feature = "rich_nlas"))] INET_DIAG_INFO => Self::TcpInfo(payload.to_vec()), INET_DIAG_CONG => { Self::Congestion(parse_string(payload).context("invalid INET_DIAG_CONG value")?) } INET_DIAG_TOS => Self::Tos(parse_u8(payload).context("invalid INET_DIAG_TOS value")?), INET_DIAG_TCLASS => { Self::Tc(parse_u8(payload).context("invalid INET_DIAG_TCLASS value")?) } INET_DIAG_SKMEMINFO => { let err = "invalid INET_DIAG_SKMEMINFO value"; let buf = MemInfoBuffer::new_checked(payload).context(err)?; Self::MemInfo(MemInfo::parse(&buf).context(err)?) } INET_DIAG_SHUTDOWN => { Self::Shutdown(parse_u8(payload).context("invalid INET_DIAG_SHUTDOWN value")?) } INET_DIAG_PROTOCOL => { Self::Protocol(parse_u8(payload).context("invalid INET_DIAG_PROTOCOL value")?) } INET_DIAG_SKV6ONLY => { Self::SkV6Only(parse_u8(payload).context("invalid INET_DIAG_SKV6ONLY value")? != 0) } INET_DIAG_MARK => { Self::Mark(parse_u32(payload).context("invalid INET_DIAG_MARK value")?) } INET_DIAG_CLASS_ID => { Self::ClassId(parse_u32(payload).context("invalid INET_DIAG_CLASS_ID value")?) } kind => { Self::Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?) } }) } } #[cfg(feature = "rich_nlas")] pub const TCP_INFO_LEN: usize = 232; #[cfg(feature = "rich_nlas")] buffer!(TcpInfoBuffer(TCP_INFO_LEN) { // State of the TCP connection. This should be set to one of the // `TCP_*` constants: `TCP_ESTABLISHED`, `TCP_SYN_SENT`, etc. This // attribute is known as `tcpi_state` in the kernel. state: (u8, 0), // State of congestion avoidance. Sender's congestion state // indicating normal or abnormal situations in the last round of // packets sent. The state is driven by the ACK information and // timer events. This should be set to one of the `TCP_CA_*` // constants. This attribute is known as `tcpi_ca_state` in the // kernel. congestion_avoidance_state: (u8, 1), // Number of retranmissions on timeout invoked. This attribute is // known as `tcpi_retransmits` in the kernel. retransmits: (u8, 2), // Number of window or keep alive probes sent. This attribute is // known as `tcpi_probes`. probes: (u8, 3), // Number of times the retransmission backoff timer invoked backoff: (u8, 4), options: (u8, 5), wscale: (u8, 6), delivery_rate_app_limited: (u8, 7), rto: (u32, 8..12), ato: (u32, 12..16), snd_mss: (u32, 16..20), rcv_mss: (u32, 20..24), unacked: (u32, 24..28), sacked: (u32, 28..32), lost: (u32, 32..36), retrans: (u32, 36..40), fackets: (u32, 40..44), // Times last_data_sent: (u32, 44..48), last_ack_sent: (u32, 48..52), last_data_recv: (u32, 52..56), last_ack_recv: (u32, 56..60), // Metrics pmtu: (u32, 60..64), rcv_ssthresh: (u32, 64..68), rtt: (u32, 68..72), rttvar: (u32, 72..76), snd_ssthresh: (u32, 76..80), snd_cwnd: (u32, 80..84), advmss: (u32, 84..88), reordering: (u32, 88..92), rcv_rtt: (u32, 92..96), rcv_space: (u32, 96..100), total_retrans: (u32, 100..104), pacing_rate: (u64, 104..112), max_pacing_rate: (u64, 112..120), bytes_acked: (u64, 120..128), // RFC4898 tcpEStatsAppHCThruOctetsAcked bytes_received: (u64, 128..136), // RFC4898 tcpEStatsAppHCThruOctetsReceived segs_out: (u32, 136..140), // RFC4898 tcpEStatsPerfSegsOut segs_in: (u32, 140..144), // RFC4898 tcpEStatsPerfSegsIn notsent_bytes: (u32, 144..148), min_rtt: (u32, 148..152), data_segs_in: (u32, 152..156), // RFC4898 tcpEStatsDataSegsIn data_segs_out: (u32, 156..160), // RFC4898 tcpEStatsDataSegsOut delivery_rate: (u64, 160..168), busy_time: (u64, 168..176), // Time (usec) busy sending data rwnd_limited: (u64, 176..184), // Time (usec) limited by receive window sndbuf_limited: (u64, 184..192), // Time (usec) limited by send buffer delivered: (u32, 192..196), delivered_ce: (u32, 196..200), bytes_sent: (u64, 200..208), // RFC4898 tcpEStatsPerfHCDataOctetsOut bytes_retrans: (u64, 208..216), // RFC4898 tcpEStatsPerfOctetsRetrans dsack_dups: (u32, 216..220), // RFC4898 tcpEStatsStackDSACKDups reord_seen: (u32, 220..224), // reordering events seen rcv_ooopack: (u32, 224..228), // Out-of-order packets received snd_wnd: (u32, 228..232), // peer's advertised receive window after scaling (bytes) }); // https://unix.stackexchange.com/questions/542712/detailed-output-of-ss-command #[cfg(feature = "rich_nlas")] #[derive(Debug, Clone, PartialEq, Eq)] pub struct TcpInfo { /// State of the TCP connection: one of `TCP_ESTABLISHED`, /// `TCP_SYN_SENT`, `TP_SYN_RECV`, `TCP_FIN_WAIT1`, /// `TCP_FIN_WAIT2` `TCP_TIME_WAIT`, `TCP_CLOSE`, /// `TCP_CLOSE_WAIT`, `TCP_LAST_ACK` `TCP_LISTEN`, `TCP_CLOSING`. pub state: u8, /// Congestion algorithm state: one of `TCP_CA_OPEN`, /// `TCP_CA_DISORDER`, `TCP_CA_CWR`, `TCP_CA_RECOVERY`, /// `TCP_CA_LOSS` pub ca_state: u8, /// pub retransmits: u8, pub probes: u8, pub backoff: u8, pub options: u8, // First 4 bits are snd_wscale, last 4 bits rcv_wscale pub wscale: u8, /// A boolean indicating if the goodput was measured when the /// socket's throughput was limited by the sending application. /// tcpi_delivery_rate_app_limited:1, tcpi_fastopen_client_fail:2 pub delivery_rate_app_limited: u8, /// Value of the RTO (Retransmission TimeOut) timer. This value is /// calculated using the RTT. pub rto: u32, /// Value of the ATO (ACK TimeOut) timer. pub ato: u32, /// MSS (Maximum Segment Size). Not shure how it differs from /// `advmss`. pub snd_mss: u32, /// MSS (Maximum Segment Size) advertised by peer pub rcv_mss: u32, /// Number of segments that have not been ACKnowledged yet, ie the /// number of in-flight segments. pub unacked: u32, /// Number of segments that have been SACKed pub sacked: u32, /// Number of segments that have been lost pub lost: u32, /// Number of segments that have been retransmitted pub retrans: u32, /// Number of segments that have been FACKed pub fackets: u32, pub last_data_sent: u32, pub last_ack_sent: u32, pub last_data_recv: u32, pub last_ack_recv: u32, pub pmtu: u32, pub rcv_ssthresh: u32, /// RTT (Round Trip Time). There RTT is the time between the /// moment a segment is sent out and the moment it is /// acknowledged. There are different kinds of RTT values, and I /// don't know which one this value corresponds to: mRTT (measured /// RTT), sRTT (smoothed RTT), RTTd (deviated RTT), etc. pub rtt: u32, /// RTT variance (or variation?) pub rttvar: u32, /// Slow-Start Threshold pub snd_ssthresh: u32, /// Size of the congestion window pub snd_cwnd: u32, /// MSS advertised by this peer pub advmss: u32, pub reordering: u32, pub rcv_rtt: u32, pub rcv_space: u32, pub total_retrans: u32, pub pacing_rate: u64, pub max_pacing_rate: u64, pub bytes_acked: u64, // RFC4898 tcpEStatsAppHCThruOctetsAcked pub bytes_received: u64, // RFC4898 tcpEStatsAppHCThruOctetsReceived pub segs_out: u32, // RFC4898 tcpEStatsPerfSegsOut pub segs_in: u32, // RFC4898 tcpEStatsPerfSegsIn pub notsent_bytes: u32, pub min_rtt: u32, pub data_segs_in: u32, // RFC4898 tcpEStatsDataSegsIn pub data_segs_out: u32, // RFC4898 tcpEStatsDataSegsOut /// The most recent goodput, as measured by tcp_rate_gen(). If the /// socket is limited by the sending application (e.g., no data to /// send), it reports the highest measurement instead of the most /// recent. The unit is bytes per second (like other rate fields /// in tcp_info). pub delivery_rate: u64, pub busy_time: u64, // Time (usec) busy sending data pub rwnd_limited: u64, // Time (usec) limited by receive window pub sndbuf_limited: u64, // Time (usec) limited by send buffer pub delivered: u32, pub delivered_ce: u32, pub bytes_sent: u64, // RFC4898 tcpEStatsPerfHCDataOctetsOut pub bytes_retrans: u64, // RFC4898 tcpEStatsPerfOctetsRetrans pub dsack_dups: u32, // RFC4898 tcpEStatsStackDSACKDups /// reordering events seen pub reord_seen: u32, /// Out-of-order packets received pub rcv_ooopack: u32, /// peer's advertised receive window after scaling (bytes) pub snd_wnd: u32, } #[cfg(feature = "rich_nlas")] impl> Parseable> for TcpInfo { fn parse(buf: &TcpInfoBuffer) -> Result { Ok(Self { state: buf.state(), ca_state: buf.congestion_avoidance_state(), retransmits: buf.retransmits(), probes: buf.probes(), backoff: buf.backoff(), options: buf.options(), wscale: buf.wscale(), delivery_rate_app_limited: buf.delivery_rate_app_limited(), rto: buf.rto(), ato: buf.ato(), snd_mss: buf.snd_mss(), rcv_mss: buf.rcv_mss(), unacked: buf.unacked(), sacked: buf.sacked(), lost: buf.lost(), retrans: buf.retrans(), fackets: buf.fackets(), last_data_sent: buf.last_data_sent(), last_ack_sent: buf.last_ack_sent(), last_data_recv: buf.last_data_recv(), last_ack_recv: buf.last_ack_recv(), pmtu: buf.pmtu(), rcv_ssthresh: buf.rcv_ssthresh(), rtt: buf.rtt(), rttvar: buf.rttvar(), snd_ssthresh: buf.snd_ssthresh(), snd_cwnd: buf.snd_cwnd(), advmss: buf.advmss(), reordering: buf.reordering(), rcv_rtt: buf.rcv_rtt(), rcv_space: buf.rcv_space(), total_retrans: buf.total_retrans(), pacing_rate: buf.pacing_rate(), max_pacing_rate: buf.max_pacing_rate(), bytes_acked: buf.bytes_acked(), bytes_received: buf.bytes_received(), segs_out: buf.segs_out(), segs_in: buf.segs_in(), notsent_bytes: buf.notsent_bytes(), min_rtt: buf.min_rtt(), data_segs_in: buf.data_segs_in(), data_segs_out: buf.data_segs_out(), delivery_rate: buf.delivery_rate(), busy_time: buf.busy_time(), rwnd_limited: buf.rwnd_limited(), sndbuf_limited: buf.sndbuf_limited(), delivered: buf.delivered(), delivered_ce: buf.delivered_ce(), bytes_sent: buf.bytes_sent(), bytes_retrans: buf.bytes_retrans(), dsack_dups: buf.dsack_dups(), reord_seen: buf.reord_seen(), rcv_ooopack: buf.rcv_ooopack(), snd_wnd: buf.snd_wnd(), }) } } #[cfg(feature = "rich_nlas")] impl Emitable for TcpInfo { fn buffer_len(&self) -> usize { TCP_INFO_LEN } fn emit(&self, buf: &mut [u8]) { let mut buf = TcpInfoBuffer::new(buf); buf.set_state(self.state); buf.set_congestion_avoidance_state(self.ca_state); buf.set_retransmits(self.retransmits); buf.set_probes(self.probes); buf.set_backoff(self.backoff); buf.set_options(self.options); buf.set_wscale(self.wscale); buf.set_delivery_rate_app_limited(self.delivery_rate_app_limited); buf.set_rto(self.rto); buf.set_ato(self.ato); buf.set_snd_mss(self.snd_mss); buf.set_rcv_mss(self.rcv_mss); buf.set_unacked(self.unacked); buf.set_sacked(self.sacked); buf.set_lost(self.lost); buf.set_retrans(self.retrans); buf.set_fackets(self.fackets); buf.set_last_data_sent(self.last_data_sent); buf.set_last_ack_sent(self.last_ack_sent); buf.set_last_data_recv(self.last_data_recv); buf.set_last_ack_recv(self.last_ack_recv); buf.set_pmtu(self.pmtu); buf.set_rcv_ssthresh(self.rcv_ssthresh); buf.set_rtt(self.rtt); buf.set_rttvar(self.rttvar); buf.set_snd_ssthresh(self.snd_ssthresh); buf.set_snd_cwnd(self.snd_cwnd); buf.set_advmss(self.advmss); buf.set_reordering(self.reordering); buf.set_rcv_rtt(self.rcv_rtt); buf.set_rcv_space(self.rcv_space); buf.set_total_retrans(self.total_retrans); buf.set_pacing_rate(self.pacing_rate); buf.set_max_pacing_rate(self.max_pacing_rate); buf.set_bytes_acked(self.bytes_acked); buf.set_bytes_received(self.bytes_received); buf.set_segs_out(self.segs_out); buf.set_segs_in(self.segs_in); buf.set_notsent_bytes(self.notsent_bytes); buf.set_min_rtt(self.min_rtt); buf.set_data_segs_in(self.data_segs_in); buf.set_data_segs_out(self.data_segs_out); buf.set_delivery_rate(self.delivery_rate); buf.set_busy_time(self.busy_time); buf.set_rwnd_limited(self.rwnd_limited); buf.set_sndbuf_limited(self.sndbuf_limited); buf.set_delivered(self.delivered); buf.set_delivered_ce(self.delivered_ce); buf.set_bytes_sent(self.bytes_sent); buf.set_bytes_retrans(self.bytes_retrans); buf.set_dsack_dups(self.dsack_dups); buf.set_reord_seen(self.reord_seen); buf.set_rcv_ooopack(self.rcv_ooopack); buf.set_snd_wnd(self.snd_wnd); } } ================================================ FILE: netlink-packet-sock-diag/src/inet/request.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use crate::{ constants::*, inet::{SocketId, SocketIdBuffer}, traits::{Emitable, Parseable, ParseableParametrized}, DecodeError, }; pub const REQUEST_LEN: usize = 56; buffer!(InetRequestBuffer(REQUEST_LEN) { family: (u8, 0), protocol: (u8, 1), extensions: (u8, 2), pad: (u8, 3), states: (u32, 4..8), socket_id: (slice, 8..56), }); /// A request for Ipv4 and Ipv6 sockets #[derive(Debug, PartialEq, Eq, Clone)] pub struct InetRequest { /// The address family, either `AF_INET` or `AF_INET6` pub family: u8, /// The IP protocol. This field should be set to one of the /// `IPPROTO_*` constants pub protocol: u8, /// Set of flags defining what kind of extended information to /// report. Each requested kind of information is reported back as /// a netlink attribute. pub extensions: ExtensionFlags, /// Bitmask that defines a filter of TCP socket states pub states: StateFlags, /// A socket ID object that is used in dump requests, in queries /// about individual sockets, and is reported back in each /// response. /// /// Unlike UNIX domain sockets, IPv4 and IPv6 sockets are /// identified using addresses and ports. pub socket_id: SocketId, } bitflags! { /// Bitmask that defines a filter of TCP socket states pub struct StateFlags: u32 { /// (server and client) represents an open connection, /// data received can be delivered to the user. The normal /// state for the data transfer phase of the connection. const ESTABLISHED = 1 << TCP_ESTABLISHED ; /// (client) represents waiting for a matching connection /// request after having sent a connection request. const SYN_SENT = 1 < + 'a> Parseable> for InetRequest { fn parse(buf: &InetRequestBuffer<&'a T>) -> Result { let err = "invalid socket_id value"; let socket_id = SocketId::parse_with_param( &SocketIdBuffer::new_checked(&buf.socket_id()).context(err)?, buf.family(), ) .context(err)?; Ok(Self { family: buf.family(), protocol: buf.protocol(), extensions: ExtensionFlags::from_bits_truncate(buf.extensions()), states: StateFlags::from_bits_truncate(buf.states()), socket_id, }) } } impl Emitable for InetRequest { fn buffer_len(&self) -> usize { REQUEST_LEN } fn emit(&self, buf: &mut [u8]) { let mut buf = InetRequestBuffer::new(buf); buf.set_family(self.family); buf.set_protocol(self.protocol); buf.set_extensions(self.extensions.bits()); buf.set_pad(0); buf.set_states(self.states.bits()); self.socket_id.emit(buf.socket_id_mut()) } } ================================================ FILE: netlink-packet-sock-diag/src/inet/response.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use smallvec::SmallVec; use std::time::Duration; use crate::{ inet::{ nlas::{Nla, NlaBuffer, NlasIterator}, SocketId, SocketIdBuffer, }, traits::{Emitable, Parseable, ParseableParametrized}, DecodeError, }; /// The type of timer that is currently active for a TCP socket. #[derive(Debug, PartialEq, Eq, Clone)] pub enum Timer { /// A retransmit timer Retransmit(Duration, u8), /// A keep-alive timer KeepAlive(Duration), /// A `TIME_WAIT` timer TimeWait, /// A zero window probe timer Probe(Duration), } pub const RESPONSE_LEN: usize = 72; buffer!(InetResponseBuffer(RESPONSE_LEN) { family: (u8, 0), state: (u8, 1), timer: (u8, 2), retransmits: (u8, 3), socket_id: (slice, 4..52), expires: (u32, 52..56), recv_queue: (u32, 56..60), send_queue: (u32, 60..64), uid: (u32, 64..68), inode: (u32, 68..72), payload: (slice, RESPONSE_LEN..), }); /// The response to a query for IPv4 or IPv6 sockets #[derive(Debug, PartialEq, Eq, Clone)] pub struct InetResponseHeader { /// This should be set to either `AF_INET` or `AF_INET6` for IPv4 /// or IPv6 sockets respectively. pub family: u8, /// The socket state. pub state: u8, /// For TCP sockets, this field describes the type of timer /// that is currently active for the socket. pub timer: Option, /// The socket ID object. pub socket_id: SocketId, /// For listening sockets: the number of pending connections. For /// other sockets: the amount of data in the incoming queue. pub recv_queue: u32, /// For listening sockets: the backlog length. For other sockets: /// the amount of memory available for sending. pub send_queue: u32, /// Socket owner UID. pub uid: u32, /// Socket inode number. pub inode: u32, } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InetResponseHeader { fn parse(buf: &InetResponseBuffer<&'a T>) -> Result { let err = "invalid socket_id value"; let socket_id = SocketId::parse_with_param( &SocketIdBuffer::new_checked(&buf.socket_id()).context(err)?, buf.family(), ) .context(err)?; let timer = match buf.timer() { 1 => { let expires = Duration::from_millis(buf.expires() as u64); let retransmits = buf.retransmits(); Some(Timer::Retransmit(expires, retransmits)) } 2 => { let expires = Duration::from_millis(buf.expires() as u64); Some(Timer::KeepAlive(expires)) } 3 => Some(Timer::TimeWait), 4 => { let expires = Duration::from_millis(buf.expires() as u64); Some(Timer::Probe(expires)) } _ => None, }; Ok(Self { family: buf.family(), state: buf.state(), timer, socket_id, recv_queue: buf.recv_queue(), send_queue: buf.send_queue(), uid: buf.uid(), inode: buf.inode(), }) } } impl Emitable for InetResponseHeader { fn buffer_len(&self) -> usize { RESPONSE_LEN } fn emit(&self, buf: &mut [u8]) { let mut buf = InetResponseBuffer::new(buf); buf.set_family(self.family); buf.set_state(self.state); match self.timer { Some(Timer::Retransmit(expires, retransmits)) => { buf.set_timer(1); buf.set_expires((expires.as_millis() & 0xffff_ffff) as u32); buf.set_retransmits(retransmits); } Some(Timer::KeepAlive(expires)) => { buf.set_timer(2); buf.set_expires((expires.as_millis() & 0xffff_ffff) as u32); buf.set_retransmits(0); } Some(Timer::TimeWait) => { buf.set_timer(3); buf.set_expires(0); buf.set_retransmits(0); } Some(Timer::Probe(expires)) => { buf.set_timer(4); buf.set_expires((expires.as_millis() & 0xffff_ffff) as u32); buf.set_retransmits(0); } None => { buf.set_timer(0); buf.set_expires(0); buf.set_retransmits(0); } } buf.set_recv_queue(self.recv_queue); buf.set_send_queue(self.send_queue); buf.set_uid(self.uid); buf.set_inode(self.inode); self.socket_id.emit(buf.socket_id_mut()) } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct InetResponse { pub header: InetResponseHeader, pub nlas: SmallVec<[Nla; 8]>, } impl<'a, T: AsRef<[u8]> + ?Sized> InetResponseBuffer<&'a T> { pub fn nlas(&self) -> impl Iterator, DecodeError>> { NlasIterator::new(self.payload()) } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for SmallVec<[Nla; 8]> { fn parse(buf: &InetResponseBuffer<&'a T>) -> Result { let mut nlas = smallvec![]; for nla_buf in buf.nlas() { nlas.push(Nla::parse(&nla_buf?)?); } Ok(nlas) } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InetResponse { fn parse(buf: &InetResponseBuffer<&'a T>) -> Result { let header = InetResponseHeader::parse(buf).context("failed to parse inet response header")?; let nlas = SmallVec::<[Nla; 8]>::parse(buf).context("failed to parse inet response NLAs")?; Ok(InetResponse { header, nlas }) } } impl Emitable for InetResponse { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); self.nlas .as_slice() .emit(&mut buffer[self.header.buffer_len()..]); } } ================================================ FILE: netlink-packet-sock-diag/src/inet/socket_id.rs ================================================ // SPDX-License-Identifier: MIT use byteorder::{BigEndian, ByteOrder}; use std::{ convert::{TryFrom, TryInto}, net::{IpAddr, Ipv4Addr, Ipv6Addr}, }; use crate::{ constants::*, traits::{Emitable, ParseableParametrized}, DecodeError, }; pub const SOCKET_ID_LEN: usize = 48; buffer!(SocketIdBuffer(SOCKET_ID_LEN) { source_port: (slice, 0..2), destination_port: (slice, 2..4), source_address: (slice, 4..20), destination_address: (slice, 20..36), interface_id: (u32, 36..40), cookie: (slice, 40..48), }); #[derive(Debug, PartialEq, Eq, Clone)] pub struct SocketId { pub source_port: u16, pub destination_port: u16, pub source_address: IpAddr, pub destination_address: IpAddr, pub interface_id: u32, /// An array of opaque identifiers that could be used along with /// other fields of this structure to specify an individual /// socket. It is ignored when querying for a list of sockets, as /// well as when all its elements are set to `0xff`. pub cookie: [u8; 8], } impl SocketId { pub fn new_v4() -> Self { Self { source_port: 0, destination_port: 0, source_address: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), destination_address: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), interface_id: 0, cookie: [0; 8], } } pub fn new_v6() -> Self { Self { source_port: 0, destination_port: 0, source_address: IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), destination_address: IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), interface_id: 0, cookie: [0; 8], } } } impl<'a, T: AsRef<[u8]> + 'a> ParseableParametrized, u8> for SocketId { fn parse_with_param(buf: &SocketIdBuffer<&'a T>, af: u8) -> Result { let (source_address, destination_address) = match af { AF_INET => { let s = &buf.source_address()[..4]; let source = IpAddr::V4(Ipv4Addr::new(s[0], s[1], s[2], s[3])); let s = &buf.destination_address()[..4]; let destination = IpAddr::V4(Ipv4Addr::new(s[0], s[1], s[2], s[3])); (source, destination) } AF_INET6 => { let bytes: [u8; 16] = buf.source_address().try_into().unwrap(); let source = IpAddr::V6(Ipv6Addr::from(bytes)); let bytes: [u8; 16] = buf.destination_address().try_into().unwrap(); let destination = IpAddr::V6(Ipv6Addr::from(bytes)); (source, destination) } _ => { return Err(DecodeError::from(format!( "unsupported address family {}: expected AF_INET ({}) or AF_INET6 ({})", af, AF_INET, AF_INET6 ))); } }; Ok(Self { source_port: BigEndian::read_u16(buf.source_port()), destination_port: BigEndian::read_u16(buf.destination_port()), source_address, destination_address, interface_id: buf.interface_id(), // Unwrapping is safe because SocketIdBuffer::cookie() // returns a slice of exactly 8 bytes. cookie: TryFrom::try_from(buf.cookie()).unwrap(), }) } } impl Emitable for SocketId { fn buffer_len(&self) -> usize { SOCKET_ID_LEN } fn emit(&self, buffer: &mut [u8]) { let mut buffer = SocketIdBuffer::new(buffer); BigEndian::write_u16(buffer.source_port_mut(), self.source_port); BigEndian::write_u16(buffer.destination_port_mut(), self.destination_port); let mut address_buf: [u8; 16] = [0; 16]; match self.source_address { IpAddr::V4(ip) => address_buf[0..4].copy_from_slice(&ip.octets()[..]), IpAddr::V6(ip) => address_buf.copy_from_slice(&ip.octets()[..]), } buffer .source_address_mut() .copy_from_slice(&address_buf[..]); address_buf = [0; 16]; match self.destination_address { IpAddr::V4(ip) => address_buf[0..4].copy_from_slice(&ip.octets()[..]), IpAddr::V6(ip) => address_buf.copy_from_slice(&ip.octets()[..]), } buffer .destination_address_mut() .copy_from_slice(&address_buf[..]); buffer.set_interface_id(self.interface_id); buffer.cookie_mut().copy_from_slice(&self.cookie[..]); } } ================================================ FILE: netlink-packet-sock-diag/src/inet/tests.rs ================================================ // SPDX-License-Identifier: MIT use std::{ net::{IpAddr, Ipv4Addr}, time::Duration, }; use crate::{ constants::*, inet::{ nlas::Nla, ExtensionFlags, InetRequest, InetRequestBuffer, InetResponse, InetResponseBuffer, InetResponseHeader, SocketId, StateFlags, Timer, }, traits::{Emitable, Parseable}, }; lazy_static! { static ref REQ_UDP: InetRequest = InetRequest { family: AF_INET as u8, protocol: IPPROTO_UDP, extensions: ExtensionFlags::empty(), states: StateFlags::ESTABLISHED, socket_id: SocketId::new_v4(), }; } #[rustfmt::skip] static REQ_UDP_BUF: [u8; 56] = [ 0x02, // family (AF_INET) 0x11, // protocol (IPPROTO_UDP) 0x00, // extensions 0x00, // padding 0x02, 0x00, 0x00, 0x00, // states // socket id 0x00, 0x00, // source port 0x00, 0x00, // destination port // source address 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // destination address 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // interface id 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // cookie ]; #[test] fn parse_udp_req() { let parsed = InetRequest::parse(&InetRequestBuffer::new_checked(&&REQ_UDP_BUF[..]).unwrap()).unwrap(); assert_eq!(parsed, *REQ_UDP); } #[test] fn emit_udp_req() { assert_eq!(REQ_UDP.buffer_len(), 56); let mut buf = vec![0; REQ_UDP.buffer_len()]; REQ_UDP.emit(&mut buf); assert_eq!(&buf[..], &REQ_UDP_BUF[..]); } lazy_static! { static ref RESP_TCP: InetResponse = InetResponse { header: InetResponseHeader { family: AF_INET as u8, state: TCP_ESTABLISHED, timer: Some(Timer::KeepAlive(Duration::from_millis(0x0000_6080))), recv_queue: 0, send_queue: 0, uid: 1000, inode: 0x0029_daa8, socket_id: SocketId { source_port: 60180, destination_port: 443, source_address: IpAddr::V4(Ipv4Addr::new(192, 168, 178, 60)), destination_address: IpAddr::V4(Ipv4Addr::new(172, 217, 23, 131)), interface_id: 0, cookie: [0x52, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], }, }, nlas: smallvec![Nla::Shutdown(0)], }; } #[rustfmt::skip] static RESP_TCP_BUF: [u8; 80] = [ 0x02, // family (AF_INET) 0x01, // state (ESTABLISHED) 0x02, // timer 0x00, // retransmits // socket id 0xeb, 0x14, // source port (60180) 0x01, 0xbb, // destination port (443) // source ip (192.168.178.60) 0xc0, 0xa8, 0xb2, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // destination ip (172.217.23.131) 0xac, 0xd9, 0x17, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // interface id 0x52, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // cookie 0x80, 0x60, 0x00, 0x00, // expires 0x00, 0x00, 0x00, 0x00, // receive queue 0x00, 0x00, 0x00, 0x00, // send queue 0xe8, 0x03, 0x00, 0x00, // uid 0xa8, 0xda, 0x29, 0x00, // inode // nlas 0x05, 0x00, // length = 5 0x08, 0x00, // type = 8 0x00, // value (0) 0x00, 0x00, 0x00 ]; #[test] fn parse_tcp_resp() { let parsed = InetResponse::parse(&InetResponseBuffer::new_checked(&&RESP_TCP_BUF[..]).unwrap()).unwrap(); assert_eq!(parsed, *RESP_TCP); } #[test] fn emit_tcp_resp() { assert_eq!(RESP_TCP.buffer_len(), 80); let mut buf = vec![0; RESP_TCP.buffer_len()]; RESP_TCP.emit(&mut buf); assert_eq!(&buf[..], &RESP_TCP_BUF[..]); } ================================================ FILE: netlink-packet-sock-diag/src/lib.rs ================================================ // SPDX-License-Identifier: MIT #[macro_use] extern crate bitflags; #[macro_use] pub(crate) extern crate netlink_packet_utils as utils; pub(crate) use self::utils::parsers; pub use self::utils::{traits, DecodeError}; pub use netlink_packet_core::{ ErrorMessage, NetlinkBuffer, NetlinkHeader, NetlinkMessage, NetlinkPayload, }; pub(crate) use netlink_packet_core::{NetlinkDeserializable, NetlinkSerializable}; #[cfg(test)] #[macro_use] extern crate lazy_static; #[macro_use] extern crate smallvec; pub mod buffer; pub mod constants; pub mod inet; pub mod message; pub mod unix; pub use self::{buffer::*, constants::*, message::*}; ================================================ FILE: netlink-packet-sock-diag/src/message.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ inet, traits::{Emitable, ParseableParametrized}, unix, DecodeError, NetlinkDeserializable, NetlinkHeader, NetlinkPayload, NetlinkSerializable, SockDiagBuffer, SOCK_DIAG_BY_FAMILY, }; #[derive(Debug, PartialEq, Eq, Clone)] pub enum SockDiagMessage { InetRequest(inet::InetRequest), InetResponse(Box), UnixRequest(unix::UnixRequest), UnixResponse(Box), } impl SockDiagMessage { pub fn is_inet_request(&self) -> bool { matches!(self, SockDiagMessage::InetRequest(_)) } pub fn is_inet_response(&self) -> bool { matches!(self, SockDiagMessage::InetResponse(_)) } pub fn is_unix_request(&self) -> bool { matches!(self, SockDiagMessage::UnixRequest(_)) } pub fn is_unix_response(&self) -> bool { matches!(self, SockDiagMessage::UnixResponse(_)) } pub fn message_type(&self) -> u16 { SOCK_DIAG_BY_FAMILY } } impl Emitable for SockDiagMessage { fn buffer_len(&self) -> usize { use SockDiagMessage::*; match self { InetRequest(ref msg) => msg.buffer_len(), InetResponse(ref msg) => msg.buffer_len(), UnixRequest(ref msg) => msg.buffer_len(), UnixResponse(ref msg) => msg.buffer_len(), } } fn emit(&self, buffer: &mut [u8]) { use SockDiagMessage::*; match self { InetRequest(ref msg) => msg.emit(buffer), InetResponse(ref msg) => msg.emit(buffer), UnixRequest(ref msg) => msg.emit(buffer), UnixResponse(ref msg) => msg.emit(buffer), } } } impl NetlinkSerializable for SockDiagMessage { fn message_type(&self) -> u16 { self.message_type() } fn buffer_len(&self) -> usize { ::buffer_len(self) } fn serialize(&self, buffer: &mut [u8]) { self.emit(buffer) } } impl NetlinkDeserializable for SockDiagMessage { type Error = DecodeError; fn deserialize(header: &NetlinkHeader, payload: &[u8]) -> Result { let buffer = SockDiagBuffer::new_checked(&payload)?; SockDiagMessage::parse_with_param(&buffer, header.message_type) } } impl From for NetlinkPayload { fn from(message: SockDiagMessage) -> Self { NetlinkPayload::InnerMessage(message) } } ================================================ FILE: netlink-packet-sock-diag/src/unix/mod.rs ================================================ // SPDX-License-Identifier: MIT mod request; pub use self::request::*; mod response; pub use self::response::*; pub mod nlas; #[cfg(test)] mod tests; ================================================ FILE: netlink-packet-sock-diag/src/unix/nlas.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; pub use crate::utils::nla::{DefaultNla, NlaBuffer, NlasIterator}; use crate::{ constants::*, parsers::{parse_string, parse_u32, parse_u8}, traits::{Emitable, Parseable}, DecodeError, }; #[derive(Debug, Eq, PartialEq, Clone)] pub enum Nla { /// Path to which the socket was bound. This attribute is known as /// `UNIX_DIAG_NAME` in the kernel. Name(String), /// VFS information for this socket. This attribute is known as /// `UNIX_DIAG_VFS` in the kernel. Vfs(Vfs), /// Inode number of the socket's peer. This attribute is reported /// for connected socket only. This attribute is known as /// `UNIX_DIAG_PEER` in the kernel. Peer(u32), /// The payload associated with this attribute is an array of /// inode numbers of sockets that have passed the `connect(2)` /// call, but haven't been processed with `accept(2)` yet. This /// attribute is reported for listening sockets only. This /// attribute is known as `UNIX_DIAG_ICONS` in the kernel. PendingConnections(Vec), /// This attribute corresponds to the `UNIX_DIAG_RQLEN`. It /// reports the length of the socket receive queue, and the queue /// size limit. Note that for **listening** sockets the receive /// queue is used to store actual data sent by other sockets. It /// is used to store pending connections. So the meaning of this /// attribute differs for listening sockets. /// /// For **listening** sockets: /// /// - the first the number is the number of pending /// connections. It should be equal to `Nla::PendingConnections` /// value's length. /// - the second number is the backlog queue maximum length, which /// equals to the value passed as the second argument to /// `listen(2)` /// /// For other sockets: /// /// - the first number is the amount of data in receive queue /// (**note**: I am not sure if it is the actual amount of data /// or the amount of memory allocated. The two might differ /// because of memory allocation strategies: more memory than /// strictly necessary may be allocated for a given `sk_buff`) /// - the second number is the memory used by outgoing data. Note /// that strictly UNIX sockets don't have a send queue, since /// the data they send is directly written into the destination /// socket receive queue. But the memory allocated for this data /// is still counted from the sender point of view. ReceiveQueueLength(u32, u32), /// Socket memory information. See [`MemInfo`] for more details. MemInfo(MemInfo), /// Shutown state: one of [`SHUT_RD`], [`SHUT_WR`] or [`SHUT_RDWR`] Shutdown(u8), /// Unknown attribute Other(DefaultNla), } pub const VFS_LEN: usize = 8; buffer!(VfsBuffer(8) { inode: (u32, 0..4), device: (u32, 4..8), }); #[derive(Debug, Eq, PartialEq, Clone)] pub struct Vfs { /// Inode number inode: u32, /// Device number device: u32, } impl> Parseable> for Vfs { fn parse(buf: &VfsBuffer) -> Result { Ok(Self { inode: buf.inode(), device: buf.device(), }) } } impl Emitable for Vfs { fn buffer_len(&self) -> usize { VFS_LEN } fn emit(&self, buf: &mut [u8]) { let mut buf = VfsBuffer::new(buf); buf.set_inode(self.inode); buf.set_device(self.device); } } pub const MEM_INFO_LEN: usize = 36; buffer!(MemInfoBuffer(MEM_INFO_LEN) { unused_sk_rmem_alloc: (u32, 0..4), so_rcvbuf: (u32, 4..8), unused_sk_wmem_queued: (u32, 8..12), max_datagram_size: (u32, 12..16), unused_sk_fwd_alloc: (u32, 16..20), alloc: (u32, 20..24), unused_sk_optmem: (u32, 24..28), unused_backlog: (u32, 28..32), unused_drops: (u32, 32..36), }); /// # Warning /// /// I don't have a good understanding of the Unix Domain Sockets, thus /// take the following documentation with a *huge* grain of salt. /// /// # Documentation /// /// ## `UNIX_DIAG_MEMINFO` vs `INET_DIAG_SK_MEMINFO` /// /// `MemInfo` represent an `UNIX_DIAG_MEMINFO` NLA. This NLA has the /// same structure than `INET_DIAG_SKMEMINFO`, but since Unix sockets /// don't actually use the network stack, many fields are not relevant /// and are always set to 0. According to iproute2 commit /// [51ff9f2453d066933f24170f0106a7deeefa02d9](https://patchwork.ozlabs.org/patch/222700/), only three attributes can have non-zero values. /// /// ## Particularities of UNIX sockets /// /// One particularity of UNIX sockets is that they don't really have a /// send queue: when sending data, the kernel finds the destination /// socket and enqueues the data directly in its receive queue (which /// [see also this StackOverflow /// answer](https://stackoverflow.com/questions/9644251/how-do-unix-domain-sockets-differentiate-between-multiple-clients)). For /// instance in `unix_dgram_sendmsg()` in `net/unix/af_unix.c` we /// have: /// /// ```c /// // `other` refers to the peer socket here /// skb_queue_tail(&other->sk_receive_queue, skb); /// ``` /// /// Another particularity is that the kernel keeps track of the memory /// using the sender's `sock.sk_wmem_alloc` attribute. The receiver's /// `sock.sk_rmem_alloc` is always zero. Memory is allocated when data /// is written to a socket, and is reclaimed when the data is read /// from the peer's socket. /// /// Last but not least, the way unix sockets handle incoming /// connection differs from the TCP sockets. For TCP sockets, the /// queue used to store pending connections is /// `sock.sk_ack_backlog`. But UNIX sockets use the receive queue to /// store them. They can do that because a listening socket only /// receive connections, they do not receive actual data from other /// socket, so there is no ambiguity about the nature of the data /// stored in the receive queue. // /// We can see that in `unix_stream_sendmsg()` for instance we have // /// the follownig function calls: // /// // /// ``` // /// unix_stream_sendmsg() // /// -> sock_alloc_send_pskb() // /// -> skb_set_owner_w() // /// -> refcount_add(size, &sk->sk_wmem_alloc); /// ``` #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub struct MemInfo { /// Value of `SO_RCVBUF`, although it does not have any effect on /// Unix Domain Sockets. As per `man unix(7)`: /// /// > The `SO_SNDBUF` socket option does have an effect for UNIX /// > domain sockets, but the `SO_RCVBUF` option does not. /// /// This attribute corresponds to `sock.sk_rcvbuf` in the kernel. pub so_rcvbuf: u32, /// Maximum size in in bytes of a datagram, as set by /// `SO_SNDBUF`. As per `man unix(7)`: /// /// > For datagram sockets, the `SO_SNDBUF` value imposes an upper /// > limit on the size of outgoing datagrams. This limit is /// > calculated as the doubled (see `socket(7)`) option value /// > less 32 bytes used for overhead. /// /// This attribute corresponds to `sock.sk_sndbuf` in the kernel. pub max_datagram_size: u32, /// Memory currently allocated for the data sent but not yet read /// from the receiving socket(s). The memory is tracked using the /// sending socket `sock.sk_wmem_queued` attribute in the kernel. /// /// Note that this quantity is a little larger than the actual /// data being sent because it takes into account the overhead of /// the `sk_buff`s used internally: /// /// ```c /// /* in net/core/sock.c, sk_wmem_alloc is set in /// skb_set_owner_w() with: */ /// refcount_add(skb->truesize, &sk->sk_wmem_alloc); /// /// /* truesize is set by __alloc_skb() in net/core/skbuff.c /// by: */ /// skb->truesize = SKB_TRUESIZE(size); /// /// /* and SKB_TRUESIZE is defined as: */ /// #define SKB_TRUESIZE(X) ((X) + \ /// SKB_DATA_ALIGN(sizeof(struct sk_buff)) + \ /// SKB_DATA_ALIGN(sizeof(struct skb_shared_info))) /// ``` pub alloc: u32, } impl> Parseable> for MemInfo { fn parse(buf: &MemInfoBuffer) -> Result { Ok(Self { so_rcvbuf: buf.so_rcvbuf(), max_datagram_size: buf.max_datagram_size(), alloc: buf.alloc(), }) } } impl Emitable for MemInfo { fn buffer_len(&self) -> usize { MEM_INFO_LEN } fn emit(&self, buf: &mut [u8]) { let mut buf = MemInfoBuffer::new(buf); buf.set_unused_sk_rmem_alloc(0); buf.set_so_rcvbuf(self.so_rcvbuf); buf.set_unused_sk_wmem_queued(0); buf.set_max_datagram_size(self.max_datagram_size); buf.set_unused_sk_fwd_alloc(0); buf.set_alloc(self.alloc); buf.set_unused_sk_optmem(0); buf.set_unused_backlog(0); buf.set_unused_drops(0); } } impl crate::utils::nla::Nla for Nla { fn value_len(&self) -> usize { use self::Nla::*; match *self { // +1 because we need to append a null byte Name(ref s) => s.as_bytes().len() + 1, Vfs(_) => VFS_LEN, Peer(_) => 4, PendingConnections(ref v) => 4 * v.len(), ReceiveQueueLength(_, _) => 8, MemInfo(_) => MEM_INFO_LEN, Shutdown(_) => 1, Other(ref attr) => attr.value_len(), } } fn emit_value(&self, buffer: &mut [u8]) { use self::Nla::*; match *self { Name(ref s) => { buffer[..s.len()].copy_from_slice(s.as_bytes()); buffer[s.len()] = 0; } Vfs(ref value) => value.emit(buffer), Peer(value) => NativeEndian::write_u32(buffer, value), PendingConnections(ref values) => { for (i, v) in values.iter().enumerate() { NativeEndian::write_u32(&mut buffer[i * 4..], *v); } } ReceiveQueueLength(v1, v2) => { NativeEndian::write_u32(buffer, v1); NativeEndian::write_u32(&mut buffer[4..], v2); } MemInfo(ref value) => value.emit(buffer), Shutdown(value) => buffer[0] = value, Other(ref attr) => attr.emit_value(buffer), } } fn kind(&self) -> u16 { use self::Nla::*; match *self { Name(_) => UNIX_DIAG_NAME, Vfs(_) => UNIX_DIAG_VFS, Peer(_) => UNIX_DIAG_PEER, PendingConnections(_) => UNIX_DIAG_ICONS, ReceiveQueueLength(_, _) => UNIX_DIAG_RQLEN, MemInfo(_) => UNIX_DIAG_MEMINFO, Shutdown(_) => UNIX_DIAG_SHUTDOWN, Other(ref attr) => attr.kind(), } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nla { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { UNIX_DIAG_NAME => { let err = "invalid UNIX_DIAG_NAME value"; Self::Name(parse_string(payload).context(err)?) } UNIX_DIAG_VFS => { let err = "invalid UNIX_DIAG_VFS value"; let buf = VfsBuffer::new_checked(payload).context(err)?; Self::Vfs(Vfs::parse(&buf).context(err)?) } UNIX_DIAG_PEER => { Self::Peer(parse_u32(payload).context("invalid UNIX_DIAG_PEER value")?) } UNIX_DIAG_ICONS => { if payload.len() % 4 != 0 { return Err(DecodeError::from("invalid UNIX_DIAG_ICONS")); } Self::PendingConnections(payload.chunks(4).map(NativeEndian::read_u32).collect()) } UNIX_DIAG_RQLEN => { if payload.len() != 8 { return Err(DecodeError::from("invalid UNIX_DIAG_RQLEN")); } Self::ReceiveQueueLength( NativeEndian::read_u32(&payload[..4]), NativeEndian::read_u32(&payload[4..]), ) } UNIX_DIAG_MEMINFO => { let err = "invalid UNIX_DIAG_MEMINFO value"; let buf = MemInfoBuffer::new_checked(payload).context(err)?; Self::MemInfo(MemInfo::parse(&buf).context(err)?) } UNIX_DIAG_SHUTDOWN => { Self::Shutdown(parse_u8(payload).context("invalid UNIX_DIAG_SHUTDOWN value")?) } kind => { Self::Other(DefaultNla::parse(buf).context(format!("unknown NLA type {}", kind))?) } }) } } ================================================ FILE: netlink-packet-sock-diag/src/unix/request.rs ================================================ // SPDX-License-Identifier: MIT use std::convert::TryFrom; use crate::{ constants::*, traits::{Emitable, Parseable}, DecodeError, }; pub const UNIX_REQUEST_LEN: usize = 24; buffer!(UnixRequestBuffer(UNIX_REQUEST_LEN) { // The address family; it should be set to `AF_UNIX` family: (u8, 0), // This field should be set to `0` protocol: (u8, 1), // This field should be set to `0` pad: (u16, 2..4), // This is a bit mask that defines a filter of sockets // states. Only those sockets whose states are in this mask will // be reported. Ignored when querying for an individual // socket. Supported values are: // // ```no_rust // 1 << UNIX_ESTABLISHED // 1 << UNIX_LISTEN // ``` state_flags: (u32, 4..8), // This is an inode number when querying for an individual // socket. Ignored when querying for a list of sockets. inode: (u32, 8..12), // This is a set of flags defining what kind of information to // report. Supported values are the `UDIAG_SHOW_*` constants. show_flags: (u32, 12..16), // This is an array of opaque identifiers that could be used // along with udiag_ino to specify an individual socket. It is // ignored when querying for a list of sockets, as well as when // all its elements are set to `0xff`. cookie: (slice, 16..UNIX_REQUEST_LEN), }); /// The request for UNIX domain sockets #[derive(Debug, PartialEq, Eq, Clone)] pub struct UnixRequest { /// This is a bit mask that defines a filter of sockets states. /// /// Only those sockets whose states are in this mask will be reported. /// Ignored when querying for an individual socket. pub state_flags: StateFlags, /// This is an inode number when querying for an individual socket. /// /// Ignored when querying for a list of sockets. pub inode: u32, /// This is a set of flags defining what kind of information to report. /// /// Each requested kind of information is reported back as a netlink attribute pub show_flags: ShowFlags, /// This is an opaque identifiers that could be used to specify an individual socket. pub cookie: [u8; 8], } bitflags! { /// Bitmask that defines a filter of UNIX socket states pub struct StateFlags: u32 { const ESTABLISHED = 1 << TCP_ESTABLISHED; const LISTEN = 1 << TCP_LISTEN; } } bitflags! { /// Bitmask that defines what kind of information to /// report. Supported values are the `UDIAG_SHOW_*` constants. pub struct ShowFlags: u32 { const NAME = UDIAG_SHOW_NAME; const VFS = UDIAG_SHOW_VFS; const PEER = UDIAG_SHOW_PEER; const ICONS = UDIAG_SHOW_ICONS; const RQLEN = UDIAG_SHOW_RQLEN; const MEMINFO = UDIAG_SHOW_MEMINFO; } } impl<'a, T: AsRef<[u8]> + 'a> Parseable> for UnixRequest { fn parse(buf: &UnixRequestBuffer<&'a T>) -> Result { Ok(Self { state_flags: StateFlags::from_bits_truncate(buf.state_flags()), inode: buf.inode(), show_flags: ShowFlags::from_bits_truncate(buf.show_flags()), // Unwrapping is safe because UnixRequestBuffer::cookie() // returns a slice of exactly 8 bytes. cookie: TryFrom::try_from(buf.cookie()).unwrap(), }) } } impl Emitable for UnixRequest { fn buffer_len(&self) -> usize { UNIX_REQUEST_LEN } fn emit(&self, buf: &mut [u8]) { let mut buffer = UnixRequestBuffer::new(buf); buffer.set_family(AF_UNIX); buffer.set_protocol(0); buffer.set_state_flags(self.state_flags.bits()); buffer.set_inode(self.inode); buffer.set_pad(0); buffer.set_show_flags(self.show_flags.bits()); buffer.cookie_mut().copy_from_slice(&self.cookie[..]); } } ================================================ FILE: netlink-packet-sock-diag/src/unix/response.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::Context; use smallvec::SmallVec; use std::convert::TryFrom; use crate::{ constants::*, traits::{Emitable, Parseable}, unix::nlas::{MemInfo, Nla, NlaBuffer, NlasIterator}, DecodeError, }; pub const UNIX_RESPONSE_HEADER_LEN: usize = 16; buffer!(UnixResponseBuffer(UNIX_RESPONSE_HEADER_LEN) { family: (u8, 0), kind: (u8, 1), state: (u8, 2), pad: (u8, 3), inode: (u32, 4..8), cookie: (slice, 8..UNIX_RESPONSE_HEADER_LEN), payload: (slice, UNIX_RESPONSE_HEADER_LEN..), }); /// The response to a query for IPv4 or IPv6 sockets #[derive(Debug, PartialEq, Eq, Clone)] pub struct UnixResponseHeader { /// One of `SOCK_PACKET`, `SOCK_STREAM`, or `SOCK_SEQPACKET` pub kind: u8, /// State of the socket. According to `man 7 sock_diag` it can be /// either `TCP_ESTABLISHED` or `TCP_LISTEN`. However datagram /// UNIX sockets are not connection oriented so I would assume /// that this field can also take other value (maybe `0`) for /// these sockets. pub state: u8, /// Socket inode number. pub inode: u32, pub cookie: [u8; 8], } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for UnixResponseHeader { fn parse(buf: &UnixResponseBuffer<&'a T>) -> Result { Ok(Self { kind: buf.kind(), state: buf.state(), inode: buf.inode(), // Unwrapping is safe because UnixResponseBuffer::cookie() // returns a slice of exactly 8 bytes. cookie: TryFrom::try_from(buf.cookie()).unwrap(), }) } } impl Emitable for UnixResponseHeader { fn buffer_len(&self) -> usize { UNIX_RESPONSE_HEADER_LEN } fn emit(&self, buf: &mut [u8]) { let mut buf = UnixResponseBuffer::new(buf); buf.set_family(AF_UNIX as u8); buf.set_kind(self.kind); buf.set_state(self.state); buf.set_pad(0); buf.set_inode(self.inode); buf.cookie_mut().copy_from_slice(&self.cookie[..]); } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct UnixResponse { pub header: UnixResponseHeader, pub nlas: SmallVec<[Nla; 8]>, } impl UnixResponse { pub fn peer(&self) -> Option { self.nlas.iter().find_map(|nla| { if let Nla::Peer(inode) = nla { Some(*inode) } else { None } }) } pub fn name(&self) -> Option<&String> { self.nlas.iter().find_map(|nla| { if let Nla::Name(name) = nla { Some(name) } else { None } }) } pub fn pending_connections(&self) -> Option<&[u32]> { self.nlas.iter().find_map(|nla| { if let Nla::PendingConnections(connections) = nla { Some(&connections[..]) } else { None } }) } fn mem_info(&self) -> Option { self.nlas.iter().find_map(|nla| { if let Nla::MemInfo(mem_info) = nla { Some(*mem_info) } else { None } }) } pub fn shutdown_state(&self) -> Option { self.nlas.iter().find_map(|nla| { if let Nla::Shutdown(shutdown_state) = nla { Some(*shutdown_state) } else { None } }) } fn receive_queue_length(&self) -> Option<(u32, u32)> { self.nlas.iter().find_map(|nla| { if let Nla::ReceiveQueueLength(x, y) = nla { Some((*x, *y)) } else { None } }) } pub fn number_of_pending_connection(&self) -> Option { if self.header.state == TCP_LISTEN { self.receive_queue_length().map(|(n, _)| n) } else { None } } pub fn max_number_of_pending_connection(&self) -> Option { if self.header.state == TCP_LISTEN { self.receive_queue_length().map(|(_, n)| n) } else { None } } pub fn receive_queue_size(&self) -> Option { if self.header.state == TCP_LISTEN { None } else { self.receive_queue_length().map(|(n, _)| n) } } pub fn send_queue_size(&self) -> Option { if self.header.state == TCP_LISTEN { self.receive_queue_length().map(|(n, _)| n) } else { None } } pub fn max_datagram_size(&self) -> Option { self.mem_info().map(|mem_info| mem_info.max_datagram_size) } pub fn memory_used_for_outgoing_data(&self) -> Option { self.mem_info().map(|mem_info| mem_info.alloc) } } impl<'a, T: AsRef<[u8]> + ?Sized> UnixResponseBuffer<&'a T> { pub fn nlas(&self) -> impl Iterator, DecodeError>> { NlasIterator::new(self.payload()) } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for SmallVec<[Nla; 8]> { fn parse(buf: &UnixResponseBuffer<&'a T>) -> Result { let mut nlas = smallvec![]; for nla_buf in buf.nlas() { nlas.push(Nla::parse(&nla_buf?)?); } Ok(nlas) } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for UnixResponse { fn parse(buf: &UnixResponseBuffer<&'a T>) -> Result { let header = UnixResponseHeader::parse(buf).context("failed to parse inet response header")?; let nlas = SmallVec::<[Nla; 8]>::parse(buf).context("failed to parse inet response NLAs")?; Ok(UnixResponse { header, nlas }) } } impl Emitable for UnixResponse { fn buffer_len(&self) -> usize { self.header.buffer_len() + self.nlas.as_slice().buffer_len() } fn emit(&self, buffer: &mut [u8]) { self.header.emit(buffer); self.nlas .as_slice() .emit(&mut buffer[self.header.buffer_len()..]); } } ================================================ FILE: netlink-packet-sock-diag/src/unix/tests.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ constants::*, traits::{Emitable, Parseable}, unix::{ nlas::Nla, ShowFlags, StateFlags, UnixRequest, UnixResponse, UnixResponseBuffer, UnixResponseHeader, }, }; lazy_static! { static ref SOCKET_INFO: UnixRequest = UnixRequest { state_flags: StateFlags::all(), inode: 0x1234, show_flags: ShowFlags::PEER, cookie: [0xff; 8] }; } #[rustfmt::skip] static SOCKET_INFO_BUF: [u8; 24] = [ 0x01, // family: AF_UNIX 0x00, // protocol 0x00, 0x00, // padding 0x02, 0x04, 0x00, 0x00, // state_flags - 1 << TCP_ESTABLISHED | 1 << TCP_LISTEN 0x34, 0x12, 0x00, 0x00, // inode number 0x04, 0x00, 0x00, 0x00, // show_flags - UDIAG_SHOW_PEER 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // cookie ]; lazy_static! { static ref LISTENING: UnixResponse = UnixResponse { header: UnixResponseHeader { kind: SOCK_STREAM, state: TCP_LISTEN, inode: 20238, cookie: [0xa0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], }, nlas: smallvec![ Nla::Name("/tmp/.ICE-unix/1151".to_string()), Nla::ReceiveQueueLength(0, 128), Nla::Shutdown(0), ] }; } #[rustfmt::skip] static LISTENING_BUF: [u8; 60] = [ 0x01, // family: AF_UNIX 0x01, // type: SOCK_STREAM 0x0a, // state: TCP_LISTEN 0x00, // padding 0x0e, 0x4f, 0x00, 0x00, // inode number 0xa0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // cookie // NLAs 0x18, 0x00, // length: 24 0x00, 0x00, // type: UNIX_DIAG_NAME // value: /tmp/.ICE-unix/1151 0x2f, 0x74, 0x6d, 0x70, 0x2f, 0x2e, 0x49, 0x43, 0x45, 0x2d, 0x75, 0x6e, 0x69, 0x78, 0x2f, 0x31, 0x31, 0x35, 0x31, 0x00, 0x0c, 0x00, // length: 12 0x04, 0x00, // type: UNIX_DIAG_RQLEN // value: ReceiveQueueLength(0, 128) 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x05, 0x00, // length: 5 0x06, 0x00, // type: UNIX_DIAG_SHUTDOWN 0x00, // value: 0 0x00, 0x00, 0x00 // padding ]; #[test] fn parse_listening() { let parsed = UnixResponse::parse(&UnixResponseBuffer::new_checked(&&LISTENING_BUF[..]).unwrap()) .unwrap(); assert_eq!(parsed, *LISTENING); } #[test] fn emit_listening() { assert_eq!(LISTENING.buffer_len(), 60); // Initialize the buffer with 0xff to check that padding bytes are // set to 0 let mut buf = vec![0xff; LISTENING.buffer_len()]; LISTENING.emit(&mut buf); assert_eq!(&buf[..], &LISTENING_BUF[..]); } lazy_static! { static ref ESTABLISHED: UnixResponse = UnixResponse { header: UnixResponseHeader { kind: SOCK_STREAM, state: TCP_ESTABLISHED, inode: 31927, cookie: [0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] }, nlas: smallvec![ Nla::Name("/run/user/1000/bus".to_string()), Nla::Peer(31062), Nla::ReceiveQueueLength(0, 0), Nla::Shutdown(0), ] }; } #[rustfmt::skip] static ESTABLISHED_BUF: [u8; 68] = [ 0x01, // family: AF_LOCAL 0x01, // kind: SOCK_STREAM, 0x01, // state: TCP_ESTABLISHED 0x00, // padding 0xb7, 0x7c, 0x00, 0x00, // inode: 31927 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // cookie // NLAs 0x17, 0x00, // length: 23 0x00, 0x00, // type: UNIX_DIAG_NAME // value: /run/user/1000/bus 0x2f, 0x72, 0x75, 0x6e, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x31, 0x30, 0x30, 0x30, 0x2f, 0x62, 0x75, 0x73, 0x00, 0x00, // padding 0x08, 0x00, // length: 8 0x02, 0x00, // type: UNIX_DIAG_PEER 0x56, 0x79, 0x00, 0x00, // value: 31062 0x0c, 0x00, // length: 12 0x04, 0x00, // type: UNIX_DIAG_RQLEN // value: ReceiveQueueLength(0, 0) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, // length: 5 0x06, 0x00, // type: UNIX_DIAG_SHUTDOWN 0x00, 0x00, 0x00, 0x00 // value: 0 ]; #[test] fn parse_established() { let parsed = UnixResponse::parse(&UnixResponseBuffer::new_checked(&&ESTABLISHED_BUF[..]).unwrap()) .unwrap(); assert_eq!(parsed, *ESTABLISHED); } #[test] fn emit_established() { assert_eq!(ESTABLISHED.buffer_len(), 68); let mut buf = vec![0xff; ESTABLISHED.buffer_len()]; ESTABLISHED.emit(&mut buf); assert_eq!(&buf[..], &ESTABLISHED_BUF[..]); } #[test] fn emit_socket_info() { assert_eq!(SOCKET_INFO.buffer_len(), 24); let mut buf = vec![0xff; SOCKET_INFO.buffer_len()]; SOCKET_INFO.emit(&mut buf); assert_eq!(&buf[..], &SOCKET_INFO_BUF[..]); } ================================================ FILE: netlink-packet-utils/Cargo.toml ================================================ [package] name = "netlink-packet-utils" version = "0.5.1" authors = ["Corentin Henry "] edition = "2018" homepage = "https://github.com/little-dude/netlink" repository = "https://github.com/little-dude/netlink" license = "MIT" description = "macros and helpers for parsing netlink messages" [dependencies] anyhow = "1.0.31" byteorder = "1.3.2" paste = "1.0" thiserror = "1" ================================================ FILE: netlink-packet-utils/src/errors.rs ================================================ // SPDX-License-Identifier: MIT use anyhow::anyhow; use thiserror::Error; #[derive(Debug, Error)] #[error("Encode error occurred: {inner}")] pub struct EncodeError { inner: anyhow::Error, } impl From<&'static str> for EncodeError { fn from(msg: &'static str) -> Self { EncodeError { inner: anyhow!(msg), } } } impl From for EncodeError { fn from(msg: String) -> Self { EncodeError { inner: anyhow!(msg), } } } impl From for EncodeError { fn from(inner: anyhow::Error) -> EncodeError { EncodeError { inner } } } #[derive(Debug, Error)] #[error("Decode error occurred: {inner}")] pub struct DecodeError { inner: anyhow::Error, } impl From<&'static str> for DecodeError { fn from(msg: &'static str) -> Self { DecodeError { inner: anyhow!(msg), } } } impl From for DecodeError { fn from(msg: String) -> Self { DecodeError { inner: anyhow!(msg), } } } impl From for DecodeError { fn from(inner: anyhow::Error) -> DecodeError { DecodeError { inner } } } ================================================ FILE: netlink-packet-utils/src/lib.rs ================================================ // SPDX-License-Identifier: MIT pub extern crate byteorder; pub extern crate paste; #[macro_use] mod macros; pub mod errors; pub use self::errors::{DecodeError, EncodeError}; pub mod parsers; pub mod traits; pub use self::traits::*; pub mod nla; ================================================ FILE: netlink-packet-utils/src/macros.rs ================================================ // SPDX-License-Identifier: MIT #[macro_export(local_inner_macros)] macro_rules! getter { ($buffer: ident, $name:ident, slice, $offset:expr) => { impl<'a, T: AsRef<[u8]> + ?Sized> $buffer<&'a T> { pub fn $name(&self) -> &'a [u8] { &self.buffer.as_ref()[$offset] } } }; ($buffer: ident, $name:ident, $ty:tt, $offset:expr) => { impl<'a, T: AsRef<[u8]>> $buffer { getter!($name, $ty, $offset); } }; ($name:ident, u8, $offset:expr) => { pub fn $name(&self) -> u8 { self.buffer.as_ref()[$offset] } }; ($name:ident, u16, $offset:expr) => { pub fn $name(&self) -> u16 { use $crate::byteorder::{ByteOrder, NativeEndian}; NativeEndian::read_u16(&self.buffer.as_ref()[$offset]) } }; ($name:ident, u32, $offset:expr) => { pub fn $name(&self) -> u32 { use $crate::byteorder::{ByteOrder, NativeEndian}; NativeEndian::read_u32(&self.buffer.as_ref()[$offset]) } }; ($name:ident, u64, $offset:expr) => { pub fn $name(&self) -> u64 { use $crate::byteorder::{ByteOrder, NativeEndian}; NativeEndian::read_u64(&self.buffer.as_ref()[$offset]) } }; ($name:ident, i8, $offset:expr) => { pub fn $name(&self) -> i8 { self.buffer.as_ref()[$offset] } }; ($name:ident, i16, $offset:expr) => { pub fn $name(&self) -> i16 { use $crate::byteorder::{ByteOrder, NativeEndian}; NativeEndian::read_i16(&self.buffer.as_ref()[$offset]) } }; ($name:ident, i32, $offset:expr) => { pub fn $name(&self) -> i32 { use $crate::byteorder::{ByteOrder, NativeEndian}; NativeEndian::read_i32(&self.buffer.as_ref()[$offset]) } }; ($name:ident, i64, $offset:expr) => { pub fn $name(&self) -> i64 { use $crate::byteorder::{ByteOrder, NativeEndian}; NativeEndian::read_i64(&self.buffer.as_ref()[$offset]) } }; } #[macro_export(local_inner_macros)] macro_rules! setter { ($buffer: ident, $name:ident, slice, $offset:expr) => { impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> $buffer<&'a mut T> { $crate::paste::item! { pub fn [<$name _mut>](&mut self) -> &mut [u8] { &mut self.buffer.as_mut()[$offset] } } } }; ($buffer: ident, $name:ident, $ty:tt, $offset:expr) => { impl<'a, T: AsRef<[u8]> + AsMut<[u8]>> $buffer { setter!($name, $ty, $offset); } }; ($name:ident, u8, $offset:expr) => { $crate::paste::item! { pub fn [](&mut self, value: u8) { self.buffer.as_mut()[$offset] = value; } } }; ($name:ident, u16, $offset:expr) => { $crate::paste::item! { pub fn [](&mut self, value: u16) { use $crate::byteorder::{ByteOrder, NativeEndian}; NativeEndian::write_u16(&mut self.buffer.as_mut()[$offset], value) } } }; ($name:ident, u32, $offset:expr) => { $crate::paste::item! { pub fn [](&mut self, value: u32) { use $crate::byteorder::{ByteOrder, NativeEndian}; NativeEndian::write_u32(&mut self.buffer.as_mut()[$offset], value) } } }; ($name:ident, u64, $offset:expr) => { $crate::paste::item! { pub fn [](&mut self, value: u64) { use $crate::byteorder::{ByteOrder, NativeEndian}; NativeEndian::write_u64(&mut self.buffer.as_mut()[$offset], value) } } }; ($name:ident, i8, $offset:expr) => { $crate::paste::item! { pub fn [](&mut self, value: i8) { self.buffer.as_mut()[$offset] = value; } } }; ($name:ident, i16, $offset:expr) => { $crate::paste::item! { pub fn [](&mut self, value: i16) { use $crate::byteorder::{ByteOrder, NativeEndian}; NativeEndian::write_i16(&mut self.buffer.as_mut()[$offset], value) } } }; ($name:ident, i32, $offset:expr) => { $crate::paste::item! { pub fn [](&mut self, value: i32) { use $crate::byteorder::{ByteOrder, NativeEndian}; NativeEndian::write_i32(&mut self.buffer.as_mut()[$offset], value) } } }; ($name:ident, i64, $offset:expr) => { $crate::paste::item! { pub fn [](&mut self, value: i64) { use $crate::byteorder::{ByteOrder, NativeEndian}; NativeEndian::write_i64(&mut self.buffer.as_mut()[$offset], value) } } }; } #[macro_export(local_inner_macros)] macro_rules! buffer { ($name:ident($buffer_len:expr) { $($field:ident : ($ty:tt, $offset:expr)),* $(,)? }) => { buffer!($name { $($field: ($ty, $offset),)* }); buffer_check_length!($name($buffer_len)); }; ($name:ident { $($field:ident : ($ty:tt, $offset:expr)),* $(,)? }) => { buffer_common!($name); fields!($name { $($field: ($ty, $offset),)* }); }; ($name:ident, $buffer_len:expr) => { buffer_common!($name); buffer_check_length!($name($buffer_len)); }; ($name:ident) => { buffer_common!($name); }; } #[macro_export(local_inner_macros)] macro_rules! fields { ($buffer:ident { $($name:ident : ($ty:tt, $offset:expr)),* $(,)? }) => { $( getter!($buffer, $name, $ty, $offset); )* $( setter!($buffer, $name, $ty, $offset); )* } } #[macro_export] macro_rules! buffer_check_length { ($name:ident($buffer_len:expr)) => { impl> $name { pub fn new_checked(buffer: T) -> Result { let packet = Self::new(buffer); packet.check_buffer_length()?; Ok(packet) } fn check_buffer_length(&self) -> Result<(), DecodeError> { let len = self.buffer.as_ref().len(); if len < $buffer_len { Err(format!( concat!("invalid ", stringify!($name), ": length {} < {}"), len, $buffer_len ) .into()) } else { Ok(()) } } } }; } #[macro_export] macro_rules! buffer_common { ($name:ident) => { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct $name { buffer: T, } impl> $name { pub fn new(buffer: T) -> Self { Self { buffer } } pub fn into_inner(self) -> T { self.buffer } } impl<'a, T: AsRef<[u8]> + ?Sized> $name<&'a T> { pub fn inner(&self) -> &'a [u8] { &self.buffer.as_ref()[..] } } impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> $name<&'a mut T> { pub fn inner_mut(&mut self) -> &mut [u8] { &mut self.buffer.as_mut()[..] } } }; } ================================================ FILE: netlink-packet-utils/src/nla.rs ================================================ // SPDX-License-Identifier: MIT use core::ops::Range; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use crate::{ traits::{Emitable, Parseable}, DecodeError, }; /// Represent a multi-bytes field with a fixed size in a packet type Field = Range; /// Identify the bits that represent the "nested" flag of a netlink attribute. pub const NLA_F_NESTED: u16 = 0x8000; /// Identify the bits that represent the "byte order" flag of a netlink attribute. pub const NLA_F_NET_BYTEORDER: u16 = 0x4000; /// Identify the bits that represent the type of a netlink attribute. pub const NLA_TYPE_MASK: u16 = !(NLA_F_NET_BYTEORDER | NLA_F_NESTED); /// NlA(RTA) align size pub const NLA_ALIGNTO: usize = 4; /// NlA(RTA) header size. (unsigned short rta_len) + (unsigned short rta_type) pub const NLA_HEADER_SIZE: usize = 4; #[macro_export] macro_rules! nla_align { ($len: expr) => { ($len + NLA_ALIGNTO - 1) & !(NLA_ALIGNTO - 1) }; } const LENGTH: Field = 0..2; const TYPE: Field = 2..4; #[allow(non_snake_case)] fn VALUE(length: usize) -> Field { TYPE.end..TYPE.end + length } // with Copy, NlaBuffer<&'buffer T> can be copied, which turns out to be pretty conveninent. And since it's // boils down to copying a reference it's pretty cheap #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct NlaBuffer> { buffer: T, } impl> NlaBuffer { pub fn new(buffer: T) -> NlaBuffer { NlaBuffer { buffer } } pub fn new_checked(buffer: T) -> Result, DecodeError> { let buffer = Self::new(buffer); buffer.check_buffer_length().context("invalid NLA buffer")?; Ok(buffer) } pub fn check_buffer_length(&self) -> Result<(), DecodeError> { let len = self.buffer.as_ref().len(); if len < TYPE.end { Err(format!( "buffer has length {}, but an NLA header is {} bytes", len, TYPE.end ) .into()) } else if len < self.length() as usize { Err(format!( "buffer has length: {}, but the NLA is {} bytes", len, self.length() ) .into()) } else if (self.length() as usize) < TYPE.end { Err(format!( "NLA has invalid length: {} (should be at least {} bytes", self.length(), TYPE.end, ) .into()) } else { Ok(()) } } /// Consume the buffer, returning the underlying buffer. pub fn into_inner(self) -> T { self.buffer } /// Return a reference to the underlying buffer pub fn inner(&mut self) -> &T { &self.buffer } /// Return a mutable reference to the underlying buffer pub fn inner_mut(&mut self) -> &mut T { &mut self.buffer } /// Return the `type` field pub fn kind(&self) -> u16 { let data = self.buffer.as_ref(); NativeEndian::read_u16(&data[TYPE]) & NLA_TYPE_MASK } pub fn nested_flag(&self) -> bool { let data = self.buffer.as_ref(); (NativeEndian::read_u16(&data[TYPE]) & NLA_F_NESTED) != 0 } pub fn network_byte_order_flag(&self) -> bool { let data = self.buffer.as_ref(); (NativeEndian::read_u16(&data[TYPE]) & NLA_F_NET_BYTEORDER) != 0 } /// Return the `length` field. The `length` field corresponds to the length of the nla /// header (type and length fields, and the value field). However, it does not account for the /// potential padding that follows the value field. pub fn length(&self) -> u16 { let data = self.buffer.as_ref(); NativeEndian::read_u16(&data[LENGTH]) } /// Return the length of the `value` field /// /// # Panic /// /// This panics if the length field value is less than the attribut header size. pub fn value_length(&self) -> usize { self.length() as usize - TYPE.end } } impl + AsMut<[u8]>> NlaBuffer { /// Set the `type` field pub fn set_kind(&mut self, kind: u16) { let data = self.buffer.as_mut(); NativeEndian::write_u16(&mut data[TYPE], kind & NLA_TYPE_MASK) } pub fn set_nested_flag(&mut self) { let kind = self.kind(); let data = self.buffer.as_mut(); NativeEndian::write_u16(&mut data[TYPE], kind | NLA_F_NESTED) } pub fn set_network_byte_order_flag(&mut self) { let kind = self.kind(); let data = self.buffer.as_mut(); NativeEndian::write_u16(&mut data[TYPE], kind | NLA_F_NET_BYTEORDER) } /// Set the `length` field pub fn set_length(&mut self, length: u16) { let data = self.buffer.as_mut(); NativeEndian::write_u16(&mut data[LENGTH], length) } } impl<'buffer, T: AsRef<[u8]> + ?Sized> NlaBuffer<&'buffer T> { /// Return the `value` field pub fn value(&self) -> &[u8] { &self.buffer.as_ref()[VALUE(self.value_length())] } } impl<'buffer, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> NlaBuffer<&'buffer mut T> { /// Return the `value` field pub fn value_mut(&mut self) -> &mut [u8] { let length = VALUE(self.value_length()); &mut self.buffer.as_mut()[length] } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct DefaultNla { kind: u16, value: Vec, } impl Nla for DefaultNla { fn value_len(&self) -> usize { self.value.len() } fn kind(&self) -> u16 { self.kind } fn emit_value(&self, buffer: &mut [u8]) { buffer.copy_from_slice(self.value.as_slice()); } } impl<'buffer, T: AsRef<[u8]> + ?Sized> Parseable> for DefaultNla { fn parse(buf: &NlaBuffer<&'buffer T>) -> Result { let mut kind = buf.kind(); if buf.network_byte_order_flag() { kind |= NLA_F_NET_BYTEORDER; } if buf.nested_flag() { kind |= NLA_F_NESTED; } Ok(DefaultNla { kind, value: buf.value().to_vec(), }) } } pub trait Nla { fn value_len(&self) -> usize; fn kind(&self) -> u16; fn emit_value(&self, buffer: &mut [u8]); #[inline] fn is_nested(&self) -> bool { (self.kind() & NLA_F_NESTED) != 0 } #[inline] fn is_network_byteorder(&self) -> bool { (self.kind() & NLA_F_NET_BYTEORDER) != 0 } } impl Emitable for T { fn buffer_len(&self) -> usize { nla_align!(self.value_len()) + NLA_HEADER_SIZE } fn emit(&self, buffer: &mut [u8]) { let mut buffer = NlaBuffer::new(buffer); buffer.set_kind(self.kind()); if self.is_network_byteorder() { buffer.set_network_byte_order_flag() } if self.is_nested() { buffer.set_nested_flag() } // do not include the padding here, but do include the header buffer.set_length(self.value_len() as u16 + NLA_HEADER_SIZE as u16); self.emit_value(buffer.value_mut()); let padding = nla_align!(self.value_len()) - self.value_len(); for i in 0..padding { buffer.inner_mut()[NLA_HEADER_SIZE + self.value_len() + i] = 0; } } } // FIXME: whern specialization lands, why can actually have // // impl<'a, T: Nla, I: Iterator> Emitable for I { ...} // // The reason this does not work today is because it conflicts with // // impl Emitable for T { ... } impl<'a, T: Nla> Emitable for &'a [T] { fn buffer_len(&self) -> usize { self.iter().fold(0, |acc, nla| { assert_eq!(nla.buffer_len() % NLA_ALIGNTO, 0); acc + nla.buffer_len() }) } fn emit(&self, buffer: &mut [u8]) { let mut start = 0; let mut end: usize; for nla in self.iter() { let attr_len = nla.buffer_len(); assert_eq!(nla.buffer_len() % NLA_ALIGNTO, 0); end = start + attr_len; nla.emit(&mut buffer[start..end]); start = end; } } } /// An iterator that iteratates over nlas without decoding them. This is useful when looking /// for specific nlas. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct NlasIterator { position: usize, buffer: T, } impl NlasIterator { pub fn new(buffer: T) -> Self { NlasIterator { position: 0, buffer, } } } impl<'buffer, T: AsRef<[u8]> + ?Sized + 'buffer> Iterator for NlasIterator<&'buffer T> { type Item = Result, DecodeError>; fn next(&mut self) -> Option { if self.position >= self.buffer.as_ref().len() { return None; } match NlaBuffer::new_checked(&self.buffer.as_ref()[self.position..]) { Ok(nla_buffer) => { self.position += nla_align!(nla_buffer.length() as usize); Some(Ok(nla_buffer)) } Err(e) => { // Make sure next time we call `next()`, we return None. We don't try to continue // iterating after we failed to return a buffer. self.position = self.buffer.as_ref().len(); Some(Err(e)) } } } } #[cfg(test)] mod tests { use super::*; #[test] fn network_byteorder() { // The IPSET_ATTR_TIMEOUT attribute should have the network byte order flag set. // IPSET_ATTR_TIMEOUT(3600) static TEST_ATTRIBUTE: &[u8] = &[0x08, 0x00, 0x06, 0x40, 0x00, 0x00, 0x0e, 0x10]; let buffer = NlaBuffer::new(TEST_ATTRIBUTE); let buffer_is_net = buffer.network_byte_order_flag(); let buffer_is_nest = buffer.nested_flag(); let nla = DefaultNla::parse(&buffer).unwrap(); let mut emitted_buffer = vec![0; nla.buffer_len()]; nla.emit(&mut emitted_buffer); let attr_is_net = nla.is_network_byteorder(); let attr_is_nest = nla.is_nested(); let emit = NlaBuffer::new(emitted_buffer); let emit_is_net = emit.network_byte_order_flag(); let emit_is_nest = emit.nested_flag(); assert_eq!([buffer_is_net, buffer_is_nest], [attr_is_net, attr_is_nest]); assert_eq!([attr_is_net, attr_is_nest], [emit_is_net, emit_is_nest]); } fn get_len() -> usize { // usize::MAX 18446744073709551615 } #[test] fn test_align() { assert_eq!(nla_align!(13), 16); assert_eq!(nla_align!(16), 16); assert_eq!(nla_align!(0), 0); assert_eq!(nla_align!(1), 4); assert_eq!(nla_align!(get_len() - 4), usize::MAX - 3); } #[test] #[should_panic] fn test_align_overflow() { assert_eq!(nla_align!(get_len() - 3), usize::MAX); } } ================================================ FILE: netlink-packet-utils/src/parsers.rs ================================================ // SPDX-License-Identifier: MIT use std::{ mem::size_of, net::{IpAddr, Ipv4Addr, Ipv6Addr}, }; use anyhow::Context; use byteorder::{BigEndian, ByteOrder, NativeEndian}; use crate::DecodeError; pub fn parse_mac(payload: &[u8]) -> Result<[u8; 6], DecodeError> { if payload.len() != 6 { return Err(format!("invalid MAC address: {:?}", payload).into()); } let mut address: [u8; 6] = [0; 6]; for (i, byte) in payload.iter().enumerate() { address[i] = *byte; } Ok(address) } pub fn parse_ipv6(payload: &[u8]) -> Result<[u8; 16], DecodeError> { if payload.len() != 16 { return Err(format!("invalid IPv6 address: {:?}", payload).into()); } let mut address: [u8; 16] = [0; 16]; for (i, byte) in payload.iter().enumerate() { address[i] = *byte; } Ok(address) } pub fn parse_ip(payload: &[u8]) -> Result { match payload.len() { 4 => Ok(Ipv4Addr::new(payload[0], payload[1], payload[2], payload[3]).into()), 16 => Ok(Ipv6Addr::from([ payload[0], payload[1], payload[2], payload[3], payload[4], payload[5], payload[6], payload[7], payload[8], payload[9], payload[10], payload[11], payload[12], payload[13], payload[14], payload[15], ]) .into()), _ => Err(format!("invalid IPv6 address: {:?}", payload).into()), } } pub fn parse_string(payload: &[u8]) -> Result { if payload.is_empty() { return Ok(String::new()); } // iproute2 is a bit inconsistent with null-terminated strings. let slice = if payload[payload.len() - 1] == 0 { &payload[..payload.len() - 1] } else { &payload[..payload.len()] }; let s = String::from_utf8(slice.to_vec()).context("invalid string")?; Ok(s) } pub fn parse_u8(payload: &[u8]) -> Result { if payload.len() != 1 { return Err(format!("invalid u8: {:?}", payload).into()); } Ok(payload[0]) } pub fn parse_u32(payload: &[u8]) -> Result { if payload.len() != size_of::() { return Err(format!("invalid u32: {:?}", payload).into()); } Ok(NativeEndian::read_u32(payload)) } pub fn parse_u64(payload: &[u8]) -> Result { if payload.len() != size_of::() { return Err(format!("invalid u64: {:?}", payload).into()); } Ok(NativeEndian::read_u64(payload)) } pub fn parse_u128(payload: &[u8]) -> Result { if payload.len() != size_of::() { return Err(format!("invalid u128: {:?}", payload).into()); } Ok(NativeEndian::read_u128(payload)) } pub fn parse_u16(payload: &[u8]) -> Result { if payload.len() != size_of::() { return Err(format!("invalid u16: {:?}", payload).into()); } Ok(NativeEndian::read_u16(payload)) } pub fn parse_i32(payload: &[u8]) -> Result { if payload.len() != 4 { return Err(format!("invalid u32: {:?}", payload).into()); } Ok(NativeEndian::read_i32(payload)) } pub fn parse_u16_be(payload: &[u8]) -> Result { if payload.len() != size_of::() { return Err(format!("invalid u16: {:?}", payload).into()); } Ok(BigEndian::read_u16(payload)) } pub fn parse_u32_be(payload: &[u8]) -> Result { if payload.len() != size_of::() { return Err(format!("invalid u32: {:?}", payload).into()); } Ok(BigEndian::read_u32(payload)) } ================================================ FILE: netlink-packet-utils/src/traits.rs ================================================ // SPDX-License-Identifier: MIT use crate::DecodeError; /// A type that implements `Emitable` can be serialized. pub trait Emitable { /// Return the length of the serialized data. fn buffer_len(&self) -> usize; /// Serialize this types and write the serialized data into the given buffer. /// /// # Panic /// /// This method panic if the buffer is not big enough. You **must** make sure the buffer is big /// enough before calling this method. You can use /// [`buffer_len()`](trait.Emitable.html#method.buffer_len) to check how big the storage needs /// to be. fn emit(&self, buffer: &mut [u8]); } /// A `Parseable` type can be used to deserialize data from the type `T` for which it is /// implemented. pub trait Parseable where Self: Sized, T: ?Sized, { /// Deserialize the current type. fn parse(buf: &T) -> Result; } /// A `Parseable` type can be used to deserialize data from the type `T` for which it is /// implemented. pub trait ParseableParametrized where Self: Sized, T: ?Sized, { /// Deserialize the current type. fn parse_with_param(buf: &T, params: P) -> Result; } ================================================ FILE: netlink-packet-wireguard/Cargo.toml ================================================ [package] name = "netlink-packet-wireguard" version = "0.2.2" authors = ["Leo ", "Jake McGinty "] edition = "2018" homepage = "https://github.com/little-dude/netlink" repository = "https://github.com/little-dude/netlink" keywords = ["wireguard", "netlink", "linux"] license = "MIT" readme = "../README.md" description = "Wireguard generic netlink packet definitions" [dependencies] anyhow = "1.0.42" byteorder = "1.4.3" libc = "0.2.98" log = "0.4.14" netlink-packet-generic = { version = "0.3.1", path = "../netlink-packet-generic" } netlink-packet-utils = { version = "0.5.1", path = "../netlink-packet-utils" } [dev-dependencies] base64 = "0.13.0" env_logger = "0.9.0" futures = "0.3.16" netlink-packet-core = { version = "0.4.2", path = "../netlink-packet-core" } netlink-proto = { version = "0.10", path = "../netlink-proto" } genetlink = { version = "0.2.1", path = "../genetlink" } tokio = { version = "1.9.0", features = ["macros", "rt-multi-thread"] } ================================================ FILE: netlink-packet-wireguard/examples/get_wireguard_info.rs ================================================ // SPDX-License-Identifier: MIT use futures::StreamExt; use genetlink::new_connection; use netlink_packet_core::{NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST}; use netlink_packet_generic::GenlMessage; use netlink_packet_wireguard::{ nlas::{WgAllowedIpAttrs, WgDeviceAttrs, WgPeerAttrs}, Wireguard, WireguardCmd, }; use std::env::args; #[tokio::main] async fn main() { env_logger::init(); let argv: Vec = args().collect(); if argv.len() < 2 { eprintln!("Usage: get_wireguard_info "); return; } let (connection, mut handle, _) = new_connection().unwrap(); tokio::spawn(connection); let genlmsg: GenlMessage = GenlMessage::from_payload(Wireguard { cmd: WireguardCmd::GetDevice, nlas: vec![WgDeviceAttrs::IfName(argv[1].clone())], }); let mut nlmsg = NetlinkMessage::from(genlmsg); nlmsg.header.flags = NLM_F_REQUEST | NLM_F_DUMP; let mut res = handle.request(nlmsg).await.unwrap(); while let Some(result) = res.next().await { let rx_packet = result.unwrap(); match rx_packet.payload { NetlinkPayload::InnerMessage(genlmsg) => { print_wg_payload(genlmsg.payload); } NetlinkPayload::Error(e) => { eprintln!("Error: {:?}", e.to_io()); } _ => (), }; } } fn print_wg_payload(wg: Wireguard) { for nla in &wg.nlas { match nla { WgDeviceAttrs::IfIndex(v) => println!("IfIndex: {}", v), WgDeviceAttrs::IfName(v) => println!("IfName: {}", v), WgDeviceAttrs::PrivateKey(_) => println!("PrivateKey: (hidden)"), WgDeviceAttrs::PublicKey(v) => println!("PublicKey: {}", base64::encode(v)), WgDeviceAttrs::ListenPort(v) => println!("ListenPort: {}", v), WgDeviceAttrs::Fwmark(v) => println!("Fwmark: {}", v), WgDeviceAttrs::Peers(nlas) => { for peer in nlas { println!("Peer: "); print_wg_peer(peer); } } _ => (), } } } fn print_wg_peer(nlas: &[WgPeerAttrs]) { for nla in nlas { match nla { WgPeerAttrs::PublicKey(v) => println!(" PublicKey: {}", base64::encode(v)), WgPeerAttrs::PresharedKey(_) => println!(" PresharedKey: (hidden)"), WgPeerAttrs::Endpoint(v) => println!(" Endpoint: {}", v), WgPeerAttrs::PersistentKeepalive(v) => println!(" PersistentKeepalive: {}", v), WgPeerAttrs::LastHandshake(v) => println!(" LastHandshake: {:?}", v), WgPeerAttrs::RxBytes(v) => println!(" RxBytes: {}", v), WgPeerAttrs::TxBytes(v) => println!(" TxBytes: {}", v), WgPeerAttrs::AllowedIps(nlas) => { for ip in nlas { print_wg_allowedip(ip); } } _ => (), } } } fn print_wg_allowedip(nlas: &[WgAllowedIpAttrs]) -> Option<()> { let ipaddr = nlas.iter().find_map(|nla| { if let WgAllowedIpAttrs::IpAddr(addr) = nla { Some(*addr) } else { None } })?; let cidr = nlas.iter().find_map(|nla| { if let WgAllowedIpAttrs::Cidr(cidr) = nla { Some(*cidr) } else { None } })?; println!(" AllowedIp: {}/{}", ipaddr, cidr); Some(()) } ================================================ FILE: netlink-packet-wireguard/src/constants.rs ================================================ // SPDX-License-Identifier: MIT pub const AF_INET: u16 = libc::AF_INET as u16; pub const AF_INET6: u16 = libc::AF_INET6 as u16; pub const WG_KEY_LEN: usize = 32; pub const WG_CMD_GET_DEVICE: u8 = 0; pub const WG_CMD_SET_DEVICE: u8 = 1; pub const WGDEVICE_F_REPLACE_PEERS: u32 = 1 << 0; pub const WGDEVICE_A_UNSPEC: u16 = 0; pub const WGDEVICE_A_IFINDEX: u16 = 1; pub const WGDEVICE_A_IFNAME: u16 = 2; pub const WGDEVICE_A_PRIVATE_KEY: u16 = 3; pub const WGDEVICE_A_PUBLIC_KEY: u16 = 4; pub const WGDEVICE_A_FLAGS: u16 = 5; pub const WGDEVICE_A_LISTEN_PORT: u16 = 6; pub const WGDEVICE_A_FWMARK: u16 = 7; pub const WGDEVICE_A_PEERS: u16 = 8; pub const WGPEER_F_REMOVE_ME: u32 = 1 << 0; pub const WGPEER_F_REPLACE_ALLOWEDIPS: u32 = 1 << 1; pub const WGPEER_F_UPDATE_ONLY: u32 = 1 << 2; pub const WGPEER_A_UNSPEC: u16 = 0; pub const WGPEER_A_PUBLIC_KEY: u16 = 1; pub const WGPEER_A_PRESHARED_KEY: u16 = 2; pub const WGPEER_A_FLAGS: u16 = 3; pub const WGPEER_A_ENDPOINT: u16 = 4; pub const WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: u16 = 5; pub const WGPEER_A_LAST_HANDSHAKE_TIME: u16 = 6; pub const WGPEER_A_RX_BYTES: u16 = 7; pub const WGPEER_A_TX_BYTES: u16 = 8; pub const WGPEER_A_ALLOWEDIPS: u16 = 9; pub const WGPEER_A_PROTOCOL_VERSION: u16 = 10; pub const WGALLOWEDIP_A_UNSPEC: u16 = 0; pub const WGALLOWEDIP_A_FAMILY: u16 = 1; pub const WGALLOWEDIP_A_IPADDR: u16 = 2; pub const WGALLOWEDIP_A_CIDR_MASK: u16 = 3; ================================================ FILE: netlink-packet-wireguard/src/lib.rs ================================================ // SPDX-License-Identifier: MIT #[macro_use] extern crate log; use crate::constants::*; use anyhow::Context; use netlink_packet_generic::{GenlFamily, GenlHeader}; use netlink_packet_utils::{nla::NlasIterator, traits::*, DecodeError}; use nlas::WgDeviceAttrs; use std::convert::{TryFrom, TryInto}; pub mod constants; pub mod nlas; mod raw; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum WireguardCmd { GetDevice, SetDevice, } impl From for u8 { fn from(cmd: WireguardCmd) -> Self { use WireguardCmd::*; match cmd { GetDevice => WG_CMD_GET_DEVICE, SetDevice => WG_CMD_SET_DEVICE, } } } impl TryFrom for WireguardCmd { type Error = DecodeError; fn try_from(value: u8) -> Result { use WireguardCmd::*; Ok(match value { WG_CMD_GET_DEVICE => GetDevice, WG_CMD_SET_DEVICE => SetDevice, cmd => { return Err(DecodeError::from(format!( "Unknown wireguard command: {}", cmd ))) } }) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Wireguard { pub cmd: WireguardCmd, pub nlas: Vec, } impl GenlFamily for Wireguard { fn family_name() -> &'static str { "wireguard" } fn version(&self) -> u8 { 1 } fn command(&self) -> u8 { self.cmd.into() } } impl Emitable for Wireguard { fn emit(&self, buffer: &mut [u8]) { self.nlas.as_slice().emit(buffer) } fn buffer_len(&self) -> usize { self.nlas.as_slice().buffer_len() } } impl ParseableParametrized<[u8], GenlHeader> for Wireguard { fn parse_with_param(buf: &[u8], header: GenlHeader) -> Result { Ok(Self { cmd: header.cmd.try_into()?, nlas: parse_nlas(buf)?, }) } } fn parse_nlas(buf: &[u8]) -> Result, DecodeError> { let mut nlas = Vec::new(); let error_msg = "failed to parse message attributes"; for nla in NlasIterator::new(buf) { let nla = &nla.context(error_msg)?; let parsed = WgDeviceAttrs::parse(nla).context(error_msg)?; nlas.push(parsed); } Ok(nlas) } #[cfg(test)] mod test { use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}; use netlink_packet_generic::GenlMessage; use crate::nlas::{WgAllowedIp, WgAllowedIpAttrs, WgPeer, WgPeerAttrs}; use super::*; const KNOWN_VALID_PACKET: &[u8] = &[ 0x74, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x05, 0x00, 0x38, 0x24, 0xd6, 0x61, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x0b, 0x00, 0x02, 0x00, 0x66, 0x72, 0x61, 0x6e, 0x64, 0x73, 0x00, 0x00, 0x54, 0x00, 0x08, 0x80, 0x50, 0x00, 0x00, 0x80, 0x24, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x08, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x09, 0x80, 0x1c, 0x00, 0x00, 0x80, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x05, 0x00, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00, ]; #[test] fn test_parse_known_valid_packet() { NetlinkMessage::>::deserialize(KNOWN_VALID_PACKET).unwrap(); } #[test] fn test_serialize_then_deserialize() { let genlmsg: GenlMessage = GenlMessage::from_payload(Wireguard { cmd: WireguardCmd::SetDevice, nlas: vec![ WgDeviceAttrs::IfName("wg0".to_string()), WgDeviceAttrs::PrivateKey([0xaa; 32]), WgDeviceAttrs::Peers(vec![ WgPeer(vec![ WgPeerAttrs::PublicKey([0x01; 32]), WgPeerAttrs::PresharedKey([0x01; 32]), WgPeerAttrs::AllowedIps(vec![WgAllowedIp(vec![ WgAllowedIpAttrs::IpAddr([10, 0, 0, 0].into()), WgAllowedIpAttrs::Cidr(24), WgAllowedIpAttrs::Family(AF_INET), ])]), ]), WgPeer(vec![ WgPeerAttrs::PublicKey([0x02; 32]), WgPeerAttrs::PresharedKey([0x01; 32]), WgPeerAttrs::AllowedIps(vec![WgAllowedIp(vec![ WgAllowedIpAttrs::IpAddr([10, 0, 1, 0].into()), WgAllowedIpAttrs::Cidr(24), WgAllowedIpAttrs::Family(AF_INET), ])]), ]), ]), ], }); let mut nlmsg = NetlinkMessage::from(genlmsg); nlmsg.header.flags = NLM_F_REQUEST | NLM_F_ACK; nlmsg.finalize(); let mut buf = [0; 4096]; nlmsg.serialize(&mut buf); let len = nlmsg.buffer_len(); NetlinkMessage::>::deserialize(&buf[..len]).unwrap(); } } ================================================ FILE: netlink-packet-wireguard/src/nlas/allowedip.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ constants::*, raw::{emit_ip, parse_ip, IPV4_LEN, IPV6_LEN}, }; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{Nla, NlaBuffer}, parsers::*, traits::*, DecodeError, }; use std::{mem::size_of_val, net::IpAddr}; #[derive(Clone, Debug, PartialEq, Eq)] pub enum WgAllowedIpAttrs { Unspec(Vec), Family(u16), IpAddr(IpAddr), Cidr(u8), } impl Nla for WgAllowedIpAttrs { fn value_len(&self) -> usize { match self { WgAllowedIpAttrs::Unspec(bytes) => bytes.len(), WgAllowedIpAttrs::Family(v) => size_of_val(v), WgAllowedIpAttrs::IpAddr(v) => match *v { IpAddr::V4(_) => IPV4_LEN, IpAddr::V6(_) => IPV6_LEN, }, WgAllowedIpAttrs::Cidr(v) => size_of_val(v), } } fn kind(&self) -> u16 { match self { WgAllowedIpAttrs::Unspec(_) => WGALLOWEDIP_A_UNSPEC, WgAllowedIpAttrs::Family(_) => WGALLOWEDIP_A_FAMILY, WgAllowedIpAttrs::IpAddr(_) => WGALLOWEDIP_A_IPADDR, WgAllowedIpAttrs::Cidr(_) => WGALLOWEDIP_A_CIDR_MASK, } } fn emit_value(&self, buffer: &mut [u8]) { match self { WgAllowedIpAttrs::Unspec(bytes) => buffer.copy_from_slice(bytes), WgAllowedIpAttrs::Family(v) => NativeEndian::write_u16(buffer, *v), WgAllowedIpAttrs::IpAddr(v) => emit_ip(v, buffer), WgAllowedIpAttrs::Cidr(v) => buffer[0] = *v, } } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for WgAllowedIpAttrs { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { WGALLOWEDIP_A_UNSPEC => Self::Unspec(payload.to_vec()), WGALLOWEDIP_A_FAMILY => { Self::Family(parse_u16(payload).context("invalid WGALLOWEDIP_A_FAMILY value")?) } WGALLOWEDIP_A_IPADDR => { Self::IpAddr(parse_ip(payload).context("invalid WGALLOWEDIP_A_IPADDR value")?) } WGALLOWEDIP_A_CIDR_MASK => Self::Cidr(payload[0]), kind => return Err(DecodeError::from(format!("invalid NLA kind: {}", kind))), }) } } ================================================ FILE: netlink-packet-wireguard/src/nlas/device.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ constants::*, nlas::{WgPeer, WgPeerAttrs}, }; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{Nla, NlaBuffer, NlasIterator}, parsers::*, traits::*, DecodeError, }; use std::{convert::TryInto, mem::size_of_val}; #[derive(Clone, Debug, PartialEq, Eq)] pub enum WgDeviceAttrs { Unspec(Vec), IfIndex(u32), IfName(String), PrivateKey([u8; WG_KEY_LEN]), PublicKey([u8; WG_KEY_LEN]), ListenPort(u16), Fwmark(u32), Peers(Vec), Flags(u32), } impl Nla for WgDeviceAttrs { fn value_len(&self) -> usize { match self { WgDeviceAttrs::Unspec(bytes) => bytes.len(), WgDeviceAttrs::IfIndex(v) => size_of_val(v), WgDeviceAttrs::IfName(v) => v.as_bytes().len() + 1, WgDeviceAttrs::PrivateKey(v) => size_of_val(v), WgDeviceAttrs::PublicKey(v) => size_of_val(v), WgDeviceAttrs::ListenPort(v) => size_of_val(v), WgDeviceAttrs::Fwmark(v) => size_of_val(v), WgDeviceAttrs::Peers(nlas) => nlas.iter().map(|op| op.buffer_len()).sum(), WgDeviceAttrs::Flags(v) => size_of_val(v), } } fn kind(&self) -> u16 { match self { WgDeviceAttrs::Unspec(_) => WGDEVICE_A_UNSPEC, WgDeviceAttrs::IfIndex(_) => WGDEVICE_A_IFINDEX, WgDeviceAttrs::IfName(_) => WGDEVICE_A_IFNAME, WgDeviceAttrs::PrivateKey(_) => WGDEVICE_A_PRIVATE_KEY, WgDeviceAttrs::PublicKey(_) => WGDEVICE_A_PUBLIC_KEY, WgDeviceAttrs::ListenPort(_) => WGDEVICE_A_LISTEN_PORT, WgDeviceAttrs::Fwmark(_) => WGDEVICE_A_FWMARK, WgDeviceAttrs::Peers(_) => WGDEVICE_A_PEERS, WgDeviceAttrs::Flags(_) => WGDEVICE_A_FLAGS, } } fn emit_value(&self, buffer: &mut [u8]) { match self { WgDeviceAttrs::Unspec(bytes) => buffer.copy_from_slice(bytes), WgDeviceAttrs::IfIndex(v) => NativeEndian::write_u32(buffer, *v), WgDeviceAttrs::IfName(s) => { buffer[..s.len()].copy_from_slice(s.as_bytes()); buffer[s.len()] = 0; } WgDeviceAttrs::PrivateKey(v) => buffer.copy_from_slice(v), WgDeviceAttrs::PublicKey(v) => buffer.copy_from_slice(v), WgDeviceAttrs::ListenPort(v) => NativeEndian::write_u16(buffer, *v), WgDeviceAttrs::Fwmark(v) => NativeEndian::write_u32(buffer, *v), WgDeviceAttrs::Peers(nlas) => { let mut len = 0; for op in nlas { op.emit(&mut buffer[len..]); len += op.buffer_len(); } } WgDeviceAttrs::Flags(v) => NativeEndian::write_u32(buffer, *v), } } fn is_nested(&self) -> bool { matches!(self, WgDeviceAttrs::Peers(_)) } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for WgDeviceAttrs { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { WGDEVICE_A_UNSPEC => Self::Unspec(payload.to_vec()), WGDEVICE_A_IFINDEX => { Self::IfIndex(parse_u32(payload).context("invalid WGDEVICE_A_IFINDEX value")?) } WGDEVICE_A_IFNAME => { Self::IfName(parse_string(payload).context("invalid WGDEVICE_A_IFNAME value")?) } WGDEVICE_A_PRIVATE_KEY => Self::PrivateKey( payload .try_into() .context("invalid WGDEVICE_A_PRIVATE_KEY value")?, ), WGDEVICE_A_PUBLIC_KEY => Self::PublicKey( payload .try_into() .context("invalid WGDEVICE_A_PUBLIC_KEY value")?, ), WGDEVICE_A_LISTEN_PORT => Self::ListenPort( parse_u16(payload).context("invalid WGDEVICE_A_LISTEN_PORT value")?, ), WGDEVICE_A_FWMARK => { Self::Fwmark(parse_u32(payload).context("invalid WGDEVICE_A_FWMARK value")?) } WGDEVICE_A_PEERS => { let error_msg = "failed to parse WGDEVICE_A_PEERS"; let mut peers = Vec::new(); for nlas in NlasIterator::new(payload) { let nlas = &nlas.context(error_msg)?; let mut group = Vec::new(); for nla in NlasIterator::new(nlas.value()) { let nla = &nla.context(error_msg)?; let parsed = WgPeerAttrs::parse(nla).context(error_msg)?; group.push(parsed); } peers.push(WgPeer(group)); } Self::Peers(peers) } WGDEVICE_A_FLAGS => { Self::Flags(parse_u32(payload).context("invalid WGDEVICE_A_FLAGS value")?) } kind => return Err(DecodeError::from(format!("invalid NLA kind: {}", kind))), }) } } ================================================ FILE: netlink-packet-wireguard/src/nlas/mod.rs ================================================ // SPDX-License-Identifier: MIT mod allowedip; mod device; mod peer; pub use allowedip::WgAllowedIpAttrs; pub use device::WgDeviceAttrs; pub use peer::{WgAllowedIp, WgPeer, WgPeerAttrs}; ================================================ FILE: netlink-packet-wireguard/src/nlas/peer.rs ================================================ // SPDX-License-Identifier: MIT use super::WgAllowedIpAttrs; use crate::{ constants::*, raw::{ emit_socket_addr, emit_timespec, parse_socket_addr, parse_timespec, SOCKET_ADDR_V4_LEN, SOCKET_ADDR_V6_LEN, TIMESPEC_LEN, }, }; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; use netlink_packet_utils::{ nla::{Nla, NlaBuffer, NlasIterator}, parsers::*, traits::*, DecodeError, }; use std::{convert::TryInto, mem::size_of_val, net::SocketAddr, ops::Deref, time::SystemTime}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct WgPeer(pub Vec); impl Nla for WgPeer { fn value_len(&self) -> usize { self.0.as_slice().buffer_len() } fn kind(&self) -> u16 { 0 } fn emit_value(&self, buffer: &mut [u8]) { self.0.as_slice().emit(buffer); } fn is_nested(&self) -> bool { true } } impl Deref for WgPeer { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct WgAllowedIp(pub Vec); impl Nla for WgAllowedIp { fn value_len(&self) -> usize { self.0.as_slice().buffer_len() } fn kind(&self) -> u16 { 0 } fn emit_value(&self, buffer: &mut [u8]) { self.0.as_slice().emit(buffer); } fn is_nested(&self) -> bool { true } } impl Deref for WgAllowedIp { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Clone, Debug, PartialEq, Eq)] pub enum WgPeerAttrs { Unspec(Vec), PublicKey([u8; WG_KEY_LEN]), PresharedKey([u8; WG_KEY_LEN]), Endpoint(SocketAddr), PersistentKeepalive(u16), LastHandshake(SystemTime), RxBytes(u64), TxBytes(u64), AllowedIps(Vec), ProtocolVersion(u32), Flags(u32), } impl Nla for WgPeerAttrs { fn value_len(&self) -> usize { match self { WgPeerAttrs::Unspec(bytes) => bytes.len(), WgPeerAttrs::PublicKey(v) => size_of_val(v), WgPeerAttrs::PresharedKey(v) => size_of_val(v), WgPeerAttrs::Endpoint(v) => match *v { SocketAddr::V4(_) => SOCKET_ADDR_V4_LEN, SocketAddr::V6(_) => SOCKET_ADDR_V6_LEN, }, WgPeerAttrs::PersistentKeepalive(v) => size_of_val(v), WgPeerAttrs::LastHandshake(_) => TIMESPEC_LEN, WgPeerAttrs::RxBytes(v) => size_of_val(v), WgPeerAttrs::TxBytes(v) => size_of_val(v), WgPeerAttrs::AllowedIps(nlas) => nlas.iter().map(|op| op.buffer_len()).sum(), WgPeerAttrs::ProtocolVersion(v) => size_of_val(v), WgPeerAttrs::Flags(v) => size_of_val(v), } } fn kind(&self) -> u16 { match self { WgPeerAttrs::Unspec(_) => WGPEER_A_UNSPEC, WgPeerAttrs::PublicKey(_) => WGPEER_A_PUBLIC_KEY, WgPeerAttrs::PresharedKey(_) => WGPEER_A_PRESHARED_KEY, WgPeerAttrs::Endpoint(_) => WGPEER_A_ENDPOINT, WgPeerAttrs::PersistentKeepalive(_) => WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, WgPeerAttrs::LastHandshake(_) => WGPEER_A_LAST_HANDSHAKE_TIME, WgPeerAttrs::RxBytes(_) => WGPEER_A_RX_BYTES, WgPeerAttrs::TxBytes(_) => WGPEER_A_TX_BYTES, WgPeerAttrs::AllowedIps(_) => WGPEER_A_ALLOWEDIPS, WgPeerAttrs::ProtocolVersion(_) => WGPEER_A_PROTOCOL_VERSION, WgPeerAttrs::Flags(_) => WGPEER_A_FLAGS, } } fn emit_value(&self, buffer: &mut [u8]) { match self { WgPeerAttrs::Unspec(bytes) => buffer.copy_from_slice(bytes), WgPeerAttrs::PublicKey(v) => buffer.copy_from_slice(v), WgPeerAttrs::PresharedKey(v) => buffer.copy_from_slice(v), WgPeerAttrs::Endpoint(v) => emit_socket_addr(v, buffer), WgPeerAttrs::PersistentKeepalive(v) => NativeEndian::write_u16(buffer, *v), WgPeerAttrs::LastHandshake(v) => emit_timespec(v, buffer), WgPeerAttrs::RxBytes(v) => NativeEndian::write_u64(buffer, *v), WgPeerAttrs::TxBytes(v) => NativeEndian::write_u64(buffer, *v), WgPeerAttrs::AllowedIps(nlas) => { let mut len = 0; for op in nlas { op.emit(&mut buffer[len..]); len += op.buffer_len(); } } WgPeerAttrs::ProtocolVersion(v) => NativeEndian::write_u32(buffer, *v), WgPeerAttrs::Flags(v) => NativeEndian::write_u32(buffer, *v), } } fn is_nested(&self) -> bool { matches!(self, WgPeerAttrs::AllowedIps(_)) } } impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for WgPeerAttrs { fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { WGPEER_A_UNSPEC => Self::Unspec(payload.to_vec()), WGPEER_A_PUBLIC_KEY => { Self::PublicKey(payload.try_into().context("invalid WGPEER_A_PUBLIC_KEY")?) } WGPEER_A_PRESHARED_KEY => Self::PresharedKey( payload .try_into() .context("invalid WGPEER_A_PRESHARED_KEY")?, ), WGPEER_A_ENDPOINT => { Self::Endpoint(parse_socket_addr(payload).context("invalid WGPEER_A_ENDPOINT")?) } WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL => Self::PersistentKeepalive( parse_u16(payload) .context("invalid WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL value")?, ), WGPEER_A_LAST_HANDSHAKE_TIME => Self::LastHandshake( parse_timespec(payload).context("invalid WGPEER_A_LAST_HANDSHAKE_TIME")?, ), WGPEER_A_RX_BYTES => { Self::RxBytes(parse_u64(payload).context("invalid WGPEER_A_RX_BYTES value")?) } WGPEER_A_TX_BYTES => { Self::TxBytes(parse_u64(payload).context("invalid WGPEER_A_TX_BYTES value")?) } WGPEER_A_ALLOWEDIPS => { let error_msg = "failed to parse WGPEER_A_ALLOWEDIPS"; let mut ips = Vec::new(); for nlas in NlasIterator::new(payload) { let nlas = &nlas.context(error_msg)?; let mut group = Vec::new(); for nla in NlasIterator::new(nlas.value()) { let nla = &nla.context(error_msg)?; let parsed = WgAllowedIpAttrs::parse(nla).context(error_msg)?; group.push(parsed); } ips.push(WgAllowedIp(group)); } Self::AllowedIps(ips) } WGPEER_A_PROTOCOL_VERSION => Self::ProtocolVersion( parse_u32(payload).context("invalid WGPEER_A_PROTOCOL_VERSION value")?, ), WGPEER_A_FLAGS => { Self::Flags(parse_u32(payload).context("invalid WGPEER_A_FLAGS value")?) } kind => return Err(DecodeError::from(format!("invalid NLA kind: {}", kind))), }) } } ================================================ FILE: netlink-packet-wireguard/src/raw.rs ================================================ // SPDX-License-Identifier: MIT use std::{ convert::TryFrom, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, time::{Duration, SystemTime}, }; use byteorder::{BigEndian, ByteOrder, NativeEndian}; use netlink_packet_utils::DecodeError; use crate::constants::{AF_INET, AF_INET6}; pub const IPV4_LEN: usize = 4; pub const IPV6_LEN: usize = 16; pub const SOCKET_ADDR_V4_LEN: usize = 16; pub const SOCKET_ADDR_V6_LEN: usize = 28; pub const TIMESPEC_LEN: usize = 16; /// Parse an IPv6 socket address, defined as: /// ```c /// struct sockaddr_in6 { /// sa_family_t sin6_family; /* AF_INET6 */ /// in_port_t sin6_port; /* port number */ /// uint32_t sin6_flowinfo; /* IPv6 flow information */ /// struct in6_addr sin6_addr; /* IPv6 address */ /// uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ /// }; /// struct in6_addr { /// unsigned char s6_addr[16]; /* IPv6 address */ /// }; /// ``` /// `sockaddr_in6` is 4 bytes aligned (28 bytes) so there's no padding. fn parse_socket_addr_v6(payload: &[u8]) -> SocketAddrV6 { assert_eq!(payload.len(), SOCKET_ADDR_V6_LEN); // We don't need the address family to build a SocketAddrv6 // let address_family = NativeEndian::read_u16(&payload[..2]); let port = BigEndian::read_u16(&payload[2..4]); let flow_info = NativeEndian::read_u32(&payload[4..8]); // We know we have exactly 16 bytes so this won't fail let ip_bytes = <[u8; 16]>::try_from(&payload[8..24]).unwrap(); let ip = Ipv6Addr::from(ip_bytes); let scope_id = NativeEndian::read_u32(&payload[24..28]); SocketAddrV6::new(ip, port, flow_info, scope_id) } /// Parse an IPv4 socket address, defined as: /// ```c /// #if __UAPI_DEF_SOCKADDR_IN /// #define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ /// struct sockaddr_in { /// __kernel_sa_family_t sin_family; /* Address family */ /// __be16 sin_port; /* Port number */ /// struct in_addr sin_addr; /* Internet address */ /// /* Pad to size of `struct sockaddr'. */ /// unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; /// }; fn parse_socket_addr_v4(payload: &[u8]) -> SocketAddrV4 { assert_eq!(payload.len(), 16); // We don't need the address family to build a SocketAddr4v // let address_family = NativeEndian::read_u16(&payload[..2]); let port = BigEndian::read_u16(&payload[2..4]); // We know we have exactly 4 bytes so this won't fail let ip_bytes = <[u8; 4]>::try_from(&payload[4..8]).unwrap(); let ip = Ipv4Addr::from(ip_bytes); SocketAddrV4::new(ip, port) } pub fn parse_ip(payload: &[u8]) -> Result { match payload.len() { IPV4_LEN => { // This won't fail since we ensure the slice is 4 bytes long let ip_bytes = <[u8; IPV4_LEN]>::try_from(payload).unwrap(); Ok(IpAddr::V4(Ipv4Addr::from(ip_bytes))) } IPV6_LEN => { // This won't fail since we ensure the slice is 16 bytes long let ip_bytes = <[u8; IPV6_LEN]>::try_from(payload).unwrap(); Ok(IpAddr::V6(Ipv6Addr::from(ip_bytes))) } _ => Err(DecodeError::from(format!( "invalid IP address: {:x?}", payload ))), } } pub fn emit_ip(addr: &IpAddr, buf: &mut [u8]) { match addr { IpAddr::V4(ip) => { buf[..IPV4_LEN].copy_from_slice(ip.octets().as_slice()); } IpAddr::V6(ip) => { buf[..IPV6_LEN].copy_from_slice(ip.octets().as_slice()); } } } /// Emit an IPv4 socket address in the given buffer. An IPv4 socket /// address is defined as: /// ```c /// #if __UAPI_DEF_SOCKADDR_IN /// #define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ /// struct sockaddr_in { /// __kernel_sa_family_t sin_family; /* Address family */ /// __be16 sin_port; /* Port number */ /// struct in_addr sin_addr; /* Internet address */ /// /* Pad to size of `struct sockaddr'. */ /// unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; /// }; /// ``` /// Note that this adds 8 bytes of padding so the buffer must be large /// enough to account for them. fn emit_socket_addr_v4(addr: &SocketAddrV4, buf: &mut [u8]) { NativeEndian::write_u16(&mut buf[..2], AF_INET); BigEndian::write_u16(&mut buf[2..4], addr.port()); buf[4..8].copy_from_slice(addr.ip().octets().as_slice()); // padding buf[8..16].copy_from_slice([0; 8].as_slice()); } /// Emit an IPv6 socket address. /// /// An IPv6 socket address is defined as: /// ```c /// struct sockaddr_in6 { /// sa_family_t sin6_family; /* AF_INET6 */ /// in_port_t sin6_port; /* port number */ /// uint32_t sin6_flowinfo; /* IPv6 flow information */ /// struct in6_addr sin6_addr; /* IPv6 address */ /// uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ /// }; /// struct in6_addr { /// unsigned char s6_addr[16]; /* IPv6 address */ /// }; /// ``` /// `sockaddr_in6` is 4 bytes aligned (28 bytes) so there's no padding. fn emit_socket_addr_v6(addr: &SocketAddrV6, buf: &mut [u8]) { NativeEndian::write_u16(&mut buf[..2], AF_INET6); BigEndian::write_u16(&mut buf[2..4], addr.port()); NativeEndian::write_u32(&mut buf[4..8], addr.flowinfo()); buf[8..24].copy_from_slice(addr.ip().octets().as_slice()); NativeEndian::write_u32(&mut buf[24..28], addr.scope_id()); } pub fn emit_socket_addr(addr: &SocketAddr, buf: &mut [u8]) { match addr { SocketAddr::V4(v4) => emit_socket_addr_v4(v4, buf), SocketAddr::V6(v6) => emit_socket_addr_v6(v6, buf), } } pub fn parse_socket_addr(buf: &[u8]) -> Result { match buf.len() { SOCKET_ADDR_V4_LEN => Ok(SocketAddr::V4(parse_socket_addr_v4(buf))), SOCKET_ADDR_V6_LEN => Ok(SocketAddr::V6(parse_socket_addr_v6(buf))), _ => Err(format!( "invalid socket address (should be 16 or 28 bytes): {:x?}", buf ) .into()), } } pub fn emit_timespec(time: &SystemTime, buf: &mut [u8]) { match time.duration_since(SystemTime::UNIX_EPOCH) { Ok(epoch_elapsed) => { NativeEndian::write_i64(&mut buf[..8], epoch_elapsed.as_secs() as i64); NativeEndian::write_i64(&mut buf[8..16], epoch_elapsed.subsec_nanos() as i64); } Err(e) => { // This method is supposed to not fail so just log an // error. If we want such errors to be handled by the // caller, we shouldn't use `SystemTime`. error!("error while emitting timespec: {:?}", e); NativeEndian::write_i64(&mut buf[..8], 0_i64); NativeEndian::write_i64(&mut buf[8..16], 0_i64); } } } pub fn parse_timespec(buf: &[u8]) -> Result { if buf.len() != TIMESPEC_LEN { return Err(DecodeError::from(format!( "Invalid timespec buffer: {:x?}", buf ))); } let epoch_elapsed_s = Duration::from_secs(NativeEndian::read_u64(&buf[..8])); let epoch_elapsed_ns = Duration::from_nanos(NativeEndian::read_u64(&buf[8..16])); Ok(SystemTime::UNIX_EPOCH + epoch_elapsed_s + epoch_elapsed_ns) } #[cfg(test)] mod test { use std::str::FromStr; use super::*; const SOCKADDR_IN_BYTES_1: &[u8] = b"\x02\x00\x1c\x7a\x7f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"; // 127.0.0.1:7290 const SOCKADDR_IN_BYTES_2: &[u8] = b"\x02\x00\xca\x6c\xc0\xa8\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00"; // 192.168.1.1:51820 const SOCKADDR_IN6_BYTES_1: &[u8] = b"\x0a\x00\xca\x6c\x10\x00\x00\x00\xfe\x80\x00\x00\x00\x00\x00\x00\xe4\x58\x8e\xad\x89\xbb\x8e\x25\x03\x00\x00\x00"; // fe80::e458:8ead:89bb:8e25%3:51820 (flow 16) #[test] fn test_parse_socket_addr_in_1() { let ipaddr = parse_socket_addr(SOCKADDR_IN_BYTES_1).unwrap(); assert_eq!(ipaddr, SocketAddrV4::new(Ipv4Addr::LOCALHOST, 7290).into()); } #[test] fn test_parse_socket_addr_in_2() { let ipaddr = parse_socket_addr(SOCKADDR_IN_BYTES_2).unwrap(); assert_eq!( ipaddr, SocketAddrV4::new(Ipv4Addr::new(192, 168, 1, 1), 51820).into() ); } #[test] fn test_parse_socket_addr_in6_1() { let ipaddr = parse_socket_addr(SOCKADDR_IN6_BYTES_1).unwrap(); assert_eq!( ipaddr, SocketAddrV6::new( Ipv6Addr::from_str("fe80::e458:8ead:89bb:8e25").unwrap(), 51820, 16, 3 ) .into() ); } } ================================================ FILE: netlink-proto/Cargo.toml ================================================ [package] authors = ["Corentin Henry "] name = "netlink-proto" version = "0.10.0" edition = "2018" homepage = "https://github.com/little-dude/netlink" keywords = ["netlink", "linux", "async"] license = "MIT" readme = "../README.md" repository = "https://github.com/little-dude/netlink" description = "async netlink protocol" [dependencies] bytes = "1.0" log = "0.4.8" futures = "0.3" tokio = { version = "1.0", default-features = false, features = ["io-util"] } netlink-packet-core = { version = "0.4.2", path = "../netlink-packet-core" } netlink-sys = { default-features = false, version = "0.8.3", path = "../netlink-sys" } thiserror = "1.0.30" [features] default = ["tokio_socket"] tokio_socket = ["netlink-sys/tokio_socket"] smol_socket = ["netlink-sys/smol_socket"] [dev-dependencies] env_logger = "0.8.2" tokio = { version = "1.0.1", default-features = false, features = ["macros", "rt-multi-thread"] } netlink-packet-route = { version = "0.13.0", path = "../netlink-packet-route" } netlink-packet-audit = { version = "0.4.1", path = "../netlink-packet-audit" } async-std = {version = "1.9.0", features = ["attributes"]} [[example]] name = "dump_links" [[example]] name = "dump_links_async" required-features = ["smol_socket"] [[example]] name = "audit_netlink_events" ================================================ FILE: netlink-proto/examples/audit_netlink_events.rs ================================================ // SPDX-License-Identifier: MIT // This example shows how to use `netlink-proto` with the tokio runtime to print audit events. // // This example shows how the netlink socket can be accessed // `netlink_proto::Connection`, and configured (in this case to // register to a multicast group). // // Compilation: // ------------ // // cargo build --example audit_events // // Usage: // ------ // // Find the example binary in the target directory, and run it *as // root*. If you compiled in debug mode with the command above, the // binary should be under: // `/target/debug/examples/audit_events`. This example runs // forever, you must hit ^C to kill it. use futures::stream::StreamExt; use netlink_packet_audit::{ AuditMessage, NetlinkMessage, NetlinkPayload, StatusMessage, NLM_F_ACK, NLM_F_REQUEST, }; use std::process; use netlink_proto::{ new_connection, sys::{protocols::NETLINK_AUDIT, SocketAddr}, }; const AUDIT_STATUS_ENABLED: u32 = 1; const AUDIT_STATUS_PID: u32 = 4; #[tokio::main] async fn main() -> Result<(), String> { // Create a netlink socket. Here: // // - `conn` is a `Connection` that has the netlink socket. It's a // `Future` that keeps polling the socket and must be spawned an // the event loop. // // - `handle` is a `Handle` to the `Connection`. We use it to send // netlink messages and receive responses to these messages. // // - `messages` is a channel receiver through which we receive // messages that we have not sollicated, ie that are not // response to a request we made. In this example, we'll receive // the audit event through that channel. let (conn, mut handle, mut messages) = new_connection(NETLINK_AUDIT) .map_err(|e| format!("Failed to create a new netlink connection: {}", e))?; // Spawn the `Connection` so that it starts polling the netlink // socket in the background. tokio::spawn(conn); // Use the `ConnectionHandle` to send a request to the kernel // asking it to start multicasting audit event messages. tokio::spawn(async move { // Craft the packet to enable audit events let mut status = StatusMessage::new(); status.enabled = 1; status.pid = process::id(); status.mask = AUDIT_STATUS_ENABLED | AUDIT_STATUS_PID; let payload = AuditMessage::SetStatus(status); let mut nl_msg = NetlinkMessage::from(payload); nl_msg.header.flags = NLM_F_REQUEST | NLM_F_ACK; // We'll send unicast messages to the kernel. let kernel_unicast: SocketAddr = SocketAddr::new(0, 0); let mut response = match handle.request(nl_msg, kernel_unicast) { Ok(response) => response, Err(e) => { eprintln!("{}", e); return; } }; while let Some(message) = response.next().await { if let NetlinkPayload::Error(err_message) = message.payload { eprintln!("Received an error message: {:?}", err_message); return; } } }); // Finally, start receiving event through the `messages` channel. println!("Starting to print audit events... press ^C to interrupt"); while let Some((message, _addr)) = messages.next().await { if let NetlinkPayload::Error(err_message) = message.payload { eprintln!("received an error message: {:?}", err_message); } else { println!("{:?}", message); } } Ok(()) } ================================================ FILE: netlink-proto/examples/dump_links.rs ================================================ // SPDX-License-Identifier: MIT use futures::StreamExt; use netlink_packet_route::{ LinkMessage, NetlinkHeader, NetlinkMessage, RtnlMessage, NLM_F_DUMP, NLM_F_REQUEST, }; use netlink_proto::{ new_connection, sys::{protocols::NETLINK_ROUTE, SocketAddr}, }; #[tokio::main] async fn main() -> Result<(), String> { // Create the netlink socket. Here, we won't use the channel that // receives unsolicited messages. let (conn, mut handle, _) = new_connection(NETLINK_ROUTE) .map_err(|e| format!("Failed to create a new netlink connection: {}", e))?; // Spawn the `Connection` in the background tokio::spawn(conn); // Create the netlink message that requests the links to be dumped let request = NetlinkMessage { header: NetlinkHeader { flags: NLM_F_DUMP | NLM_F_REQUEST, ..Default::default() }, payload: RtnlMessage::GetLink(LinkMessage::default()).into(), }; // Send the request let mut response = handle .request(request, SocketAddr::new(0, 0)) .map_err(|e| format!("Failed to send request: {}", e))?; // Print all the messages received in response loop { if let Some(packet) = response.next().await { println!("<<< {:?}", packet); } else { break; } } Ok(()) } ================================================ FILE: netlink-proto/examples/dump_links_async.rs ================================================ // SPDX-License-Identifier: MIT use futures::StreamExt; use netlink_packet_route::{ LinkMessage, NetlinkHeader, NetlinkMessage, RtnlMessage, NLM_F_DUMP, NLM_F_REQUEST, }; use netlink_proto::{ new_connection, sys::{protocols::NETLINK_ROUTE, SocketAddr}, }; #[async_std::main] async fn main() -> Result<(), String> { // Create the netlink socket. Here, we won't use the channel that // receives unsolicited messages. let (conn, mut handle, _) = new_connection(NETLINK_ROUTE) .map_err(|e| format!("Failed to create a new netlink connection: {}", e))?; // Spawn the `Connection` so that it starts polling the netlink // socket in the background. let _ = async_std::task::spawn(conn); // Create the netlink message that requests the links to be dumped let request = NetlinkMessage { header: NetlinkHeader { flags: NLM_F_DUMP | NLM_F_REQUEST, ..Default::default() }, payload: RtnlMessage::GetLink(LinkMessage::default()).into(), }; // Send the request let mut response = handle .request(request, SocketAddr::new(0, 0)) .map_err(|e| format!("Failed to send request: {}", e))?; // Print all the messages received in response loop { if let Some(packet) = response.next().await { println!("<<< {:?}", packet); } else { break; } } Ok(()) } ================================================ FILE: netlink-proto/src/codecs.rs ================================================ // SPDX-License-Identifier: MIT use std::{fmt::Debug, io}; use bytes::{BufMut, BytesMut}; use netlink_packet_core::{ NetlinkBuffer, NetlinkDeserializable, NetlinkMessage, NetlinkSerializable, }; /// Protocol to serialize and deserialize messages to and from datagrams /// /// This is separate from `tokio_util::codec::{Decoder, Encoder}` as the implementations /// rely on the buffer containing full datagrams; they won't work well with simple /// bytestreams. /// /// Officially there should be exactly one implementation of this, but the audit /// subsystem ignores way too many rules of the protocol, so they need a separate /// implementation. /// /// Although one could make a tighter binding between `NetlinkMessageCodec` and /// the message types (NetlinkDeserializable+NetlinkSerializable) it can handle, /// this would put quite some overhead on subsystems that followed the spec - so /// we simply default to the proper implementation (in `Connection`) and the /// `audit` code needs to overwrite it. pub trait NetlinkMessageCodec { /// Decode message of given type from datagram payload /// /// There might be more than one message; this needs to be called until it /// either returns `Ok(None)` or an error. fn decode(src: &mut BytesMut) -> io::Result>> where T: NetlinkDeserializable + Debug; /// Encode message to (datagram) buffer fn encode(msg: NetlinkMessage, buf: &mut BytesMut) -> io::Result<()> where T: NetlinkSerializable + Debug; } /// Standard implementation of `NetlinkMessageCodec` pub struct NetlinkCodec { // we don't need an instance of this, just the type _private: (), } impl NetlinkMessageCodec for NetlinkCodec { fn decode(src: &mut BytesMut) -> io::Result>> where T: NetlinkDeserializable + Debug, { debug!("NetlinkCodec: decoding next message"); loop { // If there's nothing to read, return Ok(None) if src.is_empty() { trace!("buffer is empty"); return Ok(None); } // This is a bit hacky because we don't want to keep `src` // borrowed, since we need to mutate it later. let len = match NetlinkBuffer::new_checked(src.as_ref()) { Ok(buf) => buf.length() as usize, Err(e) => { // We either received a truncated packet, or the // packet if malformed (invalid length field). In // both case, we can't decode the datagram, and we // cannot find the start of the next one (if // any). The only solution is to clear the buffer // and potentially lose some datagrams. error!( "failed to decode datagram, clearing buffer: {:?}: {:#x?}.", e, src.as_ref() ); src.clear(); return Ok(None); } }; let bytes = src.split_to(len); let parsed = NetlinkMessage::::deserialize(&bytes); match parsed { Ok(packet) => { trace!("<<< {:?}", packet); return Ok(Some(packet)); } Err(e) => { error!("failed to decode packet {:#x?}: {}", &bytes, e); // continue looping, there may be more datagrams in the buffer } } } } fn encode(msg: NetlinkMessage, buf: &mut BytesMut) -> io::Result<()> where T: Debug + NetlinkSerializable, { let msg_len = msg.buffer_len(); if buf.remaining_mut() < msg_len { // BytesMut can expand till usize::MAX... unlikely to hit this one. return Err(io::Error::new( io::ErrorKind::Other, format!( "message is {} bytes, but only {} bytes left in the buffer", msg_len, buf.remaining_mut() ), )); } // As NetlinkMessage::serialize needs an initialized buffer anyway // no need for any `unsafe` magic. let old_len = buf.len(); let new_len = old_len + msg_len; buf.resize(new_len, 0); msg.serialize(&mut buf[old_len..][..msg_len]); trace!(">>> {:?}", msg); Ok(()) } } ================================================ FILE: netlink-proto/src/connection.rs ================================================ // SPDX-License-Identifier: MIT use std::{ fmt::Debug, io, pin::Pin, task::{Context, Poll}, }; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, Future, Sink, Stream, }; use log::{error, warn}; use netlink_packet_core::{ NetlinkDeserializable, NetlinkMessage, NetlinkPayload, NetlinkSerializable, }; use crate::{ codecs::{NetlinkCodec, NetlinkMessageCodec}, framed::NetlinkFramed, sys::{AsyncSocket, SocketAddr}, Protocol, Request, Response, }; #[cfg(feature = "tokio_socket")] use netlink_sys::TokioSocket as DefaultSocket; #[cfg(not(feature = "tokio_socket"))] type DefaultSocket = (); /// Connection to a Netlink socket, running in the background. /// /// [`ConnectionHandle`](struct.ConnectionHandle.html) are used to pass new requests to the /// `Connection`, that in turn, sends them through the netlink socket. pub struct Connection where T: Debug + NetlinkSerializable + NetlinkDeserializable, { socket: NetlinkFramed, protocol: Protocol>>, /// Channel used by the user to pass requests to the connection. requests_rx: Option>>, /// Channel used to transmit to the ConnectionHandle the unsolicited messages received from the /// socket (multicast messages for instance). unsolicited_messages_tx: Option, SocketAddr)>>, socket_closed: bool, } impl Connection where T: Debug + NetlinkSerializable + NetlinkDeserializable + Unpin, S: AsyncSocket, C: NetlinkMessageCodec, { pub(crate) fn new( requests_rx: UnboundedReceiver>, unsolicited_messages_tx: UnboundedSender<(NetlinkMessage, SocketAddr)>, protocol: isize, ) -> io::Result { let socket = S::new(protocol)?; Ok(Connection { socket: NetlinkFramed::new(socket), protocol: Protocol::new(), requests_rx: Some(requests_rx), unsolicited_messages_tx: Some(unsolicited_messages_tx), socket_closed: false, }) } pub fn socket_mut(&mut self) -> &mut S { self.socket.get_mut() } pub fn poll_send_messages(&mut self, cx: &mut Context) { trace!("poll_send_messages called"); let Connection { ref mut socket, ref mut protocol, .. } = self; let mut socket = Pin::new(socket); while !protocol.outgoing_messages.is_empty() { trace!("found outgoing message to send checking if socket is ready"); if let Poll::Ready(Err(e)) = Pin::as_mut(&mut socket).poll_ready(cx) { // Sink errors are usually not recoverable. The socket // probably shut down. warn!("netlink socket shut down: {:?}", e); self.socket_closed = true; return; } let (mut message, addr) = protocol.outgoing_messages.pop_front().unwrap(); message.finalize(); trace!("sending outgoing message"); if let Err(e) = Pin::as_mut(&mut socket).start_send((message, addr)) { error!("failed to send message: {:?}", e); self.socket_closed = true; return; } } trace!("poll_send_messages done"); self.poll_flush(cx) } pub fn poll_flush(&mut self, cx: &mut Context) { trace!("poll_flush called"); if let Poll::Ready(Err(e)) = Pin::new(&mut self.socket).poll_flush(cx) { warn!("error flushing netlink socket: {:?}", e); self.socket_closed = true; } } pub fn poll_read_messages(&mut self, cx: &mut Context) { trace!("poll_read_messages called"); let mut socket = Pin::new(&mut self.socket); loop { trace!("polling socket"); match socket.as_mut().poll_next(cx) { Poll::Ready(Some((message, addr))) => { trace!("read datagram from socket"); self.protocol.handle_message(message, addr); } Poll::Ready(None) => { warn!("netlink socket stream shut down"); self.socket_closed = true; return; } Poll::Pending => { trace!("no datagram read from socket"); return; } } } } pub fn poll_requests(&mut self, cx: &mut Context) { trace!("poll_requests called"); if let Some(mut stream) = self.requests_rx.as_mut() { loop { match Pin::new(&mut stream).poll_next(cx) { Poll::Ready(Some(request)) => self.protocol.request(request), Poll::Ready(None) => break, Poll::Pending => return, } } let _ = self.requests_rx.take(); trace!("no new requests to handle poll_requests done"); } } pub fn forward_unsolicited_messages(&mut self) { if self.unsolicited_messages_tx.is_none() { while let Some((message, source)) = self.protocol.incoming_requests.pop_front() { warn!( "ignoring unsolicited message {:?} from {:?}", message, source ); } return; } trace!("forward_unsolicited_messages called"); let mut ready = false; let Connection { ref mut protocol, ref mut unsolicited_messages_tx, .. } = self; while let Some((message, source)) = protocol.incoming_requests.pop_front() { if unsolicited_messages_tx .as_mut() .unwrap() .unbounded_send((message, source)) .is_err() { // The channel is unbounded so the only error that can // occur is that the channel is closed because the // receiver was dropped warn!("failed to forward message to connection handle: channel closed"); ready = true; break; } } if ready { // The channel is closed so we can drop the sender. let _ = self.unsolicited_messages_tx.take(); // purge `protocol.incoming_requests` self.forward_unsolicited_messages(); } trace!("forward_unsolicited_messages done"); } pub fn forward_responses(&mut self) { trace!("forward_responses called"); let protocol = &mut self.protocol; while let Some(response) = protocol.incoming_responses.pop_front() { let Response { message, done, metadata: tx, } = response; if done { use NetlinkPayload::*; match &message.payload { // Since `self.protocol` set the `done` flag here, // we know it has already dropped the request and // its associated metadata, ie the UnboundedSender // used to forward messages back to the // ConnectionHandle. By just continuing we're // dropping the last instance of that sender, // hence closing the channel and signaling the // handle that no more messages are expected. Noop | Done | Ack(_) => { trace!("not forwarding Noop/Ack/Done message to the handle"); continue; } // I'm not sure how we should handle overrun messages Overrun(_) => unimplemented!("overrun is not handled yet"), // We need to forward error messages and messages // that are part of the netlink subprotocol, // because only the user knows how they want to // handle them. Error(_) | InnerMessage(_) => {} } } trace!("forwarding response to the handle"); if tx.unbounded_send(message).is_err() { // With an unboundedsender, an error can // only happen if the receiver is closed. warn!("failed to forward response back to the handle"); } } trace!("forward_responses done"); } pub fn should_shut_down(&self) -> bool { self.socket_closed || (self.unsolicited_messages_tx.is_none() && self.requests_rx.is_none()) } } impl Future for Connection where T: Debug + NetlinkSerializable + NetlinkDeserializable + Unpin, S: AsyncSocket, C: NetlinkMessageCodec, { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { trace!("polling Connection"); let pinned = self.get_mut(); debug!("reading incoming messages"); pinned.poll_read_messages(cx); debug!("forwarding unsolicited messages to the connection handle"); pinned.forward_unsolicited_messages(); debug!("forwaring responses to previous requests to the connection handle"); pinned.forward_responses(); debug!("handling requests"); pinned.poll_requests(cx); debug!("sending messages"); pinned.poll_send_messages(cx); trace!("done polling Connection"); if pinned.should_shut_down() { Poll::Ready(()) } else { Poll::Pending } } } ================================================ FILE: netlink-proto/src/errors.rs ================================================ // SPDX-License-Identifier: MIT use std::io; use netlink_packet_core::NetlinkMessage; #[derive(thiserror::Error, Debug)] pub enum Error { /// The netlink connection is closed #[error("the netlink connection is closed")] ConnectionClosed, /// Received an error message as a response #[error("received an error message as a response: {0:?}")] NetlinkError(NetlinkMessage), /// Error while reading from or writing to the netlink socket #[error("error while reading from or writing to the netlink socket: {0}")] SocketIo(#[from] io::Error), } ================================================ FILE: netlink-proto/src/framed.rs ================================================ // SPDX-License-Identifier: MIT use bytes::BytesMut; use std::{ fmt::Debug, io, marker::PhantomData, pin::Pin, task::{Context, Poll}, }; use futures::{Sink, Stream}; use log::error; use crate::{ codecs::NetlinkMessageCodec, sys::{AsyncSocket, SocketAddr}, }; use netlink_packet_core::{NetlinkDeserializable, NetlinkMessage, NetlinkSerializable}; pub struct NetlinkFramed { socket: S, // see https://doc.rust-lang.org/nomicon/phantom-data.html // "invariant" seems like the safe choice; using `fn(T) -> T` // should make it invariant but still Send+Sync. msg_type: PhantomData T>, // invariant codec: PhantomData C>, // invariant reader: BytesMut, writer: BytesMut, in_addr: SocketAddr, out_addr: SocketAddr, flushed: bool, } impl Stream for NetlinkFramed where T: NetlinkDeserializable + Debug, S: AsyncSocket, C: NetlinkMessageCodec, { type Item = (NetlinkMessage, SocketAddr); fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let Self { ref mut socket, ref mut in_addr, ref mut reader, .. } = Pin::get_mut(self); loop { match C::decode::(reader) { Ok(Some(item)) => return Poll::Ready(Some((item, *in_addr))), Ok(None) => {} Err(e) => { error!("unrecoverable error in decoder: {:?}", e); return Poll::Ready(None); } } reader.clear(); reader.reserve(INITIAL_READER_CAPACITY); *in_addr = match ready!(socket.poll_recv_from(cx, reader)) { Ok(addr) => addr, Err(e) => { error!("failed to read from netlink socket: {:?}", e); return Poll::Ready(None); } }; } } } impl Sink<(NetlinkMessage, SocketAddr)> for NetlinkFramed where T: NetlinkSerializable + Debug, S: AsyncSocket, C: NetlinkMessageCodec, { type Error = io::Error; fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if !self.flushed { match self.poll_flush(cx)? { Poll::Ready(()) => {} Poll::Pending => return Poll::Pending, } } Poll::Ready(Ok(())) } fn start_send( self: Pin<&mut Self>, item: (NetlinkMessage, SocketAddr), ) -> Result<(), Self::Error> { trace!("sending frame"); let (frame, out_addr) = item; let pin = self.get_mut(); C::encode(frame, &mut pin.writer)?; pin.out_addr = out_addr; pin.flushed = false; trace!("frame encoded; length={}", pin.writer.len()); Ok(()) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.flushed { return Poll::Ready(Ok(())); } trace!("flushing frame; length={}", self.writer.len()); let Self { ref mut socket, ref mut out_addr, ref mut writer, .. } = *self; let n = ready!(socket.poll_send_to(cx, writer, out_addr))?; trace!("written {}", n); let wrote_all = n == self.writer.len(); self.writer.clear(); self.flushed = true; let res = if wrote_all { Ok(()) } else { Err(io::Error::new( io::ErrorKind::Other, "failed to write entire datagram to socket", )) }; Poll::Ready(res) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.poll_flush(cx))?; Poll::Ready(Ok(())) } } // The theoritical max netlink packet size is 32KB for a netlink // message since Linux 4.9 (16KB before). See: // https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git/commit/?id=d35c99ff77ecb2eb239731b799386f3b3637a31e const INITIAL_READER_CAPACITY: usize = 64 * 1024; const INITIAL_WRITER_CAPACITY: usize = 8 * 1024; impl NetlinkFramed { /// Create a new `NetlinkFramed` backed by the given socket and codec. /// /// See struct level documentation for more details. pub fn new(socket: S) -> Self { Self { socket, msg_type: PhantomData, codec: PhantomData, out_addr: SocketAddr::new(0, 0), in_addr: SocketAddr::new(0, 0), reader: BytesMut::with_capacity(INITIAL_READER_CAPACITY), writer: BytesMut::with_capacity(INITIAL_WRITER_CAPACITY), flushed: true, } } /// Returns a reference to the underlying I/O stream wrapped by `Framed`. /// /// # Note /// /// Care should be taken to not tamper with the underlying stream of data /// coming in as it may corrupt the stream of frames otherwise being worked /// with. pub fn get_ref(&self) -> &S { &self.socket } /// Returns a mutable reference to the underlying I/O stream wrapped by /// `Framed`. /// /// # Note /// /// Care should be taken to not tamper with the underlying stream of data /// coming in as it may corrupt the stream of frames otherwise being worked /// with. pub fn get_mut(&mut self) -> &mut S { &mut self.socket } /// Consumes the `Framed`, returning its underlying I/O stream. pub fn into_inner(self) -> S { self.socket } } ================================================ FILE: netlink-proto/src/handle.rs ================================================ // SPDX-License-Identifier: MIT use futures::{ channel::mpsc::{unbounded, UnboundedSender}, Stream, }; use netlink_packet_core::NetlinkMessage; use std::fmt::Debug; use crate::{errors::Error, sys::SocketAddr, Request}; /// A handle to pass requests to a [`Connection`](struct.Connection.html). #[derive(Clone, Debug)] pub struct ConnectionHandle where T: Debug, { requests_tx: UnboundedSender>, } impl ConnectionHandle where T: Debug, { pub(crate) fn new(requests_tx: UnboundedSender>) -> Self { ConnectionHandle { requests_tx } } /// Send a new request and get the response as a stream of messages. Note that some messages /// are not part of the response stream: /// /// - **acknowledgements**: when an acknowledgement is received, the stream is closed /// - **end of dump messages**: similarly, upon receiving an "end of dump" message, the stream is /// closed pub fn request( &mut self, message: NetlinkMessage, destination: SocketAddr, ) -> Result>, Error> { let (tx, rx) = unbounded::>(); let request = Request::from((message, destination, tx)); debug!("handle: forwarding new request to connection"); UnboundedSender::unbounded_send(&self.requests_tx, request).map_err(|e| { // the channel is unbounded, so it can't be full. If this // failed, it means the Connection shut down. if e.is_full() { panic!("internal error: unbounded channel full?!"); } else if e.is_disconnected() { Error::ConnectionClosed } else { panic!("unknown error: {:?}", e); } })?; Ok(rx) } pub fn notify( &mut self, message: NetlinkMessage, destination: SocketAddr, ) -> Result<(), Error> { let (tx, _rx) = unbounded::>(); let request = Request::from((message, destination, tx)); debug!("handle: forwarding new request to connection"); UnboundedSender::unbounded_send(&self.requests_tx, request) .map_err(|_| Error::ConnectionClosed) } } ================================================ FILE: netlink-proto/src/lib.rs ================================================ // SPDX-License-Identifier: MIT //! `netlink-proto` is an asynchronous implementation of the Netlink //! protocol. //! //! # Example: listening for audit events //! //! This example shows how to use `netlink-proto` with the `tokio` //! runtime to print audit events. It requires extra external //! dependencies: //! //! - `futures = "^0.3"` //! - `tokio = "^1.0"` //! - `netlink-packet-audit = "^0.1"` //! //! ```rust,no_run //! use futures::stream::StreamExt; //! use netlink_packet_audit::{ //! AuditMessage, //! NetlinkMessage, //! NetlinkPayload, //! StatusMessage, //! NLM_F_ACK, //! NLM_F_REQUEST, //! }; //! use std::process; //! //! use netlink_proto::{ //! new_connection, //! sys::{protocols::NETLINK_AUDIT, SocketAddr}, //! }; //! //! const AUDIT_STATUS_ENABLED: u32 = 1; //! const AUDIT_STATUS_PID: u32 = 4; //! //! #[tokio::main] //! async fn main() -> Result<(), String> { //! // Create a netlink socket. Here: //! // //! // - `conn` is a `Connection` that has the netlink socket. It's a //! // `Future` that keeps polling the socket and must be spawned an //! // the event loop. //! // //! // - `handle` is a `Handle` to the `Connection`. We use it to send //! // netlink messages and receive responses to these messages. //! // //! // - `messages` is a channel receiver through which we receive //! // messages that we have not solicited, ie that are not //! // response to a request we made. In this example, we'll receive //! // the audit event through that channel. //! let (conn, mut handle, mut messages) = new_connection(NETLINK_AUDIT) //! .map_err(|e| format!("Failed to create a new netlink connection: {}", e))?; //! //! // Spawn the `Connection` so that it starts polling the netlink //! // socket in the background. //! tokio::spawn(conn); //! //! // Use the `ConnectionHandle` to send a request to the kernel //! // asking it to start multicasting audit event messages. //! tokio::spawn(async move { //! // Craft the packet to enable audit events //! let mut status = StatusMessage::new(); //! status.enabled = 1; //! status.pid = process::id(); //! status.mask = AUDIT_STATUS_ENABLED | AUDIT_STATUS_PID; //! let payload = AuditMessage::SetStatus(status); //! let mut nl_msg = NetlinkMessage::from(payload); //! nl_msg.header.flags = NLM_F_REQUEST | NLM_F_ACK; //! //! // We'll send unicast messages to the kernel. //! let kernel_unicast: SocketAddr = SocketAddr::new(0, 0); //! let mut response = match handle.request(nl_msg, kernel_unicast) { //! Ok(response) => response, //! Err(e) => { //! eprintln!("{}", e); //! return; //! } //! }; //! //! while let Some(message) = response.next().await { //! if let NetlinkPayload::Error(err_message) = message.payload { //! eprintln!("Received an error message: {:?}", err_message); //! return; //! } //! } //! }); //! //! // Finally, start receiving event through the `messages` channel. //! println!("Starting to print audit events... press ^C to interrupt"); //! while let Some((message, _addr)) = messages.next().await { //! if let NetlinkPayload::Error(err_message) = message.payload { //! eprintln!("received an error message: {:?}", err_message); //! } else { //! println!("{:?}", message); //! } //! } //! //! Ok(()) //! } //! ``` //! //! # Example: dumping all the machine's links //! //! This example shows how to use `netlink-proto` with the ROUTE //! protocol. //! //! Here we do not use `netlink_proto::new_connection()`, and instead //! create the socket manually and use call `send()` and `receive()` //! directly. In the previous example, the `NetlinkFramed` was wrapped //! in a `Connection` which was polled automatically by the runtime. //! //! ```rust,no_run //! use futures::StreamExt; //! //! use netlink_packet_route::{ //! LinkMessage, //! NetlinkHeader, //! NetlinkMessage, //! RtnlMessage, //! NLM_F_DUMP, //! NLM_F_REQUEST, //! }; //! //! use netlink_proto::{ //! new_connection, //! sys::{protocols::NETLINK_ROUTE, SocketAddr}, //! }; //! //! #[tokio::main] //! async fn main() -> Result<(), String> { //! // Create the netlink socket. Here, we won't use the channel that //! // receives unsolicited messages. //! let (conn, mut handle, _) = new_connection(NETLINK_ROUTE) //! .map_err(|e| format!("Failed to create a new netlink connection: {}", e))?; //! //! // Spawn the `Connection` in the background //! tokio::spawn(conn); //! //! // Create the netlink message that requests the links to be dumped //! let msg = NetlinkMessage { //! header: NetlinkHeader { //! sequence_number: 1, //! flags: NLM_F_DUMP | NLM_F_REQUEST, //! ..Default::default() //! }, //! payload: RtnlMessage::GetLink(LinkMessage::default()).into(), //! }; //! //! // Send the request //! let mut response = handle //! .request(msg, SocketAddr::new(0, 0)) //! .map_err(|e| format!("Failed to send request: {}", e))?; //! //! // Print all the messages received in response //! loop { //! if let Some(packet) = response.next().await { //! println!("<<< {:?}", packet); //! } else { //! break; //! } //! } //! //! Ok(()) //! } //! ``` #[macro_use] extern crate futures; #[macro_use] extern crate log; mod codecs; pub use crate::codecs::*; mod framed; pub use crate::framed::*; mod protocol; pub(crate) use self::protocol::{Protocol, Response}; pub(crate) type Request = self::protocol::Request>>; mod connection; pub use crate::connection::*; mod errors; pub use crate::errors::*; mod handle; pub use crate::handle::*; use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; use std::{fmt::Debug, io}; pub use netlink_packet_core as packet; pub mod sys { pub use netlink_sys::{protocols, AsyncSocket, AsyncSocketExt, SocketAddr}; #[cfg(feature = "tokio_socket")] pub use netlink_sys::TokioSocket; #[cfg(feature = "smol_socket")] pub use netlink_sys::SmolSocket; } /// Create a new Netlink connection for the given Netlink protocol, and returns a handle to that /// connection as well as a stream of unsolicited messages received by that connection (unsolicited /// here means messages that are not a response to a request made by the `Connection`). /// `Connection` wraps a Netlink socket and implements the Netlink protocol. /// /// `protocol` must be one of the [`crate::sys::protocols`][protos] constants. /// /// `T` is the type of netlink messages used for this protocol. For instance, if you're using the /// `NETLINK_AUDIT` protocol with the `netlink-packet-audit` crate, `T` will be /// `netlink_packet_audit::AuditMessage`. More generally, `T` is anything that can be serialized /// and deserialized into a Netlink message. See the `netlink_packet_core` documentation for /// details about the `NetlinkSerializable` and `NetlinkDeserializable` traits. /// /// Most of the time, users will want to spawn the `Connection` on an async runtime, and use the /// handle to send messages. /// /// [protos]: crate::sys::protocols #[cfg(feature = "tokio_socket")] #[allow(clippy::type_complexity)] pub fn new_connection( protocol: isize, ) -> io::Result<( Connection, ConnectionHandle, UnboundedReceiver<(packet::NetlinkMessage, sys::SocketAddr)>, )> where T: Debug + packet::NetlinkSerializable + packet::NetlinkDeserializable + Unpin, { new_connection_with_codec(protocol) } /// Variant of [`new_connection`] that allows specifying a socket type to use for async handling #[allow(clippy::type_complexity)] pub fn new_connection_with_socket( protocol: isize, ) -> io::Result<( Connection, ConnectionHandle, UnboundedReceiver<(packet::NetlinkMessage, sys::SocketAddr)>, )> where T: Debug + packet::NetlinkSerializable + packet::NetlinkDeserializable + Unpin, S: sys::AsyncSocket, { new_connection_with_codec(protocol) } /// Variant of [`new_connection`] that allows specifying a socket type to use for async handling and a special codec #[allow(clippy::type_complexity)] pub fn new_connection_with_codec( protocol: isize, ) -> io::Result<( Connection, ConnectionHandle, UnboundedReceiver<(packet::NetlinkMessage, sys::SocketAddr)>, )> where T: Debug + packet::NetlinkSerializable + packet::NetlinkDeserializable + Unpin, S: sys::AsyncSocket, C: NetlinkMessageCodec, { let (requests_tx, requests_rx) = unbounded::>(); let (messages_tx, messages_rx) = unbounded::<(packet::NetlinkMessage, sys::SocketAddr)>(); Ok(( Connection::new(requests_rx, messages_tx, protocol)?, ConnectionHandle::new(requests_tx), messages_rx, )) } ================================================ FILE: netlink-proto/src/protocol/mod.rs ================================================ // SPDX-License-Identifier: MIT #[allow(clippy::module_inception)] mod protocol; mod request; pub(crate) use protocol::{Protocol, Response}; pub(crate) use request::Request; ================================================ FILE: netlink-proto/src/protocol/protocol.rs ================================================ // SPDX-License-Identifier: MIT use std::{ collections::{hash_map, HashMap, VecDeque}, fmt::Debug, }; use netlink_packet_core::{ constants::*, NetlinkDeserializable, NetlinkMessage, NetlinkPayload, NetlinkSerializable, }; use super::Request; use crate::sys::SocketAddr; #[derive(Debug, Eq, PartialEq, Hash)] struct RequestId { sequence_number: u32, port: u32, } impl RequestId { fn new(sequence_number: u32, port: u32) -> Self { Self { sequence_number, port, } } } #[derive(Debug, Eq, PartialEq)] pub(crate) struct Response { pub done: bool, pub message: NetlinkMessage, pub metadata: M, } #[derive(Debug)] struct PendingRequest { expecting_ack: bool, metadata: M, } #[derive(Debug, Default)] pub(crate) struct Protocol { /// Counter that is incremented for each message sent sequence_id: u32, /// Requests for which we're awaiting a response. Metadata are /// associated with each request. pending_requests: HashMap>, /// Responses to pending requests pub incoming_responses: VecDeque>, /// Requests from remote peers pub incoming_requests: VecDeque<(NetlinkMessage, SocketAddr)>, /// The messages to be sent out pub outgoing_messages: VecDeque<(NetlinkMessage, SocketAddr)>, } impl Protocol where T: Debug + NetlinkSerializable + NetlinkDeserializable, M: Debug + Clone, { pub fn new() -> Self { Self { sequence_id: 0, pending_requests: HashMap::new(), incoming_responses: VecDeque::new(), incoming_requests: VecDeque::new(), outgoing_messages: VecDeque::new(), } } pub fn handle_message(&mut self, message: NetlinkMessage, source: SocketAddr) { let request_id = RequestId::new(message.header.sequence_number, source.port_number()); debug!("handling messages (request id = {:?})", request_id); if let hash_map::Entry::Occupied(entry) = self.pending_requests.entry(request_id) { Self::handle_response(&mut self.incoming_responses, entry, message); } else { self.incoming_requests.push_back((message, source)); } } fn handle_response( incoming_responses: &mut VecDeque>, entry: hash_map::OccupiedEntry>, message: NetlinkMessage, ) { let entry_key; let mut request_id = entry.key(); debug!("handling response to request {:?}", request_id); // A request is processed if we receive an Ack, Error, // Done, Overrun, or InnerMessage without the // multipart flag and we were not expecting an Ack let done = match message.payload { NetlinkPayload::InnerMessage(_) if message.header.flags & NLM_F_MULTIPART == NLM_F_MULTIPART => { false } NetlinkPayload::InnerMessage(_) => !entry.get().expecting_ack, _ => true, }; let metadata = if done { trace!("request {:?} fully processed", request_id); let (k, v) = entry.remove_entry(); entry_key = k; request_id = &entry_key; v.metadata } else { trace!("more responses to request {:?} may come", request_id); entry.get().metadata.clone() }; let response = Response:: { done, message, metadata, }; incoming_responses.push_back(response); debug!("done handling response to request {:?}", request_id); } pub fn request(&mut self, request: Request) { let Request { mut message, metadata, destination, } = request; self.set_sequence_id(&mut message); let request_id = RequestId::new(self.sequence_id, destination.port_number()); let flags = message.header.flags; self.outgoing_messages.push_back((message, destination)); // If we expect a response, we store the request id so that we // can map the response to this specific request. // // Note that we expect responses in three cases only: // - when the request has the NLM_F_REQUEST flag // - when the request has the NLM_F_ACK flag // - when the request has the NLM_F_ECHO flag let expecting_ack = flags & NLM_F_ACK == NLM_F_ACK; if flags & NLM_F_REQUEST == NLM_F_REQUEST || flags & NLM_F_ECHO == NLM_F_ECHO || expecting_ack { self.pending_requests.insert( request_id, PendingRequest { expecting_ack, metadata, }, ); } } fn set_sequence_id(&mut self, message: &mut NetlinkMessage) { self.sequence_id += 1; message.header.sequence_number = self.sequence_id; } } ================================================ FILE: netlink-proto/src/protocol/request.rs ================================================ // SPDX-License-Identifier: MIT use std::fmt::Debug; use netlink_packet_core::NetlinkMessage; use crate::sys::SocketAddr; #[derive(Debug)] pub(crate) struct Request { pub metadata: M, pub message: NetlinkMessage, pub destination: SocketAddr, } impl From<(NetlinkMessage, SocketAddr, M)> for Request where T: Debug, M: Debug, { fn from(parts: (NetlinkMessage, SocketAddr, M)) -> Self { Request { message: parts.0, destination: parts.1, metadata: parts.2, } } } impl From> for (NetlinkMessage, SocketAddr, M) where T: Debug, M: Debug, { fn from(req: Request) -> (NetlinkMessage, SocketAddr, M) { (req.message, req.destination, req.metadata) } } ================================================ FILE: netlink-sys/Cargo.toml ================================================ [package] authors = ["Corentin Henry "] name = "netlink-sys" version = "0.8.3" edition = "2018" homepage = "https://github.com/little-dude/netlink" keywords = ["netlink", "ip", "linux"] license = "MIT" readme = "../README.md" repository = "https://github.com/little-dude/netlink" description = "netlink sockets, with optional integration with tokio" [dependencies] bytes = "1.0" libc = "0.2.66" log = "0.4.8" [dependencies.futures] optional = true version = "0.3.1" [dependencies.tokio] optional = true version = "1.0.1" default-features = false # We only depend on tokio for PollEvented features = ["net"] [dependencies.mio] optional = true version = "0.8" features = ["os-poll", "os-ext"] [dependencies.async-io] optional = true version = "1.3" [features] default = [] mio_socket = ["mio"] tokio_socket = ["tokio", "futures"] smol_socket = ["async-io","futures"] [dev-dependencies] netlink-packet-audit = { version = "0.4.1", path = "../netlink-packet-audit" } [dev-dependencies.tokio] version = "1.0.1" default-features = false # We only depend on tokio for PollEvented features = ["net", "macros", "rt-multi-thread"] [dev-dependencies.async-std] version = "1.9.0" features = ["attributes"] [[example]] name = "audit_events" [[example]] name = "audit_events_tokio" required-features = ["tokio_socket"] [[example]] name = "audit_events_tokio_manual_thread_builder" required-features = ["tokio_socket"] [[example]] name = "audit_events_async_std" required-features = ["smol_socket"] ================================================ FILE: netlink-sys/examples/audit_events.rs ================================================ // SPDX-License-Identifier: MIT // Build: // // ``` // cd netlink-sys // cargo build --example audit_events // ``` // // Run *as root*: // // ``` // ../target/debug/examples/audit_events // ``` use std::process; use netlink_packet_audit::{ AuditMessage, NetlinkBuffer, NetlinkMessage, StatusMessage, NLM_F_ACK, NLM_F_REQUEST, }; use netlink_sys::{protocols::NETLINK_AUDIT, Socket, SocketAddr}; pub const AUDIT_STATUS_ENABLED: u32 = 1; pub const AUDIT_STATUS_PID: u32 = 4; fn main() { let kernel_unicast: SocketAddr = SocketAddr::new(0, 0); let socket = Socket::new(NETLINK_AUDIT).unwrap(); let mut status = StatusMessage::new(); status.enabled = 1; status.pid = process::id(); status.mask = AUDIT_STATUS_ENABLED | AUDIT_STATUS_PID; let payload = AuditMessage::SetStatus(status); let mut nl_msg = NetlinkMessage::from(payload); nl_msg.header.flags = NLM_F_REQUEST | NLM_F_ACK; nl_msg.finalize(); let mut buf = vec![0; 1024 * 8]; nl_msg.serialize(&mut buf[..nl_msg.buffer_len()]); socket .send_to(&buf[..nl_msg.buffer_len()], &kernel_unicast, 0) .unwrap(); let mut buf = vec![0; 1024 * 8]; loop { let (n, _addr) = socket.recv_from(&mut buf, 0).unwrap(); // This dance with the NetlinkBuffer should not be // necessary. It is here to work around a netlink bug. See: // https://github.com/mozilla/libaudit-go/issues/24 // https://github.com/linux-audit/audit-userspace/issues/78 { let mut nl_buf = NetlinkBuffer::new(&mut buf[0..n]); if n != nl_buf.length() as usize { nl_buf.set_length(n as u32); } } let parsed = NetlinkMessage::::deserialize(&buf[0..n]).unwrap(); println!("<<< {:?}", parsed); } } ================================================ FILE: netlink-sys/examples/audit_events_async_std.rs ================================================ // SPDX-License-Identifier: MIT // Build: // // ``` // cd netlink-sys // cargo build --example audit_events_async_std --features async_std_socket // ``` // // Run *as root*: // // ``` // ../target/debug/examples/audit_events_async_std // ``` use std::process; use netlink_packet_audit::{ AuditMessage, NetlinkBuffer, NetlinkMessage, StatusMessage, NLM_F_ACK, NLM_F_REQUEST, }; use netlink_sys::{protocols::NETLINK_AUDIT, AsyncSocket, AsyncSocketExt, SmolSocket, SocketAddr}; const AUDIT_STATUS_ENABLED: u32 = 1; const AUDIT_STATUS_PID: u32 = 4; #[async_std::main] async fn main() { let kernel_unicast: SocketAddr = SocketAddr::new(0, 0); let mut socket = SmolSocket::new(NETLINK_AUDIT).unwrap(); let mut status = StatusMessage::new(); status.enabled = 1; status.pid = process::id(); status.mask = AUDIT_STATUS_ENABLED | AUDIT_STATUS_PID; let payload = AuditMessage::SetStatus(status); let mut nl_msg = NetlinkMessage::from(payload); nl_msg.header.flags = NLM_F_REQUEST | NLM_F_ACK; nl_msg.finalize(); let mut buf = vec![0; 1024 * 8]; nl_msg.serialize(&mut buf[..nl_msg.buffer_len()]); println!(">>> {:?}", nl_msg); socket .send_to(&buf[..nl_msg.buffer_len()], &kernel_unicast) .await .unwrap(); let mut buf = bytes::BytesMut::with_capacity(1024 * 8); loop { buf.clear(); let _addr = socket.recv_from(&mut buf).await.unwrap(); // This dance with the NetlinkBuffer should not be // necessary. It is here to work around a netlink bug. See: // https://github.com/mozilla/libaudit-go/issues/24 // https://github.com/linux-audit/audit-userspace/issues/78 { let n = buf.len(); let mut nl_buf = NetlinkBuffer::new(&mut buf); if n != nl_buf.length() as usize { nl_buf.set_length(n as u32); } } let parsed = NetlinkMessage::::deserialize(&buf).unwrap(); println!("<<< {:?}", parsed); } } ================================================ FILE: netlink-sys/examples/audit_events_tokio.rs ================================================ // SPDX-License-Identifier: MIT // Build: // // ``` // cd netlink-sys // cargo build --example audit_events_async --features tokio_socket // ``` // // Run *as root*: // // ``` // ../target/debug/examples/audit_events_async // ``` use std::process; use netlink_packet_audit::{ AuditMessage, NetlinkBuffer, NetlinkMessage, StatusMessage, NLM_F_ACK, NLM_F_REQUEST, }; use netlink_sys::{protocols::NETLINK_AUDIT, AsyncSocket, AsyncSocketExt, SocketAddr, TokioSocket}; const AUDIT_STATUS_ENABLED: u32 = 1; const AUDIT_STATUS_PID: u32 = 4; #[tokio::main] async fn main() -> Result<(), Box> { let kernel_unicast: SocketAddr = SocketAddr::new(0, 0); let mut socket = TokioSocket::new(NETLINK_AUDIT).unwrap(); let mut status = StatusMessage::new(); status.enabled = 1; status.pid = process::id(); status.mask = AUDIT_STATUS_ENABLED | AUDIT_STATUS_PID; let payload = AuditMessage::SetStatus(status); let mut nl_msg = NetlinkMessage::from(payload); nl_msg.header.flags = NLM_F_REQUEST | NLM_F_ACK; nl_msg.finalize(); let mut buf = vec![0; 1024 * 8]; nl_msg.serialize(&mut buf[..nl_msg.buffer_len()]); println!(">>> {:?}", nl_msg); socket .send_to(&buf[..nl_msg.buffer_len()], &kernel_unicast) .await .unwrap(); let mut buf = bytes::BytesMut::with_capacity(1024 * 8); loop { buf.clear(); let _addr = socket.recv_from(&mut buf).await.unwrap(); // This dance with the NetlinkBuffer should not be // necessary. It is here to work around a netlink bug. See: // https://github.com/mozilla/libaudit-go/issues/24 // https://github.com/linux-audit/audit-userspace/issues/78 { let n = buf.len(); let mut nl_buf = NetlinkBuffer::new(&mut buf); if n != nl_buf.length() as usize { nl_buf.set_length(n as u32); } } let parsed = NetlinkMessage::::deserialize(&buf).unwrap(); println!("<<< {:?}", parsed); } } ================================================ FILE: netlink-sys/examples/audit_events_tokio_manual_thread_builder.rs ================================================ // SPDX-License-Identifier: MIT /* * This example shows the mimimal manual tokio initialization required to be able * to use netlink. */ use netlink_sys::{protocols::NETLINK_AUDIT, AsyncSocket, TokioSocket}; fn main() -> Result<(), String> { let rt = tokio::runtime::Builder::new_multi_thread() .enable_io() .build() .unwrap(); let future = async { TokioSocket::new(NETLINK_AUDIT).unwrap(); }; rt.handle().block_on(future); Ok(()) } ================================================ FILE: netlink-sys/src/addr.rs ================================================ // SPDX-License-Identifier: MIT use std::{ fmt, hash::{Hash, Hasher}, mem, }; /// The address of a netlink socket /// /// A netlink address is made of two parts: the unicast address of the socket, called _port number_ or _PID_, and the /// multicast address called _group ID_. In this library, we've chosen to stick to the "port number" terminology, since /// PID can be confused with process ID. However, the netlink man page mostly uses PID. /// /// ## Port number /// /// Sockets in kernel space have 0 as a port number. For sockets opened by a user-space process, the port number can /// either be assigned by the process itself, or by the kernel. The only constraint is that this port number must be /// unique: two netlink sockets created by a given process must have a different port number. However, netlinks sockets /// created by different processes can have the same port number. /// /// ### Port number assigned by the kernel /// /// One way to set the port number is to let the kernel assign it, by calling [`Socket::bind`][bind] with a port number set to /// 0. The kernel will usually use the process ID as port number for the first netlink socket created by the process, /// which is why the socket port number is also called PID. For example: /// /// ```rust /// use std::process; /// use netlink_sys::{ /// protocols::NETLINK_ROUTE, /// SocketAddr, Socket, /// }; /// /// let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); /// // The first parameter is the port number. By setting it to 0 we ask the kernel to pick a port for us /// let mut addr = SocketAddr::new(0, 0); /// socket.bind(&addr).unwrap(); /// // Retrieve the socket address /// socket.get_address(&mut addr).unwrap(); /// // the socket port number should be equal to the process ID, but there is no guarantee /// println!("socket port number = {}, process ID = {}", addr.port_number(), process::id()); /// /// let mut socket2 = Socket::new(NETLINK_ROUTE).unwrap(); /// let mut addr2 = SocketAddr::new(0, 0); /// socket2.bind(&addr2).unwrap(); /// socket2.get_address(&mut addr2).unwrap(); /// // the unicast address picked by the kernel for the second socket should be different /// assert!(addr.port_number() != addr2.port_number()); /// ``` /// /// Note that it's a little tedious to create a socket address, call `bind` and then retrive the address with /// [`Socket::get_address`][get_addr]. To avoid this boilerplate you can use [`Socket::bind_auto`][bind_auto]: /// /// ```rust /// use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; /// use std::process; /// /// let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); /// let addr = socket.bind_auto().unwrap(); /// println!("socket port number = {}", addr.port_number()); /// ``` /// /// ### Setting the port number manually /// /// The application can also pick the port number by calling Socket::bind with an address with a non-zero port /// number. However, it must ensure that this number is unique for each socket created. For instance: /// /// ```rust /// use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; /// use std::process; /// /// let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); /// // set the socket port number to 2 /// let mut addr = SocketAddr::new(2, 0); /// socket.bind(&addr).unwrap(); /// // Retrieve the socket address /// socket.get_address(&mut addr).unwrap(); /// assert_eq!(2, addr.port_number()); /// /// // Creating a second socket with the same port number fails /// let mut socket2 = Socket::new(NETLINK_ROUTE).unwrap(); /// let mut addr2 = SocketAddr::new(2, 0); /// socket2.bind(&addr2).unwrap_err(); /// ``` /// /// [bind]: crate::Socket::bind /// [bind_auto]: crate::Socket::bind_auto /// [get_addr]: crate::Socket::get_address #[derive(Copy, Clone)] pub struct SocketAddr(pub(crate) libc::sockaddr_nl); impl Hash for SocketAddr { fn hash(&self, state: &mut H) { self.0.nl_family.hash(state); self.0.nl_pid.hash(state); self.0.nl_groups.hash(state); } } impl PartialEq for SocketAddr { fn eq(&self, other: &SocketAddr) -> bool { self.0.nl_family == other.0.nl_family && self.0.nl_pid == other.0.nl_pid && self.0.nl_groups == other.0.nl_groups } } impl fmt::Debug for SocketAddr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "SocketAddr(nl_family={}, nl_pid={}, nl_groups={})", self.0.nl_family, self.0.nl_pid, self.0.nl_groups ) } } impl Eq for SocketAddr {} impl fmt::Display for SocketAddr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "address family: {}, pid: {}, multicast groups: {})", self.0.nl_family, self.0.nl_pid, self.0.nl_groups ) } } impl SocketAddr { /// Create a new socket address for with th pub fn new(port_number: u32, multicast_groups: u32) -> Self { let mut addr: libc::sockaddr_nl = unsafe { mem::zeroed() }; addr.nl_family = libc::PF_NETLINK as libc::sa_family_t; addr.nl_pid = port_number; addr.nl_groups = multicast_groups; SocketAddr(addr) } /// Get the unicast address of this socket pub fn port_number(&self) -> u32 { self.0.nl_pid } /// Get the multicast groups of this socket pub fn multicast_groups(&self) -> u32 { self.0.nl_groups } pub(crate) fn as_raw(&self) -> (*const libc::sockaddr, libc::socklen_t) { let addr_ptr = &self.0 as *const libc::sockaddr_nl as *const libc::sockaddr; // \ / \ / // +---------------+---------------+ +----------+---------+ // | | // v | // create a raw pointer to the sockaddr_nl | // v // cast *sockaddr_nl -> *sockaddr // // This kind of things seems to be pretty usual when using C APIs from Rust. It could be // written in a shorter way thank to type inference: // // let addr_ptr: *const libc:sockaddr = &self.0 as *const _ as *const _; // // But since this is my first time dealing with this kind of things I chose the most // explicit form. let addr_len = mem::size_of::() as libc::socklen_t; (addr_ptr, addr_len) } pub(crate) fn as_raw_mut(&mut self) -> (*mut libc::sockaddr, libc::socklen_t) { let addr_ptr = &mut self.0 as *mut libc::sockaddr_nl as *mut libc::sockaddr; let addr_len = mem::size_of::() as libc::socklen_t; (addr_ptr, addr_len) } } ================================================ FILE: netlink-sys/src/async_socket.rs ================================================ // SPDX-License-Identifier: MIT use std::{ io, task::{Context, Poll}, }; use crate::{Socket, SocketAddr}; /// Trait to support different async backends pub trait AsyncSocket: Sized + Unpin { /// Access underyling [`Socket`] fn socket_ref(&self) -> &Socket; /// Mutable access to underyling [`Socket`] fn socket_mut(&mut self) -> &mut Socket; /// Wrapper for [`Socket::new`] fn new(protocol: isize) -> io::Result; /// Polling wrapper for [`Socket::send`] fn poll_send(&mut self, cx: &mut Context<'_>, buf: &[u8]) -> Poll>; /// Polling wrapper for [`Socket::send_to`] fn poll_send_to( &mut self, cx: &mut Context<'_>, buf: &[u8], addr: &SocketAddr, ) -> Poll>; /// Polling wrapper for [`Socket::recv`] /// /// Passes 0 for flags, and ignores the returned length (the buffer will have advanced by the amount read). fn poll_recv(&mut self, cx: &mut Context<'_>, buf: &mut B) -> Poll> where B: bytes::BufMut; /// Polling wrapper for [`Socket::recv_from`] /// /// Passes 0 for flags, and ignores the returned length - just returns the address (the buffer will have advanced by the amount read). fn poll_recv_from( &mut self, cx: &mut Context<'_>, buf: &mut B, ) -> Poll> where B: bytes::BufMut; /// Polling wrapper for [`Socket::recv_from_full`] /// /// Passes 0 for flags, and ignores the returned length - just returns the address (the buffer will have advanced by the amount read). fn poll_recv_from_full( &mut self, cx: &mut Context<'_>, ) -> Poll, SocketAddr)>>; } ================================================ FILE: netlink-sys/src/async_socket_ext.rs ================================================ // SPDX-License-Identifier: MIT use std::{ future::Future, io, pin::Pin, task::{Context, Poll}, }; use crate::{AsyncSocket, SocketAddr}; /// Support trait for [`AsyncSocket`] /// /// Provides awaitable variants of the poll functions from [`AsyncSocket`]. pub trait AsyncSocketExt: AsyncSocket { /// `async fn send(&mut self, buf: &[u8]) -> io::Result` fn send<'a, 'b>(&'a mut self, buf: &'b [u8]) -> PollSend<'a, 'b, Self> { PollSend { socket: self, buf } } /// `async fn send(&mut self, buf: &[u8]) -> io::Result` fn send_to<'a, 'b>( &'a mut self, buf: &'b [u8], addr: &'b SocketAddr, ) -> PollSendTo<'a, 'b, Self> { PollSendTo { socket: self, buf, addr, } } /// `async fn recv(&mut self, buf: &mut [u8]) -> io::Result<()>` fn recv<'a, 'b, B>(&'a mut self, buf: &'b mut B) -> PollRecv<'a, 'b, Self, B> where B: bytes::BufMut, { PollRecv { socket: self, buf } } /// `async fn recv(&mut self, buf: &mut [u8]) -> io::Result` fn recv_from<'a, 'b, B>(&'a mut self, buf: &'b mut B) -> PollRecvFrom<'a, 'b, Self, B> where B: bytes::BufMut, { PollRecvFrom { socket: self, buf } } /// `async fn recrecv_from_full(&mut self) -> io::Result<(Vec, SocketAddr)>` fn recv_from_full(&mut self) -> PollRecvFromFull<'_, Self> { PollRecvFromFull { socket: self } } } impl AsyncSocketExt for S {} pub struct PollSend<'a, 'b, S> { socket: &'a mut S, buf: &'b [u8], } impl Future for PollSend<'_, '_, S> where S: AsyncSocket, { type Output = io::Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this: &mut Self = Pin::into_inner(self); this.socket.poll_send(cx, this.buf) } } pub struct PollSendTo<'a, 'b, S> { socket: &'a mut S, buf: &'b [u8], addr: &'b SocketAddr, } impl Future for PollSendTo<'_, '_, S> where S: AsyncSocket, { type Output = io::Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this: &mut Self = Pin::into_inner(self); this.socket.poll_send_to(cx, this.buf, this.addr) } } pub struct PollRecv<'a, 'b, S, B> { socket: &'a mut S, buf: &'b mut B, } impl Future for PollRecv<'_, '_, S, B> where S: AsyncSocket, B: bytes::BufMut, { type Output = io::Result<()>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this: &mut Self = Pin::into_inner(self); this.socket.poll_recv(cx, this.buf) } } pub struct PollRecvFrom<'a, 'b, S, B> { socket: &'a mut S, buf: &'b mut B, } impl Future for PollRecvFrom<'_, '_, S, B> where S: AsyncSocket, B: bytes::BufMut, { type Output = io::Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this: &mut Self = Pin::into_inner(self); this.socket.poll_recv_from(cx, this.buf) } } pub struct PollRecvFromFull<'a, S> { socket: &'a mut S, } impl Future for PollRecvFromFull<'_, S> where S: AsyncSocket, { type Output = io::Result<(Vec, SocketAddr)>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this: &mut Self = Pin::into_inner(self); this.socket.poll_recv_from_full(cx) } } ================================================ FILE: netlink-sys/src/constants.rs ================================================ // SPDX-License-Identifier: MIT //! This module provides a lot of netlink constants for various protocol. As we add support for the //! various protocols, these constants will be moved to their own crate. use libc::c_int as int; /// Receives routing and link updates and may be used to modify the routing tables (both IPv4 /// and IPv6), IP addresses, link parameters, neighbor setups, queueing disciplines, traffic /// classes and packet classifiers (see rtnetlink(7)). pub const NETLINK_ROUTE: isize = 0; pub const NETLINK_UNUSED: isize = 1; /// Reserved for user-mode socket protocols. pub const NETLINK_USERSOCK: isize = 2; /// Transport IPv4 packets from netfilter to user space. Used by ip_queue kernel /// module. After a long period of being declared obsolete (in favor of the more advanced /// nfnetlink_queue feature), it was removed in Linux 3.5. pub const NETLINK_FIREWALL: isize = 3; /// Query information about sockets of various protocol families from the kernel (see sock_diag(7)). pub const NETLINK_SOCK_DIAG: isize = 4; /// Netfilter/iptables ULOG. pub const NETLINK_NFLOG: isize = 5; /// IPsec. pub const NETLINK_XFRM: isize = 6; /// SELinux event notifications. pub const NETLINK_SELINUX: isize = 7; /// Open-iSCSI. pub const NETLINK_ISCSI: isize = 8; /// Auditing. pub const NETLINK_AUDIT: isize = 9; /// Access to FIB lookup from user space. pub const NETLINK_FIB_LOOKUP: isize = 10; /// Kernel connector. See `Documentation/connector/*` in the Linux kernel source tree for further information. pub const NETLINK_CONNECTOR: isize = 11; /// Netfilter subsystem. pub const NETLINK_NETFILTER: isize = 12; /// Transport IPv6 packets from netfilter to user space. Used by ip6_queue kernel module. pub const NETLINK_IP6_FW: isize = 13; /// DECnet routing messages. pub const NETLINK_DNRTMSG: isize = 14; /// Kernel messages to user space. pub const NETLINK_KOBJECT_UEVENT: isize = 15; /// Generic netlink family for simplified netlink usage. pub const NETLINK_GENERIC: isize = 16; /// SCSI transpots pub const NETLINK_SCSITRANSPORT: isize = 18; pub const NETLINK_ECRYPTFS: isize = 19; /// Infiniband RDMA. pub const NETLINK_RDMA: isize = 20; /// Netlink interface to request information about ciphers registered with the kernel crypto /// API as well as allow configuration of the kernel crypto API. pub const NETLINK_CRYPTO: isize = 21; pub const TCA_ROOT_UNSPEC: int = 0; pub const TCA_ROOT_TAB: int = 1; pub const TCA_ROOT_FLAGS: int = 2; pub const TCA_ROOT_COUNT: int = 3; pub const TCA_ROOT_TIME_DELTA: int = 4; pub const EM_NONE: u32 = 0; pub const EM_M32: u32 = 1; pub const EM_SPARC: u32 = 2; pub const EM_386: u32 = 3; pub const EM_68K: u32 = 4; pub const EM_88K: u32 = 5; pub const EM_486: u32 = 6; pub const EM_860: u32 = 7; pub const EM_MIPS: u32 = 8; pub const EM_MIPS_RS3_LE: u32 = 10; pub const EM_MIPS_RS4_BE: u32 = 10; pub const EM_PARISC: u32 = 15; pub const EM_SPARC32PLUS: u32 = 18; pub const EM_PPC: u32 = 20; pub const EM_PPC64: u32 = 21; pub const EM_SPU: u32 = 23; pub const EM_ARM: u32 = 40; pub const EM_SH: u32 = 42; pub const EM_SPARCV9: u32 = 43; pub const EM_H8_300: u32 = 46; pub const EM_IA_64: u32 = 50; pub const EM_X86_64: u32 = 62; pub const EM_S390: u32 = 22; pub const EM_CRIS: u32 = 76; pub const EM_M32R: u32 = 88; pub const EM_MN10300: u32 = 89; pub const EM_OPENRISC: u32 = 92; pub const EM_BLACKFIN: u32 = 106; pub const EM_ALTERA_NIOS2: u32 = 113; pub const EM_TI_C6000: u32 = 140; pub const EM_AARCH64: u32 = 183; pub const EM_TILEPRO: u32 = 188; pub const EM_MICROBLAZE: u32 = 189; pub const EM_TILEGX: u32 = 191; pub const EM_BPF: u32 = 247; pub const EM_FRV: u32 = 21569; pub const EM_ALPHA: u32 = 36902; pub const EM_CYGNUS_M32R: u32 = 36929; pub const EM_S390_OLD: u32 = 41872; pub const EM_CYGNUS_MN10300: u32 = 48879; pub const NLMSGERR_ATTR_UNUSED: int = 0; pub const NLMSGERR_ATTR_MSG: int = 1; pub const NLMSGERR_ATTR_OFFS: int = 2; pub const NLMSGERR_ATTR_COOKIE: int = 3; pub const NLMSGERR_ATTR_MAX: int = 3; pub const NL_MMAP_STATUS_UNUSED: int = 0; pub const NL_MMAP_STATUS_RESERVED: int = 1; pub const NL_MMAP_STATUS_VALID: int = 2; pub const NL_MMAP_STATUS_COPY: int = 3; pub const NL_MMAP_STATUS_SKIP: int = 4; pub const NETLINK_UNCONNECTED: int = 0; pub const NETLINK_CONNECTED: int = 1; pub const __BITS_PER_LONG: int = 64; pub const __FD_SETSIZE: int = 1024; pub const SI_LOAD_SHIFT: int = 16; pub const _K_SS_MAXSIZE: int = 128; pub const NETLINK_SMC: int = 22; pub const NETLINK_INET_DIAG: int = 4; pub const MAX_LINKS: int = 32; pub const NLMSG_MIN_TYPE: int = 16; pub const NETLINK_ADD_MEMBERSHIP: int = 1; pub const NETLINK_DROP_MEMBERSHIP: int = 2; pub const NETLINK_PKTINFO: int = 3; pub const NETLINK_BROADCAST_ERROR: int = 4; pub const NETLINK_NO_ENOBUFS: int = 5; pub const NETLINK_RX_RING: int = 6; pub const NETLINK_TX_RING: int = 7; pub const NETLINK_LISTEN_ALL_NSID: int = 8; pub const NETLINK_LIST_MEMBERSHIPS: int = 9; pub const NETLINK_CAP_ACK: int = 10; pub const NETLINK_EXT_ACK: int = 11; pub const NL_MMAP_MSG_ALIGNMENT: int = 4; pub const NET_MAJOR: int = 36; ================================================ FILE: netlink-sys/src/lib.rs ================================================ // SPDX-License-Identifier: MIT pub mod constants; pub mod protocols { pub use super::constants::{ NETLINK_AUDIT, NETLINK_CONNECTOR, NETLINK_CRYPTO, NETLINK_DNRTMSG, NETLINK_ECRYPTFS, NETLINK_FIB_LOOKUP, NETLINK_FIREWALL, NETLINK_GENERIC, NETLINK_IP6_FW, NETLINK_ISCSI, NETLINK_KOBJECT_UEVENT, NETLINK_NETFILTER, NETLINK_NFLOG, NETLINK_RDMA, NETLINK_ROUTE, NETLINK_SCSITRANSPORT, NETLINK_SELINUX, NETLINK_SOCK_DIAG, NETLINK_UNUSED, NETLINK_USERSOCK, NETLINK_XFRM, }; } mod socket; pub use self::socket::Socket; mod addr; pub use self::addr::SocketAddr; mod async_socket; pub use self::async_socket::AsyncSocket; pub mod async_socket_ext; pub use self::async_socket_ext::AsyncSocketExt; #[cfg(feature = "tokio_socket")] mod tokio; #[cfg(feature = "tokio_socket")] pub use self::tokio::TokioSocket; #[cfg(feature = "smol_socket")] mod smol; #[cfg(feature = "smol_socket")] pub use self::smol::SmolSocket; #[cfg(feature = "mio_socket")] mod mio; ================================================ FILE: netlink-sys/src/mio.rs ================================================ // SPDX-License-Identifier: MIT use crate::Socket; use std::os::unix::io::AsRawFd; use mio::{event::Source, unix::SourceFd}; impl Source for Socket { fn register( &mut self, registry: &mio::Registry, token: mio::Token, interests: mio::Interest, ) -> std::io::Result<()> { let raw_fd = self.as_raw_fd(); SourceFd(&raw_fd).register(registry, token, interests) } fn reregister( &mut self, registry: &mio::Registry, token: mio::Token, interests: mio::Interest, ) -> std::io::Result<()> { let raw_fd = self.as_raw_fd(); SourceFd(&raw_fd).reregister(registry, token, interests) } fn deregister(&mut self, registry: &mio::Registry) -> std::io::Result<()> { let raw_fd = self.as_raw_fd(); SourceFd(&raw_fd).deregister(registry) } } #[cfg(test)] mod tests { use super::*; fn request_neighbour_dump(socket: &mut Socket) -> std::io::Result<()> { // Buffer generated from: // ``` // let mut neighbour_dump_request = NetlinkMessage { // header: NetlinkHeader { // flags: NLM_F_DUMP | NLM_F_REQUEST, // ..Default::default() // }, // payload: NetlinkPayload::from(RtnlMessage::GetNeighbour(NeighbourMessage::default())), // }; // ``` let buf = [ 28, 0, 0, 0, 30, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; socket.send(&buf[..], 0)?; Ok(()) } #[test] fn test_event_loop() -> Result<(), Box> { use crate::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; use mio::{Events, Interest, Poll, Token}; use std::time::Duration; let mut poll = Poll::new()?; let mut events = Events::with_capacity(128); let mut socket = Socket::new(NETLINK_ROUTE)?; socket.bind_auto()?; socket.connect(&SocketAddr::new(0, 0))?; poll.registry() .register(&mut socket, Token(1), Interest::READABLE)?; // Send neighbour query request_neighbour_dump(&mut socket)?; // Make sure that we got anything poll.poll(&mut events, Some(Duration::from_secs(1)))?; assert!(!events.is_empty()); // Make sure the we didn't get a thing after removing socket from loop poll.registry().deregister(&mut socket)?; poll.poll(&mut events, Some(Duration::from_secs(1)))?; assert!(events.is_empty()); Ok(()) } } ================================================ FILE: netlink-sys/src/smol.rs ================================================ // SPDX-License-Identifier: MIT use std::{ io, os::unix::io::{AsRawFd, FromRawFd, RawFd}, task::{Context, Poll}, }; use async_io::Async; use futures::ready; use log::trace; use crate::{AsyncSocket, Socket, SocketAddr}; /// An I/O object representing a Netlink socket. pub struct SmolSocket(Async); impl FromRawFd for SmolSocket { unsafe fn from_raw_fd(fd: RawFd) -> Self { let socket = Socket::from_raw_fd(fd); socket.set_non_blocking(true).unwrap(); SmolSocket(Async::new(socket).unwrap()) } } impl AsRawFd for SmolSocket { fn as_raw_fd(&self) -> RawFd { self.0.get_ref().as_raw_fd() } } // async_io::Async<..>::{read,write}_with[_mut] functions try IO first, // and only register context if it would block. // replicate this in these poll functions: impl SmolSocket { fn poll_write_with(&mut self, cx: &mut Context<'_>, mut op: F) -> Poll> where F: FnMut(&mut Self) -> io::Result, { loop { match op(self) { Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} res => return Poll::Ready(res), } // try again if writable now, otherwise come back later: ready!(self.0.poll_writable(cx))?; } } fn poll_read_with(&mut self, cx: &mut Context<'_>, mut op: F) -> Poll> where F: FnMut(&mut Self) -> io::Result, { loop { match op(self) { Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} res => return Poll::Ready(res), } // try again if readable now, otherwise come back later: ready!(self.0.poll_readable(cx))?; } } } impl AsyncSocket for SmolSocket { fn socket_ref(&self) -> &Socket { self.0.get_ref() } /// Mutable access to underyling [`Socket`] fn socket_mut(&mut self) -> &mut Socket { self.0.get_mut() } fn new(protocol: isize) -> io::Result { let socket = Socket::new(protocol)?; Ok(Self(Async::new(socket)?)) } fn poll_send(&mut self, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { self.poll_write_with(cx, |this| this.0.get_mut().send(buf, 0)) } fn poll_send_to( &mut self, cx: &mut Context<'_>, buf: &[u8], addr: &SocketAddr, ) -> Poll> { self.poll_write_with(cx, |this| this.0.get_mut().send_to(buf, addr, 0)) } fn poll_recv(&mut self, cx: &mut Context<'_>, buf: &mut B) -> Poll> where B: bytes::BufMut, { self.poll_read_with(cx, |this| this.0.get_mut().recv(buf, 0).map(|_len| ())) } fn poll_recv_from( &mut self, cx: &mut Context<'_>, buf: &mut B, ) -> Poll> where B: bytes::BufMut, { self.poll_read_with(cx, |this| { let x = this.0.get_mut().recv_from(buf, 0); trace!("poll_recv_from: {:?}", x); x.map(|(_len, addr)| addr) }) } fn poll_recv_from_full( &mut self, cx: &mut Context<'_>, ) -> Poll, SocketAddr)>> { self.poll_read_with(cx, |this| this.0.get_mut().recv_from_full()) } } ================================================ FILE: netlink-sys/src/socket.rs ================================================ // SPDX-License-Identifier: MIT use std::{ io::{Error, Result}, mem, os::unix::io::{AsRawFd, FromRawFd, RawFd}, }; use crate::SocketAddr; /// A netlink socket. /// /// # Example /// /// In this example we: /// /// 1. open a new socket /// 2. send a message to the kernel /// 3. read the reponse /// /// ```rust /// use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; /// use std::process; /// /// // open a new socket for the NETLINK_ROUTE subsystem (see "man 7 rtnetlink") /// let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); /// // address of the remote peer we'll send a message to. This particular address is for the kernel /// let kernel_addr = SocketAddr::new(0, 0); /// // this is a valid message for listing the network links on the system /// let pkt = vec![ /// 0x14, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x03, 0xfd, 0xfe, 0x38, 0x5c, 0x00, 0x00, 0x00, /// 0x00, 0x00, 0x00, 0x00, 0x00, /// ]; /// // send the message to the kernel /// let n_sent = socket.send_to(&pkt[..], &kernel_addr, 0).unwrap(); /// assert_eq!(n_sent, pkt.len()); /// // buffer for receiving the response /// let mut buf = vec![0; 4096]; /// loop { /// // receive a datagram /// let (n_received, sender_addr) = socket.recv_from(&mut &mut buf[..], 0).unwrap(); /// assert_eq!(sender_addr, kernel_addr); /// println!("received datagram {:?}", &buf[..n_received]); /// if buf[4] == 2 && buf[5] == 0 { /// println!("the kernel responded with an error"); /// return; /// } /// if buf[4] == 3 && buf[5] == 0 { /// println!("end of dump"); /// return; /// } /// } /// ``` #[derive(Clone, Debug)] pub struct Socket(RawFd); impl AsRawFd for Socket { fn as_raw_fd(&self) -> RawFd { self.0 } } impl FromRawFd for Socket { unsafe fn from_raw_fd(fd: RawFd) -> Self { Socket(fd) } } impl Drop for Socket { fn drop(&mut self) { unsafe { libc::close(self.0) }; } } impl Socket { /// Open a new socket for the given netlink subsystem. `protocol` must be one of the /// [`netlink_sys::protocols`][protos] constants. /// /// [protos]: crate::protocols pub fn new(protocol: isize) -> Result { let res = unsafe { libc::socket( libc::PF_NETLINK, libc::SOCK_DGRAM | libc::SOCK_CLOEXEC, protocol as libc::c_int, ) }; if res < 0 { return Err(Error::last_os_error()); } Ok(Socket(res)) } /// Bind the socket to the given address pub fn bind(&mut self, addr: &SocketAddr) -> Result<()> { let (addr_ptr, addr_len) = addr.as_raw(); let res = unsafe { libc::bind(self.0, addr_ptr, addr_len) }; if res < 0 { return Err(Error::last_os_error()); } Ok(()) } /// Bind the socket to an address assigned by the kernel, and return that address. pub fn bind_auto(&mut self) -> Result { let mut addr = SocketAddr::new(0, 0); self.bind(&addr)?; self.get_address(&mut addr)?; Ok(addr) } /// Get the socket address pub fn get_address(&self, addr: &mut SocketAddr) -> Result<()> { let (addr_ptr, mut addr_len) = addr.as_raw_mut(); let addr_len_copy = addr_len; let addr_len_ptr = &mut addr_len as *mut libc::socklen_t; let res = unsafe { libc::getsockname(self.0, addr_ptr, addr_len_ptr) }; if res < 0 { return Err(Error::last_os_error()); } assert_eq!(addr_len, addr_len_copy); Ok(()) } // when building with --features smol we don't need this #[allow(dead_code)] /// Make this socket non-blocking pub fn set_non_blocking(&self, non_blocking: bool) -> Result<()> { let mut non_blocking = non_blocking as libc::c_int; let res = unsafe { libc::ioctl(self.0, libc::FIONBIO, &mut non_blocking) }; if res < 0 { return Err(Error::last_os_error()); } Ok(()) } /// Connect the socket to the given address. Netlink is a connection-less protocol, so a socket can communicate with /// multiple peers with the [`Socket::send_to`] and [`Socket::recv_from`] methods. However, if the socket only needs /// to communicate with one peer, it is convenient not to have to bother with the peer address. This is what /// `connect` is for. After calling `connect`, [`Socket::send`] and [`Socket::recv`] respectively send and receive /// datagrams to and from `remote_addr`. /// /// # Examples /// /// In this example we: /// /// 1. open a socket /// 2. connect it to the kernel with [`Socket::connect`] /// 3. send a request to the kernel with [`Socket::send`] /// 4. read the response (which can span over several messages) [`Socket::recv`] /// /// ```rust /// use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; /// use std::process; /// /// let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); /// let _ = socket.bind_auto().unwrap(); /// let kernel_addr = SocketAddr::new(0, 0); /// socket.connect(&kernel_addr).unwrap(); /// // This is a valid message for listing the network links on the system /// let msg = vec![ /// 0x14, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x03, 0xfd, 0xfe, 0x38, 0x5c, 0x00, 0x00, 0x00, /// 0x00, 0x00, 0x00, 0x00, 0x00, /// ]; /// let n_sent = socket.send(&msg[..], 0).unwrap(); /// assert_eq!(n_sent, msg.len()); /// // buffer for receiving the response /// let mut buf = vec![0; 4096]; /// loop { /// let mut n_received = socket.recv(&mut &mut buf[..], 0).unwrap(); /// println!("received {:?}", &buf[..n_received]); /// if buf[4] == 2 && buf[5] == 0 { /// println!("the kernel responded with an error"); /// return; /// } /// if buf[4] == 3 && buf[5] == 0 { /// println!("end of dump"); /// return; /// } /// } /// ``` pub fn connect(&self, remote_addr: &SocketAddr) -> Result<()> { // FIXME: // // Event though for SOCK_DGRAM sockets there's no IO, if our socket is non-blocking, // connect() might return EINPROGRESS. In theory, the right way to treat EINPROGRESS would // be to ignore the error, and let the user poll the socket to check when it becomes // writable, indicating that the connection succeeded. The code already exists in mio for // TcpStream: // // > pub fn connect(stream: net::TcpStream, addr: &SocketAddr) -> io::Result { // > set_non_block(stream.as_raw_fd())?; // > match stream.connect(addr) { // > Ok(..) => {} // > Err(ref e) if e.raw_os_error() == Some(libc::EINPROGRESS) => {} // > Err(e) => return Err(e), // > } // > Ok(TcpStream { inner: stream }) // > } // // In practice, since the connection does not require any IO for SOCK_DGRAM sockets, it // almost never returns EINPROGRESS and so for now, we just return whatever libc::connect // returns. If it returns EINPROGRESS, the caller will have to handle the error themself // // Refs: // // - https://stackoverflow.com/a/14046386/1836144 // - https://lists.isc.org/pipermail/bind-users/2009-August/077527.html let (addr, addr_len) = remote_addr.as_raw(); let res = unsafe { libc::connect(self.0, addr, addr_len) }; if res < 0 { return Err(Error::last_os_error()); } Ok(()) } // Most of the comments in this method come from a discussion on rust users forum. // [thread]: https://users.rust-lang.org/t/help-understanding-libc-call/17308/9 // /// Read a datagram from the socket and return the number of bytes that have been read and the address of the /// sender. The data being read is copied into `buf`. If `buf` is too small, the datagram is truncated. The /// supported flags are the `MSG_*` described in `man 2 recvmsg` /// /// # Warning /// /// In datagram oriented protocols, `recv` and `recvfrom` receive normally only ONE datagram, but this seems not to /// be always true for netlink sockets: with some protocols like `NETLINK_AUDIT`, multiple netlink packets can be /// read with a single call. pub fn recv_from(&self, buf: &mut B, flags: libc::c_int) -> Result<(usize, SocketAddr)> where B: bytes::BufMut, { // Create an empty storage for the address. Note that Rust standard library create a // sockaddr_storage so that it works for any address family, but here, we already know that // we'll have a Netlink address, so we can create the appropriate storage. let mut addr = unsafe { mem::zeroed::() }; // recvfrom takes a *sockaddr as parameter so that it can accept any kind of address // storage, so we need to create such a pointer for the sockaddr_nl we just initialized. // // Create a raw pointer to Cast our raw pointer to a // our storage. We cannot generic pointer to *sockaddr // pass it to recvfrom yet. that recvfrom can use // ^ ^ // | | // +--------------+---------------+ +---------+--------+ // / \ / \ let addr_ptr = &mut addr as *mut libc::sockaddr_nl as *mut libc::sockaddr; // Why do we need to pass the address length? We're passing a generic *sockaddr to // recvfrom. Somehow recvfrom needs to make sure that the address of the received packet // would fit into the actual type that is behind *sockaddr: it could be a sockaddr_nl but // also a sockaddr_in, a sockaddr_in6, or even the generic sockaddr_storage that can store // any address. let mut addrlen = mem::size_of_val(&addr); // recvfrom does not take the address length by value (see [thread]), so we need to create // a pointer to it. let addrlen_ptr = &mut addrlen as *mut usize as *mut libc::socklen_t; let chunk = buf.chunk_mut(); // Cast the *mut u8 into *mut void. // This is equivalent to casting a *char into *void // See [thread] // ^ // Create a *mut u8 | // ^ | // | | // +------+-------+ +--------+-------+ // / \ / \ let buf_ptr = chunk.as_mut_ptr() as *mut libc::c_void; let buf_len = chunk.len() as libc::size_t; let res = unsafe { libc::recvfrom(self.0, buf_ptr, buf_len, flags, addr_ptr, addrlen_ptr) }; if res < 0 { return Err(Error::last_os_error()); } else { // with `MSG_TRUNC` `res` might exceed `buf_len` let written = std::cmp::min(buf_len, res as usize); unsafe { buf.advance_mut(written); } } Ok((res as usize, SocketAddr(addr))) } /// For a connected socket, `recv` reads a datagram from the socket. The sender is the remote peer the socket is /// connected to (see [`Socket::connect`]). See also [`Socket::recv_from`] pub fn recv(&self, buf: &mut B, flags: libc::c_int) -> Result where B: bytes::BufMut, { let chunk = buf.chunk_mut(); let buf_ptr = chunk.as_mut_ptr() as *mut libc::c_void; let buf_len = chunk.len() as libc::size_t; let res = unsafe { libc::recv(self.0, buf_ptr, buf_len, flags) }; if res < 0 { return Err(Error::last_os_error()); } else { // with `MSG_TRUNC` `res` might exceed `buf_len` let written = std::cmp::min(buf_len, res as usize); unsafe { buf.advance_mut(written); } } Ok(res as usize) } /// Receive a full message. Unlike [`Socket::recv_from`], which truncates messages that exceed the length of the /// buffer passed as argument, this method always reads a whole message, no matter its size. pub fn recv_from_full(&self) -> Result<(Vec, SocketAddr)> { // Peek let mut buf: Vec = Vec::new(); let (peek_len, _) = self.recv_from(&mut buf, libc::MSG_PEEK | libc::MSG_TRUNC)?; // Receive buf.clear(); buf.reserve(peek_len); let (rlen, addr) = self.recv_from(&mut buf, 0)?; assert_eq!(rlen, peek_len); Ok((buf, addr)) } /// Send the given buffer `buf` to the remote peer with address `addr`. The supported flags are the `MSG_*` values /// documented in `man 2 send`. pub fn send_to(&self, buf: &[u8], addr: &SocketAddr, flags: libc::c_int) -> Result { let (addr_ptr, addr_len) = addr.as_raw(); let buf_ptr = buf.as_ptr() as *const libc::c_void; let buf_len = buf.len() as libc::size_t; let res = unsafe { libc::sendto(self.0, buf_ptr, buf_len, flags, addr_ptr, addr_len) }; if res < 0 { return Err(Error::last_os_error()); } Ok(res as usize) } /// For a connected socket, `send` sends the given buffer `buf` to the remote peer the socket is connected to. See /// also [`Socket::connect`] and [`Socket::send_to`]. pub fn send(&self, buf: &[u8], flags: libc::c_int) -> Result { let buf_ptr = buf.as_ptr() as *const libc::c_void; let buf_len = buf.len() as libc::size_t; let res = unsafe { libc::send(self.0, buf_ptr, buf_len, flags) }; if res < 0 { return Err(Error::last_os_error()); } Ok(res as usize) } pub fn set_pktinfo(&mut self, value: bool) -> Result<()> { let value: libc::c_int = if value { 1 } else { 0 }; setsockopt(self.0, libc::SOL_NETLINK, libc::NETLINK_PKTINFO, value) } pub fn get_pktinfo(&self) -> Result { let res = getsockopt::(self.0, libc::SOL_NETLINK, libc::NETLINK_PKTINFO)?; Ok(res == 1) } pub fn add_membership(&mut self, group: u32) -> Result<()> { setsockopt( self.0, libc::SOL_NETLINK, libc::NETLINK_ADD_MEMBERSHIP, group, ) } pub fn drop_membership(&mut self, group: u32) -> Result<()> { setsockopt( self.0, libc::SOL_NETLINK, libc::NETLINK_DROP_MEMBERSHIP, group, ) } // pub fn list_membership(&self) -> Vec { // unimplemented!(); // // getsockopt won't be enough here, because we may need to perform 2 calls, and because the // // length of the list returned by libc::getsockopt is returned by mutating the length // // argument, which our implementation of getsockopt forbids. // } /// `NETLINK_BROADCAST_ERROR` (since Linux 2.6.30). When not set, `netlink_broadcast()` only /// reports `ESRCH` errors and silently ignore `NOBUFS` errors. pub fn set_broadcast_error(&mut self, value: bool) -> Result<()> { let value: libc::c_int = if value { 1 } else { 0 }; setsockopt( self.0, libc::SOL_NETLINK, libc::NETLINK_BROADCAST_ERROR, value, ) } pub fn get_broadcast_error(&self) -> Result { let res = getsockopt::(self.0, libc::SOL_NETLINK, libc::NETLINK_BROADCAST_ERROR)?; Ok(res == 1) } /// `NETLINK_NO_ENOBUFS` (since Linux 2.6.30). This flag can be used by unicast and broadcast /// listeners to avoid receiving `ENOBUFS` errors. pub fn set_no_enobufs(&mut self, value: bool) -> Result<()> { let value: libc::c_int = if value { 1 } else { 0 }; setsockopt(self.0, libc::SOL_NETLINK, libc::NETLINK_NO_ENOBUFS, value) } pub fn get_no_enobufs(&self) -> Result { let res = getsockopt::(self.0, libc::SOL_NETLINK, libc::NETLINK_NO_ENOBUFS)?; Ok(res == 1) } /// `NETLINK_LISTEN_ALL_NSID` (since Linux 4.2). When set, this socket will receive netlink /// notifications from all network namespaces that have an nsid assigned into the network /// namespace where the socket has been opened. The nsid is sent to user space via an ancillary /// data. pub fn set_listen_all_namespaces(&mut self, value: bool) -> Result<()> { let value: libc::c_int = if value { 1 } else { 0 }; setsockopt( self.0, libc::SOL_NETLINK, libc::NETLINK_LISTEN_ALL_NSID, value, ) } pub fn get_listen_all_namespaces(&self) -> Result { let res = getsockopt::(self.0, libc::SOL_NETLINK, libc::NETLINK_LISTEN_ALL_NSID)?; Ok(res == 1) } /// `NETLINK_CAP_ACK` (since Linux 4.2). The kernel may fail to allocate the necessary room /// for the acknowledgment message back to user space. This option trims off the payload of /// the original netlink message. The netlink message header is still included, so the user can /// guess from the sequence number which message triggered the acknowledgment. pub fn set_cap_ack(&mut self, value: bool) -> Result<()> { let value: libc::c_int = if value { 1 } else { 0 }; setsockopt(self.0, libc::SOL_NETLINK, libc::NETLINK_CAP_ACK, value) } pub fn get_cap_ack(&self) -> Result { let res = getsockopt::(self.0, libc::SOL_NETLINK, libc::NETLINK_CAP_ACK)?; Ok(res == 1) } } /// Wrapper around `getsockopt`: /// /// ```no_rust /// int getsockopt(int socket, int level, int option_name, void *restrict option_value, socklen_t *restrict option_len); /// ``` pub(crate) fn getsockopt(fd: RawFd, level: libc::c_int, option: libc::c_int) -> Result { // Create storage for the options we're fetching let mut slot: T = unsafe { mem::zeroed() }; // Create a mutable raw pointer to the storage so that getsockopt can fill the value let slot_ptr = &mut slot as *mut T as *mut libc::c_void; // Let getsockopt know how big our storage is let mut slot_len = mem::size_of::() as libc::socklen_t; // getsockopt takes a mutable pointer to the length, because for some options like // NETLINK_LIST_MEMBERSHIP where the option value is a list with arbitrary length, // getsockopt uses this parameter to signal how big the storage needs to be. let slot_len_ptr = &mut slot_len as *mut libc::socklen_t; let res = unsafe { libc::getsockopt(fd, level, option, slot_ptr, slot_len_ptr) }; if res < 0 { return Err(Error::last_os_error()); } // Ignore the options that require the legnth to be set by getsockopt. // We'll deal with them individually. assert_eq!(slot_len as usize, mem::size_of::()); Ok(slot) } // adapted from rust standard library fn setsockopt(fd: RawFd, level: libc::c_int, option: libc::c_int, payload: T) -> Result<()> { let payload = &payload as *const T as *const libc::c_void; let payload_len = mem::size_of::() as libc::socklen_t; let res = unsafe { libc::setsockopt(fd, level, option, payload, payload_len) }; if res < 0 { return Err(Error::last_os_error()); } Ok(()) } #[cfg(test)] mod test { use super::*; use crate::protocols::NETLINK_ROUTE; #[test] fn new() { Socket::new(NETLINK_ROUTE).unwrap(); } #[test] fn connect() { let sock = Socket::new(NETLINK_ROUTE).unwrap(); sock.connect(&SocketAddr::new(0, 0)).unwrap(); } #[test] fn bind() { let mut sock = Socket::new(NETLINK_ROUTE).unwrap(); sock.bind(&SocketAddr::new(4321, 0)).unwrap(); } #[test] fn bind_auto() { let mut sock = Socket::new(NETLINK_ROUTE).unwrap(); let addr = sock.bind_auto().unwrap(); // make sure that the address we got from the kernel is there assert!(addr.port_number() != 0); } #[test] fn set_non_blocking() { let sock = Socket::new(NETLINK_ROUTE).unwrap(); sock.set_non_blocking(true).unwrap(); sock.set_non_blocking(false).unwrap(); } #[test] fn options() { let mut sock = Socket::new(NETLINK_ROUTE).unwrap(); sock.set_cap_ack(true).unwrap(); assert!(sock.get_cap_ack().unwrap()); sock.set_cap_ack(false).unwrap(); assert!(!sock.get_cap_ack().unwrap()); sock.set_no_enobufs(true).unwrap(); assert!(sock.get_no_enobufs().unwrap()); sock.set_no_enobufs(false).unwrap(); assert!(!sock.get_no_enobufs().unwrap()); sock.set_broadcast_error(true).unwrap(); assert!(sock.get_broadcast_error().unwrap()); sock.set_broadcast_error(false).unwrap(); assert!(!sock.get_broadcast_error().unwrap()); // FIXME: these require root permissions // sock.set_listen_all_namespaces(true).unwrap(); // assert!(sock.get_listen_all_namespaces().unwrap()); // sock.set_listen_all_namespaces(false).unwrap(); // assert!(!sock.get_listen_all_namespaces().unwrap()); } } ================================================ FILE: netlink-sys/src/tokio.rs ================================================ // SPDX-License-Identifier: MIT use std::{ io, os::unix::io::{AsRawFd, FromRawFd, RawFd}, task::{Context, Poll}, }; use futures::ready; use log::trace; use tokio::io::unix::AsyncFd; use crate::{AsyncSocket, Socket, SocketAddr}; /// An I/O object representing a Netlink socket. pub struct TokioSocket(AsyncFd); impl FromRawFd for TokioSocket { unsafe fn from_raw_fd(fd: RawFd) -> Self { let socket = Socket::from_raw_fd(fd); socket.set_non_blocking(true).unwrap(); TokioSocket(AsyncFd::new(socket).unwrap()) } } impl AsRawFd for TokioSocket { fn as_raw_fd(&self) -> RawFd { self.0.get_ref().as_raw_fd() } } impl AsyncSocket for TokioSocket { fn socket_ref(&self) -> &Socket { self.0.get_ref() } /// Mutable access to underyling [`Socket`] fn socket_mut(&mut self) -> &mut Socket { self.0.get_mut() } fn new(protocol: isize) -> io::Result { let socket = Socket::new(protocol)?; socket.set_non_blocking(true)?; Ok(Self(AsyncFd::new(socket)?)) } fn poll_send(&mut self, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { loop { // Check if the socket it writable. If // AsyncFd::poll_write_ready returns NotReady, it will // already have arranged for the current task to be // notified when the socket becomes writable, so we can // just return Pending let mut guard = ready!(self.0.poll_write_ready(cx))?; match guard.try_io(|inner| inner.get_ref().send(buf, 0)) { Ok(x) => return Poll::Ready(x), Err(_would_block) => continue, } } } fn poll_send_to( &mut self, cx: &mut Context<'_>, buf: &[u8], addr: &SocketAddr, ) -> Poll> { loop { let mut guard = ready!(self.0.poll_write_ready(cx))?; match guard.try_io(|inner| inner.get_ref().send_to(buf, addr, 0)) { Ok(x) => return Poll::Ready(x), Err(_would_block) => continue, } } } fn poll_recv(&mut self, cx: &mut Context<'_>, buf: &mut B) -> Poll> where B: bytes::BufMut, { loop { // Check if the socket is readable. If not, // AsyncFd::poll_read_ready would have arranged for the // current task to be polled again when the socket becomes // readable, so we can just return Pending let mut guard = ready!(self.0.poll_read_ready(cx))?; match guard.try_io(|inner| inner.get_ref().recv(buf, 0)) { Ok(x) => return Poll::Ready(x.map(|_len| ())), Err(_would_block) => continue, } } } fn poll_recv_from( &mut self, cx: &mut Context<'_>, buf: &mut B, ) -> Poll> where B: bytes::BufMut, { loop { trace!("poll_recv_from called"); let mut guard = ready!(self.0.poll_read_ready(cx))?; trace!("poll_recv_from socket is ready for reading"); match guard.try_io(|inner| inner.get_ref().recv_from(buf, 0)) { Ok(x) => { trace!("poll_recv_from {:?} bytes read", x); return Poll::Ready(x.map(|(_len, addr)| addr)); } Err(_would_block) => { trace!("poll_recv_from socket would block"); continue; } } } } fn poll_recv_from_full( &mut self, cx: &mut Context<'_>, ) -> Poll, SocketAddr)>> { loop { trace!("poll_recv_from_full called"); let mut guard = ready!(self.0.poll_read_ready(cx))?; trace!("poll_recv_from_full socket is ready for reading"); match guard.try_io(|inner| inner.get_ref().recv_from_full()) { Ok(x) => { trace!("poll_recv_from_full {:?} bytes read", x); return Poll::Ready(x); } Err(_would_block) => { trace!("poll_recv_from_full socket would block"); continue; } } } } } ================================================ FILE: rtnetlink/Cargo.toml ================================================ [package] name = "rtnetlink" version = "0.11.0" authors = ["Corentin Henry "] edition = "2018" homepage = "https://github.com/little-dude/netlink" keywords = ["netlink", "ip", "linux"] license = "MIT" readme = "../README.md" repository = "https://github.com/little-dude/netlink" description = "manipulate linux networking resources via netlink" [features] test_as_root = [] default = ["tokio_socket"] tokio_socket = ["netlink-proto/tokio_socket", "tokio"] smol_socket = ["netlink-proto/smol_socket", "async-global-executor"] [dependencies] futures = "0.3.11" log = "0.4.8" thiserror = "1" netlink-packet-route = { version = "0.13.0", path = "../netlink-packet-route" } netlink-proto = { default-features = false, version = "0.10", path = "../netlink-proto" } nix = { version = "0.24.1" , default-features = false, features = ["fs", "mount", "sched", "signal"] } tokio = { version = "1.0.1", features = ["rt"], optional = true} async-global-executor = { version = "2.0.2", optional = true } [dev-dependencies] env_logger = "0.8.2" ipnetwork = "0.18.0" tokio = { version = "1.0.1", features = ["macros", "rt", "rt-multi-thread"] } async-std = { version = "1.9.0", features = ["attributes"]} ================================================ FILE: rtnetlink/examples/add_address.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use std::env; use ipnetwork::IpNetwork; use rtnetlink::{new_connection, Error, Handle}; #[tokio::main] async fn main() -> Result<(), ()> { let args: Vec = env::args().collect(); if args.len() != 3 { usage(); return Ok(()); } let link_name = &args[1]; let ip: IpNetwork = args[2].parse().unwrap_or_else(|_| { eprintln!("invalid address"); std::process::exit(1); }); let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); if let Err(e) = add_address(link_name, ip, handle.clone()).await { eprintln!("{}", e); } Ok(()) } async fn add_address(link_name: &str, ip: IpNetwork, handle: Handle) -> Result<(), Error> { let mut links = handle .link() .get() .match_name(link_name.to_string()) .execute(); if let Some(link) = links.try_next().await? { handle .address() .add(link.header.index, ip.ip(), ip.prefix()) .execute() .await? } Ok(()) } fn usage() { eprintln!( "usage: cargo run --example add_address -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd rtnetlink ; cargo build --example add_address Then find the binary in the target directory: cd ../target/debug/example ; sudo ./add_address " ); } ================================================ FILE: rtnetlink/examples/add_neighbour.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{new_connection, Error, Handle}; use std::{env, net::IpAddr}; #[tokio::main] async fn main() -> Result<(), ()> { let args: Vec = env::args().collect(); if args.len() != 3 { usage(); return Ok(()); } let link_name = &args[1]; let ip: IpAddr = args[2].parse().unwrap_or_else(|_| { eprintln!("invalid IP address"); std::process::exit(1); }); let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); if let Err(e) = add_neighbour(link_name, ip, handle.clone()).await { eprintln!("{}", e); } Ok(()) } async fn add_neighbour(link_name: &str, ip: IpAddr, handle: Handle) -> Result<(), Error> { let mut links = handle .link() .get() .match_name(link_name.to_string()) .execute(); if let Some(link) = links.try_next().await? { handle .neighbours() .add(link.header.index, ip) .execute() .await?; println!("Done"); } Ok(()) } fn usage() { eprintln!( "usage: cargo run --example add_neighbour -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd rtnetlink ; cargo build --example add_neighbour Then find the binary in the target directory: cd ../target/debug/example ; sudo ./add_neighbour " ); } ================================================ FILE: rtnetlink/examples/add_netns.rs ================================================ // SPDX-License-Identifier: MIT use rtnetlink::NetworkNamespace; use std::env; #[tokio::main] async fn main() -> Result<(), String> { env_logger::init(); let args: Vec = env::args().collect(); if args.len() != 2 { usage(); return Ok(()); } let ns_name = &args[1]; NetworkNamespace::add(ns_name.to_string()) .await .map_err(|e| format!("{}", e)) } fn usage() { eprintln!( "usage: cargo run --example add_netns -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd netlink-ip ; cargo build --example add_netns Then find the binary in the target directory: cd ../target/debug/example ; sudo ./add_netns " ); } ================================================ FILE: rtnetlink/examples/add_netns_async.rs ================================================ // SPDX-License-Identifier: MIT use rtnetlink::NetworkNamespace; use std::env; #[async_std::main] async fn main() -> Result<(), String> { env_logger::init(); let args: Vec = env::args().collect(); if args.len() != 2 { usage(); return Ok(()); } let ns_name = &args[1]; NetworkNamespace::add(ns_name.to_string()) .await .map_err(|e| format!("{}", e)) } fn usage() { eprintln!( "usage: cargo run --example add_netns -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd netlink-ip ; cargo build --example add_netns Then find the binary in the target directory: cd ../target/debug/example ; sudo ./add_netns " ); } ================================================ FILE: rtnetlink/examples/add_route.rs ================================================ // SPDX-License-Identifier: MIT use std::env; use ipnetwork::Ipv4Network; use rtnetlink::{new_connection, Error, Handle}; #[tokio::main] async fn main() -> Result<(), ()> { let args: Vec = env::args().collect(); if args.len() != 3 { usage(); return Ok(()); } let dest: Ipv4Network = args[1].parse().unwrap_or_else(|_| { eprintln!("invalid destination"); std::process::exit(1); }); let gateway: Ipv4Network = args[2].parse().unwrap_or_else(|_| { eprintln!("invalid gateway"); std::process::exit(1); }); let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); if let Err(e) = add_route(&dest, &gateway, handle.clone()).await { eprintln!("{}", e); } Ok(()) } async fn add_route(dest: &Ipv4Network, gateway: &Ipv4Network, handle: Handle) -> Result<(), Error> { let route = handle.route(); route .add() .v4() .destination_prefix(dest.ip(), dest.prefix()) .gateway(gateway.ip()) .execute() .await?; Ok(()) } fn usage() { eprintln!( "usage: cargo run --example add_route -- / Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd rtnetlink ; cargo build --example add_route Then find the binary in the target directory: cd ../target/debug/example ; sudo ./add_route / " ); } ================================================ FILE: rtnetlink/examples/add_route_pref_src.rs ================================================ // SPDX-License-Identifier: MIT use futures::TryStreamExt; use std::{env, net::Ipv4Addr}; use ipnetwork::Ipv4Network; use rtnetlink::{new_connection, Error, Handle}; #[tokio::main] async fn main() -> Result<(), ()> { let args: Vec = env::args().collect(); if args.len() != 4 { usage(); return Ok(()); } let dest: Ipv4Network = args[1].parse().unwrap_or_else(|_| { eprintln!("invalid destination"); std::process::exit(1); }); let iface: String = args[2].parse().unwrap_or_else(|_| { eprintln!("invalid interface"); std::process::exit(1); }); let source: Ipv4Addr = args[3].parse().unwrap_or_else(|_| { eprintln!("invalid source"); std::process::exit(1); }); let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); if let Err(e) = add_route(&dest, iface, source, handle.clone()).await { eprintln!("{}", e); } Ok(()) } async fn add_route( dest: &Ipv4Network, iface: String, source: Ipv4Addr, handle: Handle, ) -> Result<(), Error> { let iface_idx = handle .link() .get() .match_name(iface) .execute() .try_next() .await? .unwrap() .header .index; let route = handle.route(); route .add() .v4() .destination_prefix(dest.ip(), dest.prefix()) .output_interface(iface_idx) .pref_source(source) .execute() .await?; Ok(()) } fn usage() { eprintln!( "usage: cargo run --example add_route_pref_src -- / Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd rtnetlink ; cargo build --example add_route_pref_src Then find the binary in the target directory: cd ../target/debug/example ; sudo ./add_route_pref_src / " ); } ================================================ FILE: rtnetlink/examples/add_rule.rs ================================================ // SPDX-License-Identifier: MIT use std::env; use ipnetwork::Ipv4Network; use rtnetlink::{new_connection, Error, Handle}; #[tokio::main] async fn main() -> Result<(), ()> { let args: Vec = env::args().collect(); if args.len() != 2 { usage(); return Ok(()); } let dest: Ipv4Network = args[1].parse().unwrap_or_else(|_| { eprintln!("invalid destination"); std::process::exit(1); }); let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); if let Err(e) = add_rule(&dest, handle.clone()).await { eprintln!("{}", e); } Ok(()) } async fn add_rule(dest: &Ipv4Network, handle: Handle) -> Result<(), Error> { let rule = handle.rule(); rule.add() .v4() .destination_prefix(dest.ip(), dest.prefix()) .execute() .await?; Ok(()) } fn usage() { eprintln!( "usage: cargo run --example add_rule -- / Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd rtnetlink ; cargo build --example add_rule Then find the binary in the target directory: cd ../target/debug/example ; sudo ./add_rule / " ); } ================================================ FILE: rtnetlink/examples/add_tc_qdisc_ingress.rs ================================================ // SPDX-License-Identifier: MIT use std::env; use rtnetlink::new_connection; #[tokio::main] async fn main() -> Result<(), ()> { env_logger::init(); let args: Vec = env::args().collect(); if args.len() != 2 { usage(); return Ok(()); } let index: u32 = args[1].parse().unwrap_or_else(|_| { eprintln!("invalid index"); std::process::exit(1); }); let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); if let Err(e) = handle.qdisc().add(index as i32).ingress().execute().await { eprintln!("{}", e); } Ok(()) } fn usage() { eprintln!( "usage: cargo run --example add_tc_qdisc_ingress -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd rtnetlink ; cargo build --example add_tc_qdisc_ingress Then find the binary in the target directory: cd ../target/debug/example ; sudo ./add_tc_qdisc_ingress " ); } ================================================ FILE: rtnetlink/examples/create_bond.rs ================================================ // SPDX-License-Identifier: MIT use rtnetlink::new_connection; use std::net::{Ipv4Addr, Ipv6Addr}; #[tokio::main] async fn main() -> Result<(), String> { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); handle .link() .add() .bond("my-bond".into()) .mode(1) .miimon(100) .updelay(100) .downdelay(100) .min_links(2) .arp_ip_target(vec![Ipv4Addr::new(6, 6, 7, 7), Ipv4Addr::new(8, 8, 9, 10)]) .ns_ip6_target(vec![ Ipv6Addr::new(0xfd01, 0, 0, 0, 0, 0, 0, 1), Ipv6Addr::new(0xfd02, 0, 0, 0, 0, 0, 0, 2), ]) .up() .execute() .await .map_err(|e| format!("{}", e)) } ================================================ FILE: rtnetlink/examples/create_bridge.rs ================================================ // SPDX-License-Identifier: MIT use rtnetlink::new_connection; #[tokio::main] async fn main() -> Result<(), String> { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); handle .link() .add() .bridge("my-bridge-1".into()) .execute() .await .map_err(|e| format!("{}", e)) } ================================================ FILE: rtnetlink/examples/create_macvlan.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{new_connection, Error, Handle}; use std::env; #[tokio::main] async fn main() -> Result<(), String> { let args: Vec = env::args().collect(); if args.len() != 2 { usage(); return Ok(()); } let link_name = &args[1]; let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); create_macvlan(handle, link_name.to_string()) .await .map_err(|e| format!("{}", e)) } async fn create_macvlan(handle: Handle, veth_name: String) -> Result<(), Error> { let mut links = handle.link().get().match_name(veth_name.clone()).execute(); if let Some(link) = links.try_next().await? { // hard code mode: 4u32 i.e bridge mode let request = handle .link() .add() .macvlan("test_macvlan".into(), link.header.index, 4u32); request.execute().await? } else { println!("no link link {} found", veth_name); } Ok(()) } fn usage() { eprintln!( "usage: cargo run --example create_macvlan -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd netlink-ip ; cargo build --example create_macvlan Then find the binary in the target directory: cd ../target/debug/example ; sudo ./create_macvlan " ); } ================================================ FILE: rtnetlink/examples/create_macvtap.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{new_connection, Error, Handle}; use std::env; #[tokio::main] async fn main() -> Result<(), String> { let args: Vec = env::args().collect(); if args.len() != 2 { usage(); return Ok(()); } let link_name = &args[1]; let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); create_macvtap(handle, link_name.to_string()) .await .map_err(|e| format!("{}", e)) } async fn create_macvtap(handle: Handle, veth_name: String) -> Result<(), Error> { let mut links = handle.link().get().match_name(veth_name.clone()).execute(); if let Some(link) = links.try_next().await? { // hard code mode: 4u32 i.e bridge mode let request = handle .link() .add() .macvtap("test_macvtap".into(), link.header.index, 4u32); request.execute().await? } else { println!("no link link {} found", veth_name); } Ok(()) } fn usage() { eprintln!( "usage: cargo run --example create_macvtap -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd rtnetlink; cargo build --example create_macvtap Then find the binary in the target directory: cd ../target/debug/example ; sudo ./create_macvtap " ); } ================================================ FILE: rtnetlink/examples/create_veth.rs ================================================ // SPDX-License-Identifier: MIT use rtnetlink::new_connection; #[tokio::main] async fn main() -> Result<(), String> { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); handle .link() .add() .veth("veth-rs-1".into(), "veth-rs-2".into()) .execute() .await .map_err(|e| format!("{}", e)) } ================================================ FILE: rtnetlink/examples/create_vxlan.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{new_connection, Error, Handle}; use std::env; #[tokio::main] async fn main() -> Result<(), String> { let args: Vec = env::args().collect(); if args.len() != 2 { usage(); return Ok(()); } let link_name = &args[1]; let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); create_vxlan(handle, link_name.to_string()) .await .map_err(|e| format!("{}", e)) } async fn create_vxlan(handle: Handle, name: String) -> Result<(), Error> { let mut links = handle.link().get().match_name(name.clone()).execute(); if let Some(link) = links.try_next().await? { handle .link() .add() .vxlan("vxlan0".into(), 10u32) .link(link.header.index) .port(4789) .up() .execute() .await? } else { println!("no link link {} found", name); } Ok(()) } fn usage() { eprintln!( "usage: cargo run --example create_vxlan -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd netlink-ip ; cargo build --example create_vxlan Then find the binary in the target directory: cd ../target/debug/example ; sudo ./create_vxlan " ); } ================================================ FILE: rtnetlink/examples/del_link.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{new_connection, Error, Handle}; use std::env; #[tokio::main] async fn main() -> Result<(), ()> { let args: Vec = env::args().collect(); if args.len() != 2 { usage(); return Ok(()); } let link_name = &args[1]; let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); if let Err(e) = del_link(handle, link_name.to_string()).await { eprintln!("{}", e); } Ok(()) } async fn del_link(handle: Handle, name: String) -> Result<(), Error> { let mut links = handle.link().get().match_name(name.clone()).execute(); if let Some(link) = links.try_next().await? { handle.link().del(link.header.index).execute().await } else { eprintln!("link {} not found", name); Ok(()) } } fn usage() { eprintln!( "usage: cargo run --example del_link -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd rtnetlink ; cargo build --example del_link Then find the binary in the target directory: cd ../target/debug/example ; sudo ./del_link " ); } ================================================ FILE: rtnetlink/examples/del_netns.rs ================================================ // SPDX-License-Identifier: MIT use rtnetlink::NetworkNamespace; use std::env; #[tokio::main] async fn main() -> Result<(), String> { let args: Vec = env::args().collect(); if args.len() != 2 { usage(); return Ok(()); } let ns_name = &args[1]; NetworkNamespace::del(ns_name.to_string()) .await .map_err(|e| format!("{}", e)) } fn usage() { eprintln!( "usage: cargo run --example del_netns -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd netlink-ip ; cargo build --example del_netns Then find the binary in the target directory: cd ../target/debug/example ; sudo ./del_netns " ); } ================================================ FILE: rtnetlink/examples/del_netns_async.rs ================================================ // SPDX-License-Identifier: MIT use rtnetlink::NetworkNamespace; use std::env; #[async_std::main] async fn main() -> Result<(), String> { let args: Vec = env::args().collect(); if args.len() != 2 { usage(); return Ok(()); } let ns_name = &args[1]; NetworkNamespace::del(ns_name.to_string()) .await .map_err(|e| format!("{}", e)) } fn usage() { eprintln!( "usage: cargo run --example del_netns -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd netlink-ip ; cargo build --example del_netns Then find the binary in the target directory: cd ../target/debug/example ; sudo ./del_netns " ); } ================================================ FILE: rtnetlink/examples/flush_addresses.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{new_connection, Error, Handle}; use std::env; #[tokio::main] async fn main() -> Result<(), ()> { let args: Vec = env::args().collect(); if args.len() != 2 { usage(); return Ok(()); } let link_name = &args[1]; let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); if let Err(e) = flush_addresses(handle, link_name.to_string()).await { eprintln!("{}", e); } Ok(()) } async fn flush_addresses(handle: Handle, link: String) -> Result<(), Error> { let mut links = handle.link().get().match_name(link.clone()).execute(); if let Some(link) = links.try_next().await? { // We should have received only one message assert!(links.try_next().await?.is_none()); let mut addresses = handle .address() .get() .set_link_index_filter(link.header.index) .execute(); while let Some(addr) = addresses.try_next().await? { handle.address().del(addr).execute().await?; } Ok(()) } else { eprintln!("link {} not found", link); Ok(()) } } fn usage() { eprintln!( "usage: cargo run --example flush_addresses -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd rtnetlink ; cargo build --example flush_addresses Then find the binary in the target directory: cd ../target/debug/example ; sudo ./flush_addresses " ); } ================================================ FILE: rtnetlink/examples/get_address.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{new_connection, Error, Handle}; #[tokio::main] async fn main() -> Result<(), ()> { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); let link = "lo".to_string(); println!("dumping address for link \"{}\"", link); if let Err(e) = dump_addresses(handle, link).await { eprintln!("{}", e); } Ok(()) } async fn dump_addresses(handle: Handle, link: String) -> Result<(), Error> { let mut links = handle.link().get().match_name(link.clone()).execute(); if let Some(link) = links.try_next().await? { let mut addresses = handle .address() .get() .set_link_index_filter(link.header.index) .execute(); while let Some(msg) = addresses.try_next().await? { println!("{:?}", msg); } Ok(()) } else { eprintln!("link {} not found", link); Ok(()) } } ================================================ FILE: rtnetlink/examples/get_links.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{ new_connection, packet::rtnl::{ constants::{AF_BRIDGE, RTEXT_FILTER_BRVLAN}, link::nlas::Nla, }, Error, Handle, }; #[tokio::main] async fn main() -> Result<(), ()> { env_logger::init(); let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); // Fetch a link by its index let index = 1; println!("*** retrieving link with index {} ***", index); if let Err(e) = get_link_by_index(handle.clone(), index).await { eprintln!("{}", e); } // Fetch a link by its name let name = "lo"; println!("*** retrieving link named \"{}\" ***", name); if let Err(e) = get_link_by_name(handle.clone(), name.to_string()).await { eprintln!("{}", e); } // Dump all the links and print their index and name println!("*** dumping links ***"); if let Err(e) = dump_links(handle.clone()).await { eprintln!("{}", e); } // Dump all the bridge vlan information if let Err(e) = dump_bridge_filter_info(handle.clone()).await { eprintln!("{}", e); } Ok(()) } async fn get_link_by_index(handle: Handle, index: u32) -> Result<(), Error> { let mut links = handle.link().get().match_index(index).execute(); let msg = if let Some(msg) = links.try_next().await? { msg } else { eprintln!("no link with index {} found", index); return Ok(()); }; // We should have received only one message assert!(links.try_next().await?.is_none()); for nla in msg.nlas.into_iter() { if let Nla::IfName(name) = nla { println!("found link with index {} (name = {})", index, name); return Ok(()); } } eprintln!( "found link with index {}, but this link does not have a name", index ); Ok(()) } async fn get_link_by_name(handle: Handle, name: String) -> Result<(), Error> { let mut links = handle.link().get().match_name(name.clone()).execute(); if (links.try_next().await?).is_some() { println!("found link {}", name); // We should only have one link with that name assert!(links.try_next().await?.is_none()); } else { println!("no link link {} found", name); } Ok(()) } async fn dump_links(handle: Handle) -> Result<(), Error> { let mut links = handle.link().get().execute(); 'outer: while let Some(msg) = links.try_next().await? { for nla in msg.nlas.into_iter() { if let Nla::IfName(name) = nla { println!("found link {} ({})", msg.header.index, name); continue 'outer; } } eprintln!("found link {}, but the link has no name", msg.header.index); } Ok(()) } async fn dump_bridge_filter_info(handle: Handle) -> Result<(), Error> { let mut links = handle .link() .get() .set_filter_mask(AF_BRIDGE as u8, RTEXT_FILTER_BRVLAN) .execute(); 'outer: while let Some(msg) = links.try_next().await? { for nla in msg.nlas.into_iter() { if let Nla::AfSpecBridge(data) = nla { println!( "found interface {} with AfSpecBridge data {:?})", msg.header.index, data ); continue 'outer; } } } Ok(()) } ================================================ FILE: rtnetlink/examples/get_links_async.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{ new_connection, packet::rtnl::{ constants::{AF_BRIDGE, RTEXT_FILTER_BRVLAN}, link::nlas::Nla, }, Error, Handle, }; #[async_std::main] async fn main() -> Result<(), ()> { env_logger::init(); let (connection, handle, _) = new_connection().unwrap(); async_std::task::spawn(connection); // Fetch a link by its index let index = 1; println!("*** retrieving link with index {} ***", index); if let Err(e) = get_link_by_index(handle.clone(), index).await { eprintln!("{}", e); } // Fetch a link by its name let name = "lo"; println!("*** retrieving link named \"{}\" ***", name); if let Err(e) = get_link_by_name(handle.clone(), name.to_string()).await { eprintln!("{}", e); } // Dump all the links and print their index and name println!("*** dumping links ***"); if let Err(e) = dump_links(handle.clone()).await { eprintln!("{}", e); } // Dump all the bridge vlan information if let Err(e) = dump_bridge_filter_info(handle.clone()).await { eprintln!("{}", e); } Ok(()) } async fn get_link_by_index(handle: Handle, index: u32) -> Result<(), Error> { let mut links = handle.link().get().match_index(index).execute(); let msg = if let Some(msg) = links.try_next().await? { msg } else { eprintln!("no link with index {} found", index); return Ok(()); }; // We should have received only one message assert!(links.try_next().await?.is_none()); for nla in msg.nlas.into_iter() { if let Nla::IfName(name) = nla { println!("found link with index {} (name = {})", index, name); return Ok(()); } } eprintln!( "found link with index {}, but this link does not have a name", index ); Ok(()) } async fn get_link_by_name(handle: Handle, name: String) -> Result<(), Error> { let mut links = handle.link().get().match_name(name.clone()).execute(); if (links.try_next().await?).is_some() { println!("found link {}", name); // We should only have one link with that name assert!(links.try_next().await?.is_none()); } else { println!("no link link {} found", name); } Ok(()) } async fn dump_links(handle: Handle) -> Result<(), Error> { let mut links = handle.link().get().execute(); 'outer: while let Some(msg) = links.try_next().await? { for nla in msg.nlas.into_iter() { if let Nla::IfName(name) = nla { println!("found link {} ({})", msg.header.index, name); continue 'outer; } } eprintln!("found link {}, but the link has no name", msg.header.index); } Ok(()) } async fn dump_bridge_filter_info(handle: Handle) -> Result<(), Error> { let mut links = handle .link() .get() .set_filter_mask(AF_BRIDGE as u8, RTEXT_FILTER_BRVLAN) .execute(); 'outer: while let Some(msg) = links.try_next().await? { for nla in msg.nlas.into_iter() { if let Nla::AfSpecBridge(data) = nla { println!( "found interface {} with AfSpecBridge data {:?})", msg.header.index, data ); continue 'outer; } } } Ok(()) } ================================================ FILE: rtnetlink/examples/get_links_thread_builder.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{ new_connection, packet::rtnl::{ constants::{AF_BRIDGE, RTEXT_FILTER_BRVLAN}, link::nlas::Nla, }, Error, Handle, }; async fn do_it(rt: &tokio::runtime::Runtime) -> Result<(), ()> { env_logger::init(); let (connection, handle, _) = new_connection().unwrap(); rt.spawn(connection); // Fetch a link by its index let index = 1; println!("*** retrieving link with index {} ***", index); if let Err(e) = get_link_by_index(handle.clone(), index).await { eprintln!("{}", e); } // Fetch a link by its name let name = "lo"; println!("*** retrieving link named \"{}\" ***", name); if let Err(e) = get_link_by_name(handle.clone(), name.to_string()).await { eprintln!("{}", e); } // Dump all the links and print their index and name println!("*** dumping links ***"); if let Err(e) = dump_links(handle.clone()).await { eprintln!("{}", e); } // Dump all the bridge vlan information if let Err(e) = dump_bridge_filter_info(handle.clone()).await { eprintln!("{}", e); } Ok(()) } async fn get_link_by_index(handle: Handle, index: u32) -> Result<(), Error> { let mut links = handle.link().get().match_index(index).execute(); let msg = if let Some(msg) = links.try_next().await? { msg } else { eprintln!("no link with index {} found", index); return Ok(()); }; // We should have received only one message assert!(links.try_next().await?.is_none()); for nla in msg.nlas.into_iter() { if let Nla::IfName(name) = nla { println!("found link with index {} (name = {})", index, name); return Ok(()); } } eprintln!( "found link with index {}, but this link does not have a name", index ); Ok(()) } async fn get_link_by_name(handle: Handle, name: String) -> Result<(), Error> { let mut links = handle.link().get().match_name(name.clone()).execute(); if (links.try_next().await?).is_some() { println!("found link {}", name); // We should only have one link with that name assert!(links.try_next().await?.is_none()); } else { println!("no link link {} found", name); } Ok(()) } async fn dump_links(handle: Handle) -> Result<(), Error> { let mut links = handle.link().get().execute(); 'outer: while let Some(msg) = links.try_next().await? { for nla in msg.nlas.into_iter() { if let Nla::IfName(name) = nla { println!("found link {} ({})", msg.header.index, name); continue 'outer; } } eprintln!("found link {}, but the link has no name", msg.header.index); } Ok(()) } async fn dump_bridge_filter_info(handle: Handle) -> Result<(), Error> { let mut links = handle .link() .get() .set_filter_mask(AF_BRIDGE as u8, RTEXT_FILTER_BRVLAN) .execute(); 'outer: while let Some(msg) = links.try_next().await? { for nla in msg.nlas.into_iter() { if let Nla::AfSpecBridge(data) = nla { println!( "found interface {} with AfSpecBridge data {:?})", msg.header.index, data ); continue 'outer; } } } Ok(()) } fn main() -> Result<(), String> { let rt = tokio::runtime::Builder::new_multi_thread() .enable_io() .build() .unwrap(); let future = do_it(&rt); println!("blocking in main"); rt.handle().block_on(future).unwrap(); Ok(()) } ================================================ FILE: rtnetlink/examples/get_neighbours.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{new_connection, Error, Handle, IpVersion}; #[tokio::main] async fn main() -> Result<(), ()> { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); println!("dumping neighbours"); if let Err(e) = dump_neighbours(handle.clone()).await { eprintln!("{}", e); } println!(); Ok(()) } async fn dump_neighbours(handle: Handle) -> Result<(), Error> { let mut neighbours = handle .neighbours() .get() .set_family(IpVersion::V4) .execute(); while let Some(route) = neighbours.try_next().await? { println!("{:?}", route); } Ok(()) } ================================================ FILE: rtnetlink/examples/get_route.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{new_connection, Error, Handle, IpVersion}; #[tokio::main] async fn main() -> Result<(), ()> { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); println!("dumping routes for IPv4"); if let Err(e) = dump_addresses(handle.clone(), IpVersion::V4).await { eprintln!("{}", e); } println!(); println!("dumping routes for IPv6"); if let Err(e) = dump_addresses(handle.clone(), IpVersion::V6).await { eprintln!("{}", e); } println!(); Ok(()) } async fn dump_addresses(handle: Handle, ip_version: IpVersion) -> Result<(), Error> { let mut routes = handle.route().get(ip_version).execute(); while let Some(route) = routes.try_next().await? { println!("{:?}", route); } Ok(()) } ================================================ FILE: rtnetlink/examples/get_rule.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{new_connection, Error, Handle, IpVersion}; #[tokio::main] async fn main() -> Result<(), ()> { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); println!("dumping rules for IPv4"); if let Err(e) = dump_addresses(handle.clone(), IpVersion::V4).await { eprintln!("{}", e); } println!(); println!("dumping rules for IPv6"); if let Err(e) = dump_addresses(handle.clone(), IpVersion::V6).await { eprintln!("{}", e); } println!(); Ok(()) } async fn dump_addresses(handle: Handle, ip_version: IpVersion) -> Result<(), Error> { let mut rules = handle.rule().get(ip_version).execute(); while let Some(rule) = rules.try_next().await? { println!("{:?}", rule); } Ok(()) } ================================================ FILE: rtnetlink/examples/ip_monitor.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use netlink_packet_route::constants::*; use rtnetlink::{ new_connection, sys::{AsyncSocket, SocketAddr}, }; const fn nl_mgrp(group: u32) -> u32 { if group > 31 { panic!("use netlink_sys::Socket::add_membership() for this group"); } if group == 0 { 0 } else { 1 << (group - 1) } } #[tokio::main] async fn main() -> Result<(), String> { // conn - `Connection` that has a netlink socket which is a `Future` that polls the socket // and thus must have an event loop // // handle - `Handle` to the `Connection`. Used to send/recv netlink messages. // // messages - A channel receiver. let (mut conn, mut _handle, mut messages) = new_connection().map_err(|e| format!("{}", e))?; // These flags specify what kinds of broadcast messages we want to listen for. let groups = nl_mgrp(RTNLGRP_LINK) | nl_mgrp(RTNLGRP_IPV4_IFADDR) | nl_mgrp(RTNLGRP_IPV6_IFADDR) | nl_mgrp(RTNLGRP_IPV4_ROUTE) | nl_mgrp(RTNLGRP_IPV6_ROUTE) | nl_mgrp(RTNLGRP_MPLS_ROUTE) | nl_mgrp(RTNLGRP_IPV4_MROUTE) | nl_mgrp(RTNLGRP_IPV6_MROUTE) | nl_mgrp(RTNLGRP_NEIGH) | nl_mgrp(RTNLGRP_IPV4_NETCONF) | nl_mgrp(RTNLGRP_IPV6_NETCONF) | nl_mgrp(RTNLGRP_IPV4_RULE) | nl_mgrp(RTNLGRP_IPV6_RULE) | nl_mgrp(RTNLGRP_NSID) | nl_mgrp(RTNLGRP_MPLS_NETCONF); let addr = SocketAddr::new(0, groups); conn.socket_mut() .socket_mut() .bind(&addr) .expect("Failed to bind"); // Spawn `Connection` to start polling netlink socket. tokio::spawn(conn); // Use `Handle` to send request to kernel to start multicasting rtnetlink events. tokio::spawn(async move { // Create message to enable }); // Start receiving events through `messages` channel. while let Some((message, _)) = messages.next().await { let payload = message.payload; println!("{:?}", payload); } Ok(()) } ================================================ FILE: rtnetlink/examples/listen.rs ================================================ // SPDX-License-Identifier: MIT //! This example opens a netlink socket, registers for IPv4 and IPv6 routing changes, listens for //! said changes and prints the received messages. use futures::stream::StreamExt; use rtnetlink::{ constants::{RTMGRP_IPV4_ROUTE, RTMGRP_IPV6_ROUTE}, new_connection, sys::{AsyncSocket, SocketAddr}, }; #[tokio::main] async fn main() -> Result<(), String> { // Open the netlink socket let (mut connection, _, mut messages) = new_connection().map_err(|e| format!("{}", e))?; // These flags specify what kinds of broadcast messages we want to listen for. let mgroup_flags = RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE; // A netlink socket address is created with said flags. let addr = SocketAddr::new(0, mgroup_flags); // Said address is bound so new conenctions and thus new message broadcasts can be received. connection .socket_mut() .socket_mut() .bind(&addr) .expect("failed to bind"); tokio::spawn(connection); while let Some((message, _)) = messages.next().await { let payload = message.payload; println!("Route change message - {:?}", payload); } Ok(()) } ================================================ FILE: rtnetlink/examples/property_altname.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{ new_connection, packet::{ rtnl::link::nlas::{Nla, Prop}, LinkMessage, }, Error, Handle, }; use std::env; #[tokio::main] async fn main() -> Result<(), ()> { let args: Vec = env::args().collect(); if args.len() < 3 { usage(); return Ok(()); } let link_name = &args[1]; let action = &args[2]; let alt_ifnames = &args[3..].iter().map(String::as_str).collect(); let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); match action.as_str() { "add" => { if let Err(e) = add_property_alt_ifnames(link_name, alt_ifnames, handle.clone()).await { eprintln!("{}", e); } } "del" => { if let Err(e) = del_property_alt_ifnames(link_name, alt_ifnames, handle.clone()).await { eprintln!("{}", e); } } "show" => { if let Err(e) = show_property_alt_ifnames(link_name, handle.clone()).await { eprintln!("{}", e); } } _ => panic!("Unknown action {:?}", action), } Ok(()) } async fn show_property_alt_ifnames(link_name: &str, handle: Handle) -> Result<(), Error> { for nla in get_link(link_name, handle).await?.nlas.into_iter() { if let Nla::PropList(ref prop_list) = nla { for prop in prop_list { if let Prop::AltIfName(altname) = prop { println!("altname: {}", altname); } } } } Ok(()) } async fn add_property_alt_ifnames( link_name: &str, alt_ifnames: &Vec<&str>, handle: Handle, ) -> Result<(), Error> { let link_index = get_link_index(link_name, handle.clone()).await?; handle .link() .property_add(link_index) .alt_ifname(alt_ifnames) .execute() .await?; Ok(()) } async fn del_property_alt_ifnames( link_name: &str, alt_ifnames: &Vec<&str>, handle: Handle, ) -> Result<(), Error> { let link_index = get_link_index(link_name, handle.clone()).await?; handle .link() .property_del(link_index) .alt_ifname(alt_ifnames) .execute() .await?; Ok(()) } async fn get_link(link_name: &str, handle: Handle) -> Result { let mut links = handle .link() .get() .match_name(link_name.to_string()) .execute(); match links.try_next().await? { Some(msg) => Ok(msg), _ => { eprintln!("Interface {} not found", link_name); Err(Error::RequestFailed) } } } async fn get_link_index(link_name: &str, handle: Handle) -> Result { Ok(get_link(link_name, handle.clone()).await?.header.index) } fn usage() { eprintln!( "usage: cargo run --example property_altname -- [add | del | show] ALTNAME [ALTNAME ...] Note that you need to run this program as root for add and del. Instead of running cargo as root, build the example normally: cd rtnetlink ; cargo build --example property_altname Then find the binary in the target directory: cd ../target/debug/example ; sudo ./property_altname " ); } ================================================ FILE: rtnetlink/examples/set_link_down.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use rtnetlink::{new_connection, Error, Handle}; use std::env; #[tokio::main] async fn main() -> Result<(), String> { let args: Vec = env::args().collect(); if args.len() != 2 { usage(); return Ok(()); } let link_name = &args[1]; let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); set_link_down(handle, link_name.to_string()) .await .map_err(|e| format!("{}", e)) } async fn set_link_down(handle: Handle, name: String) -> Result<(), Error> { let mut links = handle.link().get().match_name(name.clone()).execute(); if let Some(link) = links.try_next().await? { handle .link() .set(link.header.index) .down() .execute() .await? } else { println!("no link link {} found", name); } Ok(()) } fn usage() { eprintln!( "usage: cargo run --example set_link_down -- Note that you need to run this program as root. Instead of running cargo as root, build the example normally: cd netlink-ip ; cargo build --example set_link_down Then find the binary in the target directory: cd ../target/debug/example ; sudo ./set_link_down " ); } ================================================ FILE: rtnetlink/src/addr/add.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use std::net::{IpAddr, Ipv4Addr}; use netlink_packet_route::{ nlas::address::Nla, AddressMessage, NetlinkMessage, RtnlMessage, AF_INET, AF_INET6, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REPLACE, NLM_F_REQUEST, }; use crate::{try_nl, Error, Handle}; /// A request to create a new address. This is equivalent to the `ip address add` commands. pub struct AddressAddRequest { handle: Handle, message: AddressMessage, replace: bool, } impl AddressAddRequest { pub(crate) fn new(handle: Handle, index: u32, address: IpAddr, prefix_len: u8) -> Self { let mut message = AddressMessage::default(); message.header.prefix_len = prefix_len; message.header.index = index; let address_vec = match address { IpAddr::V4(ipv4) => { message.header.family = AF_INET as u8; ipv4.octets().to_vec() } IpAddr::V6(ipv6) => { message.header.family = AF_INET6 as u8; ipv6.octets().to_vec() } }; if address.is_multicast() { message.nlas.push(Nla::Multicast(address_vec)); } else if address.is_unspecified() { message.nlas.push(Nla::Unspec(address_vec)); } else if address.is_ipv6() { message.nlas.push(Nla::Address(address_vec)); } else { message.nlas.push(Nla::Address(address_vec.clone())); // for IPv4 the IFA_LOCAL address can be set to the same value as IFA_ADDRESS message.nlas.push(Nla::Local(address_vec.clone())); // set the IFA_BROADCAST address as well (IPv6 does not support broadcast) if prefix_len == 32 { message.nlas.push(Nla::Broadcast(address_vec)); } else { let ip_addr: u32 = u32::from(Ipv4Addr::new( address_vec[0], address_vec[1], address_vec[2], address_vec[3], )); let brd = Ipv4Addr::from((0xffff_ffff_u32) >> u32::from(prefix_len) | ip_addr); message.nlas.push(Nla::Broadcast(brd.octets().to_vec())); }; } AddressAddRequest { handle, message, replace: false, } } /// Replace existing matching address. pub fn replace(self) -> Self { Self { replace: true, ..self } } /// Execute the request. pub async fn execute(self) -> Result<(), Error> { let AddressAddRequest { mut handle, message, replace, } = self; let mut req = NetlinkMessage::from(RtnlMessage::NewAddress(message)); let replace = if replace { NLM_F_REPLACE } else { NLM_F_EXCL }; req.header.flags = NLM_F_REQUEST | NLM_F_ACK | replace | NLM_F_CREATE; let mut response = handle.request(req)?; while let Some(message) = response.next().await { try_nl!(message); } Ok(()) } /// Return a mutable reference to the request message. pub fn message_mut(&mut self) -> &mut AddressMessage { &mut self.message } } ================================================ FILE: rtnetlink/src/addr/del.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use crate::{ packet::{AddressMessage, NetlinkMessage, RtnlMessage, NLM_F_ACK, NLM_F_REQUEST}, try_nl, Error, Handle, }; pub struct AddressDelRequest { handle: Handle, message: AddressMessage, } impl AddressDelRequest { pub(crate) fn new(handle: Handle, message: AddressMessage) -> Self { AddressDelRequest { handle, message } } /// Execute the request pub async fn execute(self) -> Result<(), Error> { let AddressDelRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::DelAddress(message)); req.header.flags = NLM_F_REQUEST | NLM_F_ACK; let mut response = handle.request(req)?; while let Some(msg) = response.next().await { try_nl!(msg); } Ok(()) } pub fn message_mut(&mut self) -> &mut AddressMessage { &mut self.message } } ================================================ FILE: rtnetlink/src/addr/get.rs ================================================ // SPDX-License-Identifier: MIT use futures::{ future::{self, Either}, stream::{StreamExt, TryStream, TryStreamExt}, FutureExt, }; use std::net::IpAddr; use netlink_packet_route::{ nlas::address::Nla, AddressMessage, NetlinkMessage, RtnlMessage, NLM_F_DUMP, NLM_F_REQUEST, }; use crate::{try_rtnl, Error, Handle}; pub struct AddressGetRequest { handle: Handle, message: AddressMessage, filter_builder: AddressFilterBuilder, } impl AddressGetRequest { pub(crate) fn new(handle: Handle) -> Self { AddressGetRequest { handle, message: AddressMessage::default(), filter_builder: AddressFilterBuilder::new(), } } pub fn message_mut(&mut self) -> &mut AddressMessage { &mut self.message } pub fn execute(self) -> impl TryStream { let AddressGetRequest { mut handle, message, filter_builder, } = self; let mut req = NetlinkMessage::from(RtnlMessage::GetAddress(message)); req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; let filter = filter_builder.build(); match handle.request(req) { Ok(response) => Either::Left( response .map(move |msg| Ok(try_rtnl!(msg, RtnlMessage::NewAddress))) .try_filter(move |msg| future::ready(filter(msg))), ), Err(e) => Either::Right(future::err::(e).into_stream()), } } /// Return only the addresses of the given interface. pub fn set_link_index_filter(mut self, index: u32) -> Self { self.filter_builder.index = Some(index); self } /// Return only the addresses of the given prefix length. pub fn set_prefix_length_filter(mut self, prefix: u8) -> Self { self.filter_builder.prefix_len = Some(prefix); self } /// Return only the addresses of the given prefix length. pub fn set_address_filter(mut self, address: IpAddr) -> Self { self.filter_builder.address = Some(address); self } } // The reason for having filters, is that we cannot retrieve addresses // that match the given message, like we do for links. // // See: // https://lists.infradead.org/pipermail/libnl/2013-June/001014.html // https://patchwork.ozlabs.org/patch/133440/ #[derive(Default)] struct AddressFilterBuilder { index: Option, address: Option, prefix_len: Option, } impl AddressFilterBuilder { fn new() -> Self { Default::default() } fn build(self) -> impl Fn(&AddressMessage) -> bool { use Nla::*; move |msg: &AddressMessage| { if let Some(index) = self.index { if msg.header.index != index { return false; } } if let Some(prefix_len) = self.prefix_len { if msg.header.prefix_len != prefix_len { return false; } } if let Some(address) = self.address { for nla in msg.nlas.iter() { if let Unspec(x) | Address(x) | Local(x) | Multicast(x) | Anycast(x) = nla { let is_match = match address { IpAddr::V4(address) => x[..] == address.octets()[..], IpAddr::V6(address) => x[..] == address.octets()[..], }; if is_match { return true; } } } return false; } true } } } ================================================ FILE: rtnetlink/src/addr/handle.rs ================================================ // SPDX-License-Identifier: MIT use std::net::IpAddr; use super::{AddressAddRequest, AddressDelRequest, AddressGetRequest}; use crate::Handle; use netlink_packet_route::AddressMessage; pub struct AddressHandle(Handle); impl AddressHandle { pub fn new(handle: Handle) -> Self { AddressHandle(handle) } /// Retrieve the list of ip addresses (equivalent to `ip addr show`) pub fn get(&self) -> AddressGetRequest { AddressGetRequest::new(self.0.clone()) } /// Add an ip address on an interface (equivalent to `ip addr add`) pub fn add(&self, index: u32, address: IpAddr, prefix_len: u8) -> AddressAddRequest { AddressAddRequest::new(self.0.clone(), index, address, prefix_len) } /// Delete the given address pub fn del(&self, address: AddressMessage) -> AddressDelRequest { AddressDelRequest::new(self.0.clone(), address) } } ================================================ FILE: rtnetlink/src/addr/mod.rs ================================================ // SPDX-License-Identifier: MIT mod handle; pub use self::handle::*; mod add; pub use self::add::*; mod del; pub use self::del::*; mod get; pub use self::get::*; ================================================ FILE: rtnetlink/src/connection.rs ================================================ // SPDX-License-Identifier: MIT use std::io; use futures::channel::mpsc::UnboundedReceiver; use crate::{ packet::{NetlinkMessage, RtnlMessage}, proto::Connection, sys::{protocols::NETLINK_ROUTE, AsyncSocket, SocketAddr}, Handle, }; #[cfg(feature = "tokio_socket")] #[allow(clippy::type_complexity)] pub fn new_connection() -> io::Result<( Connection, Handle, UnboundedReceiver<(NetlinkMessage, SocketAddr)>, )> { new_connection_with_socket() } #[allow(clippy::type_complexity)] pub fn new_connection_with_socket() -> io::Result<( Connection, Handle, UnboundedReceiver<(NetlinkMessage, SocketAddr)>, )> where S: AsyncSocket, { let (conn, handle, messages) = netlink_proto::new_connection_with_socket(NETLINK_ROUTE)?; Ok((conn, Handle::new(handle), messages)) } ================================================ FILE: rtnetlink/src/constants.rs ================================================ // SPDX-License-Identifier: MIT pub const RTMGRP_LINK: u32 = 1; pub const RTMGRP_NOTIFY: u32 = 2; pub const RTMGRP_NEIGH: u32 = 4; pub const RTMGRP_TC: u32 = 8; pub const RTMGRP_IPV4_IFADDR: u32 = 16; pub const RTMGRP_IPV4_MROUTE: u32 = 32; pub const RTMGRP_IPV4_ROUTE: u32 = 64; pub const RTMGRP_IPV4_RULE: u32 = 128; pub const RTMGRP_IPV6_IFADDR: u32 = 256; pub const RTMGRP_IPV6_MROUTE: u32 = 512; pub const RTMGRP_IPV6_ROUTE: u32 = 1024; pub const RTMGRP_IPV6_IFINFO: u32 = 2048; pub const RTMGRP_DECNET_IFADDR: u32 = 4096; pub const RTMGRP_DECNET_ROUTE: u32 = 16_384; pub const RTMGRP_IPV6_PREFIX: u32 = 131_072; ================================================ FILE: rtnetlink/src/errors.rs ================================================ // SPDX-License-Identifier: MIT use thiserror::Error; use crate::packet::{ErrorMessage, NetlinkMessage, RtnlMessage}; #[derive(Clone, Eq, PartialEq, Debug, Error)] pub enum Error { #[error("Received an unexpected message {0:?}")] UnexpectedMessage(NetlinkMessage), #[error("Received a netlink error message {0}")] NetlinkError(ErrorMessage), #[error("A netlink request failed")] RequestFailed, #[error("Namespace error {0}")] NamespaceError(String), #[error( "Received a link message (RTM_GETLINK, RTM_NEWLINK, RTM_SETLINK or RTMGETLINK) with an invalid hardware address attribute: {0:?}." )] InvalidHardwareAddress(Vec), #[error("Failed to parse an IP address: {0:?}")] InvalidIp(Vec), #[error("Failed to parse a network address (IP and mask): {0:?}/{1:?}")] InvalidAddress(Vec, Vec), } ================================================ FILE: rtnetlink/src/handle.rs ================================================ // SPDX-License-Identifier: MIT use futures::Stream; use crate::{ packet::{NetlinkMessage, RtnlMessage}, AddressHandle, Error, LinkHandle, NeighbourHandle, QDiscHandle, RouteHandle, RuleHandle, TrafficChainHandle, TrafficClassHandle, TrafficFilterHandle, }; use netlink_proto::{sys::SocketAddr, ConnectionHandle}; #[derive(Clone, Debug)] pub struct Handle(ConnectionHandle); impl Handle { pub(crate) fn new(conn: ConnectionHandle) -> Self { Handle(conn) } pub fn request( &mut self, message: NetlinkMessage, ) -> Result>, Error> { self.0 .request(message, SocketAddr::new(0, 0)) .map_err(|_| Error::RequestFailed) } pub fn notify(&mut self, msg: NetlinkMessage) -> Result<(), Error> { self.0 .notify(msg, SocketAddr::new(0, 0)) .map_err(|_| Error::RequestFailed)?; Ok(()) } /// Create a new handle, specifically for link requests (equivalent to `ip link` commands) pub fn link(&self) -> LinkHandle { LinkHandle::new(self.clone()) } /// Create a new handle, specifically for address requests (equivalent to `ip addr` commands) pub fn address(&self) -> AddressHandle { AddressHandle::new(self.clone()) } /// Create a new handle, specifically for routing table requests (equivalent to `ip route` commands) pub fn route(&self) -> RouteHandle { RouteHandle::new(self.clone()) } /// Create a new handle, specifically for routing rule requests (equivalent to `ip rule` commands) pub fn rule(&self) -> RuleHandle { RuleHandle::new(self.clone()) } /// Create a new handle, specifically for routing neighbours requests (equivalent to `ip neighbour` commands) pub fn neighbours(&self) -> NeighbourHandle { NeighbourHandle::new(self.clone()) } /// Create a new handle, specifically for traffic control qdisc requests /// (equivalent to `tc qdisc show` commands) pub fn qdisc(&self) -> QDiscHandle { QDiscHandle::new(self.clone()) } /// Create a new handle, specifically for traffic control class requests /// (equivalent to `tc class show dev ` commands) pub fn traffic_class(&self, ifindex: i32) -> TrafficClassHandle { TrafficClassHandle::new(self.clone(), ifindex) } /// Create a new handle, specifically for traffic control filter requests /// (equivalent to `tc filter show dev ` commands) pub fn traffic_filter(&self, ifindex: i32) -> TrafficFilterHandle { TrafficFilterHandle::new(self.clone(), ifindex) } /// Create a new handle, specifically for traffic control chain requests /// (equivalent to `tc chain show dev ` commands) pub fn traffic_chain(&self, ifindex: i32) -> TrafficChainHandle { TrafficChainHandle::new(self.clone(), ifindex) } } ================================================ FILE: rtnetlink/src/lib.rs ================================================ // SPDX-License-Identifier: MIT //! This crate provides methods to manipulate networking resources (links, addresses, arp tables, //! route tables) via the netlink protocol. #![allow(clippy::module_inception)] mod handle; pub use crate::handle::*; mod ns; pub use crate::ns::*; mod errors; pub use crate::errors::*; mod link; pub use crate::link::*; mod addr; pub use crate::addr::*; mod route; pub use crate::route::*; mod rule; pub use crate::rule::*; mod connection; pub use crate::connection::*; mod traffic_control; pub use crate::traffic_control::*; mod neighbour; pub use crate::neighbour::*; pub mod constants; pub use netlink_packet_route as packet; pub mod proto { pub use netlink_proto::{ packet::{NetlinkMessage, NetlinkPayload}, Connection, ConnectionHandle, Error, }; } pub use netlink_proto::sys; mod macros; ================================================ FILE: rtnetlink/src/link/add.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use netlink_packet_route::link::nlas::InfoMacVtap; use std::net::{Ipv4Addr, Ipv6Addr}; use crate::{ packet::{ nlas::link::{ Info, InfoBond, InfoData, InfoKind, InfoMacVlan, InfoVlan, InfoVxlan, Nla, VethInfo, }, LinkMessage, NetlinkMessage, RtnlMessage, IFF_UP, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REPLACE, NLM_F_REQUEST, }, try_nl, Error, Handle, }; pub struct BondAddRequest { request: LinkAddRequest, info_data: Vec, } impl BondAddRequest { /// Execute the request. pub async fn execute(self) -> Result<(), Error> { let s = self .request .link_info(InfoKind::Bond, Some(InfoData::Bond(self.info_data))); s.execute().await } /// Sets the interface up /// This is equivalent to `ip link set up dev NAME`. pub fn up(mut self) -> Self { self.request = self.request.up(); self } /// Adds the `mode` attribute to the bond /// This is equivalent to `ip link add name NAME type bond mode MODE`. pub fn mode(mut self, mode: u8) -> Self { self.info_data.push(InfoBond::Mode(mode)); self } /// Adds the `active_slave` attribute to the bond, where `active_slave` /// is the ifindex of an interface attached to the bond. /// This is equivalent to `ip link add name NAME type bond active_slave ACTIVE_SLAVE_NAME`. pub fn active_slave(mut self, active_slave: u32) -> Self { self.info_data.push(InfoBond::ActiveSlave(active_slave)); self } /// Adds the `miimon` attribute to the bond /// This is equivalent to `ip link add name NAME type bond miimon MIIMON`. pub fn miimon(mut self, miimon: u32) -> Self { self.info_data.push(InfoBond::MiiMon(miimon)); self } /// Adds the `updelay` attribute to the bond /// This is equivalent to `ip link add name NAME type bond updelay UPDELAY`. pub fn updelay(mut self, updelay: u32) -> Self { self.info_data.push(InfoBond::UpDelay(updelay)); self } /// Adds the `downdelay` attribute to the bond /// This is equivalent to `ip link add name NAME type bond downdelay DOWNDELAY`. pub fn downdelay(mut self, downdelay: u32) -> Self { self.info_data.push(InfoBond::DownDelay(downdelay)); self } /// Adds the `use_carrier` attribute to the bond /// This is equivalent to `ip link add name NAME type bond use_carrier USE_CARRIER`. pub fn use_carrier(mut self, use_carrier: u8) -> Self { self.info_data.push(InfoBond::UseCarrier(use_carrier)); self } /// Adds the `arp_interval` attribute to the bond /// This is equivalent to `ip link add name NAME type bond arp_interval ARP_INTERVAL`. pub fn arp_interval(mut self, arp_interval: u32) -> Self { self.info_data.push(InfoBond::ArpInterval(arp_interval)); self } /// Adds the `arp_validate` attribute to the bond /// This is equivalent to `ip link add name NAME type bond arp_validate ARP_VALIDATE`. pub fn arp_validate(mut self, arp_validate: u32) -> Self { self.info_data.push(InfoBond::ArpValidate(arp_validate)); self } /// Adds the `arp_all_targets` attribute to the bond /// This is equivalent to `ip link add name NAME type bond arp_all_targets ARP_ALL_TARGETS` pub fn arp_all_targets(mut self, arp_all_targets: u32) -> Self { self.info_data .push(InfoBond::ArpAllTargets(arp_all_targets)); self } /// Adds the `primary` attribute to the bond, where `primary` is the ifindex /// of an interface. /// This is equivalent to `ip link add name NAME type bond primary PRIMARY_NAME` pub fn primary(mut self, primary: u32) -> Self { self.info_data.push(InfoBond::Primary(primary)); self } /// Adds the `primary_reselect` attribute to the bond /// This is equivalent to `ip link add name NAME type bond primary_reselect PRIMARY_RESELECT`. pub fn primary_reselect(mut self, primary_reselect: u8) -> Self { self.info_data .push(InfoBond::PrimaryReselect(primary_reselect)); self } /// Adds the `fail_over_mac` attribute to the bond /// This is equivalent to `ip link add name NAME type bond fail_over_mac FAIL_OVER_MAC`. pub fn fail_over_mac(mut self, fail_over_mac: u8) -> Self { self.info_data.push(InfoBond::FailOverMac(fail_over_mac)); self } /// Adds the `xmit_hash_policy` attribute to the bond /// This is equivalent to `ip link add name NAME type bond xmit_hash_policy XMIT_HASH_POLICY`. pub fn xmit_hash_policy(mut self, xmit_hash_policy: u8) -> Self { self.info_data .push(InfoBond::XmitHashPolicy(xmit_hash_policy)); self } /// Adds the `resend_igmp` attribute to the bond /// This is equivalent to `ip link add name NAME type bond resend_igmp RESEND_IGMP`. pub fn resend_igmp(mut self, resend_igmp: u32) -> Self { self.info_data.push(InfoBond::ResendIgmp(resend_igmp)); self } /// Adds the `num_peer_notif` attribute to the bond /// This is equivalent to `ip link add name NAME type bond num_peer_notif NUM_PEER_NOTIF`. pub fn num_peer_notif(mut self, num_peer_notif: u8) -> Self { self.info_data.push(InfoBond::NumPeerNotif(num_peer_notif)); self } /// Adds the `all_slaves_active` attribute to the bond /// This is equivalent to `ip link add name NAME type bond all_slaves_active ALL_SLAVES_ACTIVE`. pub fn all_slaves_active(mut self, all_slaves_active: u8) -> Self { self.info_data .push(InfoBond::AllSlavesActive(all_slaves_active)); self } /// Adds the `min_links` attribute to the bond /// This is equivalent to `ip link add name NAME type bond min_links MIN_LINKS`. pub fn min_links(mut self, min_links: u32) -> Self { self.info_data.push(InfoBond::MinLinks(min_links)); self } /// Adds the `lp_interval` attribute to the bond /// This is equivalent to `ip link add name NAME type bond lp_interval LP_INTERVAL`. pub fn lp_interval(mut self, lp_interval: u32) -> Self { self.info_data.push(InfoBond::LpInterval(lp_interval)); self } /// Adds the `packets_per_slave` attribute to the bond /// This is equivalent to `ip link add name NAME type bond packets_per_slave PACKETS_PER_SLAVE`. pub fn packets_per_slave(mut self, packets_per_slave: u32) -> Self { self.info_data .push(InfoBond::PacketsPerSlave(packets_per_slave)); self } /// Adds the `ad_lacp_rate` attribute to the bond /// This is equivalent to `ip link add name NAME type bond ad_lacp_rate AD_LACP_RATE`. pub fn ad_lacp_rate(mut self, ad_lacp_rate: u8) -> Self { self.info_data.push(InfoBond::AdLacpRate(ad_lacp_rate)); self } /// Adds the `ad_select` attribute to the bond /// This is equivalent to `ip link add name NAME type bond ad_select AD_SELECT`. pub fn ad_select(mut self, ad_select: u8) -> Self { self.info_data.push(InfoBond::AdSelect(ad_select)); self } /// Adds the `ad_actor_sys_prio` attribute to the bond /// This is equivalent to `ip link add name NAME type bond ad_actor_sys_prio AD_ACTOR_SYS_PRIO`. pub fn ad_actor_sys_prio(mut self, ad_actor_sys_prio: u16) -> Self { self.info_data .push(InfoBond::AdActorSysPrio(ad_actor_sys_prio)); self } /// Adds the `ad_user_port_key` attribute to the bond /// This is equivalent to `ip link add name NAME type bond ad_user_port_key AD_USER_PORT_KEY`. pub fn ad_user_port_key(mut self, ad_user_port_key: u16) -> Self { self.info_data .push(InfoBond::AdUserPortKey(ad_user_port_key)); self } /// Adds the `ad_actor_system` attribute to the bond /// This is equivalent to `ip link add name NAME type bond ad_actor_system AD_ACTOR_SYSTEM`. pub fn ad_actor_system(mut self, ad_actor_system: [u8; 6]) -> Self { self.info_data .push(InfoBond::AdActorSystem(ad_actor_system)); self } /// Adds the `tlb_dynamic_lb` attribute to the bond /// This is equivalent to `ip link add name NAME type bond tlb_dynamic_lb TLB_DYNAMIC_LB`. pub fn tlb_dynamic_lb(mut self, tlb_dynamic_lb: u8) -> Self { self.info_data.push(InfoBond::TlbDynamicLb(tlb_dynamic_lb)); self } /// Adds the `peer_notif_delay` attribute to the bond /// This is equivalent to `ip link add name NAME type bond peer_notif_delay PEER_NOTIF_DELAY`. pub fn peer_notif_delay(mut self, peer_notif_delay: u32) -> Self { self.info_data .push(InfoBond::PeerNotifDelay(peer_notif_delay)); self } /// Adds the `ad_lacp_active` attribute to the bond /// This is equivalent to `ip link add name NAME type bond ad_lacp_active AD_LACP_ACTIVE`. pub fn ad_lacp_active(mut self, ad_lacp_active: u8) -> Self { self.info_data.push(InfoBond::AdLacpActive(ad_lacp_active)); self } /// Adds the `missed_max` attribute to the bond /// This is equivalent to `ip link add name NAME type bond missed_max MISSED_MAX`. pub fn missed_max(mut self, missed_max: u8) -> Self { self.info_data.push(InfoBond::MissedMax(missed_max)); self } /// Adds the `arp_ip_target` attribute to the bond /// This is equivalent to `ip link add name NAME type bond arp_ip_target LIST`. pub fn arp_ip_target(mut self, arp_ip_target: Vec) -> Self { self.info_data.push(InfoBond::ArpIpTarget(arp_ip_target)); self } /// Adds the `ns_ip6_target` attribute to the bond /// This is equivalent to `ip link add name NAME type bond ns_ip6_target LIST`. pub fn ns_ip6_target(mut self, ns_ip6_target: Vec) -> Self { self.info_data.push(InfoBond::NsIp6Target(ns_ip6_target)); self } } /// A request to create a new vxlan link. /// This is equivalent to `ip link add NAME vxlan id ID ...` commands. /// It provides methods to customize the creation of the vxlan interface /// It provides almost all parameters that are listed by `man ip link`. pub struct VxlanAddRequest { request: LinkAddRequest, info_data: Vec, } impl VxlanAddRequest { /// Execute the request. pub async fn execute(self) -> Result<(), Error> { let s = self .request .link_info(InfoKind::Vxlan, Some(InfoData::Vxlan(self.info_data))); s.execute().await } /// Sets the interface up /// This is equivalent to `ip link set up dev NAME`. pub fn up(mut self) -> Self { self.request = self.request.up(); self } /// Adds the `dev` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI dev LINK`, /// dev LINK - specifies the physical device to use /// for tunnel endpoint communication. /// But instead of specifing a link name (`LINK`), we specify a link index. pub fn link(mut self, index: u32) -> Self { self.info_data.push(InfoVxlan::Link(index)); self } /// Adds the `dstport` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI dstport PORT`. /// dstport PORT - specifies the UDP destination port to /// communicate to the remote VXLAN tunnel endpoint. pub fn port(mut self, port: u16) -> Self { self.info_data.push(InfoVxlan::Port(port)); self } /// Adds the `group` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI group IPADDR`, /// group IPADDR - specifies the multicast IP address to join. /// This function takes an IPv4 address /// WARNING: only one between `remote` and `group` can be present. pub fn group(mut self, addr: std::net::Ipv4Addr) -> Self { self.info_data .push(InfoVxlan::Group(addr.octets().to_vec())); self } /// Adds the `group` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI group IPADDR`, /// group IPADDR - specifies the multicast IP address to join. /// This function takes an IPv6 address /// WARNING: only one between `remote` and `group` can be present. pub fn group6(mut self, addr: std::net::Ipv6Addr) -> Self { self.info_data .push(InfoVxlan::Group6(addr.octets().to_vec())); self } /// Adds the `remote` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI remote IPADDR`, /// remote IPADDR - specifies the unicast destination IP /// address to use in outgoing packets when the /// destination link layer address is not known in the /// VXLAN device forwarding database. /// This function takes an IPv4 address. /// WARNING: only one between `remote` and `group` can be present. pub fn remote(self, addr: std::net::Ipv4Addr) -> Self { self.group(addr) } /// Adds the `remote` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI remote IPADDR`, /// remote IPADDR - specifies the unicast destination IP /// address to use in outgoing packets when the /// destination link layer address is not known in the /// VXLAN device forwarding database. /// This function takes an IPv6 address. /// WARNING: only one between `remote` and `group` can be present. pub fn remote6(self, addr: std::net::Ipv6Addr) -> Self { self.group6(addr) } /// Adds the `local` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI local IPADDR`, /// local IPADDR - specifies the source IP address to use in outgoing packets. /// This function takes an IPv4 address. pub fn local(mut self, addr: std::net::Ipv4Addr) -> Self { self.info_data .push(InfoVxlan::Local(addr.octets().to_vec())); self } /// Adds the `local` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI local IPADDR`, /// local IPADDR - specifies the source IP address to use in outgoing packets. /// This function takes an IPv6 address. pub fn local6(mut self, addr: std::net::Ipv6Addr) -> Self { self.info_data .push(InfoVxlan::Local6(addr.octets().to_vec())); self } /// Adds the `tos` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI tos TOS`. /// tos TOS - specifies the TOS value to use in outgoing packets. pub fn tos(mut self, tos: u8) -> Self { self.info_data.push(InfoVxlan::Tos(tos)); self } /// Adds the `ttl` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI ttl TTL`. /// ttl TTL - specifies the TTL value to use in outgoing packets. pub fn ttl(mut self, ttl: u8) -> Self { self.info_data.push(InfoVxlan::Ttl(ttl)); self } /// Adds the `flowlabel` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI flowlabel LABEL`. /// flowlabel LABEL - specifies the flow label to use in outgoing packets. pub fn label(mut self, label: u32) -> Self { self.info_data.push(InfoVxlan::Label(label)); self } /// Adds the `learning` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI [no]learning`. /// [no]learning - specifies if unknown source link layer /// addresses and IP addresses are entered into the VXLAN /// device forwarding database. pub fn learning(mut self, learning: u8) -> Self { self.info_data.push(InfoVxlan::Learning(learning)); self } /// Adds the `ageing` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI ageing SECONDS`. /// ageing SECONDS - specifies the lifetime in seconds of /// FDB entries learnt by the kernel. pub fn ageing(mut self, seconds: u32) -> Self { self.info_data.push(InfoVxlan::Ageing(seconds)); self } /// Adds the `maxaddress` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI maxaddress LIMIT`. /// maxaddress LIMIT - specifies the maximum number of /// FDB entries. pub fn limit(mut self, limit: u32) -> Self { self.info_data.push(InfoVxlan::Limit(limit)); self } /// Adds the `srcport` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI srcport MIN MAX`. /// srcport MIN MAX - specifies the range of port numbers /// to use as UDP source ports to communicate to the /// remote VXLAN tunnel endpoint. pub fn port_range(mut self, min: u16, max: u16) -> Self { self.info_data.push(InfoVxlan::PortRange((min, max))); self } /// Adds the `proxy` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI [no]proxy`. /// [no]proxy - specifies ARP proxy is turned on. pub fn proxy(mut self, proxy: u8) -> Self { self.info_data.push(InfoVxlan::Proxy(proxy)); self } /// Adds the `rsc` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI [no]rsc`. /// [no]rsc - specifies if route short circuit is turned on. pub fn rsc(mut self, rsc: u8) -> Self { self.info_data.push(InfoVxlan::Rsc(rsc)); self } // Adds the `l2miss` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI [no]l2miss`. /// [no]l2miss - specifies if netlink LLADDR miss notifications are generated. pub fn l2miss(mut self, l2miss: u8) -> Self { self.info_data.push(InfoVxlan::L2Miss(l2miss)); self } // Adds the `l3miss` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI [no]l3miss`. /// [no]l3miss - specifies if netlink IP ADDR miss notifications are generated. pub fn l3miss(mut self, l3miss: u8) -> Self { self.info_data.push(InfoVxlan::L3Miss(l3miss)); self } pub fn collect_metadata(mut self, collect_metadata: u8) -> Self { self.info_data .push(InfoVxlan::CollectMetadata(collect_metadata)); self } // Adds the `udp_csum` attribute to the VXLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI [no]udp_csum`. /// [no]udpcsum - specifies if UDP checksum is calculated for transmitted packets over IPv4. pub fn udp_csum(mut self, udp_csum: u8) -> Self { self.info_data.push(InfoVxlan::UDPCsum(udp_csum)); self } } /// A request to create a new link. This is equivalent to the `ip link add` commands. /// /// A few methods for common actions (creating a veth pair, creating a vlan interface, etc.) are /// provided, but custom requests can be made using the [`message_mut()`](#method.message_mut) /// accessor. pub struct LinkAddRequest { handle: Handle, message: LinkMessage, replace: bool, } impl LinkAddRequest { pub(crate) fn new(handle: Handle) -> Self { LinkAddRequest { handle, message: LinkMessage::default(), replace: false, } } /// Execute the request. pub async fn execute(self) -> Result<(), Error> { let LinkAddRequest { mut handle, message, replace, } = self; let mut req = NetlinkMessage::from(RtnlMessage::NewLink(message)); let replace = if replace { NLM_F_REPLACE } else { NLM_F_EXCL }; req.header.flags = NLM_F_REQUEST | NLM_F_ACK | replace | NLM_F_CREATE; let mut response = handle.request(req)?; while let Some(message) = response.next().await { try_nl!(message); } Ok(()) } /// Return a mutable reference to the request message. /// /// # Example /// /// Let's say we want to create a vlan interface on a link with id 6. By default, the /// [`vlan()`](#method.vlan) method would create a request with the `IFF_UP` link set, so that the /// interface is up after creation. If we want to create a interface tha tis down by default we /// could do: /// /// ```rust,no_run /// use futures::Future; /// use rtnetlink::{Handle, new_connection, packet::IFF_UP}; /// /// async fn run(handle: Handle) -> Result<(), String> { /// let vlan_id = 100; /// let link_id = 6; /// let mut request = handle.link().add().vlan("my-vlan-itf".into(), link_id, vlan_id); /// // unset the IFF_UP flag before sending the request /// request.message_mut().header.flags &= !IFF_UP; /// request.message_mut().header.change_mask &= !IFF_UP; /// // send the request /// request.execute().await.map_err(|e| format!("{}", e)) /// } pub fn message_mut(&mut self) -> &mut LinkMessage { &mut self.message } /// Create a dummy link. /// This is equivalent to `ip link add NAME type dummy`. pub fn dummy(self, name: String) -> Self { self.name(name).link_info(InfoKind::Dummy, None).up() } /// Create a veth pair. /// This is equivalent to `ip link add NAME1 type veth peer name NAME2`. pub fn veth(self, name: String, peer_name: String) -> Self { // NOTE: `name` is the name of the peer in the netlink message (ie the link created via the // VethInfo::Peer attribute, and `peer_name` is the name in the main netlink message. // This is a bit weird, but it's all hidden from the user. let mut peer = LinkMessage::default(); // FIXME: we get a -107 (ENOTCONN) (???) when trying to set `name` up. // peer.header.flags = LinkFlags::from(IFF_UP); // peer.header.change_mask = LinkFlags::from(IFF_UP); peer.nlas.push(Nla::IfName(name)); let link_info_data = InfoData::Veth(VethInfo::Peer(peer)); self.name(peer_name) .up() // iproute2 does not set this one up .link_info(InfoKind::Veth, Some(link_info_data)) } /// Create VLAN on a link. /// This is equivalent to `ip link add link LINK name NAME type vlan id VLAN_ID`, /// but instead of specifying a link name (`LINK`), we specify a link index. pub fn vlan(self, name: String, index: u32, vlan_id: u16) -> Self { self.name(name) .link_info( InfoKind::Vlan, Some(InfoData::Vlan(vec![InfoVlan::Id(vlan_id)])), ) .append_nla(Nla::Link(index)) .up() } /// Create macvlan on a link. /// This is equivalent to `ip link add name NAME link LINK type macvlan mode MACVLAN_MODE`, /// but instead of specifying a link name (`LINK`), we specify a link index. /// The MACVLAN_MODE is an integer consisting of flags from MACVLAN_MODE (netlink-packet-route/src/rtnl/constants.rs) /// being: _PRIVATE, _VEPA, _BRIDGE, _PASSTHRU, _SOURCE, which can be *combined*. pub fn macvlan(self, name: String, index: u32, mode: u32) -> Self { self.name(name) .link_info( InfoKind::MacVlan, Some(InfoData::MacVlan(vec![InfoMacVlan::Mode(mode)])), ) .append_nla(Nla::Link(index)) .up() } /// Create macvtap on a link. /// This is equivalent to `ip link add name NAME link LINK type macvtap mode MACVTAP_MODE`, /// but instead of specifying a link name (`LINK`), we specify a link index. /// The MACVTAP_MODE is an integer consisting of flags from MACVTAP_MODE (netlink-packet-route/src/rtnl/constants.rs) /// being: _PRIVATE, _VEPA, _BRIDGE, _PASSTHRU, _SOURCE, which can be *combined*. pub fn macvtap(self, name: String, index: u32, mode: u32) -> Self { self.name(name) .link_info( InfoKind::MacVtap, Some(InfoData::MacVtap(vec![InfoMacVtap::Mode(mode)])), ) .append_nla(Nla::Link(index)) .up() } /// Create a VxLAN /// This is equivalent to `ip link add name NAME type vxlan id VNI`, /// it returns a VxlanAddRequest to further customize the vxlan /// interface creation. pub fn vxlan(self, name: String, vni: u32) -> VxlanAddRequest { let s = self.name(name); VxlanAddRequest { request: s, info_data: vec![InfoVxlan::Id(vni)], } } /// Create a new bond. /// This is equivalent to `ip link add link NAME type bond`. pub fn bond(self, name: String) -> BondAddRequest { let s = self.name(name); BondAddRequest { request: s, info_data: vec![], } } /// Create a new bridge. /// This is equivalent to `ip link add link NAME type bridge`. pub fn bridge(self, name: String) -> Self { self.name(name.clone()) .link_info(InfoKind::Bridge, None) .append_nla(Nla::IfName(name)) } /// Replace existing matching link. pub fn replace(self) -> Self { Self { replace: true, ..self } } fn up(mut self) -> Self { self.message.header.flags = IFF_UP; self.message.header.change_mask = IFF_UP; self } fn link_info(self, kind: InfoKind, data: Option) -> Self { let mut link_info_nlas = vec![Info::Kind(kind)]; if let Some(data) = data { link_info_nlas.push(Info::Data(data)); } self.append_nla(Nla::Info(link_info_nlas)) } fn name(mut self, name: String) -> Self { self.message.nlas.push(Nla::IfName(name)); self } fn append_nla(mut self, nla: Nla) -> Self { self.message.nlas.push(nla); self } } ================================================ FILE: rtnetlink/src/link/del.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use crate::{ packet::{LinkMessage, NetlinkMessage, RtnlMessage, NLM_F_ACK, NLM_F_REQUEST}, try_nl, Error, Handle, }; pub struct LinkDelRequest { handle: Handle, message: LinkMessage, } impl LinkDelRequest { pub(crate) fn new(handle: Handle, index: u32) -> Self { let mut message = LinkMessage::default(); message.header.index = index; LinkDelRequest { handle, message } } /// Execute the request pub async fn execute(self) -> Result<(), Error> { let LinkDelRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::DelLink(message)); req.header.flags = NLM_F_REQUEST | NLM_F_ACK; let mut response = handle.request(req)?; while let Some(message) = response.next().await { try_nl!(message) } Ok(()) } /// Return a mutable reference to the request pub fn message_mut(&mut self) -> &mut LinkMessage { &mut self.message } } ================================================ FILE: rtnetlink/src/link/get.rs ================================================ // SPDX-License-Identifier: MIT use futures::{ future::{self, Either}, stream::{StreamExt, TryStream}, FutureExt, }; use crate::{ packet::{constants::*, nlas::link::Nla, LinkMessage, NetlinkMessage, RtnlMessage}, try_rtnl, Error, Handle, }; pub struct LinkGetRequest { handle: Handle, message: LinkMessage, // There are two ways to retrieve links: we can either dump them // all and filter the result, or if we already know the index or // the name of the link we're looking for, we can just retrieve // that one. If `dump` is `true`, all the links are fetched. // Otherwise, only the link that match the given index or name // is fetched. dump: bool, } impl LinkGetRequest { pub(crate) fn new(handle: Handle) -> Self { LinkGetRequest { handle, message: LinkMessage::default(), dump: true, } } /// Setting filter mask(e.g. RTEXT_FILTER_BRVLAN and etc) pub fn set_filter_mask(mut self, family: u8, filter_mask: u32) -> Self { self.message.header.interface_family = family; self.message.nlas.push(Nla::ExtMask(filter_mask)); self } /// Execute the request pub fn execute(self) -> impl TryStream { let LinkGetRequest { mut handle, message, dump, } = self; let mut req = NetlinkMessage::from(RtnlMessage::GetLink(message)); if dump { req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; } else { req.header.flags = NLM_F_REQUEST; } match handle.request(req) { Ok(response) => { Either::Left(response.map(move |msg| Ok(try_rtnl!(msg, RtnlMessage::NewLink)))) } Err(e) => Either::Right(future::err::(e).into_stream()), } } /// Return a mutable reference to the request pub fn message_mut(&mut self) -> &mut LinkMessage { &mut self.message } /// Lookup a link by index pub fn match_index(mut self, index: u32) -> Self { self.dump = false; self.message.header.index = index; self } /// Lookup a link by name /// /// This function requires support from your kernel (>= 2.6.33). If yours is /// older, consider filtering the resulting stream of links. pub fn match_name(mut self, name: String) -> Self { self.dump = false; self.message.nlas.push(Nla::IfName(name)); self } } ================================================ FILE: rtnetlink/src/link/handle.rs ================================================ // SPDX-License-Identifier: MIT use super::{ LinkAddRequest, LinkDelPropRequest, LinkDelRequest, LinkGetRequest, LinkNewPropRequest, LinkSetRequest, }; use crate::Handle; pub struct LinkHandle(Handle); impl LinkHandle { pub fn new(handle: Handle) -> Self { LinkHandle(handle) } pub fn set(&self, index: u32) -> LinkSetRequest { LinkSetRequest::new(self.0.clone(), index) } pub fn add(&self) -> LinkAddRequest { LinkAddRequest::new(self.0.clone()) } pub fn property_add(&self, index: u32) -> LinkNewPropRequest { LinkNewPropRequest::new(self.0.clone(), index) } pub fn property_del(&self, index: u32) -> LinkDelPropRequest { LinkDelPropRequest::new(self.0.clone(), index) } pub fn del(&mut self, index: u32) -> LinkDelRequest { LinkDelRequest::new(self.0.clone(), index) } /// Retrieve the list of links (equivalent to `ip link show`) pub fn get(&mut self) -> LinkGetRequest { LinkGetRequest::new(self.0.clone()) } } ================================================ FILE: rtnetlink/src/link/mod.rs ================================================ // SPDX-License-Identifier: MIT mod handle; pub use self::handle::*; mod add; pub use self::add::*; mod del; pub use self::del::*; mod get; pub use self::get::*; mod set; pub use self::set::*; mod property_add; pub use self::property_add::*; mod property_del; pub use self::property_del::*; #[cfg(test)] mod test; ================================================ FILE: rtnetlink/src/link/property_add.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ packet::{ nlas::link::{Nla, Prop}, LinkMessage, NetlinkMessage, NetlinkPayload, RtnlMessage, NLM_F_ACK, NLM_F_APPEND, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REQUEST, }, Error, Handle, }; use futures::stream::StreamExt; pub struct LinkNewPropRequest { handle: Handle, message: LinkMessage, } impl LinkNewPropRequest { pub(crate) fn new(handle: Handle, index: u32) -> Self { let mut message = LinkMessage::default(); message.header.index = index; LinkNewPropRequest { handle, message } } /// Execute the request pub async fn execute(self) -> Result<(), Error> { let LinkNewPropRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::NewLinkProp(message)); req.header.flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE | NLM_F_APPEND; let mut response = handle.request(req)?; while let Some(message) = response.next().await { if let NetlinkPayload::Error(err) = message.payload { return Err(Error::NetlinkError(err)); } } Ok(()) } /// Return a mutable reference to the request pub fn message_mut(&mut self) -> &mut LinkMessage { &mut self.message } /// Add alternative name to the link. This is equivalent to `ip link property add altname /// ALT_IFNAME dev LINK`. pub fn alt_ifname(mut self, alt_ifnames: &[&str]) -> Self { let mut props = Vec::new(); for alt_ifname in alt_ifnames { props.push(Prop::AltIfName(alt_ifname.to_string())); } self.message.nlas.push(Nla::PropList(props)); self } } ================================================ FILE: rtnetlink/src/link/property_del.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ packet::{ nlas::link::{Nla, Prop}, LinkMessage, NetlinkMessage, NetlinkPayload, RtnlMessage, NLM_F_ACK, NLM_F_EXCL, NLM_F_REQUEST, }, Error, Handle, }; use futures::stream::StreamExt; pub struct LinkDelPropRequest { handle: Handle, message: LinkMessage, } impl LinkDelPropRequest { pub(crate) fn new(handle: Handle, index: u32) -> Self { let mut message = LinkMessage::default(); message.header.index = index; LinkDelPropRequest { handle, message } } /// Execute the request pub async fn execute(self) -> Result<(), Error> { let LinkDelPropRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::DelLinkProp(message)); req.header.flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL; let mut response = handle.request(req)?; while let Some(message) = response.next().await { if let NetlinkPayload::Error(err) = message.payload { return Err(Error::NetlinkError(err)); } } Ok(()) } /// Return a mutable reference to the request pub fn message_mut(&mut self) -> &mut LinkMessage { &mut self.message } /// Remove alternative name to the link. This is equivalent to `ip link property del altname /// ALT_IFNAME dev LINK`. pub fn alt_ifname(mut self, alt_ifnames: &[&str]) -> Self { let mut props = Vec::new(); for alt_ifname in alt_ifnames { props.push(Prop::AltIfName(alt_ifname.to_string())); } self.message.nlas.push(Nla::PropList(props)); self } } ================================================ FILE: rtnetlink/src/link/set.rs ================================================ // SPDX-License-Identifier: MIT use crate::{ packet::{ nlas::link::Nla, LinkMessage, NetlinkMessage, RtnlMessage, IFF_NOARP, IFF_PROMISC, IFF_UP, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REQUEST, }, try_nl, Error, Handle, }; use futures::stream::StreamExt; use std::os::unix::io::RawFd; pub struct LinkSetRequest { handle: Handle, message: LinkMessage, } impl LinkSetRequest { pub(crate) fn new(handle: Handle, index: u32) -> Self { let mut message = LinkMessage::default(); message.header.index = index; LinkSetRequest { handle, message } } /// Execute the request pub async fn execute(self) -> Result<(), Error> { let LinkSetRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::SetLink(message)); req.header.flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE; let mut response = handle.request(req)?; while let Some(message) = response.next().await { try_nl!(message); } Ok(()) } /// Return a mutable reference to the request pub fn message_mut(&mut self) -> &mut LinkMessage { &mut self.message } /// Attach the link to a bridge (its _master_). This is equivalent to `ip link set LINK master /// BRIDGE`. To succeed, both the bridge and the link that is being attached must be UP. /// /// To Remove a link from a bridge, set its master to zero. /// This is equvalent to `ip link set LINK nomaster` pub fn master(mut self, master_index: u32) -> Self { self.message.nlas.push(Nla::Master(master_index)); self } /// Detach the link from its _master_. This is equivalent to `ip link set LINK nomaster`. ///To succeed, the link that is being detached must be UP. pub fn nomaster(mut self) -> Self { self.message.nlas.push(Nla::Master(0u32)); self } /// Set the link with the given index up (equivalent to `ip link set dev DEV up`) pub fn up(mut self) -> Self { self.message.header.flags |= IFF_UP; self.message.header.change_mask |= 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.message.header.flags &= !IFF_UP; self.message.header.change_mask |= IFF_UP; self } /// Enable or disable promiscious mode of the link with the given index (equivalent to `ip link set dev DEV promisc on/off`) pub fn promiscuous(mut self, enable: bool) -> Self { if enable { self.message.header.flags |= IFF_PROMISC; } else { self.message.header.flags &= !IFF_PROMISC; } self.message.header.change_mask |= IFF_PROMISC; self } /// Enable or disable the ARP protocol of the link with the given index (equivalent to `ip link set dev DEV arp on/off`) pub fn arp(mut self, enable: bool) -> Self { if enable { self.message.header.flags &= !IFF_NOARP; } else { self.message.header.flags |= IFF_NOARP; } self.message.header.change_mask |= IFF_NOARP; self } /// Set the name of the link with the given index (equivalent to `ip link set DEV name NAME`) pub fn name(mut self, name: String) -> Self { self.message.nlas.push(Nla::IfName(name)); self } /// Set the mtu of the link with the given index (equivalent to `ip link set DEV mtu MTU`) pub fn mtu(mut self, mtu: u32) -> Self { self.message.nlas.push(Nla::Mtu(mtu)); self } /// Set the hardware address of the link with the given index (equivalent to `ip link set DEV address ADDRESS`) pub fn address(mut self, address: Vec) -> Self { self.message.nlas.push(Nla::Address(address)); self } /// Move this network device into the network namespace of the process with the given `pid`. pub fn setns_by_pid(mut self, pid: u32) -> Self { self.message.nlas.push(Nla::NetNsPid(pid)); self } /// Move this network device into the network namespace corresponding to the given file /// descriptor. pub fn setns_by_fd(mut self, fd: RawFd) -> Self { self.message.nlas.push(Nla::NetNsFd(fd)); self } } ================================================ FILE: rtnetlink/src/link/test.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; use tokio::runtime::Runtime; use crate::{ new_connection, packet::rtnl::link::{ nlas::{Info, InfoKind, Nla}, LinkMessage, }, Error, LinkHandle, }; const IFACE_NAME: &str = "wg142"; // rand? #[test] fn create_get_delete_wg() { let rt = Runtime::new().unwrap(); let handle = rt.block_on(_create_wg()); assert!(handle.is_ok()); let mut handle = handle.unwrap(); let msg = rt.block_on(_get_wg(&mut handle)); assert!(msg.is_ok()); let msg = msg.unwrap(); assert!(has_nla( &msg, &Nla::Info(vec![Info::Kind(InfoKind::Wireguard)]) )); rt.block_on(_del_wg(&mut handle, msg.header.index)).unwrap(); } fn has_nla(msg: &LinkMessage, nla: &Nla) -> bool { msg.nlas.iter().any(|x| x == nla) } async fn _create_wg() -> Result { let (conn, handle, _) = new_connection().unwrap(); tokio::spawn(conn); let link_handle = handle.link(); let mut req = link_handle.add(); let mutator = req.message_mut(); let info = Nla::Info(vec![Info::Kind(InfoKind::Wireguard)]); mutator.nlas.push(info); mutator.nlas.push(Nla::IfName(IFACE_NAME.to_owned())); req.execute().await?; Ok(link_handle) } async fn _get_wg(handle: &mut LinkHandle) -> Result { let mut links = handle.get().match_name(IFACE_NAME.to_owned()).execute(); let msg = links.try_next().await?; msg.ok_or(Error::RequestFailed) } async fn _del_wg(handle: &mut LinkHandle, index: u32) -> Result<(), Error> { handle.del(index).execute().await } ================================================ FILE: rtnetlink/src/macros.rs ================================================ // SPDX-License-Identifier: MIT #[macro_export] macro_rules! try_rtnl { ($msg: expr, $message_type:path) => {{ use netlink_packet_route::{NetlinkMessage, NetlinkPayload, RtnlMessage}; use $crate::Error; let (header, payload) = $msg.into_parts(); match payload { NetlinkPayload::InnerMessage($message_type(msg)) => msg, NetlinkPayload::Error(err) => return Err(Error::NetlinkError(err)), _ => { return Err(Error::UnexpectedMessage(NetlinkMessage::new( header, payload, ))) } } }}; } #[macro_export] macro_rules! try_nl { ($msg: expr) => {{ use netlink_packet_route::NetlinkPayload; use $crate::Error; if let NetlinkPayload::Error(err) = $msg.payload { return Err(Error::NetlinkError(err)); } }}; } ================================================ FILE: rtnetlink/src/neighbour/add.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use netlink_packet_route::{ constants::*, neighbour::{NeighbourMessage, Nla}, NetlinkPayload, RtnlMessage, }; use netlink_proto::packet::NetlinkMessage; use crate::{Error, Handle}; use std::net::IpAddr; pub struct NeighbourAddRequest { handle: Handle, message: NeighbourMessage, replace: bool, } impl NeighbourAddRequest { pub(crate) fn new(handle: Handle, index: u32, destination: IpAddr) -> Self { let mut message = NeighbourMessage::default(); message.header.family = match destination { IpAddr::V4(_) => AF_INET as u8, IpAddr::V6(_) => AF_INET6 as u8, }; message.header.ifindex = index; message.header.state = IFA_F_PERMANENT as u16; message.header.ntype = NDA_UNSPEC as u8; message.nlas.push(Nla::Destination(match destination { IpAddr::V4(v4) => v4.octets().to_vec(), IpAddr::V6(v6) => v6.octets().to_vec(), })); NeighbourAddRequest { handle, message, replace: false, } } pub(crate) fn new_bridge(handle: Handle, index: u32, lla: &[u8]) -> Self { let mut message = NeighbourMessage::default(); message.header.family = AF_BRIDGE as u8; message.header.ifindex = index; message.header.state = NUD_PERMANENT; message.header.ntype = NDA_UNSPEC as u8; message.nlas.push(Nla::LinkLocalAddress(lla.to_vec())); NeighbourAddRequest { handle, message, replace: false, } } /// Set a bitmask of states for the neighbor cache entry. /// It should be a combination of `NUD_*` constants. pub fn state(mut self, state: u16) -> Self { self.message.header.state = state; self } /// Set flags for the neighbor cache entry. /// It should be a combination of `NTF_*` constants. pub fn flags(mut self, flags: u8) -> Self { self.message.header.flags = flags; self } /// Set attributes applicable to the the neighbor cache entry. /// It should be one of `NDA_*` constants. pub fn ntype(mut self, ntype: u8) -> Self { self.message.header.ntype = ntype; self } /// Set a neighbor cache link layer address (see `NDA_LLADDR` for details). pub fn link_local_address(mut self, addr: &[u8]) -> Self { let lla = self.message.nlas.iter_mut().find_map(|nla| match nla { Nla::LinkLocalAddress(lla) => Some(lla), _ => None, }); if let Some(lla) = lla { *lla = addr.to_vec(); } else { self.message.nlas.push(Nla::LinkLocalAddress(addr.to_vec())); } self } /// Set the destination address for the neighbour (see `NDA_DST` for details). pub fn destination(mut self, addr: IpAddr) -> Self { let dst = self.message.nlas.iter_mut().find_map(|nla| match nla { Nla::Destination(dst) => Some(dst), _ => None, }); let addr = match addr { IpAddr::V4(v4) => v4.octets().to_vec(), IpAddr::V6(v6) => v6.octets().to_vec(), }; if let Some(dst) = dst { *dst = addr; } else { self.message.nlas.push(Nla::Destination(addr)); } self } /// Replace existing matching neighbor. pub fn replace(self) -> Self { Self { replace: true, ..self } } /// Execute the request. pub async fn execute(self) -> Result<(), Error> { let NeighbourAddRequest { mut handle, message, replace, } = self; let mut req = NetlinkMessage::from(RtnlMessage::NewNeighbour(message)); let replace = if replace { NLM_F_REPLACE } else { NLM_F_EXCL }; req.header.flags = NLM_F_REQUEST | NLM_F_ACK | replace | NLM_F_CREATE; let mut response = handle.request(req)?; while let Some(message) = response.next().await { if let NetlinkPayload::Error(err) = message.payload { return Err(Error::NetlinkError(err)); } } Ok(()) } /// Return a mutable reference to the request message. pub fn message_mut(&mut self) -> &mut NeighbourMessage { &mut self.message } } ================================================ FILE: rtnetlink/src/neighbour/del.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use netlink_packet_route::{ constants::*, neighbour::NeighbourMessage, NetlinkPayload, RtnlMessage, }; use netlink_proto::packet::NetlinkMessage; use crate::{Error, Handle}; pub struct NeighbourDelRequest { handle: Handle, message: NeighbourMessage, } impl NeighbourDelRequest { pub(crate) fn new(handle: Handle, message: NeighbourMessage) -> Self { NeighbourDelRequest { handle, message } } /// Execute the request pub async fn execute(self) -> Result<(), Error> { let NeighbourDelRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::DelNeighbour(message)); req.header.flags = NLM_F_REQUEST | NLM_F_ACK; let mut response = handle.request(req)?; while let Some(msg) = response.next().await { if let NetlinkPayload::Error(e) = msg.payload { return Err(Error::NetlinkError(e)); } } Ok(()) } pub fn message_mut(&mut self) -> &mut NeighbourMessage { &mut self.message } } ================================================ FILE: rtnetlink/src/neighbour/get.rs ================================================ // SPDX-License-Identifier: MIT use futures::{ future::{self, Either}, stream::{StreamExt, TryStream}, FutureExt, }; use netlink_packet_route::{ constants::*, neighbour::NeighbourMessage, NetlinkPayload, RtnlMessage, }; use netlink_proto::packet::NetlinkMessage; use crate::{Error, Handle, IpVersion}; pub struct NeighbourGetRequest { handle: Handle, message: NeighbourMessage, } impl NeighbourGetRequest { pub(crate) fn new(handle: Handle) -> Self { let message = NeighbourMessage::default(); NeighbourGetRequest { handle, message } } /// List neighbor proxies in the system (equivalent to: `ip neighbor show proxy`). pub fn proxies(mut self) -> Self { self.message.header.flags |= NTF_PROXY; self } pub fn set_family(mut self, ip_version: IpVersion) -> Self { self.message.header.family = ip_version.family(); self } /// Execute the request pub fn execute(self) -> impl TryStream { let NeighbourGetRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::GetNeighbour(message)); req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; match handle.request(req) { Ok(response) => Either::Left(response.map(move |msg| { let (header, payload) = msg.into_parts(); match payload { NetlinkPayload::InnerMessage(RtnlMessage::NewNeighbour(msg)) => Ok(msg), NetlinkPayload::Error(err) => Err(Error::NetlinkError(err)), _ => Err(Error::UnexpectedMessage(NetlinkMessage::new( header, payload, ))), } })), Err(e) => Either::Right(future::err::(e).into_stream()), } } /// Return a mutable reference to the request pub fn message_mut(&mut self) -> &mut NeighbourMessage { &mut self.message } } ================================================ FILE: rtnetlink/src/neighbour/handle.rs ================================================ // SPDX-License-Identifier: MIT use crate::{Handle, NeighbourAddRequest, NeighbourDelRequest, NeighbourGetRequest}; use netlink_packet_route::NeighbourMessage; use std::net::IpAddr; pub struct NeighbourHandle(Handle); impl NeighbourHandle { pub fn new(handle: Handle) -> Self { NeighbourHandle(handle) } /// List neighbour entries (equivalent to `ip neighbour show`) pub fn get(&self) -> NeighbourGetRequest { NeighbourGetRequest::new(self.0.clone()) } /// Add a new neighbour entry (equivalent to `ip neighbour add`) pub fn add(&self, index: u32, destination: IpAddr) -> NeighbourAddRequest { NeighbourAddRequest::new(self.0.clone(), index, destination) } /// Add a new fdb entry (equivalent to `bridge fdb add`) pub fn add_bridge(&self, index: u32, lla: &[u8]) -> NeighbourAddRequest { NeighbourAddRequest::new_bridge(self.0.clone(), index, lla) } /// Delete a neighbour entry (equivalent to `ip neighbour delete`) pub fn del(&self, message: NeighbourMessage) -> NeighbourDelRequest { NeighbourDelRequest::new(self.0.clone(), message) } } ================================================ FILE: rtnetlink/src/neighbour/mod.rs ================================================ // SPDX-License-Identifier: MIT mod handle; pub use self::handle::*; mod get; pub use self::get::*; mod add; pub use self::add::*; mod del; pub use self::del::*; ================================================ FILE: rtnetlink/src/ns.rs ================================================ // SPDX-License-Identifier: MIT use crate::Error; use nix::{ fcntl::OFlag, sched::CloneFlags, sys::{ stat::Mode, wait::{waitpid, WaitStatus}, }, unistd::{fork, ForkResult}, }; use std::{option::Option, path::Path, process::exit}; // if "only" smol or smol+tokio were enabled, we use smol because // it doesn't require an active tokio runtime - just to be sure. #[cfg(feature = "smol_socket")] async fn try_spawn_blocking(fut: F) -> R where F: FnOnce() -> R + Send + 'static, R: Send + 'static, { async_global_executor::spawn_blocking(fut).await } // only tokio enabled, so use tokio #[cfg(all(not(feature = "smol_socket"), feature = "tokio_socket"))] async fn try_spawn_blocking(fut: F) -> R where F: FnOnce() -> R + Send + 'static, R: Send + 'static, { match tokio::task::spawn_blocking(fut).await { Ok(v) => v, Err(err) => { std::panic::resume_unwind(err.into_panic()); } } } // neither smol nor tokio - just run blocking op directly. // hopefully not too blocking... #[cfg(all(not(feature = "smol_socket"), not(feature = "tokio_socket")))] async fn try_spawn_blocking(fut: F) -> R where F: FnOnce() -> R + Send + 'static, R: Send + 'static, { fut() } pub const NETNS_PATH: &str = "/run/netns/"; pub const SELF_NS_PATH: &str = "/proc/self/ns/net"; pub const NONE_FS: &str = "none"; pub struct NetworkNamespace(); impl NetworkNamespace { /// Add a new network namespace. /// This is equivalent to `ip netns add NS_NAME`. pub async fn add(ns_name: String) -> Result<(), Error> { // Forking process to avoid moving caller into new namespace NetworkNamespace::prep_for_fork()?; log::trace!("Forking..."); match unsafe { fork() } { Ok(ForkResult::Parent { child, .. }) => NetworkNamespace::parent_process(child), Ok(ForkResult::Child) => { NetworkNamespace::child_process(ns_name); } Err(e) => { let err_msg = format!("Fork failed: {}", e); Err(Error::NamespaceError(err_msg)) } } } /// Remove a network namespace /// This is equivalent to `ip netns del NS_NAME`. pub async fn del(ns_name: String) -> Result<(), Error> { try_spawn_blocking(move || { let mut netns_path = String::new(); netns_path.push_str(NETNS_PATH); netns_path.push_str(&ns_name); let ns_path = Path::new(&netns_path); if nix::mount::umount2(ns_path, nix::mount::MntFlags::MNT_DETACH).is_err() { let err_msg = String::from("Namespace unmount failed (are you running as root?)"); return Err(Error::NamespaceError(err_msg)); } if nix::unistd::unlink(ns_path).is_err() { let err_msg = String::from("Namespace file remove failed (are you running as root?)"); return Err(Error::NamespaceError(err_msg)); } Ok(()) }) .await } pub fn prep_for_fork() -> Result<(), Error> { // Placeholder function, nothing to do here. Ok(()) } /// This is the parent process form the fork, it waits for the /// child to exit properly pub fn parent_process(child: nix::unistd::Pid) -> Result<(), Error> { log::trace!("parent_process child PID: {}", child); log::trace!("Waiting for child to finish..."); match waitpid(child, None) { Ok(wait_status) => match wait_status { WaitStatus::Exited(_, res) => { log::trace!("Child exited with: {}", res); if res == 0 { return Ok(()); } log::error!("Error child result: {}", res); let err_msg = format!("Error child result: {}", res); Err(Error::NamespaceError(err_msg)) } WaitStatus::Signaled(_, signal, has_dump) => { log::error!("Error child killed by signal: {}", signal); let err_msg = format!( "Error child process was killed by signal: {} with core dump {}", signal, has_dump ); Err(Error::NamespaceError(err_msg)) } _ => { log::error!("Unknown child process status"); let err_msg = String::from("Unknown child process status"); Err(Error::NamespaceError(err_msg)) } }, Err(e) => { log::error!("wait error: {}", e); let err_msg = format!("wait error: {}", e); Err(Error::NamespaceError(err_msg)) } } } fn child_process(ns_name: String) -> ! { let res = std::panic::catch_unwind(|| -> Result<(), Error> { let netns_path = NetworkNamespace::child_process_create_ns(ns_name)?; NetworkNamespace::unshare_processing(netns_path)?; Ok(()) }); match res { Err(_panic) => { // panic should have already been printed by the handler log::error!("child process crashed"); std::process::abort() } Ok(Err(fail)) => { log::error!("child process failed: {}", fail); exit(1) } Ok(Ok(())) => exit(0), } } /// This is the child process, it will actually create the namespace /// resources. It creates the folder and namespace file. /// Returns the namespace file path pub fn child_process_create_ns(ns_name: String) -> Result { log::trace!("child_process will create the namespace"); let mut netns_path = String::new(); let dir_path = Path::new(NETNS_PATH); let mut mkdir_mode = Mode::empty(); let mut open_flags = OFlag::empty(); let mut mount_flags = nix::mount::MsFlags::empty(); let none_fs = Path::new(&NONE_FS); let none_p4: Option<&Path> = None; // flags in mkdir mkdir_mode.insert(Mode::S_IRWXU); mkdir_mode.insert(Mode::S_IRGRP); mkdir_mode.insert(Mode::S_IXGRP); mkdir_mode.insert(Mode::S_IROTH); mkdir_mode.insert(Mode::S_IXOTH); open_flags.insert(OFlag::O_RDONLY); open_flags.insert(OFlag::O_CREAT); open_flags.insert(OFlag::O_EXCL); netns_path.push_str(NETNS_PATH); netns_path.push_str(&ns_name); // creating namespaces folder if not exists #[allow(clippy::collapsible_if)] if nix::sys::stat::stat(dir_path).is_err() { if let Err(e) = nix::unistd::mkdir(dir_path, mkdir_mode) { log::error!("mkdir error: {}", e); let err_msg = format!("mkdir error: {}", e); return Err(Error::NamespaceError(err_msg)); } } // Try to mount /run/netns, with MS_REC | MS_SHARED // If it fails, creates the mount with MS_BIND | MS_REC // This is the same strategy used by `ip netns add NS` mount_flags.insert(nix::mount::MsFlags::MS_REC); mount_flags.insert(nix::mount::MsFlags::MS_SHARED); if nix::mount::mount( Some(Path::new("")), dir_path, Some(none_fs), mount_flags, none_p4, ) .is_err() { mount_flags = nix::mount::MsFlags::empty(); mount_flags.insert(nix::mount::MsFlags::MS_BIND); mount_flags.insert(nix::mount::MsFlags::MS_REC); if let Err(e) = nix::mount::mount( Some(Path::new(dir_path)), dir_path, Some(none_fs), mount_flags, none_p4, ) { log::error!("mount error: {}", e); let err_msg = format!("mount error: {}", e); return Err(Error::NamespaceError(err_msg)); } } mount_flags = nix::mount::MsFlags::empty(); mount_flags.insert(nix::mount::MsFlags::MS_REC); mount_flags.insert(nix::mount::MsFlags::MS_SHARED); if let Err(e) = nix::mount::mount( Some(Path::new("")), dir_path, Some(none_fs), mount_flags, none_p4, ) { log::error!("mount error: {}", e); let err_msg = format!("mount error: {}", e); return Err(Error::NamespaceError(err_msg)); } let ns_path = Path::new(&netns_path); // creating the netns file let fd = match nix::fcntl::open(ns_path, open_flags, Mode::empty()) { Ok(raw_fd) => raw_fd, Err(e) => { log::error!("open error: {}", e); let err_msg = format!("open error: {}", e); return Err(Error::NamespaceError(err_msg)); } }; if let Err(e) = nix::unistd::close(fd) { log::error!("close error: {}", e); let err_msg = format!("close error: {}", e); let _ = nix::unistd::unlink(ns_path); return Err(Error::NamespaceError(err_msg)); } Ok(netns_path) } /// This function unshare the calling process and move into /// the given network namespace #[allow(unused)] pub fn unshare_processing(netns_path: String) -> Result<(), Error> { let mut setns_flags = CloneFlags::empty(); let mut open_flags = OFlag::empty(); let ns_path = Path::new(&netns_path); let none_fs = Path::new(&NONE_FS); let none_p4: Option<&Path> = None; // unshare to the new network namespace if let Err(e) = nix::sched::unshare(CloneFlags::CLONE_NEWNET) { log::error!("unshare error: {}", e); let err_msg = format!("unshare error: {}", e); let _ = nix::unistd::unlink(ns_path); return Err(Error::NamespaceError(err_msg)); } open_flags = OFlag::empty(); open_flags.insert(OFlag::O_RDONLY); open_flags.insert(OFlag::O_CLOEXEC); let fd = match nix::fcntl::open(Path::new(&SELF_NS_PATH), open_flags, Mode::empty()) { Ok(raw_fd) => raw_fd, Err(e) => { log::error!("open error: {}", e); let err_msg = format!("open error: {}", e); return Err(Error::NamespaceError(err_msg)); } }; let self_path = Path::new(&SELF_NS_PATH); // bind to the netns if let Err(e) = nix::mount::mount( Some(self_path), ns_path, Some(none_fs), nix::mount::MsFlags::MS_BIND, none_p4, ) { log::error!("mount error: {}", e); let err_msg = format!("mount error: {}", e); let _ = nix::unistd::unlink(ns_path); return Err(Error::NamespaceError(err_msg)); } setns_flags.insert(CloneFlags::CLONE_NEWNET); if let Err(e) = nix::sched::setns(fd, setns_flags) { log::error!("setns error: {}", e); let err_msg = format!("setns error: {}", e); let _ = nix::unistd::unlink(ns_path); return Err(Error::NamespaceError(err_msg)); } Ok(()) } } ================================================ FILE: rtnetlink/src/route/add.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use std::{ marker::PhantomData, net::{Ipv4Addr, Ipv6Addr}, }; use netlink_packet_route::{ constants::*, nlas::route::Nla, NetlinkMessage, RouteMessage, RtnlMessage, }; use crate::{try_nl, Error, Handle}; /// A request to create a new route. This is equivalent to the `ip route add` commands. pub struct RouteAddRequest { handle: Handle, message: RouteMessage, replace: bool, _phantom: PhantomData, } impl RouteAddRequest { pub(crate) fn new(handle: Handle) -> Self { let mut message = RouteMessage::default(); message.header.table = RT_TABLE_MAIN; message.header.protocol = RTPROT_STATIC; message.header.scope = RT_SCOPE_UNIVERSE; message.header.kind = RTN_UNICAST; RouteAddRequest { handle, message, replace: false, _phantom: Default::default(), } } /// Sets the input interface index. pub fn input_interface(mut self, index: u32) -> Self { self.message.nlas.push(Nla::Iif(index)); self } /// Sets the output interface index. pub fn output_interface(mut self, index: u32) -> Self { self.message.nlas.push(Nla::Oif(index)); self } /// Sets the route table. /// /// Default is main route table. pub fn table(mut self, table: u8) -> Self { self.message.header.table = table; self } /// Sets the route protocol. /// /// Default is static route protocol. pub fn protocol(mut self, protocol: u8) -> Self { self.message.header.protocol = protocol; self } /// Sets the route scope. /// /// Default is universe route scope. pub fn scope(mut self, scope: u8) -> Self { self.message.header.scope = scope; self } /// Sets the route kind. /// /// Default is unicast route kind. pub fn kind(mut self, kind: u8) -> Self { self.message.header.kind = kind; self } /// Build an IP v4 route request pub fn v4(mut self) -> RouteAddRequest { self.message.header.address_family = AF_INET as u8; RouteAddRequest { handle: self.handle, message: self.message, replace: false, _phantom: Default::default(), } } /// Build an IP v6 route request pub fn v6(mut self) -> RouteAddRequest { self.message.header.address_family = AF_INET6 as u8; RouteAddRequest { handle: self.handle, message: self.message, replace: false, _phantom: Default::default(), } } /// Replace existing matching route. pub fn replace(self) -> Self { Self { replace: true, ..self } } /// Execute the request. pub async fn execute(self) -> Result<(), Error> { let RouteAddRequest { mut handle, message, replace, .. } = self; let mut req = NetlinkMessage::from(RtnlMessage::NewRoute(message)); let replace = if replace { NLM_F_REPLACE } else { NLM_F_EXCL }; req.header.flags = NLM_F_REQUEST | NLM_F_ACK | replace | NLM_F_CREATE; let mut response = handle.request(req)?; while let Some(message) = response.next().await { try_nl!(message); } Ok(()) } /// Return a mutable reference to the request message. pub fn message_mut(&mut self) -> &mut RouteMessage { &mut self.message } } impl RouteAddRequest { /// Sets the source address prefix. pub fn source_prefix(mut self, addr: Ipv4Addr, prefix_length: u8) -> Self { self.message.header.source_prefix_length = prefix_length; let src = addr.octets().to_vec(); self.message.nlas.push(Nla::Source(src)); self } /// Sets the preferred source address. pub fn pref_source(mut self, addr: Ipv4Addr) -> Self { let src = addr.octets().to_vec(); self.message.nlas.push(Nla::PrefSource(src)); self } /// Sets the destination address prefix. pub fn destination_prefix(mut self, addr: Ipv4Addr, prefix_length: u8) -> Self { self.message.header.destination_prefix_length = prefix_length; let dst = addr.octets().to_vec(); self.message.nlas.push(Nla::Destination(dst)); self } /// Sets the gateway (via) address. pub fn gateway(mut self, addr: Ipv4Addr) -> Self { let gtw = addr.octets().to_vec(); self.message.nlas.push(Nla::Gateway(gtw)); self } } impl RouteAddRequest { /// Sets the source address prefix. pub fn source_prefix(mut self, addr: Ipv6Addr, prefix_length: u8) -> Self { self.message.header.source_prefix_length = prefix_length; let src = addr.octets().to_vec(); self.message.nlas.push(Nla::Source(src)); self } /// Sets the preferred source address. pub fn pref_source(mut self, addr: Ipv6Addr) -> Self { let src = addr.octets().to_vec(); self.message.nlas.push(Nla::PrefSource(src)); self } /// Sets the destination address prefix. pub fn destination_prefix(mut self, addr: Ipv6Addr, prefix_length: u8) -> Self { self.message.header.destination_prefix_length = prefix_length; let dst = addr.octets().to_vec(); self.message.nlas.push(Nla::Destination(dst)); self } /// Sets the gateway (via) address. pub fn gateway(mut self, addr: Ipv6Addr) -> Self { let gtw = addr.octets().to_vec(); self.message.nlas.push(Nla::Gateway(gtw)); self } } ================================================ FILE: rtnetlink/src/route/del.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use crate::{ packet::{NetlinkMessage, NetlinkPayload, RouteMessage, RtnlMessage, NLM_F_ACK, NLM_F_REQUEST}, Error, Handle, }; pub struct RouteDelRequest { handle: Handle, message: RouteMessage, } impl RouteDelRequest { pub(crate) fn new(handle: Handle, message: RouteMessage) -> Self { RouteDelRequest { handle, message } } /// Execute the request pub async fn execute(self) -> Result<(), Error> { let RouteDelRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::DelRoute(message)); req.header.flags = NLM_F_REQUEST | NLM_F_ACK; let mut response = handle.request(req)?; while let Some(msg) = response.next().await { if let NetlinkPayload::Error(e) = msg.payload { return Err(Error::NetlinkError(e)); } } Ok(()) } pub fn message_mut(&mut self) -> &mut RouteMessage { &mut self.message } } ================================================ FILE: rtnetlink/src/route/get.rs ================================================ // SPDX-License-Identifier: MIT use futures::{ future::{self, Either}, stream::{StreamExt, TryStream}, FutureExt, }; use netlink_packet_route::{constants::*, NetlinkMessage, RouteMessage, RtnlMessage}; use crate::{try_rtnl, Error, Handle}; pub struct RouteGetRequest { handle: Handle, message: RouteMessage, } /// Internet Protocol (IP) version. #[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub enum IpVersion { /// IPv4 V4, /// IPv6 V6, } impl IpVersion { pub(crate) fn family(self) -> u8 { match self { IpVersion::V4 => AF_INET as u8, IpVersion::V6 => AF_INET6 as u8, } } } impl RouteGetRequest { pub(crate) fn new(handle: Handle, ip_version: IpVersion) -> Self { let mut message = RouteMessage::default(); message.header.address_family = ip_version.family(); // As per rtnetlink(7) documentation, setting the following // fields to 0 gets us all the routes from all the tables // // > For RTM_GETROUTE, setting rtm_dst_len and rtm_src_len to 0 // > means you get all entries for the specified routing table. // > For the other fields, except rtm_table and rtm_protocol, 0 // > is the wildcard. message.header.destination_prefix_length = 0; message.header.source_prefix_length = 0; message.header.scope = RT_SCOPE_UNIVERSE; message.header.kind = RTN_UNSPEC; // I don't know if these two fields matter message.header.table = RT_TABLE_UNSPEC; message.header.protocol = RTPROT_UNSPEC; RouteGetRequest { handle, message } } pub fn message_mut(&mut self) -> &mut RouteMessage { &mut self.message } pub fn execute(self) -> impl TryStream { let RouteGetRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::GetRoute(message)); req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; match handle.request(req) { Ok(response) => { Either::Left(response.map(move |msg| Ok(try_rtnl!(msg, RtnlMessage::NewRoute)))) } Err(e) => Either::Right(future::err::(e).into_stream()), } } } ================================================ FILE: rtnetlink/src/route/handle.rs ================================================ // SPDX-License-Identifier: MIT use crate::{Handle, IpVersion, RouteAddRequest, RouteDelRequest, RouteGetRequest}; use netlink_packet_route::RouteMessage; pub struct RouteHandle(Handle); impl RouteHandle { pub fn new(handle: Handle) -> Self { RouteHandle(handle) } /// Retrieve the list of routing table entries (equivalent to `ip route show`) pub fn get(&self, ip_version: IpVersion) -> RouteGetRequest { RouteGetRequest::new(self.0.clone(), ip_version) } /// Add an routing table entry (equivalent to `ip route add`) pub fn add(&self) -> RouteAddRequest { RouteAddRequest::new(self.0.clone()) } /// Delete the given routing table entry (equivalent to `ip route del`) pub fn del(&self, route: RouteMessage) -> RouteDelRequest { RouteDelRequest::new(self.0.clone(), route) } } ================================================ FILE: rtnetlink/src/route/mod.rs ================================================ // SPDX-License-Identifier: MIT mod handle; pub use self::handle::*; mod add; pub use self::add::*; mod del; pub use self::del::*; mod get; pub use self::get::*; ================================================ FILE: rtnetlink/src/rule/add.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use std::{ marker::PhantomData, net::{Ipv4Addr, Ipv6Addr}, }; use netlink_packet_route::{ constants::*, nlas::rule::Nla, NetlinkMessage, RtnlMessage, RuleMessage, }; use crate::{try_nl, Error, Handle}; /// A request to create a new rule. This is equivalent to the `ip rule add` command. pub struct RuleAddRequest { handle: Handle, message: RuleMessage, replace: bool, _phantom: PhantomData, } impl RuleAddRequest { pub(crate) fn new(handle: Handle) -> Self { let mut message = RuleMessage::default(); message.header.table = RT_TABLE_MAIN; message.header.action = FR_ACT_UNSPEC; RuleAddRequest { handle, message, replace: false, _phantom: Default::default(), } } /// Sets the input interface name. pub fn input_interface(mut self, ifname: String) -> Self { self.message.nlas.push(Nla::Iifname(ifname)); self } /// Sets the output interface name. pub fn output_interface(mut self, ifname: String) -> Self { self.message.nlas.push(Nla::OifName(ifname)); self } /// Sets the rule table. /// /// Default is main rule table. pub fn table(mut self, table: u8) -> Self { self.message.header.table = table; self } /// Set the tos. pub fn tos(mut self, tos: u8) -> Self { self.message.header.tos = tos; self } /// Set action. pub fn action(mut self, action: u8) -> Self { self.message.header.action = action; self } /// Set the priority. pub fn priority(mut self, priority: u32) -> Self { self.message.nlas.push(Nla::Priority(priority)); self } /// Build an IP v4 rule pub fn v4(mut self) -> RuleAddRequest { self.message.header.family = AF_INET as u8; RuleAddRequest { handle: self.handle, message: self.message, replace: false, _phantom: Default::default(), } } /// Build an IP v6 rule pub fn v6(mut self) -> RuleAddRequest { self.message.header.family = AF_INET6 as u8; RuleAddRequest { handle: self.handle, message: self.message, replace: false, _phantom: Default::default(), } } /// Replace existing matching rule. pub fn replace(self) -> Self { Self { replace: true, ..self } } /// Execute the request. pub async fn execute(self) -> Result<(), Error> { let RuleAddRequest { mut handle, message, replace, .. } = self; let mut req = NetlinkMessage::from(RtnlMessage::NewRule(message)); let replace = if replace { NLM_F_REPLACE } else { NLM_F_EXCL }; req.header.flags = NLM_F_REQUEST | NLM_F_ACK | replace | NLM_F_CREATE; let mut response = handle.request(req)?; while let Some(message) = response.next().await { try_nl!(message); } Ok(()) } pub fn message_mut(&mut self) -> &mut RuleMessage { &mut self.message } } impl RuleAddRequest { /// Sets the source address prefix. pub fn source_prefix(mut self, addr: Ipv4Addr, prefix_length: u8) -> Self { self.message.header.src_len = prefix_length; let src = addr.octets().to_vec(); self.message.nlas.push(Nla::Source(src)); self } /// Sets the destination address prefix. pub fn destination_prefix(mut self, addr: Ipv4Addr, prefix_length: u8) -> Self { self.message.header.dst_len = prefix_length; let dst = addr.octets().to_vec(); self.message.nlas.push(Nla::Destination(dst)); self } } impl RuleAddRequest { /// Sets the source address prefix. pub fn source_prefix(mut self, addr: Ipv6Addr, prefix_length: u8) -> Self { self.message.header.src_len = prefix_length; let src = addr.octets().to_vec(); self.message.nlas.push(Nla::Source(src)); self } /// Sets the destination address prefix. pub fn destination_prefix(mut self, addr: Ipv6Addr, prefix_length: u8) -> Self { self.message.header.dst_len = prefix_length; let dst = addr.octets().to_vec(); self.message.nlas.push(Nla::Destination(dst)); self } } ================================================ FILE: rtnetlink/src/rule/del.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use crate::{ packet::{NetlinkMessage, RtnlMessage, RuleMessage, NLM_F_ACK, NLM_F_REQUEST}, try_nl, Error, Handle, }; pub struct RuleDelRequest { handle: Handle, message: RuleMessage, } impl RuleDelRequest { pub(crate) fn new(handle: Handle, message: RuleMessage) -> Self { RuleDelRequest { handle, message } } /// Execute the request pub async fn execute(self) -> Result<(), Error> { let RuleDelRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::DelRule(message)); req.header.flags = NLM_F_REQUEST | NLM_F_ACK; let mut response = handle.request(req)?; while let Some(msg) = response.next().await { try_nl!(msg); } Ok(()) } pub fn message_mut(&mut self) -> &mut RuleMessage { &mut self.message } } ================================================ FILE: rtnetlink/src/rule/get.rs ================================================ // SPDX-License-Identifier: MIT use crate::IpVersion; use futures::{ future::{self, Either}, stream::{StreamExt, TryStream}, FutureExt, }; use netlink_packet_route::{constants::*, NetlinkMessage, RtnlMessage, RuleMessage}; use crate::{try_rtnl, Error, Handle}; pub struct RuleGetRequest { handle: Handle, message: RuleMessage, } impl RuleGetRequest { pub(crate) fn new(handle: Handle, ip_version: IpVersion) -> Self { let mut message = RuleMessage::default(); message.header.family = ip_version.family(); message.header.dst_len = 0; message.header.src_len = 0; message.header.tos = 0; message.header.action = FR_ACT_UNSPEC; message.header.table = RT_TABLE_UNSPEC; RuleGetRequest { handle, message } } pub fn message_mut(&mut self) -> &mut RuleMessage { &mut self.message } pub fn execute(self) -> impl TryStream { let RuleGetRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::GetRule(message)); req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; match handle.request(req) { Ok(response) => { Either::Left(response.map(move |msg| Ok(try_rtnl!(msg, RtnlMessage::NewRule)))) } Err(e) => Either::Right(future::err::(e).into_stream()), } } } ================================================ FILE: rtnetlink/src/rule/handle.rs ================================================ // SPDX-License-Identifier: MIT use crate::{Handle, IpVersion, RuleAddRequest, RuleDelRequest, RuleGetRequest}; use netlink_packet_route::RuleMessage; pub struct RuleHandle(Handle); impl RuleHandle { pub fn new(handle: Handle) -> Self { RuleHandle(handle) } /// Retrieve the list of route rule entries (equivalent to `ip rule show`) pub fn get(&self, ip_version: IpVersion) -> RuleGetRequest { RuleGetRequest::new(self.0.clone(), ip_version) } /// Add a route rule entry (equivalent to `ip rule add`) pub fn add(&self) -> RuleAddRequest { RuleAddRequest::new(self.0.clone()) } /// Delete the given route rule entry (equivalent to `ip rule del`) pub fn del(&self, rule: RuleMessage) -> RuleDelRequest { RuleDelRequest::new(self.0.clone(), rule) } } ================================================ FILE: rtnetlink/src/rule/mod.rs ================================================ // SPDX-License-Identifier: MIT mod handle; pub use self::handle::*; mod add; pub use self::add::*; mod del; pub use self::del::*; mod get; pub use self::get::*; ================================================ FILE: rtnetlink/src/traffic_control/add_filter.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use crate::{ packet::{ tc::{self, constants::*}, NetlinkMessage, RtnlMessage, TcMessage, NLM_F_ACK, NLM_F_REQUEST, TCM_IFINDEX_MAGIC_BLOCK, TC_H_MAKE, }, try_nl, Error, Handle, }; pub struct TrafficFilterNewRequest { handle: Handle, message: TcMessage, flags: u16, } impl TrafficFilterNewRequest { pub(crate) fn new(handle: Handle, ifindex: i32, flags: u16) -> Self { Self { handle, message: TcMessage::with_index(ifindex), flags: NLM_F_REQUEST | flags, } } /// Execute the request pub async fn execute(self) -> Result<(), Error> { let Self { mut handle, message, flags, } = self; let mut req = NetlinkMessage::from(RtnlMessage::NewTrafficFilter(message)); req.header.flags = NLM_F_ACK | flags; let mut response = handle.request(req)?; while let Some(message) = response.next().await { try_nl!(message); } Ok(()) } /// Set interface index. /// Equivalent to `dev STRING`, dev and block are mutually exlusive. pub fn index(mut self, index: i32) -> Self { assert_eq!(self.message.header.index, 0); self.message.header.index = index; self } /// Set block index. /// Equivalent to `block BLOCK_INDEX`. pub fn block(mut self, block_index: u32) -> Self { assert_eq!(self.message.header.index, 0); self.message.header.index = TCM_IFINDEX_MAGIC_BLOCK as i32; self.message.header.parent = block_index; self } /// Set parent. /// Equivalent to `[ root | ingress | egress | parent CLASSID ]` /// command args. They are mutually exlusive. pub fn parent(mut self, parent: u32) -> Self { assert_eq!(self.message.header.parent, TC_H_UNSPEC); self.message.header.parent = parent; self } /// Set parent to root. pub fn root(mut self) -> Self { assert_eq!(self.message.header.parent, TC_H_UNSPEC); self.message.header.parent = TC_H_ROOT; self } /// Set parent to ingress. pub fn ingress(mut self) -> Self { assert_eq!(self.message.header.parent, TC_H_UNSPEC); self.message.header.parent = TC_H_MAKE!(TC_H_CLSACT, TC_H_MIN_INGRESS); self } /// Set parent to egress. pub fn egress(mut self) -> Self { assert_eq!(self.message.header.parent, TC_H_UNSPEC); self.message.header.parent = TC_H_MAKE!(TC_H_CLSACT, TC_H_MIN_EGRESS); self } /// Set priority. /// Equivalent to `priority PRIO` or `pref PRIO`. pub fn priority(mut self, priority: u16) -> Self { assert_eq!(self.message.header.info & TC_H_MAJ_MASK, 0); self.message.header.info = TC_H_MAKE!((priority as u32) << 16, self.message.header.info); self } /// Set protocol. /// Equivalent to `protocol PROT`. /// Default: ETH_P_ALL 0x0003, see llproto_names at iproute2/lib/ll_proto.c. pub fn protocol(mut self, protocol: u16) -> Self { assert_eq!(self.message.header.info & TC_H_MIN_MASK, 0); self.message.header.info = TC_H_MAKE!(self.message.header.info, protocol as u32); self } /// The 32bit filter allows to match arbitrary bitfields in the packet. /// Equivalent to `tc filter ... u32`. pub fn u32(mut self, data: Vec) -> Self { assert!(!self .message .nlas .iter() .any(|nla| matches!(nla, tc::Nla::Kind(_)))); self.message .nlas .push(tc::Nla::Kind(tc::u32::KIND.to_string())); self.message.nlas.push(tc::Nla::Options( data.into_iter().map(tc::TcOpt::U32).collect(), )); self } /// Use u32 to implement traffic redirect. /// Equivalent to /// `tc filter add [dev source] [parent ffff:] [protocol all] u32 match u8 0 0 action mirred egress redirect dev dest` /// You need to set the `parent` and `protocol` before call redirect. pub fn redirect(self, dst_index: u32) -> Self { assert_eq!(self.message.nlas.len(), 0); let u32_nla = vec![ tc::u32::Nla::Sel(tc::u32::Sel { flags: TC_U32_TERMINAL, nkeys: 1, keys: vec![tc::u32::Key::default()], ..tc::u32::Sel::default() }), tc::u32::Nla::Act(vec![tc::Action { tab: TCA_ACT_TAB, nlas: vec![ tc::ActNla::Kind(tc::mirred::KIND.to_string()), tc::ActNla::Options(vec![tc::ActOpt::Mirred(tc::mirred::Nla::Parms( tc::mirred::TcMirred { action: TC_ACT_STOLEN, eaction: TCA_EGRESS_REDIR, ifindex: dst_index, ..tc::mirred::TcMirred::default() }, ))]), ], }]), ]; self.u32(u32_nla) } } #[cfg(test)] mod test { use std::{fs::File, os::unix::io::AsRawFd, path::Path}; use futures::stream::TryStreamExt; use nix::sched::{setns, CloneFlags}; use tokio::runtime::Runtime; use super::*; use crate::{new_connection, packet::LinkMessage, NetworkNamespace, NETNS_PATH, SELF_NS_PATH}; const TEST_NS: &str = "netlink_test_filter_ns"; const TEST_VETH_1: &str = "test_veth_1"; const TEST_VETH_2: &str = "test_veth_2"; struct Netns { path: String, _cur: File, last: File, } impl Netns { async fn new(path: &str) -> Self { // record current ns let last = File::open(Path::new(SELF_NS_PATH)).unwrap(); // create new ns NetworkNamespace::add(path.to_string()).await.unwrap(); // entry new ns let ns_path = Path::new(NETNS_PATH); let file = File::open(ns_path.join(path)).unwrap(); setns(file.as_raw_fd(), CloneFlags::CLONE_NEWNET).unwrap(); Self { path: path.to_string(), _cur: file, last, } } } impl Drop for Netns { fn drop(&mut self) { println!("exit ns: {}", self.path); setns(self.last.as_raw_fd(), CloneFlags::CLONE_NEWNET).unwrap(); let ns_path = Path::new(NETNS_PATH).join(&self.path); nix::mount::umount2(&ns_path, nix::mount::MntFlags::MNT_DETACH).unwrap(); nix::unistd::unlink(&ns_path).unwrap(); // _cur File will be closed auto // Since there is no async drop, NetworkNamespace::del cannot be called // here. Dummy interface will be deleted automatically after netns is // deleted. } } async fn setup_env() -> (Handle, LinkMessage, LinkMessage, Netns) { let netns = Netns::new(TEST_NS).await; // Notice: The Handle can only be created after the setns, so that the // Handle is the connection within the new ns. let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); handle .link() .add() .veth(TEST_VETH_1.to_string(), TEST_VETH_2.to_string()) .execute() .await .unwrap(); let mut links = handle .link() .get() .match_name(TEST_VETH_1.to_string()) .execute(); let link1 = links.try_next().await.unwrap(); links = handle .link() .get() .match_name(TEST_VETH_2.to_string()) .execute(); let link2 = links.try_next().await.unwrap(); (handle, link1.unwrap(), link2.unwrap(), netns) } async fn test_async_new_filter() { let (handle, test1, test2, _netns) = setup_env().await; handle .qdisc() .add(test1.header.index as i32) .ingress() .execute() .await .unwrap(); handle .qdisc() .add(test2.header.index as i32) .ingress() .execute() .await .unwrap(); handle .traffic_filter(test1.header.index as i32) .add() .parent(0xffff0000) .protocol(0x0003) .redirect(test2.header.index) .execute() .await .unwrap(); let mut filters_iter = handle .traffic_filter(test1.header.index as i32) .get() .root() .execute(); let mut found = false; while let Some(nl_msg) = filters_iter.try_next().await.unwrap() { //filters.push(nl_msg.clone()); if nl_msg.header.handle == 0x80000800 { let mut iter = nl_msg.nlas.iter(); assert_eq!( iter.next().unwrap(), &tc::Nla::Kind(String::from(tc::u32::KIND)) ); assert!(matches!(iter.next().unwrap(), &tc::Nla::Chain(_))); // TCA_OPTIONS let nla = iter.next().unwrap(); let filter = if let tc::Nla::Options(f) = nla { f } else { panic!("expect options nla"); }; let mut fi = filter.iter(); let fa = fi.next().unwrap(); let ua = if let tc::TcOpt::U32(u) = fa { u } else { panic!("expect u32 nla"); }; // TCA_U32_SEL let sel = if let tc::u32::Nla::Sel(s) = ua { s } else { panic!("expect sel nla"); }; assert_eq!(sel.flags, TC_U32_TERMINAL); assert_eq!(sel.nkeys, 1); assert_eq!(sel.keys.len(), 1); assert_eq!(sel.keys[0], tc::u32::Key::default()); found = true; break; } } if !found { panic!("not found :{} filter.", test1.header.index); } } #[test] fn test_new_filter() { Runtime::new().unwrap().block_on(test_async_new_filter()); } } ================================================ FILE: rtnetlink/src/traffic_control/add_qdisc.rs ================================================ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; use crate::{ packet::{ tc::{constants::*, nlas}, NetlinkMessage, RtnlMessage, TcMessage, NLM_F_ACK, NLM_F_REQUEST, TC_H_MAKE, }, try_nl, Error, Handle, }; pub struct QDiscNewRequest { handle: Handle, message: TcMessage, flags: u16, } impl QDiscNewRequest { pub(crate) fn new(handle: Handle, message: TcMessage, flags: u16) -> Self { Self { handle, message, flags: NLM_F_REQUEST | flags, } } /// Execute the request pub async fn execute(self) -> Result<(), Error> { let Self { mut handle, message, flags, } = self; let mut req = NetlinkMessage::from(RtnlMessage::NewQueueDiscipline(message)); req.header.flags = NLM_F_ACK | flags; let mut response = handle.request(req)?; while let Some(message) = response.next().await { try_nl!(message); } Ok(()) } /// Set handle, pub fn handle(mut self, maj: u16, min: u16) -> Self { self.message.header.handle = TC_H_MAKE!((maj as u32) << 16, min as u32); self } /// Set parent to root. pub fn root(mut self) -> Self { assert_eq!(self.message.header.parent, TC_H_UNSPEC); self.message.header.parent = TC_H_ROOT; self } /// Set parent pub fn parent(mut self, parent: u32) -> Self { assert_eq!(self.message.header.parent, TC_H_UNSPEC); self.message.header.parent = parent; self } /// New a ingress qdisc pub fn ingress(mut self) -> Self { assert_eq!(self.message.header.parent, TC_H_UNSPEC); self.message.header.parent = TC_H_INGRESS; self.message.header.handle = 0xffff0000; self.message .nlas .push(nlas::Nla::Kind("ingress".to_string())); self } } #[cfg(test)] mod test { use std::{fs::File, os::unix::io::AsRawFd, path::Path}; use futures::stream::TryStreamExt; use nix::sched::{setns, CloneFlags}; use tokio::runtime::Runtime; use super::*; use crate::{ new_connection, packet::{ rtnl::tc::nlas::Nla::{HwOffload, Kind}, LinkMessage, AF_UNSPEC, }, NetworkNamespace, NETNS_PATH, SELF_NS_PATH, }; const TEST_NS: &str = "netlink_test_qdisc_ns"; const TEST_DUMMY: &str = "test_dummy"; struct Netns { path: String, _cur: File, last: File, } impl Netns { async fn new(path: &str) -> Self { // record current ns let last = File::open(Path::new(SELF_NS_PATH)).unwrap(); // create new ns NetworkNamespace::add(path.to_string()).await.unwrap(); // entry new ns let ns_path = Path::new(NETNS_PATH); let file = File::open(ns_path.join(path)).unwrap(); setns(file.as_raw_fd(), CloneFlags::CLONE_NEWNET).unwrap(); Self { path: path.to_string(), _cur: file, last, } } } impl Drop for Netns { fn drop(&mut self) { println!("exit ns: {}", self.path); setns(self.last.as_raw_fd(), CloneFlags::CLONE_NEWNET).unwrap(); let ns_path = Path::new(NETNS_PATH).join(&self.path); nix::mount::umount2(&ns_path, nix::mount::MntFlags::MNT_DETACH).unwrap(); nix::unistd::unlink(&ns_path).unwrap(); // _cur File will be closed auto // Since there is no async drop, NetworkNamespace::del cannot be called // here. Dummy interface will be deleted automatically after netns is // deleted. } } async fn setup_env() -> (Handle, LinkMessage, Netns) { let netns = Netns::new(TEST_NS).await; // Notice: The Handle can only be created after the setns, so that the // Handle is the connection within the new ns. let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); handle .link() .add() .dummy(TEST_DUMMY.to_string()) .execute() .await .unwrap(); let mut links = handle .link() .get() .match_name(TEST_DUMMY.to_string()) .execute(); let link = links.try_next().await.unwrap(); (handle, link.unwrap(), netns) } async fn test_async_new_qdisc() { let (handle, test_link, _netns) = setup_env().await; handle .qdisc() .add(test_link.header.index as i32) .ingress() .execute() .await .unwrap(); let mut qdiscs_iter = handle .qdisc() .get() .index(test_link.header.index as i32) .ingress() .execute(); let mut found = false; while let Some(nl_msg) = qdiscs_iter.try_next().await.unwrap() { if nl_msg.header.index == test_link.header.index as i32 && nl_msg.header.handle == 0xffff0000 { assert_eq!(nl_msg.header.family, AF_UNSPEC as u8); assert_eq!(nl_msg.header.handle, 0xffff0000); assert_eq!(nl_msg.header.parent, TC_H_INGRESS); assert_eq!(nl_msg.header.info, 1); // refcount assert_eq!(nl_msg.nlas[0], Kind("ingress".to_string())); assert_eq!(nl_msg.nlas[2], HwOffload(0)); found = true; break; } } if !found { panic!("not found dev:{} qdisc.", test_link.header.index); } } #[test] fn test_new_qdisc() { Runtime::new().unwrap().block_on(test_async_new_qdisc()); } } ================================================ FILE: rtnetlink/src/traffic_control/del_qdisc.rs ================================================ // SPDX-License-Identifier: MIT use futures::StreamExt; use crate::{ packet::{NetlinkMessage, RtnlMessage, TcMessage, NLM_F_ACK, NLM_F_REQUEST}, try_nl, Error, Handle, }; pub struct QDiscDelRequest { handle: Handle, message: TcMessage, } impl QDiscDelRequest { pub(crate) fn new(handle: Handle, message: TcMessage) -> Self { QDiscDelRequest { handle, message } } // Execute the request pub async fn execute(self) -> Result<(), Error> { let QDiscDelRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::DelQueueDiscipline(message)); req.header.flags = NLM_F_REQUEST | NLM_F_ACK; let mut response = handle.request(req)?; while let Some(message) = response.next().await { try_nl!(message) } Ok(()) } /// Return a mutable reference to the request pub fn message_mut(&mut self) -> &mut TcMessage { &mut self.message } } ================================================ FILE: rtnetlink/src/traffic_control/get.rs ================================================ // SPDX-License-Identifier: MIT use futures::{ future::{self, Either}, stream::{StreamExt, TryStream}, FutureExt, }; use crate::{ packet::{tc::constants::*, NetlinkMessage, RtnlMessage, TcMessage, NLM_F_DUMP, NLM_F_REQUEST}, try_rtnl, Error, Handle, }; pub struct QDiscGetRequest { handle: Handle, message: TcMessage, } impl QDiscGetRequest { pub(crate) fn new(handle: Handle) -> Self { QDiscGetRequest { handle, message: TcMessage::default(), } } /// Execute the request pub fn execute(self) -> impl TryStream { let QDiscGetRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::GetQueueDiscipline(message)); req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; match handle.request(req) { Ok(response) => Either::Left( response.map(move |msg| Ok(try_rtnl!(msg, RtnlMessage::NewQueueDiscipline))), ), Err(e) => Either::Right(future::err::(e).into_stream()), } } pub fn index(mut self, index: i32) -> Self { self.message.header.index = index; self } /// Get ingress qdisc pub fn ingress(mut self) -> Self { assert_eq!(self.message.header.parent, TC_H_UNSPEC); self.message.header.parent = TC_H_INGRESS; self } } pub struct TrafficClassGetRequest { handle: Handle, message: TcMessage, } impl TrafficClassGetRequest { pub(crate) fn new(handle: Handle, ifindex: i32) -> Self { let mut message = TcMessage::default(); message.header.index = ifindex; TrafficClassGetRequest { handle, message } } /// Execute the request pub fn execute(self) -> impl TryStream { let TrafficClassGetRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::GetTrafficClass(message)); req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; match handle.request(req) { Ok(response) => Either::Left( response.map(move |msg| Ok(try_rtnl!(msg, RtnlMessage::NewTrafficClass))), ), Err(e) => Either::Right(future::err::(e).into_stream()), } } } pub struct TrafficFilterGetRequest { handle: Handle, message: TcMessage, } impl TrafficFilterGetRequest { pub(crate) fn new(handle: Handle, ifindex: i32) -> Self { let mut message = TcMessage::default(); message.header.index = ifindex; TrafficFilterGetRequest { handle, message } } /// Execute the request pub fn execute(self) -> impl TryStream { let TrafficFilterGetRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::GetTrafficFilter(message)); req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; match handle.request(req) { Ok(response) => Either::Left( response.map(move |msg| Ok(try_rtnl!(msg, RtnlMessage::NewTrafficFilter))), ), Err(e) => Either::Right(future::err::(e).into_stream()), } } /// Set parent to root. pub fn root(mut self) -> Self { assert_eq!(self.message.header.parent, TC_H_UNSPEC); self.message.header.parent = TC_H_ROOT; self } } pub struct TrafficChainGetRequest { handle: Handle, message: TcMessage, } impl TrafficChainGetRequest { pub(crate) fn new(handle: Handle, ifindex: i32) -> Self { let mut message = TcMessage::default(); message.header.index = ifindex; TrafficChainGetRequest { handle, message } } /// Execute the request pub fn execute(self) -> impl TryStream { let TrafficChainGetRequest { mut handle, message, } = self; let mut req = NetlinkMessage::from(RtnlMessage::GetTrafficChain(message)); req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; match handle.request(req) { Ok(response) => Either::Left( response.map(move |msg| Ok(try_rtnl!(msg, RtnlMessage::NewTrafficChain))), ), Err(e) => Either::Right(future::err::(e).into_stream()), } } } ================================================ FILE: rtnetlink/src/traffic_control/handle.rs ================================================ // SPDX-License-Identifier: MIT use super::{ QDiscDelRequest, QDiscGetRequest, QDiscNewRequest, TrafficChainGetRequest, TrafficClassGetRequest, TrafficFilterGetRequest, TrafficFilterNewRequest, }; use crate::{ packet::{TcMessage, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REPLACE}, Handle, }; pub struct QDiscHandle(Handle); impl QDiscHandle { pub fn new(handle: Handle) -> Self { QDiscHandle(handle) } /// Retrieve the list of qdisc (equivalent to `tc qdisc show`) pub fn get(&mut self) -> QDiscGetRequest { QDiscGetRequest::new(self.0.clone()) } /// Create a new qdisc, don't replace if the object already exists. /// ( equivalent to `tc qdisc add dev STRING`) pub fn add(&mut self, index: i32) -> QDiscNewRequest { let msg = TcMessage::with_index(index); QDiscNewRequest::new(self.0.clone(), msg, NLM_F_EXCL | NLM_F_CREATE) } /// Change the qdisc, the handle cannot be changed and neither can the parent. /// In other words, change cannot move a node. /// ( equivalent to `tc qdisc change dev STRING`) pub fn change(&mut self, index: i32) -> QDiscNewRequest { let msg = TcMessage::with_index(index); QDiscNewRequest::new(self.0.clone(), msg, 0) } /// Replace existing matching qdisc, create qdisc if it doesn't already exist. /// ( equivalent to `tc qdisc replace dev STRING`) pub fn replace(&mut self, index: i32) -> QDiscNewRequest { let msg = TcMessage::with_index(index); QDiscNewRequest::new(self.0.clone(), msg, NLM_F_CREATE | NLM_F_REPLACE) } /// Performs a replace where the node must exist already. /// ( equivalent to `tc qdisc link dev STRING`) pub fn link(&mut self, index: i32) -> QDiscNewRequest { let msg = TcMessage::with_index(index); QDiscNewRequest::new(self.0.clone(), msg, NLM_F_REPLACE) } /// Delete the qdisc ( equivalent to `tc qdisc del dev STRING`) pub fn del(&mut self, index: i32) -> QDiscDelRequest { let msg = TcMessage::with_index(index); QDiscDelRequest::new(self.0.clone(), msg) } } pub struct TrafficClassHandle { handle: Handle, ifindex: i32, } impl TrafficClassHandle { pub fn new(handle: Handle, ifindex: i32) -> Self { TrafficClassHandle { handle, ifindex } } /// Retrieve the list of traffic class (equivalent to /// `tc class show dev `) pub fn get(&mut self) -> TrafficClassGetRequest { TrafficClassGetRequest::new(self.handle.clone(), self.ifindex) } } pub struct TrafficFilterHandle { handle: Handle, ifindex: i32, } impl TrafficFilterHandle { pub fn new(handle: Handle, ifindex: i32) -> Self { TrafficFilterHandle { handle, ifindex } } /// Retrieve the list of filter (equivalent to /// `tc filter show dev `) pub fn get(&mut self) -> TrafficFilterGetRequest { TrafficFilterGetRequest::new(self.handle.clone(), self.ifindex) } /// Add a filter to a node, don't replace if the object already exists. /// ( equivalent to `tc filter add dev STRING`) pub fn add(&mut self) -> TrafficFilterNewRequest { TrafficFilterNewRequest::new(self.handle.clone(), self.ifindex, NLM_F_EXCL | NLM_F_CREATE) } /// Change the filter, the handle cannot be changed and neither can the parent. /// In other words, change cannot move a node. /// ( equivalent to `tc filter change dev STRING`) pub fn change(&mut self) -> TrafficFilterNewRequest { TrafficFilterNewRequest::new(self.handle.clone(), self.ifindex, 0) } /// Replace existing matching filter, create filter if it doesn't already exist. /// ( equivalent to `tc filter replace dev STRING`) pub fn replace(&mut self) -> TrafficFilterNewRequest { TrafficFilterNewRequest::new(self.handle.clone(), self.ifindex, NLM_F_CREATE) } } pub struct TrafficChainHandle { handle: Handle, ifindex: i32, } impl TrafficChainHandle { pub fn new(handle: Handle, ifindex: i32) -> Self { TrafficChainHandle { handle, ifindex } } /// Retrieve the list of chain (equivalent to /// `tc chain show dev `) pub fn get(&mut self) -> TrafficChainGetRequest { TrafficChainGetRequest::new(self.handle.clone(), self.ifindex) } } ================================================ FILE: rtnetlink/src/traffic_control/mod.rs ================================================ // SPDX-License-Identifier: MIT mod handle; pub use self::handle::*; mod get; pub use self::get::*; mod add_qdisc; pub use self::add_qdisc::*; mod del_qdisc; pub use self::del_qdisc::*; mod add_filter; pub use self::add_filter::*; #[cfg(test)] mod test; ================================================ FILE: rtnetlink/src/traffic_control/test.rs ================================================ // SPDX-License-Identifier: MIT use std::process::Command; use futures::stream::TryStreamExt; use tokio::runtime::Runtime; use crate::{ new_connection, packet::{ rtnl::tc::nlas::Nla::{Chain, HwOffload, Kind}, ErrorMessage, TcMessage, AF_UNSPEC, }, Error::NetlinkError, }; static TEST_DUMMY_NIC: &str = "netlink-test"; async fn _get_qdiscs() -> Vec { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); let mut qdiscs_iter = handle.qdisc().get().execute(); let mut qdiscs = Vec::new(); while let Some(nl_msg) = qdiscs_iter.try_next().await.unwrap() { qdiscs.push(nl_msg.clone()); } qdiscs } #[test] fn test_get_qdiscs() { let qdiscs = Runtime::new().unwrap().block_on(_get_qdiscs()); let qdisc_of_loopback_nic = &qdiscs[0]; assert_eq!(qdisc_of_loopback_nic.header.family, AF_UNSPEC as u8); assert_eq!(qdisc_of_loopback_nic.header.index, 1); assert_eq!(qdisc_of_loopback_nic.header.handle, 0); assert_eq!(qdisc_of_loopback_nic.header.parent, u32::MAX); assert_eq!(qdisc_of_loopback_nic.header.info, 2); // refcount assert_eq!(qdisc_of_loopback_nic.nlas[0], Kind("noqueue".to_string())); assert_eq!(qdisc_of_loopback_nic.nlas[1], HwOffload(0)); } async fn _get_tclasses(ifindex: i32) -> Vec { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); let mut tclasses_iter = handle.traffic_class(ifindex).get().execute(); let mut tclasses = Vec::new(); while let Some(nl_msg) = tclasses_iter.try_next().await.unwrap() { tclasses.push(nl_msg.clone()); } tclasses } // Return 0 for not found fn _get_test_dummy_interface_index() -> i32 { let output = Command::new("ip") .args(&["-o", "link", "show", TEST_DUMMY_NIC]) .output() .expect("failed to run ip command"); if !output.status.success() { 0 } else { let line = std::str::from_utf8(&output.stdout).unwrap(); line.split(": ").next().unwrap().parse::().unwrap() } } fn _add_test_dummy_interface() -> i32 { if _get_test_dummy_interface_index() == 0 { let output = Command::new("ip") .args(&["link", "add", TEST_DUMMY_NIC, "type", "dummy"]) .output() .expect("failed to run ip command"); if !output.status.success() { eprintln!( "Failed to create dummy interface {} : {:?}", TEST_DUMMY_NIC, output ); } assert!(output.status.success()); } _get_test_dummy_interface_index() } fn _remove_test_dummy_interface() { let output = Command::new("ip") .args(&["link", "del", TEST_DUMMY_NIC]) .output() .expect("failed to run ip command"); if !output.status.success() { eprintln!( "Failed to remove dummy interface {} : {:?}", TEST_DUMMY_NIC, output ); } assert!(output.status.success()); } fn _add_test_tclass_to_dummy() { let output = Command::new("tc") .args(&[ "qdisc", "add", "dev", TEST_DUMMY_NIC, "root", "handle", "1:", "htb", "default", "6", ]) .output() .expect("failed to run tc command"); if !output.status.success() { eprintln!( "Failed to add qdisc to dummy interface {} : {:?}", TEST_DUMMY_NIC, output ); } assert!(output.status.success()); let output = Command::new("tc") .args(&[ "class", "add", "dev", TEST_DUMMY_NIC, "parent", "1:", "classid", "1:1", "htb", "rate", "10mbit", "ceil", "10mbit", ]) .output() .expect("failed to run tc command"); if !output.status.success() { eprintln!( "Failed to add traffic class to dummy interface {}: {:?}", TEST_DUMMY_NIC, output ); } assert!(output.status.success()); } fn _add_test_filter_to_dummy() { let output = Command::new("tc") .args(&[ "filter", "add", "dev", TEST_DUMMY_NIC, "parent", "1:", "basic", "match", "meta(priority eq 6)", "classid", "1:1", ]) .output() .expect("failed to run tc command"); if !output.status.success() { eprintln!("Failed to add trafice filter to lo: {:?}", output); } assert!(output.status.success()); } fn _remove_test_tclass_from_dummy() { Command::new("tc") .args(&[ "class", "del", "dev", TEST_DUMMY_NIC, "parent", "1:", "classid", "1:1", ]) .status() .unwrap_or_else(|_| { panic!( "failed to remove tclass from dummy interface {}", TEST_DUMMY_NIC ) }); Command::new("tc") .args(&["qdisc", "del", "dev", TEST_DUMMY_NIC, "root"]) .status() .unwrap_or_else(|_| { panic!( "failed to remove qdisc from dummy interface {}", TEST_DUMMY_NIC ) }); } fn _remove_test_filter_from_dummy() { Command::new("tc") .args(&["filter", "del", "dev", TEST_DUMMY_NIC]) .status() .unwrap_or_else(|_| { panic!( "failed to remove filter from dummy interface {}", TEST_DUMMY_NIC ) }); } async fn _get_filters(ifindex: i32) -> Vec { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); let mut filters_iter = handle.traffic_filter(ifindex).get().execute(); let mut filters = Vec::new(); while let Some(nl_msg) = filters_iter.try_next().await.unwrap() { filters.push(nl_msg.clone()); } filters } async fn _get_chains(ifindex: i32) -> Vec { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); let mut chains_iter = handle.traffic_chain(ifindex).get().execute(); let mut chains = Vec::new(); // The traffic control chain is only supported by kernel 4.19+, // hence we might get error: 95 Operation not supported loop { match chains_iter.try_next().await { Ok(Some(nl_msg)) => { chains.push(nl_msg.clone()); } Ok(None) => { break; } Err(NetlinkError(ErrorMessage { code, header: _ })) => { assert_eq!(code, -95); eprintln!( "The chain in traffic control is not supported, \ please upgrade your kernel" ); } _ => {} } } chains } // The `cargo test` by default run all tests in parallel, in stead // of create random named veth/dummy for test, just place class, filter, and // chain query test in one test case is much simpler. #[test] #[cfg_attr(not(feature = "test_as_root"), ignore)] fn test_get_traffic_classes_filters_and_chains() { let ifindex = _add_test_dummy_interface(); _add_test_tclass_to_dummy(); _add_test_filter_to_dummy(); let tclasses = Runtime::new().unwrap().block_on(_get_tclasses(ifindex)); let filters = Runtime::new().unwrap().block_on(_get_filters(ifindex)); let chains = Runtime::new().unwrap().block_on(_get_chains(ifindex)); _remove_test_filter_from_dummy(); _remove_test_tclass_from_dummy(); _remove_test_dummy_interface(); assert_eq!(tclasses.len(), 1); let tclass = &tclasses[0]; assert_eq!(tclass.header.family, AF_UNSPEC as u8); assert_eq!(tclass.header.index, ifindex); assert_eq!(tclass.header.parent, u32::MAX); assert_eq!(tclass.nlas[0], Kind("htb".to_string())); assert_eq!(filters.len(), 2); assert_eq!(filters[0].header.family, AF_UNSPEC as u8); assert_eq!(filters[0].header.index, ifindex); assert_eq!(filters[0].header.parent, u16::MAX as u32 + 1); assert_eq!(filters[0].nlas[0], Kind("basic".to_string())); assert_eq!(filters[1].header.family, AF_UNSPEC as u8); assert_eq!(filters[1].header.index, ifindex); assert_eq!(filters[1].header.parent, u16::MAX as u32 + 1); assert_eq!(filters[1].nlas[0], Kind("basic".to_string())); assert!(chains.len() <= 1); if chains.len() == 1 { assert_eq!(chains[0].header.family, AF_UNSPEC as u8); assert_eq!(chains[0].header.index, ifindex); assert_eq!(chains[0].header.parent, u16::MAX as u32 + 1); assert_eq!(chains[0].nlas[0], Chain([0u8, 0, 0, 0].to_vec())); } } ================================================ FILE: rustfmt.toml ================================================ # requires unstable rustfmt until the options are stabilized format_code_in_doc_comments = true imports_layout = "HorizontalVertical" imports_granularity = "Crate"