├── .cargo └── config.toml ├── .githooks └── pre-commit ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── release-plz.yml │ ├── release.yml │ ├── rust-clippy.yml │ ├── rust-fmt.yml │ └── rust.yml ├── .gitignore ├── .release-plz.toml ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── bin ├── Cargo.toml ├── deployments │ └── systemd │ │ ├── atm0s-sdn-node.service │ │ ├── atm0s-sdn-node.sh │ │ ├── deploy.sh │ │ ├── remove.sh │ │ ├── servers.sh.templ │ │ ├── test_get_logs.sh │ │ ├── test_install.sh │ │ ├── test_remove_logs.sh │ │ ├── test_run.sh │ │ └── test_view_logs.sh ├── public │ ├── app.js │ └── index.html ├── src │ └── main.rs ├── start_agent.sh └── start_collector.sh ├── deny.toml ├── docs ├── imgs │ ├── dht.drawio.svg │ ├── flow.excalidraw.png │ ├── overlay.drawio.svg │ ├── pubsub-relay.drawio.svg │ ├── pubsub-relay2.drawio.svg │ └── visualization.png └── smart_routing.md ├── examples ├── .gitignore ├── Cargo.toml ├── quic-tunnel │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── main.rs │ │ ├── sdn.rs │ │ └── vnet │ │ ├── mod.rs │ │ └── socket.rs └── whip-whep │ ├── Cargo.toml │ ├── public │ ├── index.html │ ├── whep │ │ ├── index.html │ │ ├── whep.demo.js │ │ └── whep.js │ └── whip │ │ ├── index.html │ │ ├── whip.demo.js │ │ └── whip.js │ └── src │ ├── http.rs │ ├── main.rs │ ├── sfu │ ├── cluster.rs │ ├── media.rs │ ├── mod.rs │ ├── shared_port.rs │ ├── whep.rs │ └── whip.rs │ └── worker.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ ├── network_control_pkt.rs │ └── transport_msg.rs ├── packages ├── core │ ├── identity │ │ ├── CHANGELOG.md │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── conn_id.rs │ │ │ ├── lib.rs │ │ │ ├── node_addr.rs │ │ │ └── node_id.rs │ ├── router │ │ ├── CHANGELOG.md │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── benches │ │ │ └── router.rs │ │ └── src │ │ │ ├── core │ │ │ ├── mod.rs │ │ │ ├── registry.rs │ │ │ ├── registry │ │ │ │ └── dest.rs │ │ │ ├── router.rs │ │ │ ├── table.rs │ │ │ └── table │ │ │ │ ├── dest.rs │ │ │ │ ├── metric.rs │ │ │ │ └── path.rs │ │ │ ├── lib.rs │ │ │ └── shadow │ │ │ ├── mod.rs │ │ │ ├── service.rs │ │ │ └── table.rs │ └── utils │ │ ├── CHANGELOG.md │ │ ├── Cargo.toml │ │ └── src │ │ ├── error_handle.rs │ │ ├── hash.rs │ │ ├── init_array.rs │ │ ├── init_vec.rs │ │ ├── lib.rs │ │ ├── option_handle.rs │ │ └── types.rs ├── network │ ├── .DS_Store │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ ├── _fuzz_export.rs │ │ ├── base │ │ │ ├── control.rs │ │ │ ├── feature.rs │ │ │ ├── mod.rs │ │ │ ├── msg.rs │ │ │ ├── secure.rs │ │ │ └── service.rs │ │ ├── controller_plane.rs │ │ ├── controller_plane │ │ │ ├── features.rs │ │ │ ├── neighbours.rs │ │ │ ├── neighbours │ │ │ │ └── connection.rs │ │ │ └── services.rs │ │ ├── data_plane.rs │ │ ├── data_plane │ │ │ ├── connection.rs │ │ │ ├── features.rs │ │ │ └── services.rs │ │ ├── features │ │ │ ├── alias.rs │ │ │ ├── data.rs │ │ │ ├── dht_kv │ │ │ │ ├── README.md │ │ │ │ ├── client.rs │ │ │ │ ├── client │ │ │ │ │ └── map.rs │ │ │ │ ├── internal.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── msg.rs │ │ │ │ ├── server.rs │ │ │ │ └── server │ │ │ │ │ └── map.rs │ │ │ ├── mod.rs │ │ │ ├── neighbours.rs │ │ │ ├── pubsub │ │ │ │ ├── README.md │ │ │ │ ├── controller.rs │ │ │ │ ├── controller │ │ │ │ │ ├── consumers.rs │ │ │ │ │ ├── feedbacks.rs │ │ │ │ │ ├── local_relay.rs │ │ │ │ │ ├── remote_relay.rs │ │ │ │ │ └── source_hint.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── msg.rs │ │ │ │ └── worker.rs │ │ │ ├── router_sync.rs │ │ │ ├── socket.rs │ │ │ └── vpn.rs │ │ ├── lib.rs │ │ ├── secure │ │ │ ├── authorization │ │ │ │ ├── mod.rs │ │ │ │ └── static_key.rs │ │ │ ├── encryption │ │ │ │ ├── mod.rs │ │ │ │ └── x25519_dalek_aes.rs │ │ │ └── mod.rs │ │ ├── services │ │ │ ├── manual2_discovery.rs │ │ │ ├── manual_discovery.rs │ │ │ ├── mod.rs │ │ │ └── visualization.rs │ │ └── worker.rs │ └── tests │ │ ├── feature_alias.rs │ │ ├── feature_dht_kv.rs │ │ ├── feature_neighbours.rs │ │ ├── feature_pubsub.rs │ │ ├── feature_router_sync.rs │ │ ├── feature_socket.rs │ │ ├── service_manual2_discovery.rs │ │ ├── service_manual_discovery.rs │ │ ├── service_visualization.rs │ │ └── simulator.rs └── runner │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── docs │ └── architecture.excalidraw.png │ ├── examples │ ├── simple_kv.rs │ └── simple_node.rs │ ├── run-example-debug.sh │ ├── run-example-release.sh │ ├── src │ ├── builder.rs │ ├── history.rs │ ├── lib.rs │ ├── time.rs │ └── worker_inner.rs │ └── tests │ └── feature_dht_kv.rs ├── renovate.json ├── run-test.sh ├── rust-toolchain.toml └── rustfmt.toml /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [net] 2 | git-fetch-with-cli = true 3 | -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HAS_ISSUES=0 4 | FIRST_FILE=1 5 | 6 | for file in $(git diff --name-only --staged); do 7 | FMT_RESULT="$(rustfmt --check $file 2>/dev/null || true)" 8 | if [ "$FMT_RESULT" != "" ]; then 9 | echo "$file" 10 | HAS_ISSUES=1 11 | FIRST_FILE=0 12 | fi 13 | done 14 | 15 | if [ $HAS_ISSUES -eq 0 ]; then 16 | exit 0 17 | fi 18 | 19 | echo "Your code has formatting issues in files listed above. Format your code with \`make format\` or call rustfmt manually." 20 | exit 1 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: 'Bug: ' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Start Node A with ... 16 | 2. Start Node B with ... 17 | 3. Call a function in Node A ... 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Desktop (please complete the following information):** 26 | - OS: [e.g. Windows] 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'Feature Request: ' 5 | labels: 'feature request' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/release-plz.yml: -------------------------------------------------------------------------------- 1 | name: Release-plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | release-plz: 14 | name: Release-plz 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - name: Install Rust toolchain 22 | uses: dtolnay/rust-toolchain@stable 23 | - name: Run release-plz 24 | uses: MarcoIeni/release-plz-action@v0.5 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/rust-clippy.yml: -------------------------------------------------------------------------------- 1 | # rust-clippy is a tool that runs a bunch of lints to catch common 2 | # mistakes in your Rust code and help improve your Rust code. 3 | # More details at https://github.com/rust-lang/rust-clippy 4 | # and https://rust-lang.github.io/rust-clippy/ 5 | 6 | name: rust-clippy analyze 7 | 8 | on: 9 | push: 10 | branches: [ "master" ] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [ "master" ] 14 | schedule: 15 | - cron: '29 19 * * 2' 16 | 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 19 | cancel-in-progress: true 20 | 21 | env: 22 | CARGO_TERM_COLOR: always 23 | 24 | jobs: 25 | rust-clippy-analyze: 26 | name: Run rust-clippy analyzing 27 | runs-on: ubuntu-latest 28 | permissions: 29 | contents: read 30 | security-events: write 31 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 32 | steps: 33 | - name: Checkout code 34 | uses: actions/checkout@v4 35 | 36 | - uses: actions/cache@v4 37 | id: cache-cargo 38 | with: 39 | path: | 40 | ~/.cargo/registry 41 | ~/.cargo/git 42 | target 43 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 44 | restore-keys: ${{ runner.os }}-cargo- 45 | 46 | - name: Install Protoc 47 | uses: arduino/setup-protoc@v3 48 | with: 49 | version: "25.1" 50 | repo-token: ${{ secrets.GITHUB_TOKEN }} 51 | 52 | - name: Run rust-clippy 53 | run: cargo clippy --all-targets --all-features -- -D warnings 54 | 55 | - name: Install required cargo 56 | run: cargo install clippy-sarif sarif-fmt 57 | 58 | - name: Run rust-sarif 59 | run: cargo clippy --all-features --message-format=json | 60 | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt 61 | 62 | - name: Upload analysis results to GitHub 63 | uses: github/codeql-action/upload-sarif@v3 64 | with: 65 | sarif_file: rust-clippy-results.sarif 66 | wait-for-processing: true 67 | -------------------------------------------------------------------------------- /.github/workflows/rust-fmt.yml: -------------------------------------------------------------------------------- 1 | name: rust-fmt analyze 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ "master" ] 9 | schedule: 10 | - cron: '29 19 * * 2' 11 | 12 | concurrency: 13 | # One build per PR, branch or tag 14 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 15 | cancel-in-progress: true 16 | 17 | env: 18 | CARGO_TERM_COLOR: always 19 | 20 | jobs: 21 | rust-fmt-analyze: 22 | name: Run rust-fmt analyzing 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v4 27 | 28 | - uses: actions/cache@v4 29 | with: 30 | path: | 31 | ~/.cargo/bin/ 32 | ~/.cargo/registry/index/ 33 | ~/.cargo/registry/cache/ 34 | ~/.cargo/git/db/ 35 | target/ 36 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 37 | 38 | - name: cargo fmt 39 | run: cargo fmt --all -- --check 40 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | paths-ignore: 7 | - "docs/**" 8 | 9 | pull_request: 10 | branches: ["master"] 11 | paths-ignore: 12 | - "docs/**" 13 | 14 | env: 15 | CARGO_TERM_COLOR: always 16 | 17 | jobs: 18 | code-coverage: 19 | runs-on: ubuntu-latest 20 | env: 21 | CARGO_TERM_COLOR: always 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Install deps 25 | run: | 26 | sudo apt-get update 27 | sudo apt install -y libsoxr-dev libopus-dev libssl-dev libfdk-aac-dev 28 | 29 | - uses: actions/cache@v4 30 | with: 31 | path: | 32 | ~/.cargo/bin/ 33 | ~/.cargo/registry/index/ 34 | ~/.cargo/registry/cache/ 35 | ~/.cargo/git/db/ 36 | target/ 37 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 38 | 39 | - name: Install Protoc 40 | uses: arduino/setup-protoc@v3 41 | with: 42 | version: "25.1" 43 | repo-token: ${{ secrets.GITHUB_TOKEN }} 44 | - name: Install cargo-llvm-cov 45 | uses: taiki-e/install-action@cargo-llvm-cov 46 | - name: Running cargo test 47 | run: cargo test --all-features --workspace 48 | - name: Generate code coverage 49 | run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info 50 | - name: Upload coverage to Codecov 51 | uses: codecov/codecov-action@v4 52 | with: 53 | token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos 54 | files: lcov.info 55 | fail_ci_if_error: false 56 | cargo-deny: 57 | name: cargo-deny 58 | 59 | # TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved 60 | strategy: 61 | fail-fast: false 62 | matrix: 63 | platform: 64 | - x86_64-unknown-linux-gnu 65 | # - x86_64-unknown-linux-musl 66 | - aarch64-unknown-linux-gnu 67 | # - arm-unknown-linux-gnueabihf 68 | # - armv7-unknown-linux-gnueabihf 69 | # - mips-unknown-linux-gnu 70 | # - mips64-unknown-linux-gnuabi64 71 | # - mips64el-unknown-linux-gnuabi64 72 | # - mipsel-unknown-linux-gnu 73 | # - aarch64-unknown-linux-musl 74 | - x86_64-apple-darwin 75 | - aarch64-apple-darwin 76 | # - x86_64-pc-windows-gnu 77 | # - x86_64-pc-windows-msvc 78 | 79 | runs-on: ubuntu-latest 80 | steps: 81 | - uses: actions/checkout@v4 82 | - uses: EmbarkStudios/cargo-deny-action@v1 83 | with: 84 | command: check 85 | log-level: error 86 | arguments: --all-features --target ${{ matrix.platform }} 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | .idea 4 | .vscode -------------------------------------------------------------------------------- /.release-plz.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | git_release_enable = false 3 | 4 | [[package]] 5 | name = "atm0s-sdn" 6 | git_release_enable = true 7 | changelog_include = [ 8 | "atm0s-sdn-identity", 9 | "atm0s-sdn-router", 10 | "atm0s-sdn-layers-spread-router", 11 | "atm0s-sdn-multiaddr", 12 | "atm0s-sdn-utils", 13 | "atm0s-sdn-network", 14 | "atm0s-sdn-manual-discovery", 15 | "atm0s-sdn-dht-discovery", 16 | "atm0s-sdn-layers-spread-router-sync", 17 | "atm0s-sdn-key-value", 18 | "atm0s-sdn-pub-sub", 19 | "atm0s-sdn-transport-vnet", 20 | "atm0s-sdn-transport-tcp", 21 | "atm0s-sdn-transport-udp", 22 | ] -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace.metadata.workspaces] 2 | independent = true 3 | 4 | [workspace] 5 | resolver = "2" 6 | members = [ 7 | "bin", 8 | "fuzz", 9 | "packages/core/utils", 10 | "packages/core/identity", 11 | "packages/core/router", 12 | "packages/network", 13 | "packages/runner", 14 | ] 15 | 16 | [workspace.dependencies] 17 | bincode = "1.3" 18 | serde = { version = "1.0", features = ["derive"] } 19 | thiserror = "1.0" 20 | log = "0.4" 21 | rand = "0.8" 22 | parking_lot = "0.12" 23 | env_logger = "0.11" 24 | clap = { version = "4.4", features = ["derive", "env"] } 25 | mockall = "0.13" 26 | num_enum = "0.7" 27 | convert-enum = "0.1.0" 28 | sans-io-runtime = { version = "0.3", default-features = false } 29 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 as base 2 | ARG TARGETPLATFORM 3 | COPY . /tmp 4 | WORKDIR /tmp 5 | 6 | RUN echo $TARGETPLATFORM 7 | RUN ls -R /tmp/ 8 | # move the binary to root based on platform 9 | RUN case $TARGETPLATFORM in \ 10 | "linux/amd64") BUILD=x86_64-unknown-linux-gnu ;; \ 11 | "linux/arm64") BUILD=aarch64-unknown-linux-gnu ;; \ 12 | *) exit 1 ;; \ 13 | esac; \ 14 | mv /tmp/$BUILD/atm0s-sdn-standalone-$BUILD /atm0s-sdn-standalone; \ 15 | chmod +x /atm0s-sdn-standalone 16 | 17 | FROM ubuntu:22.04 18 | 19 | COPY --from=base /atm0s-sdn-standalone /atm0s-sdn-standalone 20 | 21 | ENTRYPOINT ["/atm0s-sdn-standalone"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 8xFF 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | init: 2 | git config core.hooksPath .githooks 3 | format: 4 | cargo fmt -------------------------------------------------------------------------------- /bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atm0s-sdn-standalone" 3 | version = "0.1.0" 4 | edition = "2021" 5 | repository = "https://github.com/8xFF/atm0s-sdn" 6 | description = "Decentralized Ultra-Low-Latency Software Defined Network" 7 | license = "MIT" 8 | publish = false 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | signal-hook = "0.3.17" 14 | log.workspace = true 15 | clap.workspace = true 16 | serde.workspace = true 17 | atm0s-sdn = { path = "../packages/runner", version = "0.2.8", features = [ 18 | "vpn", 19 | ] } 20 | tokio = { version = "1", features = ["full"] } 21 | poem = { version = "3", features = ["embed", "static-files", "websocket"] } 22 | rust-embed = { version = "8.2", optional = true } 23 | 24 | futures-util = "0.3" 25 | tracing-subscriber = "0.3" 26 | serde_json = "1.0" 27 | local-ip-address = "0.6" 28 | 29 | [features] 30 | default = ["embed"] 31 | embed = ["rust-embed"] 32 | -------------------------------------------------------------------------------- /bin/deployments/systemd/atm0s-sdn-node.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=atm0s-sdn-node service 3 | After=network.target 4 | 5 | [Service] 6 | ExecStart=/opt/atm0s-sdn-node.sh 7 | StandardOutput=append:/var/log/atm0s-sdn-node.log 8 | StandardError=append:/var/log/atm0s-sdn-node.log 9 | 10 | [Install] 11 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /bin/deployments/systemd/atm0s-sdn-node.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export RUST_LOG=info 3 | export UDP_PORT=10000 4 | export WORKERS=1 5 | export LOCAL_TAGS=global 6 | export CONNECT_TAGS=global 7 | export VPN=true 8 | -------------------------------------------------------------------------------- /bin/deployments/systemd/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "./servers.sh" 4 | 5 | # Loop through each server 6 | for server in "${!servers[@]}"; do 7 | # Check if the server key ends with "_node_id" or "_web_addr" 8 | if [[ $server == *"_node_id" ]] || [[ $server == *"_name" ]] || [[ $server == *"_public" ]] || [[ $server == *"_ssh_port" ]] || [[ $server == *"_seeds" ]] || [[ $server == *"_collector" ]]; then 9 | continue 10 | fi 11 | 12 | # Retrieve the node_id and web_addr for the current server 13 | node_id="${servers["$server"_node_id]}" 14 | ssh_port="${servers["$server"_ssh_port]:-22}" 15 | seeds="${servers["$server"_seeds]}" 16 | collector="${servers["$server"_collector]}" 17 | public_ip="${servers["$server"_public]}" 18 | 19 | ssh -p $ssh_port "${servers[$server]}" "systemctl stop atm0s-sdn-node" 20 | 21 | echo "Configuring for node $node_id" 22 | 23 | # Replace atm0s-sdn-node.env HERE_NODE_ID 24 | cp ./atm0s-sdn-node.sh /tmp/atm0s-sdn-node-sh 25 | echo "export NODE_ID=$node_id" >> /tmp/atm0s-sdn-node-sh 26 | if [ -n "$seeds" ]; then 27 | echo "export SEEDS=$seeds" >> /tmp/atm0s-sdn-node-sh 28 | fi 29 | if [ -n "$collector" ]; then 30 | echo "export COLLECTOR=$collector" >> /tmp/atm0s-sdn-node-sh 31 | fi 32 | if [ -n "$public_ip" ]; then 33 | echo "export CUSTOM_ADDRS=\"$public_ip:10000\"" >> /tmp/atm0s-sdn-node-sh 34 | fi 35 | 36 | echo "/opt/atm0s-sdn-node" >> /tmp/atm0s-sdn-node-sh 37 | 38 | echo "Connecting to $server" 39 | 40 | # Upload the file 41 | ssh -p $ssh_port "${servers[$server]}" "rm -f ${servers[$server]}:/opt/atm0s-sdn-node" 42 | ssh -p $ssh_port "${servers[$server]}" "rm -f ${servers[$server]}:/etc/systemd/system/atm0s-sdn-node.service" 43 | ssh -p $ssh_port "${servers[$server]}" "rm -f ${servers[$server]}:/opt/atm0s-sdn-node.sh" 44 | scp -P $ssh_port "../../../target/release/atm0s-sdn-standalone" "${servers[$server]}:/opt/atm0s-sdn-node" 45 | scp -P $ssh_port "./atm0s-sdn-node.service" "${servers[$server]}:/etc/systemd/system/atm0s-sdn-node.service" 46 | scp -P $ssh_port "/tmp/atm0s-sdn-node-sh" "${servers[$server]}:/opt/atm0s-sdn-node.sh" 47 | 48 | # Execute the command on the server 49 | ssh -p $ssh_port "${servers[$server]}" "systemctl daemon-reload" 50 | ssh -p $ssh_port "${servers[$server]}" "systemctl enable atm0s-sdn-node" 51 | ssh -p $ssh_port "${servers[$server]}" "systemctl start atm0s-sdn-node" 52 | ssh -p $ssh_port "${servers[$server]}" "systemctl status atm0s-sdn-node" 53 | done 54 | -------------------------------------------------------------------------------- /bin/deployments/systemd/remove.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "./servers.sh" 4 | 5 | # Loop through each server 6 | for server in "${!servers[@]}"; do 7 | # Check if the server key ends with "_node_id" or "_web_addr" 8 | if [[ $server == *"_node_id" ]] || [[ $server == *"_name" ]] || [[ $server == *"_public" ]] || [[ $server == *"_ssh_port" ]] || [[ $server == *"_seeds" ]] || [[ $server == *"_collector" ]]; then 9 | continue 10 | fi 11 | 12 | ssh_port="${servers["$server"_ssh_port]:-22}" 13 | 14 | echo "Disable and stop" "${servers[$server]}" 15 | 16 | ssh -p $ssh_port "${servers[$server]}" "systemctl disable atm0s-sdn-node" 17 | ssh -p $ssh_port "${servers[$server]}" "systemctl stop atm0s-sdn-node" 18 | done -------------------------------------------------------------------------------- /bin/deployments/systemd/servers.sh.templ: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define servers as an associative array with additional details 4 | declare -A servers 5 | servers=( 6 | ["server1"]="root@IP" 7 | ["server1_name"]="NAME" 8 | ["server1_public"]="IP" 9 | ["server1_ssh_port"]="22" 10 | ["server1_node_id"]="1" 11 | ["server1_collector"]="false" 12 | ["server1_seeds"]="0@/ip4/IP/udp/10000" 13 | ) -------------------------------------------------------------------------------- /bin/deployments/systemd/test_get_logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "./servers.sh" 4 | 5 | mkdir -p ./logs/ 6 | rm -f ./logs/* 7 | 8 | # Loop through each server 9 | for server in "${!servers[@]}"; do 10 | # Check if the server key ends with "_node_id" or "_web_addr" 11 | if [[ $server == *"_node_id" ]] || [[ $server == *"_name" ]] || [[ $server == *"_public" ]] || [[ $server == *"_ssh_port" ]] || [[ $server == *"_seeds" ]] || [[ $server == *"_collector" ]]; then 12 | continue 13 | fi 14 | 15 | node_id="${servers["$server"_node_id]}" 16 | ssh_port="${servers["$server"_ssh_port]:-22}" 17 | 18 | ssh -p $ssh_port "${servers[$server]}" "gzip -f --keep /var/log/atm0s-sdn-node.log" 19 | scp -P $ssh_port "${servers[$server]}:/var/log/atm0s-sdn-node.log.gz" "logs/$node_id.log.gz" 20 | done 21 | -------------------------------------------------------------------------------- /bin/deployments/systemd/test_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "./servers.sh" 4 | 5 | # Loop through each server 6 | for server in "${!servers[@]}"; do 7 | # Check if the server key ends with "_node_id" or "_web_addr" 8 | if [[ $server == *"_node_id" ]] || [[ $server == *"_name" ]] || [[ $server == *"_public" ]] || [[ $server == *"_ssh_port" ]] || [[ $server == *"_seeds" ]] || [[ $server == *"_collector" ]]; then 9 | continue 10 | fi 11 | 12 | echo "Install and config iperf3" "${servers[$server]}" 13 | ssh_port="${servers["$server"_ssh_port]:-22}" 14 | 15 | ssh -p $ssh_port "${servers[$server]}" "apt-get install -y iperf3 python3 python3-pip" 16 | ssh -p $ssh_port "${servers[$server]}" "pip3 install jc --break-system-packages" 17 | done 18 | -------------------------------------------------------------------------------- /bin/deployments/systemd/test_remove_logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "./servers.sh" 4 | 5 | # Loop through each server 6 | for server in "${!servers[@]}"; do 7 | # Check if the server key ends with "_node_id" or "_web_addr" 8 | if [[ $server == *"_node_id" ]] || [[ $server == *"_name" ]] || [[ $server == *"_public" ]] || [[ $server == *"_ssh_port" ]] || [[ $server == *"_seeds" ]] || [[ $server == *"_collector" ]]; then 9 | continue 10 | fi 11 | 12 | node_id="${servers["$server"_node_id]}" 13 | ssh_port="${servers["$server"_ssh_port]:-22}" 14 | 15 | echo "Remove log of node $node_id" 16 | 17 | ssh -p $ssh_port "${servers[$server]}" "rm -f /var/log/atm0s-sdn-node.log*" 18 | done 19 | -------------------------------------------------------------------------------- /bin/deployments/systemd/test_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "./servers.sh" 4 | 5 | mkdir -p results 6 | rm -f results/* 7 | 8 | echo "source; dest; public; vpn" > results/stats.csv 9 | 10 | # Loop through each server 11 | for server in "${!servers[@]}"; do 12 | # Check if the server key ends with "_node_id" or "_web_addr" 13 | if [[ $server == *"_node_id" ]] || [[ $server == *"_name" ]] || [[ $server == *"_public" ]] || [[ $server == *"_ssh_port" ]] || [[ $server == *"_seeds" ]] || [[ $server == *"_collector" ]]; then 14 | continue 15 | fi 16 | 17 | node_id="${servers["$server"_node_id]}" 18 | name="${servers["$server"_name]}" 19 | ssh_port="${servers["$server"_ssh_port]:-22}" 20 | public_ip="${servers["$server"_public]}" 21 | vpn_ip="10.33.33.$node_id" 22 | 23 | for target in "${!servers[@]}"; do 24 | if [[ $target == *"_node_id" ]] || [[ $target == *"_name" ]] || [[ $target == *"_public" ]] || [[ $target == *"_ssh_port" ]] || [[ $target == *"_node_id" ]] || [[ $target == *"_seeds" ]] || [[ $target == *"_collector" ]]; then 25 | continue 26 | fi 27 | 28 | target_name="${servers["$target"_name]}" 29 | target_node_id="${servers["$target"_node_id]}" 30 | target_public_ip="${servers["$target"_public]}" 31 | target_vpn_ip="10.33.33.$target_node_id" 32 | 33 | if [[ "$node_id" == "$target_node_id" ]]; then 34 | continue 35 | fi 36 | 37 | echo "Running test from $node_id $name to $target_node_id $target_name, $public_ip, $target_public_ip" 38 | 39 | ssh -p $ssh_port "${servers[$server]}" "ping -c 1 $target_public_ip | jc --ping > /tmp/$node_id-$target_node_id-ping-public.json" 40 | ssh -p $ssh_port "${servers[$server]}" "ping -c 1 $target_vpn_ip | jc --ping > /tmp/$node_id-$target_node_id-ping-vpn.json" 41 | scp -P $ssh_port "${servers[$server]}:/tmp/$node_id-$target_node_id-ping-public.json" "results/$node_id-$target_node_id-ping-public.json" 42 | scp -P $ssh_port "${servers[$server]}:/tmp/$node_id-$target_node_id-ping-vpn.json" "results/$node_id-$target_node_id-ping-vpn.json" 43 | 44 | rtt_public=$(cat results/$node_id-$target_node_id-ping-public.json | jq ".round_trip_ms_avg") 45 | rtt_vpn=$(cat results/$node_id-$target_node_id-ping-vpn.json | jq ".round_trip_ms_avg") 46 | echo "$node_id; $target_node_id; $name; $target_name; $rtt_public; $rtt_vpn" 47 | echo "$node_id; $target_node_id; $name; $target_name; $rtt_public; $rtt_vpn" >> results/stats.csv 48 | done 49 | done 50 | -------------------------------------------------------------------------------- /bin/deployments/systemd/test_view_logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "./servers.sh" 4 | 5 | mkdir -p ./logs/ 6 | rm -f ./logs/* 7 | 8 | # Loop through each server 9 | for server in "${!servers[@]}"; do 10 | # Check if the server key ends with "_node_id" or "_web_addr" 11 | if [[ $server == *"_node_id" ]] || [[ $server == *"_name" ]] || [[ $server == *"_public" ]] || [[ $server == *"_ssh_port" ]] || [[ $server == *"_seeds" ]] || [[ $server == *"_collector" ]]; then 12 | continue 13 | fi 14 | 15 | node_id="${servers["$server"_node_id]}" 16 | ssh_port="${servers["$server"_ssh_port]:-22}" 17 | 18 | echo "#########################" 19 | echo "### Node $node_id. ###" 20 | echo "#########################" 21 | 22 | ssh -p $ssh_port "${servers[$server]}" "tail -n $1 /var/log/atm0s-sdn-node.log" 23 | done 24 | -------------------------------------------------------------------------------- /bin/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Network Graph with Cytoscape 5 | 6 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /bin/start_agent.sh: -------------------------------------------------------------------------------- 1 | cargo run -- --local-tags vpn --connect-tags vpn --seeds $1 --node-id $2 --udp-port $3 2 | -------------------------------------------------------------------------------- /bin/start_collector.sh: -------------------------------------------------------------------------------- 1 | # If provided $3, it will be seeds 2 | if [ -n "$4" ]; then 3 | # $4 is defined 4 | cargo run -- --collector --local-tags vpn --connect-tags vpn --node-id $1 --udp-port $2 --web-addr $3 --seeds $4 5 | else 6 | # $4 is not defined 7 | cargo run -- --collector --local-tags vpn --connect-tags vpn --node-id $1 --udp-port $2 --web-addr $3 8 | fi 9 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [graph] 2 | 3 | targets = [] 4 | all-features = false 5 | no-default-features = false 6 | [output] 7 | feature-depth = 1 8 | 9 | [advisories] 10 | ignore = [] 11 | 12 | [licenses] 13 | allow = [ 14 | "Apache-2.0", 15 | "BSD-2-Clause", 16 | "BSD-3-Clause", 17 | "ISC", 18 | "MIT", 19 | "WTFPL", 20 | "Unicode-3.0", 21 | "NCSA", 22 | ] 23 | confidence-threshold = 0.8 24 | exceptions = [] 25 | 26 | 27 | [licenses.private] 28 | ignore = false 29 | registries = [ 30 | #"https://sekretz.com/registry 31 | ] 32 | 33 | [bans] 34 | multiple-versions = "warn" 35 | wildcards = "allow" 36 | highlight = "all" 37 | workspace-default-features = "allow" 38 | external-default-features = "allow" 39 | allow = [] 40 | deny = [] 41 | 42 | skip = [] 43 | skip-tree = [] 44 | [sources] 45 | unknown-registry = "warn" 46 | unknown-git = "warn" 47 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 48 | allow-git = [] 49 | 50 | [sources.allow-org] 51 | github = [""] 52 | gitlab = [""] 53 | bitbucket = [""] 54 | -------------------------------------------------------------------------------- /docs/imgs/flow.excalidraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8xFF/atm0s-sdn/63ae52d3e14e1963c310c7872ca6e6cdd6013a5f/docs/imgs/flow.excalidraw.png -------------------------------------------------------------------------------- /docs/imgs/visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8xFF/atm0s-sdn/63ae52d3e14e1963c310c7872ca6e6cdd6013a5f/docs/imgs/visualization.png -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .idea 4 | .vscode -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "quic-tunnel" 5 | , "whip-whep"] 6 | 7 | [workspace.package] 8 | version = "0.1.0" 9 | edition = "2021" 10 | publish = false 11 | 12 | [workspace.dependencies] 13 | atm0s-sdn = { path = "../packages/runner" } 14 | tracing-subscriber = "0.3" 15 | signal-hook = "0.3" 16 | clap = { version = "4.4", features = ["derive", "env"] } 17 | tokio = { version = "1", features = ["full"] } 18 | log = "0.4" 19 | -------------------------------------------------------------------------------- /examples/quic-tunnel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "quic-tunnel" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | log.workspace = true 8 | tracing-subscriber.workspace = true 9 | clap.workspace = true 10 | tokio.workspace = true 11 | atm0s-sdn.workspace = true 12 | signal-hook.workspace = true 13 | quinn = { version = "0.10", default-features = false, features = ["runtime-tokio", "log", "ring"] } 14 | quinn-plaintext = "0.2.0" 15 | -------------------------------------------------------------------------------- /examples/quic-tunnel/README.md: -------------------------------------------------------------------------------- 1 | # Quic-tunnel 2 | 3 | This sample implement a simple tunnel using QUIC protocol. It is based on the virtual socket feature and Quinn crate. -------------------------------------------------------------------------------- /examples/quic-tunnel/src/sdn.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::{ 3 | atomic::{AtomicBool, Ordering}, 4 | Arc, 5 | }, 6 | time::Duration, 7 | }; 8 | 9 | use atm0s_sdn::{ 10 | features::{socket, FeaturesControl, FeaturesEvent}, 11 | sans_io_runtime::backend::PollingBackend, 12 | services::visualization, 13 | NodeAddr, NodeId, SdnBuilder, SdnExtIn, SdnExtOut, SdnOwner, 14 | }; 15 | use tokio::sync::mpsc::{Receiver, Sender}; 16 | 17 | use crate::vnet::{NetworkPkt, OutEvent}; 18 | 19 | type SC = visualization::Control; 20 | type SE = visualization::Event; 21 | type TC = (); 22 | type TW = (); 23 | 24 | pub async fn run_sdn(node_id: NodeId, udp_port: u16, seeds: Vec, workers: usize, tx: Sender, mut rx: Receiver) { 25 | let term = Arc::new(AtomicBool::new(false)); 26 | signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&term)).expect("Should register hook"); 27 | let mut shutdown_wait = 0; 28 | let mut builder = SdnBuilder::<(), SC, SE, TC, TW>::new(node_id, udp_port, vec![]); 29 | 30 | builder.set_manual_discovery(vec!["tunnel".to_string()], vec!["tunnel".to_string()]); 31 | builder.set_visualization_collector(false); 32 | 33 | for seed in seeds { 34 | builder.add_seed(seed); 35 | } 36 | 37 | let mut controller = builder.build::>(workers); 38 | while controller.process().is_some() { 39 | if term.load(Ordering::Relaxed) { 40 | if shutdown_wait == 200 { 41 | log::warn!("Force shutdown"); 42 | break; 43 | } 44 | shutdown_wait += 1; 45 | controller.shutdown(); 46 | } 47 | while let Ok(c) = rx.try_recv() { 48 | // log::info!("Command: {:?}", c); 49 | match c { 50 | OutEvent::Bind(port) => { 51 | controller.send_to(0, SdnExtIn::FeaturesControl((), FeaturesControl::Socket(socket::Control::Bind(port)))); 52 | } 53 | OutEvent::Pkt(pkt) => { 54 | let send = socket::Control::SendTo(pkt.local_port, pkt.remote, pkt.remote_port, pkt.data, pkt.meta); 55 | controller.send_to(0, SdnExtIn::FeaturesControl((), FeaturesControl::Socket(send))); 56 | } 57 | OutEvent::Unbind(port) => { 58 | controller.send_to(0, SdnExtIn::FeaturesControl((), FeaturesControl::Socket(socket::Control::Unbind(port)))); 59 | } 60 | } 61 | } 62 | while let Some(event) = controller.pop_event() { 63 | // log::info!("Event: {:?}", event); 64 | match event { 65 | SdnExtOut::FeaturesEvent(_, FeaturesEvent::Socket(socket::Event::RecvFrom(local_port, remote, remote_port, data, meta))) => { 66 | if let Err(e) = tx.try_send(NetworkPkt { 67 | local_port, 68 | remote, 69 | remote_port, 70 | data, 71 | meta, 72 | }) { 73 | log::error!("Failed to send to tx: {:?}", e); 74 | } 75 | } 76 | _ => {} 77 | } 78 | } 79 | tokio::time::sleep(Duration::from_millis(1)).await; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /examples/quic-tunnel/src/vnet/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, VecDeque}; 2 | 3 | use atm0s_sdn::{base::Buffer, NodeId}; 4 | use tokio::{ 5 | select, 6 | sync::mpsc::{channel, Receiver, Sender, UnboundedReceiver, UnboundedSender}, 7 | }; 8 | 9 | pub use self::socket::VirtualUdpSocket; 10 | 11 | mod socket; 12 | 13 | #[derive(Debug)] 14 | pub enum OutEvent { 15 | Bind(u16), 16 | Pkt(NetworkPkt), 17 | Unbind(u16), 18 | } 19 | 20 | #[derive(Debug)] 21 | pub struct NetworkPkt { 22 | pub local_port: u16, 23 | pub remote: NodeId, 24 | pub remote_port: u16, 25 | pub data: Buffer, 26 | pub meta: u8, 27 | } 28 | 29 | pub struct VirtualNetwork { 30 | node_id: NodeId, 31 | in_rx: Receiver, 32 | out_tx: Sender, 33 | close_socket_tx: UnboundedSender, 34 | close_socket_rx: UnboundedReceiver, 35 | sockets: HashMap>, 36 | ports: VecDeque, 37 | } 38 | 39 | impl VirtualNetwork { 40 | pub fn new(node_id: NodeId) -> (Self, Sender, Receiver) { 41 | let (in_tx, in_rx) = tokio::sync::mpsc::channel(1000); 42 | let (out_tx, out_rx) = tokio::sync::mpsc::channel(1000); 43 | let (close_socket_tx, close_socket_rx) = tokio::sync::mpsc::unbounded_channel(); 44 | 45 | ( 46 | Self { 47 | node_id, 48 | in_rx, 49 | out_tx, 50 | close_socket_rx, 51 | close_socket_tx, 52 | sockets: HashMap::new(), 53 | ports: (0..60000).collect(), 54 | }, 55 | in_tx, 56 | out_rx, 57 | ) 58 | } 59 | 60 | pub async fn udp_socket(&mut self, port: u16) -> VirtualUdpSocket { 61 | //remove port from ports 62 | let port = if port > 0 { 63 | let index = self.ports.iter().position(|&x| x == port).expect("Should have port"); 64 | self.ports.swap_remove_back(index); 65 | port 66 | } else { 67 | self.ports.pop_front().expect("Should have port") 68 | }; 69 | self.out_tx.send(OutEvent::Bind(port)).await.expect("Should send bind"); 70 | let (tx, rx) = channel(1000); 71 | self.sockets.insert(port, tx); 72 | VirtualUdpSocket::new(self.node_id, port, self.out_tx.clone(), rx, self.close_socket_tx.clone()) 73 | } 74 | 75 | pub async fn recv(&mut self) -> Option<()> { 76 | select! { 77 | port = self.close_socket_rx.recv() => { 78 | let port = port.expect("Should have port"); 79 | self.ports.push_back(port); 80 | self.out_tx.send(OutEvent::Unbind(port)).await.expect("Should send unbind"); 81 | Some(()) 82 | } 83 | pkt = self.in_rx.recv() => { 84 | let pkt = pkt?; 85 | let src = pkt.local_port; 86 | if let Some(socket_tx) = self.sockets.get(&src) { 87 | if let Err(e) = socket_tx.try_send(pkt) { 88 | log::error!("Send to socket {} error {:?}", src, e); 89 | } 90 | } 91 | Some(()) 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /examples/quic-tunnel/src/vnet/socket.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Debug, 3 | io::IoSliceMut, 4 | net::{SocketAddr, SocketAddrV4}, 5 | ops::DerefMut, 6 | sync::Mutex, 7 | task::{Context, Poll}, 8 | }; 9 | 10 | use quinn::{ 11 | udp::{EcnCodepoint, RecvMeta, Transmit, UdpState}, 12 | AsyncUdpSocket, 13 | }; 14 | use tokio::sync::mpsc::{Receiver, Sender, UnboundedSender}; 15 | 16 | use super::{NetworkPkt, OutEvent}; 17 | 18 | pub struct VirtualUdpSocket { 19 | node_id: u32, 20 | port: u16, 21 | addr: SocketAddr, 22 | rx: Mutex>, 23 | tx: Sender, 24 | close_socket_tx: UnboundedSender, 25 | } 26 | 27 | impl VirtualUdpSocket { 28 | pub fn new(node_id: u32, port: u16, tx: Sender, rx: Receiver, close_socket_tx: UnboundedSender) -> Self { 29 | Self { 30 | node_id, 31 | port, 32 | addr: SocketAddr::V4(SocketAddrV4::new(node_id.into(), port)), 33 | rx: Mutex::new(rx), 34 | tx, 35 | close_socket_tx, 36 | } 37 | } 38 | } 39 | 40 | impl Debug for VirtualUdpSocket { 41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 42 | f.debug_struct("VirtualUdpSocket").finish() 43 | } 44 | } 45 | 46 | impl AsyncUdpSocket for VirtualUdpSocket { 47 | fn poll_send(&self, _state: &UdpState, _cx: &mut Context, transmits: &[Transmit]) -> Poll> { 48 | let mut sent = 0; 49 | for transmit in transmits { 50 | match transmit.destination { 51 | SocketAddr::V4(addr) => { 52 | let pkt = NetworkPkt { 53 | local_port: self.port, 54 | remote: u32::from_be_bytes(addr.ip().octets()), 55 | remote_port: addr.port(), 56 | data: transmit.contents.to_vec().into(), 57 | meta: transmit.ecn.map(|x| x as u8).unwrap_or(0), 58 | }; 59 | log::debug!("{} sending {} bytes to {}", self.addr, pkt.data.len(), addr); 60 | if self.tx.try_send(OutEvent::Pkt(pkt)).is_ok() { 61 | sent += 1; 62 | } 63 | } 64 | _ => { 65 | sent += 1; 66 | } 67 | } 68 | } 69 | std::task::Poll::Ready(Ok(sent)) 70 | } 71 | 72 | fn poll_recv(&self, cx: &mut Context, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> Poll> { 73 | let mut rx = self.rx.lock().expect("should lock rx"); 74 | match rx.poll_recv(cx) { 75 | std::task::Poll::Pending => std::task::Poll::Pending, 76 | std::task::Poll::Ready(Some(pkt)) => { 77 | let len = pkt.data.len(); 78 | if len <= bufs[0].len() { 79 | let addr = SocketAddr::V4(SocketAddrV4::new(pkt.remote.into(), pkt.remote_port)); 80 | log::debug!("{} received {} bytes from {}", self.addr, len, addr); 81 | bufs[0].deref_mut()[0..len].copy_from_slice(&pkt.data); 82 | meta[0] = quinn::udp::RecvMeta { 83 | addr, 84 | len, 85 | stride: len, 86 | ecn: if pkt.meta == 0 { 87 | None 88 | } else { 89 | EcnCodepoint::from_bits(pkt.meta) 90 | }, 91 | dst_ip: None, 92 | }; 93 | std::task::Poll::Ready(Ok(1)) 94 | } else { 95 | log::warn!("Buffer too small for packet {} vs {}, dropping", len, bufs[0].len()); 96 | std::task::Poll::Pending 97 | } 98 | } 99 | std::task::Poll::Ready(None) => std::task::Poll::Ready(Err(std::io::Error::new(std::io::ErrorKind::ConnectionAborted, "Socket closed"))), 100 | } 101 | } 102 | 103 | fn local_addr(&self) -> std::io::Result { 104 | Ok(self.addr) 105 | } 106 | } 107 | 108 | impl Drop for VirtualUdpSocket { 109 | fn drop(&mut self) { 110 | if let Err(e) = self.close_socket_tx.send(self.port) { 111 | log::error!("Failed to send close socket: {:?}", e); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /examples/whip-whep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "whip-whep" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | atm0s-sdn = { path = "../../packages/runner" } 9 | derive_more = "0.99.17" 10 | str0m = "0.5.0" 11 | tiny_http = "0.12.0" 12 | signal-hook = "0.3.17" 13 | env_logger = "0.11.3" 14 | log.workspace = true 15 | faster-stun = "1.0.2" 16 | clap.workspace = true 17 | serde = "1.0.197" 18 | bincode = "1.3.3" 19 | rand = "0.8.5" 20 | convert-enum = "0.1.0" 21 | -------------------------------------------------------------------------------- /examples/whip-whep/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | Whip broadcast 5 |
6 |
7 | Whep viewer 8 |
9 | 10 | -------------------------------------------------------------------------------- /examples/whip-whep/public/whep/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Whep 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /examples/whip-whep/public/whep/whep.demo.js: -------------------------------------------------------------------------------- 1 | import { WHEPClient } from "./whep.js" 2 | 3 | window.start = async () => { 4 | console.log("Will start"); 5 | //Create peerconnection 6 | const pc = window.pc = new RTCPeerConnection(); 7 | 8 | //Add recv only transceivers 9 | pc.addTransceiver("audio", { direction: 'recvonly' }); 10 | pc.addTransceiver("video", { direction: 'recvonly' }); 11 | 12 | let stream = new MediaStream(); 13 | document.querySelector("video").srcObject = stream; 14 | pc.ontrack = (event) => { 15 | stream.addTrack(event.track); 16 | } 17 | 18 | //Create whep client 19 | const whep = new WHEPClient(); 20 | 21 | const url = "/whep/endpoint"; 22 | const token = document.getElementById("room-id").value; 23 | 24 | //Start viewing 25 | whep.view(pc, url, token); 26 | 27 | window.whep_instance = whep; 28 | } 29 | 30 | window.stop = async () => { 31 | if (window.whep_instance) { 32 | window.whep_instance.stop(); 33 | } 34 | 35 | document.getElementById("video").srcObject = null; 36 | } -------------------------------------------------------------------------------- /examples/whip-whep/public/whip/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Whip 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /examples/whip-whep/public/whip/whip.demo.js: -------------------------------------------------------------------------------- 1 | import { WHIPClient } from "./whip.js" 2 | 3 | window.start = async () => { 4 | console.log("Will start"); 5 | if (window.whip_instance) { 6 | window.whip_instance.stop(); 7 | } 8 | 9 | if (window.stream_instance) { 10 | window.stream_instance.getTracks().forEach(track => track.stop()); 11 | } 12 | 13 | //Get mic+cam 14 | const stream = await navigator.mediaDevices.getUserMedia({audio:true, video:true}); 15 | 16 | document.getElementById("video").srcObject = stream; 17 | 18 | //Create peerconnection 19 | const pc = new RTCPeerConnection(); 20 | 21 | //Send all tracks 22 | for (const track of stream.getTracks()) { 23 | //You could add simulcast too here 24 | pc.addTransceiver(track, { 25 | direction: "sendonly", 26 | streams: [stream], 27 | // sendEncodings: [ 28 | // { rid: "0", active: true, scaleResolutionDownBy: 2}, 29 | // { rid: "1", active: true, scaleResolutionDownBy: 2}, 30 | // { rid: "2", active: true }, 31 | // ], 32 | }); 33 | } 34 | 35 | //Create whip client 36 | const whip = new WHIPClient(); 37 | 38 | const url = "/whip/endpoint"; 39 | const token = document.getElementById("room-id").value; 40 | 41 | //Start publishing 42 | whip.publish(pc, url, token); 43 | 44 | window.whip_instance = whip; 45 | window.stream_instance = stream; 46 | } 47 | 48 | window.stop = async () => { 49 | if (window.whip_instance) { 50 | window.whip_instance.stop(); 51 | } 52 | 53 | if (window.stream_instance) { 54 | window.stream_instance.getTracks().forEach(track => track.stop()); 55 | } 56 | 57 | document.getElementById("video").srcObject = null; 58 | } -------------------------------------------------------------------------------- /examples/whip-whep/src/http.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | use std::{collections::HashMap, fs::File, net::SocketAddr, path::Path, time::Duration}; 3 | use tiny_http::{Header, Method, Request, Response, Server}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct HttpRequest { 7 | pub req_id: u64, 8 | pub method: String, 9 | pub path: String, 10 | pub headers: HashMap, 11 | pub body: Vec, 12 | } 13 | 14 | impl HttpRequest { 15 | pub fn http_auth(&self) -> String { 16 | if let Some(auth) = self.headers.get("Authorization") { 17 | auth.clone() 18 | } else if let Some(auth) = self.headers.get("authorization") { 19 | auth.clone() 20 | } else { 21 | "demo".to_string() 22 | } 23 | } 24 | } 25 | 26 | #[derive(Debug, Clone)] 27 | pub struct HttpResponse { 28 | pub req_id: u64, 29 | pub status: u16, 30 | pub headers: HashMap, 31 | pub body: Vec, 32 | } 33 | 34 | pub struct SimpleHttpServer { 35 | req_id_seed: u64, 36 | server: Server, 37 | reqs: HashMap, 38 | } 39 | 40 | impl SimpleHttpServer { 41 | pub fn new(port: u16) -> Self { 42 | Self { 43 | req_id_seed: 0, 44 | server: Server::http(SocketAddr::from(([0, 0, 0, 0], port))).expect("Should open http port"), 45 | reqs: HashMap::new(), 46 | } 47 | } 48 | 49 | pub fn send_response(&mut self, res: HttpResponse) { 50 | log::info!("sending response for request_id {}, status {}", res.req_id, res.status); 51 | let req = self.reqs.remove(&res.req_id).expect("Should have a request."); 52 | let mut response = Response::from_data(res.body).with_status_code(res.status); 53 | for (k, v) in res.headers { 54 | response.add_header(Header::from_bytes(k.as_bytes(), v.as_bytes()).unwrap()); 55 | } 56 | response.add_header(Header::from_bytes("Access-Control-Allow-Origin", "*").unwrap()); 57 | response.add_header(Header::from_bytes("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS").unwrap()); 58 | response.add_header(Header::from_bytes("Access-Control-Allow-Headers", "*").unwrap()); 59 | response.add_header(Header::from_bytes("Access-Control-Allow-Credentials", "true").unwrap()); 60 | req.respond(response).unwrap(); 61 | } 62 | 63 | pub fn recv(&mut self, timeout: Duration) -> Result, std::io::Error> { 64 | let mut request = if let Some(req) = self.server.recv_timeout(timeout)? { 65 | req 66 | } else { 67 | return Ok(None); 68 | }; 69 | if request.url().starts_with("/public") { 70 | if let Ok(file) = File::open(&Path::new(&format!(".{}", request.url()))) { 71 | let mut response = tiny_http::Response::from_file(file); 72 | if request.url().ends_with(".js") { 73 | response.add_header(Header::from_bytes("Content-Type", "application/javascript").unwrap()); 74 | } else if request.url().ends_with(".css") { 75 | response.add_header(Header::from_bytes("Content-Type", "text/css").unwrap()); 76 | } 77 | request.respond(response).expect("Should respond file."); 78 | return Ok(None); 79 | } else { 80 | let response = Response::from_string("Not Found"); 81 | request.respond(response.with_status_code(404)).expect("Should respond 404."); 82 | return Ok(None); 83 | } 84 | } 85 | 86 | if request.method().eq(&Method::Options) { 87 | let mut response = Response::from_string("OK"); 88 | //setting CORS 89 | response.add_header(Header::from_bytes("Access-Control-Allow-Origin", "*").unwrap()); 90 | response.add_header(Header::from_bytes("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS").unwrap()); 91 | response.add_header(Header::from_bytes("Access-Control-Allow-Headers", "*").unwrap()); 92 | response.add_header(Header::from_bytes("Access-Control-Allow-Credentials", "true").unwrap()); 93 | 94 | request.respond(response).expect("Should respond options."); 95 | return Ok(None); 96 | } 97 | 98 | log::info!("received request_id {} method: {}, url: {}", self.req_id_seed, request.method(), request.url(),); 99 | 100 | let req_id = self.req_id_seed; 101 | self.req_id_seed += 1; 102 | 103 | let res = Ok(Some(HttpRequest { 104 | req_id, 105 | method: request.method().to_string(), 106 | path: request.url().to_string(), 107 | headers: request.headers().iter().map(|h| (h.field.to_string(), h.value.to_string())).collect(), 108 | body: request.as_reader().bytes().map(|b| b.unwrap()).collect(), 109 | })); 110 | self.reqs.insert(req_id, request); 111 | res 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /examples/whip-whep/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | net::SocketAddr, 3 | sync::{ 4 | atomic::{AtomicBool, Ordering}, 5 | Arc, 6 | }, 7 | time::Duration, 8 | vec, 9 | }; 10 | 11 | use atm0s_sdn::sans_io_runtime::{backend::PollingBackend, Controller}; 12 | use atm0s_sdn::{ 13 | base::ServiceBuilder, 14 | features::{FeaturesControl, FeaturesEvent}, 15 | secure::{HandshakeBuilderXDA, StaticKeyAuthorization}, 16 | services::visualization, 17 | DataWorkerHistory, NodeAddr, NodeAddrBuilder, NodeId, Protocol, SdnExtIn, 18 | }; 19 | use clap::Parser; 20 | 21 | use worker::{ChannelId, Event, ExtIn, ExtOut, ICfg, SCfg, SC, SE, TC, TW}; 22 | 23 | use crate::worker::{ControllerCfg, RunnerOwner, RunnerWorker, SdnInnerCfg}; 24 | 25 | mod http; 26 | mod sfu; 27 | mod worker; 28 | 29 | /// Quic-tunnel demo application 30 | #[derive(Parser, Debug)] 31 | #[command(version, about, long_about = None)] 32 | struct Args { 33 | /// Node Id 34 | #[arg(env, short, long, default_value_t = 1)] 35 | node_id: NodeId, 36 | 37 | /// Listen address 38 | #[arg(env, short, long, default_value_t = 10000)] 39 | udp_port: u16, 40 | 41 | /// Address of node we should connect to 42 | #[arg(env, short, long)] 43 | seeds: Vec, 44 | 45 | /// Password for the network 46 | #[arg(env, short, long, default_value = "password")] 47 | password: String, 48 | 49 | /// Workers 50 | #[arg(env, long, default_value_t = 1)] 51 | workers: usize, 52 | 53 | /// Http listen port 54 | #[arg(env, long, default_value_t = 8080)] 55 | http_port: u16, 56 | } 57 | 58 | fn main() { 59 | if std::env::var_os("RUST_LOG").is_none() { 60 | std::env::set_var("RUST_LOG", "info"); 61 | } 62 | let args = Args::parse(); 63 | env_logger::builder().format_timestamp_millis().init(); 64 | 65 | let auth = Arc::new(StaticKeyAuthorization::new(&args.password)); 66 | let history = Arc::new(DataWorkerHistory::default()); 67 | 68 | let mut server = http::SimpleHttpServer::new(args.http_port); 69 | let mut controller = Controller::::default(); 70 | let services: Vec>> = vec![Arc::new(visualization::VisualizationServiceBuilder::<(), SC, SE, TC, TW>::new(false))]; 71 | 72 | let mut addr_builder = NodeAddrBuilder::new(args.node_id); 73 | addr_builder.add_protocol(Protocol::Ip4("192.168.1.39".parse().unwrap())); 74 | addr_builder.add_protocol(Protocol::Udp(args.udp_port)); 75 | let addr = addr_builder.addr(); 76 | log::info!("Node address: {}", addr); 77 | 78 | controller.add_worker::>( 79 | Duration::from_millis(10), 80 | ICfg { 81 | sfu: "192.168.1.39:0".parse().unwrap(), 82 | sdn: SdnInnerCfg { 83 | node_id: args.node_id, 84 | tick_ms: 1000, 85 | controller: Some(ControllerCfg { 86 | session: 0, 87 | auth, 88 | handshake: Arc::new(HandshakeBuilderXDA), 89 | }), 90 | services: services.clone(), 91 | history: history.clone(), 92 | #[cfg(feature = "vpn")] 93 | vpn_tun_fd: None, 94 | }, 95 | sdn_listen: SocketAddr::from(([0, 0, 0, 0], args.udp_port)), 96 | }, 97 | None, 98 | ); 99 | 100 | for _ in 1..args.workers { 101 | controller.add_worker::>( 102 | Duration::from_millis(10), 103 | ICfg { 104 | sfu: "192.168.1.39:0".parse().unwrap(), 105 | sdn: SdnInnerCfg { 106 | node_id: args.node_id, 107 | tick_ms: 1000, 108 | controller: None, 109 | services: services.clone(), 110 | history: history.clone(), 111 | #[cfg(feature = "vpn")] 112 | vpn_tun_fd: None, 113 | }, 114 | sdn_listen: SocketAddr::from(([0, 0, 0, 0], args.udp_port)), 115 | }, 116 | None, 117 | ); 118 | } 119 | 120 | for seed in args.seeds { 121 | controller.send_to(0, ExtIn::Sdn(SdnExtIn::ConnectTo(seed))); 122 | } 123 | 124 | // let term = Arc::new(AtomicBool::new(false)); 125 | // signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&term)).expect("Should register hook"); 126 | 127 | while let Ok(req) = server.recv(Duration::from_millis(100)) { 128 | if controller.process().is_none() { 129 | break; 130 | } 131 | // if term.load(Ordering::Relaxed) { 132 | // controller.shutdown(); 133 | // } 134 | while let Some(ext) = controller.pop_event() { 135 | match ext { 136 | ExtOut::HttpResponse(resp) => { 137 | server.send_response(resp); 138 | } 139 | ExtOut::Sdn(event) => {} 140 | } 141 | } 142 | if let Some(req) = req { 143 | controller.send_to_best(ExtIn::HttpRequest(req)); 144 | } 145 | } 146 | 147 | log::info!("Server shutdown"); 148 | } 149 | -------------------------------------------------------------------------------- /examples/whip-whep/src/sfu/cluster.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | hash::{DefaultHasher, Hash, Hasher}, 4 | time::Instant, 5 | }; 6 | 7 | use atm0s_sdn::features::pubsub::{self, ChannelControl, Feedback}; 8 | use str0m::media::KeyframeRequestKind; 9 | 10 | use super::{TrackMedia, WhepOwner, WhipOwner}; 11 | 12 | pub fn room_channel(room: &str) -> u64 { 13 | let mut hasher = DefaultHasher::new(); 14 | room.hash(&mut hasher); 15 | hasher.finish() 16 | } 17 | 18 | pub enum Input { 19 | Pubsub(pubsub::Event), 20 | WhipStart(WhipOwner, String), 21 | WhipStop(WhipOwner), 22 | WhipMedia(WhipOwner, TrackMedia), 23 | WhepStart(WhepOwner, String), 24 | WhepStop(WhepOwner), 25 | WhepRequest(WhepOwner, KeyframeRequestKind), 26 | } 27 | 28 | pub enum Output { 29 | Pubsub(pubsub::Control), 30 | WhepMedia(Vec, TrackMedia), 31 | WhipControl(Vec, KeyframeRequestKind), 32 | } 33 | 34 | pub struct Channel { 35 | whips: Vec, 36 | wheps: Vec, 37 | } 38 | 39 | #[derive(Default)] 40 | pub struct ClusterLogic { 41 | channels: HashMap, 42 | whips: HashMap, 43 | wheps: HashMap, 44 | } 45 | 46 | impl ClusterLogic { 47 | pub fn on_input(&mut self, now: Instant, input: Input) -> Option { 48 | match input { 49 | Input::Pubsub(pubsub::Event(channel, event)) => match event { 50 | pubsub::ChannelEvent::RouteChanged(_) => None, 51 | pubsub::ChannelEvent::SourceData(_, data) => { 52 | let pkt = TrackMedia::from_buffer(&data); 53 | let channel = self.channels.get(&channel)?; 54 | Some(Output::WhepMedia(channel.wheps.clone(), pkt)) 55 | } 56 | pubsub::ChannelEvent::FeedbackData(fb) => { 57 | let channel = self.channels.get(&channel)?; 58 | let kind = match fb.kind { 59 | 0 => KeyframeRequestKind::Pli, 60 | _ => KeyframeRequestKind::Fir, 61 | }; 62 | Some(Output::WhipControl(channel.whips.clone(), kind)) 63 | } 64 | }, 65 | Input::WhipStart(owner, room) => { 66 | log::info!("WhipStart: {:?}, {:?}", owner, room); 67 | let channel_id = room_channel(&room); 68 | self.whips.insert(owner, channel_id); 69 | let channel = self.channels.entry(channel_id).or_insert(Channel { whips: Vec::new(), wheps: Vec::new() }); 70 | channel.whips.push(owner); 71 | if channel.whips.len() == 1 { 72 | Some(Output::Pubsub(pubsub::Control(channel_id.into(), pubsub::ChannelControl::PubStart))) 73 | } else { 74 | None 75 | } 76 | } 77 | Input::WhipStop(owner) => { 78 | log::info!("WhipStop: {:?}", owner); 79 | let channel_id = self.whips.remove(&owner)?; 80 | let channel = self.channels.get_mut(&channel_id)?; 81 | channel.whips.retain(|&o| o != owner); 82 | if channel.whips.is_empty() { 83 | Some(Output::Pubsub(pubsub::Control(channel_id.into(), pubsub::ChannelControl::PubStop))) 84 | } else { 85 | None 86 | } 87 | } 88 | Input::WhipMedia(owner, media) => { 89 | log::trace!("WhipMedia: {:?}, {}", owner, media.seq_no); 90 | let channel_id = self.whips.get(&owner)?; 91 | let buf = media.to_buffer(); 92 | Some(Output::Pubsub(pubsub::Control((*channel_id).into(), pubsub::ChannelControl::PubData(buf)))) 93 | } 94 | Input::WhepStart(owner, room) => { 95 | log::info!("WhepStart: {:?}, {:?}", owner, room); 96 | let channel_id = room_channel(&room); 97 | self.wheps.insert(owner, channel_id); 98 | let channel = self.channels.entry(channel_id).or_insert(Channel { whips: Vec::new(), wheps: Vec::new() }); 99 | channel.wheps.push(owner); 100 | if channel.wheps.len() == 1 { 101 | Some(Output::Pubsub(pubsub::Control(channel_id.into(), pubsub::ChannelControl::SubAuto))) 102 | } else { 103 | None 104 | } 105 | } 106 | Input::WhepStop(owner) => { 107 | log::info!("WhepStop: {:?}", owner); 108 | let channel_id = self.wheps.remove(&owner)?; 109 | let channel = self.channels.get_mut(&channel_id)?; 110 | channel.wheps.retain(|&o| o != owner); 111 | if channel.wheps.is_empty() { 112 | Some(Output::Pubsub(pubsub::Control(channel_id.into(), pubsub::ChannelControl::UnsubAuto))) 113 | } else { 114 | None 115 | } 116 | } 117 | Input::WhepRequest(owner, kind) => { 118 | let kind = match kind { 119 | KeyframeRequestKind::Pli => 0, 120 | KeyframeRequestKind::Fir => 1, 121 | }; 122 | let channel_id = self.wheps.get(&owner)?; 123 | Some(Output::Pubsub(pubsub::Control( 124 | (*channel_id).into(), 125 | ChannelControl::FeedbackAuto(Feedback::simple(kind, 1, 1000, 2000)), 126 | ))) 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /examples/whip-whep/src/sfu/media.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use str0m::rtp::RtpPacket; 3 | 4 | #[derive(Debug, Clone, Serialize, Deserialize)] 5 | pub struct TrackMedia { 6 | pub seq_no: u64, 7 | pub pt: u8, 8 | pub time: u32, 9 | pub marker: bool, 10 | pub payload: Vec, 11 | } 12 | 13 | impl TrackMedia { 14 | pub fn from_raw(rtp: RtpPacket) -> Self { 15 | Self { 16 | seq_no: *rtp.seq_no, 17 | pt: *rtp.header.payload_type, 18 | time: rtp.header.timestamp, 19 | marker: rtp.header.marker, 20 | payload: rtp.payload, 21 | } 22 | } 23 | 24 | pub fn to_buffer(&self) -> Vec { 25 | bincode::serialize(self).expect("") 26 | } 27 | 28 | pub fn from_buffer(data: &[u8]) -> Self { 29 | bincode::deserialize(data).expect("") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/whip-whep/src/sfu/shared_port.rs: -------------------------------------------------------------------------------- 1 | use faster_stun::attribute::*; 2 | use faster_stun::*; 3 | use std::{collections::HashMap, fmt::Debug, hash::Hash, net::SocketAddr}; 4 | 5 | #[derive(Debug)] 6 | pub struct SharedUdpPort { 7 | backend_addr: Option, 8 | task_remotes: HashMap, 9 | task_remotes_map: HashMap>, 10 | task_ufrags: HashMap, 11 | task_ufrags_reverse: HashMap, 12 | } 13 | 14 | impl Default for SharedUdpPort { 15 | fn default() -> Self { 16 | Self { 17 | backend_addr: None, 18 | task_remotes: HashMap::new(), 19 | task_remotes_map: HashMap::new(), 20 | task_ufrags: HashMap::new(), 21 | task_ufrags_reverse: HashMap::new(), 22 | } 23 | } 24 | } 25 | 26 | impl SharedUdpPort { 27 | pub fn set_backend_info(&mut self, addr: SocketAddr) { 28 | self.backend_addr = Some(addr); 29 | } 30 | 31 | pub fn get_backend_addr(&self) -> Option { 32 | self.backend_addr 33 | } 34 | 35 | pub fn add_ufrag(&mut self, ufrag: String, task: Task) { 36 | log::info!("Add ufrag {} to task {:?}", ufrag, task); 37 | self.task_ufrags.insert(ufrag.clone(), task); 38 | self.task_ufrags_reverse.insert(task, ufrag); 39 | } 40 | 41 | pub fn remove_task(&mut self, task: Task) -> Option<()> { 42 | let ufrag = self.task_ufrags_reverse.remove(&task)?; 43 | log::info!("Remove task {:?} => ufrag {}", task, ufrag); 44 | self.task_ufrags.remove(&ufrag)?; 45 | let remotes = self.task_remotes_map.remove(&task)?; 46 | for remote in remotes { 47 | log::info!(" Remove remote {:?} => task {:?}", remote, task); 48 | self.task_remotes.remove(&remote); 49 | } 50 | Some(()) 51 | } 52 | 53 | pub fn map_remote(&mut self, remote: SocketAddr, buf: &[u8]) -> Option { 54 | if let Some(task) = self.task_remotes.get(&remote) { 55 | return Some(*task); 56 | } 57 | 58 | let stun_username = Self::get_stun_username(buf)?; 59 | log::warn!("Received a stun packet from an unknown remote: {:?}, username {}", remote, stun_username); 60 | let task = self.task_ufrags.get(stun_username)?; 61 | log::info!("Mapping remote {:?} to task {:?}", remote, task); 62 | self.task_remotes.insert(remote, *task); 63 | self.task_remotes_map.entry(*task).or_default().push(remote); 64 | Some(*task) 65 | } 66 | 67 | fn get_stun_username(buf: &[u8]) -> Option<&str> { 68 | let mut attributes = Vec::new(); 69 | let message = MessageReader::decode(buf, &mut attributes).ok()?; 70 | message.get::().map(|u| u.split(':').next())? 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bin-fuzz" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | license = "MIT" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | libfuzzer-sys = "0.4" 13 | atm0s-sdn-network = { path = "../packages/network", version = "0.7.1", features = [ 14 | "fuzz", 15 | ] } 16 | 17 | [[bin]] 18 | name = "network_control_pkt" 19 | path = "fuzz_targets/network_control_pkt.rs" 20 | test = false 21 | doc = false 22 | bench = false 23 | 24 | [[bin]] 25 | name = "transport_msg" 26 | path = "fuzz_targets/transport_msg.rs" 27 | test = false 28 | doc = false 29 | bench = false 30 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/network_control_pkt.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | 5 | use atm0s_sdn_network::base::NeighboursControl; 6 | 7 | fuzz_target!(|data: &[u8]| { 8 | let _ = NeighboursControl::try_from(data); 9 | }); 10 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/transport_msg.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use atm0s_sdn_network::base::TransportMsg; 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let _ = TransportMsg::try_from(data); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/core/identity/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.3.2](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-identity-v0.3.1...atm0s-sdn-identity-v0.3.2) - 2025-02-08 10 | 11 | ### Fixed 12 | 13 | - error reported by cargo deny (#191) 14 | - router with duplicate entry in hops (#190) 15 | 16 | ## [0.3.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-identity-v0.3.0...atm0s-sdn-identity-v0.3.1) - 2024-07-22 17 | 18 | ### Other 19 | - clippy fixes ([#167](https://github.com/8xFF/atm0s-sdn/pull/167)) 20 | 21 | ## [0.2.0](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-identity-v0.1.2...atm0s-sdn-identity-v0.2.0) - 2023-12-27 22 | 23 | ### Added 24 | - node multi addrs ([#98](https://github.com/8xFF/atm0s-sdn/pull/98)) 25 | 26 | ## [0.1.2](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-identity-v0.1.1...atm0s-sdn-identity-v0.1.2) - 2023-12-11 27 | 28 | ### Other 29 | - move local deps out of workspace Cargo.toml ([#92](https://github.com/8xFF/atm0s-sdn/pull/92)) 30 | 31 | ## [0.1.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-identity-v0.1.0...atm0s-sdn-identity-v0.1.1) - 2023-11-17 32 | 33 | ### Other 34 | - release ([#63](https://github.com/8xFF/atm0s-sdn/pull/63)) 35 | -------------------------------------------------------------------------------- /packages/core/identity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atm0s-sdn-identity" 3 | version = "0.3.2" 4 | edition = "2021" 5 | description = "A library for generating and verifying identities for atm0s-sdn" 6 | license = "MIT" 7 | 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | multiaddr = { version = "0.18", default-features = false } 13 | rand = "0.8" 14 | serde = { workspace = true } 15 | -------------------------------------------------------------------------------- /packages/core/identity/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::bool_assert_comparison)] 2 | 3 | mod conn_id; 4 | mod node_addr; 5 | mod node_id; 6 | 7 | pub use conn_id::{ConnDirection, ConnId}; 8 | pub use node_addr::{NodeAddr, NodeAddrBuilder, Protocol}; 9 | pub use node_id::{NodeId, NodeIdType, NodeSegment}; 10 | -------------------------------------------------------------------------------- /packages/core/identity/src/node_addr.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::{fmt::Display, str::FromStr}; 3 | 4 | use crate::node_id::NodeId; 5 | pub use multiaddr::Protocol; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 8 | pub struct NodeAddr(NodeId, multiaddr::Multiaddr); 9 | 10 | impl NodeAddr { 11 | pub fn empty(node_id: NodeId) -> Self { 12 | Self(node_id, multiaddr::Multiaddr::empty()) 13 | } 14 | 15 | pub fn node_id(&self) -> NodeId { 16 | self.0 17 | } 18 | 19 | pub fn multiaddr(&self) -> &multiaddr::Multiaddr { 20 | &self.1 21 | } 22 | 23 | pub fn from_iter<'a>(node_id: NodeId, iter: impl IntoIterator>) -> Self { 24 | Self(node_id, multiaddr::Multiaddr::from_iter(iter)) 25 | } 26 | 27 | pub fn to_vec(&self) -> Vec { 28 | let mut buf = self.0.to_be_bytes().to_vec(); 29 | buf.extend(self.1.to_vec()); 30 | buf 31 | } 32 | 33 | pub fn from_vec(buf: &[u8]) -> Option { 34 | let node_id = NodeId::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]); 35 | let multiaddr = multiaddr::Multiaddr::try_from(buf[4..].to_vec()).ok()?; 36 | Some(Self(node_id, multiaddr)) 37 | } 38 | } 39 | 40 | impl FromStr for NodeAddr { 41 | type Err = String; 42 | 43 | fn from_str(s: &str) -> Result { 44 | let mut split = s.split('@'); 45 | let node_id = split.next().ok_or("Missing NodeId".to_string())?.parse::().map_err(|e| e.to_string())?; 46 | let multiaddr = split.next().unwrap_or("").parse::().map_err(|e| e.to_string())?; 47 | Ok(Self(node_id, multiaddr)) 48 | } 49 | } 50 | 51 | impl Display for NodeAddr { 52 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 53 | if self.1.is_empty() { 54 | write!(f, "{}", self.0) 55 | } else { 56 | write!(f, "{}@{}", self.0, self.1) 57 | } 58 | } 59 | } 60 | 61 | /// A builder for creating `NodeAddr` instances. 62 | pub struct NodeAddrBuilder { 63 | node_id: NodeId, 64 | addr: multiaddr::Multiaddr, 65 | } 66 | 67 | impl NodeAddrBuilder { 68 | pub fn new(node_id: NodeId) -> Self { 69 | Self { 70 | node_id, 71 | addr: multiaddr::Multiaddr::empty(), 72 | } 73 | } 74 | 75 | pub fn node_id(&self) -> NodeId { 76 | self.node_id 77 | } 78 | 79 | /// Adds a protocol to the node address. 80 | pub fn add_protocol(&mut self, protocol: Protocol) { 81 | self.addr.push(protocol); 82 | } 83 | 84 | /// Get the node address. 85 | pub fn addr(&self) -> NodeAddr { 86 | NodeAddr(self.node_id, self.addr.clone()) 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use std::str::FromStr; 93 | 94 | use multiaddr::Multiaddr; 95 | 96 | #[test] 97 | fn test_to_from_str() { 98 | let addr = super::NodeAddr::from_str("1@/ip4/127.0.0.1").unwrap(); 99 | assert_eq!(addr, super::NodeAddr(1, "/ip4/127.0.0.1".parse().unwrap())); 100 | assert_eq!(addr.to_string(), "1@/ip4/127.0.0.1"); 101 | } 102 | 103 | #[test] 104 | fn test_empty() { 105 | let addr = super::NodeAddr::from_str("1").unwrap(); 106 | assert_eq!(addr, super::NodeAddr(1, Multiaddr::empty())); 107 | assert_eq!(addr, super::NodeAddr::empty(1)); 108 | assert_eq!(addr.to_string(), "1"); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /packages/core/router/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.3.0](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-router-v0.2.4...atm0s-sdn-router-v0.3.0) - 2025-02-27 10 | 11 | ### Fixed 12 | 13 | - router don't clear service router info after node disconnect (#192) 14 | 15 | ## [0.2.4](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-router-v0.2.3...atm0s-sdn-router-v0.2.4) - 2025-02-08 16 | 17 | ### Added 18 | 19 | - discovery node by service broadcast (#188) 20 | 21 | ### Fixed 22 | 23 | - router with duplicate entry in hops (#190) 24 | - avoid send wrong hops with router-sync local services (#184) 25 | 26 | ## [0.2.3](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-router-v0.2.2...atm0s-sdn-router-v0.2.3) - 2024-11-26 27 | 28 | ### Added 29 | 30 | - add router dump ([#182](https://github.com/8xFF/atm0s-sdn/pull/182)) 31 | 32 | ## [0.2.2](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-router-v0.2.1...atm0s-sdn-router-v0.2.2) - 2024-11-08 33 | 34 | ### Other 35 | 36 | - update Cargo.toml dependencies 37 | 38 | ## [0.2.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-router-v0.2.0...atm0s-sdn-router-v0.2.1) - 2024-07-22 39 | 40 | ### Other 41 | - clippy fixes ([#167](https://github.com/8xFF/atm0s-sdn/pull/167)) 42 | 43 | ## [0.1.4](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-router-v0.1.3...atm0s-sdn-router-v0.1.4) - 2023-12-27 44 | 45 | ### Other 46 | - update Cargo.toml dependencies 47 | 48 | ## [0.1.3](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-router-v0.1.2...atm0s-sdn-router-v0.1.3) - 2023-12-12 49 | 50 | ### Other 51 | - update dependencies 52 | 53 | ## [0.1.2](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-router-v0.1.1...atm0s-sdn-router-v0.1.2) - 2023-12-11 54 | 55 | ### Other 56 | - move local deps out of workspace Cargo.toml ([#92](https://github.com/8xFF/atm0s-sdn/pull/92)) 57 | 58 | ## [0.1.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-router-v0.1.0...atm0s-sdn-router-v0.1.1) - 2023-12-11 59 | 60 | ### Fixed 61 | - missing register service ([#85](https://github.com/8xFF/atm0s-sdn/pull/85)) 62 | -------------------------------------------------------------------------------- /packages/core/router/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atm0s-sdn-router" 3 | version = "0.3.0" 4 | edition = "2021" 5 | description = "Router interface for atm0s-sdn" 6 | license = "MIT" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | atm0s-sdn-identity = { path = "../identity", version = "0.3.2" } 12 | atm0s-sdn-utils = { path = "../utils", version = "0.2.1" } 13 | serde = { workspace = true } 14 | log = { workspace = true } 15 | mockall = { workspace = true } 16 | 17 | 18 | [dev-dependencies] 19 | env_logger = { workspace = true } 20 | criterion = { version = "0.5.1" } 21 | rand = { version = "0.8.5" } 22 | 23 | [[bench]] 24 | name = "router" 25 | harness = false 26 | -------------------------------------------------------------------------------- /packages/core/router/README.md: -------------------------------------------------------------------------------- 1 | # atm0s-sdn-router 2 | 3 | This is router module for atm0s-sdn. 4 | 5 | ## Benchmark 6 | 7 | Bellow is result of benchmarking atm0s-sdn-router with Macbook M1 Pro. 8 | 9 | ```bash 10 | empty/next_node time: [4.1770 ns 4.2103 ns 4.2598 ns] 11 | thrpt: [234.75 Melem/s 237.51 Melem/s 239.40 Melem/s] 12 | Found 5 outliers among 100 measurements (5.00%) 13 | 2 (2.00%) high mild 14 | 3 (3.00%) high severe 15 | empty/next_closest time: [17.148 ns 17.172 ns 17.206 ns] 16 | thrpt: [58.121 Melem/s 58.236 Melem/s 58.317 Melem/s] 17 | Found 4 outliers among 100 measurements (4.00%) 18 | 2 (2.00%) low mild 19 | 1 (1.00%) high mild 20 | 1 (1.00%) high severe 21 | empty/next_service time: [2.0514 ns 2.0552 ns 2.0594 ns] 22 | thrpt: [485.59 Melem/s 486.56 Melem/s 487.48 Melem/s] 23 | Found 6 outliers among 100 measurements (6.00%) 24 | 4 (4.00%) high mild 25 | 2 (2.00%) high severe 26 | 27 | single/next_node time: [4.8156 ns 4.8222 ns 4.8298 ns] 28 | thrpt: [207.05 Melem/s 207.37 Melem/s 207.66 Melem/s] 29 | Found 3 outliers among 100 measurements (3.00%) 30 | 3 (3.00%) high mild 31 | single/next_closest time: [27.781 ns 27.937 ns 28.226 ns] 32 | thrpt: [35.429 Melem/s 35.794 Melem/s 35.996 Melem/s] 33 | Found 2 outliers among 100 measurements (2.00%) 34 | 2 (2.00%) high severe 35 | single/next_service time: [2.0487 ns 2.0515 ns 2.0547 ns] 36 | thrpt: [486.68 Melem/s 487.45 Melem/s 488.12 Melem/s] 37 | Found 3 outliers among 100 measurements (3.00%) 38 | 3 (3.00%) high mild 39 | 40 | full/next_node time: [4.8162 ns 4.8228 ns 4.8298 ns] 41 | thrpt: [207.05 Melem/s 207.35 Melem/s 207.63 Melem/s] 42 | Found 21 outliers among 100 measurements (21.00%) 43 | 14 (14.00%) high mild 44 | 7 (7.00%) high severe 45 | full/next_closest time: [257.14 ns 258.14 ns 259.53 ns] 46 | thrpt: [3.8532 Melem/s 3.8739 Melem/s 3.8889 Melem/s] 47 | Found 6 outliers among 100 measurements (6.00%) 48 | 3 (3.00%) high mild 49 | 3 (3.00%) high severe 50 | full/next_service time: [2.0579 ns 2.0616 ns 2.0651 ns] 51 | thrpt: [484.24 Melem/s 485.07 Melem/s 485.94 Melem/s] 52 | Found 5 outliers among 100 measurements (5.00%) 53 | 5 (5.00%) high mild 54 | ``` -------------------------------------------------------------------------------- /packages/core/router/benches/router.rs: -------------------------------------------------------------------------------- 1 | use std::vec; 2 | 3 | use atm0s_sdn_identity::ConnId; 4 | use atm0s_sdn_router::core::{Metric, RegistrySync, Router, RouterSync}; 5 | use criterion::{criterion_group, criterion_main, Criterion}; 6 | 7 | criterion_group!(benches, benchmark_empty, benchmark_single, benchmark_full); 8 | criterion_main!(benches); 9 | 10 | fn benchmark_empty(c: &mut Criterion) { 11 | let mut group = c.benchmark_group("empty"); 12 | group.throughput(criterion::Throughput::Elements(1)); 13 | let router = Router::new(0); 14 | group.bench_function("next_node", |b| { 15 | b.iter(|| router.next(1, &[])); 16 | }); 17 | 18 | group.bench_function("next_closest", |b| { 19 | b.iter(|| router.closest_node(rand::random(), &[])); 20 | }); 21 | 22 | let router = Router::new(0); 23 | group.bench_function("next_service", |b| { 24 | b.iter(|| router.service_next(1, &[])); 25 | }); 26 | } 27 | 28 | fn benchmark_single(c: &mut Criterion) { 29 | let mut group = c.benchmark_group("single"); 30 | group.throughput(criterion::Throughput::Elements(1)); 31 | let mut router = Router::new(0); 32 | router.set_direct(ConnId::from_in(0, 0), 1, Metric::new(1, vec![1], 100000)); 33 | group.bench_function("next_node", |b| { 34 | b.iter(|| router.next(1, &[])); 35 | }); 36 | 37 | group.bench_function("next_closest", |b| { 38 | b.iter(|| router.closest_node(rand::random(), &[])); 39 | }); 40 | 41 | let mut router = Router::new(0); 42 | router.set_direct(ConnId::from_in(0, 0), 1, Metric::new(1, vec![1], 100000)); 43 | router.apply_sync( 44 | ConnId::from_in(0, 0), 45 | 1, 46 | Metric::new(1, vec![1], 100000), 47 | RouterSync(RegistrySync(vec![(0, Metric::new(1, vec![], 100000))]), [None, None, None, None]), 48 | ); 49 | group.bench_function("next_service", |b| { 50 | b.iter(|| router.service_next(1, &[])); 51 | }); 52 | } 53 | 54 | fn benchmark_full(c: &mut Criterion) { 55 | let mut group = c.benchmark_group("full"); 56 | group.throughput(criterion::Throughput::Elements(1)); 57 | let mut router = Router::new(0); 58 | for n in 1..255 { 59 | router.set_direct(ConnId::from_in(0, n as u64), n, Metric::new(1, vec![n], 100000)); 60 | } 61 | group.bench_function("next_node", |b| { 62 | b.iter(|| router.next(1, &[])); 63 | }); 64 | 65 | group.bench_function("next_closest", |b| { 66 | b.iter(|| router.closest_node(rand::random(), &[])); 67 | }); 68 | 69 | let mut router = Router::new(0); 70 | let mut services = vec![]; 71 | for s in 0..255 { 72 | services.push((s, Metric::new(1, vec![1], 100000))); 73 | } 74 | router.set_direct(ConnId::from_in(0, 0), 1, Metric::new(1, vec![1], 100000)); 75 | router.apply_sync(ConnId::from_in(0, 0), 1, Metric::new(1, vec![1], 100000), RouterSync(RegistrySync(services), [None, None, None, None])); 76 | group.bench_function("next_service", |b| { 77 | b.iter(|| router.service_next(1, &[])); 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /packages/core/router/src/core/mod.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_identity::{ConnId, NodeId}; 2 | 3 | mod registry; 4 | mod router; 5 | mod table; 6 | 7 | pub use self::registry::{RegisterDump, RegisterRemoteDestDump, Registry, RegistryDelta, RegistryRemoteDestDelta, RegistrySync}; 8 | pub use self::router::{Router, RouterDelta, RouterDump, RouterSync}; 9 | pub use self::table::{DestDelta, DestDump, Metric, Path, TableDelta, TableDump, TableSync, BANDWIDTH_LIMIT}; 10 | 11 | #[derive(PartialEq, Debug)] 12 | pub enum ServiceDestination { 13 | Local, 14 | Remote(ConnId, NodeId), 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/router/src/core/table/metric.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | use atm0s_sdn_identity::NodeId; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | pub const BANDWIDTH_LIMIT: u32 = 10000; //10Mbps 7 | const BANDWIDTH_SCORE_PENALTY: u32 = 1000; //1s 8 | const HOP_PLUS_RTT: u16 = 10; //10ms each hops 9 | const LOCAL_BANDWIDTH: u32 = 1_000_000; //1Gbps 10 | 11 | /// Concatenate two hops array, with condition that the last hop of `a` is the first hop of `b`, if not return None 12 | pub fn concat_hops(a: &[NodeId], b: &[NodeId]) -> Vec { 13 | let mut ret = a.to_vec(); 14 | ret.extend_from_slice(&b[0..]); 15 | ret 16 | } 17 | 18 | /// Path to destination, all nodes in reverse path 19 | /// Example with local connection : A -> A => hops: [A], 20 | /// Example with direct connection : A -> B => hops: [B, A], 21 | /// Example with indirect connection : A -> B -> C => hops: [C, B, B], 22 | #[derive(Serialize, Deserialize, Debug, Clone)] 23 | pub struct Metric { 24 | latency: u16, //in milliseconds 25 | hops: Vec, //in hops, from 0 (direct) 26 | bandwidth: u32, //in kbps 27 | // pub lost: f32, 28 | // pub jitter: u16, 29 | } 30 | 31 | impl Metric { 32 | pub fn local() -> Self { 33 | Metric::new(0, vec![], LOCAL_BANDWIDTH) 34 | } 35 | 36 | pub fn direct(latency: u16, node: NodeId, bandwidth: u32) -> Self { 37 | Metric::new(latency, vec![node], bandwidth) 38 | } 39 | 40 | pub fn new(latency: u16, hops: Vec, bandwidth: u32) -> Self { 41 | Metric { latency, hops, bandwidth } 42 | } 43 | 44 | pub fn contain_in_hops(&self, node_id: NodeId) -> bool { 45 | self.hops.contains(&node_id) 46 | } 47 | 48 | pub fn add(&self, other: &Self) -> Self { 49 | Metric { 50 | latency: self.latency + other.latency, 51 | hops: concat_hops(&self.hops, &other.hops), 52 | bandwidth: std::cmp::min(self.bandwidth, other.bandwidth), 53 | } 54 | } 55 | 56 | pub fn score(&self) -> u32 { 57 | let based_score = self.latency as u32 + (self.hops.len() as u32 * HOP_PLUS_RTT as u32); 58 | if self.bandwidth >= BANDWIDTH_LIMIT { 59 | based_score 60 | } else { 61 | based_score + BANDWIDTH_SCORE_PENALTY 62 | } 63 | } 64 | 65 | /// Get destination of this metric, in case it is localy, it will be None 66 | pub fn dest_node(&self) -> Option { 67 | self.hops.first().cloned() 68 | } 69 | 70 | pub fn hops(&self) -> &[NodeId] { 71 | &self.hops 72 | } 73 | } 74 | 75 | impl Ord for Metric { 76 | fn cmp(&self, other: &Self) -> Ordering { 77 | self.score().cmp(&other.score()) 78 | } 79 | } 80 | 81 | impl Eq for Metric {} 82 | 83 | impl PartialOrd for Metric { 84 | fn partial_cmp(&self, other: &Self) -> Option { 85 | Some(self.cmp(other)) 86 | } 87 | } 88 | 89 | impl PartialEq for Metric { 90 | fn eq(&self, other: &Self) -> bool { 91 | self.score() == other.score() 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | use super::Metric; 98 | 99 | #[test] 100 | fn eq() { 101 | let m1 = Metric::new(1, vec![1], 10000); 102 | let m2 = Metric::new(1, vec![1], 10000); 103 | 104 | assert_eq!(m1, m2); 105 | } 106 | 107 | #[test] 108 | fn compare() { 109 | let m1 = Metric::new(1, vec![1], 10000); 110 | let m2 = Metric::new(2, vec![2], 10000); 111 | let m3 = Metric::new(2, vec![3, 4], 10000); 112 | 113 | assert!(m1 < m2); 114 | assert!(m2 > m1); 115 | 116 | assert!(m1 < m3); 117 | assert!(m2 < m3); 118 | assert!(m3 > m2); 119 | } 120 | 121 | #[test] 122 | fn compare_bandwidth_limit() { 123 | let m1 = Metric::new(1, vec![1], 9000); 124 | let m2 = Metric::new(2, vec![2], 10000); 125 | let m3 = Metric::new(2, vec![3], 9000); 126 | let m4 = Metric::new(2, vec![3], 11000); 127 | 128 | assert!(m2 < m1); 129 | assert!(m1 > m2); 130 | assert!(m1 < m3); 131 | assert!(m3 > m1); 132 | 133 | assert!(m2 == m4); 134 | } 135 | 136 | #[test] 137 | fn add() { 138 | let m1 = Metric::new(1, vec![1, 2], 10000); 139 | let m2 = Metric::new(2, vec![3], 20000); 140 | assert_eq!(m1.add(&m2), Metric::new(3, vec![1, 2, 3], 10000)); 141 | } 142 | 143 | #[test] 144 | fn hops_has_affect_latancy() { 145 | let m1 = Metric::new(1, vec![1, 2], 10000); 146 | let m2 = Metric::new(2, vec![2], 10000); 147 | 148 | assert!(m1 > m2); 149 | assert!(m2 < m1); 150 | 151 | let m3 = Metric::new(10, vec![1, 2], 10000); 152 | let m4 = Metric::new(12, vec![1], 10000); 153 | 154 | assert!(m3 > m4); 155 | assert!(m4 < m3); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /packages/core/router/src/core/table/path.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_identity::{ConnId, NodeId}; 2 | use std::cmp::Ordering; 3 | 4 | use super::Metric; 5 | 6 | #[derive(Debug, Clone)] 7 | /// conn_id, next_node, last_node, metric 8 | pub struct Path(ConnId, NodeId, Metric); 9 | 10 | impl Path { 11 | pub fn new(over: ConnId, over_node: NodeId, metric: Metric) -> Self { 12 | Self(over, over_node, metric) 13 | } 14 | 15 | pub fn over_node(&self) -> NodeId { 16 | self.1 17 | } 18 | 19 | pub fn conn(&self) -> ConnId { 20 | self.0 21 | } 22 | 23 | pub fn metric(&self) -> &Metric { 24 | &self.2 25 | } 26 | 27 | pub fn update_metric(&mut self, metric: Metric) { 28 | self.2 = metric; 29 | } 30 | } 31 | 32 | impl PartialOrd for Path { 33 | fn partial_cmp(&self, other: &Self) -> Option { 34 | Some(self.cmp(other)) 35 | } 36 | } 37 | 38 | impl PartialEq for Path { 39 | fn eq(&self, other: &Self) -> bool { 40 | self.2.eq(&other.2) 41 | } 42 | } 43 | 44 | impl Eq for Path {} 45 | 46 | impl Ord for Path { 47 | fn cmp(&self, other: &Self) -> Ordering { 48 | self.2.partial_cmp(&other.2).unwrap() 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use std::cmp::Ordering; 55 | 56 | use atm0s_sdn_identity::ConnId; 57 | 58 | use crate::core::Metric; 59 | 60 | use super::Path; 61 | 62 | #[test] 63 | fn test_compare_path() { 64 | let p1 = Path(ConnId::from_in(1, 1), 1, Metric::new(1, vec![], 10000)); 65 | let p2 = Path(ConnId::from_in(1, 2), 2, Metric::new(1, vec![], 10000)); 66 | 67 | assert_eq!(p1.cmp(&p2), Ordering::Equal); 68 | assert_eq!(p1.partial_cmp(&p2), Some(Ordering::Equal)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/core/router/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::bool_assert_comparison)] 2 | 3 | use atm0s_sdn_identity::{NodeId, NodeIdType}; 4 | pub mod core; 5 | pub mod shadow; 6 | 7 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 8 | pub enum ServiceBroadcastLevel { 9 | Global, 10 | Geo1, 11 | Geo2, 12 | Group, 13 | } 14 | 15 | impl ServiceBroadcastLevel { 16 | pub fn same_level(&self, node1: NodeId, node2: NodeId) -> bool { 17 | match self { 18 | ServiceBroadcastLevel::Global => true, 19 | ServiceBroadcastLevel::Geo1 => node1.geo1() == node2.geo1(), 20 | ServiceBroadcastLevel::Geo2 => node1.geo1() == node2.geo1() && node1.geo2() == node2.geo2(), 21 | ServiceBroadcastLevel::Group => node1.geo1() == node2.geo1() && node1.geo2() == node2.geo2() && node1.group() == node2.group(), 22 | } 23 | } 24 | } 25 | 26 | impl From for u8 { 27 | fn from(val: ServiceBroadcastLevel) -> Self { 28 | match val { 29 | ServiceBroadcastLevel::Global => 0, 30 | ServiceBroadcastLevel::Geo1 => 1, 31 | ServiceBroadcastLevel::Geo2 => 2, 32 | ServiceBroadcastLevel::Group => 3, 33 | } 34 | } 35 | } 36 | 37 | impl From for ServiceBroadcastLevel { 38 | fn from(val: u8) -> Self { 39 | match val { 40 | 0 => ServiceBroadcastLevel::Global, 41 | 1 => ServiceBroadcastLevel::Geo1, 42 | 2 => ServiceBroadcastLevel::Geo2, 43 | _ => ServiceBroadcastLevel::Group, 44 | } 45 | } 46 | } 47 | 48 | #[derive(Clone, Debug, Eq, PartialEq)] 49 | pub enum RouteRule { 50 | Direct, 51 | ToNode(NodeId), 52 | ToService(u8), 53 | /// First is service id, second is the level, and third is seq of message 54 | ToServices(u8, ServiceBroadcastLevel, u16), 55 | ToKey(NodeId), 56 | } 57 | 58 | /// Determine the destination of an action/message 59 | #[derive(Clone, Debug, Eq, PartialEq)] 60 | pub enum RouteAction { 61 | /// Reject the message 62 | Reject, 63 | /// Will be processed locally 64 | Local, 65 | /// Will be forward to the given connection 66 | Next(Remote), 67 | /// Will be forward to the given connection, first is local or not, next is the list of remote dests 68 | Broadcast(bool, Vec), 69 | } 70 | 71 | impl RouteAction { 72 | pub fn is_local(&self) -> bool { 73 | matches!(self, RouteAction::Local) 74 | } 75 | 76 | pub fn is_reject(&self) -> bool { 77 | matches!(self, RouteAction::Reject) 78 | } 79 | 80 | pub fn is_remote(&self) -> bool { 81 | matches!(self, RouteAction::Next(_)) 82 | } 83 | } 84 | 85 | pub trait RouterTable { 86 | /// Find the closest node for the given key 87 | fn closest_for(&self, key: NodeId) -> Option; 88 | /// Find the next node for the given destination node 89 | fn next(&self, dest: NodeId) -> Option; 90 | /// Determine the next action for the given destination node 91 | fn path_to_node(&self, dest: NodeId) -> RouteAction; 92 | /// Determine the next action for the given key 93 | fn path_to_key(&self, key: NodeId) -> RouteAction; 94 | /// Determine the next action for the given service 95 | fn path_to_service(&self, service_id: u8) -> RouteAction; 96 | /// Determine the next action if we need broadcast to all node running a service. 97 | /// If relay_from is set, it should not sending back for avoiding loop 98 | fn path_to_services(&self, service_id: u8, seq: u16, level: ServiceBroadcastLevel, source: Option, relay_from: Option) -> RouteAction; 99 | /// Determine next action for incoming messages 100 | /// given the route rule and service id 101 | fn derive_action(&self, route: &RouteRule, source: Option, relay_from: Option) -> RouteAction { 102 | match route { 103 | RouteRule::Direct => RouteAction::Local, 104 | RouteRule::ToNode(dest) => self.path_to_node(*dest), 105 | RouteRule::ToKey(key) => self.path_to_key(*key), 106 | RouteRule::ToService(service) => self.path_to_service(*service), 107 | RouteRule::ToServices(service, level, seq) => self.path_to_services(*service, *seq, *level, source, relay_from), 108 | } 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | use atm0s_sdn_identity::ConnId; 115 | type RouteAction = super::RouteAction; 116 | 117 | #[test] 118 | fn test_is_local() { 119 | let local = RouteAction::Local; 120 | let remote = RouteAction::Next(ConnId::from_in(1, 1)); 121 | let reject = RouteAction::Reject; 122 | 123 | assert!(local.is_local()); 124 | assert!(!remote.is_local()); 125 | assert!(!reject.is_local()); 126 | } 127 | 128 | #[test] 129 | fn test_is_reject() { 130 | let local = RouteAction::Local; 131 | let remote = RouteAction::Next(ConnId::from_in(1, 1)); 132 | let reject = RouteAction::Reject; 133 | 134 | assert!(!local.is_reject()); 135 | assert!(!remote.is_reject()); 136 | assert!(reject.is_reject()); 137 | } 138 | 139 | #[test] 140 | fn test_is_remote() { 141 | let local = RouteAction::Local; 142 | let remote = RouteAction::Next(ConnId::from_in(1, 1)); 143 | let reject = RouteAction::Reject; 144 | 145 | assert!(!local.is_remote()); 146 | assert!(remote.is_remote()); 147 | assert!(!reject.is_remote()); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /packages/core/router/src/shadow/service.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt::Debug, hash::Hash}; 2 | 3 | use atm0s_sdn_identity::NodeId; 4 | 5 | use crate::ServiceBroadcastLevel; 6 | 7 | #[derive(Debug, PartialEq, Eq)] 8 | pub struct ServiceConn { 9 | pub(crate) conn: Conn, 10 | pub(crate) remote: Remote, 11 | pub(crate) next: NodeId, 12 | pub(crate) dest: NodeId, 13 | pub(crate) score: u32, 14 | } 15 | 16 | impl Ord for ServiceConn { 17 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 18 | self.score.cmp(&other.score) 19 | } 20 | } 21 | 22 | impl PartialOrd for ServiceConn { 23 | fn partial_cmp(&self, other: &Self) -> Option { 24 | Some(self.score.cmp(&other.score)) 25 | } 26 | } 27 | 28 | pub struct Service { 29 | dests: Vec>, 30 | } 31 | 32 | impl Service { 33 | pub fn new() -> Self { 34 | Self { dests: Vec::new() } 35 | } 36 | 37 | /// Add a new destination to the service, if Remote already exists, it will be replaced 38 | pub fn set_conn(&mut self, conn: Conn, remote: Remote, next: NodeId, dest: NodeId, score: u32) { 39 | let index = self.dests.iter().position(|x| x.conn == conn); 40 | if let Some(index) = index { 41 | self.dests[index] = ServiceConn { conn, remote, next, dest, score }; 42 | } else { 43 | self.dests.push(ServiceConn { conn, remote, next, dest, score }); 44 | } 45 | self.dests.sort(); 46 | } 47 | 48 | /// Remove a destination from the service 49 | pub fn del_conn(&mut self, conn: Conn) { 50 | self.dests.retain(|x| x.conn != conn); 51 | } 52 | 53 | pub fn best_conn(&self) -> Option { 54 | self.dests.first().map(|x| x.remote) 55 | } 56 | 57 | /// Get all unique destinations 58 | /// If relay_from is Some, it will not return the relay_from node connection 59 | pub fn broadcast_dests(&self, node_id: NodeId, level: ServiceBroadcastLevel, relay_from: Option) -> Option> { 60 | if self.dests.is_empty() { 61 | return None; 62 | } 63 | let mut remotes = vec![]; 64 | let mut dests = HashMap::new(); 65 | for dest in &self.dests { 66 | if dests.contains_key(&dest.dest) || !level.same_level(node_id, dest.dest) { 67 | continue; 68 | } 69 | if let Some(relay_from) = &relay_from { 70 | if dest.next == *relay_from { 71 | continue; 72 | } 73 | } 74 | dests.insert(dest.dest, ()); 75 | remotes.push(dest.remote); 76 | } 77 | Some(remotes) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/core/router/src/shadow/table.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_identity::{NodeId, NodeIdType}; 2 | 3 | #[derive(Debug)] 4 | pub struct ShadowTable { 5 | layer: u8, 6 | dests: [Option; 256], 7 | } 8 | 9 | impl ShadowTable { 10 | pub fn new(layer: u8) -> Self { 11 | Self { layer, dests: [None; 256] } 12 | } 13 | 14 | pub fn set(&mut self, index: u8, remote: Remote) { 15 | self.dests[index as usize] = Some(remote); 16 | } 17 | 18 | pub fn del(&mut self, index: u8) { 19 | self.dests[index as usize] = None; 20 | } 21 | 22 | pub fn next(&self, dest: NodeId) -> Option { 23 | let index = dest.layer(self.layer); 24 | self.dests[index as usize] 25 | } 26 | 27 | /// Find the closest remote for the given key 28 | /// Returns the remote, the layer and the distance 29 | pub fn closest_for(&self, key_index: u8) -> Option<(Remote, u8, u8)> { 30 | let mut closest_distance: Option<(Remote, u8, u8)> = None; 31 | for i in 0..=255 { 32 | if let Some(remote) = self.dests[i as usize] { 33 | let distance = i ^ key_index; 34 | if closest_distance.is_none() || distance < closest_distance.expect("").2 { 35 | closest_distance = Some((remote, i, distance)); 36 | } 37 | } 38 | } 39 | closest_distance 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/utils/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.2.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-utils-v0.2.0...atm0s-sdn-utils-v0.2.1) - 2024-07-22 10 | 11 | ### Other 12 | - clippy fixes ([#167](https://github.com/8xFF/atm0s-sdn/pull/167)) 13 | 14 | ## [0.1.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-utils-v0.1.0...atm0s-sdn-utils-v0.1.1) - 2023-12-11 15 | 16 | ### Added 17 | - manual-discovery with node tags ([#84](https://github.com/8xFF/atm0s-sdn/pull/84)) 18 | -------------------------------------------------------------------------------- /packages/core/utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atm0s-sdn-utils" 3 | version = "0.2.1" 4 | edition = "2021" 5 | description = "A collection of utilities for atm0s-sdn" 6 | license = "MIT" 7 | 8 | [features] 9 | auto-clear = [] 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | log = { workspace = true } 14 | serde = { workspace = true } 15 | -------------------------------------------------------------------------------- /packages/core/utils/src/error_handle.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | pub trait ErrorUtils { 4 | fn print_error(&self, msg: &str); 5 | } 6 | 7 | impl ErrorUtils for Result 8 | where 9 | E: Debug, 10 | { 11 | fn print_error(&self, msg: &str) { 12 | match self { 13 | Ok(_) => {} 14 | Err(e) => { 15 | log::error!("Error: {} {:?}", msg, e); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/utils/src/hash.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::hash_map::DefaultHasher, hash::Hasher}; 2 | 3 | // generate hash of string, it need to consistent with all nodes type, OS or device 4 | pub fn hash_str(s: &str) -> u64 { 5 | let mut hasher = DefaultHasher::new(); 6 | hasher.write(s.as_bytes()); 7 | hasher.finish() 8 | } 9 | 10 | #[cfg(test)] 11 | mod test { 12 | use super::*; 13 | 14 | #[test] 15 | fn test_hash_str() { 16 | assert_eq!(hash_str("hello"), 16350172494705860510); 17 | assert_eq!(hash_str("world"), 17970961829702799988); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/utils/src/init_array.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! init_array ( 3 | ($ty:ty, $len:expr, $val:expr) => ( 4 | { 5 | let mut array: [$ty; $len] = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; 6 | for i in array.iter_mut() { 7 | unsafe { ::std::ptr::write(i, $val); } 8 | } 9 | array 10 | } 11 | ) 12 | ); 13 | 14 | pub use init_array; 15 | -------------------------------------------------------------------------------- /packages/core/utils/src/init_vec.rs: -------------------------------------------------------------------------------- 1 | pub fn init_vec(size: usize, builder: fn() -> T) -> Vec { 2 | let mut vec = vec![]; 3 | for _ in 0..size { 4 | vec.push(builder()); 5 | } 6 | vec 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::bool_assert_comparison)] 2 | 3 | pub mod error_handle; 4 | pub mod hash; 5 | pub mod init_array; 6 | pub mod init_vec; 7 | pub mod option_handle; 8 | pub mod types; 9 | -------------------------------------------------------------------------------- /packages/core/utils/src/option_handle.rs: -------------------------------------------------------------------------------- 1 | pub trait OptionUtils { 2 | fn print_none(&self, msg: &str); 3 | } 4 | 5 | impl OptionUtils for Option { 6 | fn print_none(&self, msg: &str) { 7 | match self { 8 | Some(_) => {} 9 | None => { 10 | log::error!("None: {}", msg); 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/utils/src/types.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! simple_pub_type { 3 | ($name:ident, $inner:ty) => { 4 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] 5 | pub struct $name(pub $inner); 6 | 7 | impl std::ops::Deref for $name { 8 | type Target = $inner; 9 | 10 | fn deref(&self) -> &Self::Target { 11 | &self.0 12 | } 13 | } 14 | 15 | impl From<$inner> for $name { 16 | fn from(value: $inner) -> Self { 17 | $name(value) 18 | } 19 | } 20 | 21 | impl Into<$inner> for $name { 22 | fn into(self) -> $inner { 23 | self.0 24 | } 25 | } 26 | 27 | impl std::fmt::Display for $name { 28 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 29 | write!(f, "{}({})", stringify!($name), self.0) 30 | } 31 | } 32 | }; 33 | } 34 | 35 | #[macro_export] 36 | macro_rules! simple_type { 37 | ($name:ident, $inner:ty) => { 38 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] 39 | struct $name(pub $inner); 40 | 41 | impl std::ops::Deref for $name { 42 | type Target = $inner; 43 | 44 | fn deref(&self) -> &Self::Target { 45 | &self.0 46 | } 47 | } 48 | 49 | impl From<$inner> for $name { 50 | fn from(value: $inner) -> Self { 51 | $name(value) 52 | } 53 | } 54 | 55 | impl Into<$inner> for $name { 56 | fn into(self) -> $inner { 57 | self.0 58 | } 59 | } 60 | 61 | impl std::fmt::Display for $name { 62 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 63 | write!(f, "$name({})", self.0) 64 | } 65 | } 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /packages/network/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8xFF/atm0s-sdn/63ae52d3e14e1963c310c7872ca6e6cdd6013a5f/packages/network/.DS_Store -------------------------------------------------------------------------------- /packages/network/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.7.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.7.0...atm0s-sdn-network-v0.7.1) - 2025-03-02 10 | 11 | ### Fixed 12 | 13 | - dht-kv missed clear remote slots on unsub (#196) 14 | 15 | ## [0.7.0](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.6.3...atm0s-sdn-network-v0.7.0) - 2025-03-01 16 | 17 | ### Fixed 18 | 19 | - wrong worker event route with specific worker_id type (#194) 20 | 21 | ## [0.6.3](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.6.2...atm0s-sdn-network-v0.6.3) - 2025-02-27 22 | 23 | ### Fixed 24 | 25 | - router don't clear service router info after node disconnect (#192) 26 | 27 | ## [0.6.2](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.6.1...atm0s-sdn-network-v0.6.2) - 2025-02-08 28 | 29 | ### Added 30 | 31 | - discovery node by service broadcast (#188) 32 | 33 | ### Fixed 34 | 35 | - router with duplicate entry in hops (#190) 36 | 37 | ## [0.6.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.6.0...atm0s-sdn-network-v0.6.1) - 2024-11-26 38 | 39 | ### Added 40 | 41 | - add router dump ([#182](https://github.com/8xFF/atm0s-sdn/pull/182)) 42 | 43 | ## [0.6.0](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.5.0...atm0s-sdn-network-v0.6.0) - 2024-11-08 44 | 45 | ### Added 46 | 47 | - task clean-up with new sans-io-runtime ([#179](https://github.com/8xFF/atm0s-sdn/pull/179)) 48 | 49 | ## [0.5.0](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.4.1...atm0s-sdn-network-v0.5.0) - 2024-07-22 50 | 51 | ### Added 52 | - listen multiple UDP addresses ([#175](https://github.com/8xFF/atm0s-sdn/pull/175)) 53 | 54 | ## [0.4.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.4.0...atm0s-sdn-network-v0.4.1) - 2024-07-22 55 | 56 | ### Fixed 57 | - switch to single bind for resolve connect error with multi-addresses machine ([#172](https://github.com/8xFF/atm0s-sdn/pull/172)) 58 | 59 | ### Other 60 | - clippy fixes ([#167](https://github.com/8xFF/atm0s-sdn/pull/167)) 61 | 62 | ## [0.3.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.3.0...atm0s-sdn-network-v0.3.1) - 2024-01-24 63 | 64 | ### Other 65 | - update Cargo.toml dependencies 66 | 67 | ## [0.3.0](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.2.2...atm0s-sdn-network-v0.3.0) - 2023-12-27 68 | 69 | ### Added 70 | - secure with static key and noise protocol ([#101](https://github.com/8xFF/atm0s-sdn/pull/101)) 71 | - node multi addrs ([#98](https://github.com/8xFF/atm0s-sdn/pull/98)) 72 | 73 | ## [0.2.2](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.2.1...atm0s-sdn-network-v0.2.2) - 2023-12-12 74 | 75 | ### Other 76 | - update dependencies 77 | 78 | ## [0.2.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.2.0...atm0s-sdn-network-v0.2.1) - 2023-12-11 79 | 80 | ### Other 81 | - move local deps out of workspace Cargo.toml ([#92](https://github.com/8xFF/atm0s-sdn/pull/92)) 82 | 83 | ## [0.2.0](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.1.1...atm0s-sdn-network-v0.2.0) - 2023-12-11 84 | 85 | ### Added 86 | - rpc service and fi ([#87](https://github.com/8xFF/atm0s-sdn/pull/87)) 87 | - manual-discovery with node tags ([#84](https://github.com/8xFF/atm0s-sdn/pull/84)) 88 | 89 | ### Fixed 90 | - missing register service ([#85](https://github.com/8xFF/atm0s-sdn/pull/85)) 91 | 92 | ## [0.1.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-network-v0.1.0...atm0s-sdn-network-v0.1.1) - 2023-11-17 93 | 94 | ### Other 95 | - release ([#63](https://github.com/8xFF/atm0s-sdn/pull/63)) 96 | -------------------------------------------------------------------------------- /packages/network/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atm0s-sdn-network" 3 | version = "0.7.1" 4 | edition = "2021" 5 | description = "Main network-plane of atm0s-sdn" 6 | license = "MIT" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | atm0s-sdn-utils = { path = "../core/utils", version = "0.2.1" } 12 | atm0s-sdn-identity = { path = "../core/identity", version = "0.3.2" } 13 | atm0s-sdn-router = { path = "../core/router", version = "0.3.0" } 14 | sans-io-runtime = { workspace = true, default-features = false } 15 | rand = { workspace = true } 16 | mockall = { workspace = true } 17 | convert-enum = { workspace = true } 18 | num_enum = { workspace = true } 19 | parking_lot = { workspace = true } 20 | log = { workspace = true } 21 | thiserror = { workspace = true } 22 | serde = { workspace = true } 23 | bytes = "1.5" 24 | bincode = "1.3" 25 | sha1 = "0.10" 26 | num = "0.4" 27 | sha2 = "0.10" 28 | x25519-dalek = { version = "2.0", features = ["getrandom"] } 29 | aes-gcm = "0.10" 30 | derivative = "2.2" 31 | 32 | [dev-dependencies] 33 | env_logger = { workspace = true } 34 | 35 | [features] 36 | default = ["fuzz"] 37 | vpn = [] 38 | fuzz = [] 39 | -------------------------------------------------------------------------------- /packages/network/README.md: -------------------------------------------------------------------------------- 1 | Network is split into 2 parts 2 | 3 | - Network and routing tables 4 | - Custom behaviours 5 | 6 | ## Network and routing tables 7 | 8 | This part handles the network and routing tables. 9 | The logic of routing table is not fixed, instead it is inject into with trait RoutingTable which provide bellow function 10 | 11 | ```rust 12 | pub trait RouterTable: Send + Sync { 13 | fn path_to_node(&self, dest: NodeId) -> RouteAction; 14 | fn path_to_key(&self, key: NodeId) -> RouteAction; 15 | fn path_to_service(&self, service_id: u8) -> RouteAction; 16 | fn path_to(&self, route: &RouteRule, service_id: u8) -> RouteAction { 17 | match route { 18 | RouteRule::Node(dest) => self.path_to_node(*dest), 19 | RouteRule::Closest(key) => self.path_to_key(*key), 20 | RouteRule::Service => self.path_to_service(service_id), 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | By the way, network providing some based function to sending and handler message: 27 | 28 | - Send to other Node by NodeId 29 | - Send to other Node, which served by a service which is identified by service_id 30 | - Send to local Node 31 | 32 | Message is sending to other node by routing table and transport 33 | 34 | ## Custom behaviours 35 | 36 | By provide a custom behaviour, we can add logic to application with modules style, each that will need to defined some parts: 37 | 38 | - Behaviour: defined main logic (this is heart of behaviour), which handle some main event like: new incoming-outgoing connection, connection disconnected, 39 | - Connection Handler: defined logic which handle separate with each connection like: connection event, transport message ... 40 | 41 | Each part also can exchanged data with other part by using Agent, which is provide in each function handler 42 | 43 | - From behavior to each connections 44 | - From each connection back to behavior 45 | - From each connection to each other connection 46 | 47 | For reference bellow is trait of behaviour 48 | 49 | ```rust 50 | pub trait NetworkBehavior 51 | where 52 | MSG: Send + Sync, 53 | { 54 | fn service_id(&self) -> u8; 55 | fn on_tick(&mut self, agent: &BehaviorAgent, ts_ms: u64, interval_ms: u64); 56 | fn check_incoming_connection( 57 | &mut self, 58 | node: NodeId, 59 | conn_id: ConnId, 60 | ) -> Result<(), ConnectionRejectReason>; 61 | fn check_outgoing_connection( 62 | &mut self, 63 | node: NodeId, 64 | conn_id: ConnId, 65 | ) -> Result<(), ConnectionRejectReason>; 66 | fn on_incoming_connection_connected( 67 | &mut self, 68 | agent: &BehaviorAgent, 69 | conn: Arc>, 70 | ) -> Option>>; 71 | fn on_outgoing_connection_connected( 72 | &mut self, 73 | agent: &BehaviorAgent, 74 | conn: Arc>, 75 | ) -> Option>>; 76 | fn on_incoming_connection_disconnected( 77 | &mut self, 78 | agent: &BehaviorAgent, 79 | conn: Arc>, 80 | ); 81 | fn on_outgoing_connection_disconnected( 82 | &mut self, 83 | agent: &BehaviorAgent, 84 | conn: Arc>, 85 | ); 86 | fn on_outgoing_connection_error( 87 | &mut self, 88 | agent: &BehaviorAgent, 89 | node_id: NodeId, 90 | conn_id: ConnId, 91 | err: &OutgoingConnectionError, 92 | ); 93 | fn on_handler_event( 94 | &mut self, 95 | agent: &BehaviorAgent, 96 | node_id: NodeId, 97 | conn_id: ConnId, 98 | event: BE, 99 | ); 100 | fn on_rpc( 101 | &mut self, 102 | agent: &BehaviorAgent, 103 | req: Req, 104 | res: Box>, 105 | ) -> bool; 106 | } 107 | ``` 108 | 109 | Bellow is trait of ConnectionHandler 110 | 111 | ```rust 112 | pub trait ConnectionHandler: Send + Sync { 113 | fn on_opened(&mut self, agent: &ConnectionAgent); 114 | fn on_tick(&mut self, agent: &ConnectionAgent, ts_ms: u64, interval_ms: u64); 115 | fn on_event(&mut self, agent: &ConnectionAgent, event: ConnectionEvent); 116 | fn on_other_handler_event( 117 | &mut self, 118 | agent: &ConnectionAgent, 119 | from_node: NodeId, 120 | from_conn: ConnId, 121 | event: HE, 122 | ); 123 | fn on_behavior_event(&mut self, agent: &ConnectionAgent, event: HE); 124 | fn on_closed(&mut self, agent: &ConnectionAgent); 125 | } 126 | ``` -------------------------------------------------------------------------------- /packages/network/src/_fuzz_export.rs: -------------------------------------------------------------------------------- 1 | pub use crate::base::NeighboursControl; 2 | pub use crate::base::TransportMsg; 3 | -------------------------------------------------------------------------------- /packages/network/src/base/control.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_identity::NodeId; 2 | use bincode::Options; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use super::Authorization; 6 | 7 | const MSG_TIMEOUT_MS: u64 = 10000; 8 | 9 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 10 | pub enum NeighboursConnectError { 11 | AlreadyConnected, 12 | InvalidSignature, 13 | InvalidData, 14 | InvalidState, 15 | Timeout, 16 | } 17 | 18 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 19 | pub enum NeighboursDisconnectReason { 20 | Shutdown, 21 | Other, 22 | } 23 | 24 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 25 | pub enum NeighboursControlCmds { 26 | ConnectRequest { to: NodeId, session: u64, handshake: Vec }, 27 | ConnectResponse { session: u64, result: Result, NeighboursConnectError> }, 28 | Ping { session: u64, seq: u64, sent_ms: u64 }, 29 | Pong { session: u64, seq: u64, sent_ms: u64 }, 30 | DisconnectRequest { session: u64, reason: NeighboursDisconnectReason }, 31 | DisconnectResponse { session: u64 }, 32 | } 33 | 34 | #[derive(Debug, Clone, Serialize, Deserialize)] 35 | pub struct NeighboursControl { 36 | pub from: NodeId, 37 | pub cmd: Vec, 38 | pub signature: Vec, 39 | } 40 | 41 | impl NeighboursControl { 42 | #[allow(clippy::result_unit_err)] 43 | pub fn validate(&self, now: u64, auth: &dyn Authorization) -> Result { 44 | auth.validate(self.from, &self.cmd, &self.signature).ok_or(())?; 45 | let (ts, cmd) = bincode::DefaultOptions::new().with_limit(1499).deserialize::<(u64, NeighboursControlCmds)>(&self.cmd).map_err(|_| ())?; 46 | if ts + MSG_TIMEOUT_MS < now { 47 | return Err(()); 48 | } 49 | Ok(cmd) 50 | } 51 | 52 | pub fn build(now: u64, from: NodeId, cmd: NeighboursControlCmds, auth: &dyn Authorization) -> Self { 53 | let cmd = bincode::DefaultOptions::new().with_limit(1499).serialize(&(now, cmd)).unwrap(); 54 | let signature = auth.sign(&cmd); 55 | Self { from, cmd, signature } 56 | } 57 | } 58 | 59 | impl TryFrom<&[u8]> for NeighboursControl { 60 | type Error = (); 61 | 62 | fn try_from(value: &[u8]) -> Result { 63 | if value.first() == Some(&255) { 64 | bincode::DefaultOptions::new().with_limit(1499).deserialize(&value[1..]).map_err(|_| ()) 65 | } else { 66 | Err(()) 67 | } 68 | } 69 | } 70 | 71 | impl TryInto> for &NeighboursControl { 72 | type Error = (); 73 | 74 | fn try_into(self) -> Result, Self::Error> { 75 | let mut buf = Vec::with_capacity(1500); 76 | buf.push(255); 77 | bincode::DefaultOptions::new().with_limit(1499).serialize_into(&mut buf, &self).map_err(|_| ())?; 78 | Ok(buf) 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use crate::secure::StaticKeyAuthorization; 85 | 86 | use super::*; 87 | 88 | #[test] 89 | fn test_neighbours_control() { 90 | let auth = StaticKeyAuthorization::new("demo_key"); 91 | let cmd = NeighboursControlCmds::Ping { 92 | session: 1000, 93 | seq: 100, 94 | sent_ms: 100, 95 | }; 96 | let control = NeighboursControl::build(0, 1, cmd.clone(), &auth); 97 | assert_eq!(control.validate(0, &auth), Ok(cmd)); 98 | assert_eq!(control.validate(MSG_TIMEOUT_MS + 1, &auth), Err(())); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /packages/network/src/base/mod.rs: -------------------------------------------------------------------------------- 1 | mod control; 2 | mod feature; 3 | mod msg; 4 | mod secure; 5 | mod service; 6 | 7 | use atm0s_sdn_identity::{ConnId, NodeId}; 8 | pub use control::*; 9 | pub use feature::*; 10 | pub use msg::*; 11 | pub use sans_io_runtime::Buffer; 12 | pub use secure::*; 13 | pub use service::*; 14 | 15 | use crate::data_plane::NetPair; 16 | 17 | #[derive(Debug, Clone)] 18 | pub struct ConnectionCtx { 19 | pub conn: ConnId, 20 | pub node: NodeId, 21 | pub pair: NetPair, 22 | } 23 | 24 | #[derive(Debug, Clone, PartialEq, Eq)] 25 | pub struct ConnectionStats { 26 | pub rtt_ms: u32, 27 | } 28 | 29 | #[derive(Debug, Clone)] 30 | pub enum ConnectionEvent { 31 | Connecting(ConnectionCtx), 32 | ConnectError(ConnectionCtx, NeighboursConnectError), 33 | Connected(ConnectionCtx, SecureContext), 34 | Stats(ConnectionCtx, ConnectionStats), 35 | Disconnected(ConnectionCtx), 36 | } 37 | -------------------------------------------------------------------------------- /packages/network/src/base/secure.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use atm0s_sdn_identity::NodeId; 4 | 5 | use super::Buffer; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct SecureContext { 9 | pub(crate) encryptor: Box, 10 | pub(crate) decryptor: Box, 11 | } 12 | 13 | #[mockall::automock] 14 | pub trait Authorization: Send + Sync { 15 | fn sign(&self, msg: &[u8]) -> Vec; 16 | fn validate(&self, node_id: NodeId, msg: &[u8], sign: &[u8]) -> Option<()>; 17 | } 18 | 19 | #[derive(Debug, PartialEq, Eq)] 20 | pub enum HandshakeError { 21 | InvalidState, 22 | InvalidPublicKey, 23 | } 24 | 25 | #[mockall::automock] 26 | pub trait HandshakeBuilder: Send + Sync { 27 | fn requester(&self) -> Box; 28 | fn responder(&self) -> Box; 29 | } 30 | 31 | #[mockall::automock] 32 | pub trait HandshakeRequester { 33 | fn create_public_request(&self) -> Result, HandshakeError>; 34 | #[allow(clippy::type_complexity)] 35 | fn process_public_response(&mut self, response: &[u8]) -> Result<(Box, Box), HandshakeError>; 36 | } 37 | 38 | #[mockall::automock] 39 | pub trait HandshakeResponder { 40 | #[allow(clippy::type_complexity)] 41 | fn process_public_request(&mut self, request: &[u8]) -> Result<(Box, Box, Vec), HandshakeError>; 42 | } 43 | 44 | #[derive(Debug)] 45 | pub enum EncryptionError { 46 | EncryptFailed, 47 | } 48 | 49 | #[mockall::automock] 50 | pub trait Encryptor: Debug + Send + Sync { 51 | fn encrypt(&mut self, now_ms: u64, data: &mut Buffer) -> Result<(), EncryptionError>; 52 | fn clone_box(&self) -> Box; 53 | } 54 | 55 | impl Clone for Box { 56 | fn clone(&self) -> Self { 57 | self.clone_box() 58 | } 59 | } 60 | 61 | #[derive(Debug)] 62 | pub enum DecryptionError { 63 | TooSmall, 64 | TooOld, 65 | DecryptError, 66 | } 67 | 68 | #[mockall::automock] 69 | pub trait Decryptor: Debug + Send + Sync { 70 | fn decrypt(&mut self, now_ms: u64, data: &mut Buffer) -> Result<(), DecryptionError>; 71 | fn clone_box(&self) -> Box; 72 | } 73 | 74 | impl Clone for Box { 75 | fn clone(&self) -> Self { 76 | self.clone_box() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/network/src/base/service.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_identity::NodeId; 2 | use atm0s_sdn_utils::simple_pub_type; 3 | use sans_io_runtime::TaskSwitcherChild; 4 | 5 | use super::ConnectionEvent; 6 | 7 | simple_pub_type!(ServiceId, u8); 8 | 9 | /// First part is Service, which is running inside the controller. 10 | #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] 11 | pub enum ServiceControlActor { 12 | Controller(UserData), 13 | Worker(u16, UserData), 14 | } 15 | 16 | #[derive(Debug, Clone)] 17 | pub enum ServiceSharedInput { 18 | Tick(u64), 19 | Connection(ConnectionEvent), 20 | } 21 | 22 | #[derive(Debug)] 23 | pub enum ServiceInput { 24 | Control(ServiceControlActor, ServiceControl), 25 | FromWorker(ToController), 26 | FeatureEvent(FeaturesEvent), 27 | } 28 | 29 | #[derive(Debug, PartialEq, Eq)] 30 | pub enum ServiceOutput { 31 | Event(ServiceControlActor, ServiceEvent), 32 | FeatureControl(FeaturesControl), 33 | BroadcastWorkers(ToWorker), 34 | OnResourceEmpty, 35 | } 36 | 37 | pub struct ServiceCtx { 38 | pub node_id: NodeId, 39 | pub session: u64, 40 | } 41 | 42 | pub trait Service { 43 | fn is_service_empty(&self) -> bool; 44 | fn service_id(&self) -> u8; 45 | fn service_name(&self) -> &str; 46 | fn on_shared_input(&mut self, _ctx: &ServiceCtx, _now: u64, _input: ServiceSharedInput); 47 | fn on_input(&mut self, _ctx: &ServiceCtx, _now: u64, input: ServiceInput); 48 | fn on_shutdown(&mut self, _ctx: &ServiceCtx, _now: u64); 49 | fn pop_output2(&mut self, _now: u64) -> Option>; 50 | } 51 | 52 | impl TaskSwitcherChild> 53 | for Box> 54 | { 55 | type Time = u64; 56 | 57 | fn empty_event(&self) -> ServiceOutput { 58 | ServiceOutput::OnResourceEmpty 59 | } 60 | 61 | fn is_empty(&self) -> bool { 62 | self.is_service_empty() 63 | } 64 | 65 | fn pop_output(&mut self, now: u64) -> Option> { 66 | self.pop_output2(now) 67 | } 68 | } 69 | 70 | /// Second part is Worker, which is running inside each data plane workers. 71 | pub enum ServiceWorkerInput { 72 | Control(ServiceControlActor, ServiceControl), 73 | FromController(ToWorker), 74 | FeatureEvent(FeaturesEvent), 75 | } 76 | 77 | pub enum ServiceWorkerOutput { 78 | ForwardControlToController(ServiceControlActor, ServiceControl), 79 | ForwardFeatureEventToController(FeaturesEvent), 80 | ToController(ToController), 81 | FeatureControl(FeaturesControl), 82 | Event(ServiceControlActor, ServiceEvent), 83 | OnResourceEmpty, 84 | } 85 | 86 | pub struct ServiceWorkerCtx { 87 | pub node_id: NodeId, 88 | } 89 | 90 | pub trait ServiceWorker { 91 | fn is_service_empty(&self) -> bool; 92 | fn service_id(&self) -> u8; 93 | fn service_name(&self) -> &str; 94 | fn on_tick(&mut self, _ctx: &ServiceWorkerCtx, _now: u64, _tick_count: u64); 95 | fn on_input(&mut self, _ctx: &ServiceWorkerCtx, _now: u64, input: ServiceWorkerInput); 96 | fn on_shutdown(&mut self, _ctx: &ServiceWorkerCtx, _now: u64); 97 | fn pop_output2(&mut self, _now: u64) -> Option>; 98 | } 99 | 100 | impl 101 | TaskSwitcherChild> 102 | for Box> 103 | { 104 | type Time = u64; 105 | 106 | fn empty_event(&self) -> ServiceWorkerOutput { 107 | ServiceWorkerOutput::OnResourceEmpty 108 | } 109 | 110 | fn is_empty(&self) -> bool { 111 | self.is_service_empty() 112 | } 113 | 114 | fn pop_output(&mut self, now: u64) -> Option> { 115 | self.pop_output2(now) 116 | } 117 | } 118 | 119 | pub trait ServiceBuilder: Send + Sync { 120 | fn service_id(&self) -> u8; 121 | fn service_name(&self) -> &str; 122 | fn discoverable(&self) -> bool { 123 | true 124 | } 125 | fn create(&self) -> Box>; 126 | fn create_worker(&self) -> Box>; 127 | } 128 | -------------------------------------------------------------------------------- /packages/network/src/controller_plane/services.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::sync::Arc; 3 | 4 | use sans_io_runtime::{TaskSwitcher, TaskSwitcherBranch, TaskSwitcherChild}; 5 | 6 | use crate::base::Service; 7 | use crate::base::{ServiceBuilder, ServiceCtx, ServiceId, ServiceInput, ServiceOutput, ServiceSharedInput}; 8 | use crate::features::{FeaturesControl, FeaturesEvent}; 9 | 10 | pub enum Output { 11 | Output(ServiceId, ServiceOutput), 12 | OnResourceEmpty, 13 | } 14 | 15 | type ServiceBox = Box>; 16 | type ServiceBoxOutput = ServiceOutput; 17 | type ServiceSwitcher = 18 | TaskSwitcherBranch, ServiceBoxOutput>; 19 | 20 | struct ServiceSlot { 21 | service: ServiceSwitcher, 22 | is_empty: bool, 23 | } 24 | 25 | /// To manage the services we need to create an object that will hold the services 26 | pub struct ServiceManager { 27 | #[allow(clippy::type_complexity)] 28 | services: [Option>; 256], 29 | services_count: usize, 30 | empty_services: HashSet, 31 | switcher: TaskSwitcher, 32 | shutdown: bool, 33 | } 34 | 35 | #[allow(clippy::type_complexity)] 36 | impl ServiceManager { 37 | pub fn new(services: Vec>>) -> Self { 38 | let max_service_id = services.iter().map(|s| s.service_id()).max().unwrap_or(0); 39 | Self { 40 | services_count: services.len(), 41 | services: std::array::from_fn(|index| { 42 | services.iter().find(|s| s.service_id() == index as u8).map(|s| ServiceSlot { 43 | service: TaskSwitcherBranch::new(s.create(), index), 44 | is_empty: false, 45 | }) 46 | }), 47 | empty_services: HashSet::default(), 48 | switcher: TaskSwitcher::new(max_service_id as usize + 1), 49 | shutdown: false, 50 | } 51 | } 52 | 53 | pub fn on_shared_input(&mut self, ctx: &ServiceCtx, now: u64, input: ServiceSharedInput) { 54 | for service in self.services.iter_mut().flatten() { 55 | service.service.input(&mut self.switcher).on_shared_input(ctx, now, input.clone()); 56 | } 57 | } 58 | 59 | pub fn on_input(&mut self, ctx: &ServiceCtx, now: u64, id: ServiceId, input: ServiceInput) { 60 | if let Some(Some(service)) = self.services.get_mut(*id as usize) { 61 | self.switcher.flag_task(*id as usize); 62 | service.service.input(&mut self.switcher).on_input(ctx, now, input); 63 | } 64 | } 65 | 66 | pub fn on_shutdown(&mut self, ctx: &ServiceCtx, now: u64) { 67 | if self.shutdown { 68 | return; 69 | } 70 | log::info!("[ControllerPlane] Services Shutdown"); 71 | for service in self.services.iter_mut().flatten() { 72 | service.service.input(&mut self.switcher).on_shutdown(ctx, now); 73 | } 74 | self.shutdown = true; 75 | } 76 | } 77 | 78 | impl TaskSwitcherChild> 79 | for ServiceManager 80 | { 81 | type Time = u64; 82 | 83 | fn empty_event(&self) -> Output { 84 | Output::OnResourceEmpty 85 | } 86 | 87 | fn is_empty(&self) -> bool { 88 | self.shutdown && self.empty_services.len() == self.services_count 89 | } 90 | 91 | fn pop_output(&mut self, now: u64) -> Option> { 92 | loop { 93 | let index = self.switcher.current()?; 94 | if let Some(Some(slot)) = self.services.get_mut(index) { 95 | if let Some(output) = slot.service.pop_output(now, &mut self.switcher) { 96 | return Some(Output::Output((index as u8).into(), output)); 97 | } else { 98 | if !slot.is_empty { 99 | if slot.service.is_empty() { 100 | slot.is_empty = true; 101 | self.empty_services.insert((index as u8).into()); 102 | return Some(Output::Output((index as u8).into(), slot.service.empty_event())); 103 | } 104 | } else { 105 | #[allow(clippy::collapsible_else_if)] 106 | if !slot.service.is_empty() { 107 | slot.is_empty = false; 108 | self.empty_services.remove(&(index as u8).into()); 109 | } 110 | } 111 | self.switcher.finished(index); 112 | } 113 | } else { 114 | self.switcher.finished(index); 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /packages/network/src/data_plane/connection.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_identity::{ConnId, NodeId}; 2 | 3 | use crate::base::{Buffer, SecureContext, TransportMsgHeader}; 4 | 5 | use super::NetPair; 6 | 7 | pub struct DataPlaneConnection { 8 | node: NodeId, 9 | conn: ConnId, 10 | #[allow(unused)] 11 | pair: NetPair, 12 | secure: SecureContext, 13 | } 14 | 15 | impl DataPlaneConnection { 16 | pub fn new(node: NodeId, conn: ConnId, pair: NetPair, secure: SecureContext) -> Self { 17 | Self { node, conn, pair, secure } 18 | } 19 | 20 | pub fn node(&self) -> NodeId { 21 | self.node 22 | } 23 | 24 | pub fn conn(&self) -> ConnId { 25 | self.conn 26 | } 27 | 28 | /// This will encrypt without first byte, which is used for TransportMsgHeader meta 29 | pub fn encrypt_if_need(&mut self, now: u64, buf: &mut Buffer) -> Option<()> { 30 | if buf.is_empty() { 31 | return None; 32 | } 33 | if !TransportMsgHeader::is_secure(buf[0]) { 34 | return Some(()); 35 | } 36 | buf.ensure_back(12 + 16); //TODO remove magic numbers 37 | buf.move_front_right(1); 38 | self.secure.encryptor.encrypt(now, buf).ok()?; 39 | buf.move_front_left(1); 40 | Some(()) 41 | } 42 | 43 | /// This will encrypt without first byte, which is used for TransportMsgHeader meta 44 | pub fn decrypt_if_need(&mut self, now: u64, buf: &mut Buffer) -> Option<()> { 45 | if buf.is_empty() { 46 | return None; 47 | } 48 | if !TransportMsgHeader::is_secure(buf[0]) { 49 | return Some(()); 50 | } 51 | buf.move_front_right(1); 52 | self.secure.decryptor.decrypt(now, buf).ok()?; 53 | buf.move_front_left(1); 54 | Some(()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/network/src/features/dht_kv/README.md: -------------------------------------------------------------------------------- 1 | # Simple DHT storage 2 | 3 | atm0s-dht is used for storing and retrieving key-value pairs in a distributed hash table (DHT). It is a simple implementation of a DHT, which is heavily inspired by the Kademlia DHT. We use this dht as a temporal storage for the network, with the idea of only trusting local data, and DHT is a way to synchronize data between sources and consumers. 4 | 5 | To do this, each node will sync its data with Set or Del commands to a node closest to its key, called RELAY. Each node that is interested in a key will send Sub or Unsub commands to a node closest to its key and will receive every change of that key. 6 | 7 | We have some terms: 8 | 9 | - SOURCE: node have data 10 | - RELAY: node take care of publish event to Consumers 11 | - CONSUMER: node interested in a key 12 | 13 | We need to solve some main problems with DHT storage: 14 | 15 | - Conflic 16 | - Out of sync 17 | 18 | ## Conflic 19 | 20 | We will have conflic when a key is updated by 2 nodes at the same time. To solve this, we will have multi-sources key, which data from a key will store independently, and will be merged at consumers. 21 | 22 | We also have a version for each node session, and we will use a pair (node_id, live_session) as an identify for a node, and called NodeSession 23 | 24 | | Key | Subkey | Source | Value | 25 | | --- | ------ | ----------- | --------- | 26 | | 1 | 2 | (200, 1234) | [1,2,3,4] | 27 | | 1 | 2 | (201, 1235) | [1,2,3,3] | 28 | 29 | 30 | ## Out of sync 31 | 32 | If there nodes don't changed overtime, then the location of each key will be remain, it case very simple. 33 | The more complex case is when network structure changed, then we need to re-locate the key to the new RELAY location. 34 | 35 | - SOURCE: will send Set to new RELAY 36 | - CONSUMERs: will send Sub to new RELAY 37 | - Old RELAY: will timeout the key and send OnDel to CONSUMERs 38 | - New RELAY: will send SetOk to Source and SubOk to CONSUMERs, and will send OnSet to CONSUMERs 39 | 40 | We can solve that by simple mechanism, which rely on Sub and SubOk. 41 | 42 | - Each subscribers is locked to a RELAY session, and only accept data from that session 43 | - Send Sub(relay_session) to RELAY, if selected RELAY is different from current locked relay_session, RELAY will send SubOk(new_session) and fires OnSet for all keys, OnSet is resend util it received OnSetAck or timeout. 44 | - Subscribers will received SubOk(new_session) and changed locked session to the new, and starting accept event from new session, by that way, OnDel(timeout) will not be accepted. 45 | 46 | We will have some edge cases: 47 | 48 | - SubOk is derivered after OnSet, then we will ignore previous and wait Relay resend OnSet after SubOk 49 | - SubOk is not derivered, then we will send Sub again 50 | - OnDel(Timeout) is derivered before SubOk: this case is very rarely, because Timeout is larger than resend Sub alot, if it happened, the consumers will have need to the key added after we send Sub, but in the end, we still have correct state. -------------------------------------------------------------------------------- /packages/network/src/features/dht_kv/client.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_router::RouteRule; 2 | use std::{ 3 | collections::{HashMap, VecDeque}, 4 | fmt::Debug, 5 | }; 6 | 7 | use crate::base::FeatureControlActor; 8 | 9 | use self::map::{LocalMap, LocalMapOutput}; 10 | 11 | const MAP_GET_TIMEOUT_MS: u64 = 5000; 12 | 13 | use super::{ 14 | msg::{ClientCommand, NodeSession, ServerEvent}, 15 | Control, Event, Map, 16 | }; 17 | 18 | mod map; 19 | 20 | fn route(key: Map) -> RouteRule { 21 | RouteRule::ToKey(key.0 as u32) 22 | } 23 | 24 | pub enum LocalStorageOutput { 25 | Local(FeatureControlActor, Event), 26 | Remote(RouteRule, ClientCommand), 27 | } 28 | 29 | pub struct LocalStorage { 30 | session: NodeSession, 31 | maps: HashMap>, 32 | map_get_waits: HashMap<(Map, u64), (FeatureControlActor, u64)>, 33 | queue: VecDeque>, 34 | req_id_seed: u64, 35 | } 36 | 37 | impl LocalStorage { 38 | pub fn new(session: NodeSession) -> Self { 39 | Self { 40 | session, 41 | maps: HashMap::new(), 42 | map_get_waits: HashMap::new(), 43 | queue: VecDeque::new(), 44 | req_id_seed: 0, 45 | } 46 | } 47 | 48 | pub fn on_tick(&mut self, now: u64) { 49 | // tick all maps and finding out if any of them should be removed 50 | let mut to_remove = vec![]; 51 | for (key, map) in self.maps.iter_mut() { 52 | map.on_tick(now); 53 | Self::pop_map_actions(*key, map, &mut self.queue); 54 | if map.should_cleanup() { 55 | to_remove.push(*key); 56 | } 57 | } 58 | 59 | for key in to_remove { 60 | log::info!("[DhtKvClient] remove old map: {}", key); 61 | self.maps.remove(&key); 62 | } 63 | 64 | // finding timeout map_get requests 65 | let mut to_remove = vec![]; 66 | for (key, info) in self.map_get_waits.iter() { 67 | if now >= info.1 + MAP_GET_TIMEOUT_MS { 68 | to_remove.push(*key); 69 | } 70 | } 71 | 72 | for key in to_remove { 73 | self.map_get_waits.remove(&key); 74 | } 75 | } 76 | 77 | pub fn on_local(&mut self, now: u64, actor: FeatureControlActor, control: Control) { 78 | match control { 79 | Control::MapCmd(key, control) => { 80 | if let Some(map) = Self::get_map(&mut self.maps, self.session, key, control.is_creator()) { 81 | if let Some(event) = map.on_control(now, actor, control) { 82 | self.queue.push_back(LocalStorageOutput::Remote(route(key), ClientCommand::MapCmd(key, event))); 83 | } 84 | Self::pop_map_actions(key, map, &mut self.queue); 85 | } 86 | } 87 | Control::MapGet(key) => { 88 | let req_id = self.req_id_seed; 89 | self.req_id_seed += 1; 90 | self.map_get_waits.insert((key, req_id), (actor, req_id)); 91 | self.queue.push_back(LocalStorageOutput::Remote(route(key), ClientCommand::MapGet(key, req_id))); 92 | } 93 | } 94 | } 95 | 96 | pub fn on_server(&mut self, now: u64, remote: NodeSession, cmd: ServerEvent) { 97 | match cmd { 98 | ServerEvent::MapEvent(key, cmd) => { 99 | if let Some(map) = self.maps.get_mut(&key) { 100 | if let Some(cmd) = map.on_server(now, remote, cmd) { 101 | self.queue.push_back(LocalStorageOutput::Remote(route(key), ClientCommand::MapCmd(key, cmd))); 102 | } 103 | Self::pop_map_actions(key, map, &mut self.queue); 104 | } else { 105 | log::warn!("Received remote command for unknown map: {:?}", key); 106 | } 107 | } 108 | ServerEvent::MapGetRes(key, req_id, res) => { 109 | if let Some((actor, _time_ms)) = self.map_get_waits.remove(&(key, req_id)) { 110 | self.queue.push_back(LocalStorageOutput::Local(actor, Event::MapGetRes(key, Ok(res)))); 111 | } 112 | } 113 | } 114 | } 115 | 116 | pub fn pop_action(&mut self) -> Option> { 117 | self.queue.pop_front() 118 | } 119 | 120 | fn get_map(maps: &mut HashMap>, session: NodeSession, key: Map, auto_create: bool) -> Option<&mut LocalMap> { 121 | if !maps.contains_key(&key) && auto_create { 122 | log::info!("[DhtKvClient] Creating new map: {}", key); 123 | maps.insert(key, LocalMap::new(session)); 124 | } 125 | maps.get_mut(&key) 126 | } 127 | 128 | fn pop_map_actions(key: Map, map: &mut LocalMap, queue: &mut VecDeque>) { 129 | while let Some(out) = map.pop_action() { 130 | queue.push_back(match out { 131 | LocalMapOutput::Local(actor, event) => LocalStorageOutput::Local(actor, Event::MapEvent(key, event)), 132 | LocalMapOutput::Remote(cmd) => LocalStorageOutput::Remote(route(key), ClientCommand::MapCmd(key, cmd)), 133 | }); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /packages/network/src/features/dht_kv/internal.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use atm0s_sdn_router::RouteRule; 4 | 5 | use crate::base::FeatureControlActor; 6 | 7 | use super::{ 8 | client::{LocalStorage, LocalStorageOutput}, 9 | msg::{NodeSession, RemoteCommand}, 10 | server::RemoteStorage, 11 | Control, Event, 12 | }; 13 | 14 | pub enum InternalOutput { 15 | Local(FeatureControlActor, Event), 16 | Remote(RouteRule, RemoteCommand), 17 | } 18 | 19 | pub struct DhtKvInternal { 20 | session: NodeSession, 21 | local: LocalStorage, 22 | remote: RemoteStorage, 23 | } 24 | 25 | impl DhtKvInternal { 26 | pub fn new(session: NodeSession) -> Self { 27 | Self { 28 | session, 29 | local: LocalStorage::new(session), 30 | remote: RemoteStorage::new(session), 31 | } 32 | } 33 | 34 | pub fn on_tick(&mut self, now: u64) { 35 | self.local.on_tick(now); 36 | self.remote.on_tick(now); 37 | } 38 | 39 | pub fn on_local(&mut self, now: u64, actor: FeatureControlActor, control: Control) { 40 | self.local.on_local(now, actor, control); 41 | } 42 | 43 | pub fn on_remote(&mut self, now: u64, cmd: RemoteCommand) { 44 | log::debug!("[DhtKvInternal] on_remote: {:?}", cmd); 45 | match cmd { 46 | RemoteCommand::Client(remote, cmd) => self.remote.on_remote(now, remote, cmd), 47 | RemoteCommand::Server(remote, cmd) => { 48 | self.local.on_server(now, remote, cmd); 49 | } 50 | } 51 | } 52 | 53 | pub fn pop_action(&mut self) -> Option> { 54 | if let Some(out) = self.local.pop_action() { 55 | match out { 56 | LocalStorageOutput::Remote(rule, cmd) => { 57 | log::debug!("[DhtKvInternal] Sending to {:?} cmd {:?}", rule, cmd); 58 | Some(InternalOutput::Remote(rule, RemoteCommand::Client(self.session, cmd))) 59 | } 60 | LocalStorageOutput::Local(actor, event) => { 61 | log::debug!("[DhtKvInternal] Send to actor {:?} event: {:?}", actor, event); 62 | Some(InternalOutput::Local(actor, event)) 63 | } 64 | } 65 | } else if let Some((session, cmd)) = self.remote.pop_action() { 66 | log::debug!("[DhtKvInternal] Sending to node {} cmd {:?}", session.0, cmd); 67 | Some(InternalOutput::Remote(RouteRule::ToNode(session.0), RemoteCommand::Server(self.session, cmd))) 68 | } else { 69 | None 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/network/src/features/dht_kv/msg.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_identity::NodeId; 2 | use atm0s_sdn_utils::simple_pub_type; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | simple_pub_type!(Map, u64); 6 | simple_pub_type!(Key, u64); 7 | simple_pub_type!(Version, u64); 8 | simple_pub_type!(Seq, u64); 9 | 10 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, Hash, PartialEq, Eq)] 11 | pub struct NodeSession(pub NodeId, pub u64); 12 | 13 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 14 | pub enum DelReason { 15 | Timeout, 16 | Remote, 17 | } 18 | 19 | #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] 20 | pub(crate) enum RemoteCommand { 21 | Client(NodeSession, ClientCommand), 22 | Server(NodeSession, ServerEvent), 23 | } 24 | 25 | // This part is for client related messages 26 | 27 | #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] 28 | pub enum ClientMapCommand { 29 | Set(Key, Version, Vec), 30 | Del(Key, Version), 31 | Sub(u64, Option), // 32 | Unsub(u64), 33 | OnSetAck(Key, NodeSession, Version), //Seq from OnHSet 34 | OnDelAck(Key, NodeSession, Version), //Seq from OnHDel 35 | } 36 | 37 | impl ClientMapCommand { 38 | pub fn is_creator(&self) -> bool { 39 | matches!(self, ClientMapCommand::Set(_, _, _) | ClientMapCommand::Sub(_, _)) 40 | } 41 | } 42 | 43 | #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] 44 | pub(crate) enum ClientCommand { 45 | MapCmd(Map, ClientMapCommand), 46 | MapGet(Map, u64), 47 | } 48 | 49 | // This part is for server related messages 50 | 51 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 52 | pub(crate) enum ServerMapEvent { 53 | SetOk(Key, Version), 54 | DelOk(Key, Version), 55 | SubOk(u64), 56 | UnsubOk(u64), 57 | OnSet { key: Key, source: NodeSession, version: Version, data: Vec }, 58 | OnDel { key: Key, source: NodeSession, version: Version }, 59 | } 60 | 61 | #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] 62 | pub(crate) enum ServerEvent { 63 | MapEvent(Map, ServerMapEvent), 64 | MapGetRes(Map, u64, Vec<(Key, NodeSession, Version, Vec)>), 65 | } 66 | -------------------------------------------------------------------------------- /packages/network/src/features/dht_kv/server.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, VecDeque}; 2 | 3 | use self::map::RemoteMap; 4 | 5 | use super::{ 6 | msg::{ClientCommand, NodeSession, ServerEvent}, 7 | Map, 8 | }; 9 | 10 | mod map; 11 | 12 | pub struct RemoteStorage { 13 | session: NodeSession, 14 | maps: HashMap, 15 | queue: VecDeque<(NodeSession, ServerEvent)>, 16 | } 17 | 18 | impl RemoteStorage { 19 | pub fn new(session: NodeSession) -> Self { 20 | Self { 21 | session, 22 | maps: HashMap::new(), 23 | queue: VecDeque::new(), 24 | } 25 | } 26 | 27 | pub fn on_tick(&mut self, now: u64) { 28 | let mut to_remove = vec![]; 29 | for (key, map) in self.maps.iter_mut() { 30 | map.on_tick(now); 31 | while let Some((session, event)) = map.pop_action() { 32 | self.queue.push_back((session, ServerEvent::MapEvent(*key, event))); 33 | } 34 | if map.should_clean() { 35 | to_remove.push(*key); 36 | } 37 | } 38 | 39 | for key in to_remove { 40 | self.maps.remove(&key); 41 | } 42 | } 43 | 44 | pub fn on_remote(&mut self, now: u64, remote: NodeSession, cmd: ClientCommand) { 45 | match cmd { 46 | ClientCommand::MapCmd(key, cmd) => { 47 | let map = if let Some(map) = self.maps.get_mut(&key) { 48 | map 49 | } else if cmd.is_creator() { 50 | log::info!("[DhtKvServer] Creating new map: {}", key); 51 | self.maps.insert(key, RemoteMap::new(self.session)); 52 | self.maps.get_mut(&key).expect("Must have value with previous inserted") 53 | } else { 54 | return; 55 | }; 56 | 57 | if let Some(event) = map.on_client(now, remote, cmd) { 58 | self.queue.push_back((remote, ServerEvent::MapEvent(key, event))); 59 | while let Some((session, event)) = map.pop_action() { 60 | self.queue.push_back((session, ServerEvent::MapEvent(key, event))); 61 | } 62 | } 63 | } 64 | ClientCommand::MapGet(key, id) => { 65 | let values = self.maps.get_mut(&key).map(|map| map.dump()).unwrap_or_default(); 66 | self.queue.push_back((remote, ServerEvent::MapGetRes(key, id, values))); 67 | } 68 | } 69 | } 70 | 71 | pub fn pop_action(&mut self) -> Option<(NodeSession, ServerEvent)> { 72 | self.queue.pop_front() 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/network/src/features/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod alias; 2 | pub mod data; 3 | pub mod dht_kv; 4 | pub mod neighbours; 5 | pub mod pubsub; 6 | pub mod router_sync; 7 | pub mod socket; 8 | pub mod vpn; 9 | 10 | /// 11 | /// FeatureManager need wrap child features in a struct to manage them 12 | /// This is a helper struct to help FeatureManager to manage the features 13 | /// 14 | 15 | #[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] 16 | #[repr(u8)] 17 | pub enum Features { 18 | Neighbours = neighbours::FEATURE_ID, 19 | Data = data::FEATURE_ID, 20 | RouterSync = router_sync::FEATURE_ID, 21 | Vpn = vpn::FEATURE_ID, 22 | DhtKv = dht_kv::FEATURE_ID, 23 | PubSub = pubsub::FEATURE_ID, 24 | Alias = alias::FEATURE_ID, 25 | Socket = socket::FEATURE_ID, 26 | } 27 | 28 | #[derive(Debug, Clone, PartialEq, Eq, convert_enum::From)] 29 | pub enum FeaturesControl { 30 | Neighbours(neighbours::Control), 31 | Data(data::Control), 32 | RouterSync(router_sync::Control), 33 | Vpn(vpn::Control), 34 | DhtKv(dht_kv::Control), 35 | PubSub(pubsub::Control), 36 | Alias(alias::Control), 37 | Socket(socket::Control), 38 | } 39 | 40 | impl FeaturesControl { 41 | pub fn to_feature(&self) -> Features { 42 | match self { 43 | Self::Neighbours(_) => Features::Neighbours, 44 | Self::Data(_) => Features::Data, 45 | Self::RouterSync(_) => Features::RouterSync, 46 | Self::Vpn(_) => Features::Vpn, 47 | Self::DhtKv(_) => Features::DhtKv, 48 | Self::PubSub(_) => Features::PubSub, 49 | Self::Alias(_) => Features::Alias, 50 | Self::Socket(_) => Features::Socket, 51 | } 52 | } 53 | } 54 | 55 | #[derive(Debug, Clone, PartialEq, Eq, convert_enum::From)] 56 | pub enum FeaturesEvent { 57 | Neighbours(neighbours::Event), 58 | Data(data::Event), 59 | RouterSync(router_sync::Event), 60 | Vpn(vpn::Event), 61 | DhtKv(dht_kv::Event), 62 | PubSub(pubsub::Event), 63 | Alias(alias::Event), 64 | Socket(socket::Event), 65 | } 66 | 67 | #[derive(Debug, Clone, convert_enum::From)] 68 | pub enum FeaturesToController { 69 | Neighbours(neighbours::ToController), 70 | Data(data::ToController), 71 | RouterSync(router_sync::ToController), 72 | Vpn(vpn::ToController), 73 | DhtKv(dht_kv::ToController), 74 | PubSub(pubsub::ToController), 75 | Alias(alias::ToController), 76 | Socket(socket::ToController), 77 | } 78 | 79 | impl FeaturesToController { 80 | pub fn to_feature(&self) -> Features { 81 | match self { 82 | Self::Neighbours(_) => Features::Neighbours, 83 | Self::Data(_) => Features::Data, 84 | Self::RouterSync(_) => Features::RouterSync, 85 | Self::Vpn(_) => Features::Vpn, 86 | Self::DhtKv(_) => Features::DhtKv, 87 | Self::PubSub(_) => Features::PubSub, 88 | Self::Alias(_) => Features::Alias, 89 | Self::Socket(_) => Features::Socket, 90 | } 91 | } 92 | } 93 | 94 | #[derive(Debug, Clone, convert_enum::From)] 95 | pub enum FeaturesToWorker { 96 | Neighbours(neighbours::ToWorker), 97 | Data(data::ToWorker), 98 | RouterSync(router_sync::ToWorker), 99 | Vpn(vpn::ToWorker), 100 | DhtKv(dht_kv::ToWorker), 101 | PubSub(pubsub::ToWorker), 102 | Alias(alias::ToWorker), 103 | Socket(socket::ToWorker), 104 | } 105 | 106 | impl FeaturesToWorker { 107 | pub fn to_feature(&self) -> Features { 108 | match self { 109 | Self::Neighbours(_) => Features::Neighbours, 110 | Self::Data(_) => Features::Data, 111 | Self::RouterSync(_) => Features::RouterSync, 112 | Self::Vpn(_) => Features::Vpn, 113 | Self::DhtKv(_) => Features::DhtKv, 114 | Self::PubSub(_) => Features::PubSub, 115 | Self::Alias(_) => Features::Alias, 116 | Self::Socket(_) => Features::Socket, 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /packages/network/src/features/pubsub/README.md: -------------------------------------------------------------------------------- 1 | # Pubsub system 2 | 3 | This package provides a pubsub system that allows you to publish messages to a topic and subscribe to messages on a topic. 4 | For simplicity, we split it to 2 parts: 5 | 6 | - Pubsub core: which is take care relaying data between concrete pubsub source to consumers. In this part, we need to determine where is the source of the channel. 7 | - Auto detect source: combine above Pubsub core with KeyValue system, we will store the sources of the channel in the DHT, and we will use the DHT to detect the source of the channel. 8 | 9 | ## Pubsub Protocol 10 | 11 | We have 5 types of message: 12 | 13 | - Sub (channel, source, uuid) 14 | - SubOk (channel, source, uuid) 15 | - Unsub (channel, source, uuid) 16 | - UnsubOk (channel, source, uuid, data) 17 | - Data(channel, source, uuid, data) 18 | 19 | We a node interested in a topic in a source node, it will find the way to the source then send Sub (channel, source, uuid) to next node by using router table. Uuid is the session of node, which can be used to determine the node is still alive or not. 20 | 21 | If the node don't interest in the topic anymore, it will send Unsub (channel, source, uuid) to the source node. 22 | 23 | Each node when receive Sub, it will send SubOk to the source node, and start to relay data from source to the node. If the node receive Unsub, it will send UnsubOk to the source node, and stop relaying data. 24 | 25 | The uuid also to be used to validate SubOk and UnsubOk. In the future, we can have more complex logic to validate the message, like signature or encryption. 26 | 27 | ## Sticky or Dynamic path 28 | 29 | Atm0s routing table can providing two way to route the message: 30 | 31 | - Sticky: the route path will be fixed until pubsub channel is closed. When any node in route path is down, the previous node will try to find the new path to the source node. 32 | - Dynamic: the route path will be updated periodically, and the route path will be changed if the network structure is changed. This is useful when the network is changed frequently. 33 | 34 | We can have combine of both, which the route path will be sticky in a period of time, and will be updated if the network structure is changed. 35 | 36 | Currently implement will keep sticky in 5 minutes, and will be updated if the network structure is changed. -------------------------------------------------------------------------------- /packages/network/src/features/pubsub/controller/local_relay.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use derivative::Derivative; 4 | 5 | use crate::{ 6 | base::FeatureControlActor, 7 | data_plane::NetPair, 8 | features::pubsub::msg::{Feedback, RelayControl}, 9 | }; 10 | 11 | use super::{consumers::RelayConsumers, feedbacks::FeedbacksAggerator, GenericRelay, GenericRelayOutput}; 12 | 13 | #[derive(Derivative)] 14 | #[derivative(Default(bound = ""))] 15 | pub struct LocalRelay { 16 | consumers: RelayConsumers, 17 | feedbacks: FeedbacksAggerator, 18 | publishers: Vec>, 19 | } 20 | 21 | impl GenericRelay for LocalRelay { 22 | fn on_tick(&mut self, now: u64) { 23 | self.feedbacks.on_tick(now); 24 | self.consumers.on_tick(now); 25 | } 26 | 27 | fn on_pub_start(&mut self, actor: FeatureControlActor) { 28 | if !self.publishers.contains(&actor) { 29 | log::debug!("[LocalRelay] on_pub_start {:?}", actor); 30 | self.publishers.push(actor); 31 | } 32 | } 33 | 34 | fn on_pub_stop(&mut self, actor: FeatureControlActor) { 35 | if let Some(index) = self.publishers.iter().position(|x| *x == actor) { 36 | log::debug!("[LocalRelay] on_pub_stop {:?}", actor); 37 | self.publishers.swap_remove(index); 38 | } 39 | } 40 | 41 | fn on_local_sub(&mut self, now: u64, actor: FeatureControlActor) { 42 | self.consumers.on_local_sub(now, actor); 43 | } 44 | 45 | fn on_local_feedback(&mut self, now: u64, actor: FeatureControlActor, feedback: Feedback) { 46 | self.feedbacks.on_local_feedback(now, actor, feedback); 47 | } 48 | 49 | fn on_local_unsub(&mut self, now: u64, actor: FeatureControlActor) { 50 | self.consumers.on_local_unsub(now, actor); 51 | } 52 | 53 | fn on_remote(&mut self, now: u64, remote: NetPair, control: RelayControl) { 54 | if let RelayControl::Feedback(fb) = control { 55 | self.feedbacks.on_remote_feedback(now, remote, fb); 56 | } else { 57 | self.consumers.on_remote(now, remote, control); 58 | } 59 | } 60 | 61 | fn conn_disconnected(&mut self, now: u64, remote: NetPair) { 62 | self.consumers.conn_disconnected(now, remote); 63 | } 64 | 65 | fn should_clear(&self) -> bool { 66 | self.consumers.should_clear() && self.publishers.is_empty() 67 | } 68 | 69 | fn relay_dests(&self) -> Option<(&[FeatureControlActor], bool)> { 70 | Some(self.consumers.relay_dests()) 71 | } 72 | 73 | fn pop_output(&mut self) -> Option> { 74 | if let Some(fb) = self.feedbacks.pop_output() { 75 | log::debug!("[LocalRelay] pop_output feedback {:?}", fb); 76 | return Some(GenericRelayOutput::Feedback(self.publishers.clone(), fb)); 77 | } 78 | self.consumers.pop_output().map(GenericRelayOutput::ToWorker) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/network/src/features/pubsub/mod.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_identity::NodeId; 2 | 3 | use crate::{ 4 | base::{FeatureControlActor, FeatureOutput, FeatureWorkerOutput}, 5 | data_plane::NetPair, 6 | }; 7 | 8 | use self::msg::{RelayControl, RelayId, SourceHint}; 9 | 10 | mod controller; 11 | mod msg; 12 | mod worker; 13 | 14 | pub use controller::PubSubFeature; 15 | pub use msg::{ChannelId, Feedback}; 16 | pub use worker::PubSubFeatureWorker; 17 | 18 | pub const FEATURE_ID: u8 = 5; 19 | pub const FEATURE_NAME: &str = "pubsub"; 20 | 21 | #[derive(Debug, Clone, PartialEq, Eq)] 22 | pub enum ChannelControl { 23 | SubAuto, 24 | FeedbackAuto(Feedback), 25 | UnsubAuto, 26 | SubSource(NodeId), 27 | UnsubSource(NodeId), 28 | PubStart, 29 | PubData(Vec), 30 | PubStop, 31 | } 32 | 33 | #[derive(Debug, Clone, PartialEq, Eq)] 34 | pub struct Control(pub ChannelId, pub ChannelControl); 35 | 36 | #[derive(Debug, Clone, PartialEq, Eq)] 37 | pub enum ChannelEvent { 38 | RouteChanged(NodeId), 39 | SourceData(NodeId, Vec), 40 | FeedbackData(Feedback), 41 | } 42 | 43 | #[derive(Debug, Clone, PartialEq, Eq)] 44 | pub struct Event(pub ChannelId, pub ChannelEvent); 45 | 46 | #[derive(Debug, Clone, PartialEq, Eq)] 47 | pub enum RelayWorkerControl { 48 | SendSub(u64, Option), 49 | SendUnsub(u64, NetPair), 50 | SendSubOk(u64, NetPair), 51 | SendUnsubOk(u64, NetPair), 52 | SendRouteChanged, 53 | SendFeedback(Feedback, NetPair), 54 | RouteSetSource(NetPair), 55 | RouteDelSource(NetPair), 56 | RouteSetLocal(FeatureControlActor), 57 | RouteDelLocal(FeatureControlActor), 58 | RouteSetRemote(NetPair, u64), 59 | RouteDelRemote(NetPair), 60 | } 61 | 62 | impl RelayWorkerControl { 63 | pub fn is_broadcast(&self) -> bool { 64 | !matches!( 65 | self, 66 | RelayWorkerControl::SendSub(_, _) 67 | | RelayWorkerControl::SendUnsub(_, _) 68 | | RelayWorkerControl::SendSubOk(_, _) 69 | | RelayWorkerControl::SendUnsubOk(_, _) 70 | | RelayWorkerControl::SendRouteChanged 71 | ) 72 | } 73 | } 74 | 75 | #[derive(Debug, Clone)] 76 | pub enum ToWorker { 77 | RelayControl(RelayId, RelayWorkerControl), 78 | SourceHint(ChannelId, Option, SourceHint), 79 | RelayData(RelayId, Vec), 80 | } 81 | 82 | #[derive(Debug, Clone)] 83 | pub enum ToController { 84 | RelayControl(NetPair, RelayId, RelayControl), 85 | SourceHint(NetPair, ChannelId, SourceHint), 86 | } 87 | 88 | pub type Output = FeatureOutput>; 89 | pub type WorkerOutput = FeatureWorkerOutput; 90 | -------------------------------------------------------------------------------- /packages/network/src/features/pubsub/msg.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_identity::NodeId; 2 | use atm0s_sdn_router::RouteRule; 3 | use atm0s_sdn_utils::simple_pub_type; 4 | use sans_io_runtime::Buffer; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::base::{TransportMsg, TransportMsgHeader, TransportMsgHeaderError}; 8 | 9 | use super::FEATURE_ID; 10 | 11 | simple_pub_type!(ChannelId, u64); 12 | 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] 14 | pub struct RelayId(pub ChannelId, pub NodeId); 15 | 16 | #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] 17 | pub struct Feedback { 18 | pub kind: u8, 19 | pub count: u64, 20 | pub max: u64, 21 | pub min: u64, 22 | pub sum: u64, 23 | pub interval_ms: u16, 24 | pub timeout_ms: u16, 25 | } 26 | 27 | impl Feedback { 28 | pub fn simple(kind: u8, value: u64, interval_ms: u16, timeout_ms: u16) -> Self { 29 | Feedback { 30 | kind, 31 | count: 1, 32 | max: value, 33 | min: value, 34 | sum: value, 35 | interval_ms, 36 | timeout_ms, 37 | } 38 | } 39 | } 40 | 41 | ///implement add to Feedback 42 | impl std::ops::Add for Feedback { 43 | type Output = Self; 44 | 45 | fn add(self, rhs: Self) -> Self::Output { 46 | Feedback { 47 | kind: self.kind, 48 | count: self.count + rhs.count, 49 | max: self.max.max(rhs.max), 50 | min: self.min.min(rhs.min), 51 | sum: self.sum + rhs.sum, 52 | interval_ms: self.interval_ms.min(rhs.interval_ms), 53 | timeout_ms: self.timeout_ms.max(rhs.timeout_ms), 54 | } 55 | } 56 | } 57 | 58 | #[derive(Debug, Clone, Serialize, Deserialize)] 59 | pub enum RelayControl { 60 | Sub(u64), 61 | Unsub(u64), 62 | SubOK(u64), 63 | UnsubOK(u64), 64 | RouteChanged(u64), 65 | Feedback(Feedback), 66 | } 67 | 68 | impl RelayControl { 69 | pub fn should_create(&self) -> bool { 70 | matches!(self, RelayControl::Sub(_)) 71 | } 72 | } 73 | 74 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 75 | pub enum SourceHint { 76 | /// This is used to notify a source is new or still alive. 77 | /// This message is send to next hop and relayed to all subscribers except sender. 78 | Register { 79 | source: NodeId, 80 | to_root: bool, 81 | }, 82 | /// This is used to notify a source is ended. 83 | /// This message is send to next hop and relayed to all subscribers except sender. 84 | Unregister { 85 | source: NodeId, 86 | to_root: bool, 87 | }, 88 | Subscribe(u64), 89 | SubscribeOk(u64), 90 | Unsubscribe(u64), 91 | UnsubscribeOk(u64), 92 | /// This is used when a new subscriber is added, it is like a snapshot for faster initing state. 93 | Sources(Vec), 94 | } 95 | 96 | impl SourceHint { 97 | pub fn should_create(&self) -> bool { 98 | matches!(self, SourceHint::Register { .. } | SourceHint::Subscribe(_)) 99 | } 100 | } 101 | 102 | pub enum PubsubMessageError { 103 | TransportError(TransportMsgHeaderError), 104 | DeserializeError, 105 | } 106 | 107 | #[derive(Debug, Serialize, Deserialize)] 108 | pub enum PubsubMessage { 109 | Control(RelayId, RelayControl), 110 | SourceHint(ChannelId, SourceHint), 111 | Data(RelayId, Vec), 112 | } 113 | 114 | impl TryFrom<&[u8]> for PubsubMessage { 115 | type Error = PubsubMessageError; 116 | 117 | fn try_from(value: &[u8]) -> Result { 118 | let msg = TransportMsg::try_from(value).map_err(PubsubMessageError::TransportError)?; 119 | msg.get_payload_bincode().map_err(|_| PubsubMessageError::DeserializeError) 120 | } 121 | } 122 | 123 | impl From for Buffer { 124 | fn from(val: PubsubMessage) -> Self { 125 | let header = TransportMsgHeader::build(FEATURE_ID, 0, RouteRule::Direct); 126 | let msg = TransportMsg::from_payload_bincode(header, &val); 127 | msg.take() 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /packages/network/src/features/vpn.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | #[cfg(feature = "vpn")] 4 | use crate::base::TransportMsg; 5 | #[cfg(feature = "vpn")] 6 | use atm0s_sdn_identity::{NodeId, NodeIdType}; 7 | #[cfg(feature = "vpn")] 8 | use atm0s_sdn_router::{RouteAction, RouteRule, RouterTable}; 9 | use derivative::Derivative; 10 | use sans_io_runtime::{collections::DynamicDeque, TaskSwitcherChild}; 11 | 12 | use crate::base::{Buffer, Feature, FeatureContext, FeatureInput, FeatureOutput, FeatureWorker, FeatureWorkerContext, FeatureWorkerInput, FeatureWorkerOutput}; 13 | 14 | pub const FEATURE_ID: u8 = 3; 15 | pub const FEATURE_NAME: &str = "vpn"; 16 | 17 | #[derive(Debug, Clone, PartialEq, Eq)] 18 | pub enum Control {} 19 | 20 | #[derive(Debug, Clone, PartialEq, Eq)] 21 | pub enum Event {} 22 | 23 | #[derive(Debug, Clone)] 24 | pub struct ToWorker; 25 | 26 | #[derive(Debug, Clone)] 27 | pub struct ToController; 28 | 29 | pub type Output = FeatureOutput; 30 | pub type WorkerOutput = FeatureWorkerOutput; 31 | 32 | #[derive(Debug, Derivative)] 33 | #[derivative(Default(bound = ""))] 34 | pub struct VpnFeature { 35 | _tmp: PhantomData, 36 | shutdown: bool, 37 | } 38 | 39 | impl Feature for VpnFeature { 40 | fn on_shared_input(&mut self, _ctx: &FeatureContext, _now: u64, _input: crate::base::FeatureSharedInput) {} 41 | 42 | fn on_input(&mut self, _ctx: &FeatureContext, _now_ms: u64, _input: FeatureInput<'_, UserData, Control, ToController>) {} 43 | 44 | fn on_shutdown(&mut self, _ctx: &FeatureContext, _now: u64) { 45 | self.shutdown = true; 46 | } 47 | } 48 | 49 | impl TaskSwitcherChild> for VpnFeature { 50 | type Time = u64; 51 | 52 | fn is_empty(&self) -> bool { 53 | self.shutdown 54 | } 55 | 56 | fn empty_event(&self) -> Output { 57 | Output::OnResourceEmpty 58 | } 59 | 60 | fn pop_output(&mut self, _now: u64) -> Option> { 61 | None 62 | } 63 | } 64 | 65 | #[derive(Derivative)] 66 | #[derivative(Default(bound = ""))] 67 | pub struct VpnFeatureWorker { 68 | queue: DynamicDeque, 16>, 69 | shutdown: bool, 70 | } 71 | 72 | impl VpnFeatureWorker { 73 | #[cfg(feature = "vpn")] 74 | fn process_tun(&mut self, ctx: &FeatureWorkerContext, mut pkt: Buffer) { 75 | #[cfg(any(target_os = "macos", target_os = "ios"))] 76 | let to_ip = &pkt[20..24]; 77 | #[cfg(any(target_os = "linux", target_os = "android"))] 78 | let to_ip = &pkt[16..20]; 79 | let dest = NodeId::build(ctx.node_id.geo1(), ctx.node_id.geo2(), ctx.node_id.group(), to_ip[3]); 80 | if dest == ctx.node_id { 81 | //This is for current node, just echo back 82 | rewrite_tun_pkt(&mut pkt); 83 | self.queue.push_back(FeatureWorkerOutput::TunPkt(pkt)); 84 | } else if let RouteAction::Next(remote) = ctx.router.path_to_node(dest) { 85 | //TODO decrease TTL 86 | //TODO how to avoid copy data here 87 | self.queue 88 | .push_back(FeatureWorkerOutput::RawDirect2(remote, TransportMsg::build(FEATURE_ID, 0, RouteRule::ToNode(dest), &pkt).take())); 89 | } 90 | } 91 | 92 | fn process_udp(&mut self, _ctx: &FeatureWorkerContext, pkt: Buffer) { 93 | #[cfg(feature = "vpn")] 94 | { 95 | self.queue.push_back(FeatureWorkerOutput::TunPkt(pkt)); 96 | } 97 | } 98 | } 99 | 100 | impl FeatureWorker for VpnFeatureWorker { 101 | fn on_input(&mut self, ctx: &mut FeatureWorkerContext, _now: u64, input: FeatureWorkerInput) { 102 | match input { 103 | #[cfg(feature = "vpn")] 104 | FeatureWorkerInput::TunPkt(pkt) => self.process_tun(ctx, pkt), 105 | FeatureWorkerInput::Network(_conn, _header, pkt) => self.process_udp(ctx, pkt), 106 | _ => {} 107 | } 108 | } 109 | 110 | fn on_shutdown(&mut self, _ctx: &mut FeatureWorkerContext, _now: u64) { 111 | log::info!("[VpnFeatureWorker] Shutdown"); 112 | self.shutdown = true; 113 | } 114 | } 115 | 116 | impl TaskSwitcherChild> for VpnFeatureWorker { 117 | type Time = u64; 118 | 119 | fn is_empty(&self) -> bool { 120 | self.shutdown && self.queue.is_empty() 121 | } 122 | 123 | fn empty_event(&self) -> WorkerOutput { 124 | WorkerOutput::OnResourceEmpty 125 | } 126 | 127 | fn pop_output(&mut self, _now: u64) -> Option> { 128 | self.queue.pop_front() 129 | } 130 | } 131 | 132 | #[cfg(feature = "vpn")] 133 | fn rewrite_tun_pkt(payload: &mut [u8]) { 134 | #[cfg(any(target_os = "macos", target_os = "ios"))] 135 | { 136 | payload[2] = 0; 137 | payload[3] = 2; 138 | } 139 | #[cfg(any(target_os = "linux", target_os = "android"))] 140 | { 141 | payload[2] = 8; 142 | payload[3] = 0; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /packages/network/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::bool_assert_comparison)] 2 | 3 | use atm0s_sdn_identity::{ConnId, NodeId}; 4 | use atm0s_sdn_router::RouteRule; 5 | use base::{FeatureControlActor, NeighboursControl, NetIncomingMeta, NetOutgoingMeta, SecureContext, ServiceControlActor, ServiceId}; 6 | use data_plane::{CrossWorker, NetPair}; 7 | use features::{Features, FeaturesControl, FeaturesEvent, FeaturesToController, FeaturesToWorker}; 8 | use sans_io_runtime::Buffer; 9 | 10 | #[cfg(feature = "fuzz")] 11 | pub mod _fuzz_export; 12 | pub mod base; 13 | pub mod controller_plane; 14 | pub mod data_plane; 15 | pub mod features; 16 | pub mod secure; 17 | pub mod services; 18 | pub mod worker; 19 | 20 | #[derive(Debug, Clone)] 21 | pub enum ExtIn { 22 | FeaturesControl(UserData, FeaturesControl), 23 | ServicesControl(ServiceId, UserData, ServicesControl), 24 | } 25 | 26 | #[derive(Debug, Clone, PartialEq, Eq)] 27 | pub enum ExtOut { 28 | FeaturesEvent(UserData, FeaturesEvent), 29 | ServicesEvent(ServiceId, UserData, ServicesEvent), 30 | } 31 | 32 | #[derive(Debug, Clone)] 33 | pub enum LogicControl { 34 | Feature(FeaturesToController), 35 | Service(ServiceId, TC), 36 | NetNeighbour(NetPair, NeighboursControl), 37 | NetRemote(Features, ConnId, NetIncomingMeta, Buffer), 38 | NetLocal(Features, NetIncomingMeta, Buffer), 39 | FeaturesControl(FeatureControlActor, FeaturesControl), 40 | ServicesControl(ServiceControlActor, ServiceId, SC), 41 | ServiceEvent(ServiceId, FeaturesEvent), 42 | ExtFeaturesEvent(UserData, FeaturesEvent), 43 | ExtServicesEvent(ServiceId, UserData, SE), 44 | } 45 | 46 | #[derive(Debug, Clone)] 47 | pub enum LogicEvent { 48 | NetNeighbour(NetPair, NeighboursControl), 49 | NetDirect(Features, NetPair, ConnId, NetOutgoingMeta, Buffer), 50 | NetRoute(Features, RouteRule, NetOutgoingMeta, Buffer), 51 | 52 | Pin(ConnId, NodeId, NetPair, SecureContext), 53 | UnPin(ConnId), 54 | /// first bool is flag for broadcast or not 55 | Feature(bool, FeaturesToWorker), 56 | Service(ServiceId, TW), 57 | /// first u16 is worker id 58 | ExtFeaturesEvent(u16, UserData, FeaturesEvent), 59 | /// first u16 is worker id 60 | ExtServicesEvent(u16, ServiceId, UserData, SE), 61 | } 62 | 63 | pub enum LogicEventDest { 64 | Broadcast(LogicEvent), 65 | Worker(u16, CrossWorker), 66 | Any(LogicEvent), 67 | } 68 | 69 | impl LogicEvent { 70 | pub fn with_dest(self) -> LogicEventDest { 71 | match self { 72 | LogicEvent::Pin(..) => LogicEventDest::Broadcast(self), 73 | LogicEvent::UnPin(..) => LogicEventDest::Broadcast(self), 74 | LogicEvent::Service(..) => LogicEventDest::Broadcast(self), 75 | LogicEvent::Feature(true, ..) => LogicEventDest::Broadcast(self), 76 | LogicEvent::Feature(false, ..) => LogicEventDest::Any(self), 77 | LogicEvent::NetNeighbour(_, _) => LogicEventDest::Any(self), 78 | LogicEvent::NetDirect(_, _, _, _, _) => LogicEventDest::Any(self), 79 | LogicEvent::NetRoute(_, _, _, _) => LogicEventDest::Any(self), 80 | LogicEvent::ExtFeaturesEvent(worker, user_data, event) => LogicEventDest::Worker(worker, CrossWorker::Feature(user_data, event)), 81 | LogicEvent::ExtServicesEvent(worker, service_id, user_data, event) => LogicEventDest::Worker(worker, CrossWorker::Service(service_id, user_data, event)), 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /packages/network/src/secure/authorization/mod.rs: -------------------------------------------------------------------------------- 1 | mod static_key; 2 | pub use static_key::StaticKeyAuthorization; 3 | -------------------------------------------------------------------------------- /packages/network/src/secure/authorization/static_key.rs: -------------------------------------------------------------------------------- 1 | //! Simplest authorization method by using hash with salt is static key 2 | //! 3 | 4 | use atm0s_sdn_identity::NodeId; 5 | use sha2::Digest; 6 | 7 | use crate::base::Authorization; 8 | 9 | pub struct StaticKeyAuthorization { 10 | key: String, 11 | } 12 | 13 | impl StaticKeyAuthorization { 14 | pub fn new(key: &str) -> Self { 15 | Self { key: key.to_string() } 16 | } 17 | } 18 | 19 | impl Authorization for StaticKeyAuthorization { 20 | /// Generate signature for message as hash with salt 21 | fn sign(&self, msg: &[u8]) -> Vec { 22 | let mut hasher = sha2::Sha256::default(); 23 | hasher.update(msg); 24 | hasher.update(self.key.as_bytes()); 25 | hasher.finalize().to_vec() 26 | } 27 | 28 | /// Validate message signature by comparing hash with salt 29 | fn validate(&self, _node_id: NodeId, msg: &[u8], sign: &[u8]) -> Option<()> { 30 | let mut hasher = sha2::Sha256::default(); 31 | hasher.update(msg); 32 | hasher.update(self.key.as_bytes()); 33 | let hash = hasher.finalize(); 34 | if hash.as_slice() == sign { 35 | Some(()) 36 | } else { 37 | None 38 | } 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn test_static_key_authorization() { 48 | let auth = StaticKeyAuthorization::new("demo-key"); 49 | 50 | let msg = b"hello"; 51 | let sign = auth.sign(msg); 52 | assert_eq!(auth.validate(1, msg, &sign), Some(())); 53 | } 54 | 55 | #[test] 56 | fn test_static_key_authorization_invalid() { 57 | let auth = StaticKeyAuthorization::new("demo-key"); 58 | 59 | let msg = b"hello"; 60 | let sign = auth.sign(msg); 61 | assert_eq!(auth.validate(1, msg, &sign[1..]), None); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/network/src/secure/encryption/mod.rs: -------------------------------------------------------------------------------- 1 | mod x25519_dalek_aes; 2 | 3 | pub use x25519_dalek_aes::HandshakeBuilderXDA; 4 | -------------------------------------------------------------------------------- /packages/network/src/secure/mod.rs: -------------------------------------------------------------------------------- 1 | mod authorization; 2 | mod encryption; 3 | 4 | pub use authorization::*; 5 | pub use encryption::*; 6 | -------------------------------------------------------------------------------- /packages/network/src/services/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod manual2_discovery; 2 | pub mod manual_discovery; 3 | pub mod visualization; 4 | -------------------------------------------------------------------------------- /packages/network/tests/feature_neighbours.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_identity::ConnId; 2 | use atm0s_sdn_network::{ 3 | features::{neighbours, FeaturesControl, FeaturesEvent}, 4 | ExtIn, ExtOut, 5 | }; 6 | 7 | use crate::simulator::{NetworkSimulator, TestNode}; 8 | 9 | mod simulator; 10 | 11 | #[test] 12 | fn feature_neighbours_two_nodes() { 13 | let node1 = 1; 14 | let node2 = 2; 15 | let mut sim = NetworkSimulator::<(), (), (), ()>::new(0); 16 | 17 | let _addr1 = sim.add_node(TestNode::new(node1, 1234, vec![])); 18 | let addr2 = sim.add_node(TestNode::new(node2, 1235, vec![])); 19 | 20 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::Sub))); 21 | sim.control(node2, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::Sub))); 22 | 23 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(addr2, false)))); 24 | 25 | // For sync 26 | for _i in 0..4 { 27 | sim.process(500); 28 | } 29 | 30 | let mut out = vec![]; 31 | while let Some((node, out_event)) = sim.pop_res() { 32 | out.push((node, out_event)); 33 | } 34 | 35 | out.sort_by(|a, b| a.0.cmp(&b.0)); 36 | 37 | assert_eq!( 38 | out, 39 | vec![ 40 | ( 41 | node1, 42 | ExtOut::FeaturesEvent((), FeaturesEvent::Neighbours(neighbours::Event::Connected(node2, ConnId::from_out(0, 1000)))) 43 | ), 44 | ( 45 | node2, 46 | ExtOut::FeaturesEvent((), FeaturesEvent::Neighbours(neighbours::Event::Connected(node1, ConnId::from_in(0, 1000)))) 47 | ), 48 | ] 49 | ); 50 | } 51 | 52 | #[test] 53 | fn feature_neighbours_request_seed_node() { 54 | let node1 = 1; 55 | let node2 = 2; 56 | let mut sim = NetworkSimulator::<(), (), (), ()>::new(0); 57 | 58 | let _addr1 = sim.add_node(TestNode::new(node1, 1234, vec![])); 59 | let addr2 = sim.add_node(TestNode::new(node2, 1235, vec![])); 60 | 61 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::Sub))); 62 | sim.control(node2, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::Sub))); 63 | 64 | // connect to node2 as seed node 65 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(addr2, true)))); 66 | 67 | // For sync 68 | for _i in 0..4 { 69 | sim.process(500); 70 | } 71 | 72 | let mut out = vec![]; 73 | while let Some((node, out_event)) = sim.pop_res() { 74 | out.push((node, out_event)); 75 | } 76 | 77 | out.sort_by(|a, b| a.0.cmp(&b.0)); 78 | 79 | assert_eq!( 80 | out, 81 | vec![ 82 | ( 83 | node1, 84 | ExtOut::FeaturesEvent((), FeaturesEvent::Neighbours(neighbours::Event::Connected(node2, ConnId::from_out(0, 1000)))) 85 | ), 86 | ( 87 | node2, 88 | ExtOut::FeaturesEvent((), FeaturesEvent::Neighbours(neighbours::Event::Connected(node1, ConnId::from_in(0, 1000)))) 89 | ), 90 | ] 91 | ); 92 | 93 | // simulate node2 disconnect from node1 94 | sim.control(node2, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::DisconnectFrom(node1)))); 95 | 96 | // For sync 97 | for _i in 0..4 { 98 | sim.process(500); 99 | } 100 | 101 | let mut out = vec![]; 102 | while let Some((node, out_event)) = sim.pop_res() { 103 | out.push((node, out_event)); 104 | } 105 | 106 | out.sort_by(|a, b| a.0.cmp(&b.0)); 107 | 108 | assert_eq!( 109 | out, 110 | vec![ 111 | ( 112 | node1, 113 | ExtOut::FeaturesEvent((), FeaturesEvent::Neighbours(neighbours::Event::Disconnected(node2, ConnId::from_out(0, 1000)))) 114 | ), 115 | (node1, ExtOut::FeaturesEvent((), FeaturesEvent::Neighbours(neighbours::Event::SeedAddressNeeded))), 116 | ( 117 | node2, 118 | ExtOut::FeaturesEvent((), FeaturesEvent::Neighbours(neighbours::Event::Disconnected(node1, ConnId::from_in(0, 1000)))) 119 | ), 120 | ] 121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /packages/network/tests/feature_socket.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_network::{ 2 | features::{neighbours, socket, FeaturesControl, FeaturesEvent}, 3 | ExtIn, ExtOut, 4 | }; 5 | 6 | use crate::simulator::{NetworkSimulator, TestNode}; 7 | 8 | mod simulator; 9 | 10 | #[test] 11 | fn feature_socket_single_node() { 12 | let node1 = 1; 13 | let mut sim = NetworkSimulator::<(), (), (), ()>::new(0); 14 | 15 | let _addr1 = sim.add_node(TestNode::new(node1, 1234, vec![])); 16 | 17 | // For sync 18 | for _i in 0..4 { 19 | sim.process(500); 20 | } 21 | 22 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Socket(socket::Control::Bind(10000)))); 23 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Socket(socket::Control::Bind(10001)))); 24 | sim.control( 25 | node1, 26 | ExtIn::FeaturesControl((), FeaturesControl::Socket(socket::Control::SendTo(10001, node1, 10000, vec![1, 2, 3, 4].into(), 0))), 27 | ); 28 | sim.process(10); 29 | assert_eq!( 30 | sim.pop_res(), 31 | Some(( 32 | node1, 33 | ExtOut::FeaturesEvent((), FeaturesEvent::Socket(socket::Event::RecvFrom(10000, node1, 10001, vec![1, 2, 3, 4].into(), 0))) 34 | )) 35 | ); 36 | } 37 | 38 | #[test] 39 | fn feature_socket_two_nodes() { 40 | let node1 = 1; 41 | let node2 = 2; 42 | let mut sim = NetworkSimulator::<(), (), (), ()>::new(0); 43 | 44 | let _addr1 = sim.add_node(TestNode::new(node1, 1234, vec![])); 45 | let addr2 = sim.add_node(TestNode::new(node2, 1235, vec![])); 46 | 47 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(addr2, false)))); 48 | 49 | // For sync 50 | for _i in 0..4 { 51 | sim.process(500); 52 | } 53 | 54 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Socket(socket::Control::Bind(10000)))); 55 | sim.control(node2, ExtIn::FeaturesControl((), FeaturesControl::Socket(socket::Control::Bind(10001)))); 56 | sim.process(10); 57 | sim.control( 58 | node2, 59 | ExtIn::FeaturesControl((), FeaturesControl::Socket(socket::Control::SendTo(10001, node1, 10000, vec![1, 2, 3, 4].into(), 0))), 60 | ); 61 | sim.process(10); 62 | assert_eq!( 63 | sim.pop_res(), 64 | Some(( 65 | node1, 66 | ExtOut::FeaturesEvent((), FeaturesEvent::Socket(socket::Event::RecvFrom(10000, node2, 10001, vec![1, 2, 3, 4].into(), 0))) 67 | )) 68 | ); 69 | } 70 | 71 | #[test] 72 | fn feature_socket_three_nodes() { 73 | // node1 <-> node2 <-> node3 74 | let node1 = 1; 75 | let node2 = 2; 76 | let node3 = 3; 77 | let mut sim = NetworkSimulator::<(), (), (), ()>::new(0); 78 | 79 | let _addr1 = sim.add_node(TestNode::new(node1, 1234, vec![])); 80 | let addr2 = sim.add_node(TestNode::new(node2, 1235, vec![])); 81 | let addr3 = sim.add_node(TestNode::new(node3, 1233, vec![])); 82 | 83 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(addr2, false)))); 84 | sim.control(node2, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(addr3, false)))); 85 | 86 | // For sync 87 | for _i in 0..4 { 88 | sim.process(500); 89 | } 90 | 91 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Socket(socket::Control::Bind(10000)))); 92 | sim.control(node3, ExtIn::FeaturesControl((), FeaturesControl::Socket(socket::Control::Bind(10001)))); 93 | sim.process(10); 94 | sim.control( 95 | node3, 96 | ExtIn::FeaturesControl((), FeaturesControl::Socket(socket::Control::SendTo(10001, node1, 10000, vec![1, 2, 3, 4].into(), 0))), 97 | ); 98 | sim.process(10); 99 | assert_eq!( 100 | sim.pop_res(), 101 | Some(( 102 | node1, 103 | ExtOut::FeaturesEvent((), FeaturesEvent::Socket(socket::Event::RecvFrom(10000, node3, 10001, vec![1, 2, 3, 4].into(), 0))) 104 | )) 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /packages/network/tests/service_manual2_discovery.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use atm0s_sdn_identity::ConnId; 4 | use atm0s_sdn_network::{ 5 | features::{neighbours, FeaturesControl, FeaturesEvent}, 6 | services::manual2_discovery::{AdvertiseTarget, Manual2DiscoveryServiceBuilder}, 7 | ExtIn, ExtOut, 8 | }; 9 | use atm0s_sdn_router::ServiceBroadcastLevel; 10 | 11 | use crate::simulator::{build_addr, NetworkSimulator, TestNode}; 12 | 13 | mod simulator; 14 | 15 | /// We create 3node with init connection: A -- B -- C 16 | /// A advertise it address to global then C should connect to A 17 | #[test] 18 | fn service_manual_discovery2_three_nodes() { 19 | let node1 = 1; 20 | let node2 = 2; 21 | let node3 = 3; 22 | let mut sim = NetworkSimulator::<(), (), (), ()>::new(0); 23 | 24 | let _addr1 = sim.add_node(TestNode::new( 25 | node1, 26 | 1234, 27 | vec![Arc::new(Manual2DiscoveryServiceBuilder::new( 28 | build_addr(node1), 29 | vec![AdvertiseTarget { 30 | service: 2.into(), 31 | level: ServiceBroadcastLevel::Global, 32 | }], 33 | 100, 34 | ))], 35 | )); 36 | let addr2 = sim.add_node(TestNode::new(node2, 1235, vec![Arc::new(Manual2DiscoveryServiceBuilder::new(build_addr(node2), vec![], 100))])); 37 | let _addr3 = sim.add_node(TestNode::new(node3, 1236, vec![Arc::new(Manual2DiscoveryServiceBuilder::new(build_addr(node3), vec![], 100))])); 38 | 39 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::Sub))); 40 | 41 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(addr2.clone(), false)))); 42 | sim.control(node3, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(addr2, false)))); 43 | 44 | // For sync 45 | for _i in 0..10 { 46 | sim.process(500); 47 | } 48 | 49 | let mut out = vec![]; 50 | while let Some((node, out_event)) = sim.pop_res() { 51 | out.push((node, out_event)); 52 | } 53 | 54 | out.sort_by(|a, b| a.0.cmp(&b.0)); 55 | 56 | assert_eq!( 57 | out, 58 | vec![ 59 | ( 60 | node1, 61 | ExtOut::FeaturesEvent((), FeaturesEvent::Neighbours(neighbours::Event::Connected(node2, ConnId::from_out(0, 1000)))) 62 | ), 63 | ( 64 | node1, 65 | ExtOut::FeaturesEvent((), FeaturesEvent::Neighbours(neighbours::Event::Connected(node3, ConnId::from_in(0, 3005)))) 66 | ), 67 | ] 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /packages/network/tests/service_manual_discovery.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use atm0s_sdn_identity::ConnId; 4 | use atm0s_sdn_network::{ 5 | features::{neighbours, FeaturesControl, FeaturesEvent}, 6 | services::manual_discovery::ManualDiscoveryServiceBuilder, 7 | ExtIn, ExtOut, 8 | }; 9 | 10 | use crate::simulator::{build_addr, NetworkSimulator, TestNode}; 11 | 12 | mod simulator; 13 | 14 | #[test] 15 | fn service_manual_discovery_three_nodes() { 16 | let node1 = 1; 17 | let node2 = 2; 18 | let node3 = 3; 19 | let mut sim = NetworkSimulator::<(), (), (), ()>::new(0); 20 | 21 | let _addr1 = sim.add_node(TestNode::new( 22 | node1, 23 | 1234, 24 | vec![Arc::new(ManualDiscoveryServiceBuilder::new(build_addr(node1), vec![], vec!["demo".to_string()]))], 25 | )); 26 | let addr2 = sim.add_node(TestNode::new(node2, 1235, vec![Arc::new(ManualDiscoveryServiceBuilder::new(build_addr(node2), vec![], vec![]))])); 27 | let addr3 = sim.add_node(TestNode::new( 28 | node3, 29 | 1236, 30 | vec![Arc::new(ManualDiscoveryServiceBuilder::new(build_addr(node3), vec!["demo".to_string()], vec![]))], 31 | )); 32 | 33 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::Sub))); 34 | 35 | sim.control(node1, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(addr2, false)))); 36 | sim.control(node2, ExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(addr3, false)))); 37 | 38 | // For sync 39 | for _i in 0..4 { 40 | sim.process(500); 41 | } 42 | 43 | assert_eq!( 44 | sim.pop_res(), 45 | Some(( 46 | node1, 47 | ExtOut::FeaturesEvent((), FeaturesEvent::Neighbours(neighbours::Event::Connected(node2, ConnId::from_out(0, 1000)))) 48 | )) 49 | ); 50 | assert_eq!( 51 | sim.pop_res(), 52 | Some(( 53 | node1, 54 | ExtOut::FeaturesEvent((), FeaturesEvent::Neighbours(neighbours::Event::Connected(node3, ConnId::from_out(0, 1005)))) 55 | )) 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /packages/runner/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.2.8](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.2.7...atm0s-sdn-v0.2.8) - 2025-03-02 10 | 11 | ### Fixed 12 | 13 | - dht-kv missed clear remote slots on unsub (#196) 14 | 15 | ## [0.2.7](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.2.6...atm0s-sdn-v0.2.7) - 2025-03-01 16 | 17 | ### Fixed 18 | 19 | - wrong worker event route with specific worker_id type (#194) 20 | 21 | ## [0.2.6](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.2.5...atm0s-sdn-v0.2.6) - 2025-02-27 22 | 23 | ### Fixed 24 | 25 | - router don't clear service router info after node disconnect (#192) 26 | 27 | ## [0.2.5](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.2.4...atm0s-sdn-v0.2.5) - 2025-02-08 28 | 29 | ### Added 30 | 31 | - discovery node by service broadcast (#188) 32 | 33 | ### Fixed 34 | 35 | - error reported by cargo deny (#191) 36 | - router with duplicate entry in hops (#190) 37 | - avoid send wrong hops with router-sync local services (#184) 38 | 39 | ## [0.2.4](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.2.3...atm0s-sdn-v0.2.4) - 2024-11-26 40 | 41 | ### Added 42 | 43 | - add router dump ([#182](https://github.com/8xFF/atm0s-sdn/pull/182)) 44 | 45 | ## [0.2.3](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.2.2...atm0s-sdn-v0.2.3) - 2024-11-08 46 | 47 | ### Added 48 | 49 | - task clean-up with new sans-io-runtime ([#179](https://github.com/8xFF/atm0s-sdn/pull/179)) 50 | 51 | ### Other 52 | 53 | - update Cargo.toml dependencies 54 | 55 | ## [0.2.2](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.2.1...atm0s-sdn-v0.2.2) - 2024-07-22 56 | 57 | ### Added 58 | - listen multiple UDP addresses ([#175](https://github.com/8xFF/atm0s-sdn/pull/175)) 59 | 60 | ## [0.2.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.2.0...atm0s-sdn-v0.2.1) - 2024-07-22 61 | 62 | ### Fixed 63 | - switch to single bind for resolve connect error with multi-addresses machine ([#172](https://github.com/8xFF/atm0s-sdn/pull/172)) 64 | 65 | ### Other 66 | - clippy fixes ([#167](https://github.com/8xFF/atm0s-sdn/pull/167)) 67 | 68 | ## [0.1.10](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.1.9...atm0s-sdn-v0.1.10) - 2024-02-21 69 | 70 | ### Other 71 | - updated the following local packages: atm0s-sdn-virtual-socket 72 | 73 | ## [0.1.9](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.1.8...atm0s-sdn-v0.1.9) - 2024-01-31 74 | 75 | ### Fixed 76 | - key value service resub failed if run before unsub ack and subscribe slot is removed ([#123](https://github.com/8xFF/atm0s-sdn/pull/123)) 77 | 78 | ### Other 79 | - update rust crate snow to 0.9.6 ([#121](https://github.com/8xFF/atm0s-sdn/pull/121)) 80 | 81 | ## [0.1.8](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.1.7...atm0s-sdn-v0.1.8) - 2024-01-24 82 | 83 | ### Fixed 84 | - *(deps)* update rust crate snow to 0.9.5 ([#120](https://github.com/8xFF/atm0s-sdn/pull/120)) 85 | - *(deps)* update rust crate local-ip-address to 0.5.7 ([#117](https://github.com/8xFF/atm0s-sdn/pull/117)) 86 | 87 | ### Other 88 | - update Cargo.toml dependencies 89 | 90 | ## [0.1.7](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.1.6...atm0s-sdn-v0.1.7) - 2024-01-09 91 | 92 | ### Added 93 | - node_alias service ([#110](https://github.com/8xFF/atm0s-sdn/pull/110)) 94 | - virtual udp socket and quinn for tunneling between two nodes ([#107](https://github.com/8xFF/atm0s-sdn/pull/107)) 95 | 96 | ### Fixed 97 | - send node-alias response more faster ([#111](https://github.com/8xFF/atm0s-sdn/pull/111)) 98 | 99 | ## [0.1.6](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.1.5...atm0s-sdn-v0.1.6) - 2023-12-28 100 | 101 | ### Fixed 102 | - fix key-value fired duplicated events in case of multi subscribers ([#104](https://github.com/8xFF/atm0s-sdn/pull/104)) 103 | 104 | ## [0.1.5](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.1.4...atm0s-sdn-v0.1.5) - 2023-12-27 105 | 106 | ### Added 107 | - secure with static key and noise protocol ([#101](https://github.com/8xFF/atm0s-sdn/pull/101)) 108 | - node multi addrs ([#98](https://github.com/8xFF/atm0s-sdn/pull/98)) 109 | 110 | ### Other 111 | - update Cargo.toml dependencies 112 | 113 | ## [0.1.4](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.1.3...atm0s-sdn-v0.1.4) - 2023-12-12 114 | 115 | ### Other 116 | - update dependencies 117 | 118 | ## [0.1.3](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.1.2...atm0s-sdn-v0.1.3) - 2023-12-11 119 | 120 | ### Other 121 | - move local deps out of workspace Cargo.toml ([#92](https://github.com/8xFF/atm0s-sdn/pull/92)) 122 | 123 | ## [0.1.2](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.1.1...atm0s-sdn-v0.1.2) - 2023-12-11 124 | 125 | ### Added 126 | - rpc service and fi ([#87](https://github.com/8xFF/atm0s-sdn/pull/87)) 127 | - manual-discovery with node tags ([#84](https://github.com/8xFF/atm0s-sdn/pull/84)) 128 | 129 | ### Fixed 130 | - missing register service ([#85](https://github.com/8xFF/atm0s-sdn/pull/85)) 131 | - *(deps)* update rust crate url to 2.5.0 ([#74](https://github.com/8xFF/atm0s-sdn/pull/74)) 132 | - *(deps)* update rust crate percent-encoding to 2.3.1 ([#73](https://github.com/8xFF/atm0s-sdn/pull/73)) 133 | - *(deps)* update rust crate data-encoding to 2.5 ([#72](https://github.com/8xFF/atm0s-sdn/pull/72)) 134 | - router sync service handler missing pop actions ([#82](https://github.com/8xFF/atm0s-sdn/pull/82)) 135 | 136 | ## [0.1.1](https://github.com/8xFF/atm0s-sdn/compare/atm0s-sdn-v0.1.0...atm0s-sdn-v0.1.1) - 2023-11-23 137 | 138 | ### Fixed 139 | - *(deps)* update rust crate serde to 1.0.193 ([#71](https://github.com/8xFF/atm0s-sdn/pull/71)) 140 | - key-value event not fired with second subs ([#79](https://github.com/8xFF/atm0s-sdn/pull/79)) 141 | -------------------------------------------------------------------------------- /packages/runner/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atm0s-sdn" 3 | version = "0.2.8" 4 | edition = "2021" 5 | repository = "https://github.com/8xFF/atm0s-sdn" 6 | description = "Decentralized Ultra-Low-Latency Software Defined Network" 7 | license = "MIT" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | thiserror = { workspace = true } 13 | sans-io-runtime = { workspace = true, features = ["poll-backend", "polling-backend", "udp"] } 14 | atm0s-sdn-identity = { path = "../core/identity", version = "0.3.2" } 15 | atm0s-sdn-router = { path = "../core/router", version = "0.3.0" } 16 | atm0s-sdn-network = { path = "../network", version = "0.7.1" } 17 | 18 | convert-enum = { workspace = true } 19 | num_enum = { workspace = true } 20 | rand.workspace = true 21 | parking_lot.workspace = true 22 | log.workspace = true 23 | serde.workspace = true 24 | bincode.workspace = true 25 | 26 | [dev-dependencies] 27 | env_logger = { workspace = true } 28 | signal-hook = "0.3" 29 | clap.workspace = true 30 | local-ip-address = "0.6" 31 | 32 | [features] 33 | default = [] 34 | vpn = ["sans-io-runtime/tun-tap", "atm0s-sdn-network/vpn"] 35 | 36 | [[example]] 37 | name = "simple_node" 38 | # features = ["vpn"] 39 | 40 | [[example]] 41 | name = "simple_kv" 42 | # features = [] 43 | -------------------------------------------------------------------------------- /packages/runner/docs/architecture.excalidraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8xFF/atm0s-sdn/63ae52d3e14e1963c310c7872ca6e6cdd6013a5f/packages/runner/docs/architecture.excalidraw.png -------------------------------------------------------------------------------- /packages/runner/examples/simple_kv.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_identity::{NodeAddr, NodeId}; 2 | use atm0s_sdn_network::{ 3 | features::{ 4 | dht_kv::{Control, Key, Map, MapControl}, 5 | FeaturesControl, 6 | }, 7 | secure::StaticKeyAuthorization, 8 | services::visualization, 9 | }; 10 | use clap::{Parser, ValueEnum}; 11 | use sans_io_runtime::backend::{PollBackend, PollingBackend}; 12 | use std::{ 13 | net::SocketAddr, 14 | sync::{ 15 | atomic::{AtomicBool, Ordering}, 16 | Arc, 17 | }, 18 | time::Duration, 19 | }; 20 | 21 | use atm0s_sdn::{features::neighbours, SdnBuilder, SdnExtIn, SdnOwner}; 22 | 23 | #[derive(Debug, Clone, ValueEnum)] 24 | enum BackendType { 25 | Poll, 26 | Polling, 27 | } 28 | 29 | /// Simple program to running a node 30 | #[derive(Parser, Debug)] 31 | #[command(version, about, long_about = None)] 32 | struct Args { 33 | /// Node Id 34 | #[arg(short, long)] 35 | node_id: NodeId, 36 | 37 | /// Listen address 38 | #[arg(short, long)] 39 | bind_addr: SocketAddr, 40 | 41 | /// Address of node we should connect to 42 | #[arg(short, long)] 43 | seeds: Vec, 44 | 45 | /// Password for the network 46 | #[arg(short, long, default_value = "password")] 47 | password: String, 48 | 49 | /// Backend type 50 | #[arg(short, long, default_value = "polling")] 51 | backend: BackendType, 52 | 53 | /// Workers 54 | #[arg(short, long, default_value_t = 1)] 55 | workers: usize, 56 | 57 | /// Kv map id 58 | #[arg(long, default_value_t = 1)] 59 | kv_map: u64, 60 | 61 | /// This side will subscribe 62 | #[arg(long)] 63 | kv_subscribe: bool, 64 | 65 | /// This side will set 66 | #[arg(long)] 67 | kv_set: bool, 68 | } 69 | 70 | type UserInfo = u32; 71 | type SC = visualization::Control; 72 | type SE = visualization::Event; 73 | type TC = (); 74 | type TW = (); 75 | 76 | fn main() { 77 | let term = Arc::new(AtomicBool::new(false)); 78 | signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&term)).expect("Should register hook"); 79 | let mut shutdown_wait = 0; 80 | let args = Args::parse(); 81 | env_logger::builder().format_timestamp_millis().init(); 82 | let mut builder = SdnBuilder::<(), SC, SE, TC, TW, UserInfo>::new(args.node_id, &[args.bind_addr], vec![]); 83 | builder.set_authorization(StaticKeyAuthorization::new(&args.password)); 84 | 85 | let mut controller = match args.backend { 86 | BackendType::Poll => builder.build::>(args.workers, args.node_id), 87 | BackendType::Polling => builder.build::>(args.workers, args.node_id), 88 | }; 89 | 90 | for seed in args.seeds { 91 | controller.send_to(0, SdnExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(seed, true)))); 92 | } 93 | 94 | if args.kv_subscribe { 95 | controller.send_to(0, SdnExtIn::FeaturesControl((), FeaturesControl::DhtKv(Control::MapCmd(Map(args.kv_map), MapControl::Sub)))); 96 | } 97 | 98 | let mut i: u32 = 0; 99 | while controller.process().is_some() { 100 | if term.load(Ordering::Relaxed) { 101 | if shutdown_wait == 200 { 102 | log::warn!("Force shutdown"); 103 | break; 104 | } 105 | shutdown_wait += 1; 106 | controller.shutdown(); 107 | } 108 | std::thread::sleep(Duration::from_millis(1)); 109 | i += 1; 110 | 111 | if i % 1000 == 0 && args.kv_set { 112 | let data = i.to_be_bytes().to_vec(); 113 | log::info!("Set key: {:?}", data); 114 | controller.send_to( 115 | 0, 116 | SdnExtIn::FeaturesControl((), FeaturesControl::DhtKv(Control::MapCmd(Map(args.kv_map), MapControl::Set(Key(200), data)))), 117 | ); 118 | } 119 | while let Some(out) = controller.pop_event() { 120 | log::info!("Got event: {:?}", out); 121 | } 122 | } 123 | 124 | log::info!("Server shutdown"); 125 | } 126 | -------------------------------------------------------------------------------- /packages/runner/examples/simple_node.rs: -------------------------------------------------------------------------------- 1 | use atm0s_sdn_identity::{NodeAddr, NodeId}; 2 | use atm0s_sdn_network::{secure::StaticKeyAuthorization, services::visualization}; 3 | use clap::{Parser, ValueEnum}; 4 | use sans_io_runtime::backend::{PollBackend, PollingBackend}; 5 | use std::{ 6 | net::SocketAddr, 7 | sync::{ 8 | atomic::{AtomicBool, Ordering}, 9 | Arc, 10 | }, 11 | time::Duration, 12 | }; 13 | 14 | use atm0s_sdn::{ 15 | features::{neighbours, FeaturesControl}, 16 | SdnBuilder, SdnExtIn, SdnOwner, 17 | }; 18 | 19 | #[derive(Debug, Clone, ValueEnum)] 20 | enum BackendType { 21 | Poll, 22 | Polling, 23 | } 24 | 25 | /// Simple program to running a node 26 | #[derive(Parser, Debug)] 27 | #[command(version, about, long_about = None)] 28 | struct Args { 29 | /// Node Id 30 | #[arg(short, long)] 31 | node_id: NodeId, 32 | 33 | /// Listen address 34 | #[arg(short, long)] 35 | bind_addr: SocketAddr, 36 | 37 | /// Address of node we should connect to 38 | #[arg(short, long)] 39 | seeds: Vec, 40 | 41 | /// Password for the network 42 | #[arg(short, long, default_value = "password")] 43 | password: String, 44 | 45 | /// Backend type 46 | #[arg(short, long, default_value = "polling")] 47 | backend: BackendType, 48 | 49 | /// Backend type 50 | #[arg(short, long)] 51 | vpn: bool, 52 | 53 | /// Workers 54 | #[arg(long, default_value_t = 2)] 55 | workers: usize, 56 | 57 | /// Custom IP 58 | #[arg(long)] 59 | custom_addrs: Vec, 60 | 61 | /// Local tags 62 | #[arg(long)] 63 | local_tags: Vec, 64 | 65 | /// Connect tags 66 | #[arg(long)] 67 | connect_tags: Vec, 68 | } 69 | 70 | type UserInfo = u32; 71 | type SC = visualization::Control; 72 | type SE = visualization::Event; 73 | type TC = (); 74 | type TW = (); 75 | 76 | fn main() { 77 | let term = Arc::new(AtomicBool::new(false)); 78 | signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&term)).expect("Should register hook"); 79 | let mut shutdown_wait = 0; 80 | let args = Args::parse(); 81 | env_logger::builder().format_timestamp_millis().init(); 82 | let mut builder = SdnBuilder::<(), SC, SE, TC, TW, UserInfo>::new(args.node_id, &[args.bind_addr], args.custom_addrs); 83 | builder.set_authorization(StaticKeyAuthorization::new(&args.password)); 84 | 85 | builder.set_manual_discovery(args.local_tags, args.connect_tags); 86 | 87 | #[cfg(feature = "vpn")] 88 | if args.vpn { 89 | builder.enable_vpn(); 90 | } 91 | 92 | let mut controller = match args.backend { 93 | BackendType::Poll => builder.build::>(args.workers, args.node_id), 94 | BackendType::Polling => builder.build::>(args.workers, args.node_id), 95 | }; 96 | 97 | for seed in args.seeds { 98 | controller.send_to(0, SdnExtIn::FeaturesControl((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(seed, true)))); 99 | } 100 | 101 | while controller.process().is_some() { 102 | if term.load(Ordering::Relaxed) { 103 | if shutdown_wait == 200 { 104 | log::warn!("Force shutdown"); 105 | break; 106 | } 107 | shutdown_wait += 1; 108 | controller.shutdown(); 109 | } 110 | std::thread::sleep(Duration::from_millis(10)); 111 | } 112 | 113 | log::info!("Server shutdown"); 114 | } 115 | -------------------------------------------------------------------------------- /packages/runner/run-example-debug.sh: -------------------------------------------------------------------------------- 1 | export RUST_LOG=info 2 | cargo build --features vpn --example simple_node 3 | sudo --preserve-env=RUST_LOG ../../target/debug/examples/simple_node $@ 4 | -------------------------------------------------------------------------------- /packages/runner/run-example-release.sh: -------------------------------------------------------------------------------- 1 | export RUST_LOG=info 2 | cargo build --release --features vpn --example simple_node 3 | sudo --preserve-env=RUST_LOG ../../target/release/examples/simple_node $@ 4 | -------------------------------------------------------------------------------- /packages/runner/src/history.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{HashMap, VecDeque}, 3 | sync::atomic::AtomicU64, 4 | }; 5 | 6 | use atm0s_sdn_identity::NodeId; 7 | use atm0s_sdn_router::shadow::ShadowRouterHistory; 8 | use parking_lot::Mutex; 9 | 10 | const HISTORY_TIMEOUT_MS: u64 = 2000; 11 | 12 | #[derive(Debug, Default)] 13 | pub struct DataWorkerHistory { 14 | now_ms: AtomicU64, 15 | #[allow(clippy::type_complexity)] 16 | queue: Mutex, u8, u16))>>, 17 | #[allow(clippy::type_complexity)] 18 | map: Mutex, u8, u16), bool>>, 19 | } 20 | 21 | impl ShadowRouterHistory for DataWorkerHistory { 22 | fn already_received_broadcast(&self, from: Option, service: u8, seq: u16) -> bool { 23 | let mut map = self.map.lock(); 24 | let mut queue = self.queue.lock(); 25 | let now_ms = self.now_ms.load(std::sync::atomic::Ordering::Relaxed); 26 | if map.contains_key(&(from, service, seq)) { 27 | return true; 28 | } 29 | 30 | map.insert((from, service, seq), true); 31 | queue.push_back((now_ms, (from, service, seq))); 32 | if queue.len() > 10000 { 33 | let (_ts, pair) = queue.pop_front().expect("queue should not empty"); 34 | map.remove(&pair); 35 | } 36 | false 37 | } 38 | 39 | fn set_ts(&self, now_ms: u64) { 40 | self.now_ms.store(now_ms, std::sync::atomic::Ordering::Relaxed); 41 | let mut map = self.map.lock(); 42 | let mut queue = self.queue.lock(); 43 | 44 | while let Some((time, key)) = queue.front() { 45 | if now_ms >= *time + HISTORY_TIMEOUT_MS { 46 | map.remove(key); 47 | queue.pop_front(); 48 | } else { 49 | break; 50 | } 51 | } 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use atm0s_sdn_router::shadow::ShadowRouterHistory; 58 | 59 | use crate::history::HISTORY_TIMEOUT_MS; 60 | 61 | use super::DataWorkerHistory; 62 | 63 | #[test] 64 | fn simple_work() { 65 | let history = DataWorkerHistory::default(); 66 | 67 | assert_eq!(history.already_received_broadcast(Some(1), 1, 1), false); 68 | assert_eq!(history.already_received_broadcast(Some(1), 1, 1), true); 69 | 70 | //after timeout 71 | history.set_ts(HISTORY_TIMEOUT_MS); 72 | assert_eq!(history.already_received_broadcast(Some(1), 1, 1), false); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/runner/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::bool_assert_comparison)] 2 | 3 | use std::{fmt::Debug, hash::Hash}; 4 | 5 | pub use atm0s_sdn_identity::{ConnDirection, ConnId, NodeAddr, NodeAddrBuilder, NodeId, NodeIdType, Protocol}; 6 | pub use atm0s_sdn_network::controller_plane::ControllerPlaneCfg; 7 | pub use atm0s_sdn_network::data_plane::DataPlaneCfg; 8 | use atm0s_sdn_network::features::FeaturesControl; 9 | pub use atm0s_sdn_network::{ 10 | base, features, secure, services, 11 | worker::{SdnWorker, SdnWorkerBusEvent, SdnWorkerCfg, SdnWorkerInput, SdnWorkerOutput}, 12 | }; 13 | pub use atm0s_sdn_network::{ 14 | base::ServiceId, 15 | data_plane::{NetInput, NetOutput}, 16 | }; 17 | pub use atm0s_sdn_router::{shadow::ShadowRouterHistory, RouteRule, ServiceBroadcastLevel}; 18 | pub use sans_io_runtime; 19 | 20 | mod builder; 21 | mod history; 22 | mod time; 23 | mod worker_inner; 24 | 25 | pub use builder::{generate_node_addr, SdnBuilder}; 26 | pub use history::DataWorkerHistory; 27 | pub use time::{TimePivot, TimeTicker}; 28 | pub use worker_inner::{SdnChannel, SdnController, SdnEvent, SdnExtIn, SdnExtOut, SdnOwner}; 29 | 30 | pub trait SdnControllerUtils { 31 | fn feature_control(&mut self, userdata: UserData, cmd: FeaturesControl); 32 | fn service_control(&mut self, service: ServiceId, userdata: UserData, cmd: SC); 33 | } 34 | 35 | impl< 36 | UserData: 'static + Send + Sync + Copy + Eq + Hash + Debug, 37 | SC: 'static + Send + Sync + Clone, 38 | SE: 'static + Send + Sync + Clone, 39 | TC: 'static + Send + Sync + Clone, 40 | TW: 'static + Send + Sync + Clone, 41 | > SdnControllerUtils for SdnController 42 | { 43 | fn feature_control(&mut self, userdata: UserData, cmd: FeaturesControl) { 44 | self.send_to(0, SdnExtIn::FeaturesControl(userdata, cmd)); 45 | } 46 | 47 | fn service_control(&mut self, service: ServiceId, userdata: UserData, cmd: SC) { 48 | self.send_to(0, SdnExtIn::ServicesControl(service, userdata, cmd)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/runner/src/time.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Instant, SystemTime, UNIX_EPOCH}; 2 | 3 | pub struct TimePivot { 4 | instant: Instant, 5 | time_us: u64, 6 | } 7 | 8 | impl TimePivot { 9 | pub fn build() -> Self { 10 | let start = SystemTime::now(); 11 | let since_the_epoch = start.duration_since(UNIX_EPOCH).expect("Time went backwards"); 12 | 13 | Self { 14 | instant: Instant::now(), 15 | time_us: since_the_epoch.as_micros() as u64, 16 | } 17 | } 18 | 19 | pub fn started_ms(&self) -> u64 { 20 | self.time_us / 1000 21 | } 22 | 23 | pub fn timestamp_ms(&self, now: Instant) -> u64 { 24 | self.time_us / 1000 + now.duration_since(self.instant).as_millis() as u64 25 | } 26 | 27 | pub fn started_us(&self) -> u64 { 28 | self.time_us 29 | } 30 | 31 | pub fn timestamp_us(&self, now: Instant) -> u64 { 32 | self.time_us + now.duration_since(self.instant).as_micros() as u64 33 | } 34 | } 35 | 36 | pub struct TimeTicker { 37 | last_tick: Instant, 38 | tick_ms: u64, 39 | } 40 | 41 | impl TimeTicker { 42 | pub fn build(tick_ms: u64) -> Self { 43 | Self { last_tick: Instant::now(), tick_ms } 44 | } 45 | 46 | pub fn tick(&mut self, now: Instant) -> bool { 47 | if now.duration_since(self.last_tick).as_millis() as u64 >= self.tick_ms { 48 | self.last_tick = now; 49 | true 50 | } else { 51 | false 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/runner/tests/feature_dht_kv.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | net::{Ipv4Addr, SocketAddr, SocketAddrV4}, 3 | time::Duration, 4 | }; 5 | 6 | use atm0s_sdn::{ 7 | features::{ 8 | dht_kv::{self, MapControl, MapEvent}, 9 | neighbours, FeaturesControl, FeaturesEvent, 10 | }, 11 | secure::StaticKeyAuthorization, 12 | services::visualization, 13 | NodeAddr, NodeId, SdnBuilder, SdnController, SdnControllerUtils, SdnExtOut, SdnOwner, 14 | }; 15 | use sans_io_runtime::backend::PollingBackend; 16 | 17 | type UserInfo = u32; 18 | type SC = visualization::Control; 19 | type SE = visualization::Event; 20 | type TC = (); 21 | type TW = (); 22 | 23 | fn process(nodes: &mut [&mut SdnController<(), SC, SE, TC, TW>], timeout_ms: u64) { 24 | let mut count = 0; 25 | while count < timeout_ms / 10 { 26 | std::thread::sleep(Duration::from_millis(10)); 27 | count += 1; 28 | for node in nodes.iter_mut() { 29 | if node.process().is_none() { 30 | panic!("Node is shutdown"); 31 | } 32 | } 33 | } 34 | } 35 | 36 | fn expect_event(node: &mut SdnController<(), SC, SE, TC, TW>, expected: dht_kv::Event) { 37 | match node.pop_event() { 38 | Some(SdnExtOut::FeaturesEvent((), FeaturesEvent::DhtKv(event))) => { 39 | assert_eq!(event, expected) 40 | } 41 | Some(event) => { 42 | panic!("Unexpected event: {:?}", event) 43 | } 44 | None => { 45 | panic!("No event") 46 | } 47 | } 48 | } 49 | 50 | fn build_node(node_id: NodeId, udp_port: u16) -> (SdnController<(), SC, SE, TC, TW>, NodeAddr) { 51 | let addrs = [ 52 | SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, udp_port)), 53 | SocketAddr::new(local_ip_address::local_ip().expect("Should have ip-v4"), udp_port), 54 | ]; 55 | let mut builder = SdnBuilder::<(), SC, SE, TC, TW, UserInfo>::new(node_id, &addrs, vec![]); 56 | builder.set_authorization(StaticKeyAuthorization::new("password-here")); 57 | let node_addr = builder.node_addr(); 58 | let node_info = node_id; 59 | let node = builder.build::>(2, node_info); 60 | (node, node_addr) 61 | } 62 | 63 | #[test] 64 | fn test_single_node() { 65 | let (mut node, _node_addr) = build_node(1, 10000); 66 | std::thread::sleep(Duration::from_millis(100)); 67 | node.feature_control((), FeaturesControl::DhtKv(dht_kv::Control::MapCmd(1000.into(), MapControl::Sub))); 68 | process(&mut [&mut node], 100); 69 | expect_event(&mut node, dht_kv::Event::MapEvent(1000.into(), MapEvent::OnRelaySelected(1))); 70 | 71 | node.feature_control((), FeaturesControl::DhtKv(dht_kv::Control::MapCmd(1000.into(), MapControl::Set(2000.into(), vec![1, 2, 3])))); 72 | process(&mut [&mut node], 100); 73 | expect_event(&mut node, dht_kv::Event::MapEvent(1000.into(), MapEvent::OnSet(2000.into(), 1, vec![1, 2, 3]))); 74 | } 75 | 76 | #[test] 77 | fn test_two_nodes() { 78 | let node1_id = 1; 79 | let node2_id = 2; 80 | let (mut node1, node_addr1) = build_node(node1_id, 11000); 81 | let (mut node2, _node_addr2) = build_node(node2_id, 11001); 82 | 83 | node2.feature_control((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(node_addr1, false))); 84 | 85 | process(&mut [&mut node1, &mut node2], 100); 86 | log::info!("sending map cmd Sub"); 87 | node1.feature_control((), FeaturesControl::DhtKv(dht_kv::Control::MapCmd(1000.into(), MapControl::Sub))); 88 | process(&mut [&mut node1, &mut node2], 100); 89 | expect_event(&mut node1, dht_kv::Event::MapEvent(1000.into(), MapEvent::OnRelaySelected(node1_id))); 90 | 91 | node2.feature_control((), FeaturesControl::DhtKv(dht_kv::Control::MapCmd(1000.into(), MapControl::Set(2000.into(), vec![1, 2, 3])))); 92 | process(&mut [&mut node1, &mut node2], 100); 93 | 94 | expect_event(&mut node1, dht_kv::Event::MapEvent(1000.into(), MapEvent::OnSet(2000.into(), node2_id, vec![1, 2, 3]))); 95 | } 96 | 97 | #[test] 98 | fn test_three_nodes() { 99 | let node1_id = 1; 100 | let node2_id = 2; 101 | let node3_id = 3; 102 | let (mut node1, node_addr1) = build_node(node1_id, 12000); 103 | let (mut node2, node_addr2) = build_node(node2_id, 12001); 104 | let (mut node3, _node_addr3) = build_node(node3_id, 12002); 105 | 106 | node2.feature_control((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(node_addr1, false))); 107 | node3.feature_control((), FeaturesControl::Neighbours(neighbours::Control::ConnectTo(node_addr2, false))); 108 | 109 | process(&mut [&mut node1, &mut node2, &mut node3], 100); 110 | log::info!("sending map cmd Sub"); 111 | node2.feature_control((), FeaturesControl::DhtKv(dht_kv::Control::MapCmd(1000.into(), MapControl::Sub))); 112 | process(&mut [&mut node1, &mut node2, &mut node3], 100); 113 | 114 | expect_event(&mut node2, dht_kv::Event::MapEvent(1000.into(), MapEvent::OnRelaySelected(node1_id))); 115 | 116 | node3.feature_control((), FeaturesControl::DhtKv(dht_kv::Control::MapCmd(1000.into(), MapControl::Set(2000.into(), vec![1, 2, 3])))); 117 | process(&mut [&mut node1, &mut node2, &mut node3], 100); 118 | 119 | expect_event(&mut node2, dht_kv::Event::MapEvent(1000.into(), MapEvent::OnSet(2000.into(), node3_id, vec![1, 2, 3]))); 120 | } 121 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /run-test.sh: -------------------------------------------------------------------------------- 1 | cargo test -- --test-threads=1 2 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.83.0" 3 | components = ["rustfmt", "clippy"] 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 200 2 | single_line_if_else_max_width = 20 3 | short_array_element_width_threshold = 20 4 | --------------------------------------------------------------------------------