Merge remote-tracking branch 'origin/develop' into feature/nym-node-api-tests

This commit is contained in:
benedettadavico
2023-11-06 16:46:31 +01:00
364 changed files with 24816 additions and 2298 deletions
+2 -26
View File
@@ -1,4 +1,4 @@
name: CD docs
name: cd-docs
on:
workflow_dispatch:
@@ -26,7 +26,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --release --all
args: --workspace --release
- name: Install mdbook
run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook)
- name: Install mdbook plugins
@@ -39,30 +39,6 @@ jobs:
run: cd documentation && ./build_all_to_dist.sh
continue-on-error: false
- name: Deploy branch master to dev
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CD_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "dist/docs/"
REMOTE_HOST: ${{ secrets.CD_WWW_REMOTE_HOST_DEV }}
REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET }}/
EXCLUDE: "/node_modules/"
- name: Deploy branch master to prod
if: github.ref == 'refs/heads/master'
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CD_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "dist/docs/"
REMOTE_HOST: ${{ secrets.CD_WWW_REMOTE_HOST_PROD }}
REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET }}/
EXCLUDE: "/node_modules/"
- name: Post process
run: cd documentation && ./post_process.sh
continue-on-error: false
@@ -1,4 +1,4 @@
name: Run config checks on all binaries
name: ci-binary-config-checker
on:
workflow_dispatch:
@@ -35,14 +35,14 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: 1.69.0
toolchain: stable
target: wasm32-unknown-unknown
override: true
- name: Install wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '112'
version: '114'
- name: Build release contracts
run: make contracts
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --release --all
args: --workspace --release
- name: Install mdbook
run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.35" mdbook)
- name: Install mdbook plugins
+3 -3
View File
@@ -1,4 +1,4 @@
name: CI for Nym API Tests
name: ci-nym-api-tests
on:
workflow_dispatch:
@@ -16,10 +16,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install npm
run: npm install
- name: Node v18
uses: actions/setup-node@v3
with:
@@ -33,6 +33,12 @@ jobs:
override: true
components: rustfmt, clippy
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-connect/desktop/Cargo.toml --all -- --check
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
@@ -45,12 +51,6 @@ jobs:
command: test
args: --manifest-path nym-connect/desktop/Cargo.toml --workspace
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-connect/desktop/Cargo.toml --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
continue-on-error: true
+40
View File
@@ -0,0 +1,40 @@
name: ci-nym-vpn-ui-js
on:
workflow_dispatch:
pull_request:
paths:
- 'nym-vpn/ui/src/**'
- 'nym-vpn/ui/package.json'
- 'nym-vpn/ui/index.html'
jobs:
check:
runs-on: custom-linux
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 18
- name: Install Yarn
run: npm install -g yarn
- name: Install dependencies
working-directory: nym-vpn/ui
run: yarn
- name: Type-check
working-directory: nym-vpn/ui
run: yarn typecheck
- name: Check lint
working-directory: nym-vpn/ui
run: yarn lint
- name: Check formatting
working-directory: nym-vpn/ui
run: yarn fmt:check
# - name: Run tests
# working-directory: nym-vpn/ui
# run: yarn test
- name: Check build
working-directory: nym-vpn/ui
run: yarn build
+63
View File
@@ -0,0 +1,63 @@
name: ci-nym-vpn-ui-rust
on:
workflow_dispatch:
pull_request:
paths:
- 'nym-vpn/ui/src-tauri/**'
jobs:
build:
runs-on: custom-linux
env:
CARGO_TERM_COLOR: always
CARGOTOML_PATH: ./nym-vpn/ui/src-tauri/Cargo.toml
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools libayatana-appindicator3-dev
continue-on-error: true
- name: Checkout
uses: actions/checkout@v4
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Prepare build
run: mkdir nym-vpn/ui/dist
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path ${{ env.CARGOTOML_PATH }} --lib --features custom-protocol
# - name: Run all tests
# uses: actions-rs/cargo@v1
# with:
# command: test
# args: --manifest-path ${{ env.CARGOTOML_PATH }}
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all -- --check
- name: Annotate with clippy checks
uses: actions-rs/clippy-check@v1
continue-on-error: true
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all-features
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all-features --all-targets -- -D warnings
+33 -8
View File
@@ -4,26 +4,26 @@ on:
workflow_dispatch:
schedule:
- cron: '14 1 * * *'
jobs:
build:
strategy:
fail-fast: false
matrix:
rust: [stable, beta]
os: [custom-linux, windows10, custom-runner-mac-m1]
os: [ubuntu-20.04, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
continue-on-error: true
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler
continue-on-error: true
if: matrix.os == 'custom-linux'
- name: Check out repository code
uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler
if: matrix.os == 'ubuntu-20.04'
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
@@ -32,6 +32,12 @@ jobs:
override: true
components: rustfmt, clippy
- name: Install Protoc
uses: arduino/setup-protoc@v2
if: matrix.os == 'macos-latest' || matrix.os == 'windows-latest'
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Check formatting
uses: actions-rs/cargo@v1
with:
@@ -42,13 +48,27 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
args: --release --workspace
- name: Build examples
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --examples
args: --release --workspace --examples
# To avoid running out of disk space, skip generating debug symbols
- name: Set debug to false (unix)
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-latest'
run: |
sed -i.bak 's/\[profile.dev\]/\[profile.dev\]\ndebug = false/' Cargo.toml
git diff
- name: Set debug to false (win)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
(Get-Content Cargo.toml) -replace '\[profile.dev\]', "`$&`ndebug = false" | Set-Content Cargo.toml
git diff
- name: Run unit tests
uses: actions-rs/cargo@v1
@@ -62,6 +82,11 @@ jobs:
command: test
args: --workspace -- --ignored
- name: Clean
uses: actions-rs/cargo@v1
with:
command: clean
- name: Clippy
uses: actions-rs/cargo@v1
with:
@@ -0,0 +1,92 @@
name: nightly-nym-connect-desktop-build
on:
workflow_dispatch:
schedule:
- cron: '14 1 * * *'
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
MANIFEST_PATH: --manifest-path nym-connect/desktop/Cargo.toml
continue-on-error: true
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
if: matrix.os == 'ubuntu-20.04'
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: ${{ env.MANIFEST_PATH }} --all -- --check
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: ${{ env.MANIFEST_PATH }} --release --workspace
- name: Unit tests
uses: actions-rs/cargo@v1
with:
command: test
args: ${{ env.MANIFEST_PATH }} --workspace
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: ${{ env.MANIFEST_PATH }} --workspace --all-targets -- -D warnings
notification:
needs: build
runs-on: custom-linux
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v3
- name: install npm
uses: actions/setup-node@v3
if: env.WORKFLOW_CONCLUSION == 'failure'
with:
node-version: 18
- name: Matrix - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "nym-connect-desktop-nightly-build"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+8 -22
View File
@@ -5,27 +5,24 @@ on:
schedule:
- cron: '14 1 * * *'
defaults:
run:
working-directory: nym-wallet
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [custom-ubuntu-20.04, macos-latest, windows10]
os: [ubuntu-20.04, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
MANIFEST_PATH: --manifest-path nym-wallet/Cargo.toml
continue-on-error: true
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
if: matrix.os == 'custom-ubuntu-20.04'
run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
if: matrix.os == 'ubuntu-20.04'
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
@@ -35,40 +32,29 @@ jobs:
override: true
components: rustfmt, clippy
- name: Install Protoc
uses: arduino/setup-protoc@v2
if: matrix.os == 'macos-latest'
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
args: ${{ env.MANIFEST_PATH }} --all -- --check
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
args: ${{ env.MANIFEST_PATH }} --release --workspace
- name: Unit tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace
- name: Annotate with clippy warnings
uses: actions-rs/clippy-check@v1
continue-on-error: true
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace
args: ${{ env.MANIFEST_PATH }} --workspace
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace --all-targets -- -D warnings
args: ${{ env.MANIFEST_PATH }} --workspace --all-targets -- -D warnings
notification:
needs: build
+2 -2
View File
@@ -14,13 +14,13 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: 1.69.0
toolchain: stable
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- name: Install wasm-opt
run: cargo install --version 0.112.0 wasm-opt
run: cargo install --version 0.114.0 wasm-opt
- name: Build release contracts
run: make contracts
@@ -39,6 +39,7 @@ jobs:
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# create variables
@@ -73,6 +74,7 @@ jobs:
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_IDENTITY_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
+11 -1
View File
@@ -12,7 +12,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18
registry-url: 'https://registry.npmjs.org'
registry-url: "https://registry.npmjs.org"
- name: Setup yarn
run: npm install -g yarn
@@ -28,6 +28,16 @@ jobs:
- name: Install wasm-opt
run: cargo install wasm-opt
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.20"
- name: Install TinyGo
uses: acifani/setup-tinygo@v1
with:
tinygo-version: "0.27.0"
- name: Install dependencies
run: yarn
+22 -3
View File
@@ -3,9 +3,28 @@
Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
- add client registry to Gateway ([#3955])
- add HTTP API to Gateway ([#3955])
- add `/client/<pub-key>`, `clients` and `register` routes to the gateway ([#3955])
## [2023.3-kinder] (2023-10-31)
- suppress error output ([#4056])
- Update frontend type for current vesting period ([#4042])
- re-exported additional types for tx queries ([#4036])
- fixed fmt::Display impl for GatewayNetworkRequesterDetails ([#4033])
- Add exit node policy from TorNull and Tor Exit Node Policy ([#4024])
- basic self-described api for gateways to dynamically announce its details + nym-api aggregation ([#4017])
- use saturating sub in case outfox is not enabled ([#3986])
- Fix sorting for mixnodes and gateways ([#3985])
- Gateway client registry and api routes ([#3955])
[#4056]: https://github.com/nymtech/nym/pull/4056
[#4042]: https://github.com/nymtech/nym/pull/4042
[#4036]: https://github.com/nymtech/nym/pull/4036
[#4033]: https://github.com/nymtech/nym/pull/4033
[#4024]: https://github.com/nymtech/nym/issues/4024
[#4017]: https://github.com/nymtech/nym/issues/4017
[#3986]: https://github.com/nymtech/nym/pull/3986
[#3985]: https://github.com/nymtech/nym/pull/3985
[#3955]: https://github.com/nymtech/nym/pull/3955
## [2023.1-milka] (2023-09-24)
Generated
+84 -41
View File
@@ -1158,7 +1158,7 @@ checksum = "f769ab9e8c1652d78dd0b3ec59cdaa1e2bcb3b6b39f6681b256abcdbe101cc14"
dependencies = [
"anyhow",
"cargo_metadata",
"clap 4.4.6",
"clap 4.4.7",
"concolor-control",
"crates-index",
"dirs-next",
@@ -1393,9 +1393,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.6"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
dependencies = [
"clap_builder",
"clap_derive",
@@ -1403,13 +1403,13 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.6"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
dependencies = [
"anstream",
"anstyle",
"clap_lex 0.5.1",
"clap_lex 0.6.0",
"strsim",
"terminal_size",
]
@@ -1420,7 +1420,7 @@ version = "4.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ae8ba90b9d8b007efe66e55e48fb936272f5ca00349b5b0e89877520d35ea7"
dependencies = [
"clap 4.4.6",
"clap 4.4.7",
]
[[package]]
@@ -1429,15 +1429,15 @@ version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29bdbe21a263b628f83fcbeac86a4416a1d588c7669dd41473bc4149e4e7d2f1"
dependencies = [
"clap 4.4.6",
"clap 4.4.7",
"clap_complete",
]
[[package]]
name = "clap_derive"
version = "4.4.2"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck 0.4.1",
"proc-macro2",
@@ -1456,9 +1456,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.5.1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "cloudabi"
@@ -2871,7 +2871,7 @@ dependencies = [
"bytes",
"cfg-if",
"chrono",
"clap 4.4.6",
"clap 4.4.7",
"config",
"digest 0.10.7",
"dirs 5.0.1",
@@ -2938,10 +2938,10 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "explorer-api"
version = "1.1.30"
version = "1.1.31"
dependencies = [
"chrono",
"clap 4.4.6",
"clap 4.4.7",
"dotenvy",
"humantime-serde",
"isocountry",
@@ -2994,7 +2994,7 @@ dependencies = [
[[package]]
name = "extension-storage"
version = "1.2.0"
version = "1.2.1"
dependencies = [
"bip39",
"console_error_panic_hook",
@@ -5559,7 +5559,7 @@ dependencies = [
[[package]]
name = "mix-fetch-wasm"
version = "1.2.0"
version = "1.2.1"
dependencies = [
"async-trait",
"futures",
@@ -5951,7 +5951,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.31"
version = "1.1.32"
dependencies = [
"actix-web",
"anyhow",
@@ -5961,7 +5961,7 @@ dependencies = [
"bs58 0.4.0",
"cfg-if",
"chrono",
"clap 4.4.6",
"clap 4.4.7",
"console-subscriber",
"cosmwasm-std",
"cw-utils",
@@ -6066,7 +6066,7 @@ name = "nym-bin-common"
version = "0.6.0"
dependencies = [
"atty",
"clap 4.4.6",
"clap 4.4.7",
"clap_complete",
"clap_complete_fig",
"log",
@@ -6101,13 +6101,13 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.30"
version = "1.1.31"
dependencies = [
"anyhow",
"base64 0.13.1",
"bip39",
"bs58 0.4.0",
"clap 4.4.6",
"clap 4.4.7",
"clap_complete",
"clap_complete_fig",
"dotenvy",
@@ -6132,7 +6132,7 @@ dependencies = [
"bip39",
"bs58 0.4.0",
"cfg-if",
"clap 4.4.6",
"clap 4.4.7",
"comfy-table",
"cosmrs",
"cosmwasm-std",
@@ -6174,9 +6174,9 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.30"
version = "1.1.31"
dependencies = [
"clap 4.4.6",
"clap 4.4.7",
"dirs 4.0.0",
"futures",
"lazy_static",
@@ -6215,6 +6215,7 @@ dependencies = [
"async-trait",
"base64 0.21.4",
"cfg-if",
"clap 4.4.7",
"dashmap",
"dirs 4.0.0",
"futures",
@@ -6259,7 +6260,7 @@ dependencies = [
[[package]]
name = "nym-client-wasm"
version = "1.2.0"
version = "1.2.1"
dependencies = [
"anyhow",
"futures",
@@ -6477,6 +6478,18 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "nym-exit-policy"
version = "0.1.0"
dependencies = [
"reqwest",
"serde",
"serde_json",
"thiserror",
"tracing",
"utoipa",
]
[[package]]
name = "nym-explorer-api-requests"
version = "0.1.0"
@@ -6504,14 +6517,14 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "1.1.30"
version = "1.1.31"
dependencies = [
"anyhow",
"async-trait",
"atty",
"bip39",
"bs58 0.4.0",
"clap 4.4.6",
"clap 4.4.7",
"colored",
"dashmap",
"dirs 4.0.0",
@@ -6528,6 +6541,7 @@ dependencies = [
"nym-credentials",
"nym-crypto",
"nym-gateway-requests",
"nym-ip-packet-router",
"nym-mixnet-client",
"nym-mixnode-common",
"nym-network-defaults",
@@ -6631,6 +6645,29 @@ dependencies = [
"thiserror",
]
[[package]]
name = "nym-ip-packet-router"
version = "0.1.0"
dependencies = [
"etherparse",
"futures",
"log",
"nym-bin-common",
"nym-client-core",
"nym-config",
"nym-sdk",
"nym-service-providers-common",
"nym-sphinx",
"nym-task",
"nym-wireguard",
"nym-wireguard-types",
"serde",
"serde_json",
"tap",
"thiserror",
"tokio",
]
[[package]]
name = "nym-mixnet-client"
version = "0.1.0"
@@ -6666,12 +6703,13 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.31"
version = "1.1.32"
dependencies = [
"anyhow",
"axum",
"bs58 0.4.0",
"cfg-if",
"clap 4.4.6",
"clap 4.4.7",
"colored",
"cpu-cycles",
"cupid",
@@ -6686,6 +6724,7 @@ dependencies = [
"nym-crypto",
"nym-mixnet-client",
"nym-mixnode-common",
"nym-node",
"nym-nonexhaustive-delayqueue",
"nym-pemstore",
"nym-sphinx",
@@ -6698,10 +6737,10 @@ dependencies = [
"opentelemetry",
"pretty_env_logger",
"rand 0.7.3",
"rocket",
"serde",
"serde_json",
"sysinfo",
"thiserror",
"tokio",
"tokio-util",
"toml 0.5.11",
@@ -6784,13 +6823,13 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.30"
version = "1.1.31"
dependencies = [
"anyhow",
"async-file-watcher",
"async-trait",
"bs58 0.4.0",
"clap 4.4.6",
"clap 4.4.7",
"dirs 4.0.0",
"futures",
"humantime-serde",
@@ -6803,6 +6842,7 @@ dependencies = [
"nym-config",
"nym-credential-storage",
"nym-crypto",
"nym-exit-policy",
"nym-network-defaults",
"nym-ordered-buffer",
"nym-sdk",
@@ -6831,7 +6871,7 @@ dependencies = [
[[package]]
name = "nym-network-statistics"
version = "1.1.30"
version = "1.1.31"
dependencies = [
"dirs 4.0.0",
"log",
@@ -6887,6 +6927,7 @@ dependencies = [
"http-api-client",
"nym-bin-common",
"nym-crypto",
"nym-exit-policy",
"nym-wireguard-types",
"schemars",
"serde",
@@ -6917,7 +6958,7 @@ dependencies = [
[[package]]
name = "nym-node-tester-wasm"
version = "1.2.0"
version = "1.2.1"
dependencies = [
"futures",
"js-sys",
@@ -6950,7 +6991,7 @@ name = "nym-nr-query"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 4.4.6",
"clap 4.4.7",
"log",
"nym-bin-common",
"nym-network-defaults",
@@ -7073,9 +7114,9 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.30"
version = "1.1.31"
dependencies = [
"clap 4.4.6",
"clap 4.4.7",
"lazy_static",
"log",
"nym-bin-common",
@@ -7178,6 +7219,7 @@ version = "0.1.0"
dependencies = [
"bincode",
"log",
"nym-exit-policy",
"nym-service-providers-common",
"nym-sphinx-addressing",
"serde",
@@ -7518,7 +7560,7 @@ dependencies = [
[[package]]
name = "nym-wasm-sdk"
version = "1.2.0"
version = "1.2.1"
dependencies = [
"mix-fetch-wasm",
"nym-client-wasm",
@@ -7541,6 +7583,7 @@ dependencies = [
"log",
"nym-task",
"nym-wireguard-types",
"rand 0.8.5",
"serde",
"tap",
"thiserror",
@@ -9500,7 +9543,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"cargo-edit",
"clap 4.4.6",
"clap 4.4.7",
"semver 1.0.20",
"serde",
"serde_json",
@@ -10262,7 +10305,7 @@ name = "ssl-inject"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 4.4.6",
"clap 4.4.7",
"hex",
"tokio",
]
+4 -1
View File
@@ -46,6 +46,7 @@ members = [
"common/crypto",
"common/dkg",
"common/execute",
"common/exit-policy",
"common/http-api-client",
"common/inclusion-probability",
"common/ledger",
@@ -89,6 +90,7 @@ members = [
"sdk/lib/socks5-listener",
"sdk/rust/nym-sdk",
"service-providers/common",
"service-providers/ip-packet-router",
"service-providers/network-requester",
"service-providers/network-statistics",
"nym-api",
@@ -119,7 +121,7 @@ default-members = [
"explorer-api",
]
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "cpu-cycles"]
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-vpn/ui/src-tauri", "cpu-cycles"]
[workspace.package]
authors = ["Nym Technologies SA"]
@@ -136,6 +138,7 @@ axum = "0.6.20"
base64 = "0.21.4"
bip39 = { version = "2.0.0", features = ["zeroize"] }
boringtun = { git = "https://github.com/cloudflare/boringtun", rev = "e1d6360d6ab4529fc942a078e4c54df107abe2ba" }
clap = "4.4.7"
cfg-if = "1.0.0"
cosmwasm-derive = "=1.3.0"
cosmwasm-schema = "=1.3.0"
+1 -5
View File
@@ -93,10 +93,6 @@ $(eval $(call add_cargo_workspace,contracts,contracts,--lib --target wasm32-unkn
$(eval $(call add_cargo_workspace,wallet,nym-wallet))
$(eval $(call add_cargo_workspace,connect,nym-connect/desktop))
# OVERRIDE: wasm-opt fails if the binary has been built with the latest rustc.
# Pin to the last working version.
contracts_BUILD_RELEASE_TOOLCHAIN := +1.69.0
# -----------------------------------------------------------------------------
# SDK
# -----------------------------------------------------------------------------
@@ -144,7 +140,7 @@ contracts: build-release-contracts wasm-opt-contracts
wasm-opt-contracts:
for contract in $(CONTRACTS_WASM); do \
wasm-opt --disable-sign-ext -Os $(CONTRACTS_OUT_DIR)/$$contract -o $(CONTRACTS_OUT_DIR)/$$contract; \
wasm-opt --signext-lowering -Os $(CONTRACTS_OUT_DIR)/$$contract -o $(CONTRACTS_OUT_DIR)/$$contract; \
done
# Consider adding 's' to make plural consistent (beware: used in github workflow)
+3 -3
View File
@@ -50,10 +50,10 @@ Node, node operator and delegator rewards are determined according to the princi
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda_{i}#gh-dark-mode-only">|ratio of stake operator has pledged to their node to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\omega_{i}#gh-dark-mode-only">|fraction of total effort undertaken by node `i`, set to `1/k`.
|<img src="https://render.githubusercontent.com/render/math?math=k#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}k#gh-dark-mode-only">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `reward set` size, and set to 720 in testnet Sandbox.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitiveness gets for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PM_{i}#gh-dark-mode-only">|declared profit margin of operator `i`, defaults to 10% in.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|A Sybil attack resistance parameter - the higher this parameter is set, the stronger the reduction in competitiveness for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PM_{i}#gh-dark-mode-only">|declared profit margin of operator `i`, defaults to 10%.
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PF_{i}#gh-dark-mode-only">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PP_{i}#gh-dark-mode-only">|cost of operating node `i` for the duration of the rewarding epoch, set to 40 NYMT.
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PP_{i}#gh-dark-mode-only">|cost of operating node `i` for the duration of the rewarding epoch, set to 40 NYMs.
Node reward for node `i` is determined as:
+85 -5
View File
@@ -1,10 +1,90 @@
Critical bug or security issue 💥
If you're here because you're trying to figure out how to notify us of a security issue, go to Discord, and alert the core engineers:
If you're here because you're trying to figure out how to notify us of a security issue, send us a PGP encrypted email to:
Dave Hrycyszyn futurechimp#5430
Jedrzej Stuczynski "Jedrzej | Nym#5666"
Fran Arbanas | franarbanas#0995
Mark Sinclair | marknym#8088
```
security@nymte.ch
```
Encrypted with our public key which is available below in plain text and also on keyservers:
```
pub rsa4096 2023-10-30 [SC] [expire : 2026-10-29]
24B2592E801A5AAA8666C8BA7C3C727F05090550
uid [ ultime ] Security Nym Technologies <security@nymte.ch>
sub rsa4096 2023-10-30 [E] [expire : 2026-10-29]
```
The fingerprint of the key is on the second line above.
If you need to chat __urgently__ to our team for a __critical__ security issue:
go to Matrix, and alert the core engineers with a private direct message:
Jedrzej Stuczynski @jstuczyn:nymtech.chat
Mark Sinclair @mark:nymtech.chat
Raphaël Walther @raphael:nymtech.chat
Please avoid opening public issues on GitHub that contain information about a potential security vulnerability as this makes it difficult to reduce the impact and harm of valid security issues.
If you don't know what Matrix is, you can follow this documentation to create an account on this federation of instant messaging servers:
[Matrix for Instant Messaging](https://matrix.org/docs/chat_basics/matrix-for-im/)
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGU/XpcBEAC+ykz0yxn8FferjEBooptXlOH/v/28aa0Nv8DfImTgj9BNY5cR
UdLk+Wa3CSXQVE7PIsi0egEjAMfyxPEywbvPlgklW4XAKDVUCf3gxpQNN47VuVgV
VwrN0VBurhhIKoEw9daO6A0P44+6nmXGIfUulCr4fMxYq82SOooog/j5w0/LfITu
rQXxVABLkXHGN/NGf4BE52QI/ppeXWoshlNVU1wdZIIYWwte+9ukikWpN+LYfJUR
ybtyCjQ4Gdf8ap1GmkKHmAru24wbUuFsBWGVgHsXAwYlKxyiNGR9YwgAxmFk6vNf
1PqKGO3i4erx5X/+mzylzNbFlCqFuksZRyUSDZvQ8fxkm8ra1zWbO38eOTp8Vhgg
SKfRTzOKeZYURZicJPxmEIfA88U4tx+YWJ54YWT/gERZkjIJL5mzIuY9UulVvKUM
vMFUIzBMHOPXH16036zGyFMC1esRd2qqil4b9KtLgCOkrD1VgpjcveoA0VyMJCN6
LmKTrVjwjjDMxby+d49BolRWGnCofXozXwvNQx+CYv8M2WPErTpyYoofYFtpqr7A
fIufc/e0+um3zoGIbHejrhsbuH9Qf+MKsI+Ng93bdDtjeHz6MEgAlsTm0qeizYpj
IyKZIObPmfvrAm08hFZ8JnGk+XuooF36XWbJYjCCy0bOyMw1r7ZG99TcSwARAQAB
tC1TZWN1cml0eSBOeW0gVGVjaG5vbG9naWVzIDxzZWN1cml0eUBueW10ZS5jaD6J
AlQEEwEKAD4WIQQkslkugBpaqoZmyLp8PHJ/BQkFUAUCZT9elwIbAwUJBaOagAUL
CQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRB8PHJ/BQkFUL7dD/9zO73uI5VR+SWx
PFmJW+9QsPiQbVRvGwNZurctmQ2s2Pe0vHRELFeqD5oYvSx2Lequ3Ir+zn/C3kDM
kNs40obSL6jCBiLPkxEY0JqzPM9jZr7EjvlibWV3f6DxooRIqEyfN57I3OBGlqZE
0Mx7sQuCcgau8C70DF952QhKUwXC2cmpmDKHVEEoio1xGSD4dQhGapCB32RQGtna
OGfAO9celNMvSq0Lp+aJxeACmWFY5T4/y79JPcT5vSs/yEIRmaH/fn2piwaFBsIq
gHJJMxO3740P1hF8j7KWUoUofuFaEALHBpEpjWTOj8ej1wmFlu+5F+jSVoc781Wb
ZZXu04cOBXnGTogzSxMpBe9TtLb28zd6WzFotC25KTI3pngMzXsQGLJLOwvoZKiS
LFjPRjg1rwobmB3Q3J2W5GYSveia0CDsZGP+g87GVVf/oD2Djpa68xyVYwIYeA6T
3DNdS77qHiRuGiS4kWXyVjDqOICboR4uCvt09zlkBuLDdTWqWYARUvZjtjs4w/Ol
rdrBI3A88ti8fRldYaNpu17ME1ilpN44yKoJtqiWc3Tisk8eYLfx6c7FQF3PrRva
mr7FZvhFsYML5CeNFHTEzN6Y3jjKN/60DvCfodWnWFK47Txkl8UAXGY2W9B0fWqQ
wUVr8uLuMyyMiKbeoufi7rGOj6AMErkCDQRlP16XARAA8FGmD5J3tM1BOM1niJxZ
JTdCauzEtxEoBL0RuqGBkR8U29sRM6DwuzjU7PwscFnBaGyU+eU73GwGkH3ozFfF
tllYhQrhP/kkN+0rEO5Xi+nR+4JCFRqrf3nJXAAPfiksURMp8er1dUOY2/e1ZSoL
tS+nzUivV8CfE+pgj/5YtGwPC+KYHLATkKkMELCrbW4UO06VWOqQsvr6kivXuJQQ
LdEAMpBlADmXFG45DmPKQzsBWUgvTwyGy3LX0nys8cgpex9BH8hhr01QmGyP469s
N3cNrtFuu8U6RAsiCD/8mlBuD3EQEU5SF0lc7kCICAZk+wElmXnimEi0TOYsbz6k
90lteicX70rA9GNeyI76H+VSOYvWpkRwaJAgUdzrAM1o9SHASq+cZ6nD85OZioQk
DWM6+Q+sf2oen0qJnnGmUr93kJIC0PIdgrXRrtiNfeRa1Z/H0LmREyyEMoFiVivn
z1vVk85Oq6Sf3ltUwvmDzuuJOtsp2Qp6+x6Snn/yKauI4uf4Cf/wKUch4r6Bwgg5
Dw49ky7lwlnALio4GIVoGLpLef93wWoDmp4Klyh3ZPf2nB0U91u3bHRUo7m+D7QJ
98cyKtqLLzjg7szGf60pIWNWRsadYQT3bSncynqknAjOV3BCvx6/ivsnpj//QjYR
HtviUAcQ1DBB6UC6q23FIs0AEQEAAYkCPAQYAQoAJhYhBCSyWS6AGlqqhmbIunw8
cn8FCQVQBQJlP16XAhsMBQkFo5qAAAoJEHw8cn8FCQVQzukP/iLxjOxT+UpPR//c
prDVSLkP4pF5bmw36U07jvqpS+/KTXsxiiQleffRabOpNLcd+K1ueavyt9nnIwHH
tHS9kM9A7DBw3LnpEbXki46QDCCI6niGijlLOEeAWqnocwMNTT05wVVgCtO3DQP2
MoSCcqHpXDChvOyr5d5xjYLVJhlctIMSomcVzGryjknPu0Yj/TkC/4c+m86ZWQUD
HqMHQIuiEenvb62/F4c5OJIRZPEn70wdddkgJuJU3eHdHrnuhCkjCC93GQGbGj03
Zqos6699y6hmPeD3U5IUv8ujwZYVCCuDm8gJfrp3R6WLfeZeK9WmTVBpCzsDg3fV
hSwmOk6pp8DAq1/Dev3yRkFggCEyGK6c9b+a0CRBncl8e5Q0QQIzNiS/uExQP3h+
ELJs3P0MLP+6FWhNUry09n3lnWkr1hY+v1M0GAxbfdv/tsCN1Pq/VQEz+CTqXqya
ftWldOHWw6Hh+gtwxcHjG4MBOrO5oICQ3lh2hGwQ58cDgZYSK/OGgJ9BggFl1CcM
0uGC0/TRCI1zt/4y+7efSZQMZkHo7VC/3MFbp2hcNejpW+BxVuwKTunFvWK3TLhq
sSlQ5yyhqchooepsFHq9bosKFjLJC01uprBv1rinoNduOy43FbyS7JPRRspANN0R
iC2pMbWdE0ZTQaFq6tPIg058pjqi
=nqgX
-----END PGP PUBLIC KEY BLOCK-----
```
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.30"
version = "1.1.31"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
@@ -20,7 +20,7 @@ futures = { workspace = true } # bunch of futures stuff, however, now that I thi
# and the single instance of abortable we have should really be refactored anyway
url = { workspace = true }
clap = { version = "4.0", features = ["cargo", "derive"] }
clap = { workspace = true, features = ["cargo", "derive"] }
dirs = "4.0"
lazy_static = "1.4.0"
log = { workspace = true } # self explanatory
@@ -36,7 +36,7 @@ tokio-tungstenite = { workspace = true }
## internal
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage"] }
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "cli"] }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-config = { path = "../../common/config" }
nym-credential-storage = { path = "../../common/credential-storage" }
+20
View File
@@ -4,6 +4,8 @@
use crate::client::config::persistence::ClientPaths;
use crate::client::config::template::CONFIG_TEMPLATE;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::cli_helpers::client_init::ClientConfig;
use nym_client_core::config::disk_persistence::CommonClientPaths;
use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use nym_config::{
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
@@ -72,6 +74,24 @@ impl NymConfigTemplate for Config {
}
}
impl ClientConfig for Config {
fn common_paths(&self) -> &CommonClientPaths {
&self.storage_paths.common_paths
}
fn core_config(&self) -> &BaseClientConfig {
&self.base
}
fn default_store_location(&self) -> PathBuf {
self.default_location()
}
fn save_to<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
save_formatted_config_to_file(self, path)
}
}
impl Config {
pub fn new<S: AsRef<str>>(id: S) -> Self {
Config {
+55 -160
View File
@@ -12,55 +12,49 @@ use crate::{
};
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::error::ClientCoreError;
use nym_client_core::init::helpers::current_gateways;
use nym_client_core::init::types::{GatewayDetails, GatewaySelectionSpecification, GatewaySetup};
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use nym_topology::NymTopology;
use nym_client_core::cli_helpers::client_init::{
initialise_client, CommonClientInitArgs, InitResultsWithConfig, InitialisableClient,
};
use serde::Serialize;
use std::fmt::Display;
use std::fs;
use std::net::IpAddr;
use std::path::PathBuf;
use std::{fs, io};
use tap::TapFallible;
struct NativeClientInit;
impl InitialisableClient for NativeClientInit {
const NAME: &'static str = "native";
type Error = ClientError;
type InitArgs = Init;
type Config = Config;
fn try_upgrade_outdated_config(id: &str) -> Result<(), Self::Error> {
try_upgrade_config(id)
}
fn initialise_storage_paths(id: &str) -> Result<(), Self::Error> {
fs::create_dir_all(default_data_directory(id))?;
fs::create_dir_all(default_config_directory(id))?;
Ok(())
}
fn default_config_path(id: &str) -> PathBuf {
default_config_filepath(id)
}
fn construct_config(init_args: &Self::InitArgs) -> Self::Config {
override_config(
Config::new(&init_args.common_args.id),
OverrideConfig::from(init_args.clone()),
)
}
}
#[derive(Args, Clone)]
pub(crate) struct Init {
/// Id of the nym-mixnet-client we want to create config for.
#[clap(long)]
id: String,
/// Id of the gateway we are going to connect to.
#[clap(long)]
gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[clap(long, conflicts_with = "gateway")]
latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[clap(long)]
force_register_gateway: bool,
/// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nyxd_validators", value_delimiter = ',', hide = true)]
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators
#[clap(
long,
alias = "api_validators",
value_delimiter = ',',
group = "network"
)]
// the alias here is included for backwards compatibility (1.1.4 and before)
nym_apis: Option<Vec<url::Url>>,
#[command(flatten)]
common_args: CommonClientInitArgs,
/// Whether to not start the websocket
#[clap(long)]
@@ -74,40 +68,28 @@ pub(crate) struct Init {
#[clap(long)]
host: Option<IpAddr>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
fastmode: bool,
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
#[clap(long, hide = true)]
no_cover: bool,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[clap(long, hide = true)]
enabled_credentials_mode: Option<bool>,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
impl AsRef<CommonClientInitArgs> for Init {
fn as_ref(&self) -> &CommonClientInitArgs {
&self.common_args
}
}
impl From<Init> for OverrideConfig {
fn from(init_config: Init) -> Self {
OverrideConfig {
nym_apis: init_config.nym_apis,
nym_apis: init_config.common_args.nym_apis,
disable_socket: init_config.disable_socket,
port: init_config.port,
host: init_config.host,
fastmode: init_config.fastmode,
no_cover: init_config.no_cover,
fastmode: init_config.common_args.fastmode,
no_cover: init_config.common_args.no_cover,
nyxd_urls: init_config.nyxd_urls,
enabled_credentials_mode: init_config.enabled_credentials_mode,
nyxd_urls: init_config.common_args.nyxd_urls,
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
}
}
}
@@ -121,15 +103,11 @@ pub struct InitResults {
}
impl InitResults {
fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self {
fn new(res: InitResultsWithConfig<Config>) -> Self {
Self {
client_core: nym_client_core::init::types::InitResults::new(
&config.base,
address,
gateway,
),
client_listening_port: config.socket.listening_port,
client_address: address.to_string(),
client_address: res.init_results.address.to_string(),
client_core: res.init_results,
client_listening_port: res.config.socket.listening_port,
}
}
}
@@ -142,97 +120,14 @@ impl Display for InitResults {
}
}
fn init_paths(id: &str) -> io::Result<()> {
fs::create_dir_all(default_data_directory(id))?;
fs::create_dir_all(default_config_directory(id))
}
pub(crate) async fn execute(args: Init) -> Result<(), ClientError> {
eprintln!("Initialising client...");
let id = &args.id;
let output = args.output;
let res = initialise_client::<NativeClientInit>(args).await?;
let already_init = if default_config_filepath(id).exists() {
// in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op)
try_upgrade_config(id)?;
eprintln!("Client \"{id}\" was already initialised before");
true
} else {
init_paths(id)?;
false
};
// Usually you only register with the gateway on the first init, however you can force
// re-registering if wanted.
let user_wants_force_register = args.force_register_gateway;
if user_wants_force_register {
eprintln!("Instructed to force registering gateway. This will overwrite keys!");
}
// If the client was already initialized, don't generate new keys and don't re-register with
// the gateway (because this would create a new shared key).
// Unless the user really wants to.
let register_gateway = !already_init || user_wants_force_register;
// Attempt to use a user-provided gateway, if possible
let user_chosen_gateway_id = args.gateway;
let selection_spec = GatewaySelectionSpecification::new(
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(args.latency_based_selection),
false,
);
// Load and potentially override config
let config = override_config(Config::new(id), OverrideConfig::from(args.clone()));
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let available_gateways = if let Some(hardcoded_topology) = args
.custom_mixnet
.map(NymTopology::new_from_file)
.transpose()?
{
// hardcoded_topology
hardcoded_topology.get_gateways()
} else {
let mut rng = rand::thread_rng();
current_gateways(&mut rng, &config.base.client.nym_api_urls).await?
};
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
overwrite_data: register_gateway,
};
let init_details =
nym_client_core::init::setup_gateway(gateway_setup, &key_store, &details_store)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
let config_save_location = config.default_location();
config.save_to_default_location().tap_err(|_| {
log::error!("Failed to save the config file");
})?;
eprintln!(
"Saved configuration file to {}",
config_save_location.display()
);
let address = init_details.client_address()?;
eprintln!("Client configuration completed.\n");
let GatewayDetails::Configured(gateway_details) = init_details.gateway_details else {
return Err(ClientCoreError::UnexpectedPersistedCustomGatewayDetails)?;
};
let init_results = InitResults::new(&config, &address, &gateway_details);
println!("{}", args.output.format(&init_results));
let init_results = InitResults::new(res);
println!("{}", output.format(&init_results));
Ok(())
}
+11 -50
View File
@@ -10,35 +10,14 @@ use crate::{
use clap::Args;
use log::*;
use nym_bin_common::version_checker::is_minor_version_compatible;
use nym_crypto::asymmetric::identity;
use nym_client_core::cli_helpers::client_run::CommonClientRunArgs;
use std::error::Error;
use std::net::IpAddr;
use std::path::PathBuf;
#[derive(Args, Clone)]
pub(crate) struct Run {
/// Id of the nym-mixnet-client we want to run.
#[clap(long)]
id: String,
/// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nyxd_validators", value_delimiter = ',', hide = true)]
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators
#[clap(
long,
alias = "api_validators",
value_delimiter = ',',
group = "network"
)]
// the alias here is included for backwards compatibility (1.1.4 and before)
nym_apis: Option<Vec<url::Url>>,
/// Id of the gateway we want to connect to. If overridden, it is user's responsibility to
/// ensure prior registration happened
#[clap(long)]
gateway: Option<identity::PublicKey>,
#[command(flatten)]
common_args: CommonClientRunArgs,
/// Whether to not start the websocket
#[clap(long)]
@@ -51,37 +30,19 @@ pub(crate) struct Run {
/// Ip for the socket (if applicable) to listen for requests.
#[clap(long)]
host: Option<IpAddr>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
fastmode: bool,
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
#[clap(long, hide = true)]
no_cover: bool,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[clap(long, hide = true)]
enabled_credentials_mode: Option<bool>,
}
impl From<Run> for OverrideConfig {
fn from(run_config: Run) -> Self {
OverrideConfig {
nym_apis: run_config.nym_apis,
nym_apis: run_config.common_args.nym_apis,
disable_socket: run_config.disable_socket,
port: run_config.port,
host: run_config.host,
fastmode: run_config.fastmode,
no_cover: run_config.no_cover,
nyxd_urls: run_config.nyxd_urls,
enabled_credentials_mode: run_config.enabled_credentials_mode,
fastmode: run_config.common_args.fastmode,
no_cover: run_config.common_args.no_cover,
nyxd_urls: run_config.common_args.nyxd_urls,
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
}
}
}
@@ -106,9 +67,9 @@ fn version_check(cfg: &Config) -> bool {
}
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn Error + Send + Sync>> {
eprintln!("Starting client {}...", args.id);
eprintln!("Starting client {}...", args.common_args.id);
let mut config = try_load_current_config(&args.id)?;
let mut config = try_load_current_config(&args.common_args.id)?;
config = override_config(config, OverrideConfig::from(args.clone()));
if !version_check(&config) {
@@ -116,7 +77,7 @@ pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn Error + Send + Sync
return Err(Box::new(ClientError::FailedLocalVersionCheck));
}
SocketClient::new(config, args.custom_mixnet)
SocketClient::new(config, args.common_args.custom_mixnet)
.run_socket_forever()
.await
}
+3 -3
View File
@@ -1,13 +1,13 @@
[package]
name = "nym-socks5-client"
version = "1.1.30"
version = "1.1.31"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
rust-version = "1.56"
[dependencies]
clap = { version = "4.0", features = ["cargo", "derive"] }
clap = { workspace = true, features = ["cargo", "derive"] }
lazy_static = "1.4.0"
log = { workspace = true }
pretty_env_logger = "0.4"
@@ -21,7 +21,7 @@ url = { workspace = true }
# internal
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage"] }
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "cli"] }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-config = { path = "../../common/config" }
nym-credentials = { path = "../../common/credentials" }
+55 -163
View File
@@ -11,27 +11,50 @@ use crate::{
};
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::error::ClientCoreError;
use nym_client_core::init::helpers::current_gateways;
use nym_client_core::init::types::{GatewayDetails, GatewaySelectionSpecification, GatewaySetup};
use nym_crypto::asymmetric::identity;
use nym_client_core::cli_helpers::client_init::{
initialise_client, CommonClientInitArgs, InitResultsWithConfig, InitialisableClient,
};
use nym_sphinx::addressing::clients::Recipient;
use nym_topology::NymTopology;
use serde::Serialize;
use std::fmt::Display;
use std::fs;
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use std::{fs, io};
use tap::TapFallible;
struct Socks5ClientInit;
impl InitialisableClient for Socks5ClientInit {
const NAME: &'static str = "socks5";
type Error = Socks5ClientError;
type InitArgs = Init;
type Config = Config;
fn try_upgrade_outdated_config(id: &str) -> Result<(), Self::Error> {
try_upgrade_config(id)
}
fn initialise_storage_paths(id: &str) -> Result<(), Self::Error> {
fs::create_dir_all(default_data_directory(id))?;
fs::create_dir_all(default_config_directory(id))?;
Ok(())
}
fn default_config_path(id: &str) -> PathBuf {
default_config_filepath(id)
}
fn construct_config(init_args: &Self::InitArgs) -> Self::Config {
override_config(
Config::new(&init_args.common_args.id, &init_args.provider.to_string()),
OverrideConfig::from(init_args.clone()),
)
}
}
#[derive(Args, Clone)]
pub(crate) struct Init {
/// Id of the nym-mixnet-client we want to create config for.
#[clap(long)]
id: String,
#[command(flatten)]
common_args: CommonClientInitArgs,
/// Address of the socks5 provider to send messages to.
#[clap(long)]
@@ -46,34 +69,6 @@ pub(crate) struct Init {
#[clap(long, alias = "use_anonymous_sender_tag")]
use_reply_surbs: Option<bool>,
/// Id of the gateway we are going to connect to.
#[clap(long)]
gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[clap(long, conflicts_with = "gateway")]
latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[clap(long)]
force_register_gateway: bool,
/// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nyxd_validators", value_delimiter = ',', hide = true)]
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators
#[clap(
long,
alias = "api_validators",
value_delimiter = ',',
group = "network"
)]
// the alias here is included for backwards compatibility (1.1.4 and before)
nym_apis: Option<Vec<url::Url>>,
/// Port for the socket to listen on in all subsequent runs
#[clap(short, long)]
port: Option<u16>,
@@ -82,41 +77,29 @@ pub(crate) struct Init {
#[clap(long)]
host: Option<IpAddr>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
fastmode: bool,
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
#[clap(long, hide = true)]
no_cover: bool,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[clap(long, hide = true)]
enabled_credentials_mode: Option<bool>,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
impl AsRef<CommonClientInitArgs> for Init {
fn as_ref(&self) -> &CommonClientInitArgs {
&self.common_args
}
}
impl From<Init> for OverrideConfig {
fn from(init_config: Init) -> Self {
OverrideConfig {
nym_apis: init_config.nym_apis,
nym_apis: init_config.common_args.nym_apis,
ip: init_config.host,
port: init_config.port,
use_anonymous_replies: init_config.use_reply_surbs,
fastmode: init_config.fastmode,
no_cover: init_config.no_cover,
fastmode: init_config.common_args.fastmode,
no_cover: init_config.common_args.no_cover,
geo_routing: None,
medium_toggle: false,
nyxd_urls: init_config.nyxd_urls,
enabled_credentials_mode: init_config.enabled_credentials_mode,
nyxd_urls: init_config.common_args.nyxd_urls,
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
outfox: false,
}
}
@@ -131,15 +114,11 @@ pub struct InitResults {
}
impl InitResults {
fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self {
fn new(res: InitResultsWithConfig<Config>) -> Self {
Self {
client_core: nym_client_core::init::types::InitResults::new(
&config.core.base,
address,
gateway,
),
socks5_listening_address: config.core.socks5.bind_adddress,
client_address: address.to_string(),
client_address: res.init_results.address.to_string(),
client_core: res.init_results,
socks5_listening_address: res.config.core.socks5.bind_adddress,
}
}
}
@@ -156,101 +135,14 @@ impl Display for InitResults {
}
}
fn init_paths(id: &str) -> io::Result<()> {
fs::create_dir_all(default_data_directory(id))?;
fs::create_dir_all(default_config_directory(id))
}
pub(crate) async fn execute(args: Init) -> Result<(), Socks5ClientError> {
eprintln!("Initialising client...");
let id = &args.id;
let provider_address = &args.provider;
let output = args.output;
let res = initialise_client::<Socks5ClientInit>(args).await?;
let already_init = if default_config_filepath(id).exists() {
// in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op)
try_upgrade_config(id)?;
eprintln!("SOCKS5 client \"{id}\" was already initialised before");
true
} else {
init_paths(id)?;
false
};
// Usually you only register with the gateway on the first init, however you can force
// re-registering if wanted.
let user_wants_force_register = args.force_register_gateway;
if user_wants_force_register {
eprintln!("Instructed to force registering gateway. This might overwrite keys!");
}
// If the client was already initialized, don't generate new keys and don't re-register with
// the gateway (because this would create a new shared key).
// Unless the user really wants to.
let register_gateway = !already_init || user_wants_force_register;
// Attempt to use a user-provided gateway, if possible
let user_chosen_gateway_id = args.gateway;
let selection_spec = GatewaySelectionSpecification::new(
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(args.latency_based_selection),
false,
);
// Load and potentially override config
let config = override_config(
Config::new(id, &provider_address.to_string()),
OverrideConfig::from(args.clone()),
);
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let available_gateways = if let Some(hardcoded_topology) = args
.custom_mixnet
.map(NymTopology::new_from_file)
.transpose()?
{
// hardcoded_topology
hardcoded_topology.get_gateways()
} else {
let mut rng = rand::thread_rng();
current_gateways(&mut rng, &config.core.base.client.nym_api_urls).await?
};
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
overwrite_data: register_gateway,
};
let init_details =
nym_client_core::init::setup_gateway(gateway_setup, &key_store, &details_store)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
// TODO: ask the service provider we specified for its interface version and set it in the config
let config_save_location = config.default_location();
config.save_to_default_location().tap_err(|_| {
log::error!("Failed to save the config file");
})?;
eprintln!(
"Saved configuration file to {}",
config_save_location.display()
);
let address = init_details.client_address()?;
let GatewayDetails::Configured(gateway_details) = init_details.gateway_details else {
return Err(ClientCoreError::UnexpectedPersistedCustomGatewayDetails)?;
};
let init_results = InitResults::new(&config, &address, &gateway_details);
println!("{}", args.output.format(&init_results));
let init_results = InitResults::new(res);
println!("{}", output.format(&init_results));
Ok(())
}
+11 -44
View File
@@ -10,19 +10,17 @@ use crate::{
use clap::Args;
use log::*;
use nym_bin_common::version_checker::is_minor_version_compatible;
use nym_client_core::cli_helpers::client_run::CommonClientRunArgs;
use nym_client_core::client::base_client::storage::OnDiskPersistent;
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
use nym_crypto::asymmetric::identity;
use nym_socks5_client_core::NymClient;
use nym_sphinx::addressing::clients::Recipient;
use std::net::IpAddr;
use std::path::PathBuf;
#[derive(Args, Clone)]
pub(crate) struct Run {
/// Id of the nym-mixnet-client we want to run.
#[clap(long)]
id: String,
#[command(flatten)]
common_args: CommonClientRunArgs,
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
/// While this is going to hide its actual address information, it will make the actual communication
@@ -37,19 +35,6 @@ pub(crate) struct Run {
#[clap(long)]
provider: Option<Recipient>,
/// Id of the gateway we want to connect to. If overridden, it is user's responsibility to
/// ensure prior registration happened
#[clap(long)]
gateway: Option<identity::PublicKey>,
/// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nyxd_validators", value_delimiter = ',', hide = true)]
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the Nym APIs
#[clap(long, value_delimiter = ',', group = "network")]
nym_apis: Option<Vec<url::Url>>,
/// Port for the socket to listen on
#[clap(short, long)]
port: Option<u16>,
@@ -58,19 +43,6 @@ pub(crate) struct Run {
#[clap(long)]
host: Option<IpAddr>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", group = "routing", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
fastmode: bool,
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
#[clap(long, hide = true)]
no_cover: bool,
/// Set geo-aware mixnode selection when sending mixnet traffic, for experiments only.
#[clap(long, hide = true, value_parser = validate_country_group, group="routing")]
geo_routing: Option<CountryGroup>,
@@ -80,11 +52,6 @@ pub(crate) struct Run {
#[clap(long, hide = true)]
medium_toggle: bool,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[clap(long, hide = true)]
enabled_credentials_mode: Option<bool>,
#[clap(long, hide = true, action)]
outfox: bool,
}
@@ -92,16 +59,16 @@ pub(crate) struct Run {
impl From<Run> for OverrideConfig {
fn from(run_config: Run) -> Self {
OverrideConfig {
nym_apis: run_config.nym_apis,
nym_apis: run_config.common_args.nym_apis,
ip: run_config.host,
port: run_config.port,
use_anonymous_replies: run_config.use_anonymous_replies,
fastmode: run_config.fastmode,
no_cover: run_config.no_cover,
fastmode: run_config.common_args.fastmode,
no_cover: run_config.common_args.no_cover,
geo_routing: run_config.geo_routing,
medium_toggle: run_config.medium_toggle,
nyxd_urls: run_config.nyxd_urls,
enabled_credentials_mode: run_config.enabled_credentials_mode,
nyxd_urls: run_config.common_args.nyxd_urls,
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
outfox: run_config.outfox,
}
}
@@ -136,9 +103,9 @@ fn version_check(cfg: &Config) -> bool {
}
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
eprintln!("Starting client {}...", args.id);
eprintln!("Starting client {}...", args.common_args.id);
let mut config = try_load_current_config(&args.id)?;
let mut config = try_load_current_config(&args.common_args.id)?;
config = override_config(config, OverrideConfig::from(args.clone()));
if !version_check(&config) {
@@ -149,7 +116,7 @@ pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn std::error::Error +
let storage =
OnDiskPersistent::from_paths(config.storage_paths.common_paths, &config.core.base.debug)
.await?;
NymClient::new(config.core, storage, args.custom_mixnet)
NymClient::new(config.core, storage, args.common_args.custom_mixnet)
.run_forever()
.await
}
+20
View File
@@ -3,6 +3,8 @@
use crate::config::template::CONFIG_TEMPLATE;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::cli_helpers::client_init::ClientConfig;
use nym_client_core::config::disk_persistence::CommonClientPaths;
use nym_config::{
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
@@ -69,6 +71,24 @@ impl NymConfigTemplate for Config {
}
}
impl ClientConfig for Config {
fn common_paths(&self) -> &CommonClientPaths {
&self.storage_paths.common_paths
}
fn core_config(&self) -> &BaseClientConfig {
&self.core.base
}
fn default_store_location(&self) -> PathBuf {
self.default_location()
}
fn save_to<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
save_formatted_config_to_file(self, path)
}
}
impl Config {
pub fn new<S: AsRef<str>>(id: S, provider_mix_address: S) -> Self {
Config {
+1 -1
View File
@@ -9,7 +9,7 @@ repository = { workspace = true }
[dependencies]
atty = "0.2"
clap = { version = "4.0", features = ["derive"] }
clap = { workspace = true, features = ["derive"] }
clap_complete = "4.0"
clap_complete_fig = "4.0"
log = { workspace = true }
+2
View File
@@ -11,6 +11,7 @@ rust-version = "1.66"
async-trait = { workspace = true }
base64 = "0.21.2"
cfg-if = "1.0.0"
clap = { workspace = true, optional = true }
dashmap = { workspace = true }
dirs = "4.0"
futures = { workspace = true }
@@ -92,6 +93,7 @@ sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite", "macro
[features]
default = []
cli = ["clap"]
fs-surb-storage = ["sqlx"]
wasm = ["nym-gateway-client/wasm"]
@@ -0,0 +1,210 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::disk_persistence::CommonClientPaths;
use crate::error::ClientCoreError;
use crate::{
client::{
base_client::storage::gateway_details::OnDiskGatewayDetails,
key_manager::persistence::OnDiskKeys,
},
init::types::{GatewayDetails, GatewaySelectionSpecification, GatewaySetup, InitResults},
};
use log::info;
use nym_crypto::asymmetric::identity;
use nym_topology::NymTopology;
use std::path::{Path, PathBuf};
pub trait InitialisableClient {
const NAME: &'static str;
type Error: From<ClientCoreError>;
type InitArgs: AsRef<CommonClientInitArgs>;
type Config: ClientConfig;
fn try_upgrade_outdated_config(id: &str) -> Result<(), Self::Error>;
fn initialise_storage_paths(id: &str) -> Result<(), Self::Error>;
fn default_config_path(id: &str) -> PathBuf;
fn construct_config(init_args: &Self::InitArgs) -> Self::Config;
}
pub trait ClientConfig {
fn common_paths(&self) -> &CommonClientPaths;
fn core_config(&self) -> &crate::config::Config;
fn default_store_location(&self) -> PathBuf;
fn save_to<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()>;
}
#[cfg_attr(feature = "cli", derive(clap::Args))]
#[derive(Debug, Clone)]
pub struct CommonClientInitArgs {
/// Id of client we want to create config for.
#[cfg_attr(feature = "cli", clap(long))]
pub id: String,
/// Id of the gateway we are going to connect to.
#[cfg_attr(feature = "cli", clap(long))]
pub gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[cfg_attr(feature = "cli", clap(long, conflicts_with = "gateway"))]
pub latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[cfg_attr(feature = "cli", clap(long))]
pub force_register_gateway: bool,
/// Comma separated list of rest endpoints of the nyxd validators
#[cfg_attr(
feature = "cli",
clap(long, alias = "nyxd_validators", value_delimiter = ',', hide = true)
)]
pub nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators
#[cfg_attr(
feature = "cli",
clap(
long,
alias = "api_validators",
value_delimiter = ',',
group = "network"
)
)]
pub nym_apis: Option<Vec<url::Url>>,
/// Path to .json file containing custom network specification.
#[cfg_attr(feature = "cli", clap(long, group = "network", hide = true))]
pub custom_mixnet: Option<PathBuf>,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub enabled_credentials_mode: Option<bool>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub fastmode: bool,
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub no_cover: bool,
}
pub struct InitResultsWithConfig<T> {
pub config: T,
pub init_results: InitResults,
}
pub async fn initialise_client<C>(
init_args: C::InitArgs,
) -> Result<InitResultsWithConfig<C::Config>, C::Error>
where
C: InitialisableClient,
{
info!("initialising {} client", C::NAME);
let common_args = init_args.as_ref();
let id = &common_args.id;
let already_init = if C::default_config_path(id).exists() {
// in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op)
C::try_upgrade_outdated_config(id)?;
eprintln!("{} client \"{id}\" was already initialised before", C::NAME);
true
} else {
C::initialise_storage_paths(id)?;
false
};
// Usually you only register with the gateway on the first init, however you can force
// re-registering if wanted.
let user_wants_force_register = common_args.force_register_gateway;
if user_wants_force_register {
eprintln!("Instructed to force registering gateway. This might overwrite keys!");
}
// If the client was already initialized, don't generate new keys and don't re-register with
// the gateway (because this would create a new shared key).
// Unless the user really wants to.
let register_gateway = !already_init || user_wants_force_register;
// Attempt to use a user-provided gateway, if possible
let user_chosen_gateway_id = common_args.gateway;
let selection_spec = GatewaySelectionSpecification::new(
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(common_args.latency_based_selection),
false,
);
// Load and potentially override config
let config = C::construct_config(&init_args);
let paths = config.common_paths();
let core = config.core_config();
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let key_store = OnDiskKeys::new(paths.keys.clone());
let details_store = OnDiskGatewayDetails::new(&paths.gateway_details);
let available_gateways = if let Some(custom_mixnet) = common_args.custom_mixnet.as_ref() {
let hardcoded_topology = NymTopology::new_from_file(custom_mixnet).map_err(|source| {
ClientCoreError::CustomTopologyLoadFailure {
file_path: custom_mixnet.clone(),
source,
}
})?;
hardcoded_topology.get_gateways()
} else {
let mut rng = rand::thread_rng();
crate::init::helpers::current_gateways(&mut rng, &core.client.nym_api_urls).await?
};
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
overwrite_data: register_gateway,
};
let init_details =
crate::init::setup_gateway(gateway_setup, &key_store, &details_store).await?;
// TODO: ask the service provider we specified for its interface version and set it in the config
let config_save_location = config.default_store_location();
if let Err(err) = config.save_to(&config_save_location) {
return Err(ClientCoreError::ConfigSaveFailure {
typ: C::NAME.to_string(),
id: id.to_string(),
path: config_save_location,
source: err,
}
.into());
}
eprintln!(
"Saved configuration file to {}",
config_save_location.display()
);
let address = init_details.client_address()?;
let GatewayDetails::Configured(gateway_details) = init_details.gateway_details else {
return Err(ClientCoreError::UnexpectedPersistedCustomGatewayDetails)?;
};
let init_results = InitResults::new(config.core_config(), address, &gateway_details);
Ok(InitResultsWithConfig {
config,
init_results,
})
}
@@ -0,0 +1,59 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_crypto::asymmetric::identity;
use std::path::PathBuf;
#[cfg_attr(feature = "cli", derive(clap::Args))]
#[derive(Debug, Clone)]
pub struct CommonClientRunArgs {
/// Id of client we want to create config for.
#[cfg_attr(feature = "cli", clap(long))]
pub id: String,
/// Id of the gateway we want to connect to. If overridden, it is user's responsibility to
/// ensure prior registration happened
#[cfg_attr(feature = "cli", clap(long))]
pub gateway: Option<identity::PublicKey>,
/// Comma separated list of rest endpoints of the nyxd validators
#[cfg_attr(
feature = "cli",
clap(long, alias = "nyxd_validators", value_delimiter = ',', hide = true)
)]
pub nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators
#[cfg_attr(
feature = "cli",
clap(
long,
alias = "api_validators",
value_delimiter = ',',
group = "network"
)
)]
pub nym_apis: Option<Vec<url::Url>>,
/// Path to .json file containing custom network specification.
#[cfg_attr(feature = "cli", clap(long, group = "network", hide = true))]
pub custom_mixnet: Option<PathBuf>,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub enabled_credentials_mode: Option<bool>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
// note: we removed the 'conflicts_with = medium_toggle', but that's fine since NR
// has defined the conflict on that field itself
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub fastmode: bool,
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
// note: we removed the 'conflicts_with = medium_toggle', but that's fine since NR
// has defined the conflict on that field itself
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub no_cover: bool,
}
@@ -0,0 +1,5 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod client_init;
pub mod client_run;
+21
View File
@@ -8,6 +8,7 @@ use nym_topology::gateway::GatewayConversionError;
use nym_topology::NymTopologyError;
use nym_validator_client::ValidatorClientError;
use std::error::Error;
use std::path::PathBuf;
#[derive(thiserror::Error, Debug)]
pub enum ClientCoreError {
@@ -132,6 +133,26 @@ pub enum ClientCoreError {
#[error("the specified gateway '{gateway}' does not support the wss protocol")]
UnsupportedWssProtocol { gateway: String },
#[error(
"failed to load custom topology using path '{}'. detailed message: {source}", file_path.display()
)]
CustomTopologyLoadFailure {
file_path: PathBuf,
#[source]
source: std::io::Error,
},
#[error(
"failed to save config file for client-{typ} id {id} using path '{}'. detailed message: {source}", path.display()
)]
ConfigSaveFailure {
typ: String,
id: String,
path: PathBuf,
#[source]
source: std::io::Error,
},
}
/// Set of messages that the client can send to listeners via the task manager
+9 -7
View File
@@ -296,16 +296,17 @@ impl<T> GatewaySetup<T> {
/// Struct describing the results of the client initialization procedure.
#[derive(Debug, Serialize)]
pub struct InitResults {
version: String,
id: String,
identity_key: String,
encryption_key: String,
gateway_id: String,
gateway_listener: String,
pub version: String,
pub id: String,
pub identity_key: String,
pub encryption_key: String,
pub gateway_id: String,
pub gateway_listener: String,
pub address: Recipient,
}
impl InitResults {
pub fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self {
pub fn new(config: &Config, address: Recipient, gateway: &GatewayEndpointConfig) -> Self {
Self {
version: config.client.version.clone(),
id: config.client.id.clone(),
@@ -313,6 +314,7 @@ impl InitResults {
encryption_key: address.encryption_key().to_base58_string(),
gateway_id: gateway.gateway_id.clone(),
gateway_listener: gateway.gateway_listener.clone(),
address,
}
}
}
+2
View File
@@ -1,5 +1,7 @@
use std::future::Future;
#[cfg(not(target_arch = "wasm32"))]
pub mod cli_helpers;
pub mod client;
pub mod config;
pub mod error;
@@ -9,7 +9,7 @@ use nym_validator_client::nyxd::contract_traits::{
#[tokio::main]
async fn main() {
setup_env(Some("../../../envs/qa-qwerty.env"));
setup_env(Some("../../../envs/qa.env"));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
@@ -9,7 +9,7 @@ use nym_validator_client::nyxd::contract_traits::{
#[tokio::main]
async fn main() {
setup_env(Some("../../../envs/qa-qwerty.env"));
setup_env(Some("../../../envs/qa.env"));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
@@ -8,6 +8,7 @@ use crate::nyxd::cosmwasm_client::types::{
Account, CodeDetails, Contract, ContractCodeId, SequenceResponse, SimulateResponse,
};
use crate::nyxd::error::NyxdError;
use crate::nyxd::Query;
use crate::rpc::TendermintRpcClient;
use async_trait::async_trait;
use cosmrs::cosmwasm::{CodeInfoResponse, ContractCodeHistoryEntry};
@@ -35,7 +36,6 @@ use std::convert::TryFrom;
use std::time::Duration;
use tendermint_rpc::{
endpoint::{block::Response as BlockResponse, broadcast, tx::Response as TxResponse},
query::Query,
Order,
};
@@ -18,7 +18,7 @@ use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, Reqwes
use async_trait::async_trait;
use cosmrs::cosmwasm;
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
use cosmrs::tx::{Msg, Raw, SignDoc};
use cosmrs::tx::{Raw, SignDoc};
use cosmwasm_std::Addr;
use nym_network_defaults::{ChainDetails, NymNetworkDetails};
use serde::{de::DeserializeOwned, Serialize};
@@ -39,6 +39,7 @@ pub use cosmrs::tendermint::block::Height;
pub use cosmrs::tendermint::hash::{self, Algorithm, Hash};
pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::Msg;
pub use cosmrs::tx::{self};
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::Gas;
@@ -47,6 +48,7 @@ pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
pub use tendermint_rpc::{
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
query::Query,
Paging,
};
pub use tendermint_rpc::{Request, Response, SimpleRequest};
@@ -57,7 +59,6 @@ use crate::http_client;
use crate::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient};
#[cfg(feature = "http-client")]
use cosmrs::rpc::{HttpClient, HttpClientUrl};
use tendermint_rpc::query::Query;
pub mod coin;
pub mod contract_traits;
+1 -1
View File
@@ -11,7 +11,7 @@ bip39 = { workspace = true }
bs58 = "0.4"
comfy-table = "6.0.0"
cfg-if = "1.0.0"
clap = { version = "4.0", features = ["derive"] }
clap = { workspace = true, features = ["derive"] }
cw-utils = { workspace = true }
handlebars = "3.0.1"
humantime-serde = "1.0"
+1
View File
@@ -15,6 +15,7 @@ pub use toml::de::Error as TomlDeError;
pub mod defaults;
pub mod helpers;
pub mod legacy_helpers;
pub mod serde_helpers;
pub const NYM_DIR: &str = ".nym";
pub const DEFAULT_NYM_APIS_DIR: &str = "nym-api";
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Deserializer};
use std::fmt::Display;
use std::path::PathBuf;
use std::str::FromStr;
pub fn de_maybe_stringified<'de, D, T, E>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr<Err = E>,
E: Display,
{
let raw = String::deserialize(deserializer)?;
if raw.is_empty() {
Ok(None)
} else {
Ok(Some(raw.parse().map_err(serde::de::Error::custom)?))
}
}
pub fn de_maybe_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
de_maybe_stringified(deserializer)
}
pub fn de_maybe_path<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
where
D: Deserializer<'de>,
{
de_maybe_stringified(deserializer)
}
pub fn de_maybe_port<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
where
D: Deserializer<'de>,
{
let port = u16::deserialize(deserializer)?;
if port == 0 {
Ok(None)
} else {
Ok(Some(port))
}
}
+33
View File
@@ -0,0 +1,33 @@
[package]
name = "nym-exit-policy"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
tracing = { workspace = true }
# feature-specific dependencies:
## client feature
reqwest = { workspace = true, optional = true }
## openapi feature
serde_json = { workspace = true, optional = true }
utoipa = { workspace = true, optional = true }
[dev-dependencies]
serde_json = { workspace = true }
[features]
default = []
client = ["reqwest"]
openapi = ["utoipa", "serde_json"]
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::policy::PolicyError;
use crate::ExitPolicy;
use reqwest::IntoUrl;
pub async fn get_exit_policy(url: impl IntoUrl) -> Result<ExitPolicy, PolicyError> {
ExitPolicy::parse_from_torrc(reqwest::get(url).await?.text().await?)
}
+233
View File
@@ -0,0 +1,233 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod policy;
#[cfg(feature = "client")]
pub mod client;
pub use crate::policy::{
AddressPolicy, AddressPolicyAction, AddressPolicyRule, AddressPortPattern, PolicyError,
PortRange,
};
pub(crate) const EXIT_POLICY_FIELD_NAME: &str = "ExitPolicy";
const COMMENT_CHAR: char = '#';
pub type ExitPolicy = AddressPolicy;
pub fn parse_exit_policy<S: AsRef<str>>(exit_policy: S) -> Result<ExitPolicy, PolicyError> {
let rules = exit_policy
.as_ref()
.lines()
.map(|maybe_rule| {
if let Some(comment_start) = maybe_rule.find(COMMENT_CHAR) {
&maybe_rule[..comment_start]
} else {
maybe_rule
}
.trim()
})
.filter(|maybe_rule| !maybe_rule.is_empty())
.map(parse_address_policy_rule)
.collect::<Result<Vec<_>, _>>()?;
Ok(AddressPolicy { rules })
}
pub fn format_exit_policy(policy: &ExitPolicy) -> String {
policy
.rules
.iter()
.map(|rule| format!("{EXIT_POLICY_FIELD_NAME} {rule}"))
.fold(String::new(), |accumulator, rule| {
accumulator + &rule + "\n"
})
.trim_end()
.to_string()
}
fn parse_address_policy_rule(rule: &str) -> Result<AddressPolicyRule, PolicyError> {
// each exit policy rule must begin with 'ExitPolicy' followed by the actual rule
rule.strip_prefix(EXIT_POLICY_FIELD_NAME)
.ok_or(PolicyError::NoExitPolicyPrefix {
entry: rule.to_string(),
})?
.trim()
.parse()
}
// for each line, ignore everything after the comment
#[cfg(test)]
mod tests {
use super::*;
use crate::policy::AddressPolicyAction::{Accept, Accept6, Reject, Reject6};
use crate::policy::{AddressPortPattern, IpPattern, PortRange};
#[test]
fn parsing_policy() {
let sample = r#"
ExitPolicy reject 1.2.3.4/32:*#comment
ExitPolicy reject 1.2.3.5:* #comment
ExitPolicy reject 1.2.3.6/16:*
ExitPolicy reject 1.2.3.6/16:123-456 # comment
ExitPolicy accept *:53 # DNS
# random comment
ExitPolicy accept6 *6:119
ExitPolicy accept *4:120
ExitPolicy reject6 [FC00::]/7:*
#ExitPolicy accept *:8080 #and another comment here
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8328:1234
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8328/64:1235
#another comment
#ExitPolicy accept *:8080
ExitPolicy reject *:*
"#;
let res = parse_exit_policy(sample).unwrap();
let mut expected = AddressPolicy::new();
// ExitPolicy reject 1.2.3.4/32:*#comment
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4 {
addr_prefix: "1.2.3.4".parse().unwrap(),
mask: 32,
},
ports: PortRange::new_all(),
},
);
// ExitPolicy reject 1.2.3.5:* #comment
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4 {
addr_prefix: "1.2.3.5".parse().unwrap(),
mask: 32,
},
ports: PortRange::new_all(),
},
);
// ExitPolicy reject 1.2.3.6/16:*
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4 {
addr_prefix: "1.2.3.6".parse().unwrap(),
mask: 16,
},
ports: PortRange::new_all(),
},
);
// ExitPolicy reject 1.2.3.6/16:123-456
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4 {
addr_prefix: "1.2.3.6".parse().unwrap(),
mask: 16,
},
ports: PortRange::new(123, 456).unwrap(),
},
);
// ExitPolicy accept *:53
expected.push(
Accept,
AddressPortPattern {
ip_pattern: IpPattern::Star,
ports: PortRange::new_singleton(53),
},
);
// ExitPolicy accept6 *6:119
expected.push(
Accept6,
AddressPortPattern {
ip_pattern: IpPattern::V6Star,
ports: PortRange::new_singleton(119),
},
);
// ExitPolicy accept *4:120
expected.push(
Accept,
AddressPortPattern {
ip_pattern: IpPattern::V4Star,
ports: PortRange::new_singleton(120),
},
);
// ExitPolicy reject6 [FC00::]/7:*
expected.push(
Reject6,
AddressPortPattern {
ip_pattern: IpPattern::V6 {
addr_prefix: "FC00::".parse().unwrap(),
mask: 7,
},
ports: PortRange::new_all(),
},
);
// ExitPolicy FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V6 {
addr_prefix: "FE80:0000:0000:0000:0202:B3FF:FE1E:8329".parse().unwrap(),
mask: 128,
},
ports: PortRange::new_all(),
},
);
// ExitPolicy FE80:0000:0000:0000:0202:B3FF:FE1E:8328:1234
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V6 {
addr_prefix: "FE80:0000:0000:0000:0202:B3FF:FE1E:8328".parse().unwrap(),
mask: 128,
},
ports: PortRange::new_singleton(1234),
},
);
// ExitPolicy FE80:0000:0000:0000:0202:B3FF:FE1E:8328/64:1235
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V6 {
addr_prefix: "FE80:0000:0000:0000:0202:B3FF:FE1E:8328".parse().unwrap(),
mask: 64,
},
ports: PortRange::new_singleton(1235),
},
);
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::Star,
ports: PortRange::new_all(),
},
);
assert_eq!(res, expected)
}
}
@@ -0,0 +1,726 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
//! Implements address policies, based on a series of accept/reject
//! rules.
use crate::policy::error::PolicyError;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::str::FromStr;
use tracing::trace;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "lowercase")]
pub enum AddressPolicyAction {
/// A rule that accepts matching address:port combinations on IPv4 and IPv6.
Accept,
/// A rule that rejects matching address:port combinations on IPv4 and IPv6.
Reject,
/// A rule that accepts matching address:port combinations on IPv6 only.
Accept6,
/// A rule that rejects matching address:port combinations on IPv6 only.
Reject6,
}
impl AddressPolicyAction {
pub fn is_accept(&self) -> bool {
matches!(
self,
AddressPolicyAction::Accept | AddressPolicyAction::Accept6
)
}
}
impl FromStr for AddressPolicyAction {
type Err = PolicyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"accept" => Ok(AddressPolicyAction::Accept),
"reject" => Ok(AddressPolicyAction::Reject),
"accept6" => Ok(AddressPolicyAction::Accept6),
"reject6" => Ok(AddressPolicyAction::Reject6),
other => Err(PolicyError::InvalidPolicyAction {
action: other.to_string(),
}),
}
}
}
impl Display for AddressPolicyAction {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
AddressPolicyAction::Accept => write!(f, "accept"),
AddressPolicyAction::Reject => write!(f, "reject"),
AddressPolicyAction::Accept6 => write!(f, "accept6"),
AddressPolicyAction::Reject6 => write!(f, "reject6"),
}
}
}
/// A sequence of rules that are applied to an address:port until one
/// matches.
///
/// Each rule is of the form "accept(6) PATTERN" or "reject(6) PATTERN",
/// where every pattern describes a set of addresses and ports.
/// Address sets are given as a prefix of 0-128 bits that the address
/// must have; port sets are given as a low-bound and high-bound that
/// the target port might lie between.
///
/// An example IPv4 policy might be:
///
/// ```text
/// reject *:25
/// reject 127.0.0.0/8:*
/// reject 192.168.0.0/16:*
/// accept *:80
/// accept *:443
/// accept *:9000-65535
/// reject *:*
/// ```
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "openapi", aliases(ExitPolicy))]
pub struct AddressPolicy {
/// A list of rules to apply to find out whether an address is
/// contained by this policy.
///
/// The rules apply in order; the first one to match determines
/// whether the address is accepted or rejected.
pub(crate) rules: Vec<AddressPolicyRule>,
}
impl AddressPolicy {
/// Create a new AddressPolicy that matches nothing.
pub const fn new() -> Self {
AddressPolicy { rules: Vec::new() }
}
/// Create a new AddressPolicy that matches everything.
pub fn new_open() -> Self {
AddressPolicy {
rules: vec![AddressPolicyRule::new(
AddressPolicyAction::Accept,
AddressPortPattern {
ip_pattern: IpPattern::Star,
ports: PortRange::new_all(),
},
)],
}
}
/// Check whether this AddressPolicy matches all patterns.
pub fn is_open(&self) -> bool {
if self.rules.len() != 1 {
return false;
}
let rule = &self.rules[0];
rule.action == AddressPolicyAction::Accept
&& rule.pattern.ip_pattern == IpPattern::Star
&& rule.pattern.ports.is_all()
}
/// Attempts to parse the AddressPolicy out of raw torrc representation.
pub fn parse_from_torrc<S: AsRef<str>>(raw: S) -> Result<Self, PolicyError> {
crate::parse_exit_policy(raw)
}
/// Formats the AddressPolicy with torrc representation
pub fn format_as_torrc(&self) -> String {
crate::format_exit_policy(self)
}
/// Apply this policy to an address:port combination
///
/// We do this by applying each rule in sequence, until one
/// matches.
///
/// Returns None if no rule matches.
pub fn allows(&self, addr: &IpAddr, port: u16) -> Option<bool> {
self.rules
.iter()
.find(|rule| rule.pattern.matches(addr, port))
.map(|rule| {
trace!("'{addr}:{port}' is covered by rule '{rule}'");
rule.action.is_accept()
})
}
/// As allows, but accept a SocketAddr.
pub fn allows_sockaddr(&self, addr: &SocketAddr) -> Option<bool> {
self.allows(&addr.ip(), addr.port())
}
/// Add a new rule to this policy.
///
/// The newly added rule is applied _after_ all previous rules.
/// It matches all addresses and ports covered by AddressPortPattern.
///
/// If accept is true, the rule is to accept addresses that match;
/// if accept is false, the rule rejects such addresses.
pub fn push(&mut self, action: AddressPolicyAction, pattern: AddressPortPattern) {
self.rules.push(AddressPolicyRule { action, pattern })
}
/// As push, but accepts a AddressPolicyRule.
pub fn push_rule(&mut self, rule: AddressPolicyRule) {
self.rules.push(rule)
}
}
/// A single rule in an address policy.
///
/// Contains a pattern, what to do with things that match it.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct AddressPolicyRule {
/// What do we do with items that match the pattern?
action: AddressPolicyAction,
/// What pattern are we trying to match?
pattern: AddressPortPattern,
}
impl FromStr for AddressPolicyRule {
type Err = PolicyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// split on the first space, i.e. separation between the action and the pattern
let Some((action, pattern)) = s.split_once(' ') else {
return Err(PolicyError::MalformedAddressPolicy { raw: s.to_string() });
};
Ok(AddressPolicyRule {
action: action.parse()?,
pattern: pattern.parse()?,
})
}
}
impl AddressPolicyRule {
pub fn new(action: AddressPolicyAction, pattern: AddressPortPattern) -> Self {
AddressPolicyRule { action, pattern }
}
}
impl Display for AddressPolicyRule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.action, self.pattern)
}
}
/// A pattern that may or may not match an address and port.
///
/// Each AddrPortPattern has an IP pattern, which matches a set of
/// addresses by prefix, and a port pattern, which matches a range of
/// ports.
///
/// # Example
///
/// ```
/// use nym_exit_policy::policy::AddressPortPattern;
/// use std::net::{IpAddr,Ipv4Addr};
/// let localhost = IpAddr::V4(Ipv4Addr::new(127,3,4,5));
/// let not_localhost = IpAddr::V4(Ipv4Addr::new(192,0,2,16));
/// let pat: AddressPortPattern = "127.0.0.0/8:*".parse().unwrap();
///
/// assert!(pat.matches(&localhost, 22));
/// assert!(!pat.matches(&not_localhost, 22));
/// ```
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct AddressPortPattern {
/// A pattern to match somewhere between zero and all IP addresses.
#[serde(with = "stringified_ip_pattern")]
#[cfg_attr(feature = "openapi", schema(example = "1.2.3.6/16", value_type = String))]
pub(crate) ip_pattern: IpPattern,
/// A pattern to match a range of ports.
pub(crate) ports: PortRange,
}
mod stringified_ip_pattern {
use super::IpPattern;
use serde::{Deserialize, Deserializer, Serializer};
use std::str::FromStr;
pub fn serialize<S: Serializer>(pattern: &IpPattern, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&pattern.to_string())
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<IpPattern, D::Error> {
let s = <String>::deserialize(deserializer)?;
IpPattern::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl AddressPortPattern {
/// Return true iff this pattern matches a given address and port.
pub fn matches(&self, addr: &IpAddr, port: u16) -> bool {
self.ip_pattern.matches(addr) && self.ports.contains(port)
}
/// As matches, but accept a SocketAddr.
pub fn matches_sockaddr(&self, addr: &SocketAddr) -> bool {
self.matches(&addr.ip(), addr.port())
}
}
impl Display for AddressPortPattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.ip_pattern, self.ports)
}
}
impl FromStr for AddressPortPattern {
type Err = PolicyError;
fn from_str(s: &str) -> Result<Self, PolicyError> {
let last_colon = s
.rfind(':')
.ok_or(PolicyError::MalformedAddressPortPattern { raw: s.to_string() })?;
// doesn't have enough chars to cover the port, even if its a wildcard
if s.len() < last_colon + 2 {
return Err(PolicyError::MalformedAddressPortPattern { raw: s.to_string() });
}
let ip_pattern = s[..last_colon].parse()?;
let ports = s[last_colon + 1..].parse()?;
Ok(AddressPortPattern { ip_pattern, ports })
}
}
/// A pattern that matches one or more IP addresses.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IpPattern {
/// Match all addresses.
Star,
/// Match all IPv4 addresses.
V4Star,
/// Match all IPv6 addresses.
V6Star,
/// Match all IPv4 addresses beginning with a given prefix and mask.
V4 { addr_prefix: Ipv4Addr, mask: u8 },
/// Match all IPv6 addresses beginning with a given prefix and mask.
V6 { addr_prefix: Ipv6Addr, mask: u8 },
}
impl IpPattern {
/// Construct an IpPattern that matches the first `mask` bits of `addr`.
fn from_addr_and_mask(address: IpAddr, target_mask: u8) -> Result<Self, PolicyError> {
match (address, target_mask) {
(IpAddr::V4(_), 0) => Ok(IpPattern::V4Star),
(IpAddr::V6(_), 0) => Ok(IpPattern::V6Star),
(IpAddr::V4(addr_prefix), mask) if mask <= 32 => {
Ok(IpPattern::V4 { addr_prefix, mask })
}
(IpAddr::V6(addr_prefix), mask) if mask <= 128 => {
Ok(IpPattern::V6 { addr_prefix, mask })
}
(addr, mask) => {
if addr.is_ipv4() {
Err(PolicyError::InvalidIpV4Mask { mask })
} else {
Err(PolicyError::InvalidIpV6Mask { mask })
}
}
}
}
/// Return true iff `addr` is matched by this pattern.
fn matches(&self, addr: &IpAddr) -> bool {
match (self, addr) {
(IpPattern::Star, _) => true,
(IpPattern::V4Star, IpAddr::V4(_)) => true,
(IpPattern::V6Star, IpAddr::V6(_)) => true,
(IpPattern::V4 { addr_prefix, mask }, IpAddr::V4(addr)) => {
let p1 = u32::from_be_bytes(addr_prefix.octets());
let p2 = u32::from_be_bytes(addr.octets());
let shift = 32 - mask;
(p1 >> shift) == (p2 >> shift)
}
(IpPattern::V6 { addr_prefix, mask }, IpAddr::V6(addr)) => {
let p1 = u128::from_be_bytes(addr_prefix.octets());
let p2 = u128::from_be_bytes(addr.octets());
let shift = 128 - mask;
(p1 >> shift) == (p2 >> shift)
}
(_, _) => false,
}
}
}
impl Display for IpPattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IpPattern::Star => write!(f, "*"),
IpPattern::V4Star => write!(f, "*4"),
IpPattern::V6Star => write!(f, "*6"),
IpPattern::V4 { addr_prefix, mask } => {
write!(f, "{addr_prefix}/{mask}")
}
IpPattern::V6 { addr_prefix, mask } => {
write!(f, "{addr_prefix}/{mask}")
}
}
}
}
/// Helper: try to parse a plain ipv4 address, or an IPv6 address
/// wrapped in brackets.
fn parse_addr(s: &str) -> Result<IpAddr, PolicyError> {
if s.starts_with('[') && s.ends_with(']') {
Ipv6Addr::from_str(&s[1..s.len() - 1]).map(IpAddr::V6)
} else {
IpAddr::from_str(s)
}
.map_err(|source| PolicyError::MalformedIpAddress {
addr: s.to_string(),
source,
})
}
/// Helper: try to parse a port making sure it's non-zero
fn parse_port(s: &str) -> Result<u16, PolicyError> {
let port = s
.parse::<u16>()
.map_err(|_| PolicyError::InvalidPort { raw: s.to_string() })?;
if port == 0 {
Err(PolicyError::InvalidPort {
raw: port.to_string(),
})
} else {
Ok(port)
}
}
impl FromStr for IpPattern {
type Err = PolicyError;
fn from_str(s: &str) -> Result<Self, PolicyError> {
let (ip_s, mask_s) = match s.find('/') {
Some(slash_idx) => (&s[..slash_idx], Some(&s[slash_idx + 1..])),
None => (s, None),
};
match (ip_s, mask_s) {
// '*' patterns
("*", Some(m)) => Err(PolicyError::MaskWithStar {
mask: m.to_string(),
}),
("*", None) => Ok(IpPattern::Star),
// '*4' patterns
("*4", Some(m)) => Err(PolicyError::MaskWithV4Star {
mask: m.to_string(),
}),
("*4", None) => Ok(IpPattern::V4Star),
// '*6' patterns
("*6", Some(m)) => Err(PolicyError::MaskWithV6Star {
mask: m.to_string(),
}),
("*6", None) => Ok(IpPattern::V6Star),
(s, Some(m)) => {
let a: IpAddr = parse_addr(s)?;
let m: u8 = m.parse().map_err(|_| PolicyError::InvalidMask {
mask: m.to_string(),
})?;
IpPattern::from_addr_and_mask(a, m)
}
(s, None) => {
let a: IpAddr = parse_addr(s)?;
let m = if a.is_ipv4() { 32 } else { 128 };
IpPattern::from_addr_and_mask(a, m)
}
}
}
}
/// A PortRange is a set of consecutively numbered TCP or UDP ports.
///
/// # Example
/// ```
/// use nym_exit_policy::policy::PortRange;
///
/// let r: PortRange = "22-8000".parse().unwrap();
/// assert!(r.contains(128));
/// assert!(r.contains(22));
/// assert!(r.contains(8000));
///
/// assert!(! r.contains(21));
/// assert!(! r.contains(8001));
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct PortRange {
/// The first port in this range.
#[cfg_attr(feature = "openapi", schema(example = 80))]
pub start: u16,
/// The last port in this range.
#[cfg_attr(feature = "openapi", schema(example = 81))]
pub end: u16,
}
impl PortRange {
/// Create a new port range spanning from start to end, asserting that
/// the correct invariants hold.
fn new_unchecked(start: u16, end: u16) -> Self {
assert_ne!(start, 0);
assert!(start <= end);
PortRange { start, end }
}
/// Create a port range containing all ports.
pub fn new_all() -> Self {
PortRange::new_unchecked(1, 65535)
}
/// Create a new PortRange.
///
/// The Portrange contains all ports between `start` and `end` inclusive.
///
/// Returns None if lo is greater than end, or if either is zero.
pub const fn new(start: u16, end: u16) -> Option<Self> {
if start != 0 && start <= end {
Some(PortRange { start, end })
} else {
None
}
}
/// Create a new singleton PortRange.
pub const fn new_singleton(value: u16) -> Self {
PortRange {
start: value,
end: value,
}
}
/// Return true if a port is in this range.
pub fn contains(&self, port: u16) -> bool {
self.start <= port && port <= self.end
}
/// Return true if this range contains all ports.
pub fn is_all(&self) -> bool {
self.start == 1 && self.end == 65535
}
}
/// A PortRange is displayed as a number if it contains a single port,
/// and as a start point and end point separated by a dash if it contains
/// more than one port.
impl Display for PortRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_all() {
write!(f, "*")
} else if self.start == self.end {
write!(f, "{}", self.start)
} else {
write!(f, "{}-{}", self.start, self.end)
}
}
}
impl FromStr for PortRange {
type Err = PolicyError;
fn from_str(s: &str) -> Result<Self, PolicyError> {
// check is if it's a star range
if s == "*" {
return Ok(PortRange::new_all());
}
if let Some(pos) = s.find('-') {
// This is a range; parse each part
let start = parse_port(&s[..pos])?;
let end = parse_port(&s[pos + 1..])?;
PortRange::new(start, end).ok_or(PolicyError::InvalidRange { start, end })
} else {
// There was no hyphen, so try to parse this range as a singleton.
let value = parse_port(s)?;
Ok(PortRange::new_singleton(value))
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_bad_rules() {
fn check(s: &str) {
assert!(s.parse::<AddressPortPattern>().is_err());
}
check("marzipan:80");
check("1.2.3.4:90-80");
check("1.2.3.4/100:8888");
check("[1.2.3.4]/16:80");
check("[::1]/130:8888");
}
#[test]
fn test_rule_matches() {
fn check(address: &str, yes: &[&str], no: &[&str]) {
use std::net::SocketAddr;
let policy = address.parse::<AddressPortPattern>().unwrap();
for s in yes {
let sa = s.parse::<SocketAddr>().unwrap();
assert!(policy.matches_sockaddr(&sa));
}
for s in no {
let sa = s.parse::<SocketAddr>().unwrap();
assert!(!policy.matches_sockaddr(&sa));
}
}
check(
"1.2.3.4/16:80",
&["1.2.3.4:80", "1.2.44.55:80"],
&["9.9.9.9:80", "1.3.3.4:80", "1.2.3.4:81"],
);
check(
"*:443-8000",
&["1.2.3.4:443", "[::1]:500"],
&["9.0.0.0:80", "[::1]:80"],
);
check(
"[face::]/8:80",
&["[fab0::7]:80"],
&["[dd00::]:80", "[face::7]:443"],
);
check("0.0.0.0/0:*", &["127.0.0.1:80"], &["[f00b::]:80"]);
check("[::]/0:*", &["[f00b::]:80"], &["127.0.0.1:80"]);
}
#[test]
fn test_policy_matches() -> Result<(), PolicyError> {
let mut policy = AddressPolicy::default();
policy.push(AddressPolicyAction::Accept, "*:443".parse()?);
policy.push(AddressPolicyAction::Accept, "[::1]:80".parse()?);
policy.push(AddressPolicyAction::Reject, "*:80".parse()?);
let policy = policy; // drop mut
assert!(policy
.allows_sockaddr(&"[::6]:443".parse().unwrap())
.unwrap());
assert!(policy
.allows_sockaddr(&"127.0.0.1:443".parse().unwrap())
.unwrap());
assert!(policy
.allows_sockaddr(&"[::1]:80".parse().unwrap())
.unwrap());
assert!(!policy
.allows_sockaddr(&"[::2]:80".parse().unwrap())
.unwrap());
assert!(!policy
.allows_sockaddr(&"127.0.0.1:80".parse().unwrap())
.unwrap());
assert!(policy
.allows_sockaddr(&"127.0.0.1:66".parse().unwrap())
.is_none());
Ok(())
}
#[test]
fn parse_portrange() {
assert_eq!(
"1-100".parse::<PortRange>().unwrap(),
PortRange::new(1, 100).unwrap()
);
assert_eq!(
"01-100".parse::<PortRange>().unwrap(),
PortRange::new(1, 100).unwrap()
);
assert_eq!(
"1-65535".parse::<PortRange>().unwrap(),
PortRange::new_all()
);
assert_eq!(
"10-30".parse::<PortRange>().unwrap(),
PortRange::new(10, 30).unwrap()
);
assert_eq!(
"9001".parse::<PortRange>().unwrap(),
PortRange::new(9001, 9001).unwrap()
);
assert_eq!(
"9001-9001".parse::<PortRange>().unwrap(),
PortRange::new(9001, 9001).unwrap()
);
assert_eq!("*".parse::<PortRange>().unwrap(), PortRange::new_all());
assert!("hello".parse::<PortRange>().is_err());
assert!("0".parse::<PortRange>().is_err());
assert!("65536".parse::<PortRange>().is_err());
assert!("65537".parse::<PortRange>().is_err());
assert!("1-2-3".parse::<PortRange>().is_err());
assert!("10-5".parse::<PortRange>().is_err());
assert!("1-".parse::<PortRange>().is_err());
assert!("-2".parse::<PortRange>().is_err());
assert!("-".parse::<PortRange>().is_err());
}
#[test]
fn test_portrange() {
assert!(PortRange::new_all().is_all());
assert!(!PortRange::new(2, 65535).unwrap().is_all());
assert!(PortRange::new_all().contains(1));
assert!(PortRange::new_all().contains(65535));
assert!(PortRange::new_all().contains(7777));
assert!(PortRange::new(20, 30).unwrap().contains(20));
assert!(PortRange::new(20, 30).unwrap().contains(25));
assert!(PortRange::new(20, 30).unwrap().contains(30));
assert!(!PortRange::new(20, 30).unwrap().contains(19));
assert!(!PortRange::new(20, 30).unwrap().contains(31));
}
// this test exists due to manually implemented 'stringified_ip_pattern' on 'AddressPortPattern'
#[test]
fn policy_serde_json_roundtrip() {
let policy = AddressPolicy::parse_from_torrc(
r#"
ExitPolicy reject 1.2.3.4/32:*
ExitPolicy reject 1.2.3.5:*
ExitPolicy reject 1.2.3.6/16:*
ExitPolicy reject 1.2.3.6/16:123-456
ExitPolicy accept *:53
ExitPolicy accept6 *6:119
ExitPolicy accept *4:120
ExitPolicy reject6 [FC00::]/7:*
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8328:1234
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8328/64:1235
ExitPolicy reject *:*"#,
)
.unwrap();
let json = serde_json::to_string(&policy).unwrap();
let recovered: AddressPolicy = serde_json::from_str(&json).unwrap();
assert_eq!(recovered, policy);
}
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::EXIT_POLICY_FIELD_NAME;
use std::net::AddrParseError;
use thiserror::Error;
/// Error from an unparsable or invalid policy.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum PolicyError {
#[cfg(feature = "client")]
#[error("failed to fetch the remote policy: {source}")]
ClientError {
#[from]
source: reqwest::Error,
},
#[error("/{mask} is not a valid mask for an IpV4 address")]
InvalidIpV4Mask { mask: u8 },
#[error("/{mask} is not a valid mask for an IpV6 address")]
InvalidIpV6Mask { mask: u8 },
#[error("'{action}' is not a valid policy action")]
InvalidPolicyAction { action: String },
#[error("'{addr}' is not a valid Ip address: {source}")]
MalformedIpAddress {
addr: String,
#[source]
source: AddrParseError,
},
/// Attempted to use a bitmask with the address "*".
#[error("attempted to use a bitmask ('/{mask}') with the address '*'")]
MaskWithStar { mask: String },
/// Attempted to use a bitmask with the address "*4".
#[error("attempted to use a bitmask ('/{mask}') with the address '*4'")]
MaskWithV4Star { mask: String },
/// Attempted to use a bitmask with the address "*6".
#[error("attempted to use a bitmask ('/{mask}') with the address '*6'")]
MaskWithV6Star { mask: String },
#[error("'/{mask}' is not a valid mask")]
InvalidMask { mask: String },
/// A port was not a number in the range 1..65535
#[error(
"the provided port '{raw}' was either malformed or was not in the valid 1..65535 range"
)]
InvalidPort { raw: String },
/// A port range had its starting-point higher than its ending point.
#[error("the provided port range ({start}-{end}) was invalid. either the start was 0 or it was greater than the end.")]
InvalidRange { start: u16, end: u16 },
#[error("could not parse '{raw}' into a valid policy address:port pattern")]
MalformedAddressPortPattern { raw: String },
#[error("could not parse '{raw}' into a valid address policy")]
MalformedAddressPolicy { raw: String },
#[error(
"the provided exit policy entry does not start with the expected '{}' prefix: '{entry}'",
EXIT_POLICY_FIELD_NAME
)]
NoExitPolicyPrefix { entry: String },
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// adapted from: https://github.com/dgoulet-tor/arti/tree/781dc4bd64f515f0c13ae9907c473c2bad8fbf71
// and https://github.com/torproject/tor/blob/3cb6a690be60fcdab60130402ff88dcfc0657596/contrib/or-tools/exitlist
// + https://github.com/torproject/tor/blob/3cb6a690be60fcdab60130402ff88dcfc0657596/src/feature/dirparse/policy_parse.c
mod address_policy;
mod error;
pub use address_policy::{
AddressPolicy, AddressPolicyAction, AddressPolicyRule, AddressPortPattern, IpPattern, PortRange,
};
pub use error::PolicyError;
+4
View File
@@ -386,12 +386,16 @@ pub fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
let mut path_segments = url
.path_segments_mut()
.expect("provided validator url does not have a base!");
path_segments.pop_if_empty();
for segment in segments {
let segment = segment.strip_prefix('/').unwrap_or(segment);
let segment = segment.strip_suffix('/').unwrap_or(segment);
path_segments.push(segment);
}
// I don't understand why compiler couldn't figure out that it's no longer used
// and can be dropped
drop(path_segments);
@@ -9,11 +9,12 @@ use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
#[derive(Clone, Default)]
pub struct AtomicVerlocResult {
inner: Arc<RwLock<VerlocResult>>,
}
#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Clone, Serialize, Default)]
pub struct VerlocResult {
total_tested: usize,
#[serde(with = "humantime_serde")]
@@ -35,13 +36,6 @@ impl AtomicVerlocResult {
}
}
// this could have also been achieved with a normal #[derive(Clone)] but I prefer to be explicit about it
pub(crate) fn clone_data_pointer(&self) -> Self {
AtomicVerlocResult {
inner: Arc::clone(&self.inner),
}
}
pub(crate) async fn reset_results(&self, new_tested: usize) {
let mut write_permit = self.inner.write().await;
write_permit.total_tested = new_tested;
+1 -1
View File
@@ -226,7 +226,7 @@ impl VerlocMeasurer {
}
pub fn get_verloc_results_pointer(&self) -> AtomicVerlocResult {
self.results.clone_data_pointer()
self.results.clone()
}
fn start_listening(&self) -> JoinHandle<()> {
+6
View File
@@ -27,6 +27,10 @@ pub const NYXD_URL: &str = "https://rpc.nymtech.net";
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/";
// I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging
pub const EXIT_POLICY_URL: &str =
"https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(NYXD_URL, Some(NYM_API))]
}
@@ -101,6 +105,7 @@ pub fn export_to_env() {
set_var_to_default(var_names::NYXD, NYXD_URL);
set_var_to_default(var_names::NYM_API, NYM_API);
set_var_to_default(var_names::EXPLORER_API, EXPLORER_API);
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
}
pub fn export_to_env_if_not_set() {
@@ -148,4 +153,5 @@ pub fn export_to_env_if_not_set() {
set_var_conditionally_to_default(var_names::NYXD, NYXD_URL);
set_var_conditionally_to_default(var_names::NYM_API, NYM_API);
set_var_conditionally_to_default(var_names::EXPLORER_API, EXPLORER_API);
set_var_conditionally_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
}
+1
View File
@@ -27,6 +27,7 @@ pub const NAME_SERVICE_CONTRACT_ADDRESS: &str = "NAME_SERVICE_CONTRACT_ADDRESS";
pub const NYXD: &str = "NYXD";
pub const NYM_API: &str = "NYM_API";
pub const EXPLORER_API: &str = "EXPLORER_API";
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION";
+1
View File
@@ -9,6 +9,7 @@ edition = "2021"
[dependencies]
bincode = "1.3.3"
log = { workspace = true }
nym-exit-policy = { path = "../../../common/exit-policy"}
nym-service-providers-common = { path = "../../../service-providers/common" }
nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
serde = { workspace = true, features = ["derive"] }
+2
View File
@@ -103,9 +103,11 @@ pub struct SendRequest {
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub enum QueryRequest {
OpenProxy,
Description,
ExitPolicy,
}
#[derive(Debug, Clone)]
+52
View File
@@ -5,6 +5,7 @@ use crate::{
make_bincode_serializer, ConnectionId, InsufficientSocketDataError, SocketData,
Socks5ProtocolVersion, Socks5RequestError,
};
use nym_exit_policy::ExitPolicy;
use nym_service_providers_common::interface::{Serializable, ServiceProviderResponse};
use serde::{Deserialize, Serialize};
use tap::TapFallible;
@@ -155,6 +156,18 @@ impl Socks5Response {
content: Socks5ResponseContent::Query(query_response),
}
}
pub fn new_query_error<S: Into<String>>(
protocol_version: Socks5ProtocolVersion,
message: S,
) -> Socks5Response {
Socks5Response {
protocol_version,
content: Socks5ResponseContent::Query(QueryResponse::Error {
message: message.into(),
}),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -296,9 +309,18 @@ impl ConnectionError {
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub enum QueryResponse {
OpenProxy(bool),
Description(String),
ExitPolicy {
enabled: bool,
upstream: String,
policy: Option<ExitPolicy>,
},
Error {
message: String,
},
}
#[cfg(test)]
@@ -364,11 +386,41 @@ mod tests {
let bytes_description = description.clone().into_bytes();
assert_eq!(bytes_description, vec![3, 1, 3, 102, 111, 111]);
let error = Socks5ResponseContent::Query(QueryResponse::Error {
message: "this is an error".to_string(),
});
let bytes_error = error.clone().into_bytes();
assert_eq!(
bytes_error,
vec![
3, 3, 16, 116, 104, 105, 115, 32, 105, 115, 32, 97, 110, 32, 101, 114, 114,
111, 114
]
);
let exit_policy = Socks5ResponseContent::Query(QueryResponse::ExitPolicy {
enabled: false,
upstream: "http://foo.bar".to_string(),
policy: Some(ExitPolicy::new_open()),
});
let bytes_exit_policy = exit_policy.clone().into_bytes();
assert_eq!(
bytes_exit_policy,
vec![
3, 2, 0, 14, 104, 116, 116, 112, 58, 47, 47, 102, 111, 111, 46, 98, 97, 114, 1,
1, 0, 1, 42, 1, 251, 255, 255
]
);
let open_proxy2 = Socks5ResponseContent::try_from_bytes(&bytes_open_proxy).unwrap();
let description2 = Socks5ResponseContent::try_from_bytes(&bytes_description).unwrap();
let error2 = Socks5ResponseContent::try_from_bytes(&bytes_error).unwrap();
let exit_policy2 = Socks5ResponseContent::try_from_bytes(&bytes_exit_policy).unwrap();
assert_eq!(open_proxy, open_proxy2);
assert_eq!(description, description2);
assert_eq!(error, error2);
assert_eq!(exit_policy, exit_policy2);
}
}
}
+28 -1
View File
@@ -119,6 +119,7 @@ pub struct GatewayNetworkRequesterDetails {
pub encryption_key: String,
pub open_proxy: bool,
pub exit_policy: bool,
pub enabled_statistics: bool,
// just a convenience wrapper around all the keys
@@ -140,9 +141,35 @@ impl fmt::Display for GatewayNetworkRequesterDetails {
writeln!(f, "\taddress: {}", self.address)?;
writeln!(f, "\tuses open proxy: {}", self.open_proxy)?;
writeln!(f, "\tuses exit policy: {}", self.exit_policy)?;
writeln!(f, "\tsends statistics: {}", self.enabled_statistics)?;
writeln!(f, "\tallow list path: {}", self.allow_list_path)?;
writeln!(f, "\tunknown list path: {}", self.allow_list_path)
writeln!(f, "\tunknown list path: {}", self.unknown_list_path)
}
}
#[derive(Serialize, Deserialize)]
pub struct GatewayIpPacketRouterDetails {
pub enabled: bool,
pub identity_key: String,
pub encryption_key: String,
// just a convenience wrapper around all the keys
pub address: String,
pub config_path: String,
}
impl fmt::Display for GatewayIpPacketRouterDetails {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "IP packet router:")?;
writeln!(f, "\tenabled: {}", self.enabled)?;
writeln!(f, "\tconfig path: {}", self.config_path)?;
writeln!(f, "\tidentity key: {}", self.identity_key)?;
writeln!(f, "\tencryption key: {}", self.encryption_key)?;
writeln!(f, "\taddress: {}", self.address)
}
}
+2
View File
@@ -13,3 +13,5 @@ pub use registration::{
#[cfg(feature = "verify")]
pub use registration::HmacSha256;
pub const WG_PORT: u16 = 51822;
+10 -25
View File
@@ -6,7 +6,6 @@ use crate::PeerPublicKey;
use base64::{engine::general_purpose, Engine};
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use std::{fmt, ops::Deref, str::FromStr};
#[cfg(feature = "verify")]
@@ -54,11 +53,17 @@ impl InitMessage {
#[serde(tag = "type", rename_all = "camelCase")]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub enum ClientRegistrationResponse {
PendingRegistration { nonce: u64 },
Registered { success: bool },
PendingRegistration {
nonce: u64,
gateway_data: GatewayClient,
wg_port: u16,
},
Registered {
success: bool,
},
}
/// Client that wants to register sends its PublicKey and SocketAddr bytes mac digest encrypted with a DH shared secret.
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
/// Gateway/Nym node can then verify pub_key payload using the same process
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
@@ -67,10 +72,6 @@ pub struct GatewayClient {
#[cfg_attr(feature = "openapi", schema(value_type = String, format = Byte))]
pub pub_key: PeerPublicKey,
/// Client's socket address
#[cfg_attr(feature = "openapi", schema(example = "1.2.3.4:51820", value_type = String))]
pub socket: SocketAddr,
/// Sha256 hmac on the data (alongside the prior nonce)
#[cfg_attr(feature = "openapi", schema(value_type = String, format = Byte))]
pub mac: ClientMac,
@@ -78,12 +79,7 @@ pub struct GatewayClient {
impl GatewayClient {
#[cfg(feature = "verify")]
pub fn new(
local_secret: &PrivateKey,
remote_public: PublicKey,
socket_address: SocketAddr,
nonce: u64,
) -> Self {
pub fn new(local_secret: &PrivateKey, remote_public: PublicKey, nonce: u64) -> Self {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = boringtun::x25519::StaticSecret::try_from(local_secret.to_bytes())
@@ -100,13 +96,10 @@ impl GatewayClient {
.expect("x25519 shared secret is always 32 bytes long");
mac.update(local_public.as_bytes());
mac.update(socket_address.ip().to_string().as_bytes());
mac.update(socket_address.port().to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
GatewayClient {
pub_key: PeerPublicKey::new(local_public),
socket: socket_address,
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
}
}
@@ -128,8 +121,6 @@ impl GatewayClient {
.expect("x25519 shared secret is always 32 bytes long");
mac.update(self.pub_key.as_bytes());
mac.update(self.socket.ip().to_string().as_bytes());
mac.update(self.socket.port().to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
mac.verify_slice(&self.mac)
@@ -142,10 +133,6 @@ impl GatewayClient {
pub fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
pub fn socket(&self) -> SocketAddr {
self.socket
}
}
// TODO: change the inner type into generic array of size HmacSha256::OutputSize
@@ -217,13 +204,11 @@ mod tests {
let gateway_key_pair = encryption::KeyPair::new(&mut rng);
let client_key_pair = encryption::KeyPair::new(&mut rng);
let socket: SocketAddr = "1.2.3.4:5678".parse().unwrap();
let nonce = 1234567890;
let client = GatewayClient::new(
client_key_pair.private_key(),
*gateway_key_pair.public_key(),
socket,
nonce,
);
assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok())
+3 -2
View File
@@ -26,11 +26,12 @@ ip_network = "0.4.1"
ip_network_table = "0.2.0"
log.workspace = true
nym-task = { path = "../task" }
nym-wireguard-types = { path = "../wireguard-types" }
rand.workspace = true
serde = { workspace = true, features = ["derive"] }
tap.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
serde = { workspace = true, features = ["derive"] }
nym-wireguard-types = { path = "../wireguard-types" }
[target.'cfg(target_os = "linux")'.dependencies]
tokio-tun = "0.9.0"
+27 -5
View File
@@ -9,8 +9,30 @@ use tokio::sync::mpsc::{self};
use crate::event::Event;
pub(crate) type PeersByKey = DashMap<x25519::PublicKey, mpsc::UnboundedSender<Event>>;
pub(crate) type PeersByAddr = DashMap<SocketAddr, mpsc::UnboundedSender<Event>>;
// Channels that are used to communicate with the various tunnels
#[derive(Clone)]
pub struct PeerEventSender(mpsc::Sender<Event>);
pub(crate) struct PeerEventReceiver(mpsc::Receiver<Event>);
impl PeerEventSender {
pub(crate) async fn send(&self, event: Event) -> Result<(), mpsc::error::SendError<Event>> {
self.0.send(event).await
}
}
impl PeerEventReceiver {
pub(crate) async fn recv(&mut self) -> Option<Event> {
self.0.recv().await
}
}
pub(crate) fn peer_event_channel() -> (PeerEventSender, PeerEventReceiver) {
let (tx, rx) = mpsc::channel(16);
(PeerEventSender(tx), PeerEventReceiver(rx))
}
pub(crate) type PeersByKey = DashMap<x25519::PublicKey, PeerEventSender>;
pub(crate) type PeersByAddr = DashMap<SocketAddr, PeerEventSender>;
#[derive(Default)]
pub(crate) struct ActivePeers {
@@ -30,7 +52,7 @@ impl ActivePeers {
&self,
public_key: x25519::PublicKey,
addr: SocketAddr,
peer_tx: mpsc::UnboundedSender<Event>,
peer_tx: PeerEventSender,
) {
self.active_peers.insert(public_key, peer_tx.clone());
self.active_peers_by_addr.insert(addr, peer_tx);
@@ -39,14 +61,14 @@ impl ActivePeers {
pub(crate) fn get_by_key_mut(
&self,
public_key: &x25519::PublicKey,
) -> Option<RefMut<'_, x25519::PublicKey, mpsc::UnboundedSender<Event>>> {
) -> Option<RefMut<'_, x25519::PublicKey, PeerEventSender>> {
self.active_peers.get_mut(public_key)
}
pub(crate) fn get_by_addr(
&self,
addr: &SocketAddr,
) -> Option<Ref<'_, SocketAddr, mpsc::UnboundedSender<Event>>> {
) -> Option<Ref<'_, SocketAddr, PeerEventSender>> {
self.active_peers_by_addr.get(addr)
}
}
+33 -13
View File
@@ -7,9 +7,11 @@ mod active_peers;
mod error;
mod event;
mod network_table;
mod packet_relayer;
mod platform;
mod registered_peers;
mod setup;
pub mod tun_task_channel;
mod udp_listener;
mod wg_tunnel;
@@ -18,16 +20,7 @@ use std::sync::Arc;
// Currently the module related to setting up the virtual network device is platform specific.
#[cfg(target_os = "linux")]
use platform::linux::tun_device;
#[derive(Clone)]
pub struct TunTaskTx(tokio::sync::mpsc::UnboundedSender<Vec<u8>>);
impl TunTaskTx {
fn send(&self, packet: Vec<u8>) -> Result<(), tokio::sync::mpsc::error::SendError<Vec<u8>>> {
self.0.send(packet)
}
}
pub use platform::linux::tun_device;
/// Start wireguard UDP listener and TUN device
///
@@ -39,16 +32,43 @@ pub async fn start_wireguard(
task_client: nym_task::TaskClient,
gateway_client_registry: Arc<GatewayClientRegistry>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let peers_by_ip = Arc::new(std::sync::Mutex::new(network_table::NetworkTable::new()));
// TODO: make this configurable
// We can optionally index peers by their IP like standard wireguard. If we don't then we do
// plain NAT where we match incoming destination IP with outgoing source IP.
let peers_by_ip = Arc::new(tokio::sync::Mutex::new(network_table::NetworkTable::new()));
// Alternative 1:
let routing_mode = tun_device::RoutingMode::new_allowed_ips(peers_by_ip.clone());
// Alternative 2:
//let routing_mode = tun_device::RoutingMode::new_nat();
// Start the tun device that is used to relay traffic outbound
let (tun, tun_task_tx) = tun_device::TunDevice::new(peers_by_ip.clone());
let (tun, tun_task_tx, tun_task_response_rx) = tun_device::TunDevice::new(routing_mode);
tun.start();
// We also index peers by a tag
let peers_by_tag = Arc::new(tokio::sync::Mutex::new(wg_tunnel::PeersByTag::new()));
// If we want to have the tun device on a separate host, it's the tun_task and
// tun_task_response channels that needs to be sent over the network to the host where the tun
// device is running.
// The packet relayer's responsibility is to route packets between the correct tunnel and the
// tun device. The tun device may or may not be on a separate host, which is why we can't do
// this routing in the tun device itself.
let (packet_relayer, packet_tx) = packet_relayer::PacketRelayer::new(
tun_task_tx.clone(),
tun_task_response_rx,
peers_by_tag.clone(),
);
packet_relayer.start();
// Start the UDP listener that clients connect to
let udp_listener = udp_listener::WgUdpListener::new(
tun_task_tx,
packet_tx,
peers_by_ip,
peers_by_tag,
Arc::clone(&gateway_client_registry),
)
.await?;
+76
View File
@@ -0,0 +1,76 @@
use std::{collections::HashMap, sync::Arc};
use tap::TapFallible;
use tokio::sync::mpsc::{self};
use crate::{
active_peers::PeerEventSender,
event::Event,
tun_task_channel::{TunTaskResponseRx, TunTaskTx},
};
#[derive(Clone)]
pub struct PacketRelaySender(pub(crate) mpsc::Sender<(u64, Vec<u8>)>);
pub(crate) struct PacketRelayReceiver(pub(crate) mpsc::Receiver<(u64, Vec<u8>)>);
pub(crate) fn packet_relay_channel() -> (PacketRelaySender, PacketRelayReceiver) {
let (tx, rx) = mpsc::channel(16);
(PacketRelaySender(tx), PacketRelayReceiver(rx))
}
// The tunnels send packets to the packet relayer, which then relays it to the tun device. And
// conversely, it's where the tun device send responses to, which are relayed back to the correct
// tunnel.
pub(crate) struct PacketRelayer {
// Receive packets from the various tunnels
packet_rx: PacketRelayReceiver,
// After receive from tunnels, send to the tun device
tun_task_tx: TunTaskTx,
// Receive responses from the tun device
tun_task_response_rx: TunTaskResponseRx,
// After receiving from the tun device, relay back to the correct tunnel
peers_by_tag: Arc<tokio::sync::Mutex<HashMap<u64, PeerEventSender>>>,
}
impl PacketRelayer {
pub(crate) fn new(
tun_task_tx: TunTaskTx,
tun_task_response_rx: TunTaskResponseRx,
peers_by_tag: Arc<tokio::sync::Mutex<HashMap<u64, PeerEventSender>>>,
) -> (Self, PacketRelaySender) {
let (packet_tx, packet_rx) = packet_relay_channel();
(
Self {
packet_rx,
tun_task_tx,
tun_task_response_rx,
peers_by_tag,
},
packet_tx,
)
}
pub(crate) async fn run(mut self) {
loop {
tokio::select! {
Some((tag, packet)) = self.packet_rx.0.recv() => {
log::info!("Sent packet to tun device with tag: {tag}");
self.tun_task_tx.send((tag, packet)).await.tap_err(|e| log::error!("{e}")).ok();
},
Some((tag, packet)) = self.tun_task_response_rx.recv() => {
log::info!("Received response from tun device with tag: {tag}");
if let Some(tx) = self.peers_by_tag.lock().await.get(&tag) {
tx.send(Event::Ip(packet.into())).await.tap_err(|e| log::error!("{e}")).ok();
}
}
}
}
}
pub(crate) fn start(self) {
tokio::spawn(async move { self.run().await });
}
}
+1 -1
View File
@@ -1 +1 @@
pub(crate) mod tun_device;
pub mod tun_device;
+131 -49
View File
@@ -1,17 +1,21 @@
use std::{net::Ipv4Addr, sync::Arc};
use std::{
collections::HashMap,
net::{IpAddr, Ipv4Addr},
sync::Arc,
};
use etherparse::{InternetSlice, SlicedPacket};
use tap::TapFallible;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
sync::mpsc::{self},
};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::{
event::Event,
setup::{TUN_BASE_NAME, TUN_DEVICE_ADDRESS, TUN_DEVICE_NETMASK},
tun_task_channel::{
tun_task_channel, tun_task_response_channel, TunTaskPayload, TunTaskResponseRx,
TunTaskResponseTx, TunTaskRx, TunTaskTx,
},
udp_listener::PeersByIp,
TunTaskTx,
};
fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> tokio_tun::Tun {
@@ -33,15 +37,48 @@ pub struct TunDevice {
tun: tokio_tun::Tun,
// Incoming data that we should send
tun_task_rx: mpsc::UnboundedReceiver<Vec<u8>>,
tun_task_rx: TunTaskRx,
// The routing table.
// An alternative would be to do NAT by just matching incoming with outgoing.
peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>,
// And when we get replies, this is where we should send it
tun_task_response_tx: TunTaskResponseTx,
routing_mode: RoutingMode,
}
pub enum RoutingMode {
// The routing table, as how wireguard does it
AllowedIps(AllowedIpsInner),
// This is an alternative to the routing table, where we just match outgoing source IP with
// incoming destination IP.
Nat(NatInner),
}
impl RoutingMode {
pub fn new_nat() -> Self {
RoutingMode::Nat(NatInner {
nat_table: HashMap::new(),
})
}
pub fn new_allowed_ips(peers_by_ip: Arc<tokio::sync::Mutex<PeersByIp>>) -> Self {
RoutingMode::AllowedIps(AllowedIpsInner { peers_by_ip })
}
}
pub struct AllowedIpsInner {
peers_by_ip: Arc<tokio::sync::Mutex<PeersByIp>>,
}
pub struct NatInner {
nat_table: HashMap<IpAddr, u64>,
}
impl TunDevice {
pub fn new(peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>) -> (Self, TunTaskTx) {
pub fn new(
routing_mode: RoutingMode,
// peers_by_ip: Option<Arc<tokio::sync::Mutex<PeersByIp>>>,
) -> (Self, TunTaskTx, TunTaskResponseRx) {
let tun = setup_tokio_tun_device(
format!("{TUN_BASE_NAME}%d").as_str(),
TUN_DEVICE_ADDRESS.parse().unwrap(),
@@ -50,25 +87,58 @@ impl TunDevice {
log::info!("Created TUN device: {}", tun.name());
// Channels to communicate with the other tasks
let (tun_task_tx, tun_task_rx) = mpsc::unbounded_channel::<Vec<u8>>();
let tun_task_tx = TunTaskTx(tun_task_tx);
let (tun_task_tx, tun_task_rx) = tun_task_channel();
let (tun_task_response_tx, tun_task_response_rx) = tun_task_response_channel();
let tun_device = TunDevice {
tun_task_rx,
tun_task_response_tx,
tun,
peers_by_ip,
routing_mode,
};
(tun_device, tun_task_tx)
(tun_device, tun_task_tx, tun_task_response_rx)
}
fn handle_tun_read(&self, packet: &[u8]) {
let dst_addr = boringtun::noise::Tunn::dst_address(packet).unwrap();
// Send outbound packets out on the wild internet
async fn handle_tun_write(&mut self, data: TunTaskPayload) {
let (tag, packet) = data;
let Some(dst_addr) = boringtun::noise::Tunn::dst_address(&packet) else {
log::error!("Unable to parse dst_address in packet that was supposed to be written to tun device");
return;
};
let Some(src_addr) = parse_src_address(&packet) else {
log::error!("Unable to parse src_address in packet that was supposed to be written to tun device");
return;
};
log::info!(
"iface: write Packet({src_addr} -> {dst_addr}, {} bytes)",
packet.len()
);
let headers = SlicedPacket::from_ip(packet).unwrap();
let src_addr = match headers.ip.unwrap() {
InternetSlice::Ipv4(ip, _) => ip.source_addr().to_string(),
InternetSlice::Ipv6(ip, _) => ip.source_addr().to_string(),
// TODO: expire old entries
if let RoutingMode::Nat(nat_table) = &mut self.routing_mode {
nat_table.nat_table.insert(src_addr, tag);
}
self.tun
.write_all(&packet)
.await
.tap_err(|err| {
log::error!("iface: write error: {err}");
})
.ok();
}
// Receive reponse packets from the wild internet
async fn handle_tun_read(&self, packet: &[u8]) {
let Some(dst_addr) = boringtun::noise::Tunn::dst_address(packet) else {
log::error!("Unable to parse dst_address in packet that was read from tun device");
return;
};
let Some(src_addr) = parse_src_address(packet) else {
log::error!("Unable to parse src_address in packet that was read from tun device");
return;
};
log::info!(
"iface: read Packet({src_addr} -> {dst_addr}, {} bytes)",
@@ -76,35 +146,37 @@ impl TunDevice {
);
// Route packet to the correct peer.
if let Some(peer_tx) = self
.peers_by_ip
.lock()
.unwrap()
.longest_match(dst_addr)
.map(|(_, tx)| tx)
{
log::info!("Forward packet to wg tunnel");
peer_tx
.send(Event::Ip(packet.to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.unwrap();
} else {
log::info!("No peer found, packet dropped");
match self.routing_mode {
// This is how wireguard does it, by consulting the AllowedIPs table.
RoutingMode::AllowedIps(ref peers_by_ip) => {
let peers = peers_by_ip.peers_by_ip.as_ref().lock().await;
if let Some(peer_tx) = peers.longest_match(dst_addr).map(|(_, tx)| tx) {
log::info!("Forward packet to wg tunnel");
peer_tx
.send(Event::Ip(packet.to_vec().into()))
.await
.tap_err(|err| log::error!("{err}"))
.ok();
return;
}
}
// But we do it by consulting the NAT table.
RoutingMode::Nat(ref nat_table) => {
if let Some(tag) = nat_table.nat_table.get(&dst_addr) {
log::info!("Forward packet to wg tunnel with tag: {tag}");
self.tun_task_response_tx
.send((*tag, packet.to_vec()))
.await
.tap_err(|err| log::error!("{err}"))
.ok();
return;
}
}
}
}
async fn handle_tun_write(&mut self, data: Vec<u8>) {
let headers = SlicedPacket::from_ip(&data).unwrap();
let (source_addr, destination_addr) = match headers.ip.unwrap() {
InternetSlice::Ipv4(ip, _) => (ip.source_addr(), ip.destination_addr()),
InternetSlice::Ipv6(_, _) => unimplemented!(),
};
log::info!(
"iface: write Packet({source_addr} -> {destination_addr}, {} bytes)",
data.len()
);
self.tun.write_all(&data).await.unwrap();
log::info!("No peer found, packet dropped");
}
pub async fn run(mut self) {
@@ -116,7 +188,7 @@ impl TunDevice {
len = self.tun.read(&mut buf) => match len {
Ok(len) => {
let packet = &buf[..len];
self.handle_tun_read(packet);
self.handle_tun_read(packet).await;
},
Err(err) => {
log::info!("iface: read error: {err}");
@@ -136,3 +208,13 @@ impl TunDevice {
tokio::spawn(async move { self.run().await });
}
}
fn parse_src_address(packet: &[u8]) -> Option<IpAddr> {
let headers = SlicedPacket::from_ip(packet)
.tap_err(|err| log::error!("Unable to parse IP packet: {err:?}"))
.ok()?;
Some(match headers.ip? {
InternetSlice::Ipv4(ip, _) => ip.source_addr().into(),
InternetSlice::Ipv6(ip, _) => ip.source_addr().into(),
})
}
-1
View File
@@ -6,7 +6,6 @@ use log::info;
// The wireguard UDP listener
pub const WG_ADDRESS: &str = "0.0.0.0";
pub const WG_PORT: u16 = 51822;
// The interface used to route traffic
pub const TUN_BASE_NAME: &str = "nymtun";
+54
View File
@@ -0,0 +1,54 @@
use tokio::sync::mpsc;
pub(crate) type TunTaskPayload = (u64, Vec<u8>);
#[derive(Clone)]
pub struct TunTaskTx(mpsc::Sender<TunTaskPayload>);
pub(crate) struct TunTaskRx(mpsc::Receiver<TunTaskPayload>);
impl TunTaskTx {
pub async fn send(
&self,
data: TunTaskPayload,
) -> Result<(), tokio::sync::mpsc::error::SendError<TunTaskPayload>> {
self.0.send(data).await
}
}
impl TunTaskRx {
pub(crate) async fn recv(&mut self) -> Option<TunTaskPayload> {
self.0.recv().await
}
}
pub(crate) fn tun_task_channel() -> (TunTaskTx, TunTaskRx) {
let (tun_task_tx, tun_task_rx) = tokio::sync::mpsc::channel(16);
(TunTaskTx(tun_task_tx), TunTaskRx(tun_task_rx))
}
// Send responses back from the tun device back to the PacketRelayer
pub(crate) struct TunTaskResponseTx(mpsc::Sender<TunTaskPayload>);
pub struct TunTaskResponseRx(mpsc::Receiver<TunTaskPayload>);
impl TunTaskResponseTx {
pub(crate) async fn send(
&self,
data: TunTaskPayload,
) -> Result<(), tokio::sync::mpsc::error::SendError<TunTaskPayload>> {
self.0.send(data).await
}
}
impl TunTaskResponseRx {
pub async fn recv(&mut self) -> Option<TunTaskPayload> {
self.0.recv().await
}
}
pub(crate) fn tun_task_response_channel() -> (TunTaskResponseTx, TunTaskResponseRx) {
let (tun_task_tx, tun_task_rx) = tokio::sync::mpsc::channel(16);
(
TunTaskResponseTx(tun_task_tx),
TunTaskResponseRx(tun_task_rx),
)
}
+25 -20
View File
@@ -7,30 +7,25 @@ use boringtun::{
use futures::StreamExt;
use log::error;
use nym_task::TaskClient;
use nym_wireguard_types::{registration::GatewayClientRegistry, PeerPublicKey};
use nym_wireguard_types::{registration::GatewayClientRegistry, PeerPublicKey, WG_PORT};
use tap::TapFallible;
use tokio::{
net::UdpSocket,
sync::{
mpsc::{self},
Mutex,
},
};
use tokio::{net::UdpSocket, sync::Mutex};
use crate::{
active_peers::ActivePeers,
active_peers::{ActivePeers, PeerEventSender},
error::WgError,
event::Event,
network_table::NetworkTable,
packet_relayer::PacketRelaySender,
registered_peers::{RegisteredPeer, RegisteredPeers},
setup::{self, WG_ADDRESS, WG_PORT},
TunTaskTx,
setup::{self, WG_ADDRESS},
wg_tunnel::PeersByTag,
};
const MAX_PACKET: usize = 65535;
// Registered peers
pub(crate) type PeersByIp = NetworkTable<mpsc::UnboundedSender<Event>>;
pub(crate) type PeersByIp = NetworkTable<PeerEventSender>;
async fn add_test_peer(registered_peers: &mut RegisteredPeers) {
let peer_static_public = PeerPublicKey::new(setup::peer_static_public_key());
@@ -55,13 +50,16 @@ pub struct WgUdpListener {
registered_peers: RegisteredPeers,
// The routing table, as defined by wireguard
peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>,
peers_by_ip: Arc<tokio::sync::Mutex<PeersByIp>>,
// ... or alternatively we can map peers by their tag
peers_by_tag: Arc<tokio::sync::Mutex<PeersByTag>>,
// The UDP socket to the peer
udp: Arc<UdpSocket>,
// Send data to the TUN device for sending
tun_task_tx: TunTaskTx,
packet_tx: PacketRelaySender,
// Wireguard rate limiter
rate_limiter: RateLimiter,
@@ -71,8 +69,9 @@ pub struct WgUdpListener {
impl WgUdpListener {
pub async fn new(
tun_task_tx: TunTaskTx,
peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>,
packet_tx: PacketRelaySender,
peers_by_ip: Arc<tokio::sync::Mutex<PeersByIp>>,
peers_by_tag: Arc<tokio::sync::Mutex<PeersByTag>>,
gateway_client_registry: Arc<GatewayClientRegistry>,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync + 'static>> {
let wg_address = SocketAddr::new(WG_ADDRESS.parse().unwrap(), WG_PORT);
@@ -94,8 +93,9 @@ impl WgUdpListener {
static_public,
registered_peers,
peers_by_ip,
peers_by_tag,
udp,
tun_task_tx,
packet_tx,
rate_limiter,
gateway_client_registry,
})
@@ -143,6 +143,7 @@ impl WgUdpListener {
log::info!("udp: received {len} bytes from {addr} from known peer");
peer_tx
.send(Event::Wg(buf[..len].to_vec().into()))
.await
.tap_err(|e| log::error!("{e}"))
.ok();
continue;
@@ -186,6 +187,7 @@ impl WgUdpListener {
// We found the peer as connected, even though the addr was not known
log::info!("udp: received {len} bytes from {addr} which is a known peer with unknown addr");
peer_tx.send(Event::WgVerified(buf[..len].to_vec().into()))
.await
.tap_err(|err| log::error!("{err}"))
.ok();
} else {
@@ -194,19 +196,22 @@ impl WgUdpListener {
// NOTE: we are NOT passing in the existing rate_limiter. Re-visit this
// choice later.
log::warn!("Creating new rate limiter, consider re-using?");
let (join_handle, peer_tx) = crate::wg_tunnel::start_wg_tunnel(
let (join_handle, peer_tx, tag) = crate::wg_tunnel::start_wg_tunnel(
addr,
self.udp.clone(),
self.static_private.clone(),
*registered_peer.public_key,
registered_peer.index,
registered_peer.allowed_ips,
self.tun_task_tx.clone(),
// self.tun_task_tx.clone(),
self.packet_tx.clone(),
);
self.peers_by_ip.lock().unwrap().insert(registered_peer.allowed_ips, peer_tx.clone());
self.peers_by_ip.lock().await.insert(registered_peer.allowed_ips, peer_tx.clone());
self.peers_by_tag.lock().await.insert(tag, peer_tx.clone());
peer_tx.send(Event::Wg(buf[..len].to_vec().into()))
.await
.tap_err(|e| log::error!("{e}"))
.ok();
+46 -22
View File
@@ -1,4 +1,4 @@
use std::{net::SocketAddr, sync::Arc, time::Duration};
use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration};
use async_recursion::async_recursion;
use boringtun::{
@@ -7,24 +7,29 @@ use boringtun::{
};
use bytes::Bytes;
use log::{debug, error, info, warn};
use rand::RngCore;
use tap::TapFallible;
use tokio::{
net::UdpSocket,
sync::{broadcast, mpsc},
time::timeout,
};
use tokio::{net::UdpSocket, sync::broadcast, time::timeout};
use crate::{
error::WgError, event::Event, network_table::NetworkTable, registered_peers::PeerIdx, TunTaskTx,
active_peers::{peer_event_channel, PeerEventReceiver, PeerEventSender},
error::WgError,
event::Event,
network_table::NetworkTable,
packet_relayer::PacketRelaySender,
registered_peers::PeerIdx,
};
const HANDSHAKE_MAX_RATE: u64 = 10;
const MAX_PACKET: usize = 65535;
// We index the tunnels by tag
pub(crate) type PeersByTag = HashMap<u64, PeerEventSender>;
pub struct WireGuardTunnel {
// Incoming data from the UDP socket received in the main event loop
peer_rx: mpsc::UnboundedReceiver<Event>,
peer_rx: PeerEventReceiver,
// UDP socket used for sending data
udp: Arc<UdpSocket>,
@@ -43,7 +48,9 @@ pub struct WireGuardTunnel {
close_rx: broadcast::Receiver<()>,
// Send data to the task that handles sending data through the tun device
tun_task_tx: TunTaskTx,
packet_tx: PacketRelaySender,
tag: u64,
}
impl Drop for WireGuardTunnel {
@@ -62,8 +69,8 @@ impl WireGuardTunnel {
index: PeerIdx,
peer_allowed_ips: ip_network::IpNetwork,
// rate_limiter: Option<RateLimiter>,
tunnel_tx: TunTaskTx,
) -> (Self, mpsc::UnboundedSender<Event>) {
packet_tx: PacketRelaySender,
) -> (Self, PeerEventSender, u64) {
let local_addr = udp.local_addr().unwrap();
let peer_addr = udp.peer_addr();
log::info!("New wg tunnel: endpoint: {endpoint}, local_addr: {local_addr}, peer_addr: {peer_addr:?}");
@@ -86,11 +93,11 @@ impl WireGuardTunnel {
index,
rate_limiter,
)
.unwrap(),
.expect("failed to create Tunn instance"),
));
// Channels with incoming data that is received by the main event loop
let (peer_tx, peer_rx) = mpsc::unbounded_channel();
let (peer_tx, peer_rx) = peer_event_channel();
// Signal close tunnel
let (close_tx, close_rx) = broadcast::channel(1);
@@ -98,6 +105,8 @@ impl WireGuardTunnel {
let mut allowed_ips = NetworkTable::new();
allowed_ips.insert(peer_allowed_ips, ());
let tag = Self::new_tag();
let tunnel = WireGuardTunnel {
peer_rx,
udp,
@@ -106,10 +115,16 @@ impl WireGuardTunnel {
wg_tunnel,
close_tx,
close_rx,
tun_task_tx: tunnel_tx,
packet_tx,
tag,
};
(tunnel, peer_tx)
(tunnel, peer_tx, tag)
}
fn new_tag() -> u64 {
// TODO: check for collisions
rand::thread_rng().next_u64()
}
fn close(&self) {
@@ -198,14 +213,22 @@ impl WireGuardTunnel {
}
TunnResult::WriteToTunnelV4(packet, addr) => {
if self.allowed_ips.longest_match(addr).is_some() {
self.tun_task_tx.send(packet.to_vec()).unwrap();
self.packet_tx
.0
.send((self.tag, packet.to_vec()))
.await
.unwrap();
} else {
warn!("Packet from {addr} not in allowed_ips");
}
}
TunnResult::WriteToTunnelV6(packet, addr) => {
if self.allowed_ips.longest_match(addr).is_some() {
self.tun_task_tx.send(packet.to_vec()).unwrap();
self.packet_tx
.0
.send((self.tag, packet.to_vec()))
.await
.unwrap();
} else {
warn!("Packet (v6) from {addr} not in allowed_ips");
}
@@ -307,23 +330,24 @@ pub(crate) fn start_wg_tunnel(
peer_static_public: x25519::PublicKey,
peer_index: PeerIdx,
peer_allowed_ips: ip_network::IpNetwork,
tunnel_tx: TunTaskTx,
packet_tx: PacketRelaySender,
) -> (
tokio::task::JoinHandle<x25519::PublicKey>,
mpsc::UnboundedSender<Event>,
PeerEventSender,
u64,
) {
let (mut tunnel, peer_tx) = WireGuardTunnel::new(
let (mut tunnel, peer_tx, tag) = WireGuardTunnel::new(
udp,
endpoint,
static_private,
peer_static_public,
peer_index,
peer_allowed_ips,
tunnel_tx,
packet_tx,
);
let join_handle = tokio::spawn(async move {
tunnel.spin_off().await;
peer_static_public
});
(join_handle, peer_tx)
(join_handle, peer_tx, tag)
}
+1 -1
View File
@@ -1,5 +1,5 @@
opt: wasm
wasm-opt --disable-sign-ext -Os ../target/wasm32-unknown-unknown/release/mixnet_contract.wasm -o ../target/wasm32-unknown-unknown/release/mixnet_contract.wasm
wasm-opt --signext-lowering -Os ../target/wasm32-unknown-unknown/release/mixnet_contract.wasm -o ../target/wasm32-unknown-unknown/release/mixnet_contract.wasm
wasm:
RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
+1 -2
View File
@@ -9,5 +9,4 @@ Each directory contains a readme with more information about running and contrib
## Scripts
* `bump_versions.sh` allows you to update the ~~`platform_release_version` and~~ `wallet_release_version` variable~~s~~ in the `book.toml` of each mdbook project at once. You can also optionally update the `minimum_rust_version` as well. Helpful for lazy-updating when cutting a new version of the docs.
* `build_all_to_dist.sh` is used by the `ci-dev.yml` and `cd-dev.yml` scripts for building all mdbook projects and moving the rendered html to `../dist/` to be rsynced with various servers.
* `post_process.sh` is a script called by the github CI and CD workflows to post process CSS/image/href links for serving several mdbooks from a subdirectory.
+3 -1
View File
@@ -19,4 +19,6 @@ theme/
theme
theme/*
.idea
.idea
notes
+24 -11
View File
@@ -18,14 +18,21 @@
# User Manuals
- [NymConnect Monero](tutorials/monero.md)
- [NymConnect Matrix](tutorials/matrix.md)
- [NymConnect Telegram](tutorials/telegram.md)
- [NymConnect X Monero](tutorials/monero.md)
- [NymConnect X Matrix](tutorials/matrix.md)
- [NymConnect X Telegram](tutorials/telegram.md)
# Code Examples
- [Custom Service Providers](examples/custom-services.md)
- [Apps Using Network Requesters](examples/using-nrs.md)
- [Browser only](examples/browser-only.md)
- [Monorepo examples](examples/monorepo-examples.md)
# Integrations
- [Integration Options](integrations/integration-options.md)
- [Mixnet Integration](integrations/mixnet-integration.md)
[//]: # (- [Mixnet Integration]&#40;integrations/mixnet-integration.md&#41;)
- [Payment Integration](integrations/payment-integration.md)
# Tutorials
@@ -40,15 +47,21 @@
- [Preparing Your Service](tutorials/cosmos-service/service.md)
- [Preparing Your Service pt2](tutorials/cosmos-service/service-src.md)
- [Querying the Chain](tutorials/cosmos-service/querying.md)
- [Typescript](tutorials/typescript.md)
- [Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md)
- [Tutorial Overview](tutorials/simple-service-provider/overview.md)
- [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md)
- [Building Your User Client](tutorials/simple-service-provider/user-client.md)
- [Preparing Your Service Provider Environment](tutorials/simple-service-provider/preparating-env2.md)
- [Building Your Service Provider](tutorials/simple-service-provider/service-provider.md)
- [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md)
- [[DEPRECATED] Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md)
- [Tutorial Overview](tutorials/simple-service-provider/overview.md)
- [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md)
- [Building Your User Client](tutorials/simple-service-provider/user-client.md)
- [Preparing Your Service Provider Environment](tutorials/simple-service-provider/preparating-env2.md)
- [Building Your Service Provider](tutorials/simple-service-provider/service-provider.md)
- [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md)
# Shipyard Builders Hackathon 2023
- [General Info & Resources](shipyard/general.md)
- [Hackathon Challenges](shipyard/challenges-overview.md)
- [A Note on Infrastructure](shipyard/infra.md)
- [Submission Guidelines](shipyard/guidelines.md)
# Events
@@ -1,8 +1,6 @@
# Community Applications
We love seeing our developer community create applications using Nym. If you would like to share your application with the community, please submit a pull request to the `main` branch of the `nymtech/dev-portal` [repository](https://github.com/nymtech/dev-portal).
If you would like to share your application here, please submit a pull request to the `main` branch of the `nymtech/dev-portal` [repository](https://github.com/nymtech/dev-portal).
## <img src='../images/profile_picture/pastenym_ntv_pp.png' style="float: right; width: 75px; height: 75px;">Pastenym
@@ -0,0 +1,10 @@
# Browser only
With the Typescript SDK you can run a Nym client in a webworker - meaning you can connect to the mixnet through the browser without having to worry about any other code than your web framework.
- [NoTrustVerify](https://notrustverify.ch/) have set up an example application using [`mixFetch`](https://sdk.nymtech.net/examples/mix-fetch) to fetch crypto prices from CoinGecko over the mixnet.
- [Website](https://notrustverify.github.io/mixfetch-examples/)
- [Codebase](https://github.com/notrustverify/mixfetch-examples)
- There is a coconut-scheme based Credential Library playground [here](https://coco-demo.nymtech.net/). This is a WASM implementation of our Coconut libraries which generate raw Coconut credentials. Test it to create and re-randomize your own credentials. For more information on what is happening here check out the [Coconut docs](https://nymtech.net/docs/coconut.html).
- You can find a browser-based 'hello world' chat app [here](https://chat-demo.nymtech.net). Either open in two browser windows and send messages to yourself, or share with a friend and send messages to each other through the mixnet.
@@ -0,0 +1,16 @@
# Custom Services
Custom services involve two pieces of code that communicate via the mixnet: a client, and a custom server/service. This custom service will most likely interact with the wider internet / a clearnet service on your behalf, with the mixnet between you and the service, acting as a privacy shield.
- PasteNym is a private pastebin alternative. It involves a browser-based frontend utilising the Typescript SDK and a Python-based backend service communicating with a standalone Nym Websocket Client. **If you're a Python developer, start here!**.
- [Frontend codebase](https://github.com/notrustverify/pastenym)
- [Backend codebase](https://github.com/notrustverify/pastenym-frontend)
- Nostr-Nym is another application written by [NoTrustVerify](https://notrustverify.ch/), standing between mixnet users and a Nostr server in order to protect their metadata from being revealed when gossiping. **Useful for Go and Python developers**.
- [Codebase](https://github.com/notrustverify/nostr-nym)
- Spook and Nym-Ethtx are both examples of Ethereum transaction broadcasters utilising the mixnet, written in Rust. Since they were written before the release of the Rust SDK, they utilise standalone clients to communicate with the mixnet.
- [Spook](https://github.com/EdenBlockVC/spook) (**Typescript**)
- [Nym-Ethtx](https://github.com/noot/nym-ethtx) (**Rust**)
- NymDrive is an early proof of concept application for privacy-enhanced file storage on IPFS. **JS and CSS**, and a good example of packaging as an Electrum app.
- [Codebase](https://github.com/saleel/nymdrive)
@@ -0,0 +1,5 @@
# Monorepo examples
As well as these examples, there are a bunch of examples for each SDK in the Nym monorepo.
- [Rust SDK examples](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples)
- [Typescript SDK examples](https://github.com/nymtech/nym/tree/develop/sdk/typescript/examples)
@@ -0,0 +1,17 @@
# Apps Using Network Requesters
These applications utilise custom app logic in the user-facing apps in order to communicate using the mixnet as a transport layer, without having to rely on custom server-side logic. Instead, they utilise existing Nym infrastructure - [Network Requesters](https://nymtech.net/operators/nodes/network-requester-setup.html) - with a custom whitelist addition.
If you are sending 'normal' application traffic, and/or don't require and custom logic to be happening on the 'other side' of the mixnet, this is most likely the best option to take as a developer who wishes to privacy-enhance their application.
> Nym will soon be switching from a whitelist-based approach to a blocklist-based approach to filtering traffic. As such, it will soon be even easier for developers to utilise the mixnet, as they will not have to run their own NRs or have to add their domains to the whitelist
- DarkFi over Nym leverages Nyms mixnet as a pluggable transport for DarkIRC, their p2p IRC variant. Users can anonymously connect to peers over the network, ensuring secure and private communication within the DarkFi ecosystem. Written in **Rust**.
- [Docs](https://darkrenaissance.github.io/darkfi/clients/nym_outbound.html?highlight=nym#3--run)
- [Github](https://github.com/darkrenaissance/darkfi/tree/master/doc)
- MiniBolt is a complete guide to building a Bitcoin & Lightning full node on a personal computer. It has the capacity to run network traffic (transactions and syncing) over the mixnet, so you can privately sync your node and not expose your home IP to the wider world when interacting with the rest of the network!
- [Docs](https://v2.minibolt.info/bonus-guides/system/nym-mixnet#proxying-bitcoin-core)
- [Codebase](https://github.com/minibolt-guide/minibolt)
- Email over Nym is a set of configuration options to set up a Network Requester to send and recieve emails over Nym, using something like Thunderbird.
- [Codebase](https://github.com/dial0ut/nymstr-email)
@@ -4,43 +4,11 @@ Discover the workings of Nym's privacy-enhancing mixnet infrastructure through t
<iframe width="700" height="400" src="https://www.youtube.com/embed/rnPpEsJS4FM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
### Mixnet Infrastructure
There are few types of Nym infrastructure nodes:
#### Mix Nodes
Mix nodes play a critical role in the Nym network by providing enhanced security and privacy to network content and metadata. They are part of the three-layer mixnet that ensures that communication remains anonymous and untraceable. Mix nodes receive `NYM` tokens as compensation for their quality of service, which is measured by the network validators.
Mix nodes anonymously relay encrypted Sphinx packets between each other, adding an extra layer of protection by reordering and delaying the packets before forwarding them to the intended recipient. Additionally, cover traffic is maintained through mix nodes sending Sphinx packets to other mix nodes, making it appear as if there is a constant flow of user messages and further protecting the privacy of legitimate data packets.
With the ability to hide, reorder and add a delay to network traffic, mix nodes make it difficult for attackers to perform time-based correlation attacks and deanonymize users. By consistently delivering high-quality service, mix nodes are rewarded with NYM tokens, reinforcing the integrity of the Nym network.
#### Gateways
Gateways serve as the point of entry for user data into the mixnet, verifying that users have acquired sufficient NYM-based bandwidth credentials before allowing encrypted packets to be forwarded to mixnodes. They are also responsible for safeguarding against denial of service attacks and act as a message storage for users who may go offline.
Gateways receive bandwidth credentials from users, which are periodically redeemed for `NYM` tokens as payment for their services. Users have the flexibility to choose a single gateway, split traffic across multiple gateways, run their own gateways, or a combination of these options.
In addition, gateways also cache messages, functioning as an inbox for users who are offline. By providing secure, reliable access to the mixnet and ensuring that data remains protected, gateways play a crucial role in maintaining the integrity of the Nym network.
#### Validators
Validators are essential to the security and integrity of the Nym network, tasked with several key responsibilities. They utilize proof-of-stake Sybil defense measures to secure the network and determine which nodes are included within it. Through their collaborative efforts, validators create Coconut threshold credentials which provide anonymous access to network data and resources.
Validators also play a critical role in maintaining the Nym Cosmos blockchain, a secure, public ledger that records network-wide information such as node public information and keys, network configuration parameters, CosmWasm smart contracts, and `NYM` and credential transactions.
#### Service Providers
Service Providers are a crucial aspect of the Nym infrastructure that support the application layer of the Nym network. Any application built with Nym will require a Service Provider, which can be created by anyone. Service Providers run a piece of binary code that enables them to handle requests from Nym users or other services, and then make requests to external servers on behalf of the users.
For example, a Service Provider could receive a request to check a mail server and then forward the response to the user. The presence of Service Providers in the Nym network enhances its security and privacy, making it a reliable and robust platform for anonymous communication and data exchange.
### Where do I go from here? 💭
Maybe you would like to concentrate on building a application that uses the mixnet:
For more in-depth information on the network architecture, head to the [Network Overview page](https://nymtech.net/docs/architecture/network-overview.html), and check out the [Operators book](https://nymtech.net/operators) if you want to run a node yourself.
* Explore the Tutorials section of the Developer Portal. Our in-depth tutorial on [Building a Simple Service Provider](../tutorials/simple-service-provider/simple-service-provider.md) give a good understanding of building User Clients and Service Providers in TypeScript, and how to configure Nym Websocket Clients for seamless communication with the mixnet.
* Get started with using the Nym Mixnet quickly and easily by exploring the [Quickstart](../quickstart/overview.md) options, such a NymConnect, proxying traffic through the Nym Socks5 client, or dive into integrating Nym into your existing application with the [Integrations](../integrations/integration-options.md) section.
Or perhaps you a developer that would like to run a infrastructure node such as a Gateway, Mix node or Network Requestor:
* Check out the [Network Overview](https://nymtech.net/docs/architecture/network-overview.html) docs page.
* Take a look at our [Node Setup Guide](https://nymtech.net/operators/nodes/setup-guides.html) with our Nym Docs, containing setup guides for setting up you own infrastructure node.
If you would like to concentrate on building an application that uses the mixnet:
* Explore the [Quickstart](../quickstart/overview.md) options.
* Check out examples of [Community Apps](../community-resources/community-applications-and-guides.md).
* Run through the [Rust SDK](../tutorials/rust-sdk.md) or [Typescript](../tutorials/typescript.md) tutorials.
@@ -1,10 +1,14 @@
# Integration Options
If you've already gone through the different [Quick Start](../quickstart/overview.md) options, you have seen the possibilities avaliable to you for quickly connecting existing application code to another Nym process.
If you've already gone through the different [Quick Start](../quickstart/overview.md) options and had a look at the tutorials, you have seen the possibilities available to you for quickly connecting existing application code to another Nym process.
This section assumes you wish to integrate with Nym into your application code.
Below are a resources that will be useful for either beginning to integrate mixnet functionality into existing application code or build a new app using Nym.
The [integrations FAQ](../faq/integrations-faq.md) has a list of common questions regarding integrating with Nym and Nyx, as well as commonly required links. _This is a good place to start to get an overall idea of the tools and software avaliable to you_.
- **We suggest you begin with this [integration decision tree](https://sdk.nymtech.net/integrations)**. This will give you a better idea of what pieces of software (SDKs, standalone clients, service providers) your integration might involve, and what is currently possible to do with as little custom code as possible.
- The [integrations FAQ](../faq/integrations-faq.md) has a list of common questions regarding integrating with Nym and Nyx, as well as commonly required links.
- To get an idea of what is possible / has already been built, check the [community applications and resources](../community-resources/community-applications-and-guides.md) page, as well as the [developer tutorials codebase](https://github.com/nymtech/developer-tutorials).
> If you wish to integrate with the Nyx blockchain to use `NYM` for payments, start with the [payment integration](./payment-integration.md) page.
If you wish to integrate with Nym to use the mixnet for application traffic, start with the [mixnet integration](./mixnet-integration.md) page.
If you wish to integrate with the Nyx blockchain to use `NYM` for payments, start with the [payment integration](./payment-integration.md) page.
@@ -13,7 +13,7 @@ As outlined in the [clients overview documentation](https://nymtech.net/docs/cli
#### Websocket client
Your first option is the native websocket client. This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It runs as a persistent process on a desktop or server machine. You can connect to it with any language that supports websockets.
You can see an example of how to connect to and manage interactions with this client in the [Simple Service Provider tutorial](../tutorials/simple-service-provider/simple-service-provider.md).
[//]: # (You can see an example of how to connect to and manage interactions with this client in the [Simple Service Provider tutorial]&#40;../tutorials/simple-service-provider/simple-service-provider.md&#41;.)
#### Webassembly client
If youre working in JavaScript or Typescript in the browser, or building an edge computing app, youll likely want to choose the webassembly client.
@@ -0,0 +1,53 @@
# Hackathon Challenges
There are a few different challenges to choose from, each with different approaches. It is also recommended to check out the _**Examples**_ directory above for inspiration.
## Tooling challenge
The tooling challenge involves creating tooling for users, operators, or developers of Nym.
### Examples of user-centric tools:
- Facilitate onboarding new users more easily to staking their Nym, and understanding the pros and cons, as well as finding a good node to stake on. Examples of tools like this:
- [ExploreNym dashboard](https://explorenym.net/)
- Show information on a dashboard about the network. NOTE due to the amount of dashboards currently available, we expect a good justification for why / something to set this apart from existing ones e.g. it is presenting information that is not already presented, or it is presented in a different manner, such as a TUI or CLI app instead of a web dashboard - maybe an onion service, or no-JS site for those who do not wish to enable Javascript in their day-to-day browsing. Examples of tools like this:
- [NTV's node dashboard](https://status.notrustverify.ch/d/CW3L7dVVk/nym-mixnet?orgId=1)
- [IsNymUp dashboard](https://isnymup.com/)
### Examples of operator-centric tooling:
- An APY calculator for determining different financial outcomes of running a node in different situations.
- Scripting for updating and maintaining nodes. Examples of tools like this:
- [ExploreNym's bash scripts](https://github.com/ExploreNYM/bash-tool)
- Scripting for packaging node binaries for different OSes.
### Examples of developer-centric tooling:
- Tooling for use in development: are there pain points youve found when developing apps with Nym that you have created scripts/hacks/workarounds for? Is there a pain point that youve thought oh it would be great if I could just do X? These are often the best places to start for building out developer tooling - if youve run into this issue, it's very likely someone else already has, or will!
- Interacting with one of the SDKs via FFI: perhaps youre a Go developer who would love to have the functionality of one of the Nym SDKs. Building an FFI tool might be something that would make your life easier, and can be shared with other developers in your situation.
## Integrations challenge
Integration options for Nym are currently relatively restrictive due to the manner in which Nym handles sending and receiving traffic (as unordered Sphinx packets). This challenge will involve (most likely) implementing custom logic for handling Nym traffic for an existing application.
There are several potential avenues developers can take here:
- If your application (or the application you wish to modify) is written in either Javascript or Typescript, and relies on the `fetch` library to make API calls, then you can use its drop-in replacement: [`mixfetch`](). Perhaps you wish to interact with Coingecko, or a private search engine like Kagi without leaking your IP and metadata, or an RPC endpoint.
- Example with [NTVs privacy-preserving Coingecko API](https://github.com/notrustverify/mixfetch-examples)
- [Mixfetch docs examples](https://github.com/nymtech/nym/tree/develop/sdk/typescript/examples)
- If you instead have an application that is able to use any of the SOCKS5, 4a, or 4 protocols (a rule of thumb: if it can communicate over Tor, it will) then you can experiment with using Nym as the transport layer.
- For Rustaceans, check out our [socks5 rust sdk example](https://nymtech.net/docs/sdk/rust.html#socks-client-example).
- For those of you who arent Crustaceans, then you will have to run the [Socks Client]() alongside your application as a separate process. _NOTE If you are taking this route, please make sure to include detailed documentation on how you expect users to do this, as well as including any process management tools, scripts, and configs (e.g. if you use systemd then include the configuration file for the client, as well as initialisation logic) that may streamline this process._
- [NTV's PasteNym backend](https://github.com/notrustverify/pastenym) is a great example of an application with this architecture.
- Nym is not only useful for blockchain-related apps, but for anything that requires network level privacy! Email clients, messaging clients, and decentralised storage are all key elements of the privacy-enabled web. Several of these sorts of apps can be found in the [community apps page](../community-resources/community-applications-and-guides.md).
- There is currently a proof of concept using Rust Libp2p with Nym as a transport layer. Perhaps you can think of an app that uses Gossipsub for p2p communication could benefit from network-level privacy.
- [GossipSub chat example](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples/libp2p_chat)
- [Chainsafe's Lighthouse Nym PoC](https://github.com/ChainSafe/lighthouse/blob/nym/USE_NYM.md#usage)
- Alternatively if you know of an app that is written in Rust or TS and could benefit from using Nym, you could fork and modify it using the SDKs. Applications such as:
- Magic Wormhole (has a [rust implementation](https://github.com/magic-wormhole/magic-wormhole.rs))
- [Qual](https://github.com/qaul/qaul.net) (uses Rust Libp2p)
- [Syncthing](https://github.com/syncthing/syncthing)
### MiniApp challenge
Write an app, either using one of the SDKs or a standalone client (harder). Think of what you can nymify e.g. a version of the [TorBirdy](https://support.torproject.org/glossary/torbirdy/) extension that uses Nym instead of Tor. This is very similar to the Integration challenge in terms of the different potential _architectures_ and approaches, but just for new applications.
@@ -0,0 +1,16 @@
# General Info & Resources
Discussions and announcements will be taking place in the [builders channel on Matrix](https://matrix.to/#/#shipyardbuilders:nymtech.chat). This channel can be used for all discussions.
There will be daily office horse between 12-14:00 CET.
This is an open call and questions will be answered on a first come first serve basis.
The timetable can be found on the [Shipyard website](https://nymtech.net/learn/shipyard).
## Links
- You can find **code examples**, **tutorials**, & **quickstart** information here, on the Developer Portal.
- [Rust SDK docs](https://nymtech.net/docs/sdk/rust.html)
- [Typescript SDK docs](https://sdk.nymtech.net)
- [Platform docs](https://nymtech.net/docs)
- [NoTrustVerify's Awesome Nym list](https://github.com/notrustverify/awesome-nym)
- [Builders channel Matrix](https://matrix.to/#/#shipyardbuilders:nymtech.chat)
@@ -0,0 +1,12 @@
# Submission Guidelines
We expect to see the following for submissions:
- Working code demos hosted publicly (Gitlab, Github, some other git instance).
- Quality > quantity here: wed prefer to see a contained, working, and well documented Proof of Concept over a sprawling and messy app that does more but is poorly explained and presented. _The repo must be open source and able to be used and modified by others. The license is up to you._
- If you already have existing apps / projects you are more than welcome to extend them, instead of starting from scratch - we will only be looking at the NEW additions to make this fair. If you are doing this, make sure to write a detailed account of what it is you;ve added to the existing project, preferably with the possibility to see the old version as well as the new one.
- Proper documentation:
- If an app / tool:
- How do you install and run the code? How is it to be used?
- An overview of the application architecture: what is it doing? Is it relying on other services?
- If a UI-based solution:
- How to run it locally? We are happy to also accept staging deployments as part of the submission (e.g. via Vercel) but this does not replace being able to run it locally.
- Please make sure that your application works on commonly reproducible system environments (e.g. if youre developing on Artix Linux please check for the necessary dependencies for more common-place OSes such as Debian, or Arch). If you are developing on Windows please make sure that it works on non-Windows machines also. Where possible please try to include build and install instructions for a variety of OSes.
@@ -0,0 +1,12 @@
# A Note on Infrastructure
If you are writing an application that requires sending messages through the mixnet, then you will either be relying on existing infrastructure nodes (network requesters), or writing your own custom service (for example, the service written as part of the Rust SDK tutorial).
If you are relying on network requesters then chances are that the IPs or domains your app relies on will not already be on the whitelist. Ideally, you would [run your own,](https://nymtech.net/operators/nodes/network-requester-setup.html) but we will also run a few nodes in open proxy mode and share the addresses so that you can use them when beginning to develop.
## Node Details:
- NR1
- Location: Singapore
- Nym Address: `FDeWfd8q686PWLXJDCqNJTCbydTk1KSux5HVftimsPyx.9XyThN4yh92eTMuLp1NvWicRZob8Ei5xpba9dvcMLxcN@9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J`
- NR2
- Location: Frankfurt
- Nym Address: `BNypKaGiGY8GNRN4gpV95GcaVS8n7CrHuoZNgQ2ezqv2.ACpaixzuaSzuMajVQj6aR7cbpbvp676tm21MiLbX1gni@678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf`
@@ -1,5 +1,12 @@
# Building a Simple Service Provider
```admonish warning
This tutorial was written before the creation of the [Typescript SDK](https://sdk.nymtech.net), and involves running a Nym Client alongside your application processes, instead of relying on the SDK to integrate the Client process into your application logic.
As such, although this tutorial is still a valid way of approaching building on Nym, it is a little less streamlined than it could be.
A more streamlined rewrite of this tutorial will be coming soon.
```
This tutorial is the best place to start for developers new to Nym. You will learn how to build a minimum viable privacy-enabled application (PEApp) able to send and receive traffic via the mixnet.
This tutorial is less about building an immediately useful application, and more about beginning to understand:
+3
View File
@@ -6,6 +6,9 @@ language = "en"
multilingual = false # for the moment - ideally work on chinese, brazillian, spanish next
src = "src"
[rust]
edition = "2018"
#################
# PREPROCESSORS #
#################
+14 -1
View File
@@ -29,7 +29,20 @@
# SDK
- [Typescript SDK](sdk/typescript.md)
- [Rust SDK](sdk/rust.md)
- [Rust SDK](sdk/rust/rust.md)
- [Message Types](sdk/rust/message-types.md)
- [Message Helpers](sdk/rust/message-helpers.md)
- [Troubleshooting](sdk/rust/troubleshooting.md)
- [Examples](sdk/rust/examples.md)
- [Simple Send](sdk/rust/examples/simple.md)
- [Create and Store Keys](sdk/rust/examples/keys.md)
- [Manual Storage](sdk/rust/examples/storage.md)
- [Anonymous Replies](sdk/rust/examples/surbs.md)
- [Use Custom Network Topology](sdk/rust/examples/custom-network.md)
- [Socks Proxy](sdk/rust/examples/socks.md)
- [Split Send and Receive](sdk/rust/examples/split-send.md)
- [Testnet Bandwidth Cred](sdk/rust/examples/credential.md)
- [Example Cargo file](sdk/rust/examples/cargo.md)
# Wallet
- [Desktop Wallet](wallet/desktop-wallet.md)
@@ -5,7 +5,7 @@ When you send data across the internet, it can be recorded by a wide range of ob
Even if the content of a network request is encrypted, observers can still see that data was transmitted, its size, frequency of transmission, and gather metadata from unencrypted parts of the data (such as IP routing information). Adversaries may then combine all the leaked information to probabilistically de-anonymize users.
The Nym mixnet provides very strong security guarantees against this sort of surveillance. It _packetizes_ and _mixes_ together IP traffic from many users inside the _mixnet_.
The Nym mixnet provides very strong security guarantees against this sort of surveillance. It _packetises_ and _mixes_ together IP traffic from many users inside the _mixnet_.
> If you're into comparisons, the Nym mixnet is conceptually similar to other systems such as Tor, but provides improved protections against end-to-end timing attacks which can de-anonymize users. When Tor was first fielded, in 2002, those kinds of attacks were regarded as science fiction. But the future is now here.
@@ -69,7 +69,7 @@ From your Nym client, your encrypted traffic is sent to:
Whatever is on the 'other side' of the mixnet from your client, all traffic will travel this way through the mixnet. If you are sending traffic to a service external to Nym (such as a chat application's servers) then your traffic will be sent from the recieving Nym client to an application that will proxy it 'out' of the mixnet to these servers, shielding your metadata from them. P2P (peer-to-peer) applications, unlike the majority of apps, might want to keep all of their traffic entirely 'within' the mixnet, as they don't have to necessarily make outbound network requests to application servers. They would simply have their local application code communicate with their Nym clients, and not forward traffic anywhere 'outside' of the mixnet.
## Acks & Package Retransmission
Whenever a hop is completed, the recieving node will send back an acknowledgement ('ack') so that the sending node knows that the packet was recieved. If it does not recieve an ack after sending, it will resend the packet, as it assumes that the packet was dropped for some reason. This is done under the hood by the binaries themselves, and is never something that developers and node operators have to worry about dealing with themselves.
Whenever a hop is completed, the receiving node will send back an acknowledgement ('ack') so that the sending node knows that the packet was received. If it does not receive an ack after sending, it will resend the packet, as it assumes that the packet was dropped for some reason. This is done under the hood by the binaries themselves, and is never something that developers and node operators have to worry about dealing with themselves.
Packet retransmission means that if a client sends 100 packets to a gateway, but only receives an acknowledgement ('ack') for 95 of them, it will resend those 5 packets to the gateway again, to make sure that all packets are received. All nodes in the mixnet support packet retransmission.
+2 -2
View File
@@ -25,7 +25,7 @@ You need to choose which one you want incorporate into your app. Which one you u
### The websocket client
Your first option is the native websocket client (`nym-client`). This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It can be run as a persistent process on a desktop or server machine. You can connect to it with **any language that supports websockets**.
_Rust developers can import websocket client functionality into their code via the [Rust SDK](../sdk/rust.md)_.
_Rust developers can import websocket client functionality into their code via the [Rust SDK](../sdk/rust/rust.md)_.
### The webassembly client
If you're working in JavaScript or Typescript in the browser, or building an [edge computing](https://en.wikipedia.org/wiki/Edge_computing) app, you'll likely want to choose the webassembly client.
@@ -39,7 +39,7 @@ The `nym-socks5-client` is useful for allowing existing applications to use the
When used as a standalone client, it's less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes.
_Rust developers can import socks client functionality into their code via the [Rust SDK](../sdk/rust.md)_.
_Rust developers can import socks client functionality into their code via the [Rust SDK](../sdk/rust/rust.md)_.
## Commonalities between clients
All Nym client packages present basically the same capabilities to the privacy application developer. They need to run as a persistent process in order to stay connected and ready to receive any incoming messages from their gateway nodes. They register and authenticate to gateways, and encrypt Sphinx packets.
+1 -1
View File
@@ -15,7 +15,7 @@ If you're specically looking for TypeScript/JavaScript related information such
**SDK examples:**
* [Typescript SDK](https://sdk.nymtech.net/)
* [Rust SDK](./sdk/rust.md)
* [Rust SDK](sdk/rust/rust.md)
**Nyx**
* [Interacting with the Nyx chain](./nyx/interacting-with-chain.md)
-144
View File
@@ -1,144 +0,0 @@
# Rust SDK
The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a seperate process on their machine. This makes both developing and running applications much easier, reducing complexity in the development process (not having to restart another client in a seperate console window/tab) and being able to have a single binary for other people to use.
Currently developers can use the Rust SDK to import either websocket client ([`nym-client`](../clients/websocket-client.md)) or [`socks-client`](../clients/socks5-client.md) functionality into their Rust code.
## Development status
The SDK is still somewhat a work in progress: interfaces are fairly stable but still may change in subsequent releases.
The `nym-sdk` crate is **not yet availiable via [crates.io](https://crates.io)**. As such, in order to import the crate you must specify the Nym monorepo in your `Cargo.toml` file:
```toml
nym-sdk = { git = "https://github.com/nymtech/nym" }
```
In order to generate the crate docs run `cargo doc --open` from `nym/sdk/rust/nym-sdk/`
In the future the SDK will be made up of several components, each of which will allow developers to interact with different parts of Nym's infrastructure.
| Component | Functionality | Released |
| --------- | ------------------------------------------------------------------------------------- | -------- |
| Mixnet | Create / load clients & keypairs, subscribe to Mixnet events, send & receive messages | ✔️ |
| Coconut | Create & verify Coconut credentials | 🛠️ |
| Validator | Sign & broadcast Nyx blockchain transactions, query the blockchain | ❌ |
The `mixnet` component currently exposes the logic of two clients: the [websocket client](../clients/websocket-client.md), and the [socks](../clients/socks5-client.md) client.
The `coconut` component is currently being worked on. Right now it exposes logic allowing for the creation of coconut credentials on the Sandbox testnet.
## Websocket client examples
> All the codeblocks below can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/master/sdk/rust/nym-sdk/examples) in the monorepo. Just navigate to `nym/sdk/rust/nym-sdk/examples/` and run the files from there. If you wish to run these outside of the workspace - such as if you want to use one as the basis for your own project - then make sure to import the `sdk`, `tokio`, and `nym_bin_common` crates.
### Different message types
There are two methods for sending messages through the mixnet using your client:
* `send_plain_message()` is the most simple: pass the recipient address and the message you wish to send as a string (this was previously `send_str()`). This is a nicer-to-use wrapper around `send_message()`.
* `send_message()` allows you to also define the amount of SURBs to send along with your message (which is sent as bytes).
### Simple example
Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code (`examples/simple.rs`):
```rust,noplayground
{{#include ../../../../sdk/rust/nym-sdk/examples/simple.rs}}
```
Simply importing the `nym_sdk` crate into your project allows you to create a client and send traffic through the mixnet.
### Creating and storing keypairs
The example above involves ephemeral keys - if we want to create and then maintain a client identity over time, our code becomes a little more complex as we need to create, store, and conditionally load these keys (`examples/builder_with_storage`):
```rust,noplayground
{{#include ../../../../sdk/rust/nym-sdk/examples/builder_with_storage.rs}}
```
As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function handles checking for keys in a storage location, loading them if present, or creating them and storing them if not, making client key management very simple.
Assuming our client config is stored in `/tmp/mixnet-client`, the following files are generated:
```
$ tree /tmp/mixnet-client
mixnet-client
├── ack_key.pem
├── db.sqlite
├── db.sqlite-shm
├── db.sqlite-wal
├── gateway_details.json
├── gateway_shared.pem
├── persistent_reply_store.sqlite
├── private_encryption.pem
├── private_identity.pem
├── public_encryption.pem
└── public_identity.pem
1 directory, 11 files
```
### Manually handling storage
If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform the actions taken automatically above (`examples/manually_handle_keys_and_config.rs`)
```rust,noplayground
{{#include ../../../../sdk/rust/nym-sdk/examples/manually_handle_storage.rs}}
```
### Anonymous replies with SURBs
Both functions used to send messages through the mixnet (`send_message` and `send_plain_message`) send a pre-determined number of SURBs along with their messages by default.
The number of SURBs is set [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/src/mixnet/client.rs#L33).
```rust,noplayground
{{#include ../../../../sdk/rust/nym-sdk/src/mixnet/client.rs:33}}
```
You can read more about how SURBs function under the hood [here](../architecture/traffic-flow.md#private-replies-using-surbs).
In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to:
```rust,noplayground
{{#include ../../../../sdk/rust/nym-sdk/examples/surb-reply.rs}}
```
### Importing and using a custom network topology
If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or performing research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html) (`examples/custom_topology_provider.rs`).
There are two ways to do this:
#### Import a custom Nym API endpoint
If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood):
```rust,noplayground
{{#include ../../../../sdk/rust/nym-sdk/examples/custom_topology_provider.rs}}
```
#### Import a specific topology manually
If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can simply overwrite the grabbed topology manually:
```rust,noplayground
{{#include ../../../../sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs}}
```
### Send and receive in different tasks
If you need to split the different actions of your client across different tasks, you can do so like this:
```rust, noplayground
{{#include ../../../../sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs}}
```
## Socks client example
There is also the option to embed the [`socks5-client`](../clients/socks5-client.md) into your app code (`examples/socks5.rs`):
```rust,noplayground
{{#include ../../../../sdk/rust/nym-sdk/examples/socks5.rs}}
```
```admonish info
If you are looking at implementing Nym as a transport layer for a crypto wallet or desktop app, this is probably the best place to start.
```
## Coconut credential generation
The following code shows how you can use the SDK to create and use a [credential](../bandwidth-credentials.md) representing paid bandwidth on the Sandbox testnet.
```rust,noplayground
{{#include ../../../../sdk/rust/nym-sdk/examples/bandwidth.rs}}
```
You can read more about Coconut credentials (also referred to as `zk-Nym`) [here](../coconut.md).
@@ -0,0 +1,12 @@
# Examples
All the following examples can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/master/sdk/rust/nym-sdk/examples) in the monorepo. Just navigate to `nym/sdk/rust/nym-sdk/examples/` and run the files from there with:
```sh
cargo run --example <NAME_OF_FILE>
```
If you wish to run these outside of the workspace - such as if you want to use one as the basis for your own project - then make sure to import the `sdk`, `tokio`, and `nym_bin_common` crates.
An example `Cargo.toml` file can be found [here](examples/cargo.md).
@@ -0,0 +1,35 @@
# Example Cargo File
This file imports the basic requirements for running these pieces of example code, and can be used as the basis for your own cargo project.
```toml
[package]
name = "your_app"
version = "x.y.z"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# Async runtime
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
# Used for (de)serialising incoming and outgoing messages
serde = "1.0.152"
serde_json = "1.0.91"
# Nym clients, addressing, etc
nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sphinx-addressing = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-bin-common = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", branch = "master" }
# Additional dependencies if you're interacting with Nyx or another Cosmos SDK blockchain
cosmrs = "=0.14.0"
nym-validator-client = { git = "https://github.com/nymtech/nym", branch = "master" }
# If you're building an app with a client and server / serivce this might be a useful structure for your repo
[[bin]]
name = "client"
path = "bin/client.rs"
[[bin]]
name = "service"
path = "bin/service.rs"
```
@@ -0,0 +1,9 @@
# Coconut credential generation
The following code shows how you can use the SDK to create and use a [credential](../../../bandwidth-credentials.md) representing paid bandwidth on the Sandbox testnet.
```rust,noplayground
{{#include ../../../../../../sdk/rust/nym-sdk/examples/bandwidth.rs}}
```
You can read more about Coconut credentials (also referred to as `zk-Nym`) [here](../../../coconut.md).
@@ -0,0 +1,18 @@
# Importing and using a custom network topology
If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or performing research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html) (`examples/custom_topology_provider.rs`).
There are two ways to do this:
## Import a custom Nym API endpoint
If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood):
```rust,noplayground
{{#include ../../../../../../sdk/rust/nym-sdk/examples/custom_topology_provider.rs}}
```
## Import a specific topology manually
If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can simply overwrite the grabbed topology manually:
```rust,noplayground
{{#include ../../../../../../sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs}}
```
@@ -0,0 +1,28 @@
# Key Creation and Use
The previous example involves ephemeral keys - if we want to create and then maintain a client identity over time, our code becomes a little more complex as we need to create, store, and conditionally load these keys (`examples/builder_with_storage`):
```rust,noplayground
{{#include ../../../../../../sdk/rust/nym-sdk/examples/builder_with_storage.rs}}
```
As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function handles checking for keys in a storage location, loading them if present, or creating them and storing them if not, making client key management very simple.
Assuming our client config is stored in `/tmp/mixnet-client`, the following files are generated:
```
$ tree /tmp/mixnet-client
mixnet-client
├── ack_key.pem
├── db.sqlite
├── db.sqlite-shm
├── db.sqlite-wal
├── gateway_details.json
├── gateway_shared.pem
├── persistent_reply_store.sqlite
├── private_encryption.pem
├── private_identity.pem
├── public_encryption.pem
└── public_identity.pem
1 directory, 11 files
```
@@ -0,0 +1,8 @@
# Simple Send
Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code (`examples/simple.rs`).
Simply importing the `nym_sdk` crate into your project allows you to create a client and send traffic through the mixnet.
```rust,noplayground
{{#include ../../../../../../sdk/rust/nym-sdk/examples/simple.rs}}
```
@@ -0,0 +1,10 @@
# Socks Proxy
There is also the option to embed the [`socks5-client`](../../../clients/socks5-client.md) into your app code (`examples/socks5.rs`):
```admonish info
If you are looking at implementing Nym as a transport layer for a crypto wallet or desktop app, this is probably the best place to start if they can speak SOCKS5, 4a, or 4.
```
```rust,noplayground
{{#include ../../../../../../sdk/rust/nym-sdk/examples/socks5.rs}}
```

Some files were not shown because too many files have changed in this diff Show More