diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0da8f80 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*] +indent_size = 2 +indent_style = space diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..fd59c10 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,17 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 + +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..ceaa406 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,125 @@ +name: CI + +on: + push: + branches: [ main ] + tags: [ '*' ] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + - aarch64-unknown-linux-gnu + + steps: + - uses: actions/checkout@v4 + + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-${{ matrix.target }}- + + - uses: dtolnay/rust-toolchain@stable + + - name: Install cross + run: wget -cO - https://github.com/cross-rs/cross/releases/latest/download/cross-x86_64-unknown-linux-gnu.tar.gz | tar -xz + + - name: Build + run: ./cross build --release --target ${{ matrix.target }} + + - name: Rename binary + run: | + mv target/${{ matrix.target }}/release/sflow_exporter sflow_exporter_${{ matrix.target }} + + - uses: actions/upload-artifact@v3 + with: + name: sflow_exporter_${{ matrix.target }} + path: sflow_exporter_${{ matrix.target }} + + - uses: crazy-max/ghaction-github-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: sflow_exporter_${{ matrix.target }} + + nix: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: cachix/install-nix-action@v25 + + - run: nix build -L + - run: nix flake check -L + + docker: + needs: build + runs-on: ubuntu-latest + + permissions: + packages: write + + steps: + - uses: actions/checkout@v4 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=edge + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 + + - name: Cache Docker layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + if: github.event_name != 'pull_request' + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + + - name: Build + uses: docker/build-push-action@v5 + with: + platforms: linux/amd64,linux/arm64/v8 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new + + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f43732 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +target/ +*.iml +meta.yaml diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..b196eaa --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +tab_spaces = 2 diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e8ae488 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1159 @@ +# 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 = "anstream" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" + +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "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 = "binrw" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173901312e9850391d4d7c1318c4e099fdc037d61870fca427429830efdb4e5f" +dependencies = [ + "array-init", + "binrw_derive", + "bytemuck", +] + +[[package]] +name = "binrw_derive" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb515fdd6f8d3a357c8e19b8ec59ef53880807864329b1cb1cba5c53bf76557e" +dependencies = [ + "either", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" + +[[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.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9fa1897e4325be0d68d48df6aa1a71ac2ed4d27723887e7754192705350730" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "etherparse" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24890603eb4b43aa788f02261ce21714449033e3e2ab93692f0ab18480c3c9b1" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" + +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "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 = "hyper" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", +] + +[[package]] +name = "indexmap" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inotify" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +dependencies = [ + "bitflags", + "futures-core", + "inotify-sys", + "libc", + "tokio", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[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.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[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 = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "thiserror", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "serde_yaml" +version = "0.9.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sflow_exporter" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "binrw", + "clap", + "etherparse", + "futures-util", + "inotify", + "prometheus", + "serde", + "serde_yaml", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[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.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[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.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +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 = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[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.50", +] + +[[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 = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[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 = [ + "pin-project-lite", + "tracing-core", +] + +[[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 = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4a2fe13 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "sflow_exporter" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap = { version = "4.4", default-features = false, features = ["std", "color", "help", "usage", "error-context", "suggestions", "derive", "env"] } +tokio = { version = "1.36", default-features = false, features = ["macros", "rt-multi-thread", "net", "signal", "fs", "sync"] } +tracing = { version = "0.1", default-features = false, features = ["release_max_level_info"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "ansi"] } +axum = { version = "0.7", default-features = false, features = ["tokio", "http1"] } +etherparse = { version = "0.14", default-features = false, features = ["std"] } +inotify = { version = "0.10", default-features = false, features = ["stream"] } +serde = { version = "1.0", default-features = false, features = ["derive"] } +binrw = { version = "0.13", default-features = false, features = ["std"] } +anyhow = { version = "1.0", default-features = false, features = ["std"] } +futures-util = { version = "0.3", default-features = false } +prometheus = { version = "0.13", default-features = false } +serde_yaml = { version = "0.9", default-features = false } + +[profile.release] +lto = true +codegen-units = 1 +panic = "abort" +strip = true + +# by overriding our dependencies' compilation settings, we can further optimize for size +# https://docs.rust-embedded.org/book/unsorted/speed-vs-size.html#optimizing-dependencies +[profile.release.package."*"] +codegen-units = 1 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dde485f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +FROM rust:slim-bookworm AS builder + +RUN update-ca-certificates + +ENV USER=sflow_exporter +ENV UID=10001 + +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + "${USER}" + +RUN cargo new --bin sflow_exporter + +WORKDIR /sflow_exporter + +COPY ./Cargo.lock ./Cargo.lock +COPY ./Cargo.toml ./Cargo.toml + +RUN cargo build --release \ + && rm src/*.rs target/release/deps/sflow_exporter* + +COPY ./src ./src +RUN cargo build --release + +FROM debian:bookworm-slim + +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /etc/group /etc/group + +WORKDIR /sflow_exporter + +COPY --from=builder /sflow_exporter/target/release/sflow_exporter ./sflow_exporter + +USER sflow_exporter:sflow_exporter + +CMD ["/sflow_exporter/sflow_exporter"] diff --git a/derivation.nix b/derivation.nix new file mode 100644 index 0000000..710e5f8 --- /dev/null +++ b/derivation.nix @@ -0,0 +1,11 @@ +{ pkgs, cargoToml, ... }: +let + manifest = (pkgs.lib.importTOML cargoToml).package; +in +pkgs.rustPlatform.buildRustPackage { + pname = manifest.name; + version = manifest.version; + cargoLock.lockFile = ./Cargo.lock; + src = pkgs.lib.cleanSource ./.; + cargoBuildFlags = "-p ${manifest.name}"; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d06d94e --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1708440434, + "narHash": "sha256-XY+B9mbhL/i+Q6fP6gBQ6P76rv9rWtpjQiUJ+DGtaUg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "526d051b128b82ae045a70e5ff1adf8e6dafa560", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..45a1a63 --- /dev/null +++ b/flake.nix @@ -0,0 +1,33 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem + (system: + let + pkgs = (import nixpkgs) { + inherit system; + }; + in + { + packages = rec { + sflow-exporter = pkgs.callPackage ./derivation.nix { + cargoToml = ./Cargo.toml; + }; + default = sflow_exporter; + }; + } + ) // { + overlays.default = _: prev: { + sflow-exporter = self.packages."${prev.system}".default; + }; + + nixosModules = rec { + sflow-exporter = import ./nixos-modules/default.nix; + default = sflow_exporter; + }; + }; +} diff --git a/module.nix b/module.nix new file mode 100644 index 0000000..1653de4 --- /dev/null +++ b/module.nix @@ -0,0 +1,76 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.services.sflow_exporter; +in +{ + options.services.sflow-exporter = { + package = lib.mkOption { + type = lib.types.package; + default = pkgs.sflow_exporter; + defaultText = lib.literalExpression "pkgs.sflow-exporter"; + description = lib.mdDoc "Which sflow_exporzer derivation to use."; + }; + enable = lib.mkEnableOption (lib.mdDoc "sflow_exporter"); + listen = { + sflow = { + addr = lib.mkOption { + type = lib.types.str; + description = lib.mdDoc "The ip address the sflow listener shuld be listening on."; + default = "::"; + }; + port = lib.mkOption { + type = lib.types.port; + description = lib.mdDoc "The port the sflow listener shuld be listening on."; + default = 6343; + }; + }; + metrics = { + addr = lib.mkOption { + type = lib.types.str; + description = lib.mdDoc "The ip address the metrics listener shuld be listening on."; + default = "::"; + }; + port = lib.mkOption { + type = lib.types.port; + description = lib.mdDoc "The port the metrics listener shuld be listening on."; + default = 9100; + }; + }; + }; + metaPath = lib.mkOption { + type = lib.types.str; + description = lib.mdDoc "The path where the meta configuration file is located."; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.listen.sflow.port ]; + + systemd.services.sflow-exporter = { + description = "sflow_exporter"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + ExecStart = "${cfg.package}/bin/sflow_exporter"; + DynamicUser = true; + User = "sflow_exporter"; + + Environment = + let + sflowAddr = cfg.listen.sflow.addr; + metricsAddr = cfg.listen.metrics.addr; + in + [ + "SFLOW_EXPORTER_SFlOW_LISTEN_ADDR=${if (lib.hasInfix ":" sflowAddr) then "[${sflowAddr}]" else sflowAddr}:${toString cfg.listen.sflow.port}" + "SFLOW_EXPORTER_METRICS_LISTEN_ADDR=${if (lib.hasInfix ":" metricsAddr) then "[${metricsAddr}]" else metricsAddr}:${toString cfg.listen.metrics.port}" + "SFLOW_EXPORTER_META=${cfg.jitsiUrl}" + ]; + }; + }; + }; +} + diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..5b052b6 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,28 @@ +use std::net::SocketAddr; +use std::path::PathBuf; + +use clap::Parser; + +#[derive(Parser)] +pub(super) struct Args { + #[clap( + long, + short, + env = "SFLOW_EXPORTER_META", + default_value = "meta.yaml" + )] + pub(super) meta: PathBuf, + #[clap( + long, + short, + env = "SFLOW_EXPORTER_SFlOW_LISTEN_ADDR", + default_value = "[::]:6343" + )] + pub(super) sflow_listen_addr: SocketAddr, + #[clap( + long, + env = "SFLOW_EXPORTER_METRICS_LISTEN_ADDR", + default_value = "[::]:9100" + )] + pub(super) metrics_listen_addr: SocketAddr, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ab09237 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,197 @@ +use axum::extract::State; +use axum::http::StatusCode; +use axum::routing::get; +use binrw::{BinRead, Endian}; +use clap::Parser; +use futures_util::stream::StreamExt; +use inotify::{Inotify, WatchMask}; +use prometheus::{Registry, TextEncoder}; +use std::future::IntoFuture; +use std::io::Cursor; +use std::path::PathBuf; +use tokio::net::{TcpListener, UdpSocket}; +use tokio::select; +use tokio::sync::mpsc; +use tracing::error; +use tracing::{info, Level}; +use tracing_subscriber::FmtSubscriber; + +use crate::args::Args; +use crate::meta::Meta; +use crate::metrics::Metrics; +use crate::sflow::record::{EthernetHeader, FlowRecord, HeaderProtocol}; +use crate::sflow::sample::Sample; +use crate::sflow::SflowDatagram; +use crate::utils::datagram_buffer; +use crate::utils::shutdown_signal; + +mod args; + +mod meta; +mod metrics; +mod sflow; +mod utils; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let args = Args::parse(); + + let subscriber = FmtSubscriber::builder() + .with_max_level(Level::INFO) + .compact() + .finish(); + + tracing::subscriber::set_global_default(subscriber)?; + + info!(concat!( + "Booting ", + env!("CARGO_PKG_NAME"), + "/", + env!("CARGO_PKG_VERSION"), + "..." + )); + + let (meta_update_tx, meta_update_rx) = mpsc::channel(10); + + let (metrics, registry) = Metrics::new(); + + let socket = UdpSocket::bind(args.sflow_listen_addr).await?; + info!("sflow listening at {}/udp...", args.sflow_listen_addr); + + let listener = TcpListener::bind(args.metrics_listen_addr).await?; + info!( + "metrics listening at http://{}/metrics...", + args.metrics_listen_addr + ); + + let router = axum::Router::new() + .route("/metrics", get(metrics_endpoint)) + .with_state(registry) + .into_make_service(); + + let inotify = { + let meta = args.meta.clone(); + tokio::spawn(async move { + let inotify = Inotify::init()?; + inotify.watches().add(meta, WatchMask::MODIFY)?; + let mut buf = [0; 1024]; + let mut stream = inotify.into_event_stream(&mut buf)?; + + while let Some(_) = stream.next().await.transpose()? { + meta_update_tx.send(()).await.unwrap() + } + + Ok::<(), anyhow::Error>(()) + }) + }; + + let handle = tokio::spawn(process_sflow(socket, meta_update_rx, args.meta, metrics)); + let axum = axum::serve(listener, router) + .with_graceful_shutdown(shutdown_signal()) + .into_future(); + + select! { + result = axum => { result? } + result = handle => { result?? } + result = inotify => { result?? } + } + + Ok(()) +} + +async fn process_sflow( + socket: UdpSocket, + mut meta_update_rx: mpsc::Receiver<()>, + meta_path: PathBuf, + metrics: Metrics, +) -> anyhow::Result<()> { + let mut buf = datagram_buffer(); + let mut meta = Meta::load(&meta_path).await?; + info!( + "Loaded {} routers, {} agents and {} ether types", + meta.customer_count(), + meta.agent_count(), + meta.important_ether_type_count() + ); + + loop { + let read = select! { + _ = meta_update_rx.recv() => { + match Meta::load(&meta_path).await { + Ok(new_meta) => { + meta = new_meta; + info!( + "Loaded {} routers, {} agents and {} important ether types", + meta.customer_count(), + meta.agent_count(), + meta.important_ether_type_count() + ); + } + Err(err) => { + error!("Unable to load meta configuration, continuing with running configuration: {:?}", err); + } + }; + continue; + } + result = socket.recv(buf.as_mut_slice()) => { result? } + }; + + let mut cursor = Cursor::new(&buf[..read]); + + let datagram = SflowDatagram::read_options(&mut cursor, Endian::Big, ())?; + + let agent = match meta.lookup_agent(&datagram.agent_addr) { + Some(agent) => agent, + None => continue, + }; + + for sample in datagram.samples { + let flow = match sample { + Sample::Flow(flow) => flow, + _ => continue, + }; + + metrics.capture_pagent_drops(&agent.label, flow.drops); + + for record in flow.records { + let packet_header = match record { + FlowRecord::RawPacketHeader(header) => header, + _ => continue, + }; + + let ethernet_header = match packet_header.protocol_header { + HeaderProtocol::Ethernet(EthernetHeader(header)) => header, + _ => continue, + }; + + // first cast, then multiply to prevent overflow (panic!) + let bytes = packet_header.frame_length as u64 * flow.sample_rate as u64; + let ether_type = meta.fmt_ether_type(ethernet_header.ether_type.0); + + metrics.capture_agent_bytes(&agent.label, ether_type, bytes); + + let src = meta.lookup_router(ðernet_header.source); + let dst = meta.lookup_router(ðernet_header.destination); + + if let (Some(src), Some(dst)) = (src, dst) { + if src.agent != agent.id || src.interface != flow.input_if_idx { + continue; + } + + metrics.capture_router_bytes(&agent.label, &src.label, &dst.label, ether_type, bytes); + } + } + } + } +} + +async fn metrics_endpoint(State(registry): State) -> Result { + let encoder = TextEncoder::new(); + match encoder.encode_to_string(®istry.gather()) { + Ok(metrics) => Ok(metrics), + Err(err) => { + error!("Error encoding metrics: {:?}", err); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } + } +} diff --git a/src/meta.rs b/src/meta.rs new file mode 100644 index 0000000..e922f7e --- /dev/null +++ b/src/meta.rs @@ -0,0 +1,140 @@ +use std::collections::HashMap; +use std::path::Path; + +use serde::Deserialize; + +use crate::sflow::IpAddr; + +const DEFAULT_ETHER_TYPE: &str = "other"; + +pub(super) struct Meta { + routers: HashMap<[u8; 6], Router>, + agents: HashMap, + ether_types: HashMap, +} + +pub(super) struct Router { + pub(super) agent: String, + pub(super) interface: u32, + pub(super) label: String, +} + +pub(super) struct Agent { + pub(super) id: String, + pub(super) label: String, +} + +#[derive(Deserialize)] +struct MetaStorage { + routers: Vec, + agents: HashMap, + ether_types: HashMap, +} + +#[derive(Deserialize)] +struct CustomerStorage { + mac: String, + agent: String, + interface: u32, + label: String, +} + +#[derive(Deserialize)] +struct AgentStorage { + label: String, + source: IpAddr, +} + +#[derive(Deserialize)] +struct EtherTypeStorage { + label: String, +} + +impl Meta { + pub(super) async fn load(path: &Path) -> anyhow::Result { + let raw_meta = tokio::fs::read_to_string(path).await?; + let meta = serde_yaml::from_str::(&raw_meta)?; + + let routers = meta + .routers + .into_iter() + .map(|customer| { + ( + convert_mac(&customer.mac), + Router { + label: customer.label, + agent: customer.agent, + interface: customer.interface, + }, + ) + }) + .collect(); + + let agents = meta + .agents + .into_iter() + .map(|(id, agent)| { + ( + agent.source, + Agent { + id, + label: agent.label, + }, + ) + }) + .collect(); + + let ether_types = meta + .ether_types + .into_iter() + .map(|(id, ether_type)| (id, ether_type.label)) + .collect(); + + Ok(Self { + routers, + agents, + ether_types, + }) + } + + pub(super) fn customer_count(&self) -> usize { + self.routers.len() + } + + pub(super) fn agent_count(&self) -> usize { + self.agents.len() + } + + pub(super) fn important_ether_type_count(&self) -> usize { + self.ether_types.len() + } + + pub(super) fn lookup_router(&self, mac: &[u8; 6]) -> Option<&Router> { + self.routers.get(mac) + } + + pub(super) fn lookup_agent(&self, addr: &IpAddr) -> Option<&Agent> { + self.agents.get(addr) + } + + pub(super) fn fmt_ether_type(&self, ether_type: u16) -> &str { + self + .ether_types + .get(ðer_type) + .map(|value| value.as_str()) + .unwrap_or(DEFAULT_ETHER_TYPE) + } +} + +fn convert_mac(input: &str) -> [u8; 6] { + let mac = input + .split(':') + .map(|byte| u8::from_str_radix(byte, 16).unwrap()) + .collect::>(); + + if mac.len() != 6 { + panic!("Invalid mac addr: {}", input); + } + + [mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]] +} diff --git a/src/metrics.rs b/src/metrics.rs new file mode 100644 index 0000000..2dbed31 --- /dev/null +++ b/src/metrics.rs @@ -0,0 +1,66 @@ +use prometheus::{IntCounterVec, Opts, Registry}; + +pub(super) struct Metrics { + router_bytes: IntCounterVec, + agent_bytes: IntCounterVec, + agent_drops: IntCounterVec, +} + +impl Metrics { + pub(super) fn new() -> (Self, Registry) { + let router_bytes = IntCounterVec::new( + Opts::new("sflow_router_bytes", "bytes"), + &["agent", "in", "out", "ether_type"], + ) + .unwrap(); + let agent_bytes = IntCounterVec::new( + Opts::new("sflow_agent_bytes", "bytes"), + &["agent", "ether_type"], + ) + .unwrap(); + let agent_drops = + IntCounterVec::new(Opts::new("sflow_agent_drops", "drops"), &["agent"]).unwrap(); + + let registry = Registry::new(); + registry.register(Box::new(router_bytes.clone())).unwrap(); + registry.register(Box::new(agent_bytes.clone())).unwrap(); + registry.register(Box::new(agent_drops.clone())).unwrap(); + + ( + Self { + router_bytes, + agent_bytes, + agent_drops, + }, + registry, + ) + } + + pub(super) fn capture_router_bytes( + &self, + agent: &str, + r#in: &str, + r#out: &str, + ether_type: &str, + bytes: u64, + ) { + self + .router_bytes + .with_label_values(&[agent, r#in, r#out, ether_type]) + .inc_by(bytes); + } + + pub(super) fn capture_agent_bytes(&self, agent: &str, ether_type: &str, bytes: u64) { + self + .agent_bytes + .with_label_values(&[agent, ether_type]) + .inc_by(bytes); + } + + pub(super) fn capture_pagent_drops(&self, agent: &str, drops: u32) { + self + .agent_drops + .with_label_values(&[agent]) + .inc_by(drops as u64); + } +} diff --git a/src/sflow/mod.rs b/src/sflow/mod.rs new file mode 100644 index 0000000..5fe528a --- /dev/null +++ b/src/sflow/mod.rs @@ -0,0 +1,63 @@ +use std::fmt::{Display, Formatter}; +use std::io::{Read, Seek}; +use std::net::{Ipv4Addr, Ipv6Addr}; + +use binrw::{BinRead, BinResult, Endian}; +use serde::Deserialize; + +use crate::sflow::sample::Sample; +use crate::sflow::IpAddr::{IPv4, IPv6}; + +pub(crate) mod record; +pub(crate) mod sample; + +#[derive(BinRead)] +pub(crate) struct SflowDatagram { + #[brw(assert(version == 5))] + pub(crate) version: u32, + pub(crate) agent_addr: IpAddr, + pub(crate) sub_agent_id: u32, + pub(crate) seq_num: u32, + pub(crate) uptime: u32, + sample_count: u32, + #[br(count = sample_count)] + pub(crate) samples: Vec, +} +#[derive(Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub(crate) enum IpAddr { + IPv4(Ipv4Addr), + IPv6(Ipv6Addr), +} + +impl Display for IpAddr { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + IPv4(v4) => Display::fmt(v4, f), + IPv6(v6) => Display::fmt(v6, f), + } + } +} + +impl BinRead for IpAddr { + type Args<'a> = (); + + fn read_options( + reader: &mut R, + endian: Endian, + args: Self::Args<'_>, + ) -> BinResult { + match u32::read_options(reader, endian, args)? { + 1 => Ok(IPv4(Ipv4Addr::from(u32::read_options( + reader, endian, args, + )?))), + 2 => Ok(IPv6(Ipv6Addr::from(u128::read_options( + reader, endian, args, + )?))), + magic => Err(binrw::Error::BadMagic { + pos: reader.stream_position()?, + found: Box::new(magic), + }), + } + } +} diff --git a/src/sflow/record.rs b/src/sflow/record.rs new file mode 100644 index 0000000..198f508 --- /dev/null +++ b/src/sflow/record.rs @@ -0,0 +1,367 @@ +use std::io::{Read, Seek, SeekFrom}; + +use binrw::{BinRead, BinResult, Endian}; +use etherparse::Ethernet2Header; + +#[derive(BinRead, Debug)] +pub(crate) enum FlowRecord { + #[brw(magic = 1u32)] + RawPacketHeader(RawPacketHeaderData), + // #[brw(magic = 2u32)] + // EthernetFrame { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + // #[brw(magic = 3u32)] + // Ipv4 { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + // #[brw(magic = 4u32)] + // Ipv6 { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + // #[brw(magic = 1001u32)] + // ExtendedSwitch { + // data_len: u32, + // src_vlan: u32, + // src_priority: u32, + // dst_vlan: u32, + // dst_priority: u32, + // }, + // #[brw(magic = 1002u32)] + // ExtendedRouter { + // data_len: u32, + // next_hop: IpAddr, + // src_mask_len: u32, + // dst_mask_len: u32, + // }, + // #[brw(magic = 1003u32)] + // ExtendedGateway { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + // #[brw(magic = 1004u32)] + // ExtendedUser { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + // #[brw(magic = 1005u32)] + // ExtendedUrl { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + // #[brw(magic = 1006u32)] + // ExtendedMpls { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + // #[brw(magic = 1006u32)] + // ExtendedNat { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + // #[brw(magic = 1008u32)] + // ExtendedMplsTunnel { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + // #[brw(magic = 1009u32)] + // ExtendedMplsVc { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + // #[brw(magic = 1010u32)] + // ExtendedMplsFec { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + // #[brw(magic = 1011u32)] + // ExtendedMplsLvpFec { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + // #[brw(magic = 1012u32)] + // ExtendedVlanTunnel { + // data_len: u32, + // #[br(count = data_len)] + // data: Vec, + // }, + Unknown { + magic: u32, + data_len: u32, + #[br(count = data_len)] + data: Vec, + }, +} + +// #[derive(BinRead, Debug)] +// pub(crate) enum CounterRecord { +// #[brw(magic = 1u32)] +// GenericInterface { +// data_len: u32, +// #[br(count = data_len)] +// data: Vec, +// }, +// #[brw(magic = 2u32)] +// EthernetInterface { +// data_len: u32, +// #[br(count = data_len)] +// data: Vec, +// }, +// #[brw(magic = 3u32)] +// TokenRing { +// data_len: u32, +// #[br(count = data_len)] +// data: Vec, +// }, +// #[brw(magic = 4u32)] +// _100BaseVGInterface { +// data_len: u32, +// #[br(count = data_len)] +// data: Vec, +// }, +// #[brw(magic = 5u32)] +// Vlan { +// data_len: u32, +// #[br(count = data_len)] +// data: Vec, +// }, +// #[brw(magic = 1001u32)] +// Processor { +// data_len: u32, +// #[br(count = data_len)] +// data: Vec, +// }, +// Unknown { +// magic: u32, +// data_len: u32, +// #[br(count = data_len)] +// data: Vec, +// }, +// } + +#[derive(BinRead)] +enum HeaderProtocolRaw { + #[brw(magic = 1u32)] + EthernetISO88023, + // #[brw(magic = 2u32)] + // ISO88024TokenBus, + // #[brw(magic = 3u32)] + // ISO88025TokenRing, + // #[brw(magic = 4u32)] + // FDDI, + // #[brw(magic = 5u32)] + // FrameRelay, + // #[brw(magic = 6u32)] + // X25, + // #[brw(magic = 7u32)] + // PPP, + // #[brw(magic = 8u32)] + // SMDS, + // #[brw(magic = 9u32)] + // AAL5, + // /* e.g. Cisco AAL5 mux */ + // #[brw(magic = 10u32)] + // AAL5IP, + // #[brw(magic = 11u32)] + // IPv4, + // #[brw(magic = 12u32)] + // IPv6, + // #[brw(magic = 13u32)] + // MPLS, + // /* RFC 1662, 2615 */ + // #[brw(magic = 14u32)] + // POS, + // Unknown { + // magic: u32, + // }, +} + +#[derive(Debug)] +pub(crate) enum HeaderProtocol { + Ethernet(EthernetHeader), + // ISO88024TokenBus, + // ISO88025TokenRing, + // FDDI, + // FrameRelay, + // X25, + // PPP, + // SMDS, + // AAL5, + // /* e.g. Cisco AAL5 mux */ + // AAL5IP, + // IPv4, + // IPv6, + // MPLS, + // /* RFC 1662, 2615 */ + // POS, + Unknown { magic: u32 }, +} +// +// #[derive(Debug)] +// pub(crate) enum EthernetPayload { +// Ipv4(Ipv4Header), +// Ipv6(Ipv6Header), +// Unknown, +// } + +// +// impl BinRead for EthernetHeader { +// type Args<'a> = (u32,); +// +// fn read_options(reader: &mut R, endian: Endian, args: Self::Args<'_>) -> BinResult { +// let mut buf = vec![0u8; args.0 as usize]; +// reader.read_exact(&mut buf[..args.0 as usize])?; +// +// let mut cursor = Cursor::new(&buf); +// +// let ethernet = Ethernet2Header::read(&mut cursor)?; +// +// Ok(EthernetHeader { +// ethernet, +// }) +// } +// } +#[derive(Debug)] +pub(crate) struct RawPacketHeaderData { + pub(crate) frame_length: u32, + pub(crate) stripped_octets: u32, + pub(crate) protocol_header: HeaderProtocol, +} + +#[derive(BinRead)] +struct RawPacketHeaderDataRaw { + data_len: u32, + protocol: HeaderProtocolRaw, + frame_length: u32, + stripped_octets: u32, + header_length: u32, +} + +#[derive(Debug)] +pub(crate) struct EthernetHeader( + pub(crate) Ethernet2Header, // payload: EthernetPayload, +); + +impl BinRead for RawPacketHeaderData { + type Args<'a> = (); + + fn read_options( + reader: &mut R, + endian: Endian, + _args: Self::Args<'_>, + ) -> BinResult { + let raw: RawPacketHeaderDataRaw = RawPacketHeaderDataRaw::read_options(reader, endian, ())?; + + let header = raw.data_len - 4 * 4; + + let protocol = match raw.protocol { + HeaderProtocolRaw::EthernetISO88023 => { + HeaderProtocol::Ethernet(EthernetHeader::read_options(reader, endian, header)?) + } // HeaderProtocolRaw::ISO88024TokenBus => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::ISO88024TokenBus + // } + // HeaderProtocolRaw::ISO88025TokenRing => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::ISO88025TokenRing + // } + // HeaderProtocolRaw::FDDI => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::FDDI + // } + // HeaderProtocolRaw::FrameRelay => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::FrameRelay + // } + // HeaderProtocolRaw::X25 => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::X25 + // } + // HeaderProtocolRaw::PPP => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::PPP + // } + // HeaderProtocolRaw::SMDS => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::SMDS + // } + // HeaderProtocolRaw::AAL5 => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::AAL5 + // } + // HeaderProtocolRaw::AAL5IP => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::AAL5IP + // } + // HeaderProtocolRaw::IPv4 => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::IPv4 + // } + // HeaderProtocolRaw::IPv6 => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::IPv6 + // } + // HeaderProtocolRaw::MPLS => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::MPLS + // } + // HeaderProtocolRaw::POS => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::POS + // } + // HeaderProtocolRaw::Unknown { magic } => { + // reader.seek(SeekFrom::Current(header as i64))?; + // HeaderProtocol::Unknown { magic } + // } + }; + + Ok(RawPacketHeaderData { + frame_length: raw.frame_length, + stripped_octets: raw.stripped_octets, + protocol_header: protocol, + }) + } +} + +impl BinRead for EthernetHeader { + type Args<'a> = u32; + + fn read_options( + reader: &mut R, + _endian: Endian, + args: Self::Args<'_>, + ) -> BinResult { + let pos = reader.stream_position()?; + let header = Ethernet2Header::read(reader)?; + reader.seek(SeekFrom::Start(pos + args as u64))?; + + // let ether_type = header.ether_type.clone(); + Ok(EthernetHeader( + header, + // payload: { + // let payload = match ether_type { + // EtherType::IPV4 => EthernetPayload::Ipv4(Ipv4Header::read(reader).unwrap()), + // EtherType::IPV6 => EthernetPayload::Ipv6(Ipv6Header::read(reader).unwrap()), + // _ => EthernetPayload::Unknown, + // }; + // reader.seek(SeekFrom::Start(pos + args as u64))?; + // payload + // }, + )) + } +} diff --git a/src/sflow/sample.rs b/src/sflow/sample.rs new file mode 100644 index 0000000..0864b1c --- /dev/null +++ b/src/sflow/sample.rs @@ -0,0 +1,123 @@ +use std::io::{Read, Seek}; + +use binrw::{BinRead, BinResult, Endian}; + +use crate::sflow::record::FlowRecord; + +#[derive(BinRead, Debug)] +pub(crate) enum Sample { + #[brw(magic = 1u32)] + Flow(FlowData), + // #[brw(magic = 2u32)] + // Counter(CounterData), + // #[brw(magic = 3u32)] + // FlowExpanded(FlowExtendedData), + // #[brw(magic = 4u32)] + // CounterExpanded(CounterExtendedData), + Unknown(UnknownData), +} + +#[derive(Debug)] +pub(crate) struct FlowData { + pub(crate) seq_num: u32, + pub(crate) source_id_idx: u32, + pub(crate) source_id_type: u32, + pub(crate) sample_rate: u32, + pub(crate) sample_pool: u32, + pub(crate) drops: u32, + pub(crate) input_if_idx: u32, + pub(crate) input_if_format: u32, + pub(crate) output_if_idx: u32, + pub(crate) output_if_format: u32, + pub(crate) direction: Direction, + pub(crate) records: Vec, +} + +#[derive(BinRead, Debug)] +struct FlowDataRaw { + data_len: u32, + seq_num: u32, + source_id: u32, + sample_rate: u32, + sample_pool: u32, + drops: u32, + input_if_idx: u32, + output_if_idx: u32, + record_count: u32, + #[br(count = record_count)] + records: Vec, +} + +// #[derive(BinRead, Debug)] +// pub(crate) struct CounterData { +// data_len: u32, +// #[br(count = data_len)] +// data: Vec, +// // seq_num: u32, +// // source_id: u32, +// // counter_count: u32, +// // #[br(count = counter_count)] +// // records: Vec, +// } +// +// #[derive(BinRead, Debug)] +// pub(crate) struct FlowExtendedData { +// data_len: u32, +// #[br(count = data_len)] +// data: Vec, +// } +// +// #[derive(BinRead, Debug)] +// pub(crate) struct CounterExtendedData { +// data_len: u32, +// #[br(count = data_len)] +// data: Vec, +// } + +#[derive(BinRead, Debug)] +pub(crate) struct UnknownData { + pub(crate) magic: u32, + data_len: u32, + #[br(count = data_len)] + data: Vec, +} + +#[derive(Debug)] +pub(crate) enum Direction { + Ingress, + Egress, + Unknown, +} + +impl BinRead for FlowData { + type Args<'a> = (); + + fn read_options( + reader: &mut R, + endian: Endian, + _args: Self::Args<'_>, + ) -> BinResult { + let raw: FlowDataRaw = FlowDataRaw::read_options(reader, endian, ())?; + + let source_id_idx = raw.source_id & 0x00ffffff; + + Ok(Self { + seq_num: raw.seq_num, + source_id_idx, + source_id_type: raw.source_id >> 24, + sample_rate: raw.sample_rate, + sample_pool: raw.sample_pool, + drops: raw.drops, + input_if_idx: raw.input_if_idx & 0x3FFFFFFF, + input_if_format: raw.input_if_idx >> 30, + output_if_idx: raw.output_if_idx & 0x3FFFFFFF, + output_if_format: raw.output_if_idx >> 30, + direction: match source_id_idx { + idx if idx == raw.output_if_idx => Direction::Egress, + idx if idx == raw.input_if_idx => Direction::Ingress, + _ => Direction::Unknown, + }, + records: raw.records, + }) + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..be5a486 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,41 @@ +use std::mem; + +use tokio::select; +use tokio::signal::ctrl_c; + +pub(super) async fn shutdown_signal() { + let ctrl_c = async { ctrl_c().await.expect("failed to install Ctrl+C handler") }; + + #[cfg(unix)] + { + let terminate = async { + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } + } + + #[cfg(not(unix))] + { + ctrl_c.await; + Ok(()) + } +} + +const MAX_DATAGRAM_SIZE: usize = u16::MAX as usize - mem::size_of::(); + +/// Creates and returns a buffer on the heap with enough space to contain any possible +/// UDP datagram. +/// +/// This is put on the heap and in a separate function to avoid the 64k buffer from ending +/// up on the stack and blowing up the size of the future using it. +#[inline(never)] +pub(super) fn datagram_buffer() -> Box<[u8; MAX_DATAGRAM_SIZE]> { + Box::new([0u8; MAX_DATAGRAM_SIZE]) +}