pax_global_header00006660000000000000000000000064145267342670014532gustar00rootroot0000000000000052 comment=1ac690051d5ba3a6dbc5898b931f7175a9d8e7ac containers-netavark-83edb4b/000077500000000000000000000000001452673426700162055ustar00rootroot00000000000000containers-netavark-83edb4b/.cirrus.yml000066400000000000000000000245761452673426700203330ustar00rootroot00000000000000--- # Format Ref: https://cirrus-ci.org/guide/writing-tasks/ # Main collection of env. vars to set for all tasks and scripts. env: # Actual|intended branch for this run DEST_BRANCH: "main" # The default is 'sh' if unspecified CIRRUS_SHELL: "/bin/bash" # Location where source repo. will be cloned CIRRUS_WORKING_DIR: "/var/tmp/netavark" # Rust package cache also lives here CARGO_HOME: "/var/cache/cargo" # Rust compiler output lives here (see Makefile) CARGO_TARGET_DIR: "$CIRRUS_WORKING_DIR/targets" # Save a little typing (path relative to $CIRRUS_WORKING_DIR) SCRIPT_BASE: "./contrib/cirrus" IMAGE_SUFFIX: "c20231116t174419z-f39f38d13" FEDORA_NETAVARK_IMAGE: "fedora-netavark-${IMAGE_SUFFIX}" AARDVARK_DNS_BRANCH: "main" AARDVARK_DNS_URL: "https://api.cirrus-ci.com/v1/artifact/github/containers/aardvark-dns/success/binary.zip?branch=${AARDVARK_DNS_BRANCH}" FEDORA_NETAVARK_AARCH64_AMI: "fedora-netavark-aws-arm64-${IMAGE_SUFFIX}" EC2_INST_TYPE: "t4g.xlarge" gcp_credentials: ENCRYPTED[d6efdb7d6d4c61e3831df2193ca6348bb02f26cd931695f69d41930b1965f7dab72a838ca0902f6ed8cde66c7deddae2] aws_credentials: ENCRYPTED[36b3e82f72ec2c909235b69d88b835a09e230aa289e2925d949b0dc4c813c1b468655aabb05edf3f7dcfed430c320b87] build_task: alias: "build" # Compiling is very CPU intensive, make it chooch quicker for this task only gce_instance: &standard_build_gce_x86_64 image_project: "libpod-218412" zone: "us-central1-c" disk: 200 # GB, do not set <200 per gcloud warning re: I/O performance cpu: 8 memory: "8Gb" image_name: "${FEDORA_NETAVARK_IMAGE}" cargo_cache: &cargo_cache # Populating this cache depends on execution of setup.sh, and runner.sh # to builds of all release, debug, plus unit-tests. folder: "$CARGO_HOME" # Cirrus-CI will automatically store separate caches for branches vs PRs. # We use the branch-name here mainly to distinguish PR-level caches in # order to properly support backport-PRs to release branches. Otherwise # all PRs & branches will share caches with other PRs and branches # for a given $DEST_BRANCH and vX value. Adjust vX if cache schema # changes. fingerprint_script: echo -e "cargo_v3_${DEST_BRANCH}_amd64\n---\n$(- netavark netavark.debug netavark.info netavark.aarch64-unknown-linux-gnu netavark.debug.aarch64-unknown-linux-gnu netavark.info.aarch64-unknown-linux-gnu clone_script: *noop bin_cache: *ro_bin_cache # The paths used for uploaded artifacts are relative here and in Cirrus script: - set -x - curl --fail --location -O --url ${API_URL_BASE}/build_aarch64/armbinary.zip - unzip armbinary.zip - rm -f armbinary.zip - mv bin/* ./ - rm -rf bin artifacts_test_script: # Other CI systems depend on all files being present - ls -la # If there's a missing file, show what it was in the output - for fn in $EXP_BINS; do [[ -r "$(echo $fn|tee /dev/stderr)" ]] || exit 1; done # Upload tested binary for consumption downstream # https://cirrus-ci.org/guide/writing-tasks/#artifacts-instruction binary_artifacts: path: ./*netavark* containers-netavark-83edb4b/.github/000077500000000000000000000000001452673426700175455ustar00rootroot00000000000000containers-netavark-83edb4b/.github/renovate.json5000066400000000000000000000044431452673426700223550ustar00rootroot00000000000000/* Renovate is a service similar to GitHub Dependabot, but with (fantastically) more configuration options. So many options in fact, if you're new I recommend glossing over this cheat-sheet prior to the official documentation: https://www.augmentedmind.de/2021/07/25/renovate-bot-cheat-sheet Configuration Update/Change Procedure: 1. Make changes 2. Manually validate changes (from repo-root): podman run -it \ -v ./.github/renovate.json5:/usr/src/app/renovate.json5:z \ docker.io/renovate/renovate:latest \ renovate-config-validator 3. Commit. Configuration Reference: https://docs.renovatebot.com/configuration-options/ Monitoring Dashboard: https://app.renovatebot.com/dashboard#github/containers Note: The Renovate bot will create/manage it's business on branches named 'renovate/*'. Otherwise, and by default, the only the copy of this file that matters is the one on the `main` branch. No other branches will be monitored or touched in any way. */ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", /************************************************* ****** Global/general configuration options ***** *************************************************/ // Re-use predefined sets of configuration options to DRY "extends": [ // https://github.com/containers/automation/blob/main/renovate/defaults.json5 "github>containers/automation//renovate/defaults.json5" ], // Permit automatic rebasing when base-branch changes by more than // one commit. "rebaseWhen": "behind-base-branch", /************************************************* *** Repository-specific configuration options *** *************************************************/ // Don't leave dep. update. PRs "hanging", assign them to people. "assignees": ["containers/netavark-maintainers"], /************************************************** ***** Manager-specific configuration options ***** **************************************************/ "dockerfile": { // Renovate has a hard-time managing base images for // Fedora and CentOS (see contrib/container_images/). Disable // all Dockerfile baes-image management. "enabled": false }, } containers-netavark-83edb4b/.github/workflows/000077500000000000000000000000001452673426700216025ustar00rootroot00000000000000containers-netavark-83edb4b/.github/workflows/check_cirrus_cron.yml000066400000000000000000000012431452673426700260120ustar00rootroot00000000000000--- # See also: # https://github.com/containers/podman/blob/main/.github/workflows/check_cirrus_cron.yml on: # Note: This only applies to the default branch. schedule: # N/B: This should correspond to a period slightly after # the last job finishes running. See job defs. at: # https://cirrus-ci.com/settings/repository/5138144844840960 - cron: '03 03 * * 1-5' # Debug: Allow triggering job manually in github-actions WebUI workflow_dispatch: {} jobs: # Ref: https://docs.github.com/en/actions/using-workflows/reusing-workflows call_cron_failures: uses: containers/podman/.github/workflows/check_cirrus_cron.yml@main secrets: inherit containers-netavark-83edb4b/.github/workflows/rerun_cirrus_cron.yml000066400000000000000000000012361452673426700260720ustar00rootroot00000000000000--- # See also: https://github.com/containers/podman/blob/main/.github/workflows/rerun_cirrus_cron.yml on: # Note: This only applies to the default branch. schedule: # N/B: This should correspond to a period slightly after # the last job finishes running. See job defs. at: # https://cirrus-ci.com/settings/repository/5138144844840960 - cron: '01 01 * * 1-5' # Debug: Allow triggering job manually in github-actions WebUI workflow_dispatch: {} jobs: # Ref: https://docs.github.com/en/actions/using-workflows/reusing-workflows call_cron_rerun: uses: containers/podman/.github/workflows/rerun_cirrus_cron.yml@main secrets: inherit containers-netavark-83edb4b/.gitignore000066400000000000000000000001771452673426700202020ustar00rootroot00000000000000/bin/ target/ targets/ *.swp netavark.1 vendor/ .idea/* src/proto-build/netavark_proxy.rs contrib/systemd/*/*.service .vscode* containers-netavark-83edb4b/.packit.yaml000066400000000000000000000025001452673426700204170ustar00rootroot00000000000000--- # See the documentation for more information: # https://packit.dev/docs/configuration/ specfile_path: rpm/netavark.spec upstream_tag_template: v{version} srpm_build_deps: - cargo - make - openssl-devel jobs: - job: copr_build trigger: pull_request notifications: failure_comment: message: "Ephemeral COPR build failed. @containers/packit-build please check." enable_net: true targets: - fedora-all-x86_64 - fedora-all-aarch64 - centos-stream+epel-next-8-x86_64 - centos-stream+epel-next-8-aarch64 - centos-stream+epel-next-9-x86_64 - centos-stream+epel-next-9-aarch64 additional_repos: - "copr://rhcontainerbot/podman-next" # Run on commit to main branch - job: copr_build trigger: commit notifications: failure_comment: message: "podman-next COPR build failed. @containers/packit-build please check." branch: main owner: rhcontainerbot project: podman-next enable_net: true - job: propose_downstream trigger: release update_release: false dist_git_branches: - fedora-all - job: koji_build trigger: commit dist_git_branches: - fedora-all - job: bodhi_update trigger: commit dist_git_branches: - fedora-branched # rawhide updates are created automatically containers-netavark-83edb4b/CODE-OF-CONDUCT.md000066400000000000000000000002771452673426700206460ustar00rootroot00000000000000## The Netavark Project Community Code of Conduct The Netavark project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/main/CODE-OF-CONDUCT.md). containers-netavark-83edb4b/Cargo.lock000066400000000000000000002022111452673426700201100ustar00rootroot00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys", ] [[package]] name = "anyhow" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-broadcast" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" dependencies = [ "event-listener 2.5.3", "futures-core", ] [[package]] name = "async-channel" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d37875bd9915b7d67c2f117ea2c30a0989874d0b2cb694fe25403c85763c0c9e" dependencies = [ "concurrent-queue", "event-listener 3.1.0", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5ea910c42e5ab19012bab31f53cb4d63d54c3a27730f9a833a88efcf4bb52d" dependencies = [ "async-lock 3.1.1", "async-task", "concurrent-queue", "fastrand 2.0.1", "futures-lite 2.0.1", "slab", ] [[package]] name = "async-fs" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" dependencies = [ "async-lock 2.8.0", "autocfg", "blocking", "futures-lite 1.13.0", ] [[package]] name = "async-io" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock 2.8.0", "autocfg", "cfg-if", "concurrent-queue", "futures-lite 1.13.0", "log", "parking", "polling 2.8.0", "rustix 0.37.27", "slab", "socket2 0.4.10", "waker-fn", ] [[package]] name = "async-io" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41ed9d5715c2d329bf1b4da8d60455b99b187f27ba726df2883799af9af60997" dependencies = [ "async-lock 3.1.1", "cfg-if", "concurrent-queue", "futures-io", "futures-lite 2.0.1", "parking", "polling 3.3.0", "rustix 0.38.25", "slab", "tracing", "waker-fn", "windows-sys", ] [[package]] name = "async-lock" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ "event-listener 2.5.3", ] [[package]] name = "async-lock" version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "655b9c7fe787d3b25cc0f804a1a8401790f0c5bc395beb5a64dc77d8de079105" dependencies = [ "event-listener 3.1.0", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-process" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" dependencies = [ "async-io 1.13.0", "async-lock 2.8.0", "async-signal", "blocking", "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", "rustix 0.38.25", "windows-sys", ] [[package]] name = "async-recursion" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "async-signal" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" dependencies = [ "async-io 2.2.0", "async-lock 2.8.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", "rustix 0.38.25", "signal-hook-registry", "slab", "windows-sys", ] [[package]] name = "async-stream" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", "pin-project-lite", ] [[package]] name = "async-stream-impl" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "async-task" version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" [[package]] name = "async-trait" version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", "bitflags 1.3.2", "bytes", "futures-util", "http", "http-body", "hyper", "itoa", "matchit", "memchr", "mime", "percent-encoding", "pin-project-lite", "rustversion", "serde", "sync_wrapper", "tower", "tower-layer", "tower-service", ] [[package]] name = "axum-core" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ "async-trait", "bytes", "futures-util", "http", "http-body", "mime", "rustversion", "tower-layer", "tower-service", ] [[package]] name = "backtrace" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "base64" version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "blocking" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ "async-channel", "async-lock 3.1.1", "async-task", "fastrand 2.0.1", "futures-io", "futures-lite 2.0.1", "piper", "tracing", ] [[package]] name = "bumpalo" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", "windows-targets", ] [[package]] name = "clap" version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "clap_lex" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "concurrent-queue" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" dependencies = [ "crossbeam-utils", ] [[package]] name = "core-foundation-sys" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] [[package]] name = "crossbeam-utils" version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "data-encoding" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "derivative" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "dhcproto" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcee045385d5f7819022821f41209b9945d17550760b0b2349aaef4ecfa14bc3" dependencies = [ "dhcproto-macros", "hex", "ipnet", "rand", "thiserror", "trust-dns-proto", "url", ] [[package]] name = "dhcproto-macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7993efb860416547839c115490d4951c6d0f8ec04a3594d9dd99d50ed7ec170" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "enum-as-inner" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ "heck", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "enum-as-inner" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "enumflags2" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939" dependencies = [ "enumflags2_derive", "serde", ] [[package]] name = "enumflags2_derive" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "env_logger" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", "log", "regex", "termcolor", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ "libc", "windows-sys", ] [[package]] name = "etherparse" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "827292ea592108849932ad8e30218f8b1f21c0dfd0696698a18b5d0aed62d990" dependencies = [ "arrayvec", ] [[package]] name = "ethtool" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d8e04a35517dc77748dc04bf38152799382d3d8f85cb07cb579bb7f4d8d3b5a" dependencies = [ "anyhow", "byteorder", "futures", "genetlink", "log", "netlink-packet-core", "netlink-packet-generic", "netlink-packet-utils", "netlink-proto", "netlink-sys", "thiserror", "tokio", ] [[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "event-listener-strategy" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d96b852f1345da36d551b9473fa1e2b1eb5c5195585c6c018118bc92a8d91160" dependencies = [ "event-listener 3.1.0", "pin-project-lite", ] [[package]] name = "fastrand" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "fs2" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" dependencies = [ "libc", "winapi", ] [[package]] name = "futures" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-lite" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand 1.9.0", "futures-core", "futures-io", "memchr", "parking", "pin-project-lite", "waker-fn", ] [[package]] name = "futures-lite" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3831c2651acb5177cbd83943f3d9c8912c5ad03c76afcc0e9511ba568ec5ebb" dependencies = [ "fastrand 2.0.1", "futures-core", "futures-io", "memchr", "parking", "pin-project-lite", ] [[package]] name = "futures-macro" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "futures-sink" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "genetlink" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f890076c1faa1298bf747ce3694a8d9e0d2cc4b06fe293f12dd95742bfd079f" dependencies = [ "futures", "log", "netlink-packet-core", "netlink-packet-generic", "netlink-packet-utils", "netlink-proto", "thiserror", ] [[package]] name = "getrandom" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "h2" version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", "http", "indexmap 2.1.0", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "home" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ "windows-sys", ] [[package]] name = "http" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", "pin-project-lite", ] [[package]] name = "httparse" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "socket2 0.4.10", "tokio", "tower-service", "tracing", "want", ] [[package]] name = "hyper-timeout" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper", "pin-project-lite", "tokio", "tokio-io-timeout", ] [[package]] name = "iana-time-zone" version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "idna" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "idna" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", ] [[package]] name = "indexmap" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown 0.14.2", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi", "libc", "windows-sys", ] [[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" dependencies = [ "serde", ] [[package]] name = "iptables" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d39f0d72d0feb83c9b7f4e1fbde2b4a629886f30841127b3f86383831dba2629" dependencies = [ "lazy_static", "nix 0.27.1", "regex", ] [[package]] name = "is-terminal" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix 0.38.25", "windows-sys", ] [[package]] name = "itertools" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "matches" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "mio" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi", "windows-sys", ] [[package]] name = "mozim" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c1c15d0df314be0af498b122169739cc873045e431a0cb1a83564f7459ac491" dependencies = [ "byteorder", "dhcproto", "etherparse", "futures", "libc", "log", "nispor", "nix 0.26.4", "rand", ] [[package]] name = "mptcp-pm" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eafa8fc63dce407b75e336f9a22f18cf5510a3a5c3a5d83262688eb5cca42d5" dependencies = [ "anyhow", "byteorder", "futures", "genetlink", "log", "netlink-packet-core", "netlink-packet-generic", "netlink-packet-utils", "netlink-proto", "netlink-sys", "thiserror", "tokio", ] [[package]] name = "multimap" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "netavark" version = "1.9.0" dependencies = [ "anyhow", "chrono", "clap", "env_logger", "fs2", "futures-channel", "futures-core", "futures-util", "http", "ipnet", "iptables", "libc", "log", "mozim", "netlink-packet-core", "netlink-packet-route", "netlink-packet-utils", "netlink-sys", "nispor", "nix 0.27.1", "once_cell", "prost", "rand", "serde", "serde-value", "serde_json", "sha2", "sysctl", "tempfile", "tokio", "tokio-stream", "tonic", "tonic-build", "tower", "url", "zbus", ] [[package]] name = "netlink-packet-core" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" dependencies = [ "anyhow", "byteorder", "netlink-packet-utils", ] [[package]] name = "netlink-packet-generic" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd7eb8ad331c84c6b8cb7f685b448133e5ad82e1ffd5acafac374af4a5a308b" dependencies = [ "anyhow", "byteorder", "netlink-packet-core", "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" dependencies = [ "anyhow", "bitflags 1.3.2", "byteorder", "libc", "netlink-packet-core", "netlink-packet-utils", ] [[package]] name = "netlink-packet-utils" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" dependencies = [ "anyhow", "byteorder", "paste", "thiserror", ] [[package]] name = "netlink-proto" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "842c6770fc4bb33dd902f41829c61ef872b8e38de1405aa0b938b27b8fba12c3" dependencies = [ "bytes", "futures", "log", "netlink-packet-core", "netlink-sys", "thiserror", "tokio", ] [[package]] name = "netlink-sys" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" dependencies = [ "bytes", "futures", "libc", "log", "tokio", ] [[package]] name = "nispor" version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d219cea7e1b66a2de5d4dd3e29fb561e91568377e57544912cce3e870b29542" dependencies = [ "ethtool", "futures", "libc", "log", "mptcp-pm", "netlink-packet-route", "netlink-packet-utils", "netlink-sys", "rtnetlink", "serde", "serde_json", "tokio", ] [[package]] name = "nix" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", "memoffset", "pin-utils", ] [[package]] name = "nix" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ "bitflags 2.4.1", "cfg-if", "libc", ] [[package]] name = "num-traits" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "object" version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "ordered-float" version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" dependencies = [ "num-traits", ] [[package]] name = "ordered-stream" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" dependencies = [ "futures-core", "pin-project-lite", ] [[package]] name = "parking" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "percent-encoding" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "petgraph" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", "indexmap 2.1.0", ] [[package]] name = "pin-project" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" dependencies = [ "atomic-waker", "fastrand 2.0.1", "futures-io", ] [[package]] name = "polling" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", "bitflags 1.3.2", "cfg-if", "concurrent-queue", "libc", "log", "pin-project-lite", "windows-sys", ] [[package]] name = "polling" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53b6af1f60f36f8c2ac2aad5459d75a5a9b4be1e8cdd40264f315d78193e531" dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", "rustix 0.38.25", "tracing", "windows-sys", ] [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", "syn 2.0.39", ] [[package]] name = "proc-macro-crate" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "prost" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5a410fc7882af66deb8d01d01737353cf3ad6204c408177ba494291a626312" dependencies = [ "bytes", "prost-derive", ] [[package]] name = "prost-build" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa3d084c8704911bfefb2771be2f9b6c5c0da7343a71e0021ee3c665cada738" dependencies = [ "bytes", "heck", "itertools", "log", "multimap", "once_cell", "petgraph", "prettyplease", "prost", "prost-types", "regex", "syn 2.0.39", "tempfile", "which", ] [[package]] name = "prost-derive" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "065717a5dfaca4a83d2fe57db3487b311365200000551d7a364e715dbf4346bc" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "prost-types" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8339f32236f590281e2f6368276441394fcd1b2133b549cc895d0ae80f2f9a52" dependencies = [ "prost", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rtnetlink" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" dependencies = [ "futures", "log", "netlink-packet-core", "netlink-packet-route", "netlink-packet-utils", "netlink-proto", "netlink-sys", "nix 0.26.4", "thiserror", "tokio", ] [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ "bitflags 1.3.2", "errno", "io-lifetimes", "libc", "linux-raw-sys 0.3.8", "windows-sys", ] [[package]] name = "rustix" version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys 0.4.11", "windows-sys", ] [[package]] name = "rustversion" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "serde" version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] [[package]] name = "serde-value" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" dependencies = [ "ordered-float", "serde", ] [[package]] name = "serde_derive" version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "serde_json" version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_repr" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "signal-hook-registry" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "socket2" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", ] [[package]] name = "socket2" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sysctl" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea" dependencies = [ "bitflags 2.4.1", "byteorder", "enum-as-inner 0.6.0", "libc", "thiserror", "walkdir", ] [[package]] name = "tempfile" version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall", "rustix 0.38.25", "windows-sys", ] [[package]] name = "termcolor" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", "pin-project-lite", "signal-hook-registry", "socket2 0.5.5", "tokio-macros", "windows-sys", ] [[package]] name = "tokio-io-timeout" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ "pin-project-lite", "tokio", ] [[package]] name = "tokio-macros" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "tokio-stream" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "tokio-util" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", "tracing", ] [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.1.0", "toml_datetime", "winnow", ] [[package]] name = "tonic" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" dependencies = [ "async-stream", "async-trait", "axum", "base64", "bytes", "h2", "http", "http-body", "hyper", "hyper-timeout", "percent-encoding", "pin-project", "prost", "tokio", "tokio-stream", "tower", "tower-layer", "tower-service", "tracing", ] [[package]] name = "tonic-build" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" dependencies = [ "prettyplease", "proc-macro2", "prost-build", "quote", "syn 2.0.39", ] [[package]] name = "tower" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", "indexmap 1.9.3", "pin-project", "pin-project-lite", "rand", "slab", "tokio", "tokio-util", "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower-layer" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "trust-dns-proto" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" dependencies = [ "async-trait", "cfg-if", "data-encoding", "enum-as-inner 0.5.1", "futures-channel", "futures-io", "futures-util", "idna 0.2.3", "ipnet", "lazy_static", "rand", "smallvec", "thiserror", "tinyvec", "tracing", "url", ] [[package]] name = "try-lock" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uds_windows" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" dependencies = [ "tempfile", "winapi", ] [[package]] name = "unicode-bidi" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "url" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna 0.4.0", "percent-encoding", "serde", ] [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "waker-fn" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" [[package]] name = "walkdir" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "which" version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", "home", "once_cell", "rustix 0.38.25", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" dependencies = [ "memchr", ] [[package]] name = "xdg-home" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" dependencies = [ "nix 0.26.4", "winapi", ] [[package]] name = "zbus" version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948" dependencies = [ "async-broadcast", "async-executor", "async-fs", "async-io 1.13.0", "async-lock 2.8.0", "async-process", "async-recursion", "async-task", "async-trait", "blocking", "byteorder", "derivative", "enumflags2", "event-listener 2.5.3", "futures-core", "futures-sink", "futures-util", "hex", "nix 0.26.4", "once_cell", "ordered-stream", "rand", "serde", "serde_repr", "sha1", "static_assertions", "tracing", "uds_windows", "winapi", "xdg-home", "zbus_macros", "zbus_names", "zvariant", ] [[package]] name = "zbus_macros" version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "regex", "syn 1.0.109", "zvariant_utils", ] [[package]] name = "zbus_names" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9" dependencies = [ "serde", "static_assertions", "zvariant", ] [[package]] name = "zvariant" version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c" dependencies = [ "byteorder", "enumflags2", "libc", "serde", "static_assertions", "zvariant_derive", ] [[package]] name = "zvariant_derive" version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", "zvariant_utils", ] [[package]] name = "zvariant_utils" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] containers-netavark-83edb4b/Cargo.toml000066400000000000000000000036201452673426700201360ustar00rootroot00000000000000[package] name = "netavark" version = "1.9.0" edition = "2021" authors = ["github.com/containers"] license = "Apache-2.0" readme = "README.md" description = "A container network stack" homepage = "https://github.com/containers/netavark" repository = "https://github.com/containers/netavark" categories = ["containers", "networking", "podman"] exclude = ["/.cirrus.yml", "/.github/*", "/hack/*"] build = "build.rs" [package.metadata.vendor-filter] platforms = ["*-unknown-linux-*"] tier = "2" [[bin]] name = "netavark" path = "src/main.rs" [[bin]] name = "netavark-dhcp-proxy-client" path = "src/dhcp_proxy_client/client.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] default = ["serde", "deps-serde"] deps-serde = ["chrono/serde", "url/serde"] [dependencies] anyhow = "1.0" clap = { version = "~4.4.8", features = ["derive"] } env_logger = "0.10.1" ipnet = { version = "2", features = ["serde"] } iptables = "0.5" libc = "0.2" log = "0.4.20" serde = { version = "1.0.190", features = ["derive"], optional = true } serde-value = "0.7.0" serde_json = "1.0.108" sysctl = "0.5.5" url = "2.4.1" zbus = { version = "3.14.1" } nix = { version = "0.27.1", features = ["sched", "signal", "user"] } rand = "0.8.5" sha2 = "0.10.8" netlink-packet-utils = "0.5.2" netlink-packet-route = "0.17.1" netlink-packet-core = "0.7.0" fs2 = "0.4.3" netlink-sys = "0.8.5" tokio = { version = "1.34", features = ["rt", "rt-multi-thread", "signal", "fs"] } tokio-stream = { version = "0.1", features = ["net"] } tonic = "0.10" mozim = "0.2.2" prost = "0.12" futures-channel="0.3.29" futures-core = "0.3.29" futures-util = "0.3.29" nispor = "1.2.14" http = "0.2.11" tower = { version = "0.4" } [build-dependencies] chrono = { version = "0.4.31", default-features = false, features = ["clock"] } tonic-build = "0.10" [dev-dependencies] once_cell = "1.18.0" rand = "0.8.5" tempfile = "3.8.1" containers-netavark-83edb4b/DISTRO_PACKAGE.md000066400000000000000000000065061452673426700206550ustar00rootroot00000000000000# Netavark: A container network stack This document is currently written with Fedora as a reference. As Netavark gets shipped in other distros, this should become a distro-agnostic document. ## Fedora Users Netavark is available as an officlal Fedora package on Fedora 35 and newer versions and is only meant to be used with Podman v4 and newer releases. ```console $ sudo dnf install netavark ``` **NOTE:** Fedora 35 users will not be able to install Podman v4 using the default yum repositories. Please consult the Podman packaging docs for instructions on how to fetch Podman v4.0 on Fedora 35. After installation, if you would like to migrate all your containers to use Netavark, you will need to set `network_backend = "netavark"` under the `[network]` section in your containers.conf (typically located at: `/usr/share/containers/containers.conf` If you would like to test the latest unreleased upstream code, try the podman-next COPR ```console $ sudo dnf copr enable rhcontainerbot/podman-next $ sudo dnf install netavark ``` **CAUTION:** The podman-next COPR provides the latest unreleased sources of Podman, Netavark and Aardvark-dns as rpms which would override the versions provided by the official packages. ## Distro Packagers The vendored sources for netavark will be attached to each netavark release as a tarball. You can download them with the following: `https://github.com/containers/netavark/releases/download/v{version}/netavark-v{version}.tar.gz` And then create a cargo config file to point it to the vendor dir. ``` tar xvf %{SOURCE} mkdir -p .cargo cat >.cargo/config << EOF [source.crates-io] replace-with = "vendored-sources" [source.vendored-sources] directory = "vendor" EOF ``` The Fedora packaging sources for Netavark are available at the [Netavark dist-git](https://src.fedoraproject.org/rpms/netavark). The Fedora packaged versions of the rust crates that Netavark depends on are frequently out of date, for example, rtnetlink, sha2, zbus and zvariant at the time of initial package creation. So, the Fedora package builds Netavark using the dependencies vendored upstream, found in the `vendor` subdirectory. The `netavark` binary is installed to `/usr/libexec/podman/netavark`. ## Dependency on aardvark-dns The netavark package has a `Recommends` on the `aardvark-dns` package. The aardvark-dns package will be installed by default with netavark, but netavark will be functional without it. ## Relationship with the CNI Plugins package While Netavark is a replacement for CNI Plugins (available as `containernetworking-plugins` on Fedora), the `netavark` package should be recommended for new installations but will not conflict with `containernetworking-plugins`. To avoid that conflict, we have made the following changes to the Fedora packages. 1. netavark package includes: ``` Provides: container-network-stack = 2 ``` 2. containernetworking-plugins package includes: ``` Provides: container-network-stack = 1 ``` 3. containers-common package includes: ``` Requires: container-network-stack Recommends: netavark ``` ## Listing bundled dependencies If you need to list the bundled dependencies in your packaging sources, you can run the `cargo tree` command in the upstream source. For example, Fedora's packaging source uses: ``` $ cargo tree --prefix none | awk '{print "Provides: bundled(crate("$1")) = "$2}' | sort | uniq ``` containers-netavark-83edb4b/LICENSE000066400000000000000000000261351452673426700172210ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. containers-netavark-83edb4b/Makefile000066400000000000000000000115211452673426700176450ustar00rootroot00000000000000# This Makefile is intended for developer convenience. For the most part # all the targets here simply wrap calls to the `cargo` tool. Therefore, # most targets must be marked 'PHONY' to prevent `make` getting in the way # #prog :=xnixperms DESTDIR ?= PREFIX ?= /usr/local LIBEXECDIR ?= ${PREFIX}/libexec LIBEXECPODMAN ?= ${LIBEXECDIR}/podman SYSTEMDDIR ?= ${PREFIX}/lib/systemd/system SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z) # Get crate version by parsing the line that starts with version. CRATE_VERSION ?= $(shell grep ^version Cargo.toml | awk '{print $$3}') GIT_TAG ?= $(shell git describe --tags) # Set this to any non-empty string to enable unoptimized # build w/ debugging features. debug ?= # Set path to cargo executable CARGO ?= cargo # All complication artifacts, including dependencies and intermediates # will be stored here, for all architectures. Use a non-default name # since the (default) 'target' is used/referenced ambiguously in many # places in the tool-chain (including 'make' itself). CARGO_TARGET_DIR ?= targets export CARGO_TARGET_DIR # 'cargo' is sensitive to this env. var. value. ifdef debug $(info debug is $(debug)) # These affect both $(CARGO_TARGET_DIR) layout and contents # Ref: https://doc.rust-lang.org/cargo/guide/build-cache.html release := profile :=debug else release :=--release profile :=release endif .PHONY: all all: build bin: mkdir -p $@ $(CARGO_TARGET_DIR): mkdir -p $@ .PHONY: build build: build_netavark build_proxy_client .PHONY: build_netavark build_netavark: bin $(CARGO_TARGET_DIR) $(CARGO) build $(release) cp $(CARGO_TARGET_DIR)/$(profile)/netavark bin/netavark$(if $(debug),.debug,) .PHONY: examples examples: bin $(CARGO_TARGET_DIR) cargo build --examples $(release) .PHONY: crate-publish crate-publish: @if [ "v$(CRATE_VERSION)" != "$(GIT_TAG)" ]; then\ echo "Git tag is not equivalent to the version set in Cargo.toml. Please checkout the correct tag";\ exit 1;\ fi @echo "It is expected that you have already done 'cargo login' before running this command. If not command may fail later" $(CARGO) publish --dry-run $(CARGO) publish .PHONY: clean clean: rm -rf bin if [ "$(CARGO_TARGET_DIR)" = "targets" ]; then rm -rf targets; fi $(MAKE) -C docs clean .PHONY: client client: bin $(CARGO_TARGET_DIR) $(CARGO) build --bin netavark-dhcp-proxy-client $(release) .PHONY: docs docs: ## build the docs on the host $(MAKE) -C docs NV_UNIT_FILES = contrib/systemd/system/netavark-dhcp-proxy.service \ contrib/systemd/system/netavark-firewalld-reload.service %.service: %.service.in sed -e 's;@@NETAVARK@@;$(LIBEXECPODMAN)/netavark;g' $< >$@.tmp.$$ \ && mv -f $@.tmp.$$ $@ .PHONY: install install: $(NV_UNIT_FILES) install ${SELINUXOPT} -D -m0755 bin/netavark $(DESTDIR)/$(LIBEXECPODMAN)/netavark $(MAKE) -C docs install install ${SELINUXOPT} -m 755 -d ${DESTDIR}${SYSTEMDDIR} install ${SELINUXOPT} -m 644 contrib/systemd/system/netavark-dhcp-proxy.socket ${DESTDIR}${SYSTEMDDIR}/netavark-dhcp-proxy.socket install ${SELINUXOPT} -m 644 contrib/systemd/system/netavark-dhcp-proxy.service ${DESTDIR}${SYSTEMDDIR}/netavark-dhcp-proxy.service install ${SELINUXOPT} -m 644 contrib/systemd/system/netavark-firewalld-reload.service ${DESTDIR}${SYSTEMDDIR}/netavark-firewalld-reload.service .PHONY: uninstall uninstall: rm -f $(DESTDIR)/$(LIBEXECPODMAN)/netavark rm -f $(PREFIX)/share/man/man1/netavark*.1 rm -f ${DESTDIR}${SYSTEMDDIR}/netavark-dhcp-proxy.service rm -f ${DESTDIR}${SYSTEMDDIR}/netavark-dhcp-proxy.socket .PHONY: test test: unit integration # Used by CI to compile the unit tests but not run them .PHONY: build_unit build_unit: $(CARGO_TARGET_DIR) $(CARGO) test --no-run .PHONY: unit unit: $(CARGO_TARGET_DIR) $(CARGO) test .PHONY: integration integration: $(CARGO_TARGET_DIR) examples # needs to be run as root or with podman unshare --rootless-netns bats test/ bats test-dhcp/ .PHONY: validate validate: $(CARGO_TARGET_DIR) $(CARGO) fmt --all -- --check $(CARGO) clippy -p netavark@$(CRATE_VERSION) -- -D warnings $(MAKE) docs .PHONY: vendor-tarball vendor-tarball: build install.cargo-vendor-filterer VERSION=$(shell bin/netavark --version | cut -f2 -d" ") && \ $(CARGO) vendor-filterer --format=tar.gz --prefix vendor/ && \ mv vendor.tar.gz netavark-v$$VERSION-vendor.tar.gz && \ gzip -c bin/netavark > netavark.gz && \ sha256sum netavark.gz netavark-v$$VERSION-vendor.tar.gz > sha256sum .PHONY: install.cargo-vendor-filterer install.cargo-vendor-filterer: $(CARGO) install cargo-vendor-filterer .PHONY: mock-rpm mock-rpm: rpkg local .PHONY: help help: @echo "usage: make $(prog) [debug=1]" .PHONY: build_proxy_client build_proxy_client: bin $(CARGO_TARGET_DIR) $(CARGO) build --bin netavark-dhcp-proxy-client $(release) cp $(CARGO_TARGET_DIR)/$(profile)/netavark-dhcp-proxy-client bin/netavark-dhcp-proxy-client$(if $(debug),.debug,) containers-netavark-83edb4b/OWNERS000066400000000000000000000006511452673426700171470ustar00rootroot00000000000000approvers: - baude - cevich - edsantiago - flouthoc - giuseppe - jwhonce - lsm5 - Luap99 - mheon - mtrmac - rhatdan - saschagrunert - TomSweeneyRedHat - umohnani8 - vrothberg reviewers: - ashley-cui - baude - cevich - edsantiago - flouthoc - giuseppe - jwhonce - lsm5 - Luap99 - mheon - mtrmac - rhatdan - saschagrunert - TomSweeneyRedHat - umohnani8 - vrothberg containers-netavark-83edb4b/README.md000066400000000000000000000031671452673426700174730ustar00rootroot00000000000000# netavark: A container network stack Netavark is a rust based network stack for containers. It is being designed to work with [Podman](https://github.com/containers/podman) but is also applicable for other OCI container management applications. ## Overview and scope Netavark is a tool for configuring networking for Linux containers. Its features include: * Configuration of container networks via JSON configuration file * Creation and management of required network interfaces, including MACVLAN networks * All required firewall configuration to perform NAT and port forwarding as required for containers * Support for iptables and firewalld at present, with support for nftables planned in a future release * Support for rootless containers * Support for IPv4 and IPv6 * Support for container DNS resolution via the [aardvark-dns](https://github.com/containers/aardvark-dns) project ## Requires - [go-md2man](https://github.com/cpuguy83/go-md2man) - [Rust](https://www.rust-lang.org/tools/install) - [Podman](https://podman.io/docs) 4.0+ - [protoc](https://grpc.io/docs/protoc-installation/) ## Build ```console $ make ``` ## Test ```console $ make test ``` Also see [./test](./test/README.md) for more information. ## Communications For general questions and discussion, please use Podman's [channels](https://podman.io/community/). For discussions around issues/bugs and features, you can use the GitHub [issues](https://github.com/containers/netavark/issues) and [PRs](https://github.com/containers/netavark/pulls) tracking system. ## Plugins Netavark also supports executing external plugins, see [./plugin-API.md](./plugin-API.md). containers-netavark-83edb4b/RELEASE_NOTES.md000066400000000000000000000063401452673426700205620ustar00rootroot00000000000000# Release Notes ## v1.9.0 * add firewalld-reload subcommand * bridge: force static mac on bridge interface * dependency updates * numerous fixes to test suite ## v1.8.0 * iptables: improve error when ip6?tables commands are missing * docs: Convert markdown with go-md2man instead of mandown * iptables: drop invalid packages * bump rust edition to 2021 * Add ACCEPT rules in firewall for bridge network with internal dns * Add vrf support for bridges ## v1.7.0 * Fix misleading dns disabled log * Dependency updates * --config is now required when dns is used * netavark dhcp-proxy correctly renews the lease after dhcp time-out * bridge: isolate=strict option has been added * macvlan: bclim option has been added * "no_default_route" option has been added * static routes can now be configured ## v1.6.0 * Now supports a driver plugin module for user defined network drivers * Initial MACVLAN DHCP support (additional unit file required for packagers) * Dependency updates ## v1.5.0 * Removed crossbeam-utils * Dependency updates * Preliminary macvlan dhcp support (not fully supported yet) * Addition of ipvlan support ## v1.4.0 * Added network update command * Corrected issue #491 to only teardown network forwarding when on complete teardown only * Fixed some rust documentation ## v1.3.0 * Housekeep and code cleanup * macvlan: remove tmp interface when name already used in netns * Add support for route metrics * netlink: return better error if ipv6 is disabled * macvlan: fix name collision on hostns * Ignore dns-enabled for macvlan (BZ2137320) * better errors on teardown * allow customer dns servers for containers * do not set route for internal-only networks * do not use ipv6 autoconf ## v1.2.0 * Reworked how netavark calls aardvark * Implemented locking when committing * Remove bridge only when no containers are attached * Updated versions of libraries where possible ## v1.1.0 * Netavark is now capable of starting Aardvark on a port other than 53 (controlled by `dns_bind_port` in `containers.conf`). Firewall rules are added to ensure DNS still functions properly despite the port change. * Added the ability to isolate networks. Networks with the isolate option set cannot communicate with other networks with the isolate option set. * Improved the way Aardvark is launched to avoid potential race conditions where DNS would not be ready when containers were started. * Fixed a bug where Aardvark could not be run in environments with a read-only `/proc` (e.g. inside a container). ## v1.0.3 * Updated dependenciess * Simplified option parsing for bridge/macvlan * Added support for an ipam `none` driver ## v1.0.2 * Fix issue [#13533](https://github.com/containers/podman/issues/13533) - only use systemd when present * Dropped vergen dependency * Updated several dependency libraries * Allow macvlans to not require a default gateway ## v1.0.1 * core,macvlan: add gateway as default route to macvlan interface * Add host_ip and container_ip version matching to iptables portforwardinhg * Remove vendor directory from upstream github repo ## v1.0.0 * First official release of netavark ## v1.0.0-RC2 * RC2 containers several bug fixes and code cleanup ## v1.0.0-RC1 * This is the first release candidate of Netavark. All functionality should be working. containers-netavark-83edb4b/SECURITY.md000066400000000000000000000003571452673426700200030ustar00rootroot00000000000000## Security and Disclosure Information Policy for the Netavark Project The Netavark Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/main/SECURITY.md) for the Containers Projects. containers-netavark-83edb4b/build.rs000066400000000000000000000057131452673426700176600ustar00rootroot00000000000000use chrono::{DateTime, NaiveDateTime, Utc}; use std::env; use std::path::{Path, PathBuf}; use std::process::Command; fn main() { let builder = tonic_build::configure() .type_attribute("netavark_proxy.Lease", "#[derive(serde::Serialize)]") .type_attribute("netavark_proxy.DhcpV4Lease", "#[derive(serde::Serialize)]") .type_attribute("netavark_proxy.DhcpV6Lease", "#[derive(serde::Serialize)]") .type_attribute("netavark_proxy.IPResponse", "#[derive(serde::Serialize)]") .type_attribute("netavark_proxy.MacAddress", "#[derive(serde::Serialize)]") .type_attribute("netavark_proxy.NvIpv4Addr", "#[derive(serde::Serialize)]") .type_attribute("netavark_proxy.Lease", "#[derive(serde::Deserialize)]") .type_attribute( "netavark_proxy.DhcpV4Lease", "#[derive(serde::Deserialize)]", ) .type_attribute( "netavark_proxy.DhcpV6Lease", "#[derive(serde::Deserialize)]", ) .type_attribute("netavark_proxy.IPResponse", "#[derive(serde::Deserialize)]") .type_attribute("netavark_proxy.MacAddress", "#[derive(serde::Deserialize)]") .type_attribute("netavark_proxy.NvIpv4Addr", "#[derive(serde::Deserialize)]") .type_attribute("netavark_proxy.MacAddress", "#[derive(Eq)]") .type_attribute("netavark_proxy.MacAddress", "#[derive(Hash)]") .type_attribute( "netavark_proxy.NetworkConfig", "#[derive(serde::Deserialize)]", ) .type_attribute( "netavark_proxy.NetworkConfig", "#[derive(serde::Serialize)]", ) .out_dir(PathBuf::from("src/proto-build")); builder .compile(&[Path::new("src/proto/proxy.proto")], &[Path::new("proto")]) .unwrap_or_else(|e| panic!("Failed at builder: {:?}", e.to_string())); // Generate the default 'cargo:' instruction output println!("cargo:rerun-if-changed=build.rs"); // get timestamp let now = match env::var("SOURCE_DATE_EPOCH") { Ok(val) => { let naive = match NaiveDateTime::from_timestamp_opt(val.parse::().unwrap(), 0) { Some(n) => n, None => Utc::now().naive_utc(), }; let datetime: DateTime = DateTime::from_naive_utc_and_offset(naive, Utc); datetime } Err(_) => Utc::now(), }; println!("cargo:rustc-env=BUILD_TIMESTAMP={}", now.to_rfc3339()); // get rust target triple from TARGET env println!( "cargo:rustc-env=BUILD_TARGET={}", std::env::var("TARGET").unwrap() ); // get git commit let command = Command::new("git").args(["rev-parse", "HEAD"]).output(); let commit = match command { Ok(output) => String::from_utf8(output.stdout).unwrap(), // if error, e.g. build from source without git repo, just show empty string Err(_) => "".to_string(), }; println!("cargo:rustc-env=GIT_COMMIT={commit}"); } containers-netavark-83edb4b/contrib/000077500000000000000000000000001452673426700176455ustar00rootroot00000000000000containers-netavark-83edb4b/contrib/cirrus/000077500000000000000000000000001452673426700211545ustar00rootroot00000000000000containers-netavark-83edb4b/contrib/cirrus/cache_groom.sh000066400000000000000000000070051452673426700237600ustar00rootroot00000000000000#!/bin/bash # # This script is intended to be run from Cirrus-CI to prepare the # rust targets cache for re-use during subsequent runs. This mainly # involves removing files and directories which change frequently # but are cheap/quick to regenerate - i.e. prevent "cache-flapping". # Any other use of this script is not supported and may cause harm. # # WARNING: This script is re-used from $DEST_BRANCH by other # repositories. Namely aardvark-dns and possibly others. Check # before removing / changing / updating. set -eo pipefail source $(dirname ${BASH_SOURCE[0]})/lib.sh if [[ "$CIRRUS_CI" != true ]]; then die "Script is not intended for use outside of Cirrus-CI" fi req_env_vars CARGO_HOME CARGO_TARGET_DIR CIRRUS_BUILD_ID # Giant-meat-cleaver HACK: It's possible (with a long-running cache key) for # the targets and/or cargo cache to grow without-bound (gigabytes). Ref: # https://github.com/rust-lang/cargo/issues/5026 # There isn't a good way to deal with this or account for outdated content # in some intelligent way w/o trolling through config and code files. So, # Any time the Cirrus-CI build ID is evenly divisible by some number (chosen # arbitrarily) clobber the whole thing and make the next run entirely # re-populate cache. This is ugly, but maybe the best option available :( if [[ "$CIRRUS_BRANCH" == "$DEST_BRANCH" ]] && ((CIRRUS_BUILD_ID%15==0)); then msg "It's a cache-clobber build, yay! This build has been randomly selected for" msg "a forced cache-wipe! Congratulations! This means the next build will be" msg "slow, and nobody will know who to to blame!. Lucky you! Hurray!" msg "(This is necessary to prevent branch-level cache from infinitely growing)" cd $CARGO_TARGET_DIR # Could use `cargo clean` for this, but it's easier to just clobber everything. rm -rf ./* ./.??* # In case somebody goes poking around, leave a calling-card hopefully leading # them back to this script. I don't know of a better way to handle this :S touch CACHE_WAS_CLOBBERED cd $CARGO_HOME rm -rf ./* ./.??* touch CACHE_WAS_CLOBBERED exit 0 fi # The following applies to both PRs and branch-level cache. It attempts to remove # things which are non-essential and/or may change frequently. It stops short of # trolling through config & code files to determine what is relevant or not. # Ref: https://doc.rust-lang.org/nightly/cargo/guide/build-cache.html # https://github.com/Swatinem/rust-cache/tree/master/src cd $CARGO_TARGET_DIR for targetname in $(find ./ -type d -maxdepth 1 -mindepth 1); do msg "Grooming $CARGO_TARGET_DIR/$targetname..." cd $CARGO_TARGET_DIR/$targetname # Any top-level hidden files or directories showrun rm -rf ./.??* # Example targets showrun rm -rf ./target/debug/examples # Documentation showrun rm -rf ./target/doc # Internal to rust build process showrun rm -rf ./target/debug/deps ./target/debug/incremental ./target/debug/build done # The following only applies to dependent packages (crates). It follows recommendations # Ref: https://doc.rust-lang.org/nightly/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci # and probably shouldn't be extended beyond what's documented. This cache plays a major # role in built-time reduction, but must also be prevented from causing "cache-flapping". cd $CARGO_HOME for dirname in $(find ./ -type d -maxdepth 2 -mindepth 1); do case "$dirname" in ./bin) ;& # same steps as next item ./registry/index) ;& ./registry/cache) ;& ./git/db) continue ;; # Keep *) rm -rf $dirname ;; # Remove esac done containers-netavark-83edb4b/contrib/cirrus/lib.sh000066400000000000000000000044011452673426700222550ustar00rootroot00000000000000 # Library of common, shared utility functions. This file is intended # to be sourced by other scripts, not called directly. # BEGIN Global export of all variables set -a # Automation library installed at image-build time, # defining $AUTOMATION_LIB_PATH in this file. if [[ -r "/etc/automation_environment" ]]; then source /etc/automation_environment fi if [[ -n "$AUTOMATION_LIB_PATH" ]]; then source $AUTOMATION_LIB_PATH/common_lib.sh else ( echo "WARNING: It does not appear that containers/automation was installed." echo " Functionality of most of this library will be negatively impacted" echo " This ${BASH_SOURCE[0]} was loaded by ${BASH_SOURCE[1]}" ) > /dev/stderr fi # Unsafe env. vars for display SECRET_ENV_RE='(ACCOUNT)|(GC[EP]..+)|(SSH)|(PASSWORD)|(TOKEN)' # setup.sh calls make_cienv() to cache these values for the life of the VM if [[ -r "/etc/ci_environment" ]]; then source /etc/ci_environment else # set default values - see make_cienv() below # VM Images are built with this setup CARGO_HOME="${CARGO_HOME:-/var/cache/cargo}" source $CARGO_HOME/env # Make caching more effective - disable incremental compilation, # so that the Rust compiler doesn't waste time creating the # additional artifacts required for incremental builds. # Ref: https://github.com/marketplace/actions/rust-cache#cache-details CARGO_INCREMENTAL=0 fi # END Global export of all variables set -a # Shortcut to automation library timeout/retry function retry() { err_retry 8 1000 "" "$@"; } # just over 4 minutes max # Helper to ensure a consistent environment across multiple CI scripts # containers, and shell environments (e.g. hack/get_ci_vm.sh) make_cienv(){ local envname local envval local SETUP_ENVIRONMENT=1 for envname in CARGO_HOME PATH CIRRUS_WORKING_DIR SETUP_ENVIRONMENT; do envval="${!envname}" # Properly escape values to prevent injection printf -- "$envname=%q\n" "$envval" done } complete_setup(){ set +x msg "************************************************************" msg "Completing environment setup, writing vars:" msg "************************************************************" make_cienv | tee -a /etc/ci_environment } containers-netavark-83edb4b/contrib/cirrus/runner.sh000077500000000000000000000040751452673426700230320ustar00rootroot00000000000000#!/bin/bash set -eo pipefail # This script runs in the Cirrus CI environment, invoked from .cirrus.yml . # It can also be invoked manually in a `hack/get_ci_cm.sh` environment, # documentation of said usage is TBI. # # The principal deciding factor is the first argument. For any # given value 'xyz' there must be a function '_run_xyz' to handle that # argument. source $(dirname ${BASH_SOURCE[0]})/lib.sh _run_noarg() { die "runner.sh must be called with a single argument" } _run_build() { # Assume we're on a fast VM, compile everything needed by the # rest of CI since subsequent tasks may have limited resources. make all debug=1 make build_unit # reuses some debug binaries make all # optimized/non-debug binaries # This will get scooped up and become part of the artifact archive. # Identify where the binary came from to benefit downstream consumers. cat | tee bin/netavark.info << EOF repo: $CIRRUS_REPO_CLONE_URL branch: $CIRRUS_BASE_BRANCH title: $CIRRUS_CHANGE_TITLE commit: $CIRRUS_CHANGE_IN_REPO build: https://cirrus-ci.com/build/$CIRRUS_BUILD_ID task: https://cirrus-ci.com/task/$CIRRUS_TASK_ID EOF } _run_build_aarch64() { _run_build } _run_validate() { make validate } _run_validate_aarch64() { _run_validate } _run_unit() { make unit } _run_unit_aarch64() { _run_unit } _run_integration() { make integration } _run_integration_aarch64() { _run_integration } show_env_vars msg "************************************************************" msg "Toolchain details" msg "************************************************************" rustc --version cargo --version msg "************************************************************" msg "Runner executing '$1' on $OS_REL_VER" msg "************************************************************" ((${SETUP_ENVIRONMENT:-0})) || \ die "Expecting setup.sh to have completed successfully" cd "${CIRRUS_WORKING_DIR}/" handler="_run_${1:-noarg}" if [ "$(type -t $handler)" != "function" ]; then die "Unknown/Unsupported runner.sh argument '$1'" fi $handler containers-netavark-83edb4b/contrib/cirrus/setup.sh000077500000000000000000000027421452673426700226600ustar00rootroot00000000000000#!/bin/bash # This script configures the CI runtime environment. It's intended # to be used by Cirrus-CI, not humans. set -e source $(dirname $0)/lib.sh # Only do this once if [[ -r "/etc/ci_environment" ]]; then msg "It appears ${BASH_SOURCE[0]} already ran, exiting." exit 0 fi trap "complete_setup" EXIT msg "************************************************************" msg "Setting up runtime environment" msg "************************************************************" show_env_vars req_env_vars AARDVARK_DNS_URL AARDVARK_DNS_BRANCH cd /usr/libexec/podman rm -vf aardvark-dns* if showrun curl --fail --location -o /tmp/aardvark-dns.zip "$AARDVARK_DNS_URL" && \ unzip -o /tmp/aardvark-dns.zip; then if [[ $(uname -m) != "x86_64" ]]; then showrun mv aardvark-dns.$(uname -m)-unknown-linux-gnu aardvark-dns fi showrun chmod a+x /usr/libexec/podman/aardvark-dns else warn "Error downloading/extracting the latest pre-compiled aardvark binary from CI" showrun cargo install \ --root /usr/libexec/podman \ --git https://github.com/containers/aardvark-dns \ --branch "$AARDVARK_DNS_BRANCH" mv -v /usr/libexec/podman/bin/aardvark-dns /usr/libexec/podman fi # show aardvark commit in CI logs showrun /usr/libexec/podman/aardvark-dns version # Warning, this isn't the end. An exit-handler is installed to finalize # setup of env. vars. This is required for runner.sh to operate properly. # See complete_setup() in lib.sh for details. containers-netavark-83edb4b/contrib/container_images/000077500000000000000000000000001452673426700231545ustar00rootroot00000000000000containers-netavark-83edb4b/contrib/container_images/Dockerfile.CentOS9000066400000000000000000000001631452673426700263710ustar00rootroot00000000000000FROM quay.io/centos/centos:stream9 RUN dnf -y --enablerepo=crb install cargo protobuf-compiler && dnf -y clean all containers-netavark-83edb4b/contrib/container_images/Dockerfile.Fedora000066400000000000000000000001511452673426700263420ustar00rootroot00000000000000FROM registry.fedoraproject.org/fedora:39 RUN dnf -y install cargo protobuf-compiler && dnf -y clean all containers-netavark-83edb4b/contrib/container_images/Dockerfile.UbuntuLatest000066400000000000000000000001671452673426700276100ustar00rootroot00000000000000FROM docker.io/library/ubuntu:rolling RUN apt-get update && apt-get -y install cargo protobuf-compiler libprotobuf-dev containers-netavark-83edb4b/contrib/systemd/000077500000000000000000000000001452673426700213355ustar00rootroot00000000000000containers-netavark-83edb4b/contrib/systemd/system/000077500000000000000000000000001452673426700226615ustar00rootroot00000000000000containers-netavark-83edb4b/contrib/systemd/system/netavark-dhcp-proxy.service.in000066400000000000000000000003541452673426700305600ustar00rootroot00000000000000[Unit] Description=Netavark DHCP proxy service Requires=netavark-dhcp-proxy.socket After=netavark-dhcp-proxy.socket StartLimitIntervalSec=0 [Service] Type=exec ExecStart=@@NETAVARK@@ dhcp-proxy -a 30 [Install] WantedBy=default.target containers-netavark-83edb4b/contrib/systemd/system/netavark-dhcp-proxy.socket000066400000000000000000000002201452673426700277730ustar00rootroot00000000000000[Unit] Description=Netavark DHCP proxy socket [Socket] ListenStream=%t/podman/nv-proxy.sock SocketMode=0660 [Install] WantedBy=sockets.target containers-netavark-83edb4b/contrib/systemd/system/netavark-firewalld-reload.service.in000066400000000000000000000006221452673426700316760ustar00rootroot00000000000000[Unit] Description=Listen for the firewalld reload event and reapply all netavark firewall rules. # This causes systemd to stop this unit when firewalld is stopped. PartOf=firewalld.service After=firewalld.service [Service] ExecStart=@@NETAVARK@@ firewalld-reload [Install] # If the unit is enabled add a wants to firewalld so it is only started when firewalld is started. WantedBy=firewalld.service containers-netavark-83edb4b/docs/000077500000000000000000000000001452673426700171355ustar00rootroot00000000000000containers-netavark-83edb4b/docs/Makefile000066400000000000000000000006571452673426700206050ustar00rootroot00000000000000PREFIX ?= /usr/local DATADIR ?= ${PREFIX}/share MANDIR ?= $(DATADIR)/man GO ?= go GOMD2MAN ?= go-md2man docs: $(patsubst %.md,%,$(wildcard *.1.md)) %.1: %.1.md $(GOMD2MAN) -in $^ -out $@ .PHONY: .install.md2man .install.md2man: $(GO) install github.com/cpuguy83/go-md2man/v2@latest .PHONY: install install: install -d ${DESTDIR}/${MANDIR}/man1 install -m 0644 *.1 ${DESTDIR}/${MANDIR}/man1 .PHONY: clean clean: $(RM) *.1 containers-netavark-83edb4b/docs/netavark-dhcp-proxy.md000066400000000000000000000023561452673426700233730ustar00rootroot00000000000000% netavark-dhcp-proxy(1) ## NAME netavark-dhcp-proxy - a proxy for DHCP interactions with containers ## SYNOPSIS **netavark-dhcp-proxy** [*options*] *command* ## DESCRIPTION When using DHCP with MacVLAN and containers, you need the container to either have an init system with DHCP clients or some sort of proxy server that can act on behalf of the container. The netavark-dhcp-proxy is the latter and should be used in combination with Podman and Netavark when setting up containers that wish to use DHCP and MacVLAN networking. **netavark-dhcp-proxy [GLOBAL OPTIONS]** ## GLOBAL OPTIONS #### **--activity-timeout, -a** Time in seconds when the proxy should exit if it has no leases. The default time is *300* seconds. A value of *0* disables the activity timeout. #### **--dir**=*path* The directory option is a path to store the lease backup files. The default is */run/podman/*. The lease name is *nv-proxy.leases*. #### **--uds** Set the unix domain socket directory instead of using the default. The default is */run/podman*. The socket name is *nv-proxy.sock*. #### **--help**, **-h** Print usage statement #### **--version**, **-v** Print the version ## HISTORY Sep 2022, Originally compiled by Brent Baude containers-netavark-83edb4b/docs/netavark.1.md000066400000000000000000000031731452673426700214350ustar00rootroot00000000000000% netavark(1) ## NAME netavark - Configure a given network namespace for use by a container ## SYNOPSIS **netavark** [*options*] *command* *network namespace path* ## DESCRIPTION Netavark configures a network namespace according to a configuration read from STDIN. The configuration is JSON formatted. ## GLOBAL OPTIONS #### **--file**, **-f** Instead of reading from STDIN, read the configuration to be applied from the given file. **-f -** may also be used to flag reading from STDIN. ## COMMANDS ### netavark setup The setup command configures the given network namespace with the given configuration, creating any interfaces and firewall rules necessary. ### netavark teardown The teardown command is the inverse of the setup command, undoing any configuration applied. Some interfaces may not be deleted (bridge interfaces, for example, will not be removed). ### CONFIGURATION FORMAT The configuration accepted is the same for both setup and teardown. It is JSON formatted. Format is https://github.com/containers/podman/blob/cd7b48198c38c5028540e85dc72dd3406f4318f0/libpod/network/types/network.go#L164-L173 but we will also send a Networks array including all the network definitions (https://github.com/containers/podman/blob/cd7b48198c38c5028540e85dc72dd3406f4318f0/libpod/network/types/network.go#L32-L62) TODO: Transcribe configuration into here in a nice tabular format ## EXAMPLE netavark setup /run/user/1000/podman/netns/d11d1f9c499d netavark -f /run/podman/828b0508ae64.conf teardown /run/podman/netns/828b0508ae64 ## SEE ALSO podman(1) ## HISTORY September 2021, Originally compiled by Matt Heon containers-netavark-83edb4b/docs/publish-crate.md000066400000000000000000000004771452673426700222310ustar00rootroot00000000000000# Publishing netavark crate to crates.io ### Steps * Make sure you have already done `cargo login` on your current session with a valid token. * `cd netavark` * Git checkout the version which you want to publish. * `make crate-publish` * New version should be reflected here: https://crates.io/crates/netavark/versions containers-netavark-83edb4b/examples/000077500000000000000000000000001452673426700200235ustar00rootroot00000000000000containers-netavark-83edb4b/examples/error-plugin.rs000066400000000000000000000016401452673426700230170ustar00rootroot00000000000000//! This is just an example plugin, do not use it in production! use netavark::{ network::types, new_error, plugin::{Info, Plugin, PluginExec, API_VERSION}, }; fn main() { let info = Info::new("0.1.0-dev".to_owned(), API_VERSION.to_owned(), None); PluginExec::new(Exec {}, info).exec(); } struct Exec {} impl Plugin for Exec { fn create( &self, _network: types::Network, ) -> Result> { Err(new_error!("create error")) } fn setup( &self, _netns: String, _opts: types::NetworkPluginExec, ) -> Result> { Err(new_error!("setup error")) } fn teardown( &self, _netns: String, _opts: types::NetworkPluginExec, ) -> Result<(), Box> { Err(new_error!("teardown error")) } } containers-netavark-83edb4b/examples/host-device-plugin.rs000066400000000000000000000074421452673426700241060ustar00rootroot00000000000000//! This is just an example plugin, do not use it in production! use std::{ collections::HashMap, net::{Ipv4Addr, Ipv6Addr}, os::fd::AsFd, }; use netavark::{ network::{ core_utils::{open_netlink_sockets, CoreUtils}, netlink, types, }, new_error, plugin::{Info, Plugin, PluginExec, API_VERSION}, }; use netlink_packet_route::{address::Nla, nlas::link}; fn main() { let info = Info::new("0.1.0-dev".to_owned(), API_VERSION.to_owned(), None); PluginExec::new(Exec {}, info).exec(); } struct Exec {} impl Plugin for Exec { fn create( &self, network: types::Network, ) -> Result> { if network.network_interface.as_deref().unwrap_or_default() == "" { return Err(new_error!("no network interface is specified")); } Ok(network) } fn setup( &self, netns: String, opts: types::NetworkPluginExec, ) -> Result> { let (mut host, netns) = open_netlink_sockets(&netns)?; let name = opts.network.network_interface.unwrap_or_default(); let link = host.netlink.get_link(netlink::LinkID::Name(name.clone()))?; let mut mac_address = String::from(""); for nla in link.nlas { if let link::Nla::Address(ref addr) = nla { mac_address = CoreUtils::encode_address_to_hex(addr); } } let addresses = host.netlink.dump_addresses()?; let mut subnets = Vec::new(); for address in addresses { if address.header.index == link.header.index { for nla in address.nlas { if let Nla::Address(a) = &nla { let ip = match a.len() { 4 => Ipv4Addr::new(a[0], a[1], a[2], a[3]).into(), 16 => Ipv6Addr::from([ a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], ]) .into(), len => { return Err(new_error!("invalid netlink address, length: {}", len)) } }; let net = ipnet::IpNet::new(ip, address.header.prefix_len)?; subnets.push(types::NetAddress { gateway: None, ipnet: net, }) } } } } host.netlink .set_link_ns(link.header.index, netns.file.as_fd())?; // interfaces map, but we only ever expect one, for response let mut interfaces: HashMap = HashMap::new(); let interface = types::NetInterface { mac_address, subnets: Option::from(subnets), }; interfaces.insert(name, interface); // StatusBlock response let response = types::StatusBlock { dns_server_ips: None, dns_search_domains: None, interfaces: Some(interfaces), }; Ok(response) } fn teardown( &self, netns: String, opts: types::NetworkPluginExec, ) -> Result<(), Box> { // on teardown revert what was done in setup let (host, mut netns) = open_netlink_sockets(&netns)?; let name = opts.network.network_interface.unwrap_or_default(); let link = netns.netlink.get_link(netlink::LinkID::Name(name))?; netns .netlink .set_link_ns(link.header.index, host.file.as_fd())?; Ok(()) } } containers-netavark-83edb4b/examples/stderr-plugin.rs000066400000000000000000000021741452673426700231740ustar00rootroot00000000000000//! This is just an example plugin, do not use it in production! use netavark::{ network::types, plugin::{Info, Plugin, PluginExec, API_VERSION}, }; fn main() { let info = Info::new("0.1.0-dev".to_owned(), API_VERSION.to_owned(), None); PluginExec::new(Exec {}, info).exec(); } struct Exec {} impl Plugin for Exec { fn create( &self, network: types::Network, ) -> Result> { eprintln!("stderr create"); Ok(network) } fn setup( &self, _netns: String, _opts: types::NetworkPluginExec, ) -> Result> { eprintln!("stderr setup"); // StatusBlock response let response = types::StatusBlock { dns_server_ips: None, dns_search_domains: None, interfaces: None, }; Ok(response) } fn teardown( &self, _netns: String, _opts: types::NetworkPluginExec, ) -> Result<(), Box> { eprintln!("stderr teardown"); Ok(()) } } containers-netavark-83edb4b/hack/000077500000000000000000000000001452673426700171135ustar00rootroot00000000000000containers-netavark-83edb4b/hack/get_ci_vm.sh000077500000000000000000000046371452673426700214200ustar00rootroot00000000000000#!/usr/bin/env bash # # For help and usage information, simply execute the script w/o any arguments. # # This script is intended to be run by Red Hat podman developers who need # to debug problems specifically related to Cirrus-CI automated testing. # It requires that you have been granted prior access to create VMs in # google-cloud. For non-Red Hat contributors, VMs are available as-needed, # with supervision upon request. set -e SCRIPT_FILEPATH=$(realpath "${BASH_SOURCE[0]}") SCRIPT_DIRPATH=$(dirname "$SCRIPT_FILEPATH") REPO_DIRPATH=$(realpath "$SCRIPT_DIRPATH/../") # Help detect what get_ci_vm container called this script GET_CI_VM="${GET_CI_VM:-0}" in_get_ci_vm() { if ((GET_CI_VM==0)); then echo "Error: $1 is not intended for use in this context" exit 2 fi } # get_ci_vm APIv1 container entrypoint calls into this script # to obtain required repo. specific configuration options. if [[ "$1" == "--config" ]]; then in_get_ci_vm "$1" # handles GET_CI_VM==0 case case "$GET_CI_VM" in 1) cat < /dev/stderr ./contrib/cirrus/setup.sh else # Pass this repo and CLI args into container for VM creation/management mkdir -p $HOME/.config/gcloud/ssh mkdir -p $HOME/.aws podman run -it --rm \ --tz=local \ -e NAME="$USER" \ -e SRCDIR=/src \ -e GCLOUD_ZONE="$GCLOUD_ZONE" \ -e A_DEBUG="${A_DEBUG:-0}" \ -v $REPO_DIRPATH:/src:O \ -v $HOME/.config/gcloud:/root/.config/gcloud:z \ -v $HOME/.config/gcloud/ssh:/root/.ssh:z \ -v $HOME/.aws:/root/.aws:z \ quay.io/libpod/get_ci_vm:latest "$@" fi containers-netavark-83edb4b/perf-netavark.sh000077500000000000000000000004141452673426700213100ustar00rootroot00000000000000#!/bin/bash # Netavark binary NETAVARK=${NETAVARK:-./bin/netavark} trap cleanup EXIT function cleanup() { kill -9 $netnspid } unshare -n sleep 100 & netnspid=$! unshare -n perf stat $NETAVARK -f ./test/testfiles/simplebridge.json setup /proc/$netnspid/ns/net containers-netavark-83edb4b/plugin-API.md000066400000000000000000000136071452673426700204430ustar00rootroot00000000000000# Description of the netavark plugin API A netavark plugin is a external binary which must implement a specific set of subcommands that will be called by podman and netavark. - `create`: creates a network config - `setup`: setup the network configuration - `teardown`: tear down the network configuration - `info`: show info about this plugin ## Create subcommand The create subcommand creates a new network config for podman. The subcommand will receive the JSON network config via STDIN. Podman will populate the network name and ID before calling the plugin. The name and ID cannot be changed by the plugin. The driver name must also not be changed. All other config fields can be changed in the plugin. Other fields such as subnet and options will also be populated by podman when these options are set on the podman network create command, i.e. `--subnet` and `--option`. The plugin validates the given values and errors out for invalid values. On success the plugin should print the generated config as JSON to STDOUT. Example JSON input and output format: ``` { "name": "example1", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "mydriver", "network_interface": "enp1", "subnets": [ { "subnet": "10.0.0.0/16", "gateway": "10.0.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "host-local" }, "options": { "custom": "opt" } } ``` ## Setup subcommand The setup subcommand sets-up the network configuration. This command is called when a container is started or network connect is used, assuming the container uses a network which was created by the plugin, see the create command above. On STDIN it receives a JSON config which contains the network config and container options. ON STDOUT the plugin must return a JSON status block. This contains information about the created interface, the assigned ip and mac address. This information will be visible in the podman inspect output. Also this command accepts one argument which is the path to the container network namespace. Example JSON input: ``` { "container_id": "752947ff91f961eb3cb47ffe9315016979f3ffbec09e4d96a4fae3fb03391697", "container_name": "testctr", "port_mappings": [ { "container_port": 80, "host_ip": "127.0.0.1", "host_port": 8080, "protocol": "tcp", "range": 1 } ], "network": { "dns_enabled": false, "driver": "bridge", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "internal": false, "ipv6_enabled": false, "name": "podman", "network_interface": "podman0", "options": null, "ipam_options": { "driver": "host-local" }, "subnets": [ { "gateway": "10.88.0.1", "lease_range": null, "subnet": "10.88.0.0/16" } ], "network_dns_servers": null }, "network_options": { "aliases": [ "752947ff91f9" ], "interface_name": "eth0", "static_ips": [ "10.88.0.50" ], "static_mac": "aa:bb:cc:dd:aa:00" } } ``` Example JSON output: ``` { "dns_search_domains": [], "dns_server_ips": [], "interfaces": { "eth0": { "mac_address": "aa:bb:cc:dd:aa:00", "subnets": [ { "gateway": "10.88.0.1", "ipnet": "10.88.0.50/16" } ] } } } ``` ## Teardown subcommand The teardown command is basically the reverse of the setup command. It should revert what the plugin did in setup. It accepts the same input as setup but it should not return anything on success. ## Info subcommand Used to output information about this plugin. It must contain the version of your plugin and the API version. Extra fields can be added. The API version must be set to `1.0.0` at the moment, it is not used the moment but could be used in the future to allow for backwards compatibility in case the plugin types change. ``` { "version": "0.1.0", "api_version": "1.0.0" } ``` ## Error handling If the plugin encounters an error it should return a special json message with the following format: ``` {"error": "message"} ``` where message should be replace with your actual error message. This message will be returned by netavark and will be visible to podman users. ## Rust types Rust types can be found in [./src/network/types.rs](./src/network/types.rs), see the documentation [here](https://docs.rs/netavark/latest/netavark/network/types). Fields that are wrapped by an `Option` can be omitted from the json, otherwise they must be set to allow proper deserialization. ## Rust plugin interface There is a simple ready to use interface for writing your plugin in rust, see [./src/plugin.rs](./src/plugin.rs) ```rust use netavark::{ network::types, plugin::{Info, Plugin, PluginExec, API_VERSION}, }; fn main() { // change the version to the version of your plugin let info = Info::new("0.1.0".to_owned(), API_VERSION.to_owned(), None); PluginExec::new(Exec {}, info).exec(); } struct Exec {} impl Plugin for Exec { fn create( &self, network: types::Network, ) -> Result> { // your logic here } fn setup( &self, netns: String, opts: types::NetworkPluginExec, ) -> Result> { // your logic here } fn teardown( &self, netns: String, opts: types::NetworkPluginExec, ) -> Result<(), Box> { // your logic here } } ``` Also see the examples in [./examples](./examples/). containers-netavark-83edb4b/rpm/000077500000000000000000000000001452673426700170035ustar00rootroot00000000000000containers-netavark-83edb4b/rpm/netavark.spec000066400000000000000000000101731452673426700214740ustar00rootroot00000000000000# Building from fedora dependencies not possible # Latest upstream rtnetlink frequently required # sha2, zbus, zvariant are currently out of date # RHEL doesn't include the package rust-packaging which provides %%__cargo macro, but EPEL # does. So we set it separately here and skip rust-packaging dependency for RHEL. # Buildability without EPEL is essential for packit builds. # ELN doesn't need this. %if %{defined rhel} && 0%{?rhel} < 10 %define __cargo %{_bindir}/env CARGO_HOME=.cargo RUSTC_BOOTSTRAP=1 RUSTFLAGS='-Copt-level=3 -Cdebuginfo=2 -Ccodegen-units=1 -Clink-arg=-Wl,-z,relro -Clink-arg=-Wl,-z,now --cap-lints=warn' %{_bindir}/cargo %endif %global with_debug 1 %if 0%{?with_debug} %global _find_debuginfo_dwz_opts %{nil} %global _dwz_low_mem_die_limit 0 %else %global debug_package %{nil} %endif Name: netavark # Set a different Epoch for copr builds %if %{defined copr_username} Epoch: 102 %endif Version: 0 Release: %autorelease # The `AND` needs to be uppercase in the License for SPDX compatibility License: Apache-2.0 AND BSD-3-Clause AND MIT %if %{defined golang_arches_future} ExclusiveArch: %{golang_arches_future} %else ExclusiveArch: aarch64 ppc64le s390x x86_64 %endif Summary: OCI network stack URL: https://github.com/containers/%{name} # Tarballs fetched from upstream's release page Source0: %{url}/archive/v%{version}.tar.gz Source1: %{url}/releases/download/v%{version}/%{name}-v%{version}-vendor.tar.gz BuildRequires: cargo BuildRequires: %{_bindir}/go-md2man # aardvark-dns and %%{name} are usually released in sync Recommends: aardvark-dns >= %{version}-1 Requires: (aardvark-dns >= %{version}-1 if fedora-release-identity-server) Provides: container-network-stack = 2 BuildRequires: make BuildRequires: protobuf-c BuildRequires: protobuf-compiler %if %{defined rhel} # rust-toolset requires the `local` repo enabled on non-koji ELN build environments BuildRequires: rust-toolset %else BuildRequires: rust-packaging BuildRequires: rust-srpm-macros %endif BuildRequires: git-core BuildRequires: systemd BuildRequires: systemd-devel %description %{summary} Netavark is a rust based network stack for containers. It is being designed to work with Podman but is also applicable for other OCI container management applications. Netavark is a tool for configuring networking for Linux containers. Its features include: * Configuration of container networks via JSON configuration file * Creation and management of required network interfaces, including MACVLAN networks * All required firewall configuration to perform NAT and port forwarding as required for containers * Support for iptables and firewalld at present, with support for nftables planned in a future release * Support for rootless containers * Support for IPv4 and IPv6 * Support for container DNS resolution via aardvark-dns. %prep %autosetup -Sgit %{name}-%{version} # Following steps are only required on environments like koji which have no # network access and thus depend on the vendored tarball. Copr pulls # dependencies directly from the network. %if !%{defined copr_username} tar fx %{SOURCE1} mkdir -p .cargo cat >.cargo/config << EOF [source.crates-io] replace-with = "vendored-sources" [source.vendored-sources] directory = "vendor" EOF %endif %build %{__make} CARGO="%{__cargo}" build cd docs %{__make} %install %{__make} DESTDIR=%{buildroot} PREFIX=%{_prefix} install %preun %systemd_preun %{name}-dhcp-proxy.service %systemd_preun %{name}-firewalld-reload.service %postun %systemd_postun %{name}-dhcp-proxy.service %systemd_postun %{name}-firewalld-reload.service %files %license LICENSE %dir %{_libexecdir}/podman %{_libexecdir}/podman/%{name}* %{_mandir}/man1/%{name}.1* %{_unitdir}/%{name}-dhcp-proxy.service %{_unitdir}/%{name}-dhcp-proxy.socket %{_unitdir}/%{name}-firewalld-reload.service %changelog %if %{defined autochangelog} %autochangelog %else # NOTE: This changelog will be visible on CentOS 8 Stream builds # Other envs are capable of handling autochangelog * Fri Jun 16 2023 RH Container Bot - Placeholder changelog for envs that are not autochangelog-ready - Contact upstream if you need to report an issue with the build. %endif containers-netavark-83edb4b/src/000077500000000000000000000000001452673426700167745ustar00rootroot00000000000000containers-netavark-83edb4b/src/commands/000077500000000000000000000000001452673426700205755ustar00rootroot00000000000000containers-netavark-83edb4b/src/commands/dhcp_proxy.rs000066400000000000000000000361171452673426700233320ustar00rootroot00000000000000#![cfg_attr(not(unix), allow(unused_imports))] use crate::dhcp_proxy::cache::{Clear, LeaseCache}; use crate::dhcp_proxy::dhcp_service::{process_client_stream, DhcpV4Service}; use crate::dhcp_proxy::ip; use crate::dhcp_proxy::lib::g_rpc::netavark_proxy_server::{NetavarkProxy, NetavarkProxyServer}; use crate::dhcp_proxy::lib::g_rpc::{ Empty, Lease as NetavarkLease, NetworkConfig, OperationResponse, }; use crate::dhcp_proxy::proxy_conf::{ get_cache_fqname, get_proxy_sock_fqname, DEFAULT_INACTIVITY_TIMEOUT, DEFAULT_TIMEOUT, }; use crate::error::{NetavarkError, NetavarkResult}; use crate::network::core_utils; use clap::Parser; use log::{debug, error, warn}; use tokio::task::AbortHandle; use std::collections::HashMap; use std::fs::File; use std::io::Write; use std::os::unix::io::FromRawFd; use std::os::unix::net::UnixListener as stdUnixListener; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::{env, fs}; #[cfg(unix)] use tokio::net::UnixListener; #[cfg(unix)] use tokio::signal::unix::{signal, SignalKind}; use tokio::sync::mpsc::Sender; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::{mpsc, oneshot}; use tokio::time::{timeout, Duration}; #[cfg(unix)] use tokio_stream::wrappers::UnixListenerStream; use tonic::{ transport::Server, Code, Code::Internal, Code::InvalidArgument, Request, Response, Status, }; #[derive(Debug)] /// This is the tonic netavark proxy service that is required to impl the Netavark Proxy trait which /// includes the gRPC methods defined in proto/proxy.proto. We can store a atomically referenced counted /// mutex cache in the structure tuple. /// /// The cache needs to be **safely mutable across multiple threads**. We need to share the lease cache /// across multiple threads for 2 reasons /// 1. Each tonic request is spawned in its own new thread. /// 2. A new thread must be spawned in any request that uses mozim, such as get_lease. This is because /// tonic creates its own runtime for each request and mozim trys to make its own runtime inside of /// a runtime. /// struct NetavarkProxyService { // cache is the lease hashmap cache: Arc>>, // the timeout for the dora operation dora_timeout: u32, // channel send-side for resetting the inactivity timeout timeout_sender: Arc>>, // All dhcp poll will be spawned on a new task, keep track of it so // we can remove it on teardown. The key is the container mac. task_map: Arc>>, } impl NetavarkProxyService { fn reset_inactivity_timeout(&self) { let sender = self.timeout_sender.clone(); let locked_sender = match sender.lock() { Ok(v) => v, Err(e) => { log::error!("{}", e.to_string()); return; } }; match locked_sender.try_send(1) { Ok(..) => {} Err(e) => log::error!("{}", e.to_string()), } } } // gRPC request and response methods #[tonic::async_trait] impl NetavarkProxy for NetavarkProxyService { /// gRPC connection to get a lease async fn setup( &self, request: Request, ) -> Result, Status> { debug!("Request from client {:?}", request.remote_addr()); // notify server of activity self.reset_inactivity_timeout(); let cache = self.cache.clone(); let timeout = self.dora_timeout; let task_map = self.task_map.clone(); // setup client side streaming let network_config = request.into_inner(); // _tx will be dropped when the request is dropped, this will trigger rx, which means the // client disconnected let (_tx, mut rx) = oneshot::channel::<()>(); let lease = tokio::task::spawn(async move { // Check if the connection has been dropped before attempting to get a lease if rx.try_recv() == Err(TryRecvError::Closed) { log::debug!("Request dropped, aborting DORA"); return Err(Status::new(Code::Aborted, "client disconnected")); } let get_lease = process_setup(network_config, timeout, cache, task_map); // watch the client and the lease, which ever finishes first return let get_lease: NetavarkLease = tokio::select! { _ = &mut rx => { // we never send to tx, so this completing means that the other end, tx, was dropped! log::debug!("Request dropped, aborting DORA"); return Err(Status::new(Code::Aborted, "client disconnected")) } lease = get_lease => { Ok::(lease?) } }?; // check after lease was found that the client is still there if rx.try_recv() == Err(TryRecvError::Closed) { log::debug!("Request dropped, aborting DORA"); return Err(Status::new(Code::Aborted, "client disconnected")); } Ok(get_lease) }) .await; return match lease { Ok(Ok(lease)) => Ok(Response::new(lease)), Ok(Err(status)) => Err(status), Err(e) => Err(Status::new(Code::Unknown, e.to_string())), }; } /// When a container is shut down this method should be called. It will clear the lease information /// from the caching system. async fn teardown( &self, request: Request, ) -> Result, Status> { // notify server of activity self.reset_inactivity_timeout(); let nc = request.into_inner(); let cache = self.cache.clone(); let tasks = self.task_map.clone(); let task = tasks .lock() .expect("lock tasks") .remove(&nc.container_mac_addr); if let Some(handle) = task { handle.abort(); } // Remove the client from the cache dir let lease = cache .lock() .expect("Could not unlock cache. A thread was poisoned") .remove_lease(&nc.container_mac_addr) .map_err(|e| Status::internal(e.to_string()))?; Ok(Response::new(lease)) } /// On teardown of the proxy the cache will be cleared gracefully. async fn clean(&self, request: Request) -> Result, Status> { debug!("Request from client: {:?}", request.remote_addr()); self.cache .clone() .lock() .expect("Could not unlock cache. A thread was poisoned") .teardown()?; Ok(Response::new(OperationResponse { success: true })) } } #[derive(Parser, Debug)] #[clap(version = env ! ("CARGO_PKG_VERSION"))] pub struct Opts { /// location to store backup files #[clap(short, long)] dir: Option, /// alternative uds location #[clap(short, long)] uds: Option, /// optional time in seconds to time out after looking for a lease #[clap(short, long)] timeout: Option, /// activity timeout #[clap(short, long)] activity_timeout: Option, } /// Handle SIGINT signal. /// /// Will wait until process receives a SIGINT/ ctrl+c signal and then clean up and shut down async fn handle_signal(uds_path: PathBuf) { tokio::spawn(async move { // Handle signal hooks with expect, it is important these are setup so data is not corrupted let mut sigterm = signal(SignalKind::terminate()).expect("Could not set up SIGTERM hook"); let mut sigint = signal(SignalKind::interrupt()).expect("Could not set up SIGINT hook"); // Wait for either a SIGINT or a SIGTERM to clean up tokio::select! { _ = sigterm.recv() => { warn!("Received SIGTERM, cleaning up and exiting"); } _ = sigint.recv() => { warn!("Received SIGINT, cleaning up and exiting"); } } if let Err(e) = fs::remove_file(uds_path) { error!("Could not close uds socket: {}", e); } std::process::exit(0x0100); }); } #[tokio::main] pub async fn serve(opts: Opts) -> NetavarkResult<()> { let optional_run_dir = opts.dir.as_deref(); let dora_timeout = opts.timeout.unwrap_or(DEFAULT_TIMEOUT); let inactivity_timeout = Duration::from_secs(opts.activity_timeout.unwrap_or(DEFAULT_INACTIVITY_TIMEOUT)); let uds_path = get_proxy_sock_fqname(optional_run_dir); debug!("socket path: {}", &uds_path.display()); let mut is_systemd_activated = false; // check if the UDS is a systemd socket activated service. if it is, // then systemd hands this over to us on FD 3. let uds: UnixListener = match env::var("LISTEN_FDS") { Ok(effds) => { if effds != "1" { return Err(NetavarkError::msg("Received more than one FD from systemd")); } is_systemd_activated = true; let systemd_socket = unsafe { stdUnixListener::from_raw_fd(3) }; systemd_socket.set_nonblocking(true)?; UnixListener::from_std(systemd_socket)? } // Use the standard socket approach Err(..) => { // Create a new uds socket path match Path::new(&uds_path).parent() { None => { return Err(NetavarkError::msg("Could not get parent from uds path")); } Some(f) => tokio::fs::create_dir_all(f).await?, } // Watch for signals after the uds path has been created, so that the socket can be closed. handle_signal(uds_path.clone()).await; UnixListener::bind(&uds_path)? } }; let uds_stream = UnixListenerStream::new(uds); // Create the cache file let fq_cache_path = get_cache_fqname(optional_run_dir); let file = match File::create(&fq_cache_path) { Ok(file) => { debug!("Successfully created leases file: {:?}", fq_cache_path); file } Err(e) => { return Err(NetavarkError::msg(format!( "Exiting. Could not create lease cache file: {e}", ))); } }; let cache = match LeaseCache::new(file) { Ok(c) => Arc::new(Mutex::new(c)), Err(e) => { return Err(NetavarkError::msg(format!( "Could not setup the cache: {e}" ))); } }; // Create send and receive channels for activity timeout. If anything is // sent by the tx side, the inactivity timeout is reset let (activity_timeout_tx, activity_timeout_rx) = mpsc::channel(5); let netavark_proxy_service = NetavarkProxyService { cache: cache.clone(), dora_timeout, timeout_sender: Arc::new(Mutex::new(activity_timeout_tx.clone())), task_map: Arc::new(Mutex::new(HashMap::new())), }; let server = Server::builder() .add_service(NetavarkProxyServer::new(netavark_proxy_service)) .serve_with_incoming(uds_stream); tokio::pin!(server); tokio::select! { // a timeout duration of 0 means NEVER _ = handle_wakeup(activity_timeout_rx, inactivity_timeout, cache.clone()), if inactivity_timeout.as_secs() > 0 => {}, _ = &mut server => {}, }; // Make sure to only remove the socket path when we do not run socket activated, // otherwise we delete the socket systemd is using which causes all new connections to fail. if !is_systemd_activated { fs::remove_file(uds_path)?; } Ok(()) } /// manages the timeout lifecycle for the proxy server based on a defined timeout. /// /// # Arguments /// /// * `rx`: receive side of channel /// * `timeout_duration`: time duration in seconds /// /// returns: () /// /// # Examples /// /// ``` /// /// ``` async fn handle_wakeup( mut rx: mpsc::Receiver, timeout_duration: Duration, current_cache: Arc>>, ) { loop { match timeout(timeout_duration, rx.recv()).await { Ok(Some(_)) => { debug!("timeout timer reset") } Ok(None) => { println!("timeout channel closed"); break; } Err(_) => { // only 'exit' if the timeout is met AND there are no leases // if we do not exit, the activity_timeout is reset if is_catch_empty(current_cache.clone()) { println!( "timeout met: exiting after {} secs of inactivity", timeout_duration.as_secs() ); break; } } } } } /// get_cache_len returns the number of leases in the hashmap in memory /// /// # Arguments /// /// * `current_cache`: /// /// returns: usize /// /// # Examples /// /// ``` /// /// ``` fn is_catch_empty(current_cache: Arc>>) -> bool { match current_cache.lock() { Ok(v) => { debug!("cache_len is {}", v.len().to_string()); v.is_empty() } Err(e) => { log::error!("{}", e.to_string()); false } } } /// Process network config into a lease and setup the ip /// /// # Arguments /// /// * `network_config`: Network config /// * `timeout`: dora timeout /// * `cache`: lease cache /// /// returns: Result async fn process_setup( network_config: NetworkConfig, timeout: u32, cache: Arc>>, tasks: Arc>>, ) -> Result { let container_network_interface = network_config.container_iface.clone(); let ns_path = network_config.ns_path.clone(); // test if mac is valid core_utils::CoreUtils::decode_address_from_hex(&network_config.container_mac_addr) .map_err(|e| Status::new(InvalidArgument, format!("{e}")))?; let mac = &network_config.container_mac_addr.clone(); let nv_lease = match network_config.version { //V4 0 => { let mut service = DhcpV4Service::new(network_config, timeout)?; let lease = service.get_lease().await?; let task = tokio::spawn(process_client_stream(service)); tasks .lock() .expect("lock tasks") .insert(mac.to_string(), task.abort_handle()); lease } //V6 TODO implement DHCPv6 1 => { return Err(Status::new(InvalidArgument, "ipv6 not yet supported")); } _ => { return Err(Status::new(InvalidArgument, "invalid protocol version")); } }; if let Err(e) = cache .lock() .expect("Could not unlock cache. A thread was poisoned") .add_lease(mac, &nv_lease) { return Err(Status::new( Internal, format!("Error caching the lease: {e}"), )); } ip::setup(&nv_lease, &container_network_interface, &ns_path)?; Ok(nv_lease) } containers-netavark-83edb4b/src/commands/firewalld_reload.rs000066400000000000000000000043061452673426700244450ustar00rootroot00000000000000use std::{ ffi::{OsStr, OsString}, path::Path, }; use zbus::{blocking::Connection, dbus_proxy, CacheProperties}; use crate::{ error::{ErrorWrap, NetavarkResult}, firewall::{get_supported_firewall_driver, state::read_fw_config}, network::constants, }; #[dbus_proxy( interface = "org.fedoraproject.FirewallD1", default_service = "org.fedoraproject.FirewallD1", default_path = "/org/fedoraproject/FirewallD1" )] trait FirewallDDbus {} const SIGNAL_NAME: &str = "Reloaded"; pub fn listen(config_dir: Option) -> NetavarkResult<()> { let config_dir = Path::new( config_dir .as_deref() .unwrap_or(OsStr::new(constants::DEFAULT_CONFIG_DIR)), ); log::debug!("looking for firewall configs in {:?}", config_dir); let conn = Connection::system()?; let proxy = FirewallDDbusProxyBlocking::builder(&conn) .cache_properties(CacheProperties::No) .build()?; // Setup fw rules on start because we are started after firewalld // this means at the time firewalld stated the fw rules were flushed // and we need to add them back. // It is important to keep things like "systemctl restart firewalld" working. reload_rules(config_dir); // This loops forever until the process is killed or there is some dbus error. for _ in proxy.receive_signal(SIGNAL_NAME)? { log::debug!("got firewalld {} signal", SIGNAL_NAME); reload_rules(config_dir); } Ok(()) } fn reload_rules(config_dir: &Path) { if let Err(e) = reload_rules_inner(config_dir) { log::error!("failed to reload firewall rules: {e}"); } } fn reload_rules_inner(config_dir: &Path) -> NetavarkResult<()> { let conf = read_fw_config(config_dir).wrap("read firewall config")?; // If we got no conf there are no containers so nothing to do. if let Some(conf) = conf { let fw_driver = get_supported_firewall_driver(Some(conf.driver))?; for net in conf.net_confs { fw_driver.setup_network(net)?; } for port in &conf.port_confs { fw_driver.setup_port_forward(port.into())?; } log::info!("Successfully reloaded firewall rules"); } Ok(()) } containers-netavark-83edb4b/src/commands/mod.rs000066400000000000000000000006631452673426700217270ustar00rootroot00000000000000use std::ffi::OsString; use crate::error::{NetavarkError, NetavarkResult}; pub mod dhcp_proxy; pub mod firewalld_reload; pub mod setup; pub mod teardown; pub mod update; pub mod version; fn get_config_dir(dir: Option, cmd: &str) -> NetavarkResult { dir.ok_or_else(|| { NetavarkError::msg(format!( "--config not specified but required for netavark {}", cmd )) }) } containers-netavark-83edb4b/src/commands/setup.rs000066400000000000000000000146121452673426700223070ustar00rootroot00000000000000//! Configures the given network namespace with provided specs use crate::commands::get_config_dir; use crate::dns::aardvark::Aardvark; use crate::error::{NetavarkError, NetavarkResult}; use crate::firewall; use crate::network::driver::{get_network_driver, DriverInfo}; use crate::network::netlink::LinkID; use crate::network::{self}; use crate::network::{core_utils, types}; use clap::builder::NonEmptyStringValueParser; use clap::Parser; use log::{debug, error, info}; use std::collections::HashMap; use std::ffi::OsString; use std::fs::{self}; use std::os::fd::AsFd; use std::path::Path; #[derive(Parser, Debug)] pub struct Setup { /// Network namespace path #[clap(required = true, value_parser = NonEmptyStringValueParser::new())] network_namespace_path: String, } impl Setup { /// The setup command configures the given network namespace with the given configuration, creating any interfaces and firewall rules necessary. pub fn new(network_namespace_path: String) -> Self { Self { network_namespace_path, } } pub fn exec( &self, input_file: Option, config_dir: Option, aardvark_bin: OsString, plugin_directories: Option>, rootless: bool, ) -> NetavarkResult<()> { match network::validation::ns_checks(&self.network_namespace_path) { Ok(_) => (), Err(e) => { return Err(NetavarkError::wrap("invalid namespace path", e)); } } debug!("{:?}", "Setting up..."); let network_options = network::types::NetworkOptions::load(input_file)?; let firewall_driver = match firewall::get_supported_firewall_driver(None) { Ok(driver) => driver, Err(e) => return Err(e), }; let mut response: HashMap = HashMap::new(); let dns_port = core_utils::get_netavark_dns_port()?; let (mut hostns, mut netns) = core_utils::open_netlink_sockets(&self.network_namespace_path)?; // setup loopback, it should be safe to assume that 1 is the loopback index netns.netlink.set_up(LinkID::ID(1))?; let config_dir = get_config_dir(config_dir, "setup")?; let mut drivers = Vec::with_capacity(network_options.network_info.len()); // Perform per-network setup for (net_name, network) in network_options.network_info.iter() { let per_network_opts = network_options.networks.get(net_name).ok_or_else(|| { NetavarkError::Message(format!("network options for network {net_name} not found")) })?; let mut driver = get_network_driver( DriverInfo { firewall: firewall_driver.as_ref(), container_id: &network_options.container_id, container_name: &network_options.container_name, container_dns_servers: &network_options.dns_servers, netns_host: hostns.file.as_fd(), netns_container: netns.file.as_fd(), netns_path: &self.network_namespace_path, network, per_network_opts, port_mappings: &network_options.port_mappings, dns_port, config_dir: Path::new(&config_dir), rootless, }, &plugin_directories, )?; // validate before we do anything driver.validate()?; drivers.push(driver); } let mut aardvark_entries = Vec::new(); // Only now after we validated all drivers we setup each. // If there is an error we have to tear down all previous drivers. for (i, driver) in drivers.iter().enumerate() { let (status, aardvark_entry) = match driver.setup((&mut hostns.netlink, &mut netns.netlink)) { Ok((s, a)) => (s, a), Err(e) => { // now teardown the already setup drivers for dri in drivers.iter().take(i) { match dri.teardown((&mut hostns.netlink, &mut netns.netlink)) { Ok(_) => {} Err(e) => { error!( "failed to cleanup previous networks after setup failed: {}", e ) } }; } return Err(e); } }; let _ = response.insert(driver.network_name(), status); if let Some(a) = aardvark_entry { aardvark_entries.push(a); } } if !aardvark_entries.is_empty() { if Path::new(&aardvark_bin).exists() { let path = Path::new(&config_dir).join("aardvark-dns"); match fs::create_dir(path.as_path()) { Ok(_) => {} // ignore error when path already exists Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => {} Err(e) => { return Err(std::io::Error::new( std::io::ErrorKind::Other, format!("failed to create aardvark-dns directory: {e}"), ) .into()); } } let aardvark_interface = Aardvark::new(path, rootless, aardvark_bin, dns_port); if let Err(er) = aardvark_interface.commit_netavark_entries(aardvark_entries) { return Err(std::io::Error::new( std::io::ErrorKind::Other, format!("Error while applying dns entries: {er}"), ) .into()); } } else { info!( "dns disabled because aardvark-dns path {:?} does not exists", &aardvark_bin ); } } debug!("{:#?}", response); let response_json = serde_json::to_string(&response)?; println!("{response_json}"); debug!("{:?}", "Setup complete"); Ok(()) } } containers-netavark-83edb4b/src/commands/teardown.rs000066400000000000000000000114411452673426700227670ustar00rootroot00000000000000use crate::commands::get_config_dir; use crate::dns::aardvark::{Aardvark, AardvarkEntry}; use crate::error::{NetavarkError, NetavarkErrorList, NetavarkResult}; use crate::network::constants::DRIVER_BRIDGE; use crate::network::core_utils; use crate::network::driver::{get_network_driver, DriverInfo}; use crate::{firewall, network}; use clap::builder::NonEmptyStringValueParser; use clap::Parser; use log::debug; use std::ffi::OsString; use std::os::fd::AsFd; use std::path::Path; #[derive(Parser, Debug)] pub struct Teardown { /// Network namespace path #[clap(required = true, value_parser = NonEmptyStringValueParser::new())] network_namespace_path: String, } impl Teardown { /// The teardown command is the inverse of the setup command, undoing any configuration applied. Some interfaces may not be deleted (bridge interfaces, for example, will not be removed). pub fn new(network_namespace_path: String) -> Self { Self { network_namespace_path, } } pub fn exec( &self, input_file: Option, config_dir: Option, aardvark_bin: OsString, plugin_directories: Option>, rootless: bool, ) -> NetavarkResult<()> { debug!("{:?}", "Tearing down.."); let network_options = network::types::NetworkOptions::load(input_file)?; let mut error_list = NetavarkErrorList::new(); let dns_port = core_utils::get_netavark_dns_port()?; let config_dir = get_config_dir(config_dir, "teardown")?; let mut aardvark_entries = Vec::new(); for (key, network) in &network_options.network_info { if network.dns_enabled && network.driver == DRIVER_BRIDGE { aardvark_entries.push(AardvarkEntry { network_name: key, network_gateways: Vec::new(), network_dns_servers: &None, container_id: &network_options.container_id, container_ips_v4: Vec::new(), container_ips_v6: Vec::new(), container_names: Vec::new(), container_dns_servers: &None, }); } } if !aardvark_entries.is_empty() { // stop dns server first before netavark clears the interface let path = Path::new(&config_dir).join("aardvark-dns"); let aardvark_interface = Aardvark::new(path, rootless, aardvark_bin, dns_port); if let Err(err) = aardvark_interface.delete_from_netavark_entries(aardvark_entries) { error_list.push(NetavarkError::wrap("remove aardvark entries", err)); } } let firewall_driver = match firewall::get_supported_firewall_driver(None) { Ok(driver) => driver, Err(e) => return Err(e), }; let (mut hostns, mut netns) = core_utils::open_netlink_sockets(&self.network_namespace_path)?; for (net_name, network) in network_options.network_info.iter() { let per_network_opts = match network_options.networks.get(net_name) { Some(opts) => opts, None => { error_list.push(NetavarkError::Message(format!( "network options for network {net_name} not found" ))); continue; } }; let driver = match get_network_driver( DriverInfo { firewall: firewall_driver.as_ref(), container_id: &network_options.container_id, container_name: &network_options.container_name, container_dns_servers: &network_options.dns_servers, netns_host: hostns.file.as_fd(), netns_container: netns.file.as_fd(), netns_path: &self.network_namespace_path, network, per_network_opts, port_mappings: &network_options.port_mappings, dns_port, config_dir: Path::new(&config_dir), rootless, }, &plugin_directories, ) { Ok(driver) => driver, Err(err) => { error_list.push(err); continue; } }; match driver.teardown((&mut hostns.netlink, &mut netns.netlink)) { Ok(_) => {} Err(err) => { error_list.push(err); continue; } }; } if !error_list.is_empty() { return Err(NetavarkError::List(error_list)); } debug!("{:?}", "Teardown complete"); Ok(()) } } containers-netavark-83edb4b/src/commands/update.rs000066400000000000000000000037031452673426700224300ustar00rootroot00000000000000use crate::commands::get_config_dir; use crate::dns::aardvark::Aardvark; use crate::error::{NetavarkError, NetavarkResult}; use crate::network::core_utils; use clap::builder::NonEmptyStringValueParser; use clap::Parser; use log::debug; use std::ffi::OsString; use std::path::Path; #[derive(Parser, Debug)] pub struct Update { /// Network name to update #[clap(required = true, value_parser = NonEmptyStringValueParser::new())] network_name: String, /// DNS Servers to update for the network #[clap(long, required = true)] network_dns_servers: Vec, } impl Update { /// Updates network dns servers for an already configured network pub fn new(network_name: String, network_dns_servers: Vec) -> Self { Self { network_name, network_dns_servers, } } pub fn exec( &mut self, config_dir: Option, aardvark_bin: OsString, rootless: bool, ) -> NetavarkResult<()> { let dns_port = core_utils::get_netavark_dns_port()?; let config_dir = get_config_dir(config_dir, "update")?; if Path::new(&aardvark_bin).exists() { let path = Path::new(&config_dir).join("aardvark-dns"); let aardvark_interface = Aardvark::new(path, rootless, aardvark_bin, dns_port); // if empty network_dns_servers are passed, pass empty array instead of `[""]` if self.network_dns_servers.len() == 1 && self.network_dns_servers[0].is_empty() { self.network_dns_servers = Vec::new(); } if let Err(err) = aardvark_interface .modify_network_dns_servers(&self.network_name, &self.network_dns_servers) { return Err(NetavarkError::wrap( "unable to modify network dns servers", err, )); } } debug!("Network update complete"); Ok(()) } } containers-netavark-83edb4b/src/commands/version.rs000066400000000000000000000012271452673426700226320ustar00rootroot00000000000000use crate::error::NetavarkResult; use clap::Parser; use serde::Serialize; #[derive(Parser, Debug)] pub struct Version {} #[derive(Debug, Serialize)] struct Info { version: &'static str, commit: &'static str, build_time: &'static str, target: &'static str, } impl Version { pub fn exec(&self) -> NetavarkResult<()> { let info = Info { version: env!("CARGO_PKG_VERSION"), commit: env!("GIT_COMMIT"), build_time: env!("BUILD_TIMESTAMP"), target: env!("BUILD_TARGET"), }; let out = serde_json::to_string_pretty(&info)?; println!("{out}"); Ok(()) } } containers-netavark-83edb4b/src/dhcp_proxy/000077500000000000000000000000001452673426700211535ustar00rootroot00000000000000containers-netavark-83edb4b/src/dhcp_proxy/cache.rs000066400000000000000000000377561452673426700226060ustar00rootroot00000000000000use crate::dhcp_proxy::lib::g_rpc::{Lease as NetavarkLease, Lease}; use log::{debug, error}; use std::collections::HashMap; use std::fs::File; use std::io; use std::io::{Cursor, Write}; #[derive(Debug)] #[allow(dead_code)] pub struct ClearError { msg: String, } /// The Writer on the cache must be clearable so that any new changes can be overwritten pub trait Clear { fn clear(&mut self) -> Result<(), ClearError>; } impl Clear for Cursor> { fn clear(&mut self) -> Result<(), ClearError> { self.set_position(0); self.get_mut().clear(); Ok(()) } } impl Clear for File { fn clear(&mut self) -> Result<(), ClearError> { match self.set_len(0) { Ok(_) => Ok(()), Err(e) => Err(ClearError { msg: e.to_string() }), } } } /// The leasing cache holds a in memory record of the leases, and a on file version #[derive(Debug)] pub struct LeaseCache { mem: HashMap>, writer: W, } impl LeaseCache { /// /// /// # Arguments /// /// * `writer`: any type that can has the Write and Clear trait implemented. In production this /// is a file. In development/testing this is a Cursor of bytes /// /// returns: Result, Error> /// pub fn new(writer: W) -> Result, io::Error> { Ok(LeaseCache { mem: HashMap::new(), writer, }) } /// Add a new lease to a memory and file system cache /// /// # Arguments /// /// * `mac_addr`: Mac address of the container /// * `lease`: New lease that should be saved in the cache /// /// returns: Result<(), Error> /// pub fn add_lease(&mut self, mac_addr: &str, lease: &NetavarkLease) -> Result<(), io::Error> { debug!("add lease: {:?}", mac_addr); // Update cache memory with new lease let cache = &mut self.mem; cache.insert(mac_addr.to_string(), vec![lease.clone()]); // write updated memory cache to the file system self.save_memory_to_fs() } /// When a lease changes, update the lease in memory and on the writer. /// /// # Arguments /// /// * `mac_addr`: Mac address of the container /// * `lease`: Newest lease information /// /// returns: Result<(), Error> /// pub fn update_lease(&mut self, mac_addr: &str, lease: NetavarkLease) -> Result<(), io::Error> { let cache = &mut self.mem; // write to the memory cache cache.insert(mac_addr.to_string(), vec![lease]); // write updated memory cache to the file system self.save_memory_to_fs() } /// When a singular container is taken down. Remove that lease from the cache memory and fs /// /// # Arguments /// /// * `mac_addr`: Mac address of the container pub fn remove_lease(&mut self, mac_addr: &str) -> Result { debug!("remove lease: {:?}", mac_addr); let mem = &mut self.mem; // Check and see if the lease exists, if not create an empty one let lease = match mem.get(mac_addr) { None => Lease { t1: 0, t2: 0, lease_time: 0, mtu: 0, domain_name: "".to_string(), mac_address: "".to_string(), is_v6: false, siaddr: "".to_string(), yiaddr: "".to_string(), srv_id: "".to_string(), subnet_mask: "".to_string(), broadcast_addr: "".to_string(), dns_servers: vec![], gateways: vec![], ntp_servers: vec![], host_name: "".to_string(), }, Some(l) => l[0].clone(), }; // Try and remove the lease. If it doesnt exist, exit with the blank lease if mem.remove(mac_addr).is_none() { return Ok(lease); } // write updated memory cache to the file system match self.save_memory_to_fs() { Ok(_) => Ok(lease), Err(e) => Err(e), } } /// Clean up the memory and file system on tear down of the proxy server pub fn teardown(&mut self) -> Result<(), io::Error> { self.mem.clear(); self.save_memory_to_fs() } /// Save the memory contents to the file system. This will remove the contents in the file, /// then write the memory map to the file. This method will be called any the lease memory cache /// changes (new lease, remove lease, update lease) fn save_memory_to_fs(&mut self) -> io::Result<()> { let mem = &self.mem; let writer = &mut self.writer; // Clear the writer so we can add the old leases match writer.clear() { Ok(_) => { serde_json::to_writer(writer.by_ref(), &mem)?; writer.flush() } Err(e) => { error!( "Could not clear the writer. Not updating lease information: {:?}", e ); Ok(()) } } } // rust validators require both len and is_empty if you define one // of them pub fn len(&self) -> usize { self.mem.len() } pub fn is_empty(&self) -> bool { if self.len() < 1 { return true; } false } } #[cfg(test)] mod cache_tests { use super::super::cache::LeaseCache; use super::super::lib::g_rpc::{Lease as NetavarkLease, Lease}; use crate::network::core_utils; use rand::{thread_rng, Rng}; use std::collections::HashMap; use std::io::Cursor; // Create a single random ipv4 addr fn random_ipv4() -> String { let mut rng = thread_rng(); format!( "{:?}.{:?}.{:?}.{:?}.", rng.gen_range(0..255), rng.gen_range(0..255), rng.gen_range(0..255), rng.gen_range(0..255) ) } // Create a single random mac address fn random_macaddr() -> String { let mut rng = thread_rng(); let bytes = vec![ rng.gen::(), rng.gen::(), rng.gen::(), rng.gen::(), rng.gen::(), rng.gen::(), ]; core_utils::CoreUtils::encode_address_to_hex(&bytes) } // Create a single random lease fn random_lease(mac_address: &String) -> Lease { Lease { t1: 0, t2: 3600, lease_time: 0, mtu: 0, domain_name: "example.domain".to_string(), mac_address: String::from(mac_address), siaddr: random_ipv4(), yiaddr: random_ipv4(), srv_id: random_ipv4(), subnet_mask: "".to_string(), broadcast_addr: "".to_string(), dns_servers: vec![], gateways: vec![], ntp_servers: vec![], host_name: "example.host_name".to_string(), is_v6: false, } } // Shared information for all tests struct CacheTestSetup { cache: LeaseCache>>, macaddrs: Vec, range: u8, } impl CacheTestSetup { fn new() -> Self { // Use byte Cursor instead of file for testing let buff = Cursor::new(Vec::new()); let cache = match LeaseCache::new(buff) { Ok(cache) => cache, Err(e) => panic!("Could not create leases cache: {e:?}"), }; // Create a random amount of randomized leases let macaddrs = Vec::new(); let mut rng = thread_rng(); // Make a random amount of leases let range: u8 = rng.gen_range(0..10); CacheTestSetup { cache, macaddrs, range, } } } #[test] fn add_leases() { let setup = CacheTestSetup::new(); let mut cache = setup.cache; let mut macaddrs = setup.macaddrs; let range = setup.range; for i in 0..range { // Create a random mac address to create a random lease of that mac address let mac_address = random_macaddr(); macaddrs.push(mac_address.clone()); let lease = random_lease(&mac_address); // Add the lease to the cache cache .add_lease(&mac_address, &lease) .expect("could not add lease to cache"); // Deserialize the written bytes to compare let lease_bytes = cache.writer.get_ref().as_slice(); let s: HashMap> = match serde_json::from_slice(lease_bytes) { Ok(s) => s, Err(e) => panic!("Error: {e:?}"), }; // Get the mac address of the lease let macaddr = macaddrs .get(i as usize) .expect("Could not get the mac address of the lease added"); // Find the lease in the set of deserialized leases let deserialized_lease = s .get(macaddr) .expect("Could not get the mac address from the map") .get(0) .expect("Could not get lease from set of mac addresses") .clone(); // Assure that the amount of leases added is correct amount assert_eq!(s.len(), (i + 1) as usize); // Assure that the lease added was correct assert_eq!(lease, deserialized_lease); } } #[test] fn remove_leases() { let setup = CacheTestSetup::new(); let mut cache = setup.cache; let mut macaddrs = setup.macaddrs; let range = setup.range; for i in 0..range { // Create a random mac address to create a random lease of that mac address let mac_address = random_macaddr(); macaddrs.push(mac_address.clone()); let lease = random_lease(&mac_address); // Add the lease to the cache cache .add_lease(&mac_address, &lease) .expect("could not add lease to cache"); // Deserialize the written bytes to compare let lease_bytes = cache.writer.get_ref().as_slice(); let s: HashMap> = match serde_json::from_slice(lease_bytes) { Ok(s) => s, Err(e) => panic!("Error: {e:?}"), }; // Get the mac address of the lease let macaddr = macaddrs .get(i as usize) .expect("Could not get the mac address of the lease added"); // Find the lease in the set of deserialized leases let deserialized_lease = s .get(macaddr) .expect("Could not get the mac address from the map") .get(0) .expect("Could not get lease from set of mac addresses") .clone(); // Assure that the amount of leases added is correct amount assert_eq!(s.len(), (i + 1) as usize); // Assure that the lease added was correct assert_eq!(lease, deserialized_lease); } for i in 0..range { // Deserialize the written bytes to compare let lease_bytes = cache.writer.get_ref().as_slice(); let s: HashMap> = match serde_json::from_slice(lease_bytes) { Ok(s) => s, Err(e) => panic!("Error: {e:?}"), }; let macaddr = macaddrs .get(i as usize) .expect("Could not get the mac address of the lease added"); let deserialized_lease = s .get(macaddr) .expect("Could not get the mac address from the map") .get(0) .expect("Could not get lease from set of mac addresses") .clone(); let removed_lease = cache .remove_lease(macaddr) .unwrap_or_else(|_| panic!("Could not remove {macaddr:?} from leases")); // Assure the lease is no longer in memory assert_eq!(deserialized_lease, removed_lease); assert_eq!(s.len(), (range - i) as usize); // Deserialize the cache again to assure the lease is not in the writer let lease_bytes = cache.writer.get_ref().as_slice(); let s: HashMap> = match serde_json::from_slice(lease_bytes) { Ok(s) => s, Err(e) => panic!("Error: {e:?}"), }; // There should be no lease under that mac address if the lease was removed let no_lease = s.get(macaddr); assert_eq!(no_lease, None); // Remove a lease that does not exist let removed_lease = cache .remove_lease(macaddr) .expect("Could not remove the lease successfully"); // The returned lease should be a blank one assert_eq!(removed_lease.mac_address, "".to_string()); } } #[test] fn update_leases() { let setup = CacheTestSetup::new(); let mut cache = setup.cache; let mut macaddrs = setup.macaddrs; let range = setup.range; for i in 0..range { // Create a random mac address to create a random lease of that mac address let mac_address = random_macaddr(); macaddrs.push(mac_address.clone()); let lease = random_lease(&mac_address); // Add the lease to the cache cache .add_lease(&mac_address, &lease) .expect("could not add lease to cache"); // Deserialize the written bytes to compare let lease_bytes = cache.writer.get_ref().as_slice(); let s: HashMap> = match serde_json::from_slice(lease_bytes) { Ok(s) => s, Err(e) => panic!("Error: {e:?}"), }; // Get the mac address of the lease let macaddr = macaddrs .get(i as usize) .expect("Could not get the mac address of the lease added"); // Find the lease in the set of deserialized leases let deserialized_lease = s .get(macaddr) .expect("Could not get the mac address from the map") .get(0) .expect("Could not get lease from set of mac addresses") .clone(); // Assure that the amount of leases added is correct amount assert_eq!(s.len(), (i + 1) as usize); // Assure that the lease added was correct assert_eq!(lease, deserialized_lease); } // Update all of the leases for i in 0..range { // Deserialize the written bytes to compare let macaddr = macaddrs .get(i as usize) .expect("Could not get the mac address of the lease added"); // Create a new random lease with the same mac address let new_lease = random_lease(macaddr); cache .update_lease(macaddr, new_lease.clone()) .expect("Could not update the lease"); // Deserialize the cache again to assure the lease is not in the writer let lease_bytes = cache.writer.get_ref().as_slice(); let s: HashMap> = match serde_json::from_slice(lease_bytes) { Ok(s) => s, Err(e) => panic!("Error: {e:?}"), }; // There should be no lease under that mac address if the lease was removed let deserialized_updated_lease = s .get(macaddr) .expect("Could not get lease from deserialized map") .get(0) .expect("Could not find lease in set of multi-homing leases"); assert_eq!(deserialized_updated_lease, &new_lease); } } } containers-netavark-83edb4b/src/dhcp_proxy/dhcp_service.rs000066400000000000000000000167171452673426700241730ustar00rootroot00000000000000use std::net::Ipv4Addr; use crate::dhcp_proxy::dhcp_service::DhcpServiceErrorKind::{ Bug, InvalidArgument, NoLease, Timeout, }; use crate::dhcp_proxy::lib::g_rpc::{Lease as NetavarkLease, NetworkConfig}; use crate::error::{ErrorWrap, NetavarkError, NetavarkResult}; use crate::network::core_utils; use crate::network::netlink::Route; use crate::wrap; use log::debug; use mozim::{DhcpV4ClientAsync, DhcpV4Config, DhcpV4Lease as MozimV4Lease}; use tokio_stream::StreamExt; use tonic::{Code, Status}; /// The kind of DhcpServiceError that can be caused when finding a dhcp lease pub enum DhcpServiceErrorKind { Timeout, InvalidArgument, InvalidDhcpServerReply, NoLease, Bug, LeaseExpired, Unimplemented, } /// A DhcpServiceError is an error caused in the process of finding a dhcp lease pub struct DhcpServiceError { kind: DhcpServiceErrorKind, msg: String, } impl DhcpServiceError { pub fn new(kind: DhcpServiceErrorKind, msg: String) -> Self { DhcpServiceError { kind, msg } } } /// DHCP service is responsible for creating, handling, and managing the dhcp lease process. pub struct DhcpV4Service { client: DhcpV4ClientAsync, network_config: NetworkConfig, previous_lease: Option, } impl DhcpV4Service { pub fn new(nc: NetworkConfig, timeout: u32) -> Result { let mut config = DhcpV4Config::new_proxy(&nc.host_iface, &nc.container_mac_addr); config.set_timeout(timeout); let client = match DhcpV4ClientAsync::init(config, None) { Ok(client) => Ok(client), Err(err) => Err(DhcpServiceError::new(InvalidArgument, err.to_string())), }?; Ok(Self { client, network_config: nc, previous_lease: None, }) } /// Performs a DHCP DORA on a ipv4 network configuration. /// # Arguments /// /// * `client`: a IPv4 mozim dhcp client. When this method is called, it takes ownership of client. /// /// returns: Result. Either finds a lease successfully, finds no lease, or fails /// pub async fn get_lease(&mut self) -> Result { if let Some(Ok(lease)) = self.client.next().await { let mut netavark_lease = >::from(lease.clone()); netavark_lease.add_domain_name(&self.network_config.domain_name); netavark_lease.add_mac_address(&self.network_config.container_mac_addr); debug!( "found a lease for {:?}, {:?}", &self.network_config.container_mac_addr, &netavark_lease ); self.previous_lease = Some(lease); return Ok(netavark_lease); } Err(DhcpServiceError::new( Timeout, "Could not find a lease within the timeout limit".to_string(), )) } } impl std::fmt::Display for DhcpServiceError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.msg) } } impl From for Status { fn from(err: DhcpServiceError) -> Self { match err.kind { Timeout => Status::new(Code::Aborted, err.msg), InvalidArgument => Status::new(Code::InvalidArgument, err.msg), NoLease => Status::new(Code::NotFound, err.msg), Bug => Status::new(Code::Internal, err.msg), _ => Status::new(Code::Internal, err.msg), } } } pub async fn process_client_stream(mut client: DhcpV4Service) { while let Some(lease) = client.client.next().await { match lease { Ok(lease) => { log::info!( "got new lease for mac {}: {:?}", &client.network_config.container_mac_addr, &lease ); // get previous lease and check if ip addr changed, if not we do not have to do anything if let Some(old_lease) = &client.previous_lease { if old_lease.yiaddr != lease.yiaddr || old_lease.subnet_mask != lease.subnet_mask || old_lease.gateways != lease.gateways { // ips do not match, remove old ones and assign new ones. log::info!( "ip or gateway for mac {} changed, update address", &client.network_config.container_mac_addr ); match update_lease_ip( &client.network_config.ns_path, &client.network_config.container_iface, old_lease, &lease, ) { Ok(_) => {} Err(err) => { log::error!("{err}"); continue; } } } } client.previous_lease = Some(lease) } Err(err) => log::error!( "Failed to renew lease for {}: {err}", &client.network_config.container_mac_addr ), } } } fn update_lease_ip( netns: &str, interface: &str, old_lease: &MozimV4Lease, new_lease: &MozimV4Lease, ) -> NetavarkResult<()> { let (_, netns) = core_utils::open_netlink_sockets(netns).wrap("failed to open netlink socket in netns")?; let mut sock = netns.netlink; let old_net = wrap!( ipnet::Ipv4Net::with_netmask(old_lease.yiaddr, old_lease.subnet_mask), "create ipnet from old lease" )?; let new_net = wrap!( ipnet::Ipv4Net::with_netmask(new_lease.yiaddr, new_lease.subnet_mask), "create ipnet from new lease" )?; if new_net != old_net { let link = sock .get_link(crate::network::netlink::LinkID::Name(interface.to_string())) .wrap("get interface in netns")?; sock.add_addr(link.header.index, &ipnet::IpNet::V4(new_net)) .wrap("add new addr")?; sock.del_addr(link.header.index, &ipnet::IpNet::V4(old_net)) .wrap("remove old addrs")?; } if new_lease.gateways != old_lease.gateways { if let Some(gws) = &old_lease.gateways { let old_gw = gws.first(); if let Some(gw) = old_gw { let route = Route::Ipv4 { dest: ipnet::Ipv4Net::new(Ipv4Addr::new(0, 0, 0, 0), 0)?, gw: *gw, metric: None, }; match sock.del_route(&route) { Ok(_) => {} Err(err) => match err.unwrap() { // special case do not error if route does not exists NetavarkError::Netlink(e) if -e.raw_code() == libc::ESRCH => {} _ => return Err(err).wrap("delete old default route"), }, }; } } if let Some(gws) = &new_lease.gateways { let new_gw = gws.first(); if let Some(gw) = new_gw { let route = Route::Ipv4 { dest: ipnet::Ipv4Net::new(Ipv4Addr::new(0, 0, 0, 0), 0)?, gw: *gw, metric: None, }; sock.add_route(&route)?; } } } Ok(()) } containers-netavark-83edb4b/src/dhcp_proxy/ip.rs000066400000000000000000000155421452673426700221400ustar00rootroot00000000000000/* This file is intended to support netavark-dhcp-proxy configuring the IP information that it got from the dhcp server. Long term this file/function should move into netavark */ pub use crate::dhcp_proxy::lib::g_rpc::{Lease as NetavarkLease, Lease}; pub use crate::dhcp_proxy::types::{CustomErr, ProxyError}; use crate::network::core_utils; use crate::network::netlink; use crate::network::netlink::Socket; use ipnet::IpNet; use log::debug; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::str::FromStr; trait IpConv { fn to_v4(&self) -> Result<&Ipv4Addr, ProxyError>; fn to_v6(&self) -> Result<&Ipv6Addr, ProxyError>; } // Simple implementation for converting from IPAddr to // specific IP type impl IpConv for IpAddr { fn to_v4(&self) -> Result<&Ipv4Addr, ProxyError> { match self { IpAddr::V4(ip) => Ok(ip), IpAddr::V6(_) => Err(ProxyError::new( "invalid value for ipv4 conversion".to_string(), )), } } fn to_v6(&self) -> Result<&Ipv6Addr, ProxyError> { match self { IpAddr::V4(_) => Err(ProxyError::new( "invalid value for ipv6 conversion".to_string(), )), IpAddr::V6(ip) => Ok(ip), } } } /* Information that came back in the DHCP lease like name_servers, domain and host names, etc. will be implemented in podman; not here. */ #[derive(Clone, Debug)] struct MacVLAN { address: IpAddr, gateways: Vec, interface: String, // Unset right now // mtu: u32, prefix_length: u8, } trait Address { fn new(l: &Lease, interface: &str) -> Result where Self: Sized; fn add_ip(&self, nls: &mut Socket) -> Result<(), ProxyError>; fn add_gws(&self, nls: &mut Socket) -> Result<(), ProxyError>; fn remove(self) -> Result<(), ProxyError>; } fn handle_gws(g: Vec, netmask: &str) -> Result, ProxyError> { // TODO Need unit test let mut gws = Vec::new(); for route in g { // TODO FIX for ipv6 let sub_mask = match Ipv4Addr::from_str(netmask) { Ok(n) => n, Err(e) => return Err(ProxyError::new(e.to_string())), }; let prefix = u32::from(sub_mask).count_ones(); let ip = match Ipv4Addr::from_str(&route) { Ok(i) => i, Err(e) => return Err(ProxyError::new(e.to_string())), }; let gw = match IpNet::new(IpAddr::from(ip), prefix as u8) { Ok(r) => r, Err(e) => return Err(ProxyError::new(format!("{e}:'{route}'"))), }; gws.push(gw); } Ok(gws) } #[test] fn test_bad_gw_handle_gws() { let gws = vec!["192.168.1.1".to_string(), "10.10.10".into()]; let netmask = "255.255.255.0"; assert!(handle_gws(gws, netmask).is_err()) } #[test] fn test_bad_subnet_handle_gws() { let gws = vec!["192.168.1.1".to_string(), "10.10.10.1".into()]; let netmask = "255.255.255"; assert!(handle_gws(gws, netmask).is_err()) } #[test] fn test_handle_gws() { let gws = vec!["192.168.1.1".to_string(), "10.10.10.1".into()]; let netmask = "255.255.255.0"; assert!(handle_gws(gws, netmask).is_ok()) } // IPV4 implementation impl Address for MacVLAN { fn new(l: &NetavarkLease, interface: &str) -> Result { debug!("new ipv4 macvlan for {}", interface); let address = match IpAddr::from_str(&l.yiaddr) { Ok(a) => a, Err(e) => { return Err(ProxyError::new(format!("bad address: {e}"))); } }; let gateways = match handle_gws(l.gateways.clone(), &l.subnet_mask) { Ok(g) => g, Err(e) => { return Err(ProxyError::new(format!("bad gateways: {}", e.to_string()))); } }; let prefix_length = match get_prefix_length_v4(&l.subnet_mask) { Ok(u) => u as u8, Err(e) => return Err(ProxyError::new(e.to_string())), }; Ok(MacVLAN { address, gateways, interface: interface.to_string(), // Disabled for now // mtu: l.mtu, prefix_length, }) } // add the ip address to the container namespace fn add_ip(&self, nls: &mut Socket) -> Result<(), ProxyError> { debug!("adding network information for {}", self.interface); let ip = IpNet::new(self.address, self.prefix_length)?; let dev = nls.get_link(netlink::LinkID::Name(self.interface.clone()))?; match nls.add_addr(dev.header.index, &ip) { Ok(_) => Ok(()), Err(e) => Err(ProxyError::new(e.to_string())), } } // add one or more routes to the container namespace fn add_gws(&self, nls: &mut Socket) -> Result<(), ProxyError> { debug!("adding gateways to {}", self.interface); match core_utils::add_default_routes(nls, &self.gateways, None) { Ok(_) => Ok(()), Err(e) => Err(ProxyError::new(e.to_string())), } } /* For now, nv will remove the interface; this causes all IP stuff to fold. */ fn remove(self) -> Result<(), ProxyError> { debug!("removing interface {}", self.interface); todo!() } } // setup takes the DHCP lease and some additional information and // applies the TCP/IP information to the namespace. pub fn setup(lease: &NetavarkLease, interface: &str, ns_path: &str) -> Result<(), ProxyError> { debug!("setting up {}", interface); let vlan = match MacVLAN::new(lease, interface) { Ok(f) => f, Err(e) => return Err(e), }; let (_, mut netns) = core_utils::open_netlink_sockets(ns_path)?; vlan.add_ip(&mut netns.netlink)?; vlan.add_gws(&mut netns.netlink) } // teardown is likely unnecessary but holding place here pub fn teardown() -> Result<(), ProxyError> { todo!() } /// get_prefix_lengh takes a subnet mask in str form and /// returns its prefix length by counting ones. /// /// # Arguments /// /// * `netmask`: str form of subnet mask (i.e. 255.255.255.0) /// /// returns: Result /// /// # Examples /// /// ``` /// /// ``` fn get_prefix_length_v4(netmask: &str) -> Result { let sub_mask = match Ipv4Addr::from_str(netmask) { Ok(n) => n, Err(e) => return Err(ProxyError::new(e.to_string())), }; Ok(u32::from(sub_mask).count_ones()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_24() { assert_eq!(get_prefix_length_v4("255.255.255.0").unwrap(), 24_u32) } #[test] fn test_16() { assert_eq!(get_prefix_length_v4("255.255.0.0").unwrap(), 16_u32) } #[test] fn test_25() { assert_eq!(get_prefix_length_v4("255.255.255.128").unwrap(), 25_u32) } #[test] fn test_bad_input() { assert!(get_prefix_length_v4("255.255.128").is_err()) } } containers-netavark-83edb4b/src/dhcp_proxy/lib.rs000066400000000000000000000237261452673426700223010ustar00rootroot00000000000000extern crate core; use crate::dhcp_proxy::lib::g_rpc::{Lease, NetworkConfig}; use crate::error::NetavarkError; use std::convert::TryFrom; use std::error::Error; use g_rpc::netavark_proxy_client::NetavarkProxyClient; use http::Uri; use log::debug; use std::fs::File; use std::net::AddrParseError; use std::net::{Ipv4Addr, Ipv6Addr}; use std::str::FromStr; use tokio::net::UnixStream; use tonic::transport::{Channel, Endpoint}; use tonic::Request; use tower::service_fn; #[allow(clippy::unwrap_used)] pub mod g_rpc { include!("../proto-build/netavark_proxy.rs"); use crate::dhcp_proxy::lib::VectorConv; use crate::dhcp_proxy::types::{CustomErr, ProxyError}; use mozim::DhcpV4Lease; use std::convert::TryFrom; use std::net::Ipv4Addr; use std::str::FromStr; impl Lease { /// Add mac address to a lease pub fn add_mac_address(&mut self, mac_addr: &String) { self.mac_address = mac_addr.to_string() } /// Update the domain name of the lease pub fn add_domain_name(&mut self, domain_name: &String) { self.domain_name = domain_name.to_string(); } } impl From for Lease { fn from(l: DhcpV4Lease) -> Lease { // Since these fields are optional as per mozim. Match them first and then set them let domain_name = match l.domain_name { None => String::from(""), Some(l) => l, }; let mtu = l.mtu.unwrap_or(0) as u32; Lease { t1: l.t1, t2: l.t2, lease_time: l.lease_time, mtu, domain_name, mac_address: "".to_string(), siaddr: l.siaddr.to_string(), yiaddr: l.yiaddr.to_string(), srv_id: l.srv_id.to_string(), subnet_mask: l.subnet_mask.to_string(), // TODO something is jacked with8 broadcast, moving on broadcast_addr: "".to_string(), dns_servers: handle_ip_vectors(l.dns_srvs), gateways: handle_ip_vectors(l.gateways), ntp_servers: handle_ip_vectors(l.ntp_srvs), host_name: l.host_name.unwrap_or_else(|| String::from("")), is_v6: false, } } } impl TryFrom for DhcpV4Lease { type Error = ProxyError; fn try_from(l: Lease) -> Result { let host_name = if !l.host_name.is_empty() { Some(l.host_name) } else { None }; let domain_name = if !l.domain_name.is_empty() { Some(l.domain_name) } else { None }; let broadcast_addr = if !l.broadcast_addr.is_empty() { Some(Ipv4Addr::from_str(&l.broadcast_addr)?) } else { None }; let mtu = match u16::try_from(l.mtu) { Ok(m) => Some(m), Err(e) => return Err(ProxyError::new(e.to_string())), }; // Have to do it the hard way because the struct in mozim has a private // called srv_id which is a vector of 6 u8s representing the DHCP server's // mac address let mut lease = DhcpV4Lease::default(); lease.siaddr = Ipv4Addr::from_str(&l.siaddr)?; lease.yiaddr = Ipv4Addr::from_str(&l.yiaddr)?; lease.t1 = l.t1; lease.t2 = l.t2; lease.lease_time = l.lease_time; lease.srv_id = Ipv4Addr::from_str(&l.srv_id)?; lease.subnet_mask = Ipv4Addr::from_str(&l.subnet_mask)?; lease.broadcast_addr = broadcast_addr; lease.dns_srvs = l.dns_servers.to_v4_addrs()?; lease.gateways = l.gateways.to_v4_addrs()?; lease.ntp_srvs = l.ntp_servers.to_v4_addrs()?; lease.mtu = mtu; lease.host_name = host_name; lease.domain_name = domain_name; Ok(lease) } } fn handle_ip_vectors(ip: Option>) -> Vec { let mut ips: Vec = Vec::new(); if let Some(j) = ip { for ip in j { ips.push(ip.to_string()); } } ips } impl From for NvIpv4Addr { fn from(ip: std::net::Ipv4Addr) -> NvIpv4Addr { NvIpv4Addr { octets: Vec::from(ip.octets()), } } } impl From> for NvIpv4Addr { fn from(ip: Option) -> Self { if let Some(addr) = ip { return NvIpv4Addr { octets: Vec::from(addr.octets()), }; } NvIpv4Addr { octets: Vec::from([0, 0, 0, 0]), } } } #[test] fn test_handle_gw() { use std::str::FromStr; let mut ips: Vec = Vec::new(); for i in 0..5 { let ip = format!("10.1.{i}.1"); let ipv4 = std::net::Ipv4Addr::from_str(&ip).expect("failed hard"); ips.push(ipv4); } let response = handle_ip_vectors(Some(ips)); // Len of response should be same as ips assert_eq!(response.len(), 5); assert_eq!(response[0].to_string(), "10.1.0.1"); } } // A collection of functions for client side connections to the proxy server impl NetworkConfig { pub fn load(path: &str) -> Result> { let file = std::io::BufReader::new(File::open(path)?); Ok(serde_json::from_reader(file)?) } /// get_client is an internal function to obtain the uds endpoint /// /// # Arguments /// /// * `p`: path to uds /// /// returns: Result, NetavarkError> /// /// # Examples /// /// ``` /// /// ``` async fn get_client(p: String) -> Result, NetavarkError> { // We do not know why the uds connections need to be done like this. The // maintainer suggested it is part of the their API. // We know this is safe and if it ever fails test will catch it let endpoint = Endpoint::try_from("http://[::1]").unwrap(); let path = p.clone(); let channel = endpoint .connect_with_connector(service_fn(move |_: Uri| { let pp = p.clone(); debug!("using uds path: {}", pp); UnixStream::connect(pp) })) .await .map_err(|e| { let msg = match e.source() { Some(err) => { // this is a bit ugly but we check if the socket was not found to provide a proper error message // and hint at the systemd socket unit match err .source() .and_then(|e| e.downcast_ref::()) .and_then(|e| { if e.kind() == std::io::ErrorKind::NotFound || e.kind() == std::io::ErrorKind::ConnectionRefused { Some(format!("socket \"{path}\": {e}, is the netavark-dhcp-proxy.socket unit enabled?")) } else { None } }) { Some(msg) => msg, None => err.to_string(), } } None => e.to_string(), }; NetavarkError::msg(msg) })?; Ok(NetavarkProxyClient::new(channel)) } /// get_lease is a wrapper function for obtaining a lease /// over grpc from the nvproxy-server /// /// # Arguments /// /// * `p`: path to uds /// /// returns: Result /// /// # Examples /// /// ``` /// /// ``` pub async fn get_lease(self, p: &str) -> Result { let mut client = NetworkConfig::get_client(p.to_string()).await?; let lease = match client.setup(Request::new(self)).await { Ok(l) => l.into_inner(), Err(s) => return Err(s.into()), }; Ok(lease) } /// drop_lease is a wrapper function to release the current /// DHCP lease via the nvproxy /// /// /// # Arguments /// /// * `p`: path to uds /// /// returns: Result /// /// # Examples /// /// ``` /// /// ``` pub async fn drop_lease(self, p: &str) -> Result { let mut client = NetworkConfig::get_client(p.to_string()).await?; let lease = match client.teardown(Request::new(self)).await { Ok(l) => l.into_inner(), Err(e) => return Err(e.into()), }; Ok(lease) } } trait VectorConv { fn to_v4_addrs(&self) -> Result>, AddrParseError>; fn to_v6_addrs(&self) -> Result>, AddrParseError>; } impl VectorConv for Vec { fn to_v4_addrs(&self) -> Result>, AddrParseError> { if self.is_empty() { return Ok(None); } let mut out_addrs = Vec::new(); for ip in self { match Ipv4Addr::from_str(ip) { Ok(i) => out_addrs.push(i), Err(e) => return Err(e), }; } Ok(Some(out_addrs)) } fn to_v6_addrs(&self) -> Result>, AddrParseError> { if self.is_empty() { return Ok(None); } let mut out_addrs = Vec::new(); for ip in self { match Ipv6Addr::from_str(ip) { Ok(i) => out_addrs.push(i), Err(e) => return Err(e), }; } Ok(Some(out_addrs)) } } containers-netavark-83edb4b/src/dhcp_proxy/mod.rs000066400000000000000000000001411452673426700222740ustar00rootroot00000000000000pub mod cache; pub mod dhcp_service; pub mod ip; pub mod lib; pub mod proxy_conf; pub mod types; containers-netavark-83edb4b/src/dhcp_proxy/proxy_conf.rs000066400000000000000000000211251452673426700237100ustar00rootroot00000000000000// TODO these constant destinations are not final. use std::env; use std::path::{Path, PathBuf}; // Where the cache and socket are stored by default pub const NETAVARK_PROXY_RUN_DIR: &str = "/run/podman"; pub const NETAVARK_PROXY_RUN_DIR_ENV: &str = "NETAVARK_PROXY_RUN_DIR_ENV"; // Default UDS path for gRPC to communicate on. pub const DEFAULT_UDS_PATH: &str = "/run/podman/nv-proxy.sock"; // Default configuration directory. pub const DEFAULT_CONFIG_DIR: &str = ""; // Default Network configuration path pub const DEFAULT_NETWORK_CONFIG: &str = "/dev/stdin"; // Default epoll wait time before dhcp socket times out pub const DEFAULT_TIMEOUT: u32 = 8; // Proxy server gRPC socket file name pub const PROXY_SOCK_NAME: &str = "nv-proxy.sock"; // Where leases are stored on the filesystem pub const CACHE_FILE_NAME: &str = "nv-proxy.lease"; // Seconds until the service should exit pub const DEFAULT_INACTIVITY_TIMEOUT: u64 = 300; /// Get the RUN_DIR where the proxy cache and socket /// are stored /// /// /// # Arguments /// /// * `run_cli`: /// /// returns: String /// /// # Examples /// /// ``` /// /// ``` pub fn get_run_dir(run_cli: Option<&str>) -> String { // if environment, return it // if opt, return it // return default match env::var(NETAVARK_PROXY_RUN_DIR_ENV) { // env::var returns an error if the key doesnt exist Ok(v) => return v, Err(_) => { if let Some(val) = run_cli { return val.to_string(); } } } NETAVARK_PROXY_RUN_DIR.to_string() } /// Returns the fully qualified path of the proxy socket file including /// the socket file name /// /// # Arguments /// /// * `run_dir_opt`: /// /// returns: PathBuf /// /// # Examples /// /// ``` /// /// ``` pub fn get_proxy_sock_fqname(run_dir_opt: Option<&str>) -> PathBuf { let run_dir = get_run_dir(run_dir_opt); Path::new(&run_dir).join(PROXY_SOCK_NAME) } /// Returns the fully qualified path of the cache file including the cache /// file name /// /// /// # Arguments /// /// * `run_dir`: /// /// returns: PathBuf /// /// # Examples /// /// ``` /// /// ``` pub fn get_cache_fqname(run_dir: Option<&str>) -> PathBuf { let run_dir = get_run_dir(run_dir); Path::new(&run_dir).join(CACHE_FILE_NAME) } #[cfg(test)] mod conf_tests { use crate::dhcp_proxy::proxy_conf::{ get_cache_fqname, get_proxy_sock_fqname, get_run_dir, CACHE_FILE_NAME, NETAVARK_PROXY_RUN_DIR, NETAVARK_PROXY_RUN_DIR_ENV, PROXY_SOCK_NAME, }; use std::path::Path; use std::collections::HashMap; use std::env; use std::ffi::OsStr; use std::hash::Hash; use std::panic::{self, RefUnwindSafe, UnwindSafe}; use std::sync::Mutex; use once_cell::sync::Lazy; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; /// Make sure that the environment isn't modified concurrently. static SERIAL_TEST: Lazy> = Lazy::new(Default::default); /// /// The following stanzas of code should be attributed to https://github.com/vmx/temp-env /// /// The previous value is restored when the closure completes or panics, before unwinding the /// panic. /// /// If `value` is set to `None`, then the environment variable is unset. pub fn with_var(key: K, value: Option, closure: F) -> R where K: AsRef + Clone + Eq + Hash, V: AsRef + Clone, F: Fn() -> R + UnwindSafe + RefUnwindSafe, { with_vars(vec![(key, value)], closure) } /// Unsets a single environment variable for the duration of the closure. /// /// The previous value is restored when the closure completes or panics, before unwinding the /// panic. /// /// This is a shorthand and identical to the following: /// ```rust /// temp_env::with_var("MY_ENV_VAR", None::<&str>, || { /// // Run some code where `MY_ENV_VAR` is unset. /// }); /// ``` pub fn with_var_unset(key: K, closure: F) -> R where K: AsRef + Clone + Eq + Hash, F: Fn() -> R + UnwindSafe + RefUnwindSafe, { with_var(key, None::<&str>, closure) } /// Sets environment variables for the duration of the closure. /// /// The previous values are restored when the closure completes or panics, before unwinding the /// panic. /// /// If a `value` is set to `None`, then the environment variable is unset. /// /// If the variable with the same name is set multiple times, the last one wins. pub fn with_vars(kvs: Vec<(K, Option)>, closure: F) -> R where K: AsRef + Clone + Eq + Hash, V: AsRef + Clone, F: Fn() -> R + UnwindSafe + RefUnwindSafe, { let guard = SERIAL_TEST.lock().unwrap(); let mut old_kvs: HashMap> = HashMap::new(); for (key, value) in kvs { // If the same key is given several times, the original/old value is only correct before // the environment was updated. if !old_kvs.contains_key(&key) { let old_value = env::var(&key).ok(); old_kvs.insert(key.clone(), old_value); } update_env(&key, value); } match panic::catch_unwind(closure) { Ok(result) => { for (key, value) in old_kvs { update_env(key, value); } result } Err(err) => { for (key, value) in old_kvs { update_env(key, value); } drop(guard); panic::resume_unwind(err); } } } fn update_env(key: K, value: Option) where K: AsRef, V: AsRef, { match value { Some(v) => env::set_var(key, v), None => env::remove_var(key), } } fn random_string(len: usize) -> String { let rand_string: String = thread_rng() .sample_iter(&Alphanumeric) .take(len) .map(char::from) .collect(); format!("/{rand_string}") } // The following tests seem to be susceptible to the environment variables poisoning // each other when run in parallel (default rust behavior). If we set --test-threads=1, // this will not happen. For now, I wrap the tests in `with_var_unset`. #[test] fn test_run_dir_env() { let r = random_string(25); with_var(NETAVARK_PROXY_RUN_DIR_ENV, Some(&r), || { assert_eq!(get_run_dir(None), r) }); } #[test] fn test_run_dir_with_opt() { let r = random_string(25); with_var_unset(NETAVARK_PROXY_RUN_DIR_ENV, || { assert_eq!(get_run_dir(Some(&r)), r) }); } #[test] fn test_run_dir_as_none() { with_var_unset(NETAVARK_PROXY_RUN_DIR_ENV, || { assert_eq!(get_run_dir(None), NETAVARK_PROXY_RUN_DIR) }); } #[test] fn test_get_cache_env() { let r = random_string(25); with_var(NETAVARK_PROXY_RUN_DIR_ENV, Some(&r), || { assert_eq!(get_cache_fqname(None), Path::new(&r).join(CACHE_FILE_NAME)); }); } #[test] fn test_get_cache_with_opt() { let r = random_string(25); with_var_unset(NETAVARK_PROXY_RUN_DIR_ENV, || { assert_eq!( get_cache_fqname(Some(&r)), Path::new(&r).join(CACHE_FILE_NAME) ) }) } #[test] fn test_get_cache_as_none() { with_var_unset(NETAVARK_PROXY_RUN_DIR_ENV, || { assert_eq!( get_cache_fqname(None), Path::new(NETAVARK_PROXY_RUN_DIR).join(CACHE_FILE_NAME) ) }); } #[test] fn test_get_proxy_sock_env() { let r = random_string(25); with_var(NETAVARK_PROXY_RUN_DIR_ENV, Some(&r), || { assert_eq!( get_proxy_sock_fqname(None), Path::new(&r).join(PROXY_SOCK_NAME) ); }); } #[test] fn test_get_proxy_sock_with_opt() { let r = random_string(25); with_var_unset(NETAVARK_PROXY_RUN_DIR_ENV, || { assert_eq!( get_proxy_sock_fqname(Some(&r)), Path::new(&r).join(PROXY_SOCK_NAME) ) }) } #[test] fn test_get_proxy_sock_as_none() { with_var_unset(NETAVARK_PROXY_RUN_DIR_ENV, || { assert_eq!( get_proxy_sock_fqname(None), Path::new(NETAVARK_PROXY_RUN_DIR).join(PROXY_SOCK_NAME) ) }); } } containers-netavark-83edb4b/src/dhcp_proxy/types.rs000066400000000000000000000035751452673426700226770ustar00rootroot00000000000000use crate::dhcp_proxy::lib::g_rpc::NetworkConfig; use crate::error::NetavarkError; use ipnet::PrefixLenError; use mozim::DhcpError; use mozim::ErrorKind::InvalidArgument; use std::net::AddrParseError; use std::num::ParseIntError; use std::str::FromStr; use std::string::ToString; use tonic::{Code, Status}; impl FromStr for NetworkConfig { type Err = ParseIntError; fn from_str(_s: &str) -> Result { // s is actually a string so if we intend to generate // a `NetworkConfig` object from `s` parse `s` and populate // instead of default empty values Ok(NetworkConfig { host_iface: "".to_string(), container_mac_addr: "".to_string(), domain_name: "".to_string(), host_name: "".to_string(), version: 0, ns_path: "".to_string(), container_iface: "".to_string(), }) } } #[derive(Debug, Clone)] pub struct ProxyError(String); pub trait CustomErr { fn new(msg: String) -> Self; } impl CustomErr for ProxyError { fn new(msg: String) -> Self { ProxyError(msg) } } impl ToString for ProxyError { fn to_string(&self) -> String { self.0.to_string() } } impl From for Status { fn from(pe: ProxyError) -> Self { Status::new(Code::Unknown, pe.to_string()) } } impl From for ProxyError { fn from(cause: NetavarkError) -> Self { ProxyError::new(cause.to_string()) } } impl From for ProxyError { fn from(cause: PrefixLenError) -> Self { ProxyError::new(cause.to_string()) } } impl From for ProxyError { fn from(e: AddrParseError) -> Self { ProxyError::new(e.to_string()) } } impl From for DhcpError { fn from(e: ProxyError) -> Self { DhcpError::new(InvalidArgument, e.to_string()) } } containers-netavark-83edb4b/src/dhcp_proxy_client/000077500000000000000000000000001452673426700225115ustar00rootroot00000000000000containers-netavark-83edb4b/src/dhcp_proxy_client/client.rs000066400000000000000000000052021452673426700243340ustar00rootroot00000000000000use clap::{Parser, Subcommand}; use commands::{setup, teardown}; use std::process; use tonic::{Code, Status}; use netavark::dhcp_proxy::lib::g_rpc::{Lease, NetworkConfig}; use netavark::dhcp_proxy::proxy_conf::{DEFAULT_NETWORK_CONFIG, DEFAULT_UDS_PATH}; use netavark::error::NetavarkError; pub mod commands; #[derive(Parser, Debug)] #[clap(version = env!("CARGO_PKG_VERSION"))] struct Opts { /// Use specific uds path #[clap(short, long)] uds: Option, /// Instead of reading from STDIN, read the configuration to be applied from the given file. #[clap(short, long)] file: Option, /// Netavark trig command #[clap(subcommand)] subcmd: SubCommand, } #[derive(Subcommand, Debug)] enum SubCommand { /// Configures the given network namespace with the given configuration. Setup(setup::Setup), /// Undo any configuration applied via setup command. Teardown(teardown::Teardown), // Display info about netavark. // Version(version::Version), } #[cfg(unix)] #[tokio::main] // This client assumes you use the default lease directory async fn main() -> Result<(), Box> { // This should be moved to somewhere central. We also need to add override logic. env_logger::builder().format_timestamp(None).init(); let opts = Opts::parse(); let file = opts .file .unwrap_or_else(|| DEFAULT_NETWORK_CONFIG.to_string()); let uds_path = opts.uds.unwrap_or_else(|| DEFAULT_UDS_PATH.to_string()); let input_config = NetworkConfig::load(&file)?; let result = match opts.subcmd { SubCommand::Setup(s) => s.exec(&uds_path, input_config).await, SubCommand::Teardown(t) => t.exec(&uds_path, input_config).await, }; let r = match result { Ok(r) => r, Err(e) => { eprintln!("Error: {e}"); match e { NetavarkError::DHCPProxy(status) => process_failure(status), _ => process::exit(1), } } }; let pp = ::serde_json::to_string_pretty(&r); // TODO this should probably return an empty lease so consumers // don't soil themselves println!("{}", pp.unwrap_or_else(|_| "".to_string())); Ok(()) } // // process_failure makes the client exit with a specific // error code // fn process_failure(status: Status) -> Lease { let mut rc: i32 = 1; match status.code() { Code::Unknown => { rc = 155; } Code::InvalidArgument => { rc = 156; } Code::DeadlineExceeded => {} Code::NotFound => { rc = 6; } _ => {} } process::exit(rc) } containers-netavark-83edb4b/src/dhcp_proxy_client/commands/000077500000000000000000000000001452673426700243125ustar00rootroot00000000000000containers-netavark-83edb4b/src/dhcp_proxy_client/commands/mod.rs000066400000000000000000000000651452673426700254400ustar00rootroot00000000000000pub mod setup; pub mod teardown; // pub mod version; containers-netavark-83edb4b/src/dhcp_proxy_client/commands/setup.rs000066400000000000000000000007011452673426700260160ustar00rootroot00000000000000use clap::Parser; use log::debug; use netavark::{ dhcp_proxy::lib::g_rpc::{Lease, NetworkConfig}, error::NetavarkError, }; #[derive(Parser, Debug)] pub struct Setup {} impl Setup { pub async fn exec(&self, p: &str, config: NetworkConfig) -> Result { debug!("{:?}", "Setting up..."); debug!("input: {:#?}", serde_json::to_string_pretty(&config)); config.clone().get_lease(p).await } } containers-netavark-83edb4b/src/dhcp_proxy_client/commands/teardown.rs000066400000000000000000000005741452673426700265110ustar00rootroot00000000000000use clap::Parser; use log::debug; use netavark::{ dhcp_proxy::lib::g_rpc::{Lease, NetworkConfig}, error::NetavarkError, }; #[derive(Parser, Debug)] pub struct Teardown {} impl Teardown { pub async fn exec(&self, p: &str, config: NetworkConfig) -> Result { debug!("Entering teardown"); config.clone().drop_lease(p).await } } containers-netavark-83edb4b/src/dns/000077500000000000000000000000001452673426700175605ustar00rootroot00000000000000containers-netavark-83edb4b/src/dns/aardvark.rs000066400000000000000000000343061452673426700217270ustar00rootroot00000000000000use crate::error::{NetavarkError, NetavarkResult}; use fs2::FileExt; use libc::pid_t; use nix::sys::signal::{self, Signal}; use nix::unistd::Pid; use std::ffi::{OsStr, OsString}; use std::fs; use std::fs::File; use std::fs::OpenOptions; use std::io::Result; use std::io::{prelude::*, ErrorKind}; use std::net::Ipv4Addr; use std::net::{IpAddr, Ipv6Addr}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; const SYSTEMD_CHECK_PATH: &str = "/run/systemd/system"; const SYSTEMD_RUN: &str = "systemd-run"; const AARDVARK_COMMIT_LOCK: &str = "aardvark.lock"; #[derive(Clone, Debug)] pub struct AardvarkEntry<'a> { pub network_name: &'a str, pub network_gateways: Vec, pub network_dns_servers: &'a Option>, pub container_id: &'a str, pub container_ips_v4: Vec, pub container_ips_v6: Vec, pub container_names: Vec, pub container_dns_servers: &'a Option>, } #[derive(Debug, Clone)] pub struct Aardvark { /// aardvark's config directory pub config: PathBuf, /// tells if container is rootfull or rootless pub rootless: bool, /// path to the aardvark-dns binary pub aardvark_bin: OsString, /// port to bind to pub port: OsString, } impl Aardvark { pub fn new(config: PathBuf, rootless: bool, aardvark_bin: OsString, port: u16) -> Self { Aardvark { config, rootless, aardvark_bin, port: port.to_string().into(), } } /// On success returns aardvark server's pid or returns -1; fn get_aardvark_pid(&self) -> NetavarkResult { let path = Path::new(&self.config).join("aardvark.pid"); let pid: i32 = match fs::read_to_string(path) { Ok(content) => match content.parse::() { Ok(val) => val, Err(e) => { return Err(NetavarkError::msg(format!("parse aardvark pid: {e}"))); } }, Err(e) => { return Err(NetavarkError::Io(e)); } }; Ok(pid) } fn is_executable_in_path(program: &str) -> bool { if let Ok(path) = std::env::var("PATH") { for p in path.split(':') { let p_str = format!("{p}/{program}"); if fs::metadata(p_str).is_ok() { return true; } } } false } pub fn start_aardvark_server(&self) -> Result<()> { log::debug!("Spawning aardvark server"); let mut aardvark_args = vec![]; // only use systemd when it is booted, see sd_booted(3) if Path::new(SYSTEMD_CHECK_PATH).exists() && Aardvark::is_executable_in_path(SYSTEMD_RUN) { // TODO: This could be replaced by systemd-api. aardvark_args = vec![ OsStr::new(SYSTEMD_RUN), OsStr::new("-q"), OsStr::new("--scope"), ]; if self.rootless { aardvark_args.push(OsStr::new("--user")); } } aardvark_args.extend(vec![ self.aardvark_bin.as_os_str(), OsStr::new("--config"), self.config.as_os_str(), OsStr::new("-p"), self.port.as_os_str(), OsStr::new("run"), ]); log::debug!("start aardvark-dns: {:?}", aardvark_args); // After https://github.com/containers/aardvark-dns/pull/148 this command // will block till aardvark-dns's parent process returns back and let // aardvark inherit all the fds. Command::new(aardvark_args[0]) .args(&aardvark_args[1..]) .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) // set RUST_LOG for aardvark .env("RUST_LOG", log::max_level().as_str()) .output()?; Ok(()) } pub fn notify(&self, start: bool) -> NetavarkResult<()> { match self.get_aardvark_pid() { Ok(pid) => { match signal::kill(Pid::from_raw(pid), Signal::SIGHUP) { Ok(_) => return Ok(()), Err(err) => { // ESRCH == process does not exists // start new sever below in that case and not error if err != nix::errno::Errno::ESRCH { return Err(NetavarkError::msg(format!( "failed to send SIGHUP to aardvark: {err}" ))); } } } } Err(err) => { if !start { return Err(NetavarkError::wrap("failed to get aardvark pid", err)); } } }; self.start_aardvark_server()?; Ok(()) } pub fn commit_entries(&self, entries: Vec) -> Result<()> { // Acquire fs lock to ensure other instance of aardvark cannot commit // or start aardvark instance till already running instance has not // completed its `commit` phase. let lockfile_path = Path::new(&self.config) .join("..") .join(AARDVARK_COMMIT_LOCK); let lockfile = match OpenOptions::new() .read(true) .write(true) .create(true) .open(&lockfile_path) { Ok(file) => file, Err(e) => { return Err(std::io::Error::new( std::io::ErrorKind::Other, format!("Failed to open/create lockfile {:?}: {}", &lockfile_path, e), )); } }; if let Err(er) = lockfile.lock_exclusive() { return Err(std::io::Error::new( std::io::ErrorKind::Other, format!("Failed to acquire exclusive lock on {lockfile_path:?}: {er}"), )); } for entry in &entries { let path = Path::new(&self.config).join(entry.network_name); let file = match OpenOptions::new().write(true).create_new(true).open(&path) { Ok(mut f) => { // collect gateway let gws = entry .network_gateways .iter() .map(|g| g.to_string()) .collect::>() .join(","); // collect network dns servers if specified let network_dns_servers = if let Some(network_dns_servers) = &entry.network_dns_servers { if !network_dns_servers.is_empty() { let dns_server_collected = network_dns_servers .iter() .map(|g| g.to_string()) .collect::>() .join(","); format!(" {dns_server_collected}") } else { "".to_string() } } else { "".to_string() }; let data = format!("{gws}{network_dns_servers}\n"); f.write_all(data.as_bytes())?; // return error if write fails f } Err(ref e) if e.kind() == ErrorKind::AlreadyExists => { OpenOptions::new().append(true).open(&path)? } Err(e) => { return Err(e); } }; match Aardvark::commit_entry(entry, file) { Err(er) => { // drop lockfile when commit is completed if let Err(er) = lockfile.unlock() { return Err(std::io::Error::new( std::io::ErrorKind::Other, format!("Failed to unlock exclusive lock on {lockfile_path:?}: {er}"), )); } return Err(std::io::Error::new( std::io::ErrorKind::Other, format!("Failed to commit entry {entry:?}: {er}"), )); } Ok(_) => continue, } } // drop lockfile when commit is completed if let Err(er) = lockfile.unlock() { return Err(std::io::Error::new( std::io::ErrorKind::Other, format!("Failed to unlock exclusive lock on {lockfile_path:?}: {er}"), )); } Ok(()) } fn commit_entry(entry: &AardvarkEntry, mut file: File) -> Result<()> { let container_names = entry.container_names.join(","); let ipv4s = entry .container_ips_v4 .iter() .map(|g| g.to_string()) .collect::>() .join(","); let ipv6s = entry .container_ips_v6 .iter() .map(|g| g.to_string()) .collect::>() .join(","); let dns_server = if let Some(dns_servers) = &entry.container_dns_servers { if !dns_servers.is_empty() { let dns_server_collected = dns_servers .iter() .map(|g| g.to_string()) .collect::>() .join(","); format!(" {dns_server_collected}") } else { "".to_string() } } else { "".to_string() }; let data = format!( "{} {} {} {}{}\n", entry.container_id, ipv4s, ipv6s, container_names, dns_server ); file.write_all(data.as_bytes())?; // return error if write fails Ok(()) } pub fn commit_netavark_entries(&self, entries: Vec) -> NetavarkResult<()> { if !entries.is_empty() { self.commit_entries(entries)?; self.notify(true)?; } Ok(()) } pub fn delete_entry(&self, container_id: &str, network_name: &str) -> Result<()> { let path = Path::new(&self.config).join(network_name); let file_content = fs::read_to_string(&path)?; let lines: Vec<&str> = file_content.split_terminator('\n').collect(); let mut idx = 0; let mut file = File::create(&path)?; for line in lines { if line.contains(container_id) { continue; } file.write_all(line.as_bytes())?; file.write_all(b"\n")?; idx += 1; } // nothing left in file (only header), remove it if idx <= 1 { fs::remove_file(&path)? } Ok(()) } // Modifies network dns_servers for a specific network and notifies aardvark-dns server // with the change. // Note: If no aardvark dns config exists for a network function will return success without // doing anything, because `podman network update` is applicable for networks even when no // container is attached to it. pub fn modify_network_dns_servers( &self, network_name: &str, network_dns_servers: &Vec, ) -> NetavarkResult<()> { let mut dns_servers_modified = false; let path = Path::new(&self.config).join(network_name); let file_content = match fs::read_to_string(&path) { Ok(content) => content, Err(error) => { if error.kind() == std::io::ErrorKind::NotFound { // Most likely `podman network update` was called // but no container on the network is running hence // no aardvark file is there in such case return success // since podman database still got updated and it will be // populated correctly for the next container. return Ok(()); } else { return Err(NetavarkError::Io(error)); } } }; let mut file = File::create(&path)?; //for line in lines { for (idx, line) in file_content.split_terminator('\n').enumerate() { if idx == 0 { // If this is first line, we have to modify this // first line has a format of `... ..` // We will read the first line and get the first column and // override the second column with new network dns servers. let network_parts = line.split(' ').collect::>(); if network_parts.is_empty() { return Err(NetavarkError::msg(format!( "invalid network configuration file: {}", path.display() ))); } let network_dns_servers_collected = if !network_dns_servers.is_empty() { dns_servers_modified = true; let dns_server_collected = network_dns_servers .iter() .map(|g| g.to_string()) .collect::>() .join(","); format!(" {dns_server_collected}") } else { "".to_string() }; // Modify line to support new format let content = format!("{}{}", network_parts[0], network_dns_servers_collected); file.write_all(content.as_bytes())?; } else { file.write_all(line.as_bytes())?; } file.write_all(b"\n")?; } // If dns servers were updated notify the aardvark-dns server // if refresh is needed. if dns_servers_modified { self.notify(false)?; } Ok(()) } pub fn delete_from_netavark_entries(&self, entries: Vec) -> NetavarkResult<()> { for entry in &entries { self.delete_entry(entry.container_id, entry.network_name)?; } self.notify(false) } } containers-netavark-83edb4b/src/dns/mod.rs000066400000000000000000000000221452673426700206770ustar00rootroot00000000000000pub mod aardvark; containers-netavark-83edb4b/src/error/000077500000000000000000000000001452673426700201255ustar00rootroot00000000000000containers-netavark-83edb4b/src/error/mod.rs000066400000000000000000000134151452673426700212560ustar00rootroot00000000000000use std::error::Error; use std::fmt; pub type NetavarkResult = Result; /// wrap any result into a NetavarkError and add the given msg #[macro_export] macro_rules! wrap { ($result:expr, $msg:expr) => { $result.map_err(|err| NetavarkError::wrap($msg, err.into())) }; } /// Contains a list of errors, this is useful for teardown operations since we /// should cleanup as much as possible before return all encountered errors. #[derive(Debug)] pub struct NetavarkErrorList(Vec); impl NetavarkErrorList { pub fn new() -> Self { Self(vec![]) } pub fn push(&mut self, err: NetavarkError) { match err { // make sure the flatten the error list, nested lists would just look ugly NetavarkError::List(mut list) => self.0.append(&mut list.0), err => self.0.push(err), } } pub fn is_empty(&self) -> bool { self.0.is_empty() } } // clippy wants the default implementation even if it is not needed impl Default for NetavarkErrorList { fn default() -> Self { Self::new() } } pub trait ErrorWrap { /// wrap NetavarkResult error into a NetavarkError and add the given msg fn wrap(self, msg: S) -> NetavarkResult where S: Into; } impl ErrorWrap for NetavarkResult { fn wrap(self, msg: S) -> NetavarkResult where S: Into, { self.map_err(|err| NetavarkError::wrap(msg, err)) } } /// The main Netavark error type #[derive(Debug)] pub enum NetavarkError { // A string message Message(String), // A string message that sets a specific exit code for Netavark ExitCode(String, i32), // A chain of multiple errors Chain(String, Box), Io(std::io::Error), Dbus(zbus::Error), DbusVariant(zbus::zvariant::Error), Sysctl(sysctl::SysctlError), Serde(serde_json::Error), Netlink(netlink_packet_core::error::ErrorMessage), DHCPProxy(tonic::Status), List(NetavarkErrorList), } /// Internal struct for JSON output #[derive(Debug, Serialize, Deserialize)] pub struct JsonError { pub error: String, } impl NetavarkError { pub fn msg(msg: S) -> NetavarkError where S: Into, { NetavarkError::Message(msg.into()) } pub fn wrap(msg: S, chained: NetavarkError) -> NetavarkError where S: Into, { NetavarkError::Chain(msg.into(), Box::new(chained)) } /// Print the error in a standardized JSON format recognized by callers of /// Netavark. pub fn print_json(&self) { let to_json = JsonError { error: self.to_string(), }; println!( "{}", serde_json::to_string(&to_json).unwrap_or(format!( "Failed to serialize error message: {}", to_json.error )) ); } /// Get the exit code that Netavark should exit with pub fn get_exit_code(&self) -> i32 { match *self { NetavarkError::ExitCode(_, i) => i, _ => 1, } } /// unwrap the chain error recursively until we a non chain type error pub fn unwrap(&self) -> &NetavarkError { match self { NetavarkError::Chain(_, inner) => inner.unwrap(), _ => self, } } } impl fmt::Display for NetavarkError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { NetavarkError::Message(s) => write!(f, "{s}"), NetavarkError::ExitCode(s, _) => write!(f, "{s}"), NetavarkError::Chain(s, e) => write!(f, "{s}: {e}"), NetavarkError::Io(e) => write!(f, "IO error: {e}"), NetavarkError::Dbus(e) => write!(f, "DBus error: {e}"), NetavarkError::DbusVariant(e) => write!(f, "DBus Variant Error: {e}"), NetavarkError::Sysctl(e) => write!(f, "Sysctl error: {e}"), NetavarkError::Serde(e) => write!(f, "JSON Decoding error: {e}"), NetavarkError::Netlink(e) => write!(f, "Netlink error: {e}"), NetavarkError::DHCPProxy(e) => write!(f, "dhcp proxy error: {e}"), NetavarkError::List(list) => { if list.0.len() == 1 { write!(f, "{}", list.0[0]) } else { write!(f, "netavark encountered multiple errors:")?; for e in &list.0 { write!(f, "\n\t- {e}")?; } Ok(()) } } } } } impl Error for NetavarkError {} impl From for NetavarkError { fn from(err: std::io::Error) -> NetavarkError { NetavarkError::Io(err) } } impl From for NetavarkError { fn from(err: zbus::Error) -> NetavarkError { NetavarkError::Dbus(err) } } impl From for NetavarkError { fn from(err: zbus::zvariant::Error) -> NetavarkError { NetavarkError::DbusVariant(err) } } impl From for NetavarkError { fn from(err: sysctl::SysctlError) -> NetavarkError { NetavarkError::Sysctl(err) } } impl From for NetavarkError { fn from(err: serde_json::Error) -> NetavarkError { NetavarkError::Serde(err) } } impl From for NetavarkError { fn from(e: ipnet::PrefixLenError) -> Self { NetavarkError::Message(format!("{e}")) } } impl From for NetavarkError { fn from(err: netlink_packet_core::error::ErrorMessage) -> Self { NetavarkError::Netlink(err) } } impl From for NetavarkError { fn from(err: tonic::Status) -> Self { NetavarkError::DHCPProxy(err) } } containers-netavark-83edb4b/src/firewall/000077500000000000000000000000001452673426700206015ustar00rootroot00000000000000containers-netavark-83edb4b/src/firewall/firewalld.rs000066400000000000000000000617671452673426700231410ustar00rootroot00000000000000use crate::error::{NetavarkError, NetavarkResult}; use crate::firewall; use crate::network::internal_types; use crate::network::internal_types::{PortForwardConfig, TearDownNetwork, TeardownPortForward}; use crate::network::types::PortMapping; use core::convert::TryFrom; use log::{debug, info}; use std::collections::HashMap; use std::vec::Vec; use zbus::{ blocking::Connection, zvariant::{Array, Signature, Value}, }; const ZONENAME: &str = "netavark_zone"; const POLICYNAME: &str = "netavark_policy"; const PORTPOLICYNAME: &str = "netavark_portfwd"; // Firewalld driver - uses a dbus connection to communicate with firewalld. pub struct FirewallD { conn: Connection, } pub fn new(conn: Connection) -> Result, NetavarkError> { Ok(Box::new(FirewallD { conn })) } impl firewall::FirewallDriver for FirewallD { fn driver_name(&self) -> &str { firewall::FIREWALLD } fn setup_network(&self, network_setup: internal_types::SetupNetwork) -> NetavarkResult<()> { let mut need_reload = false; need_reload |= match create_zone_if_not_exist(&self.conn, ZONENAME) { Ok(b) => b, Err(e) => { return Err(NetavarkError::wrap( format!("Error creating zone {ZONENAME}"), e, )) } }; need_reload |= match add_policy_if_not_exist(&self.conn, POLICYNAME, ZONENAME, "ACCEPT", true) { Ok(b) => b, Err(e) => { return Err(NetavarkError::wrap( format!("Error creating policy {POLICYNAME}"), e, )) } }; need_reload |= match add_policy_if_not_exist(&self.conn, PORTPOLICYNAME, "ANY", "CONTINUE", false) { Ok(b) => b, Err(e) => { return Err(NetavarkError::wrap( format!("Error creating policy {POLICYNAME}"), e, )) } }; if need_reload { debug!("Reloading firewalld config to bring up zone and policy"); let _ = self.conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1", Some("org.fedoraproject.FirewallD1"), "reload", &(), )?; } // MUST come after the reload; otherwise the zone we made might not be // in the running config. if let Some(nets) = network_setup.subnets { match add_source_subnets_to_zone(&self.conn, ZONENAME, &nets) { Ok(_) => {} Err(e) => { return Err(NetavarkError::wrap( format!("Error adding source subnets to zone {ZONENAME}"), e, )) } }; } Ok(()) } fn teardown_network(&self, tear: TearDownNetwork) -> NetavarkResult<()> { if !tear.complete_teardown { return Ok(()); } if let Some(subnets) = tear.config.subnets { for subnet in subnets { debug!("Removing subnet {} from zone {}", subnet, ZONENAME); let _ = self.conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1", Some("org.fedoraproject.FirewallD1.zone"), "removeSource", &(ZONENAME, subnet.to_string()), )?; } } Ok(()) } fn setup_port_forward(&self, setup_portfw: PortForwardConfig) -> Result<(), NetavarkError> { // NOTE: There is a serious TOCTOU risk in this function if netavark // is either run in parallel, or is not the only thing to edit this // policy. // Because of Podman's locking, this should be safe in the typical // case. // I don't think there's a safer way, unfortunately. // Get the current configuration for the policy let policy_config_msg = self.conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1", Some("org.fedoraproject.FirewallD1.policy"), "getPolicySettings", &(PORTPOLICYNAME), )?; let policy_config: HashMap<&str, Value> = match policy_config_msg.body() { Ok(m) => m, Err(e) => { return Err(NetavarkError::wrap( format!( "Error decoding DBus message for policy {PORTPOLICYNAME} configuration" ), e.into(), )) } }; let mut port_forwarding_rules: Array; match policy_config.get("forward_ports") { Some(a) => match a { Value::Array(arr) => port_forwarding_rules = arr.clone(), _ => { return Err(NetavarkError::msg( "forward-port in firewalld policy object has a bad type", )) } }, None => { // No existing rules // Make us a new array. let sig = match Signature::try_from("(ssss)") { Ok(s) => s, Err(e) => { return Err(NetavarkError::wrap( "Error creating signature for new DBus array", e.into(), )) } }; port_forwarding_rules = Array::new(sig); } } // Create any necessary port forwarding rule(s) and add them to the // policy config we grabbed above. // Note that this does *absolutely no* conflict detection or // prevention - if two ports end up mapped to different containers, // that is not detected, and firewalld will allow it to happen. // Only one of them will win and be active, though. match setup_portfw.port_mappings { Some(ports) => { for port in ports { if !port.host_ip.is_empty() { port_forwarding_rules .append(Value::new(make_port_tuple(port, &port.host_ip)))?; } else { if let Some(v4) = setup_portfw.container_ip_v4 { port_forwarding_rules .append(Value::new(make_port_tuple(port, &v4.to_string())))?; } if let Some(v6) = setup_portfw.container_ip_v6 { port_forwarding_rules .append(Value::new(make_port_tuple(port, &v6.to_string())))?; } } } } None => {} }; // dns port forwarding requires rich rules as we also want to match destination ip // only bother if configured dns port isn't 53 let mut rich_rules_option: Option = None; if setup_portfw.dns_port != 53 && !setup_portfw.dns_server_ips.is_empty() { let mut rich_rules: Array; match policy_config.get("rich_rules") { Some(a) => match a { Value::Array(arr) => rich_rules = arr.clone(), _ => { return Err(NetavarkError::msg( "forward-port in firewalld policy object has a bad type", )) } }, None => { // No existing rules // Make us a new array. let sig = match Signature::try_from("s") { Ok(s) => s, Err(e) => { return Err(NetavarkError::wrap( "Error creating signature for new DBus array", e.into(), )) } }; rich_rules = Array::new(sig); } } for dns_ip in setup_portfw.dns_server_ips { let ip_family = if dns_ip.is_ipv6() { "ipv6" } else { "ipv4" }; let rule = format!("rule family=\"{}\" destination address=\"{}\" forward-port port=\"53\" protocol=\"udp\" to-port=\"{}\" to-addr=\"{}\"", ip_family, dns_ip, setup_portfw.dns_port, dns_ip); rich_rules.append(Value::new(rule))?; } rich_rules_option = Some(rich_rules) } // Firewalld won't alter keys we don't mention, so make a new config // map - with only the changes to ports. let new_pf_rules = Value::new(port_forwarding_rules); let mut new_policy_config = HashMap::<&str, &Value>::new(); new_policy_config.insert("forward_ports", &new_pf_rules); let new_rich_rules = rich_rules_option.map(Value::new); if let Some(rich) = &new_rich_rules { new_policy_config.insert("rich_rules", rich); } // Send the updated configuration back to firewalld. match self.conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1", Some("org.fedoraproject.FirewallD1.policy"), "setPolicySettings", &(PORTPOLICYNAME, new_policy_config), ) { Ok(_) => info!( "Successfully added port-forwarding rules for container {}", setup_portfw.container_id ), Err(e) => { return Err(NetavarkError::wrap( format!( "Failed to update policy {} to add container {} port forwarding rules", PORTPOLICYNAME, setup_portfw.container_id ), e.into(), )) } }; Ok(()) } fn teardown_port_forward(&self, teardown_pf: TeardownPortForward) -> NetavarkResult<()> { // Get the current configuration for the policy let policy_config_msg = self.conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1", Some("org.fedoraproject.FirewallD1.policy"), "getPolicySettings", &(PORTPOLICYNAME), )?; let policy_config: HashMap<&str, Value> = match policy_config_msg.body() { Ok(m) => m, Err(e) => { return Err(NetavarkError::wrap( format!( "Error decoding DBus message for policy {PORTPOLICYNAME} configuration" ), e.into(), )) } }; let old_port_forwarding_rules_option: Option = match policy_config.get("forward_ports") { Some(a) => match a { Value::Array(arr) => Some(arr.clone()), _ => { return Err(NetavarkError::msg( "forward-port in firewalld policy object has a bad type", )) } }, None => { // No existing rules - skip None } }; let mut port_forwarding_rules_option: Option = None; if let Some(old_port_forwarding_rules) = old_port_forwarding_rules_option { let sig = match Signature::try_from("(ssss)") { Ok(s) => s, Err(e) => { return Err(NetavarkError::wrap( "Error creating signature for new dbus array", e.into(), )) } }; let mut port_forwarding_rules = Array::new(sig); // use an invalid string if we don't have a valid v4 or v6 address. // This is ugly, but easiest code-wise. let ipv4 = match teardown_pf.config.container_ip_v4 { Some(i) => i.to_string(), None => "DOES NOT EXIST".to_string(), }; let ipv6 = match teardown_pf.config.container_ip_v6 { Some(i) => i.to_string(), None => "DOES NOT EXIST".to_string(), }; // Iterate through old rules, remove anything with the IPv4 or IPv6 of // this container as the destination IP. for port_tuple in old_port_forwarding_rules.iter() { match port_tuple { Value::Structure(s) => { let fields = s.clone().into_fields(); if fields.len() != 4 { return Err(NetavarkError::msg( "Port forwarding rule that was not a 4-tuple encountered", )); } let port_ip = match fields[3].clone() { Value::Str(s) => s.as_str().to_string(), _ => return Err(NetavarkError::msg("Port forwarding tuples must contain only strings, encountered a non-string object")), }; debug!("IP string from firewalld is {}", port_ip); if port_ip != ipv4 && port_ip != ipv6 { port_forwarding_rules.append(port_tuple.clone())?; } } _ => { return Err(NetavarkError::msg( "Port forwarding rule that was not a structure encountered", )) } } } port_forwarding_rules_option = Some(port_forwarding_rules) } // iterate through rich rules to remove dns forwarding if this // is the last container of the network e.g. teardown complete // only bother if configured dns port isn't 53 let mut rich_rules_option: Option = None; let mut old_rich_rules_option: Option = None; if teardown_pf.complete_teardown && teardown_pf.config.dns_port != 53 && !teardown_pf.config.dns_server_ips.is_empty() { if let Some(a) = policy_config.get("rich_rules") { match a { Value::Array(arr) => old_rich_rules_option = Some(arr.clone()), _ => { return Err(NetavarkError::msg( "forward-port in firewalld policy object has a bad type", )) } } } } if let Some(old_rich_rules) = old_rich_rules_option { let mut rules_to_delete: Vec = vec![]; for dns_ip in teardown_pf.config.dns_server_ips { let ip_family = if dns_ip.is_ipv6() { "ipv6" } else { "ipv4" }; let rule = format!("rule family=\"{}\" destination address=\"{}\" forward-port port=\"53\" protocol=\"udp\" to-port=\"{}\" to-addr=\"{}\"", ip_family, dns_ip, teardown_pf.config.dns_port, dns_ip); rules_to_delete.push(rule); } let sig = match Signature::try_from("s") { Ok(s) => s, Err(e) => { return Err(NetavarkError::wrap( "Error creating signature for new DBus array", e.into(), )) } }; let mut rich_rules = Array::new(sig); for rule in old_rich_rules.iter() { match rule { Value::Str(old_rule) => { if !rules_to_delete.contains(&old_rule.to_string()) { rich_rules.append(rule.clone())?; } } _ => { return Err(NetavarkError::msg( "Rich rule that was not a string encountered", )) } } } rich_rules_option = Some(rich_rules); } // Firewalld won't alter keys we don't mention, so make a new config // map - with only the changes to ports. let mut new_policy_config = HashMap::<&str, &Value>::new(); let new_pf_rules = port_forwarding_rules_option.map(Value::new); if let Some(pf) = &new_pf_rules { new_policy_config.insert("forward_ports", pf); } let new_rich_rules = rich_rules_option.map(Value::new); if let Some(rich) = &new_rich_rules { new_policy_config.insert("rich_rules", rich); } // Send the updated configuration back to firewalld. match self.conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1", Some("org.fedoraproject.FirewallD1.policy"), "setPolicySettings", &(PORTPOLICYNAME, new_policy_config), ) { Ok(_) => info!( "Successfully added port-forwarding rules for container {}", teardown_pf.config.container_id ), Err(e) => { return Err(NetavarkError::wrap( format!( "Failed to update policy {} to remove container {} port forwarding rules", PORTPOLICYNAME, teardown_pf.config.container_id ), e.into(), )) } }; Ok(()) } } /// Create a firewalld zone to hold all our interfaces. fn create_zone_if_not_exist(conn: &Connection, zone_name: &str) -> NetavarkResult { debug!("Creating firewall zone {}", zone_name); // First, double-check if the zone exists in the running config. let zones_msg = conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1", Some("org.fedoraproject.FirewallD1.zone"), "getZones", &(), )?; let zones: Vec<&str> = match zones_msg.body() { Ok(b) => b, Err(e) => { return Err(NetavarkError::wrap( "Error decoding DBus message for active zones", e.into(), )) } }; for &zone in zones.iter() { if zone == zone_name { debug!("Zone exists and is running"); return Ok(false); } } // Zone is not in running config - check permanent config. let perm_zones_msg = conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1/config", Some("org.fedoraproject.FirewallD1.config"), "getZoneNames", &(), )?; let zones_perm: Vec<&str> = match perm_zones_msg.body() { Ok(b) => b, Err(e) => { return Err(NetavarkError::wrap( "Error decoding DBus message for permanent zones", e.into(), )) } }; for &zone in zones_perm.iter() { if zone == zone_name { debug!("Zone exists and is not running"); return Ok(true); } } // We can probably avoid the permanent zones check about if we create // unconditionally and parse error strings to look for "duplicate name" // errors - but I really don't want to deal with matching error strings and // the complexities that could entail. // TODO: We can add a description to the zone, should do that. let _ = conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1/config", Some("org.fedoraproject.FirewallD1.config"), "addZone2", &(zone_name, HashMap::<&str, &Value>::new()), )?; Ok(true) } /// Add source subnets to the zone. pub fn add_source_subnets_to_zone( conn: &Connection, zone_name: &str, subnets: &[ipnet::IpNet], ) -> NetavarkResult<()> { for net in subnets { // Check if subnet already exists in zone let subnet_zone = conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1", Some("org.fedoraproject.FirewallD1.zone"), "getZoneOfSource", &(net.to_string()), )?; let zone_string: String = match subnet_zone.body() { Ok(s) => s, Err(e) => { return Err(NetavarkError::wrap( "Error decoding DBus message for zone of subnet", e.into(), )) } }; if zone_string == zone_name { debug!("Subnet {} already exists in zone {}", net, zone_name); return Ok(()); } debug!("Adding subnet {} to zone {} as source", net, zone_name); let _ = conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1", Some("org.fedoraproject.FirewallD1.zone"), "changeZoneOfSource", &(zone_name, net.to_string()), )?; } Ok(()) } /// Add a policy object for the zone to handle masquerading. fn add_policy_if_not_exist( conn: &Connection, policy_name: &str, ingress_zone_name: &str, target: &str, masquerade: bool, ) -> NetavarkResult { debug!( "Adding firewalld policy {} (ingress zone {}, egress zone ANY)", policy_name, ingress_zone_name ); // Does policy exist in running policies? let policies_msg = conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1", Some("org.fedoraproject.FirewallD1.policy"), "getPolicies", &(), )?; let policies: Vec<&str> = match policies_msg.body() { Ok(v) => v, Err(e) => { return Err(NetavarkError::wrap( "Error decoding policy list response", e.into(), )) } }; for &policy in policies.iter() { if policy == policy_name { debug!("Policy exists and is running"); return Ok(false); } } // Does the policy exist in permanent policies? let perm_policies_msg = conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1/config", Some("org.fedoraproject.FirewallD1.config"), "getPolicyNames", &(), )?; let perm_policies: Vec<&str> = match perm_policies_msg.body() { Ok(v) => v, Err(e) => { return Err(NetavarkError::wrap( "Error decoding permanent policy list response", e.into(), )) } }; for &policy in perm_policies.iter() { if policy == policy_name { debug!("Policy exists and is not running"); return Ok(true); } } // Options for the new policy let mut policy_opts = HashMap::<&str, &Value>::new(); let egress_zones = Value::new(Array::from(vec!["ANY"])); let ingress_zones = Value::new(Array::from(vec![ingress_zone_name])); policy_opts.insert("egress_zones", &egress_zones); policy_opts.insert("ingress_zones", &ingress_zones); let masquerade_bool = Value::new(true); if masquerade { policy_opts.insert("masquerade", &masquerade_bool); } let target = Value::new(target); policy_opts.insert("target", &target); // Policy does not exist, create it. // Returns object path, which we don't need. let _ = conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1/config", Some("org.fedoraproject.FirewallD1.config"), "addPolicy", &(policy_name, &policy_opts), )?; Ok(true) } /// Make a port-forward tuple for firewalld /// Port forward rules are a 4-tuple of: /// (port, protocol, to-port, to-addr) /// Port, to-port can be ranges (separated via hyphen) /// Also accepts IP address to forward to. fn make_port_tuple(port: &PortMapping, addr: &str) -> (String, String, String, String) { if port.range > 1 { // Subtract 1 as these are 1-indexed strings - range of 2 is 1000-1001 let end_host_range = port.host_port + port.range - 1; let end_ctr_range = port.container_port + port.range - 1; ( format!("{}-{}", port.host_port, end_host_range), port.protocol.clone(), format!("{}-{}", port.container_port, end_ctr_range), addr.to_string(), ) } else { let to_return = ( format!("{}", port.host_port), port.protocol.clone(), format!("{}", port.container_port), addr.to_string(), ); debug!("Port is {:?}", to_return); to_return } } containers-netavark-83edb4b/src/firewall/fwnone.rs000066400000000000000000000017461452673426700224530ustar00rootroot00000000000000use crate::firewall; use crate::firewall::NetavarkResult; use crate::network::internal_types::{ PortForwardConfig, SetupNetwork, TearDownNetwork, TeardownPortForward, }; // Iptables driver - uses direct iptables commands via the iptables crate. pub struct Fwnone {} pub fn new() -> NetavarkResult> { Ok(Box::new(Fwnone {})) } impl firewall::FirewallDriver for Fwnone { fn driver_name(&self) -> &str { firewall::NONE } fn setup_network(&self, _network_setup: SetupNetwork) -> NetavarkResult<()> { Ok(()) } // teardown_network should only be called in the case of // a complete teardown. fn teardown_network(&self, _tear: TearDownNetwork) -> NetavarkResult<()> { Ok(()) } fn setup_port_forward(&self, _setup_portfw: PortForwardConfig) -> NetavarkResult<()> { Ok(()) } fn teardown_port_forward(&self, _tear: TeardownPortForward) -> NetavarkResult<()> { Ok(()) } } containers-netavark-83edb4b/src/firewall/iptables.rs000066400000000000000000000217421452673426700227600ustar00rootroot00000000000000use crate::error::{NetavarkError, NetavarkResult}; use crate::firewall; use crate::firewall::firewalld; use crate::firewall::varktables::types::TeardownPolicy::OnComplete; use crate::firewall::varktables::types::{ create_network_chains, get_network_chains, get_port_forwarding_chains, TeardownPolicy, }; use crate::network::internal_types::{ PortForwardConfig, SetupNetwork, TearDownNetwork, TeardownPortForward, }; use iptables; use iptables::IPTables; use log::{debug, warn}; use zbus::blocking::Connection; pub(crate) const MAX_HASH_SIZE: usize = 13; // Iptables driver - uses direct iptables commands via the iptables crate. pub struct IptablesDriver { conn: IPTables, conn6: IPTables, } pub fn new() -> NetavarkResult> { // create an iptables connection let ipt = match iptables::new(false) { Ok(i) => i, Err(e) => return Err(NetavarkError::Message(format!("iptables: {e}"))), }; let ipt6 = match iptables::new(true) { Ok(i) => i, Err(e) => return Err(NetavarkError::Message(format!("ip6tables: {e}"))), }; let driver = IptablesDriver { conn: ipt, conn6: ipt6, }; Ok(Box::new(driver)) } impl firewall::FirewallDriver for IptablesDriver { fn driver_name(&self) -> &str { firewall::IPTABLES } fn setup_network(&self, network_setup: SetupNetwork) -> NetavarkResult<()> { if let Some(subnet) = network_setup.subnets { for network in subnet { let is_ipv6 = network.network().is_ipv6(); let mut conn = &self.conn; if is_ipv6 { conn = &self.conn6; } let chains = get_network_chains( conn, network, &network_setup.network_hash_name, is_ipv6, network_setup.bridge_name.clone(), network_setup.isolation, network_setup.dns_port, ); create_network_chains(chains)?; add_firewalld_if_possible(&network); } } Ok(()) } // teardown_network should only be called in the case of // a complete teardown. fn teardown_network(&self, tear: TearDownNetwork) -> NetavarkResult<()> { // Remove network specific general NAT rules if let Some(subnet) = tear.config.subnets { for network in subnet { let is_ipv6 = network.network().is_ipv6(); let mut conn = &self.conn; if is_ipv6 { conn = &self.conn6; } let chains = get_network_chains( conn, network, &tear.config.network_hash_name, is_ipv6, tear.config.bridge_name.clone(), tear.config.isolation, tear.config.dns_port, ); for c in &chains { c.remove_rules(tear.complete_teardown)?; } for c in chains { match &c.td_policy { None => {} Some(policy) => { if tear.complete_teardown && *policy == OnComplete { c.remove()?; } } } } if tear.complete_teardown { rm_firewalld_if_possible(&network) } } } Result::Ok(()) } fn setup_port_forward(&self, setup_portfw: PortForwardConfig) -> NetavarkResult<()> { if let Some(v4) = setup_portfw.container_ip_v4 { let subnet_v4 = match setup_portfw.subnet_v4 { Some(s) => s, None => { return Err(std::io::Error::new( std::io::ErrorKind::Other, "ipv4 address but provided but no v4 subnet provided", ) .into()) } }; let chains = get_port_forwarding_chains(&self.conn, &setup_portfw, &v4, &subnet_v4, false)?; create_network_chains(chains)?; } if let Some(v6) = setup_portfw.container_ip_v6 { let subnet_v6 = match setup_portfw.subnet_v6 { Some(s) => s, None => { return Err(std::io::Error::new( std::io::ErrorKind::Other, "ipv6 address but provided but no v6 subnet provided", ) .into()) } }; let chains = get_port_forwarding_chains(&self.conn6, &setup_portfw, &v6, &subnet_v6, true)?; create_network_chains(chains)?; }; Result::Ok(()) } fn teardown_port_forward(&self, tear: TeardownPortForward) -> NetavarkResult<()> { if let Some(v4) = tear.config.container_ip_v4 { let subnet_v4 = match tear.config.subnet_v4 { Some(s) => s, None => { return Err(std::io::Error::new( std::io::ErrorKind::Other, "ipv4 address but provided but no v4 subnet provided", ) .into()) } }; let chains = get_port_forwarding_chains(&self.conn, &tear.config, &v4, &subnet_v4, false)?; for chain in &chains { chain.remove_rules(tear.complete_teardown)?; } for chain in &chains { if !tear.complete_teardown || !chain.create { continue; } match &chain.td_policy { None => {} Some(policy) => { if *policy == TeardownPolicy::OnComplete { chain.remove()?; } } } } } if let Some(v6) = tear.config.container_ip_v6 { let subnet_v6 = match tear.config.subnet_v6 { Some(s) => s, None => { return Err(std::io::Error::new( std::io::ErrorKind::Other, "ipv6 address but provided but no v6 subnet provided", ) .into()) } }; let chains = get_port_forwarding_chains(&self.conn6, &tear.config, &v6, &subnet_v6, true)?; for chain in &chains { chain.remove_rules(tear.complete_teardown)?; } for chain in &chains { if !tear.complete_teardown || !chain.create { continue; } match &chain.td_policy { None => {} Some(policy) => { if *policy == TeardownPolicy::OnComplete { chain.remove()?; } } } } } Result::Ok(()) } } /// Check if firewalld is running fn is_firewalld_running(conn: &Connection) -> bool { conn.call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetNameOwner", &"org.fedoraproject.FirewallD1", ) .is_ok() } /// If possible, add a firewalld rule to allow traffic. /// Ignore all errors, beyond possibly logging them. fn add_firewalld_if_possible(net: &ipnet::IpNet) { let conn = match Connection::system() { Ok(conn) => conn, Err(_) => return, }; if !is_firewalld_running(&conn) { return; } debug!("Adding firewalld rules for network {}", net.to_string()); match firewalld::add_source_subnets_to_zone(&conn, "trusted", &[*net]) { Ok(_) => {} Err(e) => warn!( "Error adding subnet {} from firewalld trusted zone: {}", net.to_string(), e ), } } // If possible, remove a firewalld rule to allow traffic. // Ignore all errors, beyond possibly logging them. fn rm_firewalld_if_possible(net: &ipnet::IpNet) { let conn = match Connection::system() { Ok(conn) => conn, Err(_) => return, }; if !is_firewalld_running(&conn) { return; } debug!("Removing firewalld rules for IPs {}", net.to_string()); match conn.call_method( Some("org.fedoraproject.FirewallD1"), "/org/fedoraproject/FirewallD1", Some("org.fedoraproject.FirewallD1.zone"), "removeSource", &("trusted", net.to_string()), ) { Ok(_) => {} Err(e) => warn!( "Error removing subnet {} from firewalld trusted zone: {}", net.to_string(), e ), }; } containers-netavark-83edb4b/src/firewall/mod.rs000066400000000000000000000103401452673426700217240ustar00rootroot00000000000000use crate::error::{NetavarkError, NetavarkResult}; use crate::network::internal_types::{ PortForwardConfig, SetupNetwork, TearDownNetwork, TeardownPortForward, }; use log::{debug, info}; use std::env; use zbus::blocking::Connection; pub mod firewalld; pub mod fwnone; pub mod iptables; pub mod state; mod varktables; const IPTABLES: &str = "iptables"; const FIREWALLD: &str = "firewalld"; const NFTABLES: &str = "nftables"; const NONE: &str = "none"; /// Firewall drivers have the ability to set up per-network firewall forwarding /// and port mappings. pub trait FirewallDriver { /// Set up firewall rules for the given network, fn setup_network(&self, network_setup: SetupNetwork) -> NetavarkResult<()>; /// Tear down firewall rules for the given network. fn teardown_network(&self, tear: TearDownNetwork) -> NetavarkResult<()>; /// Set up port-forwarding firewall rules for a given container. fn setup_port_forward(&self, setup_pw: PortForwardConfig) -> NetavarkResult<()>; /// Tear down port-forwarding firewall rules for a single container. fn teardown_port_forward(&self, teardown_pf: TeardownPortForward) -> NetavarkResult<()>; /// Return the name of the driver. fn driver_name(&self) -> &str; } /// Types of firewall backend enum FirewallImpl { Iptables, Firewalld(Connection), Nftables, Fwnone, } /// What firewall implementations does this system support? fn get_firewall_impl(driver_name: Option) -> NetavarkResult { // It respects "firewalld", "iptables", "nftables", "none". // If not requested lookup in NETAVARK_FW env var as well. let driver = driver_name.or_else(|| env::var("NETAVARK_FW").ok()); if let Some(var) = driver { debug!("Forcibly using firewall driver {}", var); match var.to_lowercase().as_str() { FIREWALLD => { let conn = match Connection::system() { Ok(c) => c, Err(e) => { return Err(NetavarkError::wrap( "Error retrieving dbus connection for requested firewall backend", e.into(), )) } }; return Ok(FirewallImpl::Firewalld(conn)); } IPTABLES => return Ok(FirewallImpl::Iptables), NFTABLES => return Ok(FirewallImpl::Nftables), NONE => return Ok(FirewallImpl::Fwnone), any => { return Err(NetavarkError::Message(format!( "Must provide a valid firewall backend, got {any}" ))) } } } // Until firewalld 1.1.0 with support for self-port forwarding lands: // Just use iptables Ok(FirewallImpl::Iptables) // Is firewalld running? // let conn = match Connection::system() { // Ok(conn) => conn, // Err(_) => return FirewallImpl::Iptables, // }; // match conn.call_method( // Some("org.freedesktop.DBus"), // "/org/freedesktop/DBus", // Some("org.freedesktop.DBus"), // "GetNameOwner", // &"org.fedoraproject.FirewallD1", // ) { // Ok(_) => FirewallImpl::Firewalld(conn), // Err(_) => FirewallImpl::Iptables, // } } /// Get the preferred firewall implementation for the current system /// configuration. pub fn get_supported_firewall_driver( driver_name: Option, ) -> NetavarkResult> { match get_firewall_impl(driver_name) { Ok(fw) => match fw { FirewallImpl::Iptables => { info!("Using iptables firewall driver"); iptables::new() } FirewallImpl::Firewalld(conn) => { info!("Using firewalld firewall driver"); firewalld::new(conn) } FirewallImpl::Nftables => { info!("Using nftables firewall driver"); Err(NetavarkError::msg( "nftables support presently not available", )) } FirewallImpl::Fwnone => { info!("Not using firewall"); fwnone::new() } }, Err(e) => Err(e), } } containers-netavark-83edb4b/src/firewall/state.rs000066400000000000000000000275351452673426700223030ustar00rootroot00000000000000use std::{ fs::{self, File, OpenOptions}, io::{self, ErrorKind, Write}, path::{Path, PathBuf}, }; use fs2::FileExt; use serde::de::DeserializeOwned; use crate::{ error::{NetavarkError, NetavarkResult}, network::internal_types::{PortForwardConfig, PortForwardConfigOwned, SetupNetwork}, wrap, }; /// File layout looks like this /// $config/firewall/ /// - firewall-driver -> name of the firewall driver /// - networks/$netID -> network config setup /// - ports/$netID_$conID -> port config const FIREWALL_DIR: &str = "firewall"; const FIREWALL_DRIVER_FILE: &str = "firewall-driver"; const FIREWALL_LOCK_FILE: &str = "firewall-reload.lock"; const NETWORK_CONF_DIR: &str = "networks"; const PORT_CONF_DIR: &str = "ports"; struct FilePaths { fw_driver_file: PathBuf, net_conf_file: PathBuf, port_conf_file: PathBuf, /// The file is returned locked, it does not need /// to be unlocked as rust does it automatically on drop. /// This file is required to ensure that remove_fw_config is not racing against /// the firewall reload service, i.e. without it would be possible that we read /// the config files and then during re-adding the rules the file got removed. /// This leaves a chance that the service will add rules that should not be added /// anymore. lock_file: File, } /// macro to quickly wrap the IO error with useful context /// First argument is the function, second the path, third the extra error message. /// The full error is "$msg $path: $org_error" macro_rules! fs_err { ($func:expr, $path:expr, $msg:expr) => { $func($path).map_err(|err| { NetavarkError::wrap(format!("{} {:?}", $msg, $path.display()), err.into()) }) }; } macro_rules! ignore_enoent { ($call:expr, $action:expr) => { match $call { Ok(ok) => Ok(ok), Err(err) if err.kind() == std::io::ErrorKind::NotFound => $action, Err(e) => Err(e), } }; } fn remove_file_ignore_enoent>(path: P) -> io::Result<()> { match fs::remove_file(path) { Ok(ok) => Ok(ok), Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()), Err(e) => Err(e), } } fn firewall_config_dir(config_dir: &Path) -> PathBuf { Path::new(config_dir).join(FIREWALL_DIR) } /// Assemble file paths for the config files, when create_dirs is set to true /// it will create the parent dirs as well so the caller does not have to. /// /// As a special case when network_id and container_id is empty it will return /// the paths for the directories instead which are used to walk the dir for all configs. fn get_file_paths( config_dir: &Path, network_id: &str, container_id: &str, create_dirs: bool, ) -> NetavarkResult { let path = firewall_config_dir(config_dir); let fw_driver_file = path.join(FIREWALL_DRIVER_FILE); let mut net_conf_file = path.join(NETWORK_CONF_DIR); let mut port_conf_file = path.join(PORT_CONF_DIR); // we need to always create this for the lockfile fs_err!(fs::create_dir_all, &path, "create firewall config dir")?; if create_dirs { fs_err!( fs::create_dir_all, &net_conf_file, "create network config dir" )?; fs_err!( fs::create_dir_all, &port_conf_file, "create port config dir" )?; } if !network_id.is_empty() && !container_id.is_empty() { net_conf_file.push(network_id); port_conf_file.push(network_id.to_string() + "_" + container_id); } let lock_file = fs_err!( File::create, &path.join(FIREWALL_LOCK_FILE), "create firewall lock file" )?; wrap!(lock_file.lock_exclusive(), "lock firewall lock file")?; Ok(FilePaths { fw_driver_file, net_conf_file, port_conf_file, lock_file, }) } /// Store the firewall configs on disk. /// This should be caller after firewall setup to allow the firewalld reload /// service to read the configs later and readd the rules. pub fn write_fw_config( config_dir: &Path, network_id: &str, container_id: &str, fw_driver: &str, net_conf: &SetupNetwork, port_conf: &PortForwardConfig, ) -> NetavarkResult<()> { let paths = get_file_paths(config_dir, network_id, container_id, true)?; fs_err!( File::create, &paths.fw_driver_file, "create firewall-driver file" )? .write_all(fw_driver.as_bytes()) .map_err(|err| NetavarkError::wrap("failed to write firewall-driver file", err.into()))?; match OpenOptions::new() .write(true) .create_new(true) .open(&paths.net_conf_file) { Ok(f) => serde_json::to_writer(f, &net_conf)?, // net config file already exists no need to write the same stuff again. Err(ref e) if e.kind() == ErrorKind::AlreadyExists => (), Err(e) => { return Err(NetavarkError::wrap( format!("create network config {:?}", &paths.net_conf_file.display()), e.into(), )); } }; let ports_file = fs_err!(File::create, &paths.port_conf_file, "create port config")?; serde_json::to_writer(ports_file, &port_conf)?; Ok(()) } /// Remove firewall config files. /// On firewall teardown remove the specific config files again so the /// firewalld reload service does not keep using them. pub fn remove_fw_config( config_dir: &Path, network_id: &str, container_id: &str, complete_teardown: bool, ) -> NetavarkResult<()> { let paths = get_file_paths(config_dir, network_id, container_id, false)?; fs_err!( remove_file_ignore_enoent, &paths.port_conf_file, "remove port config" )?; if complete_teardown { fs_err!( remove_file_ignore_enoent, &paths.net_conf_file, "remove network config" )?; } Ok(()) } pub struct FirewallConfig { /// Name of the firewall driver pub driver: String, /// All the network firewall configs pub net_confs: Vec, /// All port forwarding configs pub port_confs: Vec, /// Lock file for the firewall code to prevent us from adding rules while the state files /// have been removed in the meantime. /// We never do anything with it but we need to keep it open as closing it closes the lock /// So once this struct is dropped the lock is closed automatically. #[allow(dead_code)] lock_file: File, } /// Read all firewall configs files from the dir. pub fn read_fw_config(config_dir: &Path) -> NetavarkResult> { let paths = get_file_paths(config_dir, "", "", false)?; // now it is possible the firewall-reload is started before any containers were started so we just // return None in this case. let driver = wrap!( ignore_enoent!(fs::read_to_string(&paths.fw_driver_file), return Ok(None)), format!("read firewall-driver {:?}", &paths.fw_driver_file.display()) )?; let net_confs = read_dir_conf(paths.net_conf_file)?; let port_confs = read_dir_conf(paths.port_conf_file)?; Ok(Some(FirewallConfig { driver, net_confs, port_confs, lock_file: paths.lock_file, })) } fn read_dir_conf(dir: PathBuf) -> NetavarkResult> { let mut confs = Vec::new(); for entry in fs_err!(fs::read_dir, &dir, "read dir")? { let path = ignore_enoent!(entry, continue)?.path(); let content = wrap!( ignore_enoent!(fs::read_to_string(&path), continue), format!("read config {:?}", path.display()) )?; // Note one might think we should use from_reader() instead of reading // into one string. However the files we act on are small enough that it // should't matter to have the content into memory at once and based on // https://github.com/serde-rs/json/issues/160 this here is much faster. let conf: T = serde_json::from_str(&content)?; confs.push(conf); } Ok(confs) } #[cfg(test)] mod tests { use std::net::{IpAddr, Ipv4Addr}; use crate::network::internal_types::IsolateOption; use super::*; use tempfile::Builder; #[test] fn test_fw_config() { let network_id = "abc"; let container_id = "123"; let driver = "iptables"; let tmpdir = Builder::new().prefix("netavark-tests").tempdir().unwrap(); let config_dir = tmpdir.path(); let net_conf = SetupNetwork { subnets: Some(vec!["10.0.0.0/24".parse().unwrap()]), bridge_name: "bridge".to_string(), network_hash_name: "hash".to_string(), isolation: IsolateOption::Never, dns_port: 53, }; let net_conf_json = r#"{"subnets":["10.0.0.0/24"],"bridge_name":"bridge","network_hash_name":"hash","isolation":"Never","dns_port":53}"#; let port_conf = PortForwardConfig { container_id: container_id.to_string(), port_mappings: &None, network_name: "name".to_string(), network_hash_name: "hash".to_string(), container_ip_v4: Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2))), subnet_v4: Some("10.0.0.0/24".parse().unwrap()), container_ip_v6: None, subnet_v6: None, dns_port: 53, dns_server_ips: &vec![], }; let port_conf_json = r#"{"container_id":"123","port_mappings":null,"network_name":"name","network_hash_name":"hash","container_ip_v4":"10.0.0.2","subnet_v4":"10.0.0.0/24","container_ip_v6":null,"subnet_v6":null,"dns_port":53,"dns_server_ips":[]}"#; let res = write_fw_config( config_dir, network_id, container_id, driver, &net_conf, &port_conf, ); assert!(res.is_ok(), "write_fw_config failed"); let paths = get_file_paths(config_dir, network_id, container_id, false).unwrap(); drop(paths.lock_file); // unlock to prevent deadlock with other calls let res = fs::read_to_string(paths.fw_driver_file).unwrap(); assert_eq!(res, "iptables", "read fw driver"); let res = fs::read_to_string(&paths.net_conf_file).unwrap(); assert_eq!(res, net_conf_json, "read net conf"); let res = fs::read_to_string(&paths.port_conf_file).unwrap(); assert_eq!(res, port_conf_json, "read port conf"); let res = read_fw_config(config_dir) .unwrap() .expect("no fw config files"); assert_eq!(res.driver, driver, "correct fw driver"); assert_eq!(res.net_confs, vec![net_conf], "same net configs"); let port_confs_ref: Vec = res.port_confs.iter().map(|f| f.into()).collect(); assert_eq!(port_confs_ref, vec![port_conf], "same port configs"); // unlock lock file drop(res); let res = remove_fw_config(config_dir, network_id, container_id, true); assert!(res.is_ok(), "remove_fw_config failed"); assert_eq!( paths.net_conf_file.exists(), false, "net conf should not exists" ); assert_eq!( paths.port_conf_file.exists(), false, "port conf should not exists" ); // now again since we ignore ENOENT it should still return no error let res = remove_fw_config(config_dir, network_id, container_id, true); assert!(res.is_ok(), "remove_fw_config failed second time"); } #[test] fn test_read_fw_config_empty() { let tmpdir = Builder::new().prefix("netavark-tests").tempdir().unwrap(); let config_dir = tmpdir.path(); let res = read_fw_config(config_dir).expect("no read_fw_config error"); assert!(res.is_none(), "no firewall config should be given"); } } containers-netavark-83edb4b/src/firewall/varktables/000077500000000000000000000000001452673426700227375ustar00rootroot00000000000000containers-netavark-83edb4b/src/firewall/varktables/helpers.rs000066400000000000000000000070251452673426700247530ustar00rootroot00000000000000use crate::error::{NetavarkError, NetavarkResult}; use iptables::IPTables; use log::debug; // append a rule to chain if it does not exist // Note: While there is an API provided for this exact thing, the API returns // an error that is not defined if the rule exists. This function just returns // an error if there is a problem. pub fn append_unique( driver: &IPTables, table: &str, chain: &str, rule: &str, ) -> NetavarkResult<()> { let exists = match driver.exists(table, chain, rule) { Ok(b) => b, Err(e) => return Err(NetavarkError::Message(e.to_string())), }; if exists { debug_rule_exists(table, chain, rule.to_string()); return Ok(()); } if let Err(e) = driver .append(table, chain, rule) .map(|_| debug_rule_create(table, chain, rule.to_string())) { return Err(NetavarkError::Message(format!( "unable to append rule '{rule}' to table '{table}': {e}", ))); } Result::Ok(()) } // add a chain if it does not exist, else do nothing pub fn add_chain_unique(driver: &IPTables, table: &str, new_chain: &str) -> NetavarkResult<()> { // Note: while there is an API provided to check if a chain exists in a table // by iptables, it, for some reason, is slow. Instead we just get a list of // chains in a table and iterate. Same is being done in golang implementations let exists = chain_exists(driver, table, new_chain)?; if exists { debug_chain_exists(table, new_chain); return Ok(()); } match driver .new_chain(table, new_chain) .map(|_| debug_chain_create(table, new_chain)) { Ok(_) => Ok(()), Err(e) => Err(NetavarkError::Message(e.to_string())), } } // returns a bool as to whether the chain exists fn chain_exists(driver: &IPTables, table: &str, chain: &str) -> NetavarkResult { let c = match driver.list_chains(table) { Ok(b) => b, Err(e) => return Err(NetavarkError::Message(e.to_string())), }; if c.iter().any(|i| i == chain) { debug_chain_exists(table, chain); return serde::__private::Result::Ok(true); } serde::__private::Result::Ok(false) } pub fn remove_if_rule_exists( driver: &IPTables, table: &str, chain: &str, rule: &str, ) -> NetavarkResult<()> { // If the rule is not present, do not error let exists = match driver.exists(table, chain, rule) { Ok(b) => b, Err(e) => return Err(NetavarkError::Message(e.to_string())), }; if !exists { debug_rule_no_exists(table, chain, rule.to_string()); return Ok(()); } if let Err(e) = driver.delete(table, chain, rule) { return Err(NetavarkError::Message(format!( "failed to remove rule '{rule}' from table '{chain}': {e}" ))); } Result::Ok(()) } fn debug_chain_create(table: &str, chain: &str) { debug!("chain {} created on table {}", chain, table); } fn debug_chain_exists(table: &str, chain: &str) { debug!("chain {} exists on table {}", chain, table); } pub fn debug_rule_create(table: &str, chain: &str, rule: String) { debug!( "rule {} created on table {} and chain {}", rule, table, chain ); } fn debug_rule_exists(table: &str, chain: &str, rule: String) { debug!( "rule {} exists on table {} and chain {}", rule, table, chain ); } fn debug_rule_no_exists(table: &str, chain: &str, rule: String) { debug!( "no rule {} exists on table {} and chain {}", rule, table, chain ); } containers-netavark-83edb4b/src/firewall/varktables/mod.rs000066400000000000000000000000561452673426700240650ustar00rootroot00000000000000pub(crate) mod helpers; pub(crate) mod types; containers-netavark-83edb4b/src/firewall/varktables/types.rs000066400000000000000000000550401452673426700244550ustar00rootroot00000000000000use crate::error::{NetavarkError, NetavarkResult}; use crate::firewall::varktables::helpers::{ add_chain_unique, append_unique, remove_if_rule_exists, }; use crate::firewall::varktables::types::TeardownPolicy::{Never, OnComplete}; use crate::network::internal_types::{IsolateOption, PortForwardConfig}; use ipnet::IpNet; use iptables::IPTables; use log::debug; use std::net::IpAddr; // Chain names const NAT: &str = "nat"; const FILTER: &str = "filter"; const POSTROUTING: &str = "POSTROUTING"; const PREROUTING: &str = "PREROUTING"; const NETAVARK_FORWARD: &str = "NETAVARK_FORWARD"; const NETAVARK_FIREWALL_RULE_BUILDER: &str = "-m comment --comment 'netavark firewall rules' -j "; const NETAVARK_INPUT: &str = "NETAVARK_INPUT"; const OUTPUT: &str = "OUTPUT"; const INPUT: &str = "INPUT"; const FORWARD: &str = "FORWARD"; const ACCEPT: &str = "ACCEPT"; const NETAVARK_HOSTPORT_DNAT: &str = "NETAVARK-HOSTPORT-DNAT"; const NETAVARK_HOSTPORT_SETMARK: &str = "NETAVARK-HOSTPORT-SETMARK"; const NETAVARK_HOSTPORT_MASK: &str = "NETAVARK-HOSTPORT-MASQ"; const MASQUERADE: &str = "MASQUERADE"; const MARK: &str = "MARK"; const DNAT: &str = "DNAT"; const NETAVARK_ISOLATION_1: &str = "NETAVARK_ISOLATION_1"; const NETAVARK_ISOLATION_2: &str = "NETAVARK_ISOLATION_2"; const NETAVARK_ISOLATION_3: &str = "NETAVARK_ISOLATION_3"; const CONTAINER_DN_CHAIN: &str = "NETAVARK-DN-"; const HEXMARK: &str = "0x2000"; const MULTICAST_NET_V4: &str = "224.0.0.0/4"; const MULTICAST_NET_V6: &str = "ff00::/8"; #[derive(PartialEq, Eq, Debug, Clone)] pub enum TeardownPolicy { OnComplete, Never, } #[derive(Clone, Debug)] pub struct VarkRule { // Formatted string of the rule itself pub rule: String, pub td_policy: Option, /// position can be set to specify the exact rule position, /// if None the rule will be appended. pub position: Option, } impl VarkRule { fn new(rule: String, policy: Option) -> VarkRule { VarkRule { rule, td_policy: policy, position: None, } } fn to_str(&self) -> &str { &self.rule } } // Varkchain is an iptable chain with extra info pub struct VarkChain<'a> { // name of chain pub chain_name: String, // should the chain be created by us pub create: bool, // the connection to iptables, v4 or v6 pub driver: &'a IPTables, // an array of iptables rules to be added to the chain pub rules: Vec, // name of table pub table: String, // if the chain should be removed pub td_policy: Option, } impl<'a> VarkChain<'a> { fn new( driver: &IPTables, table: String, chain_name: String, td_policy: Option, ) -> VarkChain { VarkChain { driver, chain_name, table, rules: vec![], create: false, td_policy, } } // create a queue of rules in a vector fn build_rule(&mut self, rule: VarkRule) { self.rules.push(rule) } // actually add the rules to iptables pub fn add_rules(&self) -> NetavarkResult<()> { for rule in &self.rules { // If the rule comes with an optional position, then instead of append // we should use insert if it does not already exist match rule.position { None => { append_unique(self.driver, &self.table, &self.chain_name, rule.to_str())?; } Some(pos) => { let exists = match self .driver .exists(&self.table, &self.chain_name, &rule.rule) { Ok(b) => b, Err(e) => return Err(NetavarkError::Message(e.to_string())), }; if !exists { match self .driver .insert(&self.table, &self.chain_name, &rule.rule, pos) { Ok(_) => {} Err(e) => return Err(NetavarkError::Message(e.to_string())), }; } } } } Ok(()) } // remove a vector of rules pub fn remove_rules(&self, complete_teardown: bool) -> NetavarkResult<()> { for rule in &self.rules { // If the rule policy is Never or this is not a // complete teardown of the network, then we skip removal // of the rule match &rule.td_policy { None => {} Some(policy) => { if *policy == TeardownPolicy::Never || !complete_teardown { continue; } } } remove_if_rule_exists(self.driver, &self.table, &self.chain_name, rule.to_str())?; } Ok(()) } // remove the chain itself. pub fn remove(&self) -> NetavarkResult<()> { // this might be a perf hit but we are going to start this // way and think of faster AND logical approach. let remaining_rules = match self.driver.list(&self.table, &self.chain_name) { Ok(o) => o, Err(e) => return Err(NetavarkError::Message(e.to_string())), }; // if for some reason there is a rule left, dont remove the chain and // also dont make this a fatal error. The vec returned by list always // reserves [0] for the chain name (-A chain_name), hence the <= 1 if remaining_rules.len() <= 1 { match self.driver.delete_chain(&self.table, &self.chain_name) { Ok(_) => {} Err(e) => return Err(NetavarkError::Message(e.to_string())), }; } Result::Ok(()) } } pub fn create_network_chains(chains: Vec>) -> NetavarkResult<()> { // we have to create first all chains because some might be referenced by other rules // and this will fail if they do not exist yet for c in &chains { // If the chain needs to be created, we make it if c.create { add_chain_unique(c.driver, &c.table, &c.chain_name)?; } } for c in &chains { c.add_rules()? } Ok(()) } pub fn get_network_chains<'a>( conn: &'a IPTables, network: IpNet, network_hash_name: &'a str, is_ipv6: bool, interface_name: String, isolation: IsolateOption, dns_port: u16, ) -> Vec> { let mut chains = Vec::new(); let prefixed_network_hash_name = format!("{}-{}", "NETAVARK", network_hash_name); // NETAVARK-HASH let mut hashed_network_chain = VarkChain::new( conn, NAT.to_string(), prefixed_network_hash_name.clone(), Some(OnComplete), ); hashed_network_chain.create = true; hashed_network_chain.build_rule(VarkRule::new( format!("-d {network} -j {ACCEPT}"), Some(TeardownPolicy::OnComplete), )); let mut multicast_dest = MULTICAST_NET_V4; if is_ipv6 { multicast_dest = MULTICAST_NET_V6; } hashed_network_chain.build_rule(VarkRule::new( format!("! -d {multicast_dest} -j {MASQUERADE}"), Some(TeardownPolicy::OnComplete), )); chains.push(hashed_network_chain); // POSTROUTING let mut postrouting_chain = VarkChain::new(conn, NAT.to_string(), POSTROUTING.to_string(), None); postrouting_chain.build_rule(VarkRule::new( format!("-s {network} -j {prefixed_network_hash_name}"), Some(TeardownPolicy::OnComplete), )); chains.push(postrouting_chain); // FORWARD chain let mut forward_chain: VarkChain<'_> = VarkChain::new(conn, FILTER.to_string(), FORWARD.to_string(), None); // INPUT chain let mut input_chain: VarkChain<'_> = VarkChain::new(conn, FILTER.to_string(), INPUT.to_string(), None); // used to prepend specific rules let mut ind = 1; // NETAVARK_ISOLATION_2 // NETAVARK_ISOLATION_2 chain must always exist, // because non-isolation creates DROP rule in NETAVARK_ISOLATION_3 // and NETAVARK_ISOLATION_3 references this as a jump target. let mut netavark_isolation_chain_2 = VarkChain::new( conn, FILTER.to_string(), NETAVARK_ISOLATION_2.to_string(), None, ); netavark_isolation_chain_2.create = true; // NETAVARK_ISOLATION_3 // NETAVARK_ISOLATION_3 chain must exist when IsolateOption is Never or Strict. // bacause non-isolation creates DROP rule in NETAVARK_ISOLATION_3. // and strict isolation references NETAVARK_ISOLATION_3 as a jump target. let mut netavark_isolation_chain_3 = VarkChain::new( conn, FILTER.to_string(), NETAVARK_ISOLATION_3.to_string(), None, ); netavark_isolation_chain_3.create = true; if let IsolateOption::Nomal | IsolateOption::Strict = isolation { debug!("Add extra isolate rules"); // NETAVARK_ISOLATION_1 let mut netavark_isolation_chain_1 = VarkChain::new( conn, FILTER.to_string(), NETAVARK_ISOLATION_1.to_string(), None, ); netavark_isolation_chain_1.create = true; // -A FORWARD -j NETAVARK_ISOLATION_1 forward_chain.build_rule(VarkRule { rule: format!("-j {NETAVARK_ISOLATION_1}"), position: Some(ind), td_policy: Some(TeardownPolicy::OnComplete), }); let netavark_isolation_1_target = if let IsolateOption::Strict = isolation { // NETAVARK_ISOLATION_1 -i bridge_name ! -o bridge_name -j NETAVARK_ISOLATION_3 NETAVARK_ISOLATION_3 } else { // NETAVARK_ISOLATION_1 -i bridge_name ! -o bridge_name -j NETAVARK_ISOLATION_2 NETAVARK_ISOLATION_2 }; netavark_isolation_chain_1.build_rule(VarkRule { rule: format!( "-i {interface_name} ! -o {interface_name} -j {netavark_isolation_1_target}" ), position: Some(ind), td_policy: Some(TeardownPolicy::OnComplete), }); // NETAVARK_ISOLATION_2 -i bridge_name ! -o bridge_name -j DROP netavark_isolation_chain_2.build_rule(VarkRule { rule: format!("-o {} -j {}", interface_name, "DROP"), position: Some(ind), td_policy: Some(TeardownPolicy::OnComplete), }); // NETAVARK_ISOLATION_3 -j NETAVARK_ISOLATION_2 netavark_isolation_chain_3.build_rule(VarkRule { rule: format!("-j {NETAVARK_ISOLATION_2}"), position: Some(ind), td_policy: Some(TeardownPolicy::Never), }); ind += 1; // PUSH CHAIN chains.push(netavark_isolation_chain_1); } else { // create DROP rule for non-isolations to enforce strict isolation rules. // NETAVARK_ISOLATION_3 -o bridge_name -j DROP netavark_isolation_chain_3.build_rule(VarkRule { rule: format!("-o {} -j {}", interface_name, "DROP"), position: Some(ind), td_policy: Some(TeardownPolicy::OnComplete), }); // NETAVARK_ISOLATION_3 -j NETAVARK_ISOLATION_2 netavark_isolation_chain_3.build_rule(VarkRule { rule: format!("-j {NETAVARK_ISOLATION_2}"), // position +1 to place this rule under all of NETAVARK_ISOLATION_3 DROP rules. position: Some(ind + 1), td_policy: Some(TeardownPolicy::Never), }); } // PUSH CHAIN chains.push(netavark_isolation_chain_2); chains.push(netavark_isolation_chain_3); forward_chain.build_rule(VarkRule { rule: format!("{} {}", NETAVARK_FIREWALL_RULE_BUILDER, NETAVARK_FORWARD), position: Some(ind), td_policy: Some(TeardownPolicy::Never), }); chains.push(forward_chain); // NETAVARK_FORWARD let mut netavark_forward_chain = VarkChain::new(conn, FILTER.to_string(), NETAVARK_FORWARD.to_string(), None); netavark_forward_chain.create = true; // Add NETAVARK_INPUT chain to INPUT chain input_chain.build_rule(VarkRule { rule: format!("{} {}", NETAVARK_FIREWALL_RULE_BUILDER, NETAVARK_INPUT), position: Some(1), td_policy: Some(TeardownPolicy::Never), }); chains.push(input_chain); // NETAVARK_INPUT let mut netavark_input_chain = VarkChain::new(conn, FILTER.to_string(), NETAVARK_INPUT.to_string(), None); netavark_input_chain.create = true; // Always add ACCEPT rules in firewall for dns traffic from containers // to gateway when using bridge network with internal dns. netavark_input_chain.build_rule(VarkRule::new( format!( "-p {} -s {} --dport {} -j {}", "udp", network, dns_port, ACCEPT ), Some(TeardownPolicy::OnComplete), )); chains.push(netavark_input_chain); // Drop all invalid packages, due a race the container source ip could be leaked on the local // network and we should avoid that, https://bugzilla.redhat.com/show_bug.cgi?id=2230144 // This should't harm anything so just add one global rule instead of filtering per subnet. netavark_forward_chain.build_rule(VarkRule::new( "-m conntrack --ctstate INVALID -j DROP".to_string(), Some(TeardownPolicy::Never), )); // Create incoming traffic rule // CNI did this by IP address, this is implemented per subnet netavark_forward_chain.build_rule(VarkRule::new( format!("-d {network} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT"), Some(TeardownPolicy::OnComplete), )); // Create outgoing traffic rule // CNI did this by IP address, this is implemented per subnet netavark_forward_chain.build_rule(VarkRule::new( format!("-s {network} -j ACCEPT"), Some(TeardownPolicy::OnComplete), )); chains.push(netavark_forward_chain); chains } pub fn get_port_forwarding_chains<'a>( conn: &'a IPTables, pfwd: &PortForwardConfig, container_ip: &IpAddr, network_address: &IpNet, is_ipv6: bool, ) -> NetavarkResult>> { let mut localhost_ip = "127.0.0.1"; if is_ipv6 { localhost_ip = "::1"; } let mut chains = Vec::new(); // Set up all chains let network_dn_chain_name = CONTAINER_DN_CHAIN.to_owned() + &pfwd.network_hash_name; let comment_dn_network_cid = format!( "-m comment --comment 'dnat name: {} id: {}'", pfwd.network_name, pfwd.container_id ); // // NETAVARK-HASH // NETAVARK-DN-HASH let mut netavark_hashed_dn_chain = VarkChain::new( conn, NAT.to_string(), CONTAINER_DN_CHAIN.to_string() + &pfwd.network_hash_name, Some(OnComplete), ); // NETAVARK_HOSTPORT_DNAT // We need to create that chain for prerouting/output chain rules // using it, even if there are no port mappings. let mut netavark_hostport_dn_chain = VarkChain::new( conn, NAT.to_string(), NETAVARK_HOSTPORT_DNAT.to_string(), None, ); netavark_hostport_dn_chain.create = true; // Setup one-off rules that have nothing to do with ports // PREROUTING let mut prerouting_chain = VarkChain::new(conn, NAT.to_string(), PREROUTING.to_string(), None); prerouting_chain.build_rule(VarkRule::new( format!("-j {NETAVARK_HOSTPORT_DNAT} -m addrtype --dst-type LOCAL"), Some(TeardownPolicy::Never), )); // OUTPUT let mut output_chain = VarkChain::new(conn, NAT.to_string(), OUTPUT.to_string(), None); output_chain.build_rule(VarkRule::new( format!("-j {NETAVARK_HOSTPORT_DNAT} -m addrtype --dst-type LOCAL"), Some(TeardownPolicy::Never), )); // NETAVARK-HOSTPORT-SETMARK let mut netavark_hostport_setmark = VarkChain::new( conn, NAT.to_string(), NETAVARK_HOSTPORT_SETMARK.to_string(), None, ); netavark_hostport_setmark.create = true; netavark_hostport_setmark.build_rule(VarkRule::new( format!("-j {MARK} --set-xmark {HEXMARK}/{HEXMARK}"), Some(TeardownPolicy::Never), )); chains.push(netavark_hostport_setmark); // NETAVARK-HOSTPORT-MASQ let mut netavark_hostport_masq_chain = VarkChain::new( conn, NAT.to_string(), NETAVARK_HOSTPORT_MASK.to_string(), None, ); netavark_hostport_masq_chain.create = true; netavark_hostport_masq_chain.build_rule(VarkRule::new( format!( "-j {MASQUERADE} -m comment --comment 'netavark portfw masq mark' -m mark --mark {HEXMARK}/{HEXMARK}" ), Some(TeardownPolicy::Never), )); netavark_hostport_masq_chain.create = true; chains.push(netavark_hostport_masq_chain); // POSTROUTING let mut postrouting = VarkChain::new(conn, NAT.to_string(), POSTROUTING.to_string(), None); // This rule must be in the first position postrouting.build_rule(VarkRule { rule: format!("-j {NETAVARK_HOSTPORT_MASK} "), position: Some(1), td_policy: Some(Never), }); chains.push(postrouting); // Determine if we need to create chains if pfwd.port_mappings.is_some() { netavark_hashed_dn_chain.create = true; } // Create redirection for aardvark-dns on non-standard port if pfwd.dns_port != 53 { for dns_ip in pfwd.dns_server_ips { if is_ipv6 != dns_ip.is_ipv6() { continue; } let mut ip_value = dns_ip.to_string(); if is_ipv6 { ip_value = format!("[{ip_value}]") } netavark_hostport_dn_chain.create = true; netavark_hostport_dn_chain.build_rule(VarkRule::new( format!( "-j {} -d {} -p {} --dport {} --to-destination {}:{}", DNAT, dns_ip, "udp", 53, ip_value, pfwd.dns_port ), Some(TeardownPolicy::OnComplete), )); } } match pfwd.port_mappings { Some(ports) => { for i in ports { let host_ip = if i.host_ip.is_empty() { None } else { match i.host_ip.parse() { Ok(ip) => match ip { IpAddr::V4(v4) => { if is_ipv6 { continue; } if !v4.is_unspecified() { Some(IpAddr::V4(v4)) } else { None } } IpAddr::V6(v6) => { if !is_ipv6 { continue; } if !v6.is_unspecified() { Some(IpAddr::V6(v6)) } else { None } } }, Err(_) => { return Err(NetavarkError::msg(format!( "invalid host ip \"{}\" provided for port {}", i.host_ip, i.host_port, ))); } } }; // hostport dnat let is_range = i.range > 1; let mut host_port = i.host_port.to_string(); if is_range { host_port = format!("{}:{}", i.host_port, (i.host_port + (i.range - 1))) } netavark_hostport_dn_chain.build_rule(VarkRule::new( format!( // I'm leaving this commented code for now in the case // we need to revert. // "-j {} -p {} -m multiport --destination-ports {} {}", "-j {} -p {} --dport {} {}", network_dn_chain_name, i.protocol, &host_port, comment_dn_network_cid ), None, )); let mut dn_setmark_rule_localhost = format!( "-j {} -s {} -p {} --dport {}", NETAVARK_HOSTPORT_SETMARK, network_address, i.protocol, &host_port ); let mut dn_setmark_rule_subnet = format!( "-j {} -s {} -p {} --dport {}", NETAVARK_HOSTPORT_SETMARK, localhost_ip, i.protocol, &host_port ); // if a destination ip address is provided, we need to alter // the rule a bit if let Some(host_ip) = host_ip { dn_setmark_rule_localhost = format!("{dn_setmark_rule_localhost} -d {host_ip}"); dn_setmark_rule_subnet = format!("{dn_setmark_rule_subnet} -d {host_ip}"); } // dn container (the actual port usages) netavark_hashed_dn_chain.build_rule(VarkRule::new(dn_setmark_rule_localhost, None)); netavark_hashed_dn_chain.build_rule(VarkRule::new(dn_setmark_rule_subnet, None)); let mut container_ip_value = container_ip.to_string(); if is_ipv6 { container_ip_value = format!("[{container_ip_value}]") } let mut container_port = i.container_port.to_string(); if is_range { container_port = format!( "{}-{}/{}", i.container_port, (i.container_port + (i.range - 1)), i.host_port ); } let mut dnat_rule = format!( "-j {} -p {} --to-destination {}:{} --destination-port {}", DNAT, i.protocol, container_ip_value, container_port, &host_port ); // if a destination ip address is provided, we need to alter // the rule a bit if let Some(host_ip) = host_ip { dnat_rule = format!("{dnat_rule} -d {host_ip}") } netavark_hashed_dn_chain.build_rule(VarkRule::new(dnat_rule, None)); } } None => {} }; // The order is important here. Be certain before changing it chains.push(netavark_hashed_dn_chain); chains.push(netavark_hostport_dn_chain); chains.push(prerouting_chain); chains.push(output_chain); Ok(chains) } containers-netavark-83edb4b/src/lib.rs000066400000000000000000000002601452673426700201060ustar00rootroot00000000000000#[macro_use] extern crate serde; extern crate serde_json; pub mod commands; pub mod dhcp_proxy; pub mod dns; pub mod error; pub mod firewall; pub mod network; pub mod plugin; containers-netavark-83edb4b/src/main.rs000066400000000000000000000057261452673426700203000ustar00rootroot00000000000000use std::ffi::OsString; use clap::{Parser, Subcommand}; use netavark::commands::dhcp_proxy; use netavark::commands::firewalld_reload; use netavark::commands::setup; use netavark::commands::teardown; use netavark::commands::update; use netavark::commands::version; #[derive(Parser, Debug)] #[clap(version = env!("CARGO_PKG_VERSION"))] struct Opts { /// Instead of reading from STDIN, read the configuration to be applied from the given file. #[clap(short, long)] file: Option, /// config directory for aardvark, usually path to a tmpfs. #[clap(short, long)] config: Option, /// Tells if current netavark invocation is for rootless container. #[clap(short, long)] rootless: Option, #[clap(short, long)] /// Path to the aardvark-dns binary. aardvark_binary: Option, /// Path to netavark plugin directories, can be set multiple times to specify more than one directory. #[clap(long, long = "plugin-directory")] plugin_directories: Option>, /// Netavark trig command #[clap(subcommand)] subcmd: SubCommand, } #[derive(Subcommand, Debug)] enum SubCommand { /// Configures the given network namespace with the given configuration. Setup(setup::Setup), /// Updates network dns servers for an already configured network. Update(update::Update), /// Undo any configuration applied via setup command. Teardown(teardown::Teardown), /// Display info about netavark. Version(version::Version), /// Start dhcp-proxy DHCPProxy(dhcp_proxy::Opts), /// Listen for the firewalld reload event and reload fw rules #[command(name = "firewalld-reload")] FirewallDReload, } fn main() { env_logger::builder().format_timestamp(None).init(); let opts = Opts::parse(); // aardvark config directory must be supplied by parent or it defaults to /tmp/aardvark let config = opts.config; let rootless = opts.rootless.unwrap_or(false); let aardvark_bin = opts .aardvark_binary .unwrap_or_else(|| OsString::from("/usr/libexec/podman/aardvark-dns")); let result = match opts.subcmd { SubCommand::Setup(setup) => setup.exec( opts.file, config, aardvark_bin, opts.plugin_directories, rootless, ), SubCommand::Teardown(teardown) => teardown.exec( opts.file, config, aardvark_bin, opts.plugin_directories, rootless, ), SubCommand::Update(mut update) => update.exec(config, aardvark_bin, rootless), SubCommand::Version(version) => version.exec(), SubCommand::DHCPProxy(proxy) => dhcp_proxy::serve(proxy), SubCommand::FirewallDReload => firewalld_reload::listen(config), }; match result { Ok(_) => {} Err(err) => { err.print_json(); std::process::exit(err.get_exit_code()); } } } #[cfg(test)] mod test; containers-netavark-83edb4b/src/network/000077500000000000000000000000001452673426700204655ustar00rootroot00000000000000containers-netavark-83edb4b/src/network/bridge.rs000066400000000000000000000677561452673426700223140ustar00rootroot00000000000000use std::{collections::HashMap, net::IpAddr, os::fd::BorrowedFd, sync::Once}; use ipnet::IpNet; use log::{debug, error}; use netlink_packet_route::{ nlas::link::{Info, InfoData, InfoKind, Nla, VethInfo}, LinkMessage, }; use crate::{ dns::aardvark::AardvarkEntry, error::{ErrorWrap, NetavarkError, NetavarkErrorList, NetavarkResult}, exec_netns, firewall::{ iptables::MAX_HASH_SIZE, state::{remove_fw_config, write_fw_config}, }, network::{constants, core_utils::disable_ipv6_autoconf, types}, }; use super::{ constants::{ ISOLATE_OPTION_FALSE, ISOLATE_OPTION_STRICT, ISOLATE_OPTION_TRUE, NO_CONTAINER_INTERFACE_ERROR, OPTION_ISOLATE, OPTION_METRIC, OPTION_MTU, OPTION_NO_DEFAULT_ROUTE, OPTION_VRF, }, core_utils::{self, get_ipam_addresses, join_netns, parse_option, CoreUtils}, driver::{self, DriverInfo}, internal_types::{ IPAMAddresses, IsolateOption, PortForwardConfig, SetupNetwork, TearDownNetwork, TeardownPortForward, }, netlink, types::StatusBlock, }; const NO_BRIDGE_NAME_ERROR: &str = "no bridge interface name given"; struct InternalData { /// interface name of the veth pair inside the container netns container_interface_name: String, /// interface name of the bridge for on the host bridge_interface_name: String, /// static mac address mac_address: Option>, /// ip addresses ipam: IPAMAddresses, /// mtu for the network interfaces (0 if default) mtu: u32, /// if this network should be isolated from others isolate: IsolateOption, /// Route metric for any default routes added for the network metric: Option, /// if set, no default gateway will be added no_default_route: bool, /// sef vrf for bridge vrf: Option, // TODO: add vlan } pub struct Bridge<'a> { info: DriverInfo<'a>, data: Option, } impl<'a> Bridge<'a> { pub fn new(info: DriverInfo<'a>) -> Self { Bridge { info, data: None } } } impl driver::NetworkDriver for Bridge<'_> { fn network_name(&self) -> String { self.info.network.name.clone() } fn validate(&mut self) -> NetavarkResult<()> { let bridge_name = get_interface_name(self.info.network.network_interface.clone())?; if self.info.per_network_opts.interface_name.is_empty() { return Err(NetavarkError::msg(NO_CONTAINER_INTERFACE_ERROR)); } let ipam = get_ipam_addresses(self.info.per_network_opts, self.info.network)?; let mtu: u32 = parse_option(&self.info.network.options, OPTION_MTU)?.unwrap_or(0); let isolate: IsolateOption = get_isolate_option(&self.info.network.options)?; let metric: u32 = parse_option(&self.info.network.options, OPTION_METRIC)?.unwrap_or(100); let no_default_route: bool = parse_option(&self.info.network.options, OPTION_NO_DEFAULT_ROUTE)?.unwrap_or(false); let vrf: Option = parse_option(&self.info.network.options, OPTION_VRF)?; let static_mac = match &self.info.per_network_opts.static_mac { Some(mac) => Some(CoreUtils::decode_address_from_hex(mac)?), None => None, }; self.data = Some(InternalData { bridge_interface_name: bridge_name, container_interface_name: self.info.per_network_opts.interface_name.clone(), mac_address: static_mac, ipam, mtu, isolate, metric: Some(metric), no_default_route, vrf, }); Ok(()) } fn setup( &self, netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket), ) -> NetavarkResult<(StatusBlock, Option)> { let data = match &self.data { Some(d) => d, None => return Err(NetavarkError::msg("must call validate() before setup()")), }; debug!("Setup network {}", self.info.network.name); debug!( "Container interface name: {} with IP addresses {:?}", data.container_interface_name, data.ipam.container_addresses ); debug!( "Bridge name: {} with IP addresses {:?}", data.bridge_interface_name, data.ipam.gateway_addresses ); setup_ipv4_fw_sysctl()?; if data.ipam.ipv6_enabled { setup_ipv6_fw_sysctl()?; } let (host_sock, netns_sock) = netlink_sockets; let container_veth_mac = create_interfaces( host_sock, netns_sock, data, self.info.network.internal, self.info.netns_host, self.info.netns_container, )?; // StatusBlock response let mut response = types::StatusBlock { dns_server_ips: Some(Vec::::new()), dns_search_domains: Some(Vec::::new()), interfaces: Some(HashMap::new()), }; // interfaces map, but we only ever expect one, for response let mut interfaces: HashMap = HashMap::new(); let interface = types::NetInterface { mac_address: container_veth_mac, subnets: Option::from(data.ipam.net_addresses.clone()), }; // Add interface to interfaces (part of StatusBlock) interfaces.insert(data.container_interface_name.clone(), interface); let _ = response.interfaces.insert(interfaces); let aardvark_entry = if self.info.network.dns_enabled { let _ = response .dns_server_ips .insert(data.ipam.nameservers.clone()); // Note: this is being added so podman setup is backward compatible with the design // which we had with dnsname/dnsmasq. I believe this can be fixed in later releases. let _ = response .dns_search_domains .insert(vec![constants::PODMAN_DEFAULT_SEARCH_DOMAIN.to_string()]); let mut ipv4 = Vec::new(); let mut ipv6 = Vec::new(); for ipnet in &data.ipam.container_addresses { match ipnet.addr() { IpAddr::V4(v4) => { ipv4.push(v4); } IpAddr::V6(v6) => { ipv6.push(v6); } } } let mut names = vec![self.info.container_name.to_string()]; match &self.info.per_network_opts.aliases { Some(n) => { names.extend(n.clone()); } None => {} } let gw = data .ipam .gateway_addresses .iter() .map(|ipnet| ipnet.addr()) .collect(); Some(AardvarkEntry { network_name: &self.info.network.name, container_id: self.info.container_id, network_gateways: gw, network_dns_servers: &self.info.network.network_dns_servers, container_ips_v4: ipv4, container_ips_v6: ipv6, container_names: names, container_dns_servers: self.info.container_dns_servers, }) } else { // If --dns-enable=false and --dns was set then return following DNS servers // in status_block so podman can use these and populate resolv.conf if let Some(container_dns_servers) = self.info.container_dns_servers { let _ = response .dns_server_ips .insert(container_dns_servers.clone()); } None }; // if the network is internal block routing and do not setup firewall rules if self.info.network.internal { CoreUtils::apply_sysctl_value( format!( "/proc/sys/net/ipv4/conf/{}/forwarding", data.bridge_interface_name ), "0", )?; if data.ipam.ipv6_enabled { CoreUtils::apply_sysctl_value( format!( "/proc/sys/net/ipv6/conf/{}/forwarding", data.bridge_interface_name ), "0", )?; } // return here to skip setting up firewall rules return Ok((response, aardvark_entry)); } self.setup_firewall(data)?; Ok((response, aardvark_entry)) } fn teardown( &self, netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket), ) -> NetavarkResult<()> { let (host_sock, netns_sock) = netlink_sockets; let mut error_list = NetavarkErrorList::new(); let routes = core_utils::create_route_list(&self.info.network.routes)?; for route in routes.iter() { netns_sock .del_route(route) .unwrap_or_else(|err| error_list.push(err)) } let bridge_name = get_interface_name(self.info.network.network_interface.clone())?; let complete_teardown = match remove_link( host_sock, netns_sock, &bridge_name, &self.info.per_network_opts.interface_name, ) { Ok(teardown) => teardown, Err(err) => { error_list.push(err); false } }; if self.info.network.internal { if !error_list.is_empty() { return Err(NetavarkError::List(error_list)); } return Ok(()); } match self.teardown_firewall(complete_teardown, bridge_name) { Ok(_) => {} Err(err) => { error_list.push(err); } }; if !error_list.is_empty() { return Err(NetavarkError::List(error_list)); } Ok(()) } } fn get_interface_name(name: Option) -> NetavarkResult { let name = match name { None => return Err(NetavarkError::msg(NO_BRIDGE_NAME_ERROR)), Some(n) => { if n.is_empty() { return Err(NetavarkError::msg(NO_BRIDGE_NAME_ERROR)); } n } }; Ok(name) } impl<'a> Bridge<'a> { fn get_firewall_conf( &'a self, container_addresses: &Vec, nameservers: &'a Vec, isolate: IsolateOption, bridge_name: String, ) -> NetavarkResult<(SetupNetwork, PortForwardConfig)> { let id_network_hash = CoreUtils::create_network_hash(&self.info.network.name, MAX_HASH_SIZE); let sn = SetupNetwork { subnets: self .info .network .subnets .as_ref() .map(|nets| nets.iter().map(|n| n.subnet).collect()), bridge_name, network_hash_name: id_network_hash.clone(), isolation: isolate, dns_port: self.info.dns_port, }; let mut has_ipv4 = false; let mut has_ipv6 = false; let mut addr_v4: Option = None; let mut addr_v6: Option = None; let mut net_v4: Option = None; let mut net_v6: Option = None; for net in container_addresses { match net { IpNet::V4(v4) => { if has_ipv4 { continue; } addr_v4 = Some(IpAddr::V4(v4.addr())); net_v4 = Some(IpNet::new(v4.network().into(), v4.prefix_len())?); has_ipv4 = true; } IpNet::V6(v6) => { if has_ipv6 { continue; } addr_v6 = Some(IpAddr::V6(v6.addr())); net_v6 = Some(IpNet::new(v6.network().into(), v6.prefix_len())?); has_ipv6 = true; } } } let spf = PortForwardConfig { container_id: self.info.container_id.clone(), port_mappings: self.info.port_mappings, network_name: self.info.network.name.clone(), network_hash_name: id_network_hash, container_ip_v4: addr_v4, subnet_v4: net_v4, container_ip_v6: addr_v6, subnet_v6: net_v6, dns_port: self.info.dns_port, dns_server_ips: nameservers, }; Ok((sn, spf)) } fn setup_firewall(&self, data: &InternalData) -> NetavarkResult<()> { let (sn, spf) = self.get_firewall_conf( &data.ipam.container_addresses, &data.ipam.nameservers, data.isolate, data.bridge_interface_name.clone(), )?; if !self.info.rootless { write_fw_config( self.info.config_dir, &self.info.network.id, self.info.container_id, self.info.firewall.driver_name(), &sn, &spf, )?; } self.info.firewall.setup_network(sn)?; if spf.port_mappings.is_some() { // Need to enable sysctl localnet so that traffic can pass // through localhost to containers CoreUtils::apply_sysctl_value( format!( "net.ipv4.conf.{}.route_localnet", data.bridge_interface_name ), "1", )?; } self.info.firewall.setup_port_forward(spf)?; Ok(()) } fn teardown_firewall( &self, complete_teardown: bool, bridge_name: String, ) -> NetavarkResult<()> { // we have to allocate the vecoros here in the top level to avoid // "borrow later used" problems let (container_addresses, nameservers); let (container_addresses_ref, nameservers_ref, isolate) = match &self.data { Some(d) => (&d.ipam.container_addresses, &d.ipam.nameservers, d.isolate), None => { let isolate = get_isolate_option(&self.info.network.options).unwrap_or_else(|e| { // just log we still try to do as much as possible for cleanup error!("failed to parse {} option: {}", OPTION_ISOLATE, e); IsolateOption::Never }); (container_addresses, nameservers) = match get_ipam_addresses(self.info.per_network_opts, self.info.network) { Ok(i) => (i.container_addresses, i.nameservers), Err(e) => { // just log we still try to do as much as possible for cleanup error!("failed to parse ipam options: {}", e); (Vec::new(), Vec::new()) } }; (&container_addresses, &nameservers, isolate) } }; let (sn, spf) = self.get_firewall_conf( container_addresses_ref, nameservers_ref, isolate, bridge_name, )?; let tn = TearDownNetwork { config: sn, complete_teardown, }; if !self.info.rootless { // IMPORTANT: This must happen before we actually teardown rules. remove_fw_config( self.info.config_dir, &self.info.network.id, self.info.container_id, complete_teardown, )?; } if complete_teardown { // FIXME store error and continue self.info.firewall.teardown_network(tn)?; } let tpf = TeardownPortForward { config: spf, complete_teardown, }; self.info.firewall.teardown_port_forward(tpf)?; Ok(()) } } // sysctl forward static IPV4_FORWARD_ONCE: Once = Once::new(); static IPV6_FORWARD_ONCE: Once = Once::new(); const IPV4_FORWARD: &str = "net.ipv4.ip_forward"; const IPV6_FORWARD: &str = "net.ipv6.conf.all.forwarding"; fn setup_ipv4_fw_sysctl() -> NetavarkResult<()> { let mut result = Ok("".to_string()); IPV4_FORWARD_ONCE.call_once(|| { result = CoreUtils::apply_sysctl_value(IPV4_FORWARD, "1"); }); match result { Ok(_) => {} Err(e) => return Err(e.into()), }; Ok(()) } fn setup_ipv6_fw_sysctl() -> NetavarkResult<()> { let mut result = Ok("".to_string()); IPV6_FORWARD_ONCE.call_once(|| { result = CoreUtils::apply_sysctl_value(IPV6_FORWARD, "1"); }); match result { Ok(_) => {} Err(e) => return Err(e.into()), }; Ok(()) } /// returns the container veth mac address fn create_interfaces( host: &mut netlink::Socket, netns: &mut netlink::Socket, data: &InternalData, internal: bool, hostns_fd: BorrowedFd<'_>, netns_fd: BorrowedFd<'_>, ) -> NetavarkResult { let (bridge_index, mac) = match host.get_link(netlink::LinkID::Name( data.bridge_interface_name.to_string(), )) { Ok(bridge) => ( check_link_is_bridge(bridge, &data.bridge_interface_name)? .header .index, None, ), Err(err) => match err.unwrap() { NetavarkError::Netlink(e) => { if -e.raw_code() != libc::ENODEV { // if bridge does not exists we will create it below, // for all other errors we want to return the error return Err(err).wrap("get bridge interface"); } let mut create_link_opts = netlink::CreateLinkOptions::new( data.bridge_interface_name.to_string(), InfoKind::Bridge, ); create_link_opts.mtu = data.mtu; if let Some(vrf_name) = &data.vrf { let vrf = match host.get_link(netlink::LinkID::Name(vrf_name.to_string())) { Ok(vrf) => check_link_is_vrf(vrf, vrf_name)?, Err(err) => return Err(err).wrap("get vrf to set up bridge interface"), }; create_link_opts.primary_index = vrf.header.index; } host.create_link(create_link_opts).wrap("create bridge")?; if data.ipam.ipv6_enabled { // Disable duplicate address detection if ipv6 enabled // Do not accept Router Advertisements if ipv6 is enabled let br_accept_dad = format!( "/proc/sys/net/ipv6/conf/{}/accept_dad", &data.bridge_interface_name ); let br_accept_ra = format!("net/ipv6/conf/{}/accept_ra", &data.bridge_interface_name); CoreUtils::apply_sysctl_value(br_accept_dad, "0")?; CoreUtils::apply_sysctl_value(br_accept_ra, "0")?; } let link = host .get_link(netlink::LinkID::Name( data.bridge_interface_name.to_string(), )) .wrap("get bridge interface")?; let mut mac = None; for nla in link.nlas.into_iter() { if let Nla::Address(addr) = nla { mac = Some(addr); } } if mac.is_none() { return Err(NetavarkError::msg( "failed to get the mac address from the bridge interface", )); } for addr in &data.ipam.gateway_addresses { host.add_addr(link.header.index, addr) .wrap("add ip addr to bridge")?; } host.set_up(netlink::LinkID::ID(link.header.index)) .wrap("set bridge up")?; (link.header.index, mac) } _ => return Err(err), }, }; create_veth_pair( host, netns, data, bridge_index, mac, internal, hostns_fd, netns_fd, ) } /// return the container veth mac address #[allow(clippy::too_many_arguments)] fn create_veth_pair<'fd>( host: &mut netlink::Socket, netns: &mut netlink::Socket, data: &InternalData, primary_index: u32, bridge_mac: Option>, internal: bool, hostns_fd: BorrowedFd<'fd>, netns_fd: BorrowedFd<'fd>, ) -> NetavarkResult { let mut peer_opts = netlink::CreateLinkOptions::new(data.container_interface_name.to_string(), InfoKind::Veth); peer_opts.mac = data.mac_address.clone().unwrap_or_default(); peer_opts.mtu = data.mtu; peer_opts.netns = Some(netns_fd); let mut peer = LinkMessage::default(); netlink::parse_create_link_options(&mut peer, peer_opts); let mut host_veth = netlink::CreateLinkOptions::new(String::from(""), InfoKind::Veth); host_veth.mtu = data.mtu; host_veth.primary_index = primary_index; host_veth.info_data = Some(InfoData::Veth(VethInfo::Peer(peer))); host.create_link(host_veth).map_err(|err| match err { NetavarkError::Netlink(ref e) if -e.raw_code() == libc::EEXIST => NetavarkError::wrap( format!( "create veth pair: interface {} already exists on container namespace", data.container_interface_name ), err, ), _ => NetavarkError::wrap("create veth pair", err), })?; let veth = netns .get_link(netlink::LinkID::Name( data.container_interface_name.to_string(), )) .wrap("get container veth")?; let mut mac = String::from(""); let mut host_link = 0; for nla in veth.nlas.into_iter() { if let Nla::Address(ref addr) = nla { mac = CoreUtils::encode_address_to_hex(addr); } if let Nla::Link(link) = nla { host_link = link; } } if mac.is_empty() { return Err(NetavarkError::Message( "failed to get the mac address from the container veth interface".to_string(), )); } exec_netns!(hostns_fd, netns_fd, res, { disable_ipv6_autoconf(&data.container_interface_name)?; if data.ipam.ipv6_enabled { // Disable dad inside the container too let disable_dad_in_container = format!( "/proc/sys/net/ipv6/conf/{}/accept_dad", &data.container_interface_name ); core_utils::CoreUtils::apply_sysctl_value(disable_dad_in_container, "0")?; } let enable_arp_notify = format!( "/proc/sys/net/ipv4/conf/{}/arp_notify", &data.container_interface_name ); core_utils::CoreUtils::apply_sysctl_value(enable_arp_notify, "1")?; Ok::<(), NetavarkError>(()) }); // check the result and return error res?; if data.ipam.ipv6_enabled { let host_veth = host.get_link(netlink::LinkID::ID(host_link))?; for nla in host_veth.nlas.into_iter() { if let Nla::IfName(name) = nla { // Disable dad inside on the host too let disable_dad_in_container = format!("/proc/sys/net/ipv6/conf/{name}/accept_dad"); core_utils::CoreUtils::apply_sysctl_value(disable_dad_in_container, "0")?; } } } host.set_up(netlink::LinkID::ID(host_link)) .wrap("failed to set host veth up")?; // Ok this is extremely strange, by default the kernel will always choose the mac address with the // lowest value from all connected interfaces for the bridge. This means as our veth interfaces are // added and removed the bridge mac can change randomly which causes problems with ARP. This causes // package loss until the old incorrect ARP entry is updated with the new bridge mac which for some // reason can take a very long time, we noticed delays up to 100s. // This here forces a static mac because we explicitly requested one even though we still just only // set the same autogenerated one. Not that this must happen after the first veth interface is // connected otherwise no connectivity is possible at all and I have no idea why but CNI does it // also in the same way. if let Some(m) = bridge_mac { host.set_mac_address(netlink::LinkID::ID(primary_index), m) .wrap("set static mac on bridge")?; } for addr in &data.ipam.container_addresses { netns .add_addr(veth.header.index, addr) .wrap("add ip addr to container veth")?; } netns .set_up(netlink::LinkID::ID(veth.header.index)) .wrap("set container veth up")?; if !internal && !data.no_default_route { core_utils::add_default_routes(netns, &data.ipam.gateway_addresses, data.metric)?; } // add static routes for route in data.ipam.routes.iter() { netns.add_route(route)? } Ok(mac) } /// make sure the LinkMessage has the kind bridge fn check_link_is_bridge(msg: LinkMessage, br_name: &str) -> NetavarkResult { for nla in msg.nlas.iter() { if let Nla::Info(info) = nla { for inf in info.iter() { if let Info::Kind(kind) = inf { if *kind == InfoKind::Bridge { return Ok(msg); } else { return Err(NetavarkError::Message(format!( "bridge interface {br_name} already exists but is a {kind:?} interface" ))); } } } } } Err(NetavarkError::Message(format!( "could not determine namespace link kind for bridge {br_name}" ))) } /// make sure the LinkMessage is the kind VRF fn check_link_is_vrf(msg: LinkMessage, vrf_name: &str) -> NetavarkResult { for nla in msg.nlas.iter() { if let Nla::Info(info) = nla { for inf in info.iter() { if let Info::Kind(kind) = inf { if *kind == InfoKind::Vrf { return Ok(msg); } else { return Err(NetavarkError::Message(format!( "vrf {} already exists but is a {:?} interface", vrf_name, kind ))); } } } } } Err(NetavarkError::Message(format!( "could not determine namespace link kind for vrf {}", vrf_name ))) } fn remove_link( host: &mut netlink::Socket, netns: &mut netlink::Socket, br_name: &str, container_veth_name: &str, ) -> NetavarkResult { netns .del_link(netlink::LinkID::Name(container_veth_name.to_string())) .wrap(format!( "failed to delete container veth {container_veth_name}" ))?; let br = host .get_link(netlink::LinkID::Name(br_name.to_string())) .wrap("failed to get bridge interface")?; let links = host .dump_links(&mut vec![Nla::Master(br.header.index)]) .wrap("failed to get connected bridge interfaces")?; // no connected interfaces on that bridge we can remove it if links.is_empty() { log::info!("removing bridge {}", br_name); host.del_link(netlink::LinkID::ID(br.header.index)) .wrap(format!("failed to delete bridge {container_veth_name}"))?; return Ok(true); } Ok(false) } fn get_isolate_option(opts: &Option>) -> NetavarkResult { let isolate = parse_option(opts, OPTION_ISOLATE)?.unwrap_or(ISOLATE_OPTION_FALSE.to_string()); // return isolate option value "false" if unknown value or no value passed Ok(match isolate.as_str() { ISOLATE_OPTION_STRICT => IsolateOption::Strict, ISOLATE_OPTION_TRUE => IsolateOption::Nomal, ISOLATE_OPTION_FALSE => IsolateOption::Never, _ => IsolateOption::Never, }) } containers-netavark-83edb4b/src/network/constants.rs000066400000000000000000000022201452673426700230430ustar00rootroot00000000000000//Following module contains all the network constants // default search domain pub static PODMAN_DEFAULT_SEARCH_DOMAIN: &str = "dns.podman"; // IPAM drivers pub const IPAM_HOSTLOCAL: &str = "host-local"; pub const IPAM_DHCP: &str = "dhcp"; pub const IPAM_NONE: &str = "none"; pub const DRIVER_BRIDGE: &str = "bridge"; pub const DRIVER_IPVLAN: &str = "ipvlan"; pub const DRIVER_MACVLAN: &str = "macvlan"; pub const OPTION_ISOLATE: &str = "isolate"; pub const ISOLATE_OPTION_TRUE: &str = "true"; pub const ISOLATE_OPTION_FALSE: &str = "false"; pub const ISOLATE_OPTION_STRICT: &str = "strict"; pub const OPTION_MTU: &str = "mtu"; pub const OPTION_MODE: &str = "mode"; pub const OPTION_METRIC: &str = "metric"; pub const OPTION_NO_DEFAULT_ROUTE: &str = "no_default_route"; pub const OPTION_BCLIM: &str = "bclim"; pub const OPTION_VRF: &str = "vrf"; /// 100 is the default metric for most Linux networking tools. pub const DEFAULT_METRIC: u32 = 100; pub const NO_CONTAINER_INTERFACE_ERROR: &str = "no container interface name given"; /// make sure this is the same rootful default as used in podman. pub const DEFAULT_CONFIG_DIR: &str = "/run/containers/networks"; containers-netavark-83edb4b/src/network/core_utils.rs000066400000000000000000000342561452673426700232150ustar00rootroot00000000000000use crate::error::{ErrorWrap, NetavarkError, NetavarkResult}; use crate::network::{constants, internal_types, types}; use crate::wrap; use ipnet::IpNet; use log::debug; use netlink_packet_route::{ MACVLAN_MODE_BRIDGE, MACVLAN_MODE_PASSTHRU, MACVLAN_MODE_PRIVATE, MACVLAN_MODE_SOURCE, MACVLAN_MODE_VEPA, }; use nix::sched; use sha2::{Digest, Sha512}; use std::collections::HashMap; use std::env; use std::fmt::Display; use std::fs::File; use std::io::{self, Error}; use std::net::IpAddr; use std::net::Ipv4Addr; use std::net::Ipv6Addr; use std::os::unix::prelude::*; use std::str::FromStr; use sysctl::{Sysctl, SysctlError}; use super::netlink; pub const IPVLAN_MODE_L2: u16 = 0; pub const IPVLAN_MODE_L3: u16 = 1; pub const IPVLAN_MODE_L3S: u16 = 2; pub struct CoreUtils { pub networkns: String, } pub fn get_netavark_dns_port() -> Result { match env::var("NETAVARK_DNS_PORT") { Ok(port_string) => match port_string.parse() { Ok(port) => Ok(port), Err(e) => Err(NetavarkError::Message(format!( "Invalid NETAVARK_DNS_PORT {port_string}: {e}" ))), }, Err(_) => Ok(53), } } pub fn parse_option( opts: &Option>, name: &str, ) -> NetavarkResult> where T: FromStr, ::Err: Display, { let val = match opts.as_ref().and_then(|map| map.get(name)) { Some(val) => match val.parse::() { Ok(mtu) => mtu, Err(err) => { return Err(NetavarkError::Message(format!( "unable to parse \"{name}\": {err}" ))); } }, // if no option is set return None None => return Ok(None), }; Ok(Some(val)) } pub fn get_ipam_addresses<'a>( per_network_opts: &'a types::PerNetworkOptions, network: &'a types::Network, ) -> Result { let addresses = match network .ipam_options .as_ref() .and_then(|map| map.get("driver").cloned()) .as_deref() { // when option is none default to host local Some(constants::IPAM_HOSTLOCAL) | None => { // static ip vector let mut container_addresses = Vec::new(); // gateway ip vector let mut gateway_addresses = Vec::new(); // network addresses for response let mut net_addresses: Vec = Vec::new(); // bool for ipv6 let mut ipv6_enabled = false; // nameservers which can be configured for this container let mut nameservers: Vec = Vec::new(); let static_ips = match per_network_opts.static_ips.as_ref() { None => { return Err(std::io::Error::new( std::io::ErrorKind::Other, "no static ips provided", )) } Some(i) => i, }; // prepare a vector of static aps with appropriate cidr for (idx, subnet) in network.subnets.iter().flatten().enumerate() { let subnet_mask_cidr = subnet.subnet.prefix_len(); if let Some(gw) = subnet.gateway { let gw_net = match ipnet::IpNet::new(gw, subnet_mask_cidr) { Ok(dest) => dest, Err(err) => { return Err(std::io::Error::new( std::io::ErrorKind::Other, format!("failed to parse address {gw}/{subnet_mask_cidr}: {err}"), )) } }; gateway_addresses.push(gw_net); nameservers.push(gw); } // for dual-stack network.ipv6_enabled could be false do explicit check if subnet.subnet.addr().is_ipv6() { ipv6_enabled = true; } // Build up response information let container_address: ipnet::IpNet = match format!("{}/{}", static_ips[idx], subnet_mask_cidr).parse() { Ok(i) => i, Err(e) => { return Err(Error::new(std::io::ErrorKind::Other, e)); } }; // Add the IP to the address_vector container_addresses.push(container_address); net_addresses.push(types::NetAddress { gateway: subnet.gateway, ipnet: container_address, }); } let routes: Vec = match create_route_list(&network.routes) { Ok(r) => r, Err(e) => { return Err(Error::new(std::io::ErrorKind::Other, e)); } }; internal_types::IPAMAddresses { container_addresses, dhcp_enabled: false, gateway_addresses, routes, net_addresses, nameservers, ipv6_enabled, } } Some(constants::IPAM_NONE) => { // no ipam just return empty vectors internal_types::IPAMAddresses { container_addresses: vec![], dhcp_enabled: false, gateway_addresses: vec![], routes: vec![], net_addresses: vec![], nameservers: vec![], ipv6_enabled: false, } } Some(constants::IPAM_DHCP) => internal_types::IPAMAddresses { container_addresses: vec![], dhcp_enabled: true, gateway_addresses: vec![], routes: vec![], ipv6_enabled: false, net_addresses: vec![], nameservers: vec![], }, Some(driver) => { return Err(std::io::Error::new( std::io::ErrorKind::Other, format!("unsupported ipam driver {driver}"), )); } }; Ok(addresses) } impl CoreUtils { pub fn encode_address_to_hex(bytes: &[u8]) -> String { let address: String = bytes .iter() .map(|x| format!("{x:02x}")) .collect::>() .join(":"); address } pub fn decode_address_from_hex(input: &str) -> Result, std::io::Error> { let bytes: Result, _> = input .split(|c| c == ':' || c == '-') .map(|b| u8::from_str_radix(b, 16)) .collect(); let result = match bytes { Ok(bytes) => { if bytes.len() != 6 { return Err(std::io::Error::new( std::io::ErrorKind::Other, format!("invalid mac length for address: {input}"), )); } bytes } Err(e) => { return Err(std::io::Error::new( std::io::ErrorKind::Other, format!("unable to parse mac address {input}: {e}"), )); } }; Ok(result) } pub fn get_macvlan_mode_from_string(mode: Option<&str>) -> NetavarkResult { match mode { // default to bridge when unset None | Some("") | Some("bridge") => Ok(MACVLAN_MODE_BRIDGE), Some("private") => Ok(MACVLAN_MODE_PRIVATE), Some("vepa") => Ok(MACVLAN_MODE_VEPA), Some("passthru") => Ok(MACVLAN_MODE_PASSTHRU), Some("source") => Ok(MACVLAN_MODE_SOURCE), // default to bridge Some(name) => Err(NetavarkError::msg(format!( "invalid macvlan mode \"{name}\"" ))), } } pub fn get_ipvlan_mode_from_string(mode: Option<&str>) -> NetavarkResult { match mode { // default to l2 when unset None | Some("") | Some("l2") => Ok(IPVLAN_MODE_L2), Some("l3") => Ok(IPVLAN_MODE_L3), Some("l3s") => Ok(IPVLAN_MODE_L3S), Some(name) => Err(NetavarkError::msg(format!( "invalid ipvlan mode \"{name}\"" ))), } } pub fn create_network_hash(network_name: &str, length: usize) -> String { let mut hasher = Sha512::new(); hasher.update(network_name.as_bytes()); let result = hasher.finalize(); let hash_string = format!("{result:X}"); let response = &hash_string[0..length]; response.to_string() } /// Set a sysctl value by value's namespace. pub fn apply_sysctl_value( ns_value: impl AsRef, val: impl AsRef, ) -> Result { let ns_value = ns_value.as_ref(); let val = val.as_ref(); debug!("Setting sysctl value for {} to {}", ns_value, val); let ctl = sysctl::Ctl::new(ns_value)?; match ctl.value_string() { Ok(result) => { if result == val { return Ok(result); } } Err(e) => return Err(e), } ctl.set_value_string(val) } } pub fn join_netns(fd: Fd) -> NetavarkResult<()> { match sched::setns(fd, sched::CloneFlags::CLONE_NEWNET) { Ok(_) => Ok(()), Err(e) => Err(NetavarkError::wrap( "setns", NetavarkError::Io(io::Error::from(e)), )), } } /// safe way to join the namespace and join back to the host after the task is done /// This first arg should be the hostns fd, the second is the container ns fd. /// The third is the result variable name and the last the closure that should be /// executed in the ns. #[macro_export] macro_rules! exec_netns { ($host:expr, $netns:expr, $result:ident, $exec:expr) => { join_netns($netns)?; let $result = $exec; join_netns($host)?; }; } pub struct NamespaceOptions { /// Note we have to return the File object since the fd is only valid /// as long as the File object is valid pub file: File, pub netlink: netlink::Socket, } pub fn open_netlink_sockets( netns_path: &str, ) -> NetavarkResult<(NamespaceOptions, NamespaceOptions)> { let netns = open_netlink_socket(netns_path).wrap("open container netns")?; let hostns = open_netlink_socket("/proc/self/ns/net").wrap("open host netns")?; let host_socket = netlink::Socket::new().wrap("host netlink socket")?; exec_netns!( hostns.as_fd(), netns.as_fd(), res, netlink::Socket::new().wrap("netns netlink socket") ); let netns_sock = res?; Ok(( NamespaceOptions { file: hostns, netlink: host_socket, }, NamespaceOptions { file: netns, netlink: netns_sock, }, )) } fn open_netlink_socket(netns_path: &str) -> NetavarkResult { wrap!(File::open(netns_path), format!("open {netns_path}")) } pub fn add_default_routes( sock: &mut netlink::Socket, gws: &[ipnet::IpNet], metric: Option, ) -> NetavarkResult<()> { let mut ipv4 = false; let mut ipv6 = false; for addr in gws { let route = match addr { ipnet::IpNet::V4(v4) => { if ipv4 { continue; } ipv4 = true; netlink::Route::Ipv4 { dest: ipnet::Ipv4Net::new(Ipv4Addr::new(0, 0, 0, 0), 0)?, gw: v4.addr(), metric, } } ipnet::IpNet::V6(v6) => { if ipv6 { continue; } ipv6 = true; netlink::Route::Ipv6 { dest: ipnet::Ipv6Net::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), 0)?, gw: v6.addr(), metric, } } }; sock.add_route(&route) .wrap(format!("add default route {}", &route))?; } Ok(()) } pub fn create_route_list( routes: &Option>, ) -> NetavarkResult> { match routes { Some(rs) => rs .iter() .map(|r| { let gw = r.gateway; let dst = r.destination; let mtr = r.metric; match (gw, dst) { (IpAddr::V4(gw4), IpNet::V4(dst4)) => Ok(netlink::Route::Ipv4 { dest: dst4, gw: gw4, metric: mtr, }), (IpAddr::V6(gw6), IpNet::V6(dst6)) => Ok(netlink::Route::Ipv6 { dest: dst6, gw: gw6, metric: mtr, }), (IpAddr::V4(gw4), IpNet::V6(dst6)) => Err(NetavarkError::Message(format!( "Route with ipv6 destination and ipv4 gateway ({dst6} via {gw4})" ))), (IpAddr::V6(gw6), IpNet::V4(dst4)) => Err(NetavarkError::Message(format!( "Route with ipv4 destination and ipv6 gateway ({dst4} via {gw6})" ))), } }) .collect(), None => Ok(vec![]), } } pub fn disable_ipv6_autoconf(if_name: &str) -> NetavarkResult<()> { // make sure autoconf is off, we want manual config only if let Err(err) = CoreUtils::apply_sysctl_value(format!("/proc/sys/net/ipv6/conf/{if_name}/autoconf"), "0") { match err { SysctlError::NotFound(_) => { // if the sysctl is not found we likely run on a system without ipv6 // just ignore that case } // if we have a read only /proc we ignore it as well SysctlError::IoError(ref e) if e.raw_os_error() == Some(libc::EROFS) => {} _ => { return Err(NetavarkError::wrap( "failed to set autoconf sysctl", NetavarkError::Sysctl(err), )); } } }; Ok(()) } containers-netavark-83edb4b/src/network/driver.rs000066400000000000000000000047621452673426700223370ustar00rootroot00000000000000use crate::{ dns::aardvark::AardvarkEntry, error::{NetavarkError, NetavarkResult}, firewall::FirewallDriver, }; use std::{ffi::OsString, net::IpAddr, os::fd::BorrowedFd, path::Path}; use super::{ bridge::Bridge, constants, netlink, plugin::PluginDriver, types::{Network, PerNetworkOptions, PortMapping, StatusBlock}, vlan::Vlan, }; use std::os::unix::fs::PermissionsExt; pub struct DriverInfo<'a> { pub firewall: &'a dyn FirewallDriver, pub container_id: &'a String, pub container_name: &'a String, pub container_dns_servers: &'a Option>, pub netns_host: BorrowedFd<'a>, pub netns_container: BorrowedFd<'a>, pub netns_path: &'a str, pub network: &'a Network, pub per_network_opts: &'a PerNetworkOptions, pub port_mappings: &'a Option>, pub dns_port: u16, pub config_dir: &'a Path, pub rootless: bool, } pub trait NetworkDriver { /// validate the driver options fn validate(&mut self) -> NetavarkResult<()>; /// setup the network interfaces/firewall rules for this driver fn setup( &self, netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket), ) -> NetavarkResult<(StatusBlock, Option)>; /// teardown the network interfaces/firewall rules for this driver fn teardown( &self, netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket), ) -> NetavarkResult<()>; /// return the network name fn network_name(&self) -> String; } pub fn get_network_driver<'a>( info: DriverInfo<'a>, plugins_directories: &Option>, ) -> NetavarkResult> { match info.network.driver.as_str() { constants::DRIVER_BRIDGE => Ok(Box::new(Bridge::new(info))), constants::DRIVER_IPVLAN | constants::DRIVER_MACVLAN => Ok(Box::new(Vlan::new(info))), name => { if let Some(dirs) = plugins_directories { for path in dirs.iter() { let path = Path::new(path).join(name); if let Ok(meta) = path.metadata() { if meta.is_file() && meta.permissions().mode() & 0o111 != 0 { return Ok(Box::new(PluginDriver::new(path, info))); } } } } Err(NetavarkError::Message(format!( "unknown network driver \"{}\"", info.network.driver ))) } } } containers-netavark-83edb4b/src/network/internal_types.rs000066400000000000000000000100051452673426700240670ustar00rootroot00000000000000use super::netlink; use crate::network::types; use std::net::IpAddr; /// Teardown contains options for tearing down behind a container #[derive(Debug)] pub struct TeardownPortForward<'a> { pub config: PortForwardConfig<'a>, /// remove network related information pub complete_teardown: bool, } /// SetupNetwork contains options for setting up a container #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct SetupNetwork { /// subnets used for this network pub subnets: Option>, /// bridge interface name pub bridge_name: String, /// hash id for the network pub network_hash_name: String, /// isolation determines whether the network can communicate with others outside of its interface pub isolation: IsolateOption, /// port used for the dns server pub dns_port: u16, } #[derive(Debug)] pub struct TearDownNetwork { pub config: SetupNetwork, pub complete_teardown: bool, } #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct PortForwardConfigGeneric { /// id of container pub container_id: String, /// port mappings pub port_mappings: Ports, /// name of network pub network_name: String, /// hash id for the network pub network_hash_name: String, /// ipv4 address of the container to bind to. /// If multiple v4 addresses are present, use the first one for this. /// At least one of container_ip_v6 and container_ip_v6 must be set. Both can /// be set at the same time as well. pub container_ip_v4: Option, /// subnet associated with the IPv4 address. /// Must be set if v4 address is set. pub subnet_v4: Option, /// ipv6 address of the container. /// If multiple v6 addresses are present, use the first one for this. /// At least one of container_ip_v6 and container_ip_v6 must be set. Both can /// be set at the same time as well. pub container_ip_v6: Option, /// subnet associated with the ipv6 address. /// Must be set if the v6 address is set. pub subnet_v6: Option, /// port used by DNS that should create forwarding rules /// forwarding is not setup if this is 53. pub dns_port: u16, /// dns servers IPs where forwarding rule to port 53 from dns_port are necessary pub dns_server_ips: IpAddresses, } // Some trickery to define two struct one with references and one with owned data, // basically the reference version should be used everywhere and the owned version // is only needed to deserialize the json data. pub type PortForwardConfigOwned = PortForwardConfigGeneric>, Vec>; pub type PortForwardConfig<'a> = PortForwardConfigGeneric<&'a Option>, &'a Vec>; impl<'a> From<&'a PortForwardConfigOwned> for PortForwardConfig<'a> { fn from(p: &'a PortForwardConfigOwned) -> PortForwardConfig<'a> { Self { container_id: p.container_id.clone(), port_mappings: &p.port_mappings, network_name: p.network_name.clone(), network_hash_name: p.network_hash_name.clone(), container_ip_v4: p.container_ip_v4, subnet_v4: p.subnet_v4, container_ip_v6: p.container_ip_v6, subnet_v6: p.subnet_v6, dns_port: p.dns_port, dns_server_ips: &p.dns_server_ips, } } } /// IPAMAddresses is used to pass ipam information around pub struct IPAMAddresses { // ip addresses for netlink pub container_addresses: Vec, // if using macvlan and dhcp, then true pub dhcp_enabled: bool, pub gateway_addresses: Vec, pub routes: Vec, pub ipv6_enabled: bool, // result for podman pub net_addresses: Vec, pub nameservers: Vec, } // IsolateOption is used to select isolate option value #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum IsolateOption { Strict, Nomal, Never, } containers-netavark-83edb4b/src/network/macvlan_dhcp.rs000066400000000000000000000101771452673426700234600ustar00rootroot00000000000000use crate::error::{NetavarkError, NetavarkResult}; use crate::network::types::NetAddress; use ipnet::IpNet; use std::net::IpAddr; use std::str::FromStr; use crate::dhcp_proxy::lib::g_rpc::NetworkConfig; use crate::dhcp_proxy::proxy_conf::DEFAULT_UDS_PATH; /// dhcp performs the connection to the nv-proxy over grpc where it /// requests it to perform a lease via the host's network interface /// but passes it the network interface from the container netns.:w /// /// /// # Arguments /// /// * `host_network_interface`: host interface name in &str /// * `container_network_interface`: container network interface (eth0) /// * `ns_path`: path to the container netns /// * `container_macvlan_mac`: mac address of the container network interface above. /// /// returns: Result, NetavarkError> /// /// # Examples /// /// ``` /// /// ``` pub fn get_dhcp_lease( host_network_interface: &str, container_network_interface: &str, ns_path: &str, container_macvlan_mac: &str, ) -> NetavarkResult> { let nvp_config = NetworkConfig { host_iface: host_network_interface.to_string(), // TODO add in domain name support domain_name: "".to_string(), // TODO add in host name support host_name: "".to_string(), version: 0, ns_path: ns_path.to_string(), container_iface: container_network_interface.to_string(), container_mac_addr: container_macvlan_mac.to_string(), }; let lease = match { tokio::task::LocalSet::new().block_on( match &tokio::runtime::Builder::new_current_thread() .enable_io() .build() { Ok(r) => r, Err(e) => { return Err(NetavarkError::msg(format!("unable to build thread: {e}"))); } }, nvp_config.get_lease(DEFAULT_UDS_PATH), ) } { Ok(l) => l, Err(e) => { return Err(NetavarkError::msg(format!("unable to obtain lease: {e}"))); } }; // Note: technically DHCP can return multiple gateways but // we are just plucking the one. let gw = match IpAddr::from_str(&lease.gateways[0]) { Ok(g) => g, Err(e) => { return Err(NetavarkError::msg(format!("bad gateway address: {e}"))); } }; let ip_addr = match IpAddr::from_str(&lease.yiaddr) { Ok(i) => i, Err(e) => return Err(NetavarkError::Message(e.to_string())), }; let subnet_mask = match std::net::Ipv4Addr::from_str(&lease.subnet_mask) { Ok(s) => s, Err(e) => return Err(NetavarkError::Message(e.to_string())), }; let prefix_len = u32::from(subnet_mask).count_ones(); let ip = match IpNet::new(ip_addr, prefix_len as u8) { Ok(i) => i, Err(e) => return Err(NetavarkError::msg(e.to_string())), }; let ns = NetAddress { gateway: Some(gw), ipnet: ip, }; Ok(vec![ns]) } pub fn release_dhcp_lease( host_network_interface: &str, container_network_interface: &str, ns_path: &str, container_macvlan_mac: &str, ) -> NetavarkResult<()> { let nvp_config = NetworkConfig { host_iface: host_network_interface.to_string(), // TODO add in domain name support domain_name: "".to_string(), // TODO add in host name support host_name: "".to_string(), version: 0, ns_path: ns_path.to_string(), container_iface: container_network_interface.to_string(), container_mac_addr: container_macvlan_mac.to_string(), }; match { tokio::task::LocalSet::new().block_on( match &tokio::runtime::Builder::new_current_thread() .enable_io() .build() { Ok(r) => r, Err(e) => { return Err(NetavarkError::msg(format!("unable to build thread: {e}"))); } }, nvp_config.drop_lease(DEFAULT_UDS_PATH), ) } { Ok(_) => {} Err(e) => { return Err(NetavarkError::Message(e.to_string())); } }; Ok(()) } containers-netavark-83edb4b/src/network/mod.rs000066400000000000000000000015011452673426700216070ustar00rootroot00000000000000pub mod types; pub mod validation; use std::{ ffi::OsString, fs::File, io::{self, BufReader}, }; use crate::{ error::{NetavarkError, NetavarkResult}, wrap, }; pub mod bridge; pub mod constants; pub mod core_utils; pub mod driver; pub mod internal_types; mod macvlan_dhcp; pub mod netlink; pub mod plugin; pub mod vlan; impl types::NetworkOptions { pub fn load(path: Option) -> NetavarkResult { wrap!(Self::load_inner(path), "failed to load network options") } fn load_inner(path: Option) -> Result { let opts = match path { Some(path) => serde_json::from_reader(BufReader::new(File::open(path)?)), None => serde_json::from_reader(io::stdin()), }?; Ok(opts) } } containers-netavark-83edb4b/src/network/netlink.rs000066400000000000000000000425001452673426700225000ustar00rootroot00000000000000use std::{ net::{Ipv4Addr, Ipv6Addr}, os::fd::{AsFd, AsRawFd, BorrowedFd}, }; use crate::{ error::{ErrorWrap, NetavarkError, NetavarkResult}, network::constants, wrap, }; use log::{info, trace}; use netlink_packet_core::{ NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_EXCL, NLM_F_REQUEST, }; use netlink_packet_route::{ nlas::link::{Info, InfoData, InfoKind, Nla}, AddressMessage, LinkMessage, RouteMessage, RtnlMessage, AF_INET, AF_INET6, IFF_UP, RTN_UNICAST, RTPROT_STATIC, RTPROT_UNSPEC, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN, }; use netlink_sys::{protocols::NETLINK_ROUTE, SocketAddr}; pub struct Socket { socket: netlink_sys::Socket, sequence_number: u32, /// buffer size for reading netlink messages, see NLMSG_GOODSIZE in the kernel buffer: [u8; 8192], } #[derive(Clone)] pub struct CreateLinkOptions<'fd> { pub name: String, kind: InfoKind, pub info_data: Option, pub mtu: u32, pub primary_index: u32, pub link: u32, pub mac: Vec, pub netns: Option>, } pub enum LinkID { ID(u32), Name(String), } pub enum Route { Ipv4 { dest: ipnet::Ipv4Net, gw: Ipv4Addr, metric: Option, }, Ipv6 { dest: ipnet::Ipv6Net, gw: Ipv6Addr, metric: Option, }, } impl std::fmt::Display for Route { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let (dest, gw, metric) = match self { Route::Ipv4 { dest, gw, metric } => ( dest.to_string(), gw.to_string(), metric.unwrap_or(constants::DEFAULT_METRIC), ), Route::Ipv6 { dest, gw, metric } => ( dest.to_string(), gw.to_string(), metric.unwrap_or(constants::DEFAULT_METRIC), ), }; write!(f, "(dest: {dest} ,gw: {gw}, metric {metric})") } } macro_rules! expect_netlink_result { ($result:expr, $count:expr) => { if $result.len() != $count { return Err(NetavarkError::msg(format!( "{}: unexpected netlink result (got {} result(s), want {})", function!(), $result.len(), $count ))); } }; } /// get the function name of the currently executed function /// taken from https://stackoverflow.com/a/63904992 macro_rules! function { () => {{ fn f() {} fn type_name_of(_: T) -> &'static str { std::any::type_name::() } let name = type_name_of(f); // Find and cut the rest of the path match &name[..name.len() - 3].rfind(':') { Some(pos) => &name[pos + 1..name.len() - 3], None => &name[..name.len() - 3], } }}; } impl Socket { pub fn new() -> NetavarkResult { let mut socket = wrap!(netlink_sys::Socket::new(NETLINK_ROUTE), "open")?; let addr = &SocketAddr::new(0, 0); wrap!(socket.bind(addr), "bind")?; wrap!(socket.connect(addr), "connect")?; Ok(Socket { socket, sequence_number: 0, buffer: [0; 8192], }) } pub fn get_link(&mut self, id: LinkID) -> NetavarkResult { let mut msg = LinkMessage::default(); match id { LinkID::ID(id) => msg.header.index = id, LinkID::Name(name) => msg.nlas.push(Nla::IfName(name)), } let mut result = self.make_netlink_request(RtnlMessage::GetLink(msg), 0)?; expect_netlink_result!(result, 1); match result.remove(0) { RtnlMessage::NewLink(m) => Ok(m), m => Err(NetavarkError::Message(format!( "unexpected netlink message type: {}", m.message_type() ))), } } pub fn create_link(&mut self, options: CreateLinkOptions) -> NetavarkResult<()> { let mut msg = LinkMessage::default(); parse_create_link_options(&mut msg, options); let result = self.make_netlink_request( RtnlMessage::NewLink(msg), NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE, )?; expect_netlink_result!(result, 0); Ok(()) } pub fn set_link_name(&mut self, id: u32, name: String) -> NetavarkResult<()> { let mut msg = LinkMessage::default(); msg.header.index = id; msg.nlas.push(Nla::IfName(name)); let result = self.make_netlink_request(RtnlMessage::SetLink(msg), NLM_F_ACK)?; expect_netlink_result!(result, 0); Ok(()) } pub fn del_link(&mut self, id: LinkID) -> NetavarkResult<()> { let mut msg = LinkMessage::default(); match id { LinkID::ID(id) => msg.header.index = id, LinkID::Name(name) => msg.nlas.push(Nla::IfName(name)), } let result = self.make_netlink_request(RtnlMessage::DelLink(msg), NLM_F_ACK)?; expect_netlink_result!(result, 0); Ok(()) } pub fn set_link_ns(&mut self, link_id: u32, netns: Fd) -> NetavarkResult<()> { let mut msg = LinkMessage::default(); msg.header.index = link_id; msg.nlas.push(Nla::NetNsFd(netns.as_fd().as_raw_fd())); let result = self.make_netlink_request(RtnlMessage::SetLink(msg), NLM_F_ACK)?; expect_netlink_result!(result, 0); Ok(()) } fn create_addr_msg(link_id: u32, addr: &ipnet::IpNet) -> AddressMessage { let mut msg = AddressMessage::default(); msg.header.index = link_id; let addr_vec = match addr { ipnet::IpNet::V4(v4) => { msg.header.family = AF_INET as u8; msg.nlas.push(netlink_packet_route::address::Nla::Broadcast( v4.broadcast().octets().to_vec(), )); v4.addr().octets().to_vec() } ipnet::IpNet::V6(v6) => { msg.header.family = AF_INET6 as u8; v6.addr().octets().to_vec() } }; msg.header.prefix_len = addr.prefix_len(); msg.nlas .push(netlink_packet_route::address::Nla::Local(addr_vec)); msg } pub fn add_addr(&mut self, link_id: u32, addr: &ipnet::IpNet) -> NetavarkResult<()> { let msg = Self::create_addr_msg(link_id, addr); let result = match self.make_netlink_request( RtnlMessage::NewAddress(msg), NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE, ) { Ok(result) => result, Err(err) => match err { // kernel returns EACCES when we try to add an ipv6 but ipv6 is disabled in the kernel NetavarkError::Netlink(ref e) if -e.raw_code() == libc::EACCES => match addr { ipnet::IpNet::V6(_) => { return Err(NetavarkError::wrap( "failed to add ipv6 address, is ipv6 enabled in the kernel?", err, )); } _ => return Err(err), }, err => return Err(err), }, }; expect_netlink_result!(result, 0); Ok(()) } pub fn del_addr(&mut self, link_id: u32, addr: &ipnet::IpNet) -> NetavarkResult<()> { let msg = Self::create_addr_msg(link_id, addr); let result = self.make_netlink_request(RtnlMessage::DelAddress(msg), NLM_F_ACK)?; expect_netlink_result!(result, 0); Ok(()) } fn create_route_msg(route: &Route) -> RouteMessage { let mut msg = RouteMessage::default(); msg.header.table = RT_TABLE_MAIN; msg.header.protocol = RTPROT_STATIC; msg.header.scope = RT_SCOPE_UNIVERSE; msg.header.kind = RTN_UNICAST; let (dest_vec, dest_prefix, gateway_vec, final_metric) = match route { Route::Ipv4 { dest, gw, metric } => { msg.header.address_family = AF_INET as u8; ( dest.addr().octets().to_vec(), dest.prefix_len(), gw.octets().to_vec(), metric.unwrap_or(constants::DEFAULT_METRIC), ) } Route::Ipv6 { dest, gw, metric } => { msg.header.address_family = AF_INET6 as u8; ( dest.addr().octets().to_vec(), dest.prefix_len(), gw.octets().to_vec(), metric.unwrap_or(constants::DEFAULT_METRIC), ) } }; msg.header.destination_prefix_length = dest_prefix; msg.nlas .push(netlink_packet_route::route::Nla::Destination(dest_vec)); msg.nlas .push(netlink_packet_route::route::Nla::Gateway(gateway_vec)); msg.nlas .push(netlink_packet_route::route::Nla::Priority(final_metric)); msg } pub fn add_route(&mut self, route: &Route) -> NetavarkResult<()> { let msg = Self::create_route_msg(route); info!("Adding route {}", route); let result = self.make_netlink_request(RtnlMessage::NewRoute(msg), NLM_F_ACK | NLM_F_CREATE)?; expect_netlink_result!(result, 0); Ok(()) } pub fn del_route(&mut self, route: &Route) -> NetavarkResult<()> { let msg = Self::create_route_msg(route); info!("Deleting route {}", route); let result = self.make_netlink_request(RtnlMessage::DelRoute(msg), NLM_F_ACK)?; expect_netlink_result!(result, 0); Ok(()) } pub fn dump_routes(&mut self) -> NetavarkResult> { let mut msg = RouteMessage::default(); msg.header.table = RT_TABLE_MAIN; msg.header.protocol = RTPROT_UNSPEC; msg.header.scope = RT_SCOPE_UNIVERSE; msg.header.kind = RTN_UNICAST; let results = self.make_netlink_request(RtnlMessage::GetRoute(msg), NLM_F_DUMP | NLM_F_ACK)?; let mut routes = Vec::with_capacity(results.len()); for res in results { match res { RtnlMessage::NewRoute(m) => routes.push(m), m => { return Err(NetavarkError::Message(format!( "unexpected netlink message type: {}", m.message_type() ))) } }; } Ok(routes) } pub fn dump_links(&mut self, nlas: &mut Vec) -> NetavarkResult> { let mut msg = LinkMessage::default(); msg.nlas.append(nlas); let results = self.make_netlink_request(RtnlMessage::GetLink(msg), NLM_F_DUMP | NLM_F_ACK)?; let mut links = Vec::with_capacity(results.len()); for res in results { match res { RtnlMessage::NewLink(m) => links.push(m), m => { return Err(NetavarkError::Message(format!( "unexpected netlink message type: {}", m.message_type() ))) } }; } Ok(links) } pub fn dump_addresses(&mut self) -> NetavarkResult> { let msg = AddressMessage::default(); let results = self.make_netlink_request(RtnlMessage::GetAddress(msg), NLM_F_DUMP | NLM_F_ACK)?; let mut addresses = Vec::with_capacity(results.len()); for res in results { match res { RtnlMessage::NewAddress(m) => addresses.push(m), m => { return Err(NetavarkError::Message(format!( "unexpected netlink message type: {}", m.message_type() ))) } }; } Ok(addresses) } pub fn set_up(&mut self, id: LinkID) -> NetavarkResult<()> { let mut msg = LinkMessage::default(); match id { LinkID::ID(id) => msg.header.index = id, LinkID::Name(name) => msg.nlas.push(Nla::IfName(name)), } msg.header.flags |= IFF_UP; msg.header.change_mask |= IFF_UP; let result = self.make_netlink_request( RtnlMessage::SetLink(msg), NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE, )?; expect_netlink_result!(result, 0); Ok(()) } pub fn set_mac_address(&mut self, id: LinkID, mac: Vec) -> NetavarkResult<()> { let mut msg = LinkMessage::default(); match id { LinkID::ID(id) => msg.header.index = id, LinkID::Name(name) => msg.nlas.push(Nla::IfName(name)), } msg.nlas.push(Nla::Address(mac)); let result = self.make_netlink_request(RtnlMessage::SetLink(msg), NLM_F_ACK)?; expect_netlink_result!(result, 0); Ok(()) } fn make_netlink_request( &mut self, msg: RtnlMessage, flags: u16, ) -> NetavarkResult> { self.send(msg, flags).wrap("send to netlink")?; self.recv(flags & NLM_F_DUMP == NLM_F_DUMP) } fn send(&mut self, msg: RtnlMessage, flags: u16) -> NetavarkResult<()> { let mut packet = NetlinkMessage::new(NetlinkHeader::default(), NetlinkPayload::from(msg)); packet.header.flags = NLM_F_REQUEST | flags; packet.header.sequence_number = { self.sequence_number += 1; self.sequence_number }; packet.finalize(); packet.serialize(&mut self.buffer[..]); trace!("send netlink packet: {:?}", packet); self.socket.send(&self.buffer[..packet.buffer_len()], 0)?; Ok(()) } fn recv(&mut self, multi: bool) -> NetavarkResult> { let mut offset = 0; let mut result = Vec::new(); // if multi is set we expect a multi part message loop { let size = wrap!( self.socket.recv(&mut &mut self.buffer[..], 0), "recv from netlink" )?; loop { let bytes = &self.buffer[offset..]; let rx_packet: NetlinkMessage = NetlinkMessage::deserialize(bytes) .map_err(|e| { NetavarkError::Message(format!( "failed to deserialize netlink message: {e}", )) })?; trace!("read netlink packet: {:?}", rx_packet); if rx_packet.header.sequence_number != self.sequence_number { return Err(NetavarkError::msg(format!( "netlink: sequence_number out of sync (got {}, want {})", rx_packet.header.sequence_number, self.sequence_number, ))); } match rx_packet.payload { NetlinkPayload::Done(_) => return Ok(result), NetlinkPayload::Error(e) => { if e.code.is_some() { return Err(e.into()); } return Ok(result); } NetlinkPayload::Noop => { return Err(NetavarkError::msg( "unimplemented netlink message type NOOP", )) } NetlinkPayload::Overrun(_) => { return Err(NetavarkError::msg( "unimplemented netlink message type OVERRUN", )) } NetlinkPayload::InnerMessage(msg) => { result.push(msg); if !multi { return Ok(result); } } _ => {} }; offset += rx_packet.header.length as usize; if offset == size || rx_packet.header.length == 0 { offset = 0; break; } } } } } impl CreateLinkOptions<'_> { pub fn new(name: String, kind: InfoKind) -> Self { CreateLinkOptions { name, kind, info_data: None, mtu: 0, primary_index: 0, link: 0, mac: vec![], netns: None, } } } pub fn parse_create_link_options(msg: &mut LinkMessage, options: CreateLinkOptions) { // add link specific data let mut link_info_nlas = vec![Info::Kind(options.kind)]; if let Some(data) = options.info_data { link_info_nlas.push(Info::Data(data)); } msg.nlas.push(Nla::Info(link_info_nlas)); // add name if !options.name.is_empty() { msg.nlas.push(Nla::IfName(options.name)); } // add mtu if options.mtu != 0 { msg.nlas.push(Nla::Mtu(options.mtu)); } // add mac address if !options.mac.is_empty() { msg.nlas.push(Nla::Address(options.mac)); } // add primary device if options.primary_index != 0 { msg.nlas.push(Nla::Master(options.primary_index)); } // add link device if options.link != 0 { msg.nlas.push(Nla::Link(options.link)); } // add netnsfd if let Some(netns) = options.netns { msg.nlas.push(Nla::NetNsFd(netns.as_raw_fd())); } } containers-netavark-83edb4b/src/network/plugin.rs000066400000000000000000000110471452673426700223340ustar00rootroot00000000000000use std::{ io::Read, path::PathBuf, process::{Command, Stdio}, }; use crate::{ dns::aardvark::AardvarkEntry, error::{ErrorWrap, JsonError, NetavarkError, NetavarkResult}, wrap, }; use super::{ driver::{DriverInfo, NetworkDriver}, types, }; pub struct PluginDriver<'a> { path: PathBuf, info: DriverInfo<'a>, } impl<'a> PluginDriver<'a> { pub fn new(path: PathBuf, info: DriverInfo<'a>) -> Self { PluginDriver { path, info } } } impl NetworkDriver for PluginDriver<'_> { fn validate(&mut self) -> NetavarkResult<()> { // Note the the plugin API does not implement validate(). // This would just add an extra fork()/exec() overhead which seems // undesirable since most times it will work without errors. Ok(()) } fn setup( &self, _netlink_sockets: (&mut super::netlink::Socket, &mut super::netlink::Socket), ) -> NetavarkResult<(types::StatusBlock, Option)> { let result = self.exec_plugin(true, self.info.netns_path).wrap(format!( "plugin {:?} failed", &self.path.file_name().unwrap_or_default() ))?; // The unwrap should be safe, only if the exec_plugin has a bug this // could fail, in which case the test should catch it. Ok((result.unwrap(), None)) } fn teardown( &self, _netlink_sockets: (&mut super::netlink::Socket, &mut super::netlink::Socket), ) -> NetavarkResult<()> { self.exec_plugin(false, self.info.netns_path).wrap(format!( "plugin {:?} failed", &self.path.file_name().unwrap_or_default() ))?; Ok(()) } fn network_name(&self) -> String { self.info.network.name.clone() } } impl PluginDriver<'_> { fn exec_plugin(&self, setup: bool, netns: &str) -> NetavarkResult> { // problem we always need to clone since you can only deserialize owned data, // it is not a problem here but for the plugin it is required. // If performance becomes a concern we could use two types for it but the // maintenance overhead does not seem worth right now. let input = types::NetworkPluginExec { container_name: self.info.container_name.clone(), container_id: self.info.container_id.clone(), port_mappings: self.info.port_mappings.clone(), network: self.info.network.clone(), network_options: self.info.per_network_opts.clone(), }; let mut child = Command::new(&self.path) .arg(if setup { "setup" } else { "teardown" }) .arg(netns) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) .spawn()?; let stdin = child.stdin.take().unwrap(); serde_json::to_writer(&stdin, &input)?; // Close stdin here to avoid that the plugin waits forever for an EOF. // And then we would wait for the child to exit which would cause a hang. drop(stdin); // Note: We need to buffer the output and then deserialize into the correct type after // the plugin exits, Since the plugin can return two different json types depending on // the exit code. let mut buffer: Vec = Vec::new(); let mut stdout = child.stdout.take().unwrap(); // Do not handle error here, we have to wait for the child first. let result = stdout.read_to_end(&mut buffer); let exit_status = wrap!(child.wait(), "wait for plugin to exit")?; if let Some(rc) = exit_status.code() { // make sure the buffer is correct wrap!(result, "read into buffer")?; if rc == 0 { // read status block and setup if setup { let status = serde_json::from_slice(&buffer)?; return Ok(Some(status)); } else { return Ok(None); } } else { // exit code not 0 => error let err: JsonError = serde_json::from_slice(&buffer)?; return Err(NetavarkError::msg(format!( "exit code {}, message: {}", rc, err.error ))); } } // If we could not get the exit code then the process was killed by a signal. // I don't think it is necessary to read and return the signal so we just return a generic error. Err(NetavarkError::msg("plugin killed by signal")) } } containers-netavark-83edb4b/src/network/types.rs000066400000000000000000000213641452673426700222050ustar00rootroot00000000000000// Crate contains the types which are accepted by netavark. use ipnet::IpNet; use std::collections::HashMap; use std::net::IpAddr; // Network describes the Network attributes. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Network { /// Set up dns for this network #[serde(rename = "dns_enabled")] pub dns_enabled: bool, /// Driver for this Network, e.g. bridge, macvlan... #[serde(rename = "driver")] pub driver: String, /// ID of the Network. #[serde(rename = "id")] pub id: String, /// Internal is whether the Network should not have external routes /// to public or other Networks. #[serde(rename = "internal")] pub internal: bool, /// This network contains at least one ipv6 subnet. #[serde(rename = "ipv6_enabled")] pub ipv6_enabled: bool, /// Name of the Network. #[serde(rename = "name")] pub name: String, /// NetworkInterface is the network interface name on the host. #[serde(rename = "network_interface")] pub network_interface: Option, /// Options is a set of key-value options that have been applied to /// the Network. #[serde(rename = "options")] pub options: Option>, /// IPAM options is a set of key-value options that have been applied to /// the Network. #[serde(rename = "ipam_options")] pub ipam_options: Option>, /// Subnets to use for this network. #[serde(rename = "subnets")] pub subnets: Option>, /// Static routes to use for this network. #[serde(rename = "routes")] pub routes: Option>, /// Network DNS servers for aardvark-dns. #[serde(rename = "network_dns_servers")] pub network_dns_servers: Option>, } /// NetworkOptions for a given container. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NetworkOptions { /// The container id, used for iptables comments and ipam allocation. #[serde(rename = "container_id")] pub container_id: String, /// The container name, used as dns name. #[serde(rename = "container_name")] pub container_name: String, /// The options used to create the interfaces with. /// The networks listed in "network_info" have to match this, /// both use the network name as key for the map. #[serde(rename = "networks")] pub networks: HashMap, /// The networks which are needed to run this. /// It has to match the networks listed in "networks", /// both use the network name as key for the map. #[serde(rename = "network_info")] pub network_info: HashMap, /// The port mappings for this container. #[serde(rename = "port_mappings")] pub port_mappings: Option>, /// Custom DNS servers for aardvark-dns. #[serde(rename = "dns_servers")] pub dns_servers: Option>, } /// PerNetworkOptions are options which should be set on a per network basis #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PerNetworkOptions { /// Aliases contains a list of names which the dns server should resolve /// to this container. Should only be set when DNSEnabled is true on the Network. /// If aliases are set but there is no dns support for this network the /// network interface implementation should ignore this and NOT error. #[serde(rename = "aliases")] pub aliases: Option>, /// InterfaceName for this container. Required. #[serde(rename = "interface_name")] pub interface_name: String, /// StaticIPs for this container. #[serde(rename = "static_ips")] pub static_ips: Option>, /// MAC address for the container interface. #[serde(rename = "static_mac")] pub static_mac: Option, } /// PortMapping is one or more ports that will be mapped into the container. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PortMapping { /// ContainerPort is the port number that will be exposed from the /// container. #[serde(rename = "container_port")] pub container_port: u16, /// HostIP is the IP that we will bind to on the host. /// If unset, assumed to be 0.0.0.0 (all interfaces). #[serde(rename = "host_ip")] pub host_ip: String, /// HostPort is the port number that will be forwarded from the host into /// the container. #[serde(rename = "host_port")] pub host_port: u16, /// Protocol is the protocol forward. /// Must be either "tcp", "udp", and "sctp", or some combination of these /// separated by commas. /// If unset, assumed to be TCP. #[serde(rename = "protocol")] pub protocol: String, /// Range is the number of ports that will be forwarded, starting at /// HostPort and ContainerPort and counting up. /// This is 1-indexed, so 1 is assumed to be a single port (only the /// Hostport:Containerport mapping will be added), 2 is two ports (both /// Hostport:Containerport and Hostport+1:Containerport+1), etc. /// If unset, assumed to be 1 (a single port). /// Both hostport + range and containerport + range must be less than /// 65536. #[serde(rename = "range")] pub range: u16, } /// StatusBlock contains the network information about a container /// connected to one Network. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct StatusBlock { /// Aardvark supports resolving queries with /// having fewer than ndots dots. So we dont /// need this as of now. /// DNS search domains for /etc/resolv.conf #[serde(rename = "dns_search_domains")] pub dns_search_domains: Option>, /// DNS nameservers /etc/resolv.conf will be populated by these #[serde(rename = "dns_server_ips")] pub dns_server_ips: Option>, /// Interfaces contains the created network interface in the container. /// The map key is the interface name. #[serde(rename = "interfaces")] pub interfaces: Option>, } /// NetInterface contains the settings for a given network interface. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NetInterface { /// MacAddress for this Interface. #[serde(rename = "mac_address")] pub mac_address: String, /// Subnets list of assigned subnets with their gateway. #[serde(rename = "subnets")] pub subnets: Option>, } /// NetAddress contains the ip address, subnet and gateway. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NetAddress { /// Gateway for the network. This can be empty if there is no gateway, e.g. internal network. #[serde(rename = "gateway")] pub gateway: Option, /// IPNet of this NetAddress. Note that this is a subnet but it has to contain the /// actual ip of the network interface and not the network address. #[serde(rename = "ipnet")] pub ipnet: IpNet, } /// Subnet for a network. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Subnet { /// Gateway IP for this Network. #[serde(rename = "gateway")] pub gateway: Option, /// LeaseRange contains the range where IP are leased. Optional. #[serde(rename = "lease_range")] pub lease_range: Option, /// Subnet for this Network in CIDR form. #[serde(rename = "subnet")] pub subnet: IpNet, } /// Static routes for a network. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Route { /// Gateway IP for this route. #[serde(rename = "gateway")] pub gateway: IpAddr, /// Destination for this route in CIDR form. #[serde(rename = "destination")] pub destination: IpNet, /// Route Metric #[serde(rename = "metric")] pub metric: Option, } /// LeaseRange contains the range where IP are leased. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct LeaseRange { /// EndIP last IP in the subnet which should be used to assign ips. #[serde(rename = "end_ip")] pub end_ip: Option, /// StartIP first IP in the subnet which should be used to assign ips. #[serde(rename = "start_ip")] pub start_ip: Option, } /// Type used for the plugin setup and teardown command #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NetworkPluginExec { /// The id for the container #[serde(rename = "container_id")] pub container_id: String, /// The name for the container #[serde(rename = "container_name")] pub container_name: String, /// The port mappings for this container. Optional #[serde(rename = "port_mappings")] pub port_mappings: Option>, /// The network config for this network pub network: Network, /// The special network options for this specific container pub network_options: PerNetworkOptions, } containers-netavark-83edb4b/src/network/validation.rs000066400000000000000000000004111452673426700231610ustar00rootroot00000000000000use crate::error::NetavarkResult; use log::debug; use std::fs::File; pub fn ns_checks(file: &str) -> NetavarkResult<()> { debug!("{:?}", "Validating network namespace..."); // TODO check for FS_MAGIC let _ = File::open(file)?.metadata()?; Ok(()) } containers-netavark-83edb4b/src/network/vlan.rs000066400000000000000000000350431452673426700220000ustar00rootroot00000000000000use log::{debug, error}; use std::os::fd::BorrowedFd; use std::{collections::HashMap, net::IpAddr}; use netlink_packet_route::nlas::link::{InfoData, InfoIpVlan, InfoKind, InfoMacVlan, Nla}; use rand::distributions::{Alphanumeric, DistString}; use crate::network::macvlan_dhcp::{get_dhcp_lease, release_dhcp_lease}; use crate::{ dns::aardvark::AardvarkEntry, error::{ErrorWrap, NetavarkError, NetavarkResult}, exec_netns, network::core_utils::{disable_ipv6_autoconf, join_netns}, }; use super::{ constants::{ NO_CONTAINER_INTERFACE_ERROR, OPTION_BCLIM, OPTION_METRIC, OPTION_MODE, OPTION_MTU, OPTION_NO_DEFAULT_ROUTE, }, core_utils::{self, get_ipam_addresses, parse_option, CoreUtils}, driver::{self, DriverInfo}, internal_types::IPAMAddresses, netlink::{self, CreateLinkOptions}, types::{NetInterface, StatusBlock}, }; enum KindData { MacVlan { /// static mac address mac_address: Option>, /// macvlan mode mode: u32, // IFLA_MACVLAN_BC_CUTOFF option if set bclim: Option, }, IpVlan { /// ipvlan mode mode: u16, }, } impl core::fmt::Display for KindData { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { f.write_str(match self { Self::MacVlan { .. } => "macvlan", Self::IpVlan { .. } => "ipvlan", }) } } struct InternalData { /// interface name inside the container container_interface_name: String, /// interface name on the host host_interface_name: String, /// ip addresses ipam: IPAMAddresses, /// mtu for the network interfaces (0 if default) mtu: u32, /// Route metric for default routes added to the network metric: Option, /// kind-specific data kind: KindData, /// if set, no default gateway will be added no_default_route: bool, // TODO: add vlan } pub struct Vlan<'a> { info: DriverInfo<'a>, data: Option, } impl<'a> Vlan<'a> { pub fn new(info: DriverInfo<'a>) -> Self { Self { info, data: None::, } } } impl driver::NetworkDriver for Vlan<'_> { fn network_name(&self) -> String { self.info.network.name.clone() } fn validate(&mut self) -> NetavarkResult<()> { if self.info.per_network_opts.interface_name.is_empty() { return Err(NetavarkError::msg(NO_CONTAINER_INTERFACE_ERROR)); } let mode: Option = parse_option(&self.info.network.options, OPTION_MODE)?; let mut ipam = get_ipam_addresses(self.info.per_network_opts, self.info.network)?; let mtu = parse_option(&self.info.network.options, OPTION_MTU)?.unwrap_or(0); let metric = parse_option(&self.info.network.options, OPTION_METRIC)?.unwrap_or(100); let no_default_route: bool = parse_option(&self.info.network.options, OPTION_NO_DEFAULT_ROUTE)?.unwrap_or(false); // Remove gateways when marked as internal network if self.info.network.internal { ipam.gateway_addresses = Vec::new(); } self.data = Some(InternalData { container_interface_name: self.info.per_network_opts.interface_name.clone(), host_interface_name: self .info .network .network_interface .clone() .unwrap_or_default(), ipam, mtu, metric: Some(metric), kind: match self.info.network.driver.as_str() { super::constants::DRIVER_IPVLAN => KindData::IpVlan { mode: CoreUtils::get_ipvlan_mode_from_string(mode.as_deref())?, }, super::constants::DRIVER_MACVLAN => { let bclim = parse_option(&self.info.network.options, OPTION_BCLIM)?; KindData::MacVlan { mode: CoreUtils::get_macvlan_mode_from_string(mode.as_deref())?, mac_address: match &self.info.per_network_opts.static_mac { Some(mac) => Some(CoreUtils::decode_address_from_hex(mac)?), None => None, }, bclim, } } other => return Err(NetavarkError::msg(format!("unsupported VLAN type {other}"))), }, no_default_route, }); Ok(()) } fn setup( &self, netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket), ) -> Result<(StatusBlock, Option), NetavarkError> { let data = match &self.data { Some(d) => d, None => return Err(NetavarkError::msg("must call validate() before setup()")), }; debug!("Setup network {}", self.info.network.name); debug!( "Container interface name: {} with IP addresses {:?}", self.info.per_network_opts.interface_name, data.ipam.container_addresses ); let (host_sock, netns_sock) = netlink_sockets; let container_vlan_mac = setup( host_sock, netns_sock, &self.info.per_network_opts.interface_name, data, self.info.netns_host, self.info.netns_container, &data.kind, )?; // StatusBlock response is what we return at the end // of all of this let mut response = StatusBlock { dns_server_ips: Some(Vec::::new()), dns_search_domains: Some(Vec::::new()), interfaces: Some(HashMap::new()), }; // interfaces map, but we only ever expect one, for response let mut interfaces: HashMap = HashMap::new(); // if dhcp is enabled, we need to call the dhcp proxy to perform // a dhcp lease. it will also perform the IP address assignment // to the macvlan interface. let subnets = if data.ipam.dhcp_enabled { get_dhcp_lease( &data.host_interface_name, &data.container_interface_name, self.info.netns_path, &container_vlan_mac, )? } else { data.ipam.net_addresses.clone() }; let interface = NetInterface { mac_address: container_vlan_mac, subnets: Option::from(subnets), }; // Add interface to interfaces (part of StatusBlock) interfaces.insert(self.info.per_network_opts.interface_name.clone(), interface); let _ = response.interfaces.insert(interfaces); Ok((response, None)) } fn teardown( &self, netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket), ) -> NetavarkResult<()> { let ipam = get_ipam_addresses(self.info.per_network_opts, self.info.network)?; let if_name = self.info.per_network_opts.interface_name.clone(); // If we are using DHCP macvlan, we need to at least call to the proxy so that // the proxy's cache can get updated and the current lease can be released. if ipam.dhcp_enabled { let dev = netlink_sockets .1 .get_link(netlink::LinkID::Name(if_name)) .wrap(format!( "get macvlan interface {}", &self.info.per_network_opts.interface_name ))?; let container_mac_address = get_mac_address(dev.nlas)?; release_dhcp_lease( &self .info .network .network_interface .clone() .unwrap_or_default(), &self.info.per_network_opts.interface_name, self.info.netns_path, &container_mac_address, )? } let routes = core_utils::create_route_list(&self.info.network.routes)?; for route in routes.iter() { netlink_sockets.1.del_route(route)?; } netlink_sockets.1.del_link(netlink::LinkID::Name( self.info.per_network_opts.interface_name.to_string(), ))?; Ok(()) } } fn setup( host: &mut netlink::Socket, netns: &mut netlink::Socket, if_name: &str, data: &InternalData, hostns_fd: BorrowedFd<'_>, netns_fd: BorrowedFd<'_>, kind_data: &KindData, ) -> NetavarkResult { let primary_ifname = match data.host_interface_name.as_ref() { "" => get_default_route_interface(host)?, host_name => host_name.to_string(), }; let link = host.get_link(netlink::LinkID::Name(primary_ifname))?; let opts = match kind_data { KindData::IpVlan { mode } => { let mut opts = CreateLinkOptions::new(if_name.to_string(), InfoKind::IpVlan); opts.mtu = data.mtu; opts.netns = Some(netns_fd); opts.link = link.header.index; opts.info_data = Some(InfoData::IpVlan(vec![InfoIpVlan::Mode(*mode)])); opts } KindData::MacVlan { mode, mac_address, bclim, } => { let mut opts = CreateLinkOptions::new(if_name.to_string(), InfoKind::MacVlan); opts.mac = mac_address.clone().unwrap_or_default(); opts.mtu = data.mtu; opts.netns = Some(netns_fd); opts.link = link.header.index; let mut mv_opts = vec![InfoMacVlan::Mode(*mode)]; if let Some(bclim) = bclim { debug!("setting macvlan bclim to {bclim}"); mv_opts.push(InfoMacVlan::BcCutoff(*bclim)) } opts.info_data = Some(InfoData::MacVlan(mv_opts)); opts } }; let mut result = host.create_link(opts.clone()); // Sigh, the kernel creates the interface first in the hostns before moving it into the netns. // Therefore it can fail with EEXIST if the name is already used on the host. Create the link // with tmp name, then rename it in the netns. // If you change the iterations here make sure to match the number in early return case below as well. for i in 0..3 { match result { // no error we can break Ok(_) => break, Err(err) => match err { NetavarkError::Netlink(ref e) if -e.raw_code() == libc::EEXIST => { let random = Alphanumeric.sample_string(&mut rand::thread_rng(), 10); let tmp_name = "mv-".to_string() + &random; let mut opts = opts.clone(); opts.name = tmp_name.clone(); result = host.create_link(opts); if let Err(ref e) = result { // if last element return directly if i == 2 { return Err(NetavarkError::msg(format!( "create {kind_data} interface: {e}" ))); } // retry, error could EEXIST again because we pick a random name continue; } let link = netns .get_link(netlink::LinkID::Name(tmp_name.clone())) .wrap(format!("get tmp {kind_data} interface"))?; netns .set_link_name(link.header.index, if_name.to_string()) .wrap(format!("rename tmp {kind_data} interface")) .map_err(|err| { // If there is an error here most likely the name in the netns is already used, // make sure to delete the tmp interface. if let Err(err) = netns.del_link(netlink::LinkID::ID(link.header.index)) { error!( "failed to delete tmp {} link {}: {}", kind_data, tmp_name, err ); }; err })?; // successful run, break out of loop break; } err => return Err(err).wrap(format!("create {kind_data} interface"))?, }, } } exec_netns!(hostns_fd, netns_fd, res, { disable_ipv6_autoconf(if_name) }); res?; // return autoconf sysctl error let dev = netns .get_link(netlink::LinkID::Name(if_name.to_string())) .wrap(format!("get {kind_data} interface"))?; for addr in &data.ipam.container_addresses { netns .add_addr(dev.header.index, addr) .wrap(format!("add ip addr to {kind_data}"))?; } netns .set_up(netlink::LinkID::ID(dev.header.index)) .wrap(format!("set {kind_data} up"))?; if !data.no_default_route { core_utils::add_default_routes(netns, &data.ipam.gateway_addresses, data.metric)?; } // add static routes for route in data.ipam.routes.iter() { netns.add_route(route)? } get_mac_address(dev.nlas) } fn get_mac_address(v: Vec) -> NetavarkResult { for nla in v.into_iter() { if let Nla::Address(ref addr) = nla { return Ok(CoreUtils::encode_address_to_hex(addr)); } } Err(NetavarkError::msg( "failed to get the the container mac address", )) } fn get_default_route_interface(host: &mut netlink::Socket) -> NetavarkResult { let routes = host.dump_routes().wrap("dump routes")?; for route in routes { let mut dest = false; let mut out_if = 0; for nla in route.nlas { if let netlink_packet_route::route::Nla::Destination(_) = nla { dest = true; } if let netlink_packet_route::route::Nla::Oif(oif) = nla { out_if = oif; } } // if there is no dest we have a default route // return the output interface for this route if !dest && out_if > 0 { let link = host.get_link(netlink::LinkID::ID(out_if))?; let name = link.nlas.iter().find_map(|nla| { if let Nla::IfName(name) = nla { Some(name) } else { None } }); if let Some(name) = name { return Ok(name.to_owned()); } } } Err(NetavarkError::msg("failed to get default route interface")) } containers-netavark-83edb4b/src/plugin.rs000066400000000000000000000073641452673426700206520ustar00rootroot00000000000000use std::{collections::HashMap, env, error::Error, io}; use serde::Serialize; use crate::{error, network::types}; pub const API_VERSION: &str = "1.0.0"; // create new boxed error with string error message, also accepts format!() style arguments #[macro_export] macro_rules! new_error { ($msg:ident) => { Box::new(std::io::Error::new(std::io::ErrorKind::Other, $msg)) }; ($($arg:tt)*) => {{ Box::new(std::io::Error::new(std::io::ErrorKind::Other, format!($($arg)*))) }}; } /// Contains info about this plugin #[derive(Serialize)] pub struct Info { /// The version of this plugin. version: String, // The api version for the netavark plugin API. api_version: String, /// Optional fields you want to be displayed for the info command #[serde(flatten)] extra_info: Option>, } impl Info { pub fn new( version: String, api_version: String, extra_info: Option>, ) -> Self { Self { version, api_version, extra_info, } } } /// Define the plugin functions pub trait Plugin { // create a network config fn create(&self, network: types::Network) -> Result>; /// set up the network configuration fn setup( &self, netns: String, opts: types::NetworkPluginExec, ) -> Result>; /// tear down the network configuration fn teardown(&self, netns: String, opts: types::NetworkPluginExec) -> Result<(), Box>; } pub struct PluginExec { plugin: P, info: Info, } impl PluginExec

{ pub fn new(plugin: P, info: Info) -> Self { PluginExec { plugin, info } } pub fn exec(&self) { match self.inner_exec() { Ok(_) => {} Err(err) => { let e = error::JsonError { error: err.to_string(), }; serde_json::to_writer(io::stdout(), &e) .unwrap_or_else(|e| println!("failed to write json error: {e}: {err}")); std::process::exit(1); } }; } fn inner_exec(&self) -> Result<(), Box> { let mut args = env::args(); args.next() .ok_or_else(|| new_error!("zero arguments given"))?; // match subcommand match args.next().as_deref() { Some("create") => { let mut network = serde_json::from_reader(io::stdin())?; network = self.plugin.create(network)?; serde_json::to_writer(io::stdout(), &network)?; } Some("setup") => { let netns = args .next() .ok_or_else(|| new_error!("netns path argument is missing"))?; let opts = serde_json::from_reader(io::stdin())?; let status_block = self.plugin.setup(netns, opts)?; serde_json::to_writer(io::stdout(), &status_block)?; } Some("teardown") => { let netns = args .next() .ok_or_else(|| new_error!("netns path argument is missing"))?; let opts = serde_json::from_reader(io::stdin())?; self.plugin.teardown(netns, opts)?; } Some("info") => self.print_info()?, Some(unknown) => { return Err(new_error!("unknown subcommand: {}", unknown)); } None => self.print_info()?, }; Ok(()) } fn print_info(&self) -> Result<(), Box> { serde_json::to_writer(io::stdout(), &self.info)?; Ok(()) } } containers-netavark-83edb4b/src/proto-build/000077500000000000000000000000001452673426700212345ustar00rootroot00000000000000containers-netavark-83edb4b/src/proto-build/.proto-build000066400000000000000000000000001452673426700234630ustar00rootroot00000000000000containers-netavark-83edb4b/src/proto/000077500000000000000000000000001452673426700201375ustar00rootroot00000000000000containers-netavark-83edb4b/src/proto/proxy.proto000066400000000000000000000025161452673426700224110ustar00rootroot00000000000000syntax = "proto3"; package netavark_proxy; service NetavarkProxy { // Client side streaming to detect client disconnection rpc Setup(NetworkConfig) returns (Lease) {} rpc Teardown(NetworkConfig) returns (Lease) {} rpc Clean(Empty) returns (OperationResponse) {} } // Netavark sends the proxy the Network Configuration that it wants to setup message NetworkConfig { string host_iface = 1; string container_iface = 2; string container_mac_addr = 3; string domain_name = 4; string host_name = 5; Version version = 6; string ns_path = 7; } // Lease can either contain a IPv4 or IPv6 DHCP lease, and the common IP information message Lease { uint32 t1 = 1; uint32 t2 = 2; uint32 lease_time = 3; uint32 mtu = 4; string domain_name = 5; string mac_address= 6; bool isV6 = 10; string siaddr = 11; string yiaddr = 12; string srv_id = 16; string subnet_mask = 17; string broadcast_addr = 18; repeated string dns_servers = 19; repeated string gateways = 20; repeated string ntp_servers = 21; string host_name = 22; } // Empty Message to send when calling for a shutdown message Empty{} // Response to netavark on successful teardown message OperationResponse { bool success = 1; } enum Version { V4 = 0; V6 = 1; } message NvIpv4Addr { bytes octets = 1; } message NvIpv6Addr { bytes octets = 1; } containers-netavark-83edb4b/src/test/000077500000000000000000000000001452673426700177535ustar00rootroot00000000000000containers-netavark-83edb4b/src/test/config/000077500000000000000000000000001452673426700212205ustar00rootroot00000000000000containers-netavark-83edb4b/src/test/config/portmapping.json000066400000000000000000000017321452673426700244560ustar00rootroot00000000000000{ "container_id": "ad1df727792c3041dc82c5b7791365debce6f5ada7c7a0320a2d1368e1635d30", "container_name": "ecstatic_lamarr", "port_mappings": [ { "host_ip": "", "container_port": 80, "host_port": 8080, "range": 1, "protocol": "tcp" } ], "networks": { "podman": { "static_ips": [ "10.88.0.2" ], "aliases": [ "ad1df727792c" ], "interface_name": "eth0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "bridge", "network_interface": "podman0", "created": "2021-11-18T01:58:22.148419519Z", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/src/test/config/setupopts.test.json000066400000000000000000000015051452673426700251400ustar00rootroot00000000000000{ "container_id": "6ce776ea58b5", "container_name": "testcontainer", "port_mappings": [ { "host_ip": "127.0.0.1", "container_port": 5000, "host_port": 5001, "range": 3, "protocol": "tcp" } ], "networks": { "defaultNetwork": { "interface_name": "eth0" } }, "network_info": { "defaultNetwork": { "dns_enabled": true, "driver": "bridge", "id": "53ce4390f2adb1681eb1a90ec8b48c49c015e0a8d336c197637e7f65e365fa9e", "internal": false, "ipv6_enabled": true, "name": "defaultNetwork", "network_interface": "podman0", "subnets": [ { "gateway": "192.168.43.1", "subnet": "192.168.43.0/24" } ] } } } containers-netavark-83edb4b/src/test/config/setupopts2.test.json000066400000000000000000000014571452673426700252300ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ "10.88.0.2" ], "interface_name": "ethsomething0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "bridge", "network_interface": "podman0", "created": "2021-10-26T16:42:48.769170534+02:00", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/src/test/config/setupoptsmacvlan.test.json000066400000000000000000000014621452673426700265040ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ "10.88.0.2" ], "interface_name": "ethsomething0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "macvlan", "network_interface": "wlp0s20f3", "created": "2021-10-26T16:42:48.769170534+02:00", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/src/test/config/twoNetworks.json000066400000000000000000000032611452673426700244630ustar00rootroot00000000000000{ "container_id": "2d0fe608dc86d7ba65dcec724a335b618328f08daa62afd2ee7fa9d41f74e5a9", "container_name": "", "networks": { "podman1": { "static_ips": [ "10.0.0.2" ], "interface_name": "eth0" }, "podman2": { "static_ips": [ "10.1.0.2" ], "interface_name": "eth1" } }, "network_info": { "podman1": { "name": "podman1", "id": "4937f73ac0df011d4f2848d5f83f5c20b707e71a8d98789bbe80d8f64a815e79", "driver": "bridge", "network_interface": "podman1", "created": "2021-11-04T19:08:39.124321192+01:00", "subnets": [ { "subnet": "10.0.0.0/24", "gateway": "10.0.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "host-local" } }, "podman2": { "name": "podman2", "id": "488a7d9be4fa72a5b80811bd847aac1d99d1a09060739b4e08687949c957cda8", "driver": "bridge", "network_interface": "podman2", "created": "2021-11-04T19:08:39.124800596+01:00", "subnets": [ { "subnet": "10.1.0.0/24", "gateway": "10.1.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/src/test/mod.rs000066400000000000000000000000371452673426700211000ustar00rootroot00000000000000pub mod netlink; pub mod test; containers-netavark-83edb4b/src/test/netlink.rs000066400000000000000000000154321452673426700217720ustar00rootroot00000000000000#[cfg(test)] mod tests { use netavark::network::netlink::*; use netlink_packet_route::{address, nlas::link::InfoKind}; macro_rules! test_setup { () => { if !nix::unistd::getuid().is_root() { // there is no actual way to mark a test as skipped // https://internals.rust-lang.org/t/pre-rfc-skippable-tests/14611 eprintln!("test skipped, requires root"); return; } nix::sched::unshare(nix::sched::CloneFlags::CLONE_NEWNET) .expect("unshare(CLONE_NEWNET)"); }; } macro_rules! run_command { ($command:expr $(, $args:expr)*) => { std::process::Command::new($command).args([$($args),*]).output() .expect("failed to run command") }; } #[test] fn test_socket_new() { test_setup!(); assert!(Socket::new().is_ok(), "Netlink Socket::new() should work"); } #[test] fn test_add_link() { test_setup!(); let mut sock = Socket::new().expect("Socket::new()"); let name = String::from("test1"); sock.create_link(CreateLinkOptions::new(name.clone(), InfoKind::Dummy)) .expect("create link failed"); let out = String::from_utf8(run_command!("ip", "link", "show", &name).stdout) .expect("convert to string failed"); assert!(out.contains(&name), "link test1 does not exists"); } #[test] fn test_add_addr() { test_setup!(); let mut sock = Socket::new().expect("Socket::new()"); let out = run_command!("ip", "link", "add", "test1", "type", "dummy"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); assert!(out.status.success(), "failed to add link via ip"); let link = sock .get_link(LinkID::Name("test1".into())) .expect("get_link failed"); let net = "10.0.0.2/24"; sock.add_addr(link.header.index, &net.parse().unwrap()) .expect("add_addr failed"); let out = String::from_utf8(run_command!("ip", "addr", "show", "test1").stdout) .expect("convert to string failed"); assert!(out.contains(net), "addr does not exists"); } #[test] fn test_del_addr() { test_setup!(); let mut sock = Socket::new().expect("Socket::new()"); let out = run_command!("ip", "link", "add", "test1", "type", "dummy"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); assert!(out.status.success(), "failed to add link via ip"); let net = "10.0.0.2/24"; let out = run_command!("ip", "addr", "add", net, "dev", "test1"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); assert!(out.status.success(), "failed to add addr via ip"); let link = sock .get_link(LinkID::Name("test1".into())) .expect("get_link failed"); sock.del_addr(link.header.index, &net.parse().unwrap()) .expect("del_addr failed"); let out = run_command!("ip", "addr", "show", "test1"); let stdout = String::from_utf8(out.stdout).unwrap(); eprintln!("{stdout}"); assert!(out.status.success(), "failed to show addr via ip"); assert!(!stdout.contains(net), "addr does exist"); } /// This test fails because we do not have actual functioning routes in the test netns /// For some reason the kernel expects us to set different options to make it work but /// I do not want to expose them just for a test. /// With these option you could get it to work but it will not work in the actual use case /// msg.header.protocol = RTPROT_UNSPEC; /// msg.header.scope = RT_SCOPE_NOWHERE; /// msg.header.kind = RTN_UNSPEC; #[test] #[ignore] fn test_del_route() { test_setup!(); let mut sock = Socket::new().expect("Socket::new()"); let out = run_command!("ip", "link", "add", "test1", "type", "dummy"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); assert!(out.status.success(), "failed to add link via ip"); let net = "10.0.0.2/24"; let out = run_command!("ip", "addr", "add", net, "dev", "test1"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); assert!(out.status.success(), "failed to add addr via ip"); // route requires that the link is up! let out = run_command!("ip", "link", "set", "dev", "test1", "up"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); assert!(out.status.success(), "failed to set test1 up via ip"); let net = "10.0.1.0/24"; let gw = "10.0.0.2"; let out = run_command!("ip", "route", "add", net, "via", gw); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); assert!(out.status.success(), "failed to add route via ip"); let out = run_command!("ip", "route", "show"); let stdout = String::from_utf8(out.stdout).unwrap(); eprintln!("{stdout}"); assert!(out.status.success(), "failed to show addr via ip"); assert!(stdout.contains(net), "route should exist"); sock.del_route(&Route::Ipv4 { dest: net.parse().unwrap(), gw: gw.parse().unwrap(), metric: None, }) .expect("del_route failed"); let out = run_command!("ip", "route", "show"); let stdout = String::from_utf8(out.stdout).unwrap(); eprintln!("{stdout}"); assert!(out.status.success(), "failed to show addr via ip"); assert!(!stdout.contains(net), "route should not exist"); } #[test] fn test_dump_addr() { test_setup!(); let mut sock = Socket::new().expect("Socket::new()"); let out = run_command!("ip", "link", "add", "test1", "type", "dummy"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); assert!(out.status.success(), "failed to add link via ip"); let net = "10.0.0.2/24"; let out = run_command!("ip", "addr", "add", net, "dev", "test1"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); assert!(out.status.success(), "failed to add addr via ip"); let out = run_command!("ip", "link", "set", "up", "lo"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); assert!(out.status.success(), "failed to set up lo via ip"); let addresses = sock.dump_addresses().expect("dump_addresses failed"); for nla in addresses[0].nlas.iter() { if let address::Nla::Address(a) = nla { assert_eq!(a, &vec![127, 0, 0, 1]) } } for nla in addresses[1].nlas.iter() { if let address::Nla::Address(a) = nla { assert_eq!(a, &vec![10, 0, 0, 2]) } } } } containers-netavark-83edb4b/src/test/test.rs000066400000000000000000000030661452673426700213050ustar00rootroot00000000000000//use super::*; #[cfg(test)] mod tests { use std::ffi::OsString; use netavark::network; #[test] // Test setup options loader fn test_setup_opts_load() { match network::types::NetworkOptions::load(Some(OsString::from( "src/test/config/setupopts.test.json", ))) { Ok(_) => {} Err(e) => panic!("{}", e), } } // Test if we can deserialize values correctly #[test] fn test_setup_opts_assert() { match network::types::NetworkOptions::load(Some(OsString::from( "src/test/config/setupopts.test.json", ))) { Ok(setupopts) => { assert_eq!(setupopts.container_name, "testcontainer") } Err(e) => panic!("{}", e), } } // Deserialize values correctly // Try mutating deserialized struct #[test] fn test_setup_opts_mutability() { match network::types::NetworkOptions::load(Some(OsString::from( "src/test/config/setupopts.test.json", ))) { Ok(mut setupopts) => { assert_eq!(setupopts.container_name, "testcontainer"); setupopts.container_name = "mutatedcontainername".to_string(); assert_eq!(setupopts.container_name, "mutatedcontainername"); } Err(e) => panic!("{}", e), } } // Test commands::setup::ns_checks works correctly #[test] fn test_ns_checks() { assert!(network::validation::ns_checks("src/test/config/setupopts.test.json").is_ok()); } } containers-netavark-83edb4b/test-dhcp/000077500000000000000000000000001452673426700201005ustar00rootroot00000000000000containers-netavark-83edb4b/test-dhcp/001-basic.bats000066400000000000000000000001521452673426700223300ustar00rootroot00000000000000#!/usr/bin/env bats -*- bats -*- # # basic netavark tests # load helpers @test "simple example" { } containers-netavark-83edb4b/test-dhcp/002-setup.bats000066400000000000000000000044671452673426700224250ustar00rootroot00000000000000#!/usr/bin/env bats -*- bats -*- # # basic netavark tests # load helpers @test "basic setup" { read -r -d '\0' input_config < setup|teardown foo ``` containers-netavark-83edb4b/test-dhcp/README.md000066400000000000000000000004231452673426700213560ustar00rootroot00000000000000# netavark-dhcp-proxy integration test with bats ## Running tests To run the tests locally in your sandbox, you can use one of these methods: * bats ./test/001-basic.bats # runs just the specified test * bats ./test/ # runs all ## Requirements - bats - jq containers-netavark-83edb4b/test-dhcp/configfiles/000077500000000000000000000000001452673426700223705ustar00rootroot00000000000000containers-netavark-83edb4b/test-dhcp/configfiles/bad_interface.json000066400000000000000000000002231452673426700260260ustar00rootroot00000000000000{ "host_iface": "badinterfacename", "mac_addr": "3c:e1:a1:c1:7a:3f", "domain_name": "example.com", "host_name": "foobar", "version": 0 } containers-netavark-83edb4b/test-dhcp/configfiles/bad_mac.json000066400000000000000000000002031452673426700246240ustar00rootroot00000000000000{ "host_iface": "veth0", "mac_addr": "3c:e1:a1a:3f", "domain_name": "example.com", "host_name": "foobar", "version": 0 } containers-netavark-83edb4b/test-dhcp/configfiles/basic.json000066400000000000000000000003221452673426700243410ustar00rootroot00000000000000{ "host_iface": "veth1", "container_iface": "veth0", "container_mac_addr": "66:c0:05:74:df:d0", "domain_name": "example.com", "host_name": "foobar", "version": 0, "ns_path": "/run/netns/foobar" } containers-netavark-83edb4b/test-dhcp/configfiles/no_mac.json000066400000000000000000000001671452673426700245230ustar00rootroot00000000000000{ "host_iface": "veth0", "mac_addr": "", "domain_name": "example.com", "host_name": "foobar", "version": 0 } containers-netavark-83edb4b/test-dhcp/dnsmasqfiles/000077500000000000000000000000001452673426700225715ustar00rootroot00000000000000containers-netavark-83edb4b/test-dhcp/dnsmasqfiles/sample.conf000066400000000000000000000015721452673426700247260ustar00rootroot00000000000000# Set the interface on which dnsmasq operates. # If not set, all the interfaces is used. interface=brtest # To disable dnsmasq's DNS server functionality. port=0 # To enable dnsmasq's DHCP server functionality. dhcp-range=172.172.1.50,172.172.1.150,255.255.255.0,12h #dhcp-range=192.168.0.50,192.168.0.150,12h # Set static IPs of other PCs and the Router. dhcp-host=90:9f:44:d8:16:fc,iptime,192.168.0.1,infinite # Router dhcp-host=31:25:99:36:c2:bb,server-right,192.168.0.3,infinite # PC1 dhcp-host=ac:97:0e:f2:6f:ab,yul-x230,192.168.0.13,infinite # PC2 # Set gateway as Router. Following two lines are identical. #dhcp-option=option:router,192.168.0.1 dhcp-option=3,172.172.1.1 # Set DNS server as Router. dhcp-option=6,172.172.1.1 # Logging. log-facility=/var/log/dnsmasq.log # logfile path. log-async log-queries # log queries. log-dhcp # log dhcp related messages. containers-netavark-83edb4b/test-dhcp/helpers.bash000066400000000000000000000344111452673426700224040ustar00rootroot00000000000000# -*- bash -*- CONTAINER_MAC= DNSMASQ_PIDFILE= NS_NAME= NS_PATH= PROXY_PID= SUBNET_CIDR= TMP_TESTDIR= # Netavark binary to run NETAVARK=${NETAVARK:-./bin/netavark} TESTSDIR=${TESTSDIR:-$(dirname ${BASH_SOURCE})} # export RUST_BACKTRACE so that we get a helpful stack trace export RUST_BACKTRACE=full #### Functions below are taken from podman and buildah and adapted to netavark. ################ # run_helper # Invoke args, with timeout, using BATS 'run' ################ # # Second, we use 'timeout' to abort (with a diagnostic) if something # takes too long; this is preferable to a CI hang. # # Third, we log the command run and its output. This doesn't normally # appear in BATS output, but it will if there's an error. # # Next, we check exit status. Since the normal desired code is 0, # that's the default; but the expected_rc var can override: # # expected_rc=125 run_helper nonexistent-subcommand # expected_rc=? run_helper some-other-command # let our caller check status # # Since we use the BATS 'run' mechanism, $output and $status will be # defined for our caller. # function run_helper() { # expected_rc if unset set default to 0 expected_rc="${expected_rc-0}" if [ "$expected_rc" == "?" ]; then expected_rc= fi # Remember command args, for possible use in later diagnostic messages MOST_RECENT_COMMAND="$*" # stdout is only emitted upon error; this echo is to help a debugger echo "$_LOG_PROMPT $*" # BATS hangs if a subprocess remains and keeps FD 3 open; this happens # if a process crashes unexpectedly without cleaning up subprocesses. run timeout --foreground -v --kill=10 10 "$@" 3>/dev/null # without "quotes", multiple lines are glommed together into one if [ -n "$output" ]; then echo "$output" fi if [ "$status" -ne 0 ]; then echo -n "[ rc=$status " if [ -n "$expected_rc" ]; then if [ "$status" -eq "$expected_rc" ]; then echo -n "(expected) " else echo -n "(** EXPECTED $expected_rc **) " fi fi echo "]" fi if [ "$status" -eq 124 ]; then if expr "$output" : ".*timeout: sending" >/dev/null; then # It's possible for a subtest to _want_ a timeout if [[ "$expected_rc" != "124" ]]; then echo "*** TIMED OUT ***" false fi fi fi if [ -n "$expected_rc" ]; then if [ "$status" -ne "$expected_rc" ]; then die "exit code is $status; expected $expected_rc" fi fi # unset unset expected_rc } ################ # run_in_container_netns # Run args in container netns ################ # function run_in_container_netns() { run_helper ip netns exec "${NS_NAME}" "$@" } ######### # die # Abort with helpful message ######### function die() { # FIXME: handle multi-line output echo "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" >&2 echo "#| FAIL: $*" >&2 echo "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" >&2 false } ############ # assert # Compare actual vs expected string; fail if mismatch ############ # # Compares string (default: $output) against the given string argument. # By default we do an exact-match comparison against $output, but there # are two different ways to invoke us, each with an optional description: # # xpect "EXPECT" [DESCRIPTION] # xpect "RESULT" "OP" "EXPECT" [DESCRIPTION] # # The first form (one or two arguments) does an exact-match comparison # of "$output" against "EXPECT". The second (three or four args) compares # the first parameter against EXPECT, using the given OPerator. If present, # DESCRIPTION will be displayed on test failure. # # Examples: # # xpect "this is exactly what we expect" # xpect "${lines[0]}" =~ "^abc" "first line begins with abc" # function assert() { local actual_string="$output" local operator='==' local expect_string="$1" local testname="$2" case "${#*}" in 0) die "Internal error: 'assert' requires one or more arguments" ;; 1 | 2) ;; 3 | 4) actual_string="$1" operator="$2" expect_string="$3" testname="$4" ;; *) die "Internal error: too many arguments to 'assert'" ;; esac # Comparisons. # Special case: there is no !~ operator, so fake it via '! x =~ y' local not= local actual_op="$operator" if [[ $operator == '!~' ]]; then not='!' actual_op='=~' fi if [[ $operator == '=' || $operator == '==' ]]; then # Special case: we can't use '=' or '==' inside [[ ... ]] because # the right-hand side is treated as a pattern... and '[xy]' will # not compare literally. There seems to be no way to turn that off. if [ "$actual_string" = "$expect_string" ]; then return fi elif [[ $operator == '!=' ]]; then # Same special case as above if [ "$actual_string" != "$expect_string" ]; then return fi else if eval "[[ $not \$actual_string $actual_op \$expect_string ]]"; then return elif [ $? -gt 1 ]; then die "Internal error: could not process 'actual' $operator 'expect'" fi fi # Test has failed. Get a descriptive test name. if [ -z "$testname" ]; then testname="${MOST_RECENT_BUILDAH_COMMAND:-[no test name given]}" fi # Display optimization: the typical case for 'expect' is an # exact match ('='), but there are also '=~' or '!~' or '-ge' # and the like. Omit the '=' but show the others; and always # align subsequent output lines for ease of comparison. local op='' local ws='' if [ "$operator" != '==' ]; then op="$operator " ws=$(printf "%*s" ${#op} "") fi # This is a multi-line message, which may in turn contain multi-line # output, so let's format it ourself, readably local actual_split IFS=$'\n' read -rd '' -a actual_split <<<"$actual_string" || true printf "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" >&2 printf "#| FAIL: %s\n" "$testname" >&2 printf "#| expected: %s'%s'\n" "$op" "$expect_string" >&2 printf "#| actual: %s'%s'\n" "$ws" "${actual_split[0]}" >&2 local line for line in "${actual_split[@]:1}"; do printf "#| > %s'%s'\n" "$ws" "$line" >&2 done printf "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" >&2 false } ################# # assert_json # Compare actual json vs expected string; fail if mismatch ################# # assert_json works like assert except that it accepts one extra parameter, # the jq query string. # There are two different ways to invoke us, each with an optional description: # # xpect "JQ_QUERY" "EXPECT" [DESCRIPTION] # xpect "JSON_STRING" "JQ_QUERY" "OP" "EXPECT" [DESCRIPTION] # Important this function will overwrite $output, so if you need to use the value # more than once you need to safe it in another variable. function assert_json() { local actual_json="$output" local operator='==' local jq_query="$1" local expect_string="$2" local testname="$3" case "${#*}" in 0 | 1) die "Internal error: 'assert_json' requires two or more arguments" ;; 2 | 3) ;; 4 | 5) actual_json="$1" jq_query="$2" operator="$3" expect_string="$4" testname="$5" ;; *) die "Internal error: too many arguments to 'assert_json'" ;; esac run_helper jq -r "$jq_query" <<<"$actual_json" assert "$output" "$operator" "$expect_string" "$testname" } function setup() { echo "### Setup ###" NS_PATH="/var/run/netns/$(random_string)" NS_NAME=$(basename "$NS_PATH") ip netns add "${NS_NAME}" basic_setup } function teardown() { echo "### Teardown ###" basic_teardown ip netns delete "${NS_NAME}" } function basic_teardown(){ # TODO # Make dynamic stop_proxy remove_veth "veth0" "br0" remove_bridge "br0" stop_dhcp "$DNSMASQ_PID" run_in_container_netns ip link set lo down rm -rf "$TMP_TESTDIR" } function basic_setup() { SUBNET_CIDR=$(random_subnet) set_tmpdir add_bridge "br0" add_veth "veth0" "br0" run_in_container_netns ip -j link show veth0 CONTAINER_MAC=$(echo "$output" | jq -r .[0].address) add_veth "veth1" "br0" run_in_container_netns ip link set lo up run_dhcp "$TESTSDIR/dnsmasqfiles" start_proxy } # # add_bridge br0 # function add_bridge() { local bridge_name="$1" br_cidr=$(gateway_from_subnet "$SUBNET_CIDR") run_in_container_netns brctl addbr $bridge_name run_in_container_netns ifconfig $bridge_name $br_cidr up run_in_container_netns firewall-cmd --add-interface=$bridge_name --zone=trusted } # # remove_bridge br0 # function remove_bridge() { local bridge_name="$1" run_in_container_netns firewall-cmd --remove-interface="$bridge_name" --zone=trusted run_in_container_netns ip link set "$bridge_name" down # shellcheck disable=SC2086 run_in_container_netns brctl delbr $bridge_name } # # remove_veth veth0 br0 # function remove_veth() { local veth_name="$1" local bridge_name="$2" local veth_br_name="${veth_name}br" run_in_container_netns ip link set "$veth_br_name" down run_in_container_netns ip link set "$veth_name" down run_in_container_netns brctl delif "$bridge_name" "$veth_br_name" run_in_container_netns ip link del "$veth_br_name" type veth peer name "$veth_name" } # # add_veth veth0 br0 # function add_veth() { local veth_name="$1" local bridge_name="$2" local veth_br_name="${veth_name}br" run_in_container_netns ip link add "$veth_br_name" type veth peer name "$veth_name" run_in_container_netns brctl addif "$bridge_name" "$veth_br_name" run_in_container_netns ip link set "$veth_br_name" up run_in_container_netns ip link set "$veth_name" up } # # run_dhcp /var/tmp/conf # function run_dhcp() { gw=$(gateway_from_subnet "$SUBNET_CIDR") stripped_subnet=$(strip_last_octet_from_subnet) read -r -d '\0' dnsmasq_config < "$dnsmasq_testdir/test.conf" ip netns exec "${NS_NAME}" dnsmasq --log-debug --log-dhcp --no-daemon --conf-dir "${dnsmasq_testdir}" &>>"$TMP_TESTDIR/dnsmasq.log" & DNSMASQ_PID=$! } # # stop_dhcp 27231 # function stop_dhcp() { echo "dnsmasq log:" cat "${TMP_TESTDIR}/dnsmasq.log" kill -9 "$DNSMASQ_PID" } function start_proxy() { RUST_LOG=info ip netns exec "$NS_NAME" $NETAVARK dhcp-proxy --dir "$TMP_TESTDIR" --uds "$TMP_TESTDIR" &>"$TMP_TESTDIR/proxy.log" & PROXY_PID=$! } function stop_proxy(){ echo "proxy log:" cat "$TMP_TESTDIR/proxy.log" kill -9 $PROXY_PID } function run_setup(){ local conf=$1 NS_PATH=$(echo "${conf}" | jq -r .ns_path) NS_NAME=$(basename "$NS_PATH") echo "$conf" > "$TMP_TESTDIR/setup.json" run_client "setup" "${TMP_TESTDIR}/setup.json" } function run_teardown(){ local conf=$1 echo "$conf" > "$TMP_TESTDIR/teardown.json" run_client "teardown" "${TMP_TESTDIR}/teardown.json" } # The first arg is the incoming config from "netavark" ################### # run_client # use test client ################### function run_client(){ local verb=$1 local conf=$2 run_in_container_netns "./bin/netavark-dhcp-proxy-client" --uds "$TMP_TESTDIR/nv-proxy.sock" -f "${conf}" "${verb}" } ################### # random_subnet # generate a random private subnet ################### # # by default it will return a 10.x.x.0/24 ipv4 subnet # if "6" is given as first argument it will return a "fdx:x:x:x::/64" ipv6 subnet function random_subnet() { if [[ "$1" == "6" ]]; then printf "fd%x:%x:%x:%x::/64" $((RANDOM % 256)) $((RANDOM % 65535)) $((RANDOM % 65535)) $((RANDOM % 65535)) else printf "10.%d.%d.0/24" $((RANDOM % 256)) $((RANDOM % 256)) fi } ######################### # random_ip_in_subnet # get a random from a given subnet ######################### # the first arg must be an subnet created by random_subnet # otherwise this function might return an invalid ip function random_ip_in_subnet() { # first trim subnet local net_ip=${1%/*} local num= # if ip has colon it is ipv6 if [[ "$net_ip" == *":"* ]]; then # make sure to not get 0 or 1 num=$(printf "%x" $((RANDOM % 65533 + 2))) else # if ipv4 we have to trim the final 0 net_ip=${net_ip%0} # make sure to not get 0, 1 or 255 num=$(printf "%d" $((RANDOM % 252 + 2))) fi printf "$net_ip%s" $num } ######################### # gateway_from_subnet # get the first ip from a given subnet ######################### # the first arg must be an subnet created by random_subnet # otherwise this function might return an invalid ip function gateway_from_subnet() { local num=1 net_ip=$(strip_last_octet_from_subnet "$SUBNET_CIDR") printf "$net_ip%s" $num } function strip_last_octet_from_subnet() { # first trim subnet local net_ip=${SUBNET_CIDR%/*} # set first ip in network as gateway # if ip has dor it is ipv4 if [[ "$net_ip" == *"."* ]]; then # if ipv4 we have to trim the final 0 net_ip=${net_ip%0} fi printf "$net_ip" } ######################### # generate_mac # random generated mac address ######################### # No args required function generate_mac(){ openssl rand -hex 6 | sed 's/\(..\)/\1:/g; s/.$//' } function set_tmpdir(){ TMP_TESTDIR=$(mktemp -d /tmp/nv-proxyXXX) } ################### # random_string # Pseudorandom alphanumeric string of given length ################### function random_string() { local length=${1:-10} head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length } function has_ip() { local container_ip=$1 local interface=$2 run_in_container_netns ip -j address show $interface addr_info=$(jq '.[0].addr_info' <<<"$output") assert "$addr_info" =~ "$container_ip" "ip not set on interface $interface" } containers-netavark-83edb4b/test-dhcp/setup.sh000077500000000000000000000007651452673426700216070ustar00rootroot00000000000000ip netns add new ip link add dev outside type veth peer name outsidebr ip link add dev inside type veth peer name insidebr ip link add brtest type bridge ip addr add 172.172.1.1/24 dev brtest ip link set outsidebr master brtest ip link set insidebr master brtest ip link set brtest up ip link set inside netns new ip link set outsidebr up ip link set insidebr up ip addr add 172.172.1.2/24 dev outside ip link set outside up ip netns exec new ip link set lo up ip netns exec new ip link set inside up containers-netavark-83edb4b/test/000077500000000000000000000000001452673426700171645ustar00rootroot00000000000000containers-netavark-83edb4b/test/001-basic.bats000066400000000000000000000025221452673426700214170ustar00rootroot00000000000000#!/usr/bin/env bats -*- bats -*- # # basic netavark tests # load helpers @test "netavark version" { run_netavark --version assert "$output" =~ "netavark 1\.[0-9]+\.[0-9]+(-rc|-dev)?" "expected version" run_netavark version json="$output" assert_json "$json" ".version" =~ "^1\.[0-9]+\.[0-9]+(-rc[0-9]|-dev)?" "correct version" assert_json "$json" ".commit" =~ "[0-9a-f]{40}" "shows commit sha" assert_json "$json" ".build_time" =~ "20.*" "show build date" assert_json "$json" ".target" =~ ".*" "contains target string" } @test "netavark error - invalid ns path" { expected_rc=1 run_netavark -f ${TESTSDIR}/testfiles/simplebridge.json setup /test/1 assert_json ".error" "invalid namespace path: IO error: No such file or directory (os error 2)" "Namespace path does not exists" } @test "netavark error - invalid config path" { expected_rc=1 run_netavark -f /test/1 setup $(get_container_netns_path) assert_json ".error" "failed to load network options: IO error: No such file or directory (os error 2)" "Config file does not exists" } @test "netavark - check non utf-8 paths" { # do not use run_netavark here as it sets --config run_helper $NETAVARK --config $'/tmp/\xff.test' version json="$output" assert_json "$json" ".version" =~ "^1\.[0-9]+\.[0-9]+(-rc[0-9]|-dev)?" "correct version" } containers-netavark-83edb4b/test/100-bridge-iptables.bats000066400000000000000000001314521452673426700234000ustar00rootroot00000000000000#!/usr/bin/env bats -*- bats -*- # # bridge driver tests with iptables firewall driver # load helpers fw_driver=iptables @test "check iptables driver is in use" { RUST_LOG=netavark=info run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json setup $(get_container_netns_path) assert "${lines[0]}" "==" "[INFO netavark::firewall] Using iptables firewall driver" "iptables driver is in use" } @test "$fw_driver - internal network" { run_in_host_netns iptables -t nat -nvL before="$output" run_netavark --file ${TESTSDIR}/testfiles/internal.json setup $(get_container_netns_path) run_in_host_netns iptables -t nat -nvL assert "$output" == "$before" "make sure tables have not changed" run_in_container_netns ip route show assert "$output" "!~" "default" "No default route for internal networks" run_in_container_netns ping -c 1 10.88.0.1 run_netavark --file ${TESTSDIR}/testfiles/internal.json teardown $(get_container_netns_path) } @test "$fw_driver - simple bridge" { run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json setup $(get_container_netns_path) result="$output" assert_json "$result" 'has("podman")' == "true" "object key exists" mac=$(jq -r '.podman.interfaces.eth0.mac_address' <<<"$result") # check that interface exists run_in_container_netns ip -j --details link show eth0 link_info="$output" assert_json "$link_info" ".[].address" == "$mac" "MAC matches container mac" assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Container interface is up" assert_json "$link_info" ".[].linkinfo.info_kind" == "veth" "Container interface is a veth device" ipaddr="10.88.0.2/16" run_in_container_netns ip addr show eth0 assert "$output" =~ "$ipaddr" "IP address matches container address" assert_json "$result" ".podman.interfaces.eth0.subnets[0].ipnet" == "$ipaddr" "Result contains correct IP address" run_in_host_netns ip -j --details link show podman0 link_info="$output" assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Host bridge interface is up" assert_json "$link_info" ".[].linkinfo.info_kind" == "bridge" "The bridge interface is actually a bridge" bridge_mac=$(jq -r '.[].address' <<<"$link_info") run_in_host_netns ip -j link show veth0 veth_info="$output" assert_json "$veth_info" ".[].address" != "$bridge_mac" "Bridge and Veth must have different mac address" ipaddr="10.88.0.1" run_in_host_netns ip addr show podman0 assert "$output" =~ "$ipaddr" "IP address matches bridge gateway address" assert_json "$result" ".podman.interfaces.eth0.subnets[0].gateway" == "$ipaddr" "Result contains gateway address" # check that the loopback adapter is up run_in_container_netns ip addr show lo assert "$output" =~ "127.0.0.1" "Loopback adapter is up (has address)" run_in_host_netns ping -c 1 10.88.0.2 check_simple_bridge_iptables run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json teardown $(get_container_netns_path) # now check that iptables rules are gone run_in_host_netns iptables -S # check FORWARD rules run_in_host_netns iptables -S FORWARD assert "${lines[1]}" == "-A FORWARD -m comment --comment \"netavark firewall rules\" -j NETAVARK_FORWARD" "FORWARD rule" assert "${#lines[@]}" = 2 "too many FORWARD rules after teardown" # rule 1 should be DROP for any existing networks run_in_host_netns iptables -S NETAVARK_FORWARD assert "${lines[1]}" == "-A NETAVARK_FORWARD -m conntrack --ctstate INVALID -j DROP" "NETAVARK_FORWARD rule 1" assert "${#lines[@]}" = 2 "too many NETAVARK_FORWARD rules after teardown" # check POSTROUTING nat rules run_in_host_netns iptables -S POSTROUTING -t nat assert "${lines[1]}" =~ "-A POSTROUTING -j NETAVARK-HOSTPORT-MASQ" "POSTROUTING HOSTPORT-MASQ rule" assert "${#lines[@]}" = 2 "too many POSTROUTING rules after teardown" # NETAVARK-1D8721804F16F chain should not exists expected_rc=1 run_in_host_netns iptables -nvL NETAVARK-1D8721804F16F -t nat # bridge should be removed on teardown expected_rc=1 run_in_host_netns ip addr show podman0 } @test "$fw_driver - bridge with static routes" { # add second interface and routes through that interface to test proper teardown run_in_container_netns ip link add type dummy run_in_container_netns ip a add 10.91.0.10/24 dev dummy0 run_in_container_netns ip link set dummy0 up run_netavark --file ${TESTSDIR}/testfiles/bridge-staticroutes.json setup $(get_container_netns_path) # check static routes run_in_container_netns ip r assert "$output" "=~" "10.89.0.0/24 via 10.88.0.2" "static route not set" assert "$output" "=~" "10.90.0.0/24 via 10.88.0.3" "static route not set" assert "$output" "=~" "10.92.0.0/24 via 10.91.0.1" "static route not set" run_netavark --file ${TESTSDIR}/testfiles/bridge-staticroutes.json teardown $(get_container_netns_path) # check static routes get removed assert "$output" "!~" "10.89.0.0/24 via 10.88.0.2" "static route not set" assert "$output" "!~" "10.90.0.0/24 via 10.88.0.3" "static route not set" assert "$output" "!~" "10.92.0.0/24 via 10.91.0.1" "static route not removed" } @test "$fw_driver - bridge with no default route" { run_netavark --file ${TESTSDIR}/testfiles/bridge-nodefaultroute.json setup $(get_container_netns_path) run_in_container_netns ip r assert "$output" "!~" "default" "default route exists" run_in_container_netns ip -6 r assert "$output" "!~" "default" "default route exists" run_netavark --file ${TESTSDIR}/testfiles/bridge-nodefaultroute.json teardown $(get_container_netns_path) assert "" "no errors" } @test "$fw_driver - bridge driver must generate config for aardvark with multiple custom dns server with network dns servers and perform update" { # get a random port directly to avoid low ports e.g. 53 would not create iptables dns_port=$((RANDOM+10000)) NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ setup $(get_container_netns_path) # check aardvark config and running run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" assert "${lines[0]}" =~ "10.89.3.1,fd10:88:a::1 127.0.0.1,3.3.3.3" "aardvark set to listen to all IPs" assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename 8.8.8.8,1.1.1.1$" "aardvark config's container" assert "${#lines[@]}" = 2 "too many lines in aardvark config" aardvark_pid=$(cat "$NETAVARK_TMPDIR/config/aardvark-dns/aardvark.pid") assert "$ardvark_pid" =~ "[0-9]*" "aardvark pid not found" run_helper ps "$aardvark_pid" assert "${lines[1]}" =~ ".*aardvark-dns --config $NETAVARK_TMPDIR/config/aardvark-dns -p $dns_port run" "aardvark not running or bad options" NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ update podman1 --network-dns-servers 8.8.8.8 # check aardvark config and running run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" assert "${lines[0]}" =~ "10.89.3.1,fd10:88:a::1 8.8.8.8" "aardvark set to listen to all IPs" assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename 8.8.8.8,1.1.1.1$" "aardvark config's container" assert "${#lines[@]}" = 2 "too many lines in aardvark config" # remove network and check running and verify if aardvark config has no nameserver NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ update podman1 --network-dns-servers "" # check aardvark config and running run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" assert "${lines[0]}" == "10.89.3.1,fd10:88:a::1" "aardvark set to listen to all IPs" assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename 8.8.8.8,1.1.1.1$" "aardvark config's container" assert "${#lines[@]}" = 2 "too many lines in aardvark config" } # netavark must do no-op on upates when no aardvark config is there @test "run netavark update - no-op" { # get a random port directly to avoid low ports e.g. 53 would not create iptables dns_port=$((RANDOM+10000)) NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ update podman1 --network-dns-servers 8.8.8.8 } @test "$fw_driver - ipv6 bridge" { run_netavark --file ${TESTSDIR}/testfiles/ipv6-bridge.json setup $(get_container_netns_path) result="$output" assert_json "$result" 'has("podman1")' == "true" "object key exists" mac=$(jq -r '.podman1.interfaces.eth0.mac_address' <<<"$result") # check that interface exists run_in_container_netns ip -j --details link show eth0 link_info="$output" assert_json "$link_info" ".[].address" == "$mac" "MAC matches container mac" assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Container interface is up" assert_json "$link_info" ".[].linkinfo.info_kind" == "veth" "Container interface is a veth device" ipaddr="fd10:88:a::2/64" run_in_container_netns ip addr show eth0 assert "$output" =~ "$ipaddr" "IP address matches container address" assert_json "$result" ".podman1.interfaces.eth0.subnets[0].ipnet" == "$ipaddr" "Result contains correct IP address" run_in_host_netns ip -j --details link show podman1 link_info="$output" assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Host bridge interface is up" assert_json "$link_info" ".[].linkinfo.info_kind" == "bridge" "The bridge interface is actually a bridge" ipaddr="fd10:88:a::1" run_in_host_netns ip addr show podman1 assert "$output" =~ "$ipaddr" "IP address matches bridge gateway address" assert_json "$result" ".podman1.interfaces.eth0.subnets[0].gateway" == "$ipaddr" "Result contains gateway address" # check that the loopback adapter is up run_in_container_netns ip addr show lo assert "$output" =~ "127.0.0.1" "Loopback adapter is up (has address)" run_in_host_netns ping6 -c 1 fd10:88:a::2 run_netavark --file ${TESTSDIR}/testfiles/ipv6-bridge.json teardown $(get_container_netns_path) } @test "$fw_driver - ipv6 bridge with static routes" { # add second interface and routes through that interface to test proper teardown run_in_container_netns ip link add type dummy run_in_container_netns ip a add fd10:49:b::2/64 dev dummy0 run_in_container_netns ip link set dummy0 up run_netavark --file ${TESTSDIR}/testfiles/ipv6-bridge-staticroutes.json setup $(get_container_netns_path) # check static routes run_in_container_netns ip -6 -br r assert "$output" "=~" "fd10:89:b::/64 via fd10:88:a::ac02" "static route not set" assert "$output" "=~" "fd10:89:c::/64 via fd10:88:a::ac03" "static route not set" assert "$output" "=~" "fd10:51:b::/64 via fd10:49:b::30" "static route not set" run_netavark --file ${TESTSDIR}/testfiles/ipv6-bridge-staticroutes.json teardown $(get_container_netns_path) # check static routes get removed run_in_container_netns ip -6 -br r assert "$output" "!~" "fd10:89:b::/64 via fd10:88:a::ac02" "static route not removed" assert "$output" "!~" "fd10:89:c::/64 via fd10:88:a::ac03" "static route not removed" assert "$output" "!~" "fd10:51:b::/64 via fd10:49:b::30" "static route not removed" run_in_container_netns ip link delete dummy0 } @test "$fw_driver - bridge driver must generate config for aardvark with custom dns server" { # get a random port directly to avoid low ports e.g. 53 would not create iptables dns_port=$((RANDOM+10000)) NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-custom-dns-server.json \ setup $(get_container_netns_path) # check aardvark config and running run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" assert "${lines[0]}" =~ "10.89.3.1,fd10:88:a::1" "aardvark set to listen to all IPs" assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename 8.8.8.8$" "aardvark config's container" assert "${#lines[@]}" = 2 "too many lines in aardvark config" aardvark_pid=$(cat "$NETAVARK_TMPDIR/config/aardvark-dns/aardvark.pid") assert "$ardvark_pid" =~ "[0-9]*" "aardvark pid not found" run_helper ps "$aardvark_pid" assert "${lines[1]}" =~ ".*aardvark-dns --config $NETAVARK_TMPDIR/config/aardvark-dns -p $dns_port run" "aardvark not running or bad options" } @test "$fw_driver - bridge driver must generate config for aardvark with multiple custom dns server" { # get a random port directly to avoid low ports e.g. 53 would not create iptables dns_port=$((RANDOM+10000)) NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-multiple-custom-dns-server.json \ setup $(get_container_netns_path) # check aardvark config and running run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" assert "${lines[0]}" =~ "10.89.3.1,fd10:88:a::1" "aardvark set to listen to all IPs" assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename 8.8.8.8,1.1.1.1$" "aardvark config's container" assert "${#lines[@]}" = 2 "too many lines in aardvark config" aardvark_pid=$(cat "$NETAVARK_TMPDIR/config/aardvark-dns/aardvark.pid") assert "$ardvark_pid" =~ "[0-9]*" "aardvark pid not found" run_helper ps "$aardvark_pid" assert "${lines[1]}" =~ ".*aardvark-dns --config $NETAVARK_TMPDIR/config/aardvark-dns -p $dns_port run" "aardvark not running or bad options" } @test "$fw_driver - bridge driver must generate config for aardvark with multiple custom dns server with network dns servers" { # get a random port directly to avoid low ports e.g. 53 would not create iptables dns_port=$((RANDOM+10000)) NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ setup $(get_container_netns_path) # check aardvark config and running run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" assert "${lines[0]}" =~ "10.89.3.1,fd10:88:a::1 127.0.0.1,3.3.3.3" "aardvark set to listen to all IPs" assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename 8.8.8.8,1.1.1.1$" "aardvark config's container" assert "${#lines[@]}" = 2 "too many lines in aardvark config" aardvark_pid=$(cat "$NETAVARK_TMPDIR/config/aardvark-dns/aardvark.pid") assert "$ardvark_pid" =~ "[0-9]*" "aardvark pid not found" run_helper ps "$aardvark_pid" assert "${lines[1]}" =~ ".*aardvark-dns --config $NETAVARK_TMPDIR/config/aardvark-dns -p $dns_port run" "aardvark not running or bad options" } @test "$fw_driver - dual stack dns with alt port" { # get a random port directly to avoid low ports e.g. 53 would not create iptables dns_port=$((RANDOM+10000)) NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ setup $(get_container_netns_path) # check iptables run_in_host_netns iptables -t nat -S NETAVARK-HOSTPORT-DNAT assert "${lines[1]}" == "-A NETAVARK-HOSTPORT-DNAT -d 10.89.3.1/32 -p udp -m udp --dport 53 -j DNAT --to-destination 10.89.3.1:$dns_port" "ipv4 dns forward rule" run_in_host_netns ip6tables -t nat -S NETAVARK-HOSTPORT-DNAT assert "${lines[1]}" == "-A NETAVARK-HOSTPORT-DNAT -d fd10:88:a::1/128 -p udp -m udp --dport 53 -j DNAT --to-destination [fd10:88:a::1]:$dns_port" "ipv6 dns forward rule" # check aardvark config and running run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" assert "${lines[0]}" =~ "10.89.3.1,fd10:88:a::1" "aardvark set to listen to all IPs" assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename$" "aardvark config's container" assert "${#lines[@]}" = 2 "too many lines in aardvark config" aardvark_pid=$(cat "$NETAVARK_TMPDIR/config/aardvark-dns/aardvark.pid") assert "$ardvark_pid" =~ "[0-9]*" "aardvark pid not found" run_helper ps "$aardvark_pid" assert "${lines[1]}" =~ ".*aardvark-dns --config $NETAVARK_TMPDIR/config/aardvark-dns -p $dns_port run" "aardvark not running or bad options" # test redirection actually works run_in_container_netns dig +short "somename.dns.podman" @10.89.3.1 A "somename.dns.podman" @10.89.3.1 AAAA assert "${lines[0]}" =~ "10.89.3.2" "ipv4 dns resolution works 1/2" assert "${lines[1]}" =~ "fd10:88:a::2" "ipv6 dns resolution works 2/2" run_in_container_netns dig +short "somename.dns.podman" @fd10:88:a::1 assert "${lines[0]}" =~ "10.89.3.2" "ipv6 dns resolution works" NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ teardown $(get_container_netns_path) # check iptables got removed run_in_host_netns iptables -t nat -S NETAVARK-HOSTPORT-DNAT assert "${#lines[@]}" = 1 "too many v4 NETAVARK_HOSTPORT-DNAT rules after teardown" run_in_host_netns ip6tables -t nat -S NETAVARK-HOSTPORT-DNAT assert "${#lines[@]}" = 1 "too many v6 NETAVARK_HOSTPORT-DNAT rules after teardown" # check aardvark config got cleared, process killed expected_rc=2 run_helper ls "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" expected_rc=1 run_helper ps "$aardvark_pid" } @test "$fw_driver - dns with default drop policy" { run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ setup $(get_container_netns_path) run_in_host_netns iptables -P INPUT DROP run_in_host_netns iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT # check aardvark config and running run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" assert "${lines[0]}" =~ "10.89.3.1,fd10:88:a::1" "aardvark set to listen to all IPs" assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename$" "aardvark config's container" assert "${#lines[@]}" = 2 "too many lines in aardvark config" # test redirection actually works run_in_container_netns dig +short "somename.dns.podman" @10.89.3.1 A "somename.dns.podman" @10.89.3.1 AAAA assert "${lines[0]}" =~ "10.89.3.2" "ipv4 dns resolution works 1/2" assert "${lines[1]}" =~ "fd10:88:a::2" "ipv6 dns resolution works 2/2" run_in_container_netns dig +short "somename.dns.podman" @fd10:88:a::1 assert "${lines[0]}" =~ "10.89.3.2" "ipv6 dns resolution works" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ teardown $(get_container_netns_path) # check iptables got removed run_in_host_netns iptables -t nat -S NETAVARK-HOSTPORT-DNAT assert "${#lines[@]}" = 1 "too many v4 NETAVARK_HOSTPORT-DNAT rules after teardown" run_in_host_netns ip6tables -t nat -S NETAVARK-HOSTPORT-DNAT assert "${#lines[@]}" = 1 "too many v6 NETAVARK_HOSTPORT-DNAT rules after teardown" # check aardvark config got cleared, process killed expected_rc=2 run_helper ls "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" expected_rc=1 run_helper ps "$aardvark_pid" } @test "$fw_driver - dns with default drop policy with non-default dns port" { # get a random port dns_port=$((RANDOM+10000)) NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ setup $(get_container_netns_path) # check iptables run_in_host_netns iptables -t filter -S NETAVARK_INPUT assert "${lines[1]}" == "-A NETAVARK_INPUT -s 10.89.3.0/24 -p udp -m udp --dport $dns_port -j ACCEPT" "ipv4 dns forward rule" run_in_host_netns ip6tables -t filter -S NETAVARK_INPUT assert "${lines[1]}" == "-A NETAVARK_INPUT -s fd10:88:a::/64 -p udp -m udp --dport $dns_port -j ACCEPT" "ipv6 dns forward rule" run_in_host_netns iptables -P INPUT DROP run_in_host_netns iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT # check aardvark config and running run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" assert "${lines[0]}" =~ "10.89.3.1,fd10:88:a::1" "aardvark set to listen to all IPs" assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename$" "aardvark config's container" assert "${#lines[@]}" = 2 "too many lines in aardvark config" # test redirection actually works run_in_container_netns dig +short "somename.dns.podman" @10.89.3.1 A "somename.dns.podman" @10.89.3.1 AAAA assert "${lines[0]}" =~ "10.89.3.2" "ipv4 dns resolution works 1/2" assert "${lines[1]}" =~ "fd10:88:a::2" "ipv6 dns resolution works 2/2" run_in_container_netns dig +short "somename.dns.podman" @fd10:88:a::1 assert "${lines[0]}" =~ "10.89.3.2" "ipv6 dns resolution works" NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ teardown $(get_container_netns_path) # check iptables got removed run_in_host_netns iptables -t nat -S NETAVARK-HOSTPORT-DNAT assert "${#lines[@]}" = 1 "too many v4 NETAVARK_HOSTPORT-DNAT rules after teardown" run_in_host_netns ip6tables -t nat -S NETAVARK-HOSTPORT-DNAT assert "${#lines[@]}" = 1 "too many v6 NETAVARK_HOSTPORT-DNAT rules after teardown" # check aardvark config got cleared, process killed expected_rc=2 run_helper ls "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" expected_rc=1 run_helper ps "$aardvark_pid" } @test "$fw_driver - check error message from netns thread" { # create interface in netns to force error run_in_container_netns ip link add eth0 type dummy expected_rc=1 run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json setup $(get_container_netns_path) assert_json ".error" "create veth pair: interface eth0 already exists on container namespace: Netlink error: File exists (os error 17)" "interface exists on netns" } @test "$fw_driver - port forwarding ipv4 - tcp" { test_port_fw } @test "$fw_driver - port forwarding ipv6 - tcp" { test_port_fw ip=6 } @test "$fw_driver - port forwarding dualstack - tcp" { test_port_fw ip=dual } @test "$fw_driver - port forwarding ipv4 - udp" { test_port_fw proto=udp } @test "$fw_driver - port forwarding ipv6 - udp" { test_port_fw ip=6 proto=udp } @test "$fw_driver - port forwarding dualstack - udp" { test_port_fw ip=dual proto=udp } @test "$fw_driver - port forwarding ipv4 - sctp" { setup_sctp_kernel_module test_port_fw proto=sctp } @test "$fw_driver - port forwarding ipv6 - sctp" { setup_sctp_kernel_module test_port_fw ip=6 proto=sctp } @test "$fw_driver - port forwarding dualstack - sctp" { setup_sctp_kernel_module test_port_fw ip=dual proto=sctp } @test "$fw_driver - port range forwarding ipv4 - tcp" { test_port_fw range=3 } @test "$fw_driver - port range forwarding ipv6 - tcp" { test_port_fw ip=6 range=3 } @test "$fw_driver - port range forwarding ipv4 - udp" { test_port_fw proto=udp range=3 } @test "$fw_driver - port range forwarding ipv6 - udp" { test_port_fw ip=6 proto=udp range=3 } @test "$fw_driver - port range forwarding dual - udp" { test_port_fw ip=dual proto=udp range=3 } @test "$fw_driver - port range forwarding dual - tcp" { test_port_fw ip=dual proto=tcp range=3 } @test "$fw_driver - port forwarding with hostip ipv4 - tcp" { add_dummy_interface_on_host dummy0 "172.16.0.1/24" test_port_fw hostip="172.16.0.1" } @test "$fw_driver - port forwarding with hostip ipv4 dual stack - tcp" { add_dummy_interface_on_host dummy0 "172.16.0.1/24" test_port_fw ip=dual hostip="172.16.0.1" } @test "$fw_driver - port forwarding with hostip ipv6 - tcp" { add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" test_port_fw ip=6 hostip="fd65:8371:648b:0c06::1" } @test "$fw_driver - port forwarding with hostip ipv6 dual stack - tcp" { add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" test_port_fw ip=dual hostip="fd65:8371:648b:0c06::1" } @test "$fw_driver - port forwarding with wildcard hostip ipv4 - tcp" { add_dummy_interface_on_host dummy0 "172.16.0.1/24" test_port_fw hostip="0.0.0.0" connectip="172.16.0.1" } @test "$fw_driver - port forwarding with wildcard hostip ipv4 dual stack - tcp" { add_dummy_interface_on_host dummy0 "172.16.0.1/24" test_port_fw ip=dual hostip="0.0.0.0" connectip="172.16.0.1" } @test "$fw_driver - port forwarding with wildcard hostip ipv6 - tcp" { add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" test_port_fw ip=6 hostip="::" connectip="fd65:8371:648b:0c06::1" } @test "$fw_driver - port forwarding with wildcard hostip ipv6 dual stack - tcp" { add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" test_port_fw ip=dual hostip="::" connectip="fd65:8371:648b:0c06::1" } @test "$fw_driver - port forwarding with hostip ipv4 - udp" { add_dummy_interface_on_host dummy0 "172.16.0.1/24" test_port_fw proto=udp hostip="172.16.0.1" } @test "$fw_driver - port forwarding with hostip ipv6 - udp" { add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" test_port_fw ip=6 proto=udp hostip="fd65:8371:648b:0c06::1" } @test "bridge ipam none" { read -r -d '\0' config < /proc/sys/net/ipv4/ip_forward" run_in_container_netns sh -c "echo 1 > /proc/sys/net/ipv4/conf/default/arp_notify" run_in_host_netns mount -t proc -o ro,nosuid,nodev,noexec proc /proc run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json setup $(get_container_netns_path) run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json teardown $(get_container_netns_path) run_in_host_netns mount -t proc -o remount,rw /proc run_in_host_netns sh -c "echo 0 > /proc/sys/net/ipv4/ip_forward" run_in_host_netns mount -t proc -o remount,ro /proc expected_rc=1 run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json setup $(get_container_netns_path) assert_json ".error" "Sysctl error: IO Error: Read-only file system (os error 30)" "Sysctl error because fs is read only" } @test "$fw_driver - bridge static mac" { mac="aa:bb:cc:dd:ee:ff" read -r -d '\0' config < /proc/sys/net/ipv6/conf/default/accept_dad" #run_in_container_netns sh -c "echo 0 > /proc/sys/net/ipv6/conf/default/accept_dad" # run_in_host_netns sh -c "echo 0 > /proc/sys/net/ipv6/conf/default/accept_ra" run_netavark --file ${TESTSDIR}/testfiles/ipv6-bridge.json setup $(get_container_netns_path) result="$output" assert_json "$result" 'has("podman1")' == "true" "object key exists" mac=$(jq -r '.podman1.interfaces.eth0.mac_address' <<<"$result") # check that interface exists run_in_container_netns ip -j --details link show eth0 link_info="$output" assert_json "$link_info" ".[].address" == "$mac" "MAC matches container mac" assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Container interface is up" assert_json "$link_info" ".[].linkinfo.info_kind" == "veth" "Container interface is a veth device" ipaddr="fd10:88:a::2/64" run_in_container_netns ip addr show eth0 assert "$output" =~ "$ipaddr" "IP address matches container address" assert_json "$result" ".podman1.interfaces.eth0.subnets[0].ipnet" == "$ipaddr" "Result contains correct IP address" run_in_host_netns ip -j --details link show podman1 link_info="$output" assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Host bridge interface is up" assert_json "$link_info" ".[].linkinfo.info_kind" == "bridge" "The bridge interface is actually a bridge" ipaddr="fd10:88:a::1" run_in_host_netns ip addr show podman1 assert "$output" =~ "$ipaddr" "IP address matches bridge gateway address" assert_json "$result" ".podman1.interfaces.eth0.subnets[0].gateway" == "$ipaddr" "Result contains gateway address" # check that the loopback adapter is up run_in_container_netns ip addr show lo assert "$output" =~ "127.0.0.1" "Loopback adapter is up (has address)" run_in_host_netns ping6 -c 1 fd10:88:a::2 } @test "$fw_driver - ipv6 bridge with static routes" { # add second interface and routes through that interface to test proper teardown run_in_container_netns ip link add type dummy run_in_container_netns ip a add fd10:49:b::2/64 dev dummy0 run_in_container_netns ip link set dummy0 up run_netavark --file ${TESTSDIR}/testfiles/ipv6-bridge-staticroutes.json setup $(get_container_netns_path) # check static routes run_in_container_netns ip -6 -br r assert "$output" "=~" "fd10:89:b::/64 via fd10:88:a::ac02" "static route not set" assert "$output" "=~" "fd10:89:c::/64 via fd10:88:a::ac03" "static route not set" assert "$output" "=~" "fd10:51:b::/64 via fd10:49:b::30" "static route not set" run_netavark --file ${TESTSDIR}/testfiles/ipv6-bridge-staticroutes.json teardown $(get_container_netns_path) # check static routes get removed run_in_container_netns ip -6 -br r assert "$output" "!~" "fd10:89:b::/64 via fd10:88:a::ac02" "static route not removed" assert "$output" "!~" "fd10:89:c::/64 via fd10:88:a::ac03" "static route not removed" assert "$output" "!~" "fd10:51:b::/64 via fd10:49:b::30" "static route not removed" run_in_container_netns ip link delete dummy0 } @test "$fw_driver - dual stack dns with alt port" { skip "FIXME (#846): firewalld 2.0 broken port redirect" # get a random port directly to avoid low ports e.g. 53 would not create iptables dns_port=$((RANDOM+10000)) NETAVARK_FW=firewalld NETAVARK_DNS_PORT="$dns_port" \ run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ setup $(get_container_netns_path) # check iptables # firewall-cmd --list-rich-rules does not guarantee order, use sort run_in_host_netns sh -c 'firewall-cmd --policy netavark_portfwd --list-rich-rules | sort' assert "${lines[0]}" =~ "rule family=\"ipv4\" destination address=\"10.89.3.1\" forward-port port=\"53\" protocol=\"udp\" to-port=\"$dns_port\" to-addr=\"10.89.3.1\"" "ipv4 dns redirection" assert "${lines[1]}" =~ "rule family=\"ipv6\" destination address=\"fd10:88:a::1\" forward-port port=\"53\" protocol=\"udp\" to-port=\"$dns_port\" to-addr=\"fd10:88:a::1\"" "ipv6 dns redirection" assert "${#lines[@]}" = 2 "too many rich rules" # check aardvark config and running run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" assert "${lines[0]}" =~ "10.89.3.1,fd10:88:a::1" "aardvark set to listen to all IPs" assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename$" "aardvark config's container" assert "${#lines[@]}" = 2 "too many lines in aardvark config" aardvark_pid=$(cat "$NETAVARK_TMPDIR/config/aardvark-dns/aardvark.pid") assert "$ardvark_pid" =~ "[0-9]*" "aardvark pid not found" run_helper ps "$aardvark_pid" assert "${lines[1]}" =~ ".*aardvark-dns --config $NETAVARK_TMPDIR/config/aardvark-dns -p $dns_port run" "aardvark not running or bad options" # test redirection actually works run_in_container_netns dig +short "somename.dns.podman" @10.89.3.1 A "somename.dns.podman" @10.89.3.1 AAAA assert "${lines[0]}" =~ "10.89.3.2" "ipv4 dns resolution works 1/2" assert "${lines[1]}" =~ "fd10:88:a::2" "ipv6 dns resolution works 2/2" run_in_container_netns dig +short "somename.dns.podman" @fd10:88:a::1 assert "${lines[0]}" =~ "10.89.3.2" "ipv6 dns resolution works" NETAVARK_FW=firewalld NETAVARK_DNS_PORT="$dns_port" \ run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ teardown $(get_container_netns_path) # check iptables got removed run_in_host_netns firewall-cmd --policy netavark_portfwd --list-rich-rules assert "${#lines[@]}" = 0 "rich rules did not get removed on teardown" # check aardvark config got cleared, process killed expected_rc=2 run_helper ls "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" expected_rc=1 run_helper ps "$aardvark_pid" } @test "$fw_driver - check error message from netns thread" { # create interface in netns to force error run_in_container_netns ip link add eth0 type dummy expected_rc=1 run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json setup $(get_container_netns_path) assert_json ".error" "create veth pair: interface eth0 already exists on container namespace: Netlink error: File exists (os error 17)" "interface exists on netns" } @test "$fw_driver - port forwarding ipv4 - tcp" { test_port_fw } @test "$fw_driver - port forwarding ipv6 - tcp" { test_port_fw ip=6 } @test "$fw_driver - port forwarding dualstack - tcp" { test_port_fw ip=dual } @test "$fw_driver - port forwarding ipv4 - udp" { test_port_fw proto=udp } @test "$fw_driver - port forwarding ipv6 - udp" { test_port_fw ip=6 proto=udp } @test "$fw_driver - port forwarding dualstack - udp" { test_port_fw ip=dual proto=udp } @test "$fw_driver - port forwarding ipv4 - sctp" { setup_sctp_kernel_module test_port_fw proto=sctp } @test "$fw_driver - port forwarding ipv6 - sctp" { setup_sctp_kernel_module test_port_fw ip=6 proto=sctp } @test "$fw_driver - port forwarding dualstack - sctp" { setup_sctp_kernel_module test_port_fw ip=dual proto=sctp } @test "$fw_driver - port range forwarding ipv4 - tcp" { test_port_fw range=3 } @test "$fw_driver - port range forwarding ipv6 - tcp" { test_port_fw ip=6 range=3 } @test "$fw_driver - port range forwarding ipv4 - udp" { test_port_fw proto=udp range=3 } @test "$fw_driver - port range forwarding ipv6 - udp" { test_port_fw ip=6 proto=udp range=3 } @test "$fw_driver - port range forwarding dual - udp" { test_port_fw ip=dual proto=udp range=3 } @test "$fw_driver - port range forwarding dual - tcp" { test_port_fw ip=dual proto=tcp range=3 } @test "$fw_driver - port forwarding with hostip ipv4 - tcp" { add_dummy_interface_on_host dummy0 "172.16.0.1/24" test_port_fw hostip="172.16.0.1" } @test "$fw_driver - port forwarding with hostip ipv4 dual stack - tcp" { add_dummy_interface_on_host dummy0 "172.16.0.1/24" test_port_fw ip=dual hostip="172.16.0.1" } @test "$fw_driver - port forwarding with hostip ipv6 - tcp" { add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" test_port_fw ip=6 hostip="fd65:8371:648b:0c06::1" } @test "$fw_driver - port forwarding with hostip ipv6 dual stack - tcp" { add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" test_port_fw ip=dual hostip="fd65:8371:648b:0c06::1" } @test "$fw_driver - port forwarding with hostip ipv4 - udp" { add_dummy_interface_on_host dummy0 "172.16.0.1/24" test_port_fw proto=udp hostip="172.16.0.1" } @test "$fw_driver - port forwarding with hostip ipv6 - udp" { add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" test_port_fw ip=6 proto=udp hostip="fd65:8371:648b:0c06::1" } @test "$fw_driver - port forwarding with wildcard hostip ipv4 - tcp" { add_dummy_interface_on_host dummy0 "172.16.0.1/24" test_port_fw hostip="0.0.0.0" connectip="172.16.0.1" } @test "$fw_driver - port forwarding with wildcard hostip ipv4 dual stack - tcp" { add_dummy_interface_on_host dummy0 "172.16.0.1/24" test_port_fw ip=dual hostip="0.0.0.0" connectip="172.16.0.1" } @test "$fw_driver - port forwarding with wildcard hostip ipv6 - tcp" { add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" test_port_fw ip=6 hostip="::" connectip="fd65:8371:648b:0c06::1" } @test "$fw_driver - port forwarding with wildcard hostip ipv6 dual stack - tcp" { add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" test_port_fw ip=dual hostip="::" connectip="fd65:8371:648b:0c06::1" } @test "netavark error - invalid host_ip in port mappings" { expected_rc=1 run_netavark -f ${TESTSDIR}/testfiles/invalid-port.json setup $(get_container_netns_path) assert_json ".error" "invalid host ip \"abcd\" provided for port 8080" "host ip error" } containers-netavark-83edb4b/test/300-macvlan.bats000066400000000000000000000304021452673426700217570ustar00rootroot00000000000000#!/usr/bin/env bats -*- bats -*- # # macvlan driver test # load helpers function setup() { basic_setup # create a extra interface which we can use to connect the macvlan to run_in_host_netns ip link add dummy0 type dummy } @test "simple macvlan setup" { run_netavark --file ${TESTSDIR}/testfiles/macvlan.json setup $(get_container_netns_path) result="$output" mac=$(jq -r '.podman.interfaces.eth0.mac_address' <<< "$result" ) # check that interface exists run_in_container_netns ip -j --details link show eth0 link_info="$output" assert_json "$link_info" ".[].address" "==" "$mac" "MAC matches container mac" assert_json "$link_info" '.[].flags[] | select(.=="UP")' "==" "UP" "Container interface is up" assert_json "$link_info" ".[].linkinfo.info_kind" "==" "macvlan" "Container interface is a macvlan device" ipaddr="10.88.0.2/16" run_in_container_netns ip addr show eth0 assert "$output" "=~" "$ipaddr" "IP address matches container address" assert_json "$result" ".podman.interfaces.eth0.subnets[0].ipnet" "==" "$ipaddr" "Result contains correct IP address" # check gateway assignment run_in_container_netns ip r assert "$output" "=~" "default via 10.88.0.1" "gateway must be there in default route" assert_json "$result" ".podman.interfaces.eth0.subnets[0].gateway" == "10.88.0.1" "Result contains gateway address" run_in_container_netns cat /proc/sys/net/ipv6/conf/eth0/autoconf assert "0" "autoconf is disabled" run_netavark --file ${TESTSDIR}/testfiles/macvlan.json teardown $(get_container_netns_path) assert "" "no errors" } @test "macvlan setup with static routes" { # add second interface and routes through that interface to test proper teardown run_in_container_netns ip link add type dummy run_in_container_netns ip a add 10.91.0.10/24 dev dummy0 run_in_container_netns ip link set dummy0 up run_netavark --file ${TESTSDIR}/testfiles/macvlan-staticroutes.json setup $(get_container_netns_path) # check static routes run_in_container_netns ip r assert "$output" "=~" "10.89.0.0/24 via 10.88.0.2" "static route not set" assert "$output" "=~" "10.90.0.0/24 via 10.88.0.3" "static route not set" assert "$output" "=~" "10.92.0.0/24 via 10.91.0.1" "static route not set" run_in_container_netns ip -6 r assert "$output" "=~" "fd:2f2f::/64 via fd:1f1f::20" "static route not set" run_netavark --file ${TESTSDIR}/testfiles/macvlan-staticroutes.json teardown $(get_container_netns_path) assert "" "no errors" # check static routes get removed run_in_container_netns ip r assert "$output" "!~" "10.89.0.0/24 via 10.88.0.2" "static route not removed" assert "$output" "!~" "10.90.0.0/24 via 10.88.0.3" "static route not removed" assert "$output" "!~" "10.92.0.0/24 via 10.91.0.1" "static route not removed" run_in_container_netns ip -6 r assert "$output" "!~" "fd:2f2f::/64 via fd:1f1f::20" "static route not removed" run_in_container_netns ip link delete dummy0 } @test "macvlan setup no default route" { run_netavark --file ${TESTSDIR}/testfiles/macvlan-nodefaultroute.json setup $(get_container_netns_path) run_in_container_netns ip r assert "$output" "!~" "default" "default route exists" run_in_container_netns ip -6 r assert "$output" "!~" "default" "default route exists" run_netavark --file ${TESTSDIR}/testfiles/macvlan-nodefaultroute.json teardown $(get_container_netns_path) assert "" "no errors" } @test "macvlan setup internal" { run_netavark --file ${TESTSDIR}/testfiles/macvlan-internal.json setup $(get_container_netns_path) result="$output" mac=$(jq -r '.podman.interfaces.eth0.mac_address' <<< "$result" ) # check that interface exists run_in_container_netns ip -j --details link show eth0 link_info="$output" assert_json "$link_info" ".[].address" "==" "$mac" "MAC matches container mac" assert_json "$link_info" '.[].flags[] | select(.=="UP")' "==" "UP" "Container interface is up" assert_json "$link_info" ".[].linkinfo.info_kind" "==" "macvlan" "Container interface is a macvlan device" ipaddr="10.88.0.2/16" run_in_container_netns ip addr show eth0 assert "$output" "=~" "$ipaddr" "IP address matches container address" assert_json "$result" ".podman.interfaces.eth0.subnets[0].ipnet" "==" "$ipaddr" "Result contains correct IP address" # internal macvlan must not contain run_in_container_netns ip r assert "$output" !~ 'default' "macvlan must not contain default gateway in route at all" } @test "macvlan setup with mtu" { run_netavark --file ${TESTSDIR}/testfiles/macvlan-mtu.json setup $(get_container_netns_path) result="$output" mac=$(jq -r '.podman.interfaces.eth0.mac_address' <<< "$result" ) # check that interface exists run_in_container_netns ip -j --details link show eth0 link_info="$output" assert_json "$link_info" ".[].mtu" "==" "1400" "MTU matches configured MTU" assert_json "$link_info" ".[].address" "==" "$mac" "MAC matches container mac" assert_json "$link_info" '.[].flags[] | select(.=="UP")' "==" "UP" "Container interface is up" assert_json "$link_info" ".[].linkinfo.info_kind" "==" "macvlan" "Container interface is a macvlan device" ipaddr="10.88.0.2" run_in_container_netns ip -j addr show eth0 link_info="$output" assert_json "$link_info" ".[].addr_info[0].local" "==" "$ipaddr" "IP address matches container address" assert_json "$link_info" ".[].addr_info[0].prefixlen" "==" "16" "IP prefix matches container subnet" assert_json "$result" ".podman.interfaces.eth0.subnets[0].ipnet" "==" "$ipaddr/16" "Result contains correct IP address" } @test "macvlan modes" { for mode in bridge private vepa passthru source; do # echo here so we know which test failed echo "mode $mode" read -r -d '\0' config <"$NETAVARK_TMPDIR/firewalld.log" & FIREWALLD_PID=$! echo "firewalld pid: $FIREWALLD_PID" # wait for firewalld to become ready timeout=5 while [ $timeout -gt 0 ]; do # query firewalld with firewall-cmd expected_rc="?" run_in_host_netns firewall-cmd --state if [ "$status" -eq 0 ]; then break fi sleep 1 timeout=$(($timeout - 1)) if [ $timeout -eq 0 ]; then cat "$NETAVARK_TMPDIR/firewalld.log" die "failed to start firewalld - timeout" fi done } function teardown_firewalld() { if [ -n "${NETAVARK_FIREWALLD_RELOAD_PID}" ]; then kill -9 $NETAVARK_FIREWALLD_RELOAD_PID fi if [ -n "${FIREWALLD_PID}" ]; then kill -9 $FIREWALLD_PID fi if [ -n "${DBUS_PID}" ]; then kill -9 $DBUS_PID fi unset DBUS_SYSTEM_BUS_ADDRESS } # Provide the above as default methods. function setup() { basic_setup } function teardown() { basic_teardown } function create_netns() { # create a new netns and mountns and run a sleep process to keep it alive # we have to redirect stdout/err to /dev/null otherwise bats will hang unshare -nm sleep inf &>/dev/null & echo $! } function get_container_netns_path() { local which="0" if [[ $# -eq 1 ]]; then which=$1 fi echo /proc/"${CONTAINER_NS_PIDS[$which]}"/ns/net } ################ # run_netavark # Invoke $NETAVARK, with timeout, using BATS 'run' ################ # # This is the preferred mechanism for invoking netavark: first, it # it joins the test network namespace before it invokes $NETAVARK, # which may be 'netavark' or '/some/path/netavark'. function run_netavark() { run_in_host_netns $NETAVARK --rootless "$rootless" \ --config "$NETAVARK_TMPDIR/config" "$@" } function run_netavark_firewalld_reload() { # need to use nsetner as this will be run in the background nsenter -n -t $HOST_NS_PID $NETAVARK --config "$NETAVARK_TMPDIR/config" firewalld-reload & NETAVARK_FIREWALLD_RELOAD_PID=$! } ################ # run_in_container_netns # Run args in container netns ################ # function run_in_container_netns() { local i="0" isnum='^[0-9]+$' if [[ $1 =~ $isnum ]]; then i=$1 shift 1 fi run_helper nsenter -n -m -w -t "${CONTAINER_NS_PIDS[$i]}" "$@" } ################ # run_in_host_netns # Run args in host netns ################ function run_in_host_netns() { run_helper nsenter -n -m -w -t $HOST_NS_PID "$@" } #### Functions below are taken from podman and buildah and adapted to netavark. ################ # run_helper # Invoke args, with timeout, using BATS 'run' ################ # # Second, we use 'timeout' to abort (with a diagnostic) if something # takes too long; this is preferable to a CI hang. # # Third, we log the command run and its output. This doesn't normally # appear in BATS output, but it will if there's an error. # # Next, we check exit status. Since the normal desired code is 0, # that's the default; but the expected_rc var can override: # # expected_rc=125 run_helper nonexistent-subcommand # expected_rc=? run_helper some-other-command # let our caller check status # # Since we use the BATS 'run' mechanism, $output and $status will be # defined for our caller. # function run_helper() { # expected_rc if unset set default to 0 expected_rc="${expected_rc-0}" if [ "$expected_rc" == "?" ]; then expected_rc= fi # Remember command args, for possible use in later diagnostic messages MOST_RECENT_COMMAND="$*" # stdout is only emitted upon error; this echo is to help a debugger echo "$_LOG_PROMPT $*" # BATS hangs if a subprocess remains and keeps FD 3 open; this happens # if a process crashes unexpectedly without cleaning up subprocesses. run timeout --foreground -v --kill=10 10 "$@" 3>/dev/null # without "quotes", multiple lines are glommed together into one if [ -n "$output" ]; then echo "$output" fi if [ "$status" -ne 0 ]; then echo -n "[ rc=$status " if [ -n "$expected_rc" ]; then if [ "$status" -eq "$expected_rc" ]; then echo -n "(expected) " else echo -n "(** EXPECTED $expected_rc **) " fi fi echo "]" fi if [ "$status" -eq 124 ]; then if expr "$output" : ".*timeout: sending" >/dev/null; then # It's possible for a subtest to _want_ a timeout if [[ "$expected_rc" != "124" ]]; then echo "*** TIMED OUT ***" false fi fi fi if [ -n "$expected_rc" ]; then if [ "$status" -ne "$expected_rc" ]; then die "exit code is $status; expected $expected_rc" fi fi # unset unset expected_rc } ######### # die # Abort with helpful message ######### function die() { # FIXME: handle multi-line output echo "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" >&2 echo "#| FAIL: $*" >&2 echo "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" >&2 false } ############ # assert # Compare actual vs expected string; fail if mismatch ############ # # Compares string (default: $output) against the given string argument. # By default we do an exact-match comparison against $output, but there # are two different ways to invoke us, each with an optional description: # # xpect "EXPECT" [DESCRIPTION] # xpect "RESULT" "OP" "EXPECT" [DESCRIPTION] # # The first form (one or two arguments) does an exact-match comparison # of "$output" against "EXPECT". The second (three or four args) compares # the first parameter against EXPECT, using the given OPerator. If present, # DESCRIPTION will be displayed on test failure. # # Examples: # # xpect "this is exactly what we expect" # xpect "${lines[0]}" =~ "^abc" "first line begins with abc" # function assert() { local actual_string="$output" local operator='==' local expect_string="$1" local testname="$2" case "${#*}" in 0) die "Internal error: 'assert' requires one or more arguments" ;; 1 | 2) ;; 3 | 4) actual_string="$1" operator="$2" expect_string="$3" testname="$4" ;; *) die "Internal error: too many arguments to 'assert'" ;; esac # Comparisons. # Special case: there is no !~ operator, so fake it via '! x =~ y' local not= local actual_op="$operator" if [[ $operator == '!~' ]]; then not='!' actual_op='=~' fi if [[ $operator == '=' || $operator == '==' ]]; then # Special case: we can't use '=' or '==' inside [[ ... ]] because # the right-hand side is treated as a pattern... and '[xy]' will # not compare literally. There seems to be no way to turn that off. if [ "$actual_string" = "$expect_string" ]; then return fi elif [[ $operator == '!=' ]]; then # Same special case as above if [ "$actual_string" != "$expect_string" ]; then return fi else if eval "[[ $not \$actual_string $actual_op \$expect_string ]]"; then return elif [ $? -gt 1 ]; then die "Internal error: could not process 'actual' $operator 'expect'" fi fi # Test has failed. Get a descriptive test name. if [ -z "$testname" ]; then testname="${MOST_RECENT_BUILDAH_COMMAND:-[no test name given]}" fi # Display optimization: the typical case for 'expect' is an # exact match ('='), but there are also '=~' or '!~' or '-ge' # and the like. Omit the '=' but show the others; and always # align subsequent output lines for ease of comparison. local op='' local ws='' if [ "$operator" != '==' ]; then op="$operator " ws=$(printf "%*s" ${#op} "") fi # This is a multi-line message, which may in turn contain multi-line # output, so let's format it ourself, readably local actual_split IFS=$'\n' read -rd '' -a actual_split <<<"$actual_string" || true printf "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" >&2 printf "#| FAIL: %s\n" "$testname" >&2 printf "#| expected: %s'%s'\n" "$op" "$expect_string" >&2 printf "#| actual: %s'%s'\n" "$ws" "${actual_split[0]}" >&2 local line for line in "${actual_split[@]:1}"; do printf "#| > %s'%s'\n" "$ws" "$line" >&2 done printf "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" >&2 false } ################# # assert_json # Compare actual json vs expected string; fail if mismatch ################# # assert_json works like assert except that it accepts one extra parameter, # the jq query string. # There are two different ways to invoke us, each with an optional description: # # xpect "JQ_QUERY" "EXPECT" [DESCRIPTION] # xpect "JSON_STRING" "JQ_QUERY" "OP" "EXPECT" [DESCRIPTION] # Important this function will overwrite $output, so if you need to use the value # more than once you need to safe it in another variable. function assert_json() { local actual_json="$output" local operator='==' local jq_query="$1" local expect_string="$2" local testname="$3" case "${#*}" in 0 | 1) die "Internal error: 'assert_json' requires two or more arguments" ;; 2 | 3) ;; 4 | 5) actual_json="$1" jq_query="$2" operator="$3" expect_string="$4" testname="$5" ;; *) die "Internal error: too many arguments to 'assert_json'" ;; esac run_helper jq -r "$jq_query" <<<"$actual_json" assert "$output" "$operator" "$expect_string" "$testname" } ################## # test_port_fw # test port forwarding ################## # test port forwarding # by default this will create a ipv4 config with tcp as protocol # # The following arguments are supported, the order does not matter: # ip={4, 6, dual} # proto={tcp,udp,sctp} or some comma separated list of the protocols # hostip=$ip the ip which is used for binding on the host # hostport=$port the port which is binded on the host # containerport=$port the port which is binded in the container # range=$num >=1 specify a port range which will forward hostport+range ports # connectip=$ip the ip which is used to connect to in the ncat test # firewalld_reload={false,true} call firewall-cmd --reload to check for port rules # function test_port_fw() { local ipv4=true local ipv6=false local proto=tcp local host_ip="" local host_port="" local container_port="" local range=1 local connect_ip="" local firewalld_reload=false # parse arguments while [[ "$#" -gt 0 ]]; do IFS='=' read -r arg value <<<"$1" case "$arg" in ip) case "$value" in 4) ipv4=true ;; 6) ipv6=true ipv4=false ;; dual) ipv6=true ;; *) die "unknown argument '$value' for ip=" ;; esac ;; proto) proto="$value" ;; hostip) host_ip="$value" ;; connectip) connect_ip="$value" ;; hostport) host_port="$value" ;; containerport) container_port="$value" ;; range) range="$value" ;; firewalld_reload) firewalld_reload="$value" ;; *) die "unknown argument for '$arg' test_port_fw" ;; esac shift done if [ -z "$host_port" ]; then host_port=$(random_port) fi if [ -z "$container_port" ]; then container_port=$(random_port) fi local container_id=$(random_string 64) local container_name="name-$(random_string 10)" local static_ips="" local subnets="" if [ $ipv4 = true ]; then ipv4_subnet=$(random_subnet) ipv4_gateway=$(gateway_from_subnet $ipv4_subnet) ipv4_container_ip=$(random_ip_in_subnet $ipv4_subnet) static_ips="\"$ipv4_container_ip\"" subnets="{\"subnet\":\"$ipv4_subnet\",\"gateway\":\"$ipv4_gateway\"}" fi if [ $ipv6 = true ]; then ipv6_subnet=$(random_subnet 6) ipv6_gateway=$(gateway_from_subnet $ipv6_subnet) ipv6_container_ip=$(random_ip_in_subnet $ipv6_subnet) if [ $ipv4 = true ]; then # add comma for the json static_ips="$static_ips, " subnets="$subnets, " fi static_ips="$static_ips\"$ipv6_container_ip\"" subnets="$subnets {\"subnet\":\"$ipv6_subnet\",\"gateway\":\"$ipv6_gateway\"}" fi read -r -d '\0' config <"$NETAVARK_TMPDIR/nc-out" <$stdin & # make sure to wait until port is bound otherwise test can flake # https://github.com/containers/netavark/issues/433 if [ "$proto" = "tcp" ] || [ "$proto" = "udp" ]; then wait_for_port "${CONTAINER_NS_PIDS[$container_ns]}" $container_port $proto else # TODO add support for sctp port reading from /proc/net/sctp/eps, # for now just sleep sleep 0.5 fi data=$(random_string) run_in_host_netns nc $nc_common_args $connect_ip $host_port <<<"$data" got=$(cat "$NETAVARK_TMPDIR/nc-out") assert "$got" == "$data" "ncat received data" } ################# # random_port # get a random port number between 1-32768 ################# function random_port() { printf $(($RANDOM + 1)) } ################### # random_string # Pseudorandom alphanumeric string of given length ################### function random_string() { local length=${1:-10} head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length } ################### # random_subnet # generate a random private subnet ################### # # by default it will return a 10.x.x.0/24 ipv4 subnet # if "6" is given as first argument it will return a "fdx:x:x:x::/64" ipv6 subnet function random_subnet() { if [[ "$1" == "6" ]]; then printf "fd%x:%x:%x:%x::/64" $((RANDOM % 256)) $((RANDOM % 65535)) $((RANDOM % 65535)) $((RANDOM % 65535)) else printf "10.%d.%d.0/24" $((RANDOM % 256)) $((RANDOM % 256)) fi } ######################### # random_ip_in_subnet # get a random from a given subnet ######################### # the first arg must be an subnet created by random_subnet # otherwise this function might return an invalid ip function random_ip_in_subnet() { # first trim subnet local net_ip=${1%/*} local num= # if ip has colon it is ipv6 if [[ "$net_ip" == *":"* ]]; then # make sure to not get 0 or 1 num=$(printf "%x" $((RANDOM % 65533 + 2))) else # if ipv4 we have to trim the final 0 net_ip=${net_ip%0} # make sure to not get 0, 1 or 255 num=$(printf "%d" $((RANDOM % 252 + 2))) fi printf "$net_ip%s" $num } ######################### # random_ip_in_subnet # get the first ip from a given subnet ######################### # the first arg must be an subnet created by random_subnet # otherwise this function might return an invalid ip function gateway_from_subnet() { # first trim subnet local net_ip=${1%/*} # set first ip in network as gateway local num=1 # if ip has dor it is ipv4 if [[ "$net_ip" == *"."* ]]; then # if ipv4 we have to trim the final 0 net_ip=${net_ip%0} fi printf "$net_ip%s" $num } ############################## # setup_sctp_kernel_module # ############################## # tries to load the sctp kernel module if possible # otherwise it will skip the test function setup_sctp_kernel_module() { modprobe sctp || skip "cannot load sctp kernel module" } ################################# # add_dummy_interface_on_host # ################################# # create a dummy interface with the given name and subnet # the first arg is the name # the second arg is the subnet (optional) function add_dummy_interface_on_host() { name="$1" ipaddr="$2" run_in_host_netns ip link add "$name" type dummy if [ -n "$ipaddr" ]; then run_in_host_netns ip addr add "$ipaddr" dev "$name" fi run_in_host_netns ip link set "$name" up } ### Below functions are taken from podman system tests, ### see Stefano Brivio's commit https://github.com/containers/podman/pull/16141/commits/ea4f168b3a6603991f2cbdc2dcfe6268a46bf1ba # ipv6_to_procfs() - RFC 5952 IPv6 address text representation to procfs format # $1: Address in any notation described by RFC 5952 function ipv6_to_procfs() { local addr="${1}" # Add leading zero if missing case ${addr} in "::"*) addr=0"${addr}" ;; esac # Double colon can mean any number of all-zero fields. Expand to fill # as many colons as are missing. (This will not be a valid IPv6 form, # but we don't need it for long). E.g., 0::1 -> 0:::::::1 case ${addr} in *"::"*) # All the colons in the address local colons colons=$(tr -dc : <<<$addr) # subtract those from a string of eight colons; this gives us # a string of two to six colons... local pad pad=$(sed -e "s/$colons//" <<<":::::::") # ...which we then inject in place of the double colon. addr=$(sed -e "s/::/::$pad/" <<<$addr) ;; esac # Print as a contiguous string of zero-filled 16-bit words # (The additional ":" below is needed because 'read -d x' actually # means "x is a TERMINATOR, not a delimiter") local group while read -d : group; do printf "%04X" "0x${group:-0}" done <<<"${addr}:" } # __ipv4_to_procfs() - Print bytes in hexadecimal notation reversing arguments # $@: IPv4 address as separate bytes function __ipv4_to_procfs() { printf "%02X%02X%02X%02X" ${4} ${3} ${2} ${1} } # ipv4_to_procfs() - IPv4 address representation to big-endian procfs format # $1: Text representation of IPv4 address function ipv4_to_procfs() { IFS='.' __ipv4_to_procfs ${1} } # port_is_bound() - Check if TCP or UDP port is bound for a given address # $1: Netns PID # $2: Port number # $3: Optional protocol, or optional IPv4 or IPv6 address, default: tcp # $4: Optional IPv4 or IPv6 address, or optional protocol, default: any function port_is_bound() { local pid=$1 local port=${2?Usage: port_is_bound PORT [tcp|udp] [ADDRESS]} if [ "${3}" = "tcp" ] || [ "${3}" = "udp" ]; then local address="${4}" local proto="${3}" elif [ "${4}" = "tcp" ] || [ "${4}" = "udp" ]; then local address="${3}" local proto="${4}" else local address="${3}" # Might be empty local proto="tcp" fi port=$(printf %04X ${port}) case "${address}" in *":"*) nsenter -n -t $pid grep -e "^[^:]*: $(ipv6_to_procfs "${address}"):${port} .*" \ -e "^[^:]*: $(ipv6_to_procfs "::0"):${port} .*" \ -q "/proc/net/${proto}6" ;; *"."*) nsenter -n -t $pid grep -e "^[^:]*: $(ipv4_to_procfs "${address}"):${port}" \ -e "^[^:]*: $(ipv4_to_procfs "0.0.0.0"):${port}" \ -q "/proc/net/${proto}" ;; *) # No address: check both IPv4 and IPv6, for any bound address nsenter -n -t $pid grep "^[^:]*: [^:]*:${port} .*" -q "/proc/net/${proto}6" || \ nsenter -n -t $pid grep "^[^:]*: [^:]*:${port} .*" -q "/proc/net/${proto}" ;; esac } # port_is_free() - Check if TCP or UDP port is free to bind for a given address # $1: Netns PID # $2: Port number # $3: Optional protocol, or optional IPv4 or IPv6 address, default: tcp # $4: Optional IPv4 or IPv6 address, or optional protocol, default: any function port_is_free() { ! port_is_bound ${@} } # wait_for_port() - Return when port is binded # $1: Netns PID # $2: Port number # $3: Optional protocol, or optional IPv4 or IPv6 address, default: tcp # $4: Optional IPv4 or IPv6 address, or optional protocol, default: any # $5: Optional timeout, 5 seconds if not given function wait_for_port() { local pid=$1 local port=$2 local proto=$3 local host=$4 local _timeout=${5:-5} # Wait while [ $_timeout -gt 0 ]; do port_is_bound ${pid} ${port} ${proto} ${host} && return sleep 1 _timeout=$(( $_timeout - 1 )) done die "Timed out waiting for $host:$port" } containers-netavark-83edb4b/test/testfiles/000077500000000000000000000000001452673426700211665ustar00rootroot00000000000000containers-netavark-83edb4b/test/testfiles/bridge-nodefaultroute.json000066400000000000000000000015071452673426700263560ustar00rootroot00000000000000{ "container_id": "6ce776ea58b5", "container_name": "testcontainer", "networks": { "podman": { "interface_name": "eth0", "static_ips": [ "10.88.0.2" ] } }, "network_info": { "podman": { "dns_enabled": false, "driver": "bridge", "id": "53ce4390f2adb1681eb1a90ec8b48c49c015e0a8d336c197637e7f65e365fa9e", "internal": false, "ipv6_enabled": false, "name": "podman", "network_interface": "podman0", "subnets": [ { "gateway": "10.88.0.1", "subnet": "10.88.0.0/16" } ], "options": { "no_default_route": "true" } } } } containers-netavark-83edb4b/test/testfiles/bridge-staticroutes.json000066400000000000000000000022431452673426700260450ustar00rootroot00000000000000{ "container_id": "6ce776ea58b5", "container_name": "testcontainer", "networks": { "podman": { "interface_name": "eth0", "static_ips": [ "10.88.0.2" ] } }, "network_info": { "podman": { "dns_enabled": false, "driver": "bridge", "id": "53ce4390f2adb1681eb1a90ec8b48c49c015e0a8d336c197637e7f65e365fa9e", "internal": false, "ipv6_enabled": false, "name": "podman", "network_interface": "podman0", "subnets": [ { "gateway": "10.88.0.1", "subnet": "10.88.0.0/16" } ], "routes": [ { "destination": "10.89.0.0/24", "gateway": "10.88.0.2" }, { "destination": "10.90.0.0/24", "gateway": "10.88.0.3" }, { "destination": "10.92.0.0/24", "gateway": "10.91.0.1" } ] } } } containers-netavark-83edb4b/test/testfiles/dualstack-bridge-custom-dns-server.json000066400000000000000000000020731452673426700306660ustar00rootroot00000000000000{ "container_id": "f031bf33eecba75d0d84952337b1ceef6a239eb8e94b48aee0993d0791345325", "container_name": "somename", "dns_servers": ["8.8.8.8"], "networks": { "podman1": { "static_ips": [ "10.89.3.2", "fd10:88:a::2" ], "interface_name": "eth0" } }, "network_info": { "podman1": { "name": "podman1", "id": "ec79dd0cad82083c8ac5cc23e9542e4ddea813dff60d68258d36e84f6393b63b", "driver": "bridge", "network_interface": "podman1", "subnets": [ { "subnet": "10.89.3.0/24", "gateway": "10.89.3.1" }, { "subnet": "fd10:88:a::/64", "gateway": "fd10:88:a::1" } ], "ipv6_enabled": true, "internal": false, "dns_enabled": true, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/test/testfiles/dualstack-bridge-multiple-custom-dns-server.json000066400000000000000000000021061452673426700325140ustar00rootroot00000000000000{ "container_id": "f031bf33eecba75d0d84952337b1ceef6a239eb8e94b48aee0993d0791345325", "container_name": "somename", "dns_servers": ["8.8.8.8", "1.1.1.1"], "networks": { "podman1": { "static_ips": [ "10.89.3.2", "fd10:88:a::2" ], "interface_name": "eth0" } }, "network_info": { "podman1": { "name": "podman1", "id": "ec79dd0cad82083c8ac5cc23e9542e4ddea813dff60d68258d36e84f6393b63b", "driver": "bridge", "network_interface": "podman1", "subnets": [ { "subnet": "10.89.3.0/24", "gateway": "10.89.3.1" }, { "subnet": "fd10:88:a::/64", "gateway": "fd10:88:a::1" } ], "ipv6_enabled": true, "internal": false, "dns_enabled": true, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/test/testfiles/dualstack-bridge-network-container-dns-server.json000066400000000000000000000017171452673426700330310ustar00rootroot00000000000000{ "container_id": "f031bf33eecba75d0d84952337b1ceef6a239eb8e94b48aee0993d0791345325", "container_name": "somename", "dns_servers": [ "8.8.8.8", "1.1.1.1" ], "networks": { "podman1": { "static_ips": [ "10.89.3.2", "fd10:88:a::2" ], "interface_name": "eth0" } }, "network_info": { "podman1": { "name": "podman1", "id": "ec79dd0cad82083c8ac5cc23e9542e4ddea813dff60d68258d36e84f6393b63b", "driver": "bridge", "network_interface": "podman1", "subnets": [ { "subnet": "10.89.3.0/24", "gateway": "10.89.3.1" }, { "subnet": "fd10:88:a::/64", "gateway": "fd10:88:a::1" } ], "ipv6_enabled": true, "internal": false, "dns_enabled": true, "network_dns_servers": [ "127.0.0.1", "3.3.3.3" ], "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/test/testfiles/dualstack-bridge.json000066400000000000000000000020331452673426700252640ustar00rootroot00000000000000{ "container_id": "f031bf33eecba75d0d84952337b1ceef6a239eb8e94b48aee0993d0791345325", "container_name": "somename", "networks": { "podman1": { "static_ips": [ "10.89.3.2", "fd10:88:a::2" ], "interface_name": "eth0" } }, "network_info": { "podman1": { "name": "podman1", "id": "ec79dd0cad82083c8ac5cc23e9542e4ddea813dff60d68258d36e84f6393b63b", "driver": "bridge", "network_interface": "podman1", "subnets": [ { "subnet": "10.89.3.0/24", "gateway": "10.89.3.1" }, { "subnet": "fd10:88:a::/64", "gateway": "fd10:88:a::1" } ], "ipv6_enabled": true, "internal": false, "dns_enabled": true, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/test/testfiles/firewalld-dbus.conf000066400000000000000000000066611452673426700247520ustar00rootroot00000000000000 EXTERNAL unix:path=/tmp/dummy containers-netavark-83edb4b/test/testfiles/internal.json000066400000000000000000000013631452673426700237000ustar00rootroot00000000000000{ "container_id": "6ce776ea58b5", "container_name": "testcontainer", "networks": { "podman": { "interface_name": "eth0", "static_ips": [ "10.88.0.2" ] } }, "network_info": { "podman": { "dns_enabled": false, "driver": "bridge", "id": "53ce4390f2adb1681eb1a90ec8b48c49c015e0a8d336c197637e7f65e365fa9e", "internal": true, "ipv6_enabled": false, "name": "podman", "network_interface": "podman0", "subnets": [ { "gateway": "10.88.0.1", "subnet": "10.88.0.0/16" } ] } } } containers-netavark-83edb4b/test/testfiles/invalid-port.json000066400000000000000000000016661452673426700245020ustar00rootroot00000000000000{ "container_id": "6ce776ea58b5", "container_name": "testcontainer", "port_mappings": [ { "host_ip": "abcd", "container_port": 8080, "host_port": 8080, "range": 1, "protocol": "tcp" } ], "networks": { "podman": { "interface_name": "eth0", "static_ips": [ "10.88.0.2" ] } }, "network_info": { "podman": { "dns_enabled": false, "driver": "bridge", "id": "53ce4390f2adb1681eb1a90ec8b48c49c015e0a8d336c197637e7f65e365fa9e", "internal": false, "ipv6_enabled": false, "name": "podman", "network_interface": "podman0", "subnets": [ { "gateway": "10.88.0.1", "subnet": "10.88.0.0/16" } ] } } } containers-netavark-83edb4b/test/testfiles/ipv6-bridge-staticroutes.json000066400000000000000000000025061452673426700267310ustar00rootroot00000000000000{ "container_id": "f031bf33eecba75d0d84952337b1ceef6a239eb8e94b48aee0993d0791345325", "container_name": "somename", "networks": { "podman1": { "static_ips": [ "fd10:88:a::2" ], "interface_name": "eth0" } }, "network_info": { "podman1": { "name": "podman1", "id": "ec79dd0cad82083c8ac5cc23e9542e4ddea813dff60d68258d36e84f6393b63b", "driver": "bridge", "network_interface": "podman1", "subnets": [ { "subnet": "fd10:88:a::/64", "gateway": "fd10:88:a::1" } ], "routes": [ { "destination": "fd10:89:b::/64", "gateway": "fd10:88:a::ac02" }, { "destination": "fd10:89:c::/64", "gateway": "fd10:88:a::ac03" }, { "destination": "fd10:51:b::/64", "gateway": "fd10:49:b::30" } ], "ipv6_enabled": true, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/test/testfiles/ipv6-bridge.json000066400000000000000000000016011452673426700241750ustar00rootroot00000000000000{ "container_id": "f031bf33eecba75d0d84952337b1ceef6a239eb8e94b48aee0993d0791345325", "container_name": "somename", "networks": { "podman1": { "static_ips": [ "fd10:88:a::2" ], "interface_name": "eth0" } }, "network_info": { "podman1": { "name": "podman1", "id": "ec79dd0cad82083c8ac5cc23e9542e4ddea813dff60d68258d36e84f6393b63b", "driver": "bridge", "network_interface": "podman1", "subnets": [ { "subnet": "fd10:88:a::/64", "gateway": "fd10:88:a::1" } ], "ipv6_enabled": true, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/test/testfiles/ipvlan-internal.json000066400000000000000000000014101452673426700251600ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ "10.88.0.2" ], "interface_name": "eth0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "ipvlan", "network_interface": "dummy0", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], "ipv6_enabled": false, "internal": true, "dns_enabled": false, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/test/testfiles/ipvlan-mtu.json000066400000000000000000000015121452673426700241540ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ "10.88.0.2" ], "interface_name": "eth0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "ipvlan", "network_interface": "dummy0", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "host-local" }, "options": { "mtu": "1400" } } } } containers-netavark-83edb4b/test/testfiles/ipvlan-nodefaultroute.json000066400000000000000000000015231452673426700264110ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ "10.88.0.2" ], "interface_name": "eth0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "ipvlan", "network_interface": "dummy0", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": true, "ipam_options": { "driver": "host-local" }, "options": { "no_default_route": "true" } } } } containers-netavark-83edb4b/test/testfiles/ipvlan-staticroutes.json000066400000000000000000000025741452673426700261110ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ "10.88.0.2", "fd:1f1f::2" ], "interface_name": "eth0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "ipvlan", "network_interface": "dummy0", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" }, { "subnet": "fd:1f1f::/64", "gateway": "fd:1f1f::1" } ], "routes": [ { "destination": "10.89.0.0/24", "gateway": "10.88.0.2" }, { "destination": "10.90.0.0/24", "gateway": "10.88.0.3" }, { "destination": "10.92.0.0/24", "gateway": "10.91.0.1" }, { "destination": "fd:2f2f::/64", "gateway": "fd:1f1f::20" } ], "ipv6_enabled": true, "internal": false, "dns_enabled": true, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/test/testfiles/ipvlan.json000066400000000000000000000014101452673426700233460ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ "10.88.0.2" ], "interface_name": "eth0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "ipvlan", "network_interface": "dummy0", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": true, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/test/testfiles/isolate1.json000066400000000000000000000017371452673426700236120ustar00rootroot00000000000000{ "container_id": "01f0b94d5f4c1", "container_name": "isolatedcontainer1", "networks": { "isolate1": { "interface_name": "eth1", "static_ips": [ "10.89.0.2", "fd90::2" ] } }, "network_info": { "isolate1": { "dns_enabled": false, "driver": "bridge", "id": "f3ac3c903b76f20e8a122b1eb2ba393ab2519ba516626be5b490b66dc96b54b7", "internal": false, "ipv6_enabled": false, "name": "isolate1", "network_interface": "isolate1", "subnets": [ { "gateway": "10.89.0.1", "subnet": "10.89.0.0/24" }, { "subnet": "fd90::/64", "gateway": "fd90::1" } ], "options": { "isolate": "true" } } } } containers-netavark-83edb4b/test/testfiles/isolate2.json000066400000000000000000000017371452673426700236130ustar00rootroot00000000000000{ "container_id": "01f0b94d5f4c1", "container_name": "isolatedcontainer1", "networks": { "isolate2": { "interface_name": "eth2", "static_ips": [ "10.89.1.2", "fd99::2" ] } }, "network_info": { "isolate2": { "dns_enabled": false, "driver": "bridge", "id": "f3ac3c903b76f20e8a122b1eb2ba393ab2519ba516626be5b490b66dc96b54b7", "internal": false, "ipv6_enabled": false, "name": "isolate2", "network_interface": "isolate2", "subnets": [ { "gateway": "10.89.1.1", "subnet": "10.89.1.0/24" }, { "subnet": "fd99::/64", "gateway": "fd99::1" } ], "options": { "isolate": "true" } } } } containers-netavark-83edb4b/test/testfiles/isolate3.json000066400000000000000000000017411452673426700236070ustar00rootroot00000000000000{ "container_id": "01f0b94d5f4c1", "container_name": "isolatedcontainer1", "networks": { "isolate3": { "interface_name": "eth3", "static_ips": [ "10.89.2.2", "fd92::2" ] } }, "network_info": { "isolate3": { "dns_enabled": false, "driver": "bridge", "id": "f3ac3c903b76f20e8a122b1eb2ba393ab2519ba516626be5b490b66dc96b54b7", "internal": false, "ipv6_enabled": false, "name": "isolate3", "network_interface": "isolate3", "subnets": [ { "gateway": "10.89.2.1", "subnet": "10.89.2.0/24" }, { "subnet": "fd92::/64", "gateway": "fd92::1" } ], "options": { "isolate": "strict" } } } } containers-netavark-83edb4b/test/testfiles/isolate4.json000066400000000000000000000017411452673426700236100ustar00rootroot00000000000000{ "container_id": "01f0b94d5f4c1", "container_name": "isolatedcontainer1", "networks": { "isolate4": { "interface_name": "eth4", "static_ips": [ "10.89.3.2", "fd93::2" ] } }, "network_info": { "isolate4": { "dns_enabled": false, "driver": "bridge", "id": "f3ac3c903b76f20e8a122b1eb2ba393ab2519ba516626be5b490b66dc96b54b7", "internal": false, "ipv6_enabled": false, "name": "isolate4", "network_interface": "isolate4", "subnets": [ { "gateway": "10.89.3.1", "subnet": "10.89.3.0/24" }, { "subnet": "fd93::/64", "gateway": "fd93::1" } ], "options": { "isolate": "strict" } } } } containers-netavark-83edb4b/test/testfiles/macvlan-dhcp.json000066400000000000000000000012021452673426700244110ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ ], "interface_name": "eth0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "macvlan", "network_interface": "enp9s0u2u1u2", "subnets": [ ], "ipv6_enabled": false, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "dhcp" } } } } containers-netavark-83edb4b/test/testfiles/macvlan-internal.json000066400000000000000000000014111452673426700253110ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ "10.88.0.2" ], "interface_name": "eth0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "macvlan", "network_interface": "dummy0", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], "ipv6_enabled": false, "internal": true, "dns_enabled": false, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/test/testfiles/macvlan-mtu.json000066400000000000000000000015131452673426700243050ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ "10.88.0.2" ], "interface_name": "eth0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "macvlan", "network_interface": "dummy0", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "host-local" }, "options": { "mtu": "1400" } } } } containers-netavark-83edb4b/test/testfiles/macvlan-nodefaultroute.json000066400000000000000000000015241452673426700265420ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ "10.88.0.2" ], "interface_name": "eth0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "macvlan", "network_interface": "dummy0", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": true, "ipam_options": { "driver": "host-local" }, "options": { "no_default_route": "true" } } } } containers-netavark-83edb4b/test/testfiles/macvlan-staticroutes.json000066400000000000000000000025751452673426700262420ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ "10.88.0.2", "fd:1f1f::2" ], "interface_name": "eth0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "macvlan", "network_interface": "dummy0", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" }, { "subnet": "fd:1f1f::/64", "gateway": "fd:1f1f::1" } ], "routes": [ { "destination": "10.89.0.0/24", "gateway": "10.88.0.2" }, { "destination": "10.90.0.0/24", "gateway": "10.88.0.3" }, { "destination": "10.92.0.0/24", "gateway": "10.91.0.1" }, { "destination": "fd:2f2f::/64", "gateway": "fd:1f1f::20" } ], "ipv6_enabled": true, "internal": false, "dns_enabled": true, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/test/testfiles/macvlan.json000066400000000000000000000014111452673426700234770ustar00rootroot00000000000000{ "container_id": "someID", "container_name": "someName", "networks": { "podman": { "static_ips": [ "10.88.0.2" ], "interface_name": "eth0" } }, "network_info": { "podman": { "name": "podman", "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "driver": "macvlan", "network_interface": "dummy0", "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": true, "ipam_options": { "driver": "host-local" } } } } containers-netavark-83edb4b/test/testfiles/metric.json000066400000000000000000000017771452673426700233600ustar00rootroot00000000000000{ "container_id": "bc14fe7cd3633e7be338522002bb0c3ccb18150da7a6c733735ffdf8ff7e85d1", "container_name": "metrictest", "networks": { "metric": { "interface_name": "eth1", "static_ips": [ "10.89.0.2", "fde0::2" ] } }, "network_info": { "metric": { "dns_enabled": false, "driver": "bridge", "id": "7ba44a9a709f8093621eae1a1db2ccafc2471bae19cdf9dd2ea7cf3773b9211c", "internal": false, "ipv6_enabled": true, "name": "metric", "network_interface": "metric", "subnets": [ { "gateway": "10.89.0.1", "subnet": "10.89.0.0/24" }, { "subnet": "fde0::/64", "gateway": "fde0::1" } ], "options": { "metric": "200" } } } } containers-netavark-83edb4b/test/testfiles/simplebridge-vrf.json000066400000000000000000000015131452673426700253220ustar00rootroot00000000000000{ "container_id": "6ce776ea58b5", "container_name": "testcontainer", "networks": { "podman": { "interface_name": "eth0", "static_ips": [ "10.88.0.2" ] } }, "network_info": { "podman": { "dns_enabled": false, "driver": "bridge", "id": "53ce4390f2adb1681eb1a90ec8b48c49c015e0a8d336c197637e7f65e365fa9e", "internal": false, "ipv6_enabled": false, "name": "podman", "network_interface": "podman0", "subnets": [ { "gateway": "10.88.0.1", "subnet": "10.88.0.0/16" } ], "options": { "vrf": "test-vrf" } } } } containers-netavark-83edb4b/test/testfiles/simplebridge.json000066400000000000000000000013641452673426700245330ustar00rootroot00000000000000{ "container_id": "6ce776ea58b5", "container_name": "testcontainer", "networks": { "podman": { "interface_name": "eth0", "static_ips": [ "10.88.0.2" ] } }, "network_info": { "podman": { "dns_enabled": false, "driver": "bridge", "id": "53ce4390f2adb1681eb1a90ec8b48c49c015e0a8d336c197637e7f65e365fa9e", "internal": false, "ipv6_enabled": false, "name": "podman", "network_interface": "podman0", "subnets": [ { "gateway": "10.88.0.1", "subnet": "10.88.0.0/16" } ] } } } containers-netavark-83edb4b/test/testfiles/two-networks.json000066400000000000000000000035641452673426700245540ustar00rootroot00000000000000{ "container_id": "a417588994662895d8b41adf8d74a83ac0cc38eb56d85d8e1268aae1e19e07e1", "container_name": "heuristic_archimedes", "port_mappings": [ { "host_ip": "127.0.0.1", "container_port": 8080, "host_port": 8080, "range": 1, "protocol": "tcp" } ], "networks": { "t1": { "static_ips": [ "10.89.1.2" ], "interface_name": "eth0" }, "t2": { "static_ips": [ "10.89.2.2" ], "interface_name": "eth1" } }, "network_info": { "t1": { "name": "t1", "id": "fae505bba2b3ad2b9bc748f0d322864d44a3c976c642ee347e5276729dfb4d51", "driver": "bridge", "network_interface": "podman2", "created": "2022-10-18T18:34:21.701124201+02:00", "subnets": [ { "subnet": "10.89.1.0/24", "gateway": "10.89.1.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "host-local" } }, "t2": { "name": "t2", "id": "d7322dfb9353cb5c1adabfc46555164654f339fa3dd0d2533d103e2a4c5240e2", "driver": "bridge", "network_interface": "podman3", "created": "2022-10-18T18:34:23.266425802+02:00", "subnets": [ { "subnet": "10.89.2.0/24", "gateway": "10.89.2.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": false, "ipam_options": { "driver": "host-local" } } } }