Compare commits

..

134 Commits

Author SHA1 Message Date
Tommy Verrall afc37988e3 Connections timeout after 5 minutes due to IPv6 DNS queries failing on IPv6-disabled networks 2025-11-14 11:55:38 +01:00
Bogdan-Ștefan Neacşu 18b0943846 [bugfix] Distinguish authenticator errors by credential spent (#6176) (#6192)
* distinguish authenticator errors by credential spent

* nitpicking fixes

* fix CI to see those changes

* error naming

Co-authored-by: Simon Wicky <simon@nymtech.net>
2025-11-12 14:13:09 +02:00
Tommy Verrall 15223fb19f Merge pull request #6190 from nymtech/dns-debug-fig
Dns debug fig
2025-11-12 12:47:46 +01:00
Tommy Verrall 90886091ee Fix clippy 2025-11-12 12:06:37 +01:00
benedettadavico babf113fe5 update changelog 2025-11-12 08:39:48 +01:00
jmwample fc2bd74d75 fix change in interface 2025-11-06 17:35:58 -07:00
Jack Wampler b21c43b106 Implement Static DNS fallback (#6178) 2025-11-06 17:08:33 -07:00
Jack Wampler f7a9e22cf3 DNS Reliability Fixes (#6175) 2025-11-06 17:08:33 -07:00
benedettadavico 0d7487f530 bump versions 2025-10-31 13:24:27 +01:00
Jędrzej Stuczyński 378f32e6d7 disconnect mixnet client if registration fails (#6169)
Co-authored-by: Simon Wicky <simon@nymtech.net>
2025-10-31 12:07:22 +00:00
Jędrzej Stuczyński 3e9b8d237f Merge pull request #6168 from nymtech/chore/clippy-1.91
chore: resolve clippy 1.91 warnings
2025-10-31 12:00:55 +00:00
Jędrzej Stuczyński f5a4dbc555 removed useless use of vec! 2025-10-31 11:42:42 +00:00
Jędrzej Stuczyński 4480534e4d derived Default impl for EpochState 2025-10-31 11:39:58 +00:00
Jędrzej Stuczyński d355f9d752 Merge pull request #6167 from nymtech/master
master -> develop
2025-10-31 11:38:15 +00:00
Jędrzej Stuczyński 9f3a370496 Merge pull request #6166 from nymtech/merge/release/2025.19-kase-cherry-picked
release/2025.19 kase into master
2025-10-31 11:28:51 +00:00
Jędrzej Stuczyński e4adc5d954 Merge pull request #6165 from nymtech/release/2025.19-kase-cherry-picked
release/2025.19 kase into develop
2025-10-31 11:28:44 +00:00
Jędrzej Stuczyński 00373b70e2 Merge branch 'master' into merge/release/2025.19-kase-cherry-picked 2025-10-31 10:54:29 +00:00
benedettadavico 65f2017422 update changelog 2025-10-31 10:47:36 +00:00
benedettadavico 192f258463 update workflow 2025-10-31 10:47:34 +00:00
Tommy Verrall a5eee7444b Merge pull request #6143 from nymtech/bugfix/mix-tx-closed-v2
Bugfix: Add circuit breaker
2025-10-31 10:47:20 +00:00
benedettadavico 6abd7e7352 bump versions 2025-10-31 10:47:10 +00:00
Tommy Verrall 1ecc2a8dda Merge pull request #6158 from nymtech/simon/registration_fail
[bugfix] Disconnect mixnet client if registration fails
2025-10-31 10:05:41 +01:00
Jędrzej Stuczyński 3306ca5357 merge 'master' into 'develop' 2025-10-30 17:43:42 +00:00
Jędrzej Stuczyński 9c2ccead0e Merge branch 'master' into merge/master/develop 2025-10-30 17:30:39 +00:00
import this b7aeb51362 [DOCs/operators]: Release notes for v2025.19 kase (#6157)
* add release and operators notes

* bump up version

* fix location in csv to USA

* bump up stats

* typo fix
2025-10-30 16:21:04 +00:00
benedetta davico e9e725defe Merge pull request #6093 from nymtech/bugfix/ns-api-node-custom-http-api-port
ns-api: fix scraping bug when operator specifies custom node HTTP API port in bond
2025-10-30 16:49:02 +01:00
Simon Wicky f9659ef42c disconnect mixnet client if registration fails 2025-10-30 16:06:53 +01:00
import this c74494a21d [Feature/operators]: QUIC bridge deployment script v2 (#6145)
* new quick deployment script

* docs tweak

* update script to use .deb postinst

* final clean - ready to go

* correct nym-node config dir search with a fallback
2025-10-30 12:22:55 +00:00
Simon Wicky 54f6c98c22 remove unused deps (#6151) 2025-10-29 11:48:49 +01:00
Simon Wicky 846484bbb4 use typed builder (#6150) 2025-10-27 17:49:50 +01:00
Tommy Verrall fb3f5501ba Merge pull request #6143 from nymtech/bugfix/mix-tx-closed-v2
Bugfix: Add circuit breaker
2025-10-27 16:45:41 +01:00
Tommy Verrall e8a607f520 Merge pull request #6149 from nymtech/simon/tommy_too_quick
tommy is too quick
2025-10-27 13:52:55 +01:00
Simon Wicky f5f6df9eaf tommy is too quick 2025-10-27 13:50:49 +01:00
Tommy Verrall c647ab5605 Merge pull request #6148 from nymtech/simon/registration_client_timeout
configurable mixnet client startup timeout
2025-10-27 13:47:48 +01:00
Simon Wicky 416c21a42e configurable mixnet client startup timeout 2025-10-27 13:35:46 +01:00
Simon Wicky fd5a95fa4d allow overwriting existing sdk shutdown manager 2025-10-24 16:17:29 +02:00
Simon Wicky c61df79182 typo 2025-10-24 14:11:56 +02:00
Simon Wicky 08559a7660 calling for shutdown from the MixTrafficController 2025-10-24 14:07:15 +02:00
Jędrzej Stuczyński 6dce55a99b using same hierarchy of trackers for client shutdown control 2025-10-24 14:03:18 +02:00
Tommy Verrall bc0b89b31c Internal comments 2025-10-24 12:44:10 +02:00
Tommy Verrall 67c32faa11 Fix comments 2025-10-23 19:22:26 +02:00
Tommy Verrall aa0d15ee67 Better message to come in the PR description 2025-10-23 19:06:27 +02:00
p17o 4f0974fcf1 Update quic_bridge_deployment.sh for IPv4 and .deb package (#6138)
Updated ping commands to explicitly use IPv4 and adjusted file permission checks with sudo. Changed the forward address prompt to specify IPv4 and modified the binary download process to fetch and install the latest .deb release URL automatically.
2025-10-22 15:46:23 +00:00
Jędrzej Stuczyński bd2174641e bugfix: update internal owner address in transferred share (#6139) 2025-10-22 10:42:26 +01:00
Tommy Verrall 59b62fabc9 Merge pull request #6134 from nymtech/feature/domain-fronting-v2
Domain fronting
2025-10-22 11:08:21 +02:00
Tommy Verrall e6f4bae895 Last failing test - fix 2025-10-21 19:34:20 +02:00
Tommy Verrall d71742af32 Use explicit Vec<ApiUrl> handling in BaseClientBuilder
- Replace NymNetworkDetails with explicit API URL handling
- Fix deprecated from_network() usage and improve fallback logic
- Add URL validation and remove unused backwards compatibility
2025-10-21 19:15:24 +02:00
Tommy Verrall 3b7c07e249 Actually commit the recommended changes 2025-10-21 18:12:38 +02:00
Tommy Verrall 3b429dde69 Fix broken tests in CI 2025-10-21 16:29:26 +02:00
Tommy Verrall 3a29c296da Replace deprecated from_network() with new_with_fronted_urls() 2025-10-21 16:05:41 +02:00
Tommy Verrall 8544c54f8f Merge develop into feature/domain-fronting-v2
- Use new_with_fronted_urls() for explicit domain fronting
- Deprecate from_network() in favor of explicit method
2025-10-21 15:58:20 +02:00
Jędrzej Stuczyński 9f9639950b feat: expose more explicit new_with_fronted_urls builder for http API client (#6136) 2025-10-21 14:47:58 +01:00
Jędrzej Stuczyński 111a0b20b6 bugfix: update stored epoch share when changing ownership (#6135) 2025-10-21 14:10:20 +01:00
Tommy Verrall 67b300d0b8 Fix new_from_env() to populate nym_api_urls for domain fronting 2025-10-21 12:22:51 +02:00
Jędrzej Stuczyński 88c4e0ce6c bugfix: update stored epoch share when changing announce address (#6131)
* bugfix: update stored epoch share when changing announce address

* chore: remove placeholder legacy mixnode bonding test [mixnet contract]
2025-10-21 10:59:17 +01:00
Tommy Verrall f6800aff0a fix all clippy messages 2025-10-21 11:32:47 +02:00
Tommy Verrall 0c7c927ca2 Add more tests for retry logic 2025-10-21 11:32:47 +02:00
Tommy Verrall a69c8b1660 Fix confusing tracing logs 2025-10-21 11:32:47 +02:00
Tommy Verrall f3ea310a46 Fix retries - this is working 2025-10-21 11:32:46 +02:00
Tommy Verrall 92f9ff035d Add configuration-based domain fronting support
Changes:
- Add network_details field to BaseClientBuilder (optional, backwards compatible)
- Add with_network_details() method for opt-in domain fronting
- Update construct_nym_api_client() to use from_network() when network_details provided
- Enable network-defaults feature in nym-client-core Cargo.toml
- SDK passes network_details to BaseClientBuilder
2025-10-21 11:32:46 +02:00
Tommy Verrall 5a817e1df1 Merge pull request #6126 from nymtech/multiple-fall-back-urls
Changes:

Multiple URL fallback with configurable retries (defaults to 3)
Infallible URL conversion per Andrews feedback (Url::from() instead of parse().ok())
Non-breaking builder pattern for BuilderConfig per Andrej's "too many arguments" feedback
Reverted redundant node filtering per Andrew's clarification that API already filters by supported_roles.entry
2025-10-21 11:27:37 +02:00
Tommy Verrall a07a24db00 Fix CI issues 2025-10-21 11:01:04 +02:00
Tommy Verrall a0cb812eff Allow clippy::enum_variant_names for BuilderConfigError 2025-10-21 10:35:57 +02:00
Tommy Verrall 923c1fa184 Improve error handling
Changes:
- Replace String error with BuilderConfigError enum in BuilderConfigBuilder
- Update tests to use pattern matching instead of string assertions
2025-10-20 16:57:31 +02:00
Tommy Verrall 35ea7e4926 - Add DEFAULT_NYM_API_RETRIES constant (replaces magic number 3)
- Run cargo fmt on all affected packages
- All clippy warnings resolved
2025-10-20 16:51:07 +02:00
Tommy Verrall d1cb9afaf0 not sure what happened but it's fixed 2025-10-20 15:20:24 +02:00
Tommy Verrall 79d4b4b2e3 Merge branch 'develop' into multiple-fall-back-urls 2025-10-20 15:16:36 +02:00
Tommy Verrall 8460b33946 Merge branch 'multiple-fall-back-urls' of https://github.com/nymtech/nym into multiple-fall-back-urls 2025-10-20 15:16:17 +02:00
Tommy Verrall ae6539e07c Merge resolution 2025-10-20 15:14:48 +02:00
Tommy Verrall 18cebdfedc Add accessor methods for Url internals
Add inner_url() and fronts() accessor methods to nym_http_api_client::Url
for VPN client integration
2025-10-20 14:33:57 +02:00
Tommy Verrall c448ec823a Remove tests for removed with_nym_api_client method
These tests were referencing with_nym_api_client() which was removed when
cleaning domain fronting code from this branch
2025-10-20 11:52:04 +02:00
Tommy Verrall a266137278 Add optional builder pattern for BuilderConfig (non-breaking)
Addresses @jstuczyn's feedback about too many arguments by adding
BuilderConfigBuilder as an alternative to the existing new() method.
2025-10-20 11:39:50 +02:00
Tommy Verrall 6f4dfd1dab fix conversion type && make the retry count configurable 2025-10-20 11:15:31 +02:00
Andy Duplain 57719787db Merge pull request #6130 from nymtech/andy/url_fronts
VPN-4262: Update `Url` to return `url` and `front` fields.
2025-10-17 15:44:08 +01:00
Andy Duplain 29a57bf172 VPN-4262: Update Url to return url and front fields.
The VPN client is using the `Url` type alot now and in order to avoid
double URL-parsing we would like the content of the `Url` type exposed.
2025-10-17 15:37:07 +01:00
Mark Sinclair 17d11f201e change migration and bump version 2025-10-17 15:04:53 +01:00
Mark Sinclair fef7e42eb4 bump version to rc 2025-10-17 14:56:02 +01:00
Mark Sinclair ceeeb6211b add tracing output 2025-10-17 14:52:59 +01:00
Mark Sinclair cd77b1032f clippy 2025-10-17 14:48:40 +01:00
Mark Sinclair 6bbb14f12f save custom_http_port to db 2025-10-17 14:48:40 +01:00
Mark Sinclair de8030d85a allow NS API to run once for scraping for troubleshooting and debugging 2025-10-17 14:48:40 +01:00
Mark Sinclair e18e64bf21 wip 2025-10-17 14:48:40 +01:00
Mark Sinclair a50c9ac3fb ns-api: fix scraping bug when operator specifies custom node HTTP API port in bond 2025-10-17 14:48:39 +01:00
Tommy Verrall db813b6e3e Revert node filtering changes per Andrew's feedback
Andrew clarified that get_basic_entry_assigned_nodes_v2() already filters by
supported_roles.entry
2025-10-17 15:18:28 +02:00
Tommy Verrall 1be5ba310a Remove domain fronting code to keep gateway changes only
This branch now contains only gateway registration improvements:
- Multiple URL fallback support in gateways_for_init()
- Get all entry-capable nodes for registration
- Performance and code quality improvements
2025-10-17 14:27:31 +02:00
Tommy Verrall 41ff3f7824 Address PR feedback: simplify code and reduce log noise
- Reverted all changes to topology_control/nym_api_provider.rs
- Changed info/warn logs to debug for custom client messages
- Removed unused _rng parameter from gateways_for_init()
- Simplified URL builder to always use new_with_urls()
2025-10-17 14:20:12 +02:00
Tommy Verrall c9d4d62446 Fix clippy warnings: use arrays instead of vec! in tests 2025-10-17 13:30:30 +02:00
Tommy Verrall e839a0d80e Merge develop into multiple-fall-back-urls
Resolved conflicts:
- Added event_tx field to MixnetClientBuilder alongside custom_nym_api_client
- Both features are independent and coexist:
  * custom_nym_api_client: for domain fronting support
  * event_tx: for event handling
- Updated all constructors and methods to properly handle both fields
2025-10-17 13:23:04 +02:00
Tommy Verrall cd61f930bf feat: pass custom HTTP client through SDK stack for domain fronting
- Add with_nym_api_client() to BaseClientBuilder, MixnetClientBuilder, and RegistrationClientBuilderConfig

- Modify nym_api_provider to fetch all nodes then filter by supported_roles.entry (fixes metadata inconsistency)

- Update helpers.rs to build HTTP client with all nym_apis URLs and retries for fallback support

- Fix SDK to use entry_capable_nodes() instead of entry_gateways() for broader gateway selection

This enables domain fronting and URL rotation throughout the entire SDK stack, improving censorship resistance and connection reliability. All changes are backward compatible - custom client is optional.
2025-10-17 08:36:23 +02:00
Bogdan-Ștefan Neacşu 0674f31227 Introduce event backchannel (#6119)
* Introduce even backchannel

* Rust fmt

* Rename Event to MixnetClientEvent

* Use unbounded_send for events

* Remove unused file

* Remove mut borrow

* Event hierarchy and mixnet client intermediary

* Export MixTrafficEvent in sdk
2025-10-16 19:02:36 +03:00
Jędrzej Stuczyński 3e4f563dce Merge pull request #6099 from nymtech/bugfix/incompatibility-fixes
Bugfix/incompatibility fixes
2025-10-16 15:58:43 +01:00
Tommy Verrall edcf2b1204 enable URL rotation and retries for mixnet gateway init 2025-10-16 16:22:57 +02:00
Jędrzej Stuczyński b07fb18113 Merge pull request #6125 from nymtech/merge/release/2025.18-jarlsberg
Merge/release/2025.18 jarlsberg
2025-10-16 14:50:16 +01:00
benedettadavico 017dea4afd update changelog 2025-10-16 14:09:46 +01:00
Jędrzej Stuczyński 5a9ce13beb Bugfix/bloomfilters purge (#6089)
* remove all old bloomfilters upon starting binary

* remove old bloomfilter file upon purging secondary data
2025-10-16 14:09:45 +01:00
benedettadavico 514cf25c68 bump versions 2025-10-16 13:53:06 +01:00
Andrej Mihajlov 49ee0636e4 Merge pull request #6109 from nymtech/am/update-dirs-6
Update dirs to 6.0
2025-10-16 12:59:31 +02:00
Jędrzej Stuczyński bb971ce99c bugfix: nym-credential-proxy query params parsing regression (#6121) 2025-10-16 11:40:12 +01:00
Tommy Verrall 54de369c1e Skip ipv6 metadata endpoint request (#6118)
Co-authored-by: Tommy Verrall <tommy@nymtech.net>
2025-10-16 11:39:53 +01:00
Jędrzej Stuczyński 6d6ce284df bugfix: revert some dep updates introduced in #6043 (#6120) 2025-10-16 11:39:09 +01:00
Andrej Mihajlov 56ad1c6c8e Merge pull request #6115 from nymtech/am/revert-cancel-token
Revert "Propagate cancel token to mixnet client"
2025-10-15 16:54:49 +02:00
Jędrzej Stuczyński 10b4a288c8 chore: restore pending dkg contract state migration (#6116)
since it has not yet been run on mainnet
2025-10-15 14:18:03 +01:00
benedetta davico bbbb9486ce Merge pull request #6117 from nymtech/probe/remove-1mb-file
update to no longer use 1mb files
2025-10-15 15:17:01 +02:00
benedetta davico 16e86e1a07 Update lib.go 2025-10-15 15:15:20 +02:00
Jędrzej Stuczyński ca0c9898f0 bugfix: retrieve and update ticketbook in the same query (#6101)
* bugfix: retrieve and update ticketbook in the same query

* bump up NS version

* Update Cargo.toml

* remove SKIP LOCKED part of the query

---------

Co-authored-by: benedetta davico <46782255+benedettadavico@users.noreply.github.com>
2025-10-15 13:53:07 +01:00
Andrej Mihajlov 8b73d4e615 Revert "Propagate cancel token to mixnet client"
This reverts commit 50a259d454.
2025-10-15 14:17:36 +02:00
mfahampshire 6a9a767ab4 DOCS Jarlsberg Release (#6111)
* First pass release notes

* build info
2025-10-15 09:20:03 +00:00
Andrej Mihajlov e03a9fa16f Merge pull request #6105 from nymtech/am/reg-client-mixnet-cancel-token
Propagate cancel token to mixnet client
2025-10-14 13:10:02 +02:00
Andrej Mihajlov a0fbd57d5b Update dirs to 6.0 2025-10-14 11:17:33 +02:00
mfahampshire 9856198356 Patch for operators to open wg metadata port (#6106) 2025-10-13 14:47:43 +00:00
Jędrzej Stuczyński 5c33846e57 bugfix: use custom topology provider for list of init gateways (#6092) 2025-10-13 12:01:51 +01:00
Andrej Mihajlov cfa7635ae1 Propagate cancel token to mixnet client 2025-10-13 12:25:54 +02:00
Jędrzej Stuczyński 5d45544c27 bugfix: include network name in the default gateway probe config path (#6100) 2025-10-13 10:05:54 +01:00
Jędrzej Stuczyński aa6a79cb3e feat: expose obtaining reference to Mnemonic from DirectSecp256k1HdWallet (#6083)
* feat: expose obtaining reference to Mnemonic from DirectSecp256k1HdWallet

* updated getters for stringified mnemonic
2025-10-13 09:22:15 +01:00
Georgio Nicolas b3a940770a Merge pull request #5919 from nymtech/georgio/dkg-fixes
Additional DKG Fixes
2025-10-10 17:47:11 +02:00
Mark Sinclair e980f76a81 ns-api: add descriptions to dVPN gateway responses (#6102)
* ns-api: add descriptions to dVPN gateway responses

* clippy

* fmt

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2025-10-10 15:40:18 +01:00
import this 9b38fef28f [DOCs/operators] QUIC deployment script & docs (#6098)
* add quic_bridge_deployment.sh

* create a snippet with quick install steps

* add quic deployment to changelog

* add quic deployment to node config page

* add version compatibility callout

* last edits and scraped stats update

* correct name of QUIC snippet

* fix naming

* fix naming

* re-run python-prebuild.sh aka time-now updated

* attempt to fix vercel build the hard way

* rerun npm

* build with pnpm

* restore lock file and rebuild w pnpm

* chore: update pnpm lockfile

* attempt to fix build

* attempt to fix runtime builds

* update ci-docs run OS
2025-10-10 14:38:37 +00:00
Mark Sinclair 43910ca635 Update ci-docs.yml 2025-10-10 15:00:25 +01:00
Mark Sinclair d3ccd7575a NS API: use new probe download filesize and milliseconds field (#6097)
* use milliseconds field

* change score thresholds

* bump to version 4.0.8

* NS API: adjust score categories (#6103)

* testing scores

* test version

* Update Cargo.toml

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: benedetta davico <46782255+benedettadavico@users.noreply.github.com>
2025-10-10 14:47:36 +01:00
Jędrzej Stuczyński 422f889df7 bugfix: testnet manager 02sql migration (#6096) 2025-10-10 09:38:45 +01:00
Jędrzej Stuczyński c9e96edc35 chore: remove unnecessary closure in 'calculate_score' inside node-status-api 2025-10-09 15:46:15 +01:00
benedetta davico 7768317046 Merge pull request #6095 from nymtech/bugfix/ns-api-download-filesize
ns-api: use download files size from probes instead of parsing filenames
2025-10-08 18:14:00 +02:00
Mark Sinclair 0ebbb1a540 ns-api: use download files size from probes instead of parsing filenames 2025-10-08 17:05:56 +01:00
Jędrzej Stuczyński 827c13b69e moved nym-gateway-probe to monorepo and updated rust-edition to 2024 (#6094)
dont build netstack in CI

additional rust 2024 fixes

fixes

removed temp.rs

first round of cleanup

removed duplicated NS types

moved gateway probe to the monorepo
2025-10-08 16:17:43 +01:00
Mark Sinclair 18ff09608c ns-api: add new fields for probe output for query_metadata and download file size and duration in ms (#6091)
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2025-10-08 09:47:04 +01:00
Mark Sinclair 8cc996bc0d NS API: clamp load to offline when score is offline and add mixnet_score field to preformance_v2 (#6076)
* ns-api: when `score` is `Offline`, clamp `load` to `Offline`

* ns-api: bump version

* ns-api: add mixnet score field to performance_v2 struct

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2025-10-07 17:30:37 +01:00
mfahampshire 83a598907f Max/fix wasm client + build commands (#6043)
* Debug logging 

* Yield based logging

* Reintroduce non-dummy task manager, try add counting for
BatchMessageSender, a couple of compiler target introductions on use
statements.

* Fixed time runtime err

* Uncomment forgetme/rememberme

* remove diffs from debug

* missed commented out forgetme

* yet more forgetme comments

* * Added missing clientreqestsender clone to wasm client to stop
  premature drop & busyloop
* Removed hacky mem::forget fix

* Remove debug panic_hook

* Conditional import + use of wasm_utils::console_log

* add wasm_util dep

* Commenting out or removing debug logging

* Remove missed comment

* cleanup gitignore

* clippy

* update go version in ci

* removed unused deps

* add clippy ignore

* remove mixfetch from ci build

* add minifetch fix

* comment out unused ts builds

* stop contract clients killing ci for the moment

* wasm target locking for imports

* Either remove console_log! macro or introduce cfg(debug_assertions)

* downgrade netlink

* debug assertions for console_log import

* modify config logging (debug -> normal)

* remove clone for client_request_sender + grab directly in struct
  creation

* reintroduce debug print for config in debug mode

* remove ood / unused custom topology from worker example file

* clippy

* clippy - ignore todo() tests

* modified humantime test in line with new parsing rules
2025-10-07 09:55:41 +00:00
Georgio Nicolas bb06a1b7a8 Another offering for Clippy 2025-09-12 20:34:50 +02:00
Georgio Nicolas e783a5fced Offerings for clippy 2025-09-12 20:28:49 +02:00
Georgio Nicolas 8a24b45b5d Precompute BSGS table 2025-09-12 20:21:57 +02:00
Georgio Nicolas 10e4eba727 Use LazyLock to precompute generators 2025-08-08 19:14:37 +02:00
Georgio Nicolas 8ebf482f36 Fix clippy suggestion 2025-07-29 16:33:25 +02:00
Georgio Nicolas 6940ca427e Fix zeroization 2025-07-29 15:42:23 +02:00
Georgio Nicolas 24f877fda5 replace unsafe static values by function calls 2025-07-29 15:04:11 +02:00
32 changed files with 906 additions and 278 deletions
+3
View File
@@ -8,10 +8,13 @@ on:
- 'gateway/**'
- 'integrations/**'
- 'nym-api/**'
- 'nym-authenticator-client/**'
- 'nym-credential-proxy/**'
- 'nym-ip-packet-client/**'
- 'nym-network-monitor/**'
- 'nym-node/**'
- 'nym-node-status-api/**'
- 'nym-registration-client/**'
- 'nym-statistics-api/**'
- 'nym-outfox/**'
- 'nym-validator-rewarder/**'
+3 -30
View File
@@ -3,11 +3,6 @@ name: Build and upload Node Status agent container to harbor.nymte.ch
on:
workflow_dispatch:
inputs:
gateway_probe_git_ref:
type: string
default: nym-vpn-core-v1.4.0
required: true
description: Which gateway probe git ref to build the image with
release_image:
description: 'Tag image as a release'
required: true
@@ -43,16 +38,6 @@ jobs:
VERSION=$(yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml)
echo "result=$VERSION" >> $GITHUB_OUTPUT
- name: cleanup-gateway-probe-ref
id: cleanup_gateway_probe_ref
run: |
GATEWAY_PROBE_GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }}
GIT_REF_SLUG="${GATEWAY_PROBE_GIT_REF//\//-}"
echo "git_ref=${GIT_REF_SLUG}" >> $GITHUB_OUTPUT
- name: Set GIT_TAG variable
run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
- name: Initialize RELEASE_TAG
run: echo "RELEASE_TAG=" >> $GITHUB_ENV
@@ -61,24 +46,12 @@ jobs:
run: echo "RELEASE_TAG=golden-" >> $GITHUB_ENV
- name: Set IMAGE_NAME_AND_TAGS variable
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV
- name: New env vars
run: echo "RELEASE_TAG='$RELEASE_TAG' GIT_TAG='$GIT_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
# - name: Remove existing tag if exists
# run: |
# if git rev-parse $${{ env.GIT_TAG }} >/dev/null 2>&1; then
# git push --delete origin $${{ env.GIT_TAG }}
# git tag -d $${{ env.GIT_TAG }}
# fi
# - name: Create tag
# run: |
# git tag -a $${{ env.GIT_TAG }} -m "Version ${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}"
# git push origin $${{ env.GIT_TAG }}
run: echo "RELEASE_TAG='$RELEASE_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
- name: BuildAndPushImageOnHarbor
run: |
docker build --build-arg GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }}
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }}
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
+72
View File
@@ -4,6 +4,78 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.20-leerdammer] (2025-11-12)
- Max/tweak ts sdk actions ([#6185])
- chore: resolve clippy 1.91 warnings ([#6168])
- [chore] Remove unused dependencies ([#6151])
- Use typed-builder for registration client builder config ([#6150])
- tommy is too quick ([#6149])
- configurable mixnet client startup timeout ([#6148])
- [Feature/operators]: QUIC bridge deployment script v2 ([#6145])
- Bugfix: Add circuit breaker ([#6143])
- bugfix: update internal owner address in transferred share ([#6139])
- Update quic_bridge_deployment.sh for IPv4 and .deb package ([#6138])
- feat: expose more explicit new_with_fronted_urls builder for http API client ([#6136])
- bugfix: update stored epoch share when changing ownership ([#6135])
- Domain fronting ([#6134])
- bugfix: update stored epoch share when changing announce address ([#6131])
[#6185]: https://github.com/nymtech/nym/pull/6185
[#6168]: https://github.com/nymtech/nym/pull/6168
[#6151]: https://github.com/nymtech/nym/pull/6151
[#6150]: https://github.com/nymtech/nym/pull/6150
[#6149]: https://github.com/nymtech/nym/pull/6149
[#6148]: https://github.com/nymtech/nym/pull/6148
[#6145]: https://github.com/nymtech/nym/pull/6145
[#6143]: https://github.com/nymtech/nym/pull/6143
[#6139]: https://github.com/nymtech/nym/pull/6139
[#6138]: https://github.com/nymtech/nym/pull/6138
[#6136]: https://github.com/nymtech/nym/pull/6136
[#6135]: https://github.com/nymtech/nym/pull/6135
[#6134]: https://github.com/nymtech/nym/pull/6134
[#6131]: https://github.com/nymtech/nym/pull/6131
## [2025.19-kase] (2025-10-30)
- update ns agent workflow ([#6154])
- Cherry pick - request #6143 from nymtech/bugfix/mix-tx-closed-v2 ([#6153])
- bugfix: nym-credential-proxy query params parsing regression ([#6121])
- bugfix: revert some dep updates introduced in #6043 ([#6120])
- Skip ipv6 metadata endpoint request ([#6118])
- update to no longer use 1mb files ([#6117])
- chore: restore pending dkg contract state migration ([#6116])
- Revert "Propagate cancel token to mixnet client" ([#6115])
- Update dirs to 6.0 ([#6109])
- Propagate cancel token to mixnet client ([#6105])
- bugfix: retrieve and update ticketbook in the same query ([#6101])
- bugfix: include network name in the default gateway probe config path ([#6100])
- Bugfix/incompatibility fixes ([#6099])
- [DOCs/operators] QUIC deployment script & docs ([#6098])
- bugfix: testnet manager 02sql migration ([#6096])
- feat: move gateway probe to monorepo (and update to rust edition 2024) ([#6094])
- bugfix: use custom topology provider for list of init gateways ([#6092])
- Max/fix wasm client + build commands ([#6043])
[#6154]: https://github.com/nymtech/nym/pull/6154
[#6153]: https://github.com/nymtech/nym/pull/6153
[#6121]: https://github.com/nymtech/nym/pull/6121
[#6120]: https://github.com/nymtech/nym/pull/6120
[#6118]: https://github.com/nymtech/nym/pull/6118
[#6117]: https://github.com/nymtech/nym/pull/6117
[#6116]: https://github.com/nymtech/nym/pull/6116
[#6115]: https://github.com/nymtech/nym/pull/6115
[#6109]: https://github.com/nymtech/nym/pull/6109
[#6105]: https://github.com/nymtech/nym/pull/6105
[#6101]: https://github.com/nymtech/nym/pull/6101
[#6100]: https://github.com/nymtech/nym/pull/6100
[#6099]: https://github.com/nymtech/nym/pull/6099
[#6098]: https://github.com/nymtech/nym/pull/6098
[#6096]: https://github.com/nymtech/nym/pull/6096
[#6094]: https://github.com/nymtech/nym/pull/6094
[#6092]: https://github.com/nymtech/nym/pull/6092
[#6043]: https://github.com/nymtech/nym/pull/6043
## [2025.18-jarlsberg] (2025-10-14)
- ns-api: add descriptions to dVPN gateway responses ([#6102])
Generated
+8 -7
View File
@@ -4824,7 +4824,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.67"
version = "1.1.69"
dependencies = [
"anyhow",
"async-trait",
@@ -5050,7 +5050,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.64"
version = "1.1.66"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -5133,7 +5133,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.64"
version = "1.1.66"
dependencies = [
"bs58",
"clap",
@@ -6050,6 +6050,7 @@ dependencies = [
"thiserror 2.0.12",
"tokio",
"tracing",
"tracing-subscriber",
"url",
"wasmtimer",
]
@@ -6354,7 +6355,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.65"
version = "1.1.67"
dependencies = [
"addr",
"anyhow",
@@ -6404,7 +6405,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.19.0"
version = "1.21.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -6931,7 +6932,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.64"
version = "1.1.66"
dependencies = [
"bs58",
"clap",
@@ -7670,7 +7671,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.29"
version = "0.1.31"
dependencies = [
"anyhow",
"bytes",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.64"
version = "1.1.66"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.64"
version = "1.1.66"
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"
+1 -1
View File
@@ -45,7 +45,7 @@ pub enum ClientCoreError {
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
#[error("no gateways on network")]
NoGatewaysOnNetwork,
+1 -1
View File
@@ -441,7 +441,7 @@ mod tests {
#[test]
fn test_multiple_urls_prepared_for_retries() {
let urls = vec![
let urls = [
Url::parse("https://api1.nym.com").unwrap(),
Url::parse("https://api2.nym.com").unwrap(),
Url::parse("https://api3.nym.com").unwrap(),
@@ -30,7 +30,6 @@ pub(crate) async fn connect_async(
resolver
.resolve_str(domain)
.await?
.into_iter()
.map(|a| SocketAddr::new(a, port))
.collect()
}
@@ -39,7 +39,6 @@ pub(crate) async fn connect_async(
resolver
.resolve_str(domain)
.await?
.into_iter()
.map(|a| SocketAddr::new(a, port))
.collect()
}
@@ -54,7 +54,7 @@ pub enum GatewayClientError {
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
#[error("No shared key was provided or obtained")]
NoSharedKeyAvailable,
@@ -241,23 +241,28 @@ impl Epoch {
//
// Note: It's important that the variant ordering is not changed otherwise it would mess up the derived `PartialOrd`
#[cw_serde]
#[derive(Copy)]
#[derive(Copy, Default)]
pub enum EpochState {
#[default]
WaitingInitialisation,
PublicKeySubmission { resharing: bool },
DealingExchange { resharing: bool },
VerificationKeySubmission { resharing: bool },
VerificationKeyValidation { resharing: bool },
VerificationKeyFinalization { resharing: bool },
PublicKeySubmission {
resharing: bool,
},
DealingExchange {
resharing: bool,
},
VerificationKeySubmission {
resharing: bool,
},
VerificationKeyValidation {
resharing: bool,
},
VerificationKeyFinalization {
resharing: bool,
},
InProgress,
}
impl Default for EpochState {
fn default() -> Self {
Self::WaitingInitialisation
}
}
impl Display for EpochState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
@@ -246,7 +246,7 @@ mod tests {
let _exp_date_sigs = generate_expiration_date_signatures(
sig_req.expiration_date.ecash_unix_timestamp(),
&[signing_keys.secret_key()],
&vec![signing_keys.verification_key()],
&[signing_keys.verification_key()],
&signing_keys.verification_key(),
&[1],
)?;
@@ -263,7 +263,7 @@ mod tests {
let wallet = issuance.aggregate_signature_shares(
&signing_keys.verification_key(),
&vec![partial_wallet],
&[partial_wallet],
sig_req,
)?;
+3 -3
View File
@@ -82,7 +82,7 @@ mod tests {
let exp_date_sigs = generate_expiration_date_signatures(
sig_req.expiration_date.ecash_unix_timestamp(),
&[keypair.secret_key()],
&vec![keypair.verification_key()],
&[keypair.verification_key()],
&keypair.verification_key(),
&[keypair.index.unwrap()],
)
@@ -106,14 +106,14 @@ mod tests {
.unwrap();
let wallet = issuance
.aggregate_signature_shares(&keypair.verification_key(), &vec![partial_wallet], sig_req)
.aggregate_signature_shares(&keypair.verification_key(), &[partial_wallet], sig_req)
.unwrap();
let mut issued = issuance.into_issued_ticketbook(wallet, 1);
let coin_indices_signatures = generate_coin_indices_signatures(
nym_credentials_interface::ecash_parameters(),
&[keypair.secret_key()],
&vec![keypair.verification_key()],
&[keypair.verification_key()],
&keypair.verification_key(),
&[keypair.index.unwrap()],
)
+2 -3
View File
@@ -32,7 +32,7 @@ thiserror = { workspace = true }
tracing = { workspace = true }
itertools = { workspace = true }
inventory = { workspace = true }
tokio = { workspace = true, features = ["rt", "macros", "time"] }
# used for decoding text responses (they were already implicitly included)
bytes = { workspace = true }
encoding_rs = { workspace = true }
@@ -52,5 +52,4 @@ workspace = true
features = ["tokio"]
[dev-dependencies]
tokio = { workspace = true, features = ["rt", "macros"] }
tracing-subscriber.workspace = true
+367 -88
View File
@@ -30,19 +30,26 @@
use crate::ClientBuilder;
use std::{
net::SocketAddr,
collections::HashMap,
net::{IpAddr, SocketAddr},
str::FromStr,
sync::{Arc, LazyLock},
time::Duration,
};
use hickory_resolver::{
ResolveError, TokioResolver,
TokioResolver,
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ServerOrderingStrategy},
lookup_ip::{LookupIp, LookupIpIntoIter},
lookup_ip::LookupIpIntoIter,
name_server::TokioConnectionProvider,
};
use once_cell::sync::OnceCell;
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
use tracing::warn;
use tracing::*;
mod constants;
mod static_resolver;
pub use static_resolver::*;
impl ClientBuilder {
/// Override the DNS resolver implementation used by the underlying http client.
@@ -59,10 +66,6 @@ impl ClientBuilder {
}
}
struct SocketAddrs {
iter: LookupIpIntoIter,
}
// n.b. static items do not call [`Drop`] on program termination, so this won't be deallocated.
// this is fine, as the OS can deallocate the terminated program faster than we can free memory
// but tools like valgrind might report "memory leaks" as it isn't obvious this is intentional.
@@ -72,11 +75,17 @@ static SHARED_RESOLVER: LazyLock<HickoryDnsResolver> = LazyLock::new(|| {
});
#[derive(Debug, thiserror::Error)]
#[error("hickory-dns resolver error: {hickory_error}")]
#[allow(missing_docs)]
/// Error occurring while resolving a hostname into an IP address.
pub struct HickoryDnsError {
#[from]
hickory_error: ResolveError,
pub enum ResolveError {
#[error("invalid name: {0}")]
InvalidNameError(String),
#[error("hickory-dns resolver error: {0}")]
ResolveError(#[from] hickory_resolver::ResolveError),
#[error("high level lookup timed out")]
Timeout,
#[error("hostname not found in static lookup table")]
StaticLookupMiss,
}
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
@@ -87,69 +96,118 @@ pub struct HickoryDnsError {
/// The default initialization uses a shared underlying `AsyncResolver`. If a thread local resolver
/// is required use `thread_resolver()` to build a resolver with an independently instantiated
/// internal `AsyncResolver`.
#[derive(Debug, Default, Clone)]
#[derive(Debug, Clone)]
pub struct HickoryDnsResolver {
// Since we might not have been called in the context of a
// Tokio Runtime in initialization, so we must delay the actual
// construction of the resolver.
state: Arc<OnceCell<TokioResolver>>,
fallback: Option<Arc<OnceCell<TokioResolver>>>,
static_base: Option<Arc<OnceCell<StaticResolver>>>,
dont_use_shared: bool,
/// Overall timeout for dns lookup associated with any individual host resolution. For example,
/// use of retries, server_ordering_strategy, etc. ends absolutely if this timeout is reached.
overall_dns_timeout: Duration,
}
impl Default for HickoryDnsResolver {
fn default() -> Self {
Self {
state: Default::default(),
// Disable system resolver fallback by default - often blocked by firewalls in VPN environments
// Enable static fallback for known domains
fallback: None,
static_base: Some(Default::default()),
dont_use_shared: Default::default(),
overall_dns_timeout: Duration::from_secs(10),
}
}
}
impl Resolve for HickoryDnsResolver {
fn resolve(&self, name: Name) -> Resolving {
let resolver = self.state.clone();
let maybe_fallback = self.fallback.clone();
let maybe_static = self.static_base.clone();
let independent = self.dont_use_shared;
let overall_dns_timeout = self.overall_dns_timeout;
Box::pin(async move {
let resolver = resolver.get_or_try_init(|| {
// using a closure here is slightly gross, but this makes sure that if the
// lazy-init returns an error it can be handled by the client
if independent {
new_resolver()
} else {
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
}
})?;
resolve(
name,
resolver,
maybe_fallback,
maybe_static,
independent,
overall_dns_timeout,
)
.await
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
})
}
}
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
let lookup = match resolver.lookup_ip(name.as_str()).await {
Ok(res) => res,
Err(e) => {
if let Some(ref fallback) = maybe_fallback {
// on failure use the fall back system configured DNS resolver
if !e.is_no_records_found() {
warn!("primary DNS failed w/ error {e}: using system fallback");
}
let resolver = fallback.get_or_try_init(|| {
// using a closure here is slightly gross, but this makes sure that if the
// lazy-init returns an error it can be handled by the client
if independent {
new_resolver_system()
} else {
Ok(SHARED_RESOLVER
.fallback
.as_ref()
.ok_or(e)? // if the shared resolver has no fallback return the original error
.get_or_try_init(new_resolver_system)?
.clone())
}
})?;
resolver.lookup_ip(name.as_str()).await?
} else {
return Err(e.into());
}
}
};
async fn resolve(
name: Name,
resolver: Arc<OnceCell<TokioResolver>>,
maybe_fallback: Option<Arc<OnceCell<TokioResolver>>>,
maybe_static: Option<Arc<OnceCell<StaticResolver>>>,
independent: bool,
overall_dns_timeout: Duration,
) -> Result<Addrs, ResolveError> {
let resolver = resolver.get_or_try_init(|| HickoryDnsResolver::new_resolver(independent))?;
// Attempt a lookup using the primary resolver
let resolve_fut = tokio::time::timeout(overall_dns_timeout, resolver.lookup_ip(name.as_str()));
let primary_err = match resolve_fut.await {
Err(_) => ResolveError::Timeout,
Ok(Ok(lookup)) => {
let addrs: Addrs = Box::new(SocketAddrs {
iter: lookup.into_iter(),
});
Ok(addrs)
})
return Ok(addrs);
}
Ok(Err(e)) => {
// on failure use the fall back system configured DNS resolver
if !e.is_no_records_found() {
warn!("primary DNS failed w/ error: {e}");
}
e.into()
}
};
// If the primary resolver encountered an error, attempt a lookup using the fallback
// resolver if one is configured.
if let Some(ref fallback) = maybe_fallback {
let resolver =
fallback.get_or_try_init(|| HickoryDnsResolver::new_resolver_system(independent))?;
let resolve_fut =
tokio::time::timeout(overall_dns_timeout, resolver.lookup_ip(name.as_str()));
if let Ok(Ok(lookup)) = resolve_fut.await {
let addrs: Addrs = Box::new(SocketAddrs {
iter: lookup.into_iter(),
});
return Ok(addrs);
}
}
// If no record has been found and a static map of fallback addresses is configured
// check the table for our entry
if let Some(ref static_resolver) = maybe_static {
debug!("checking static");
let resolver =
static_resolver.get_or_init(|| HickoryDnsResolver::new_static_fallback(independent));
if let Ok(addrs) = resolver.resolve(name).await {
return Ok(addrs);
}
}
Err(primary_err)
}
struct SocketAddrs {
iter: LookupIpIntoIter,
}
impl Iterator for SocketAddrs {
@@ -162,28 +220,22 @@ impl Iterator for SocketAddrs {
impl HickoryDnsResolver {
/// Attempt to resolve a domain name to a set of ['IpAddr']s
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
let resolver = self.state.get_or_try_init(|| self.new_resolver())?;
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
let lookup = match resolver.lookup_ip(name).await {
Ok(res) => res,
Err(e) => {
if let Some(ref fallback) = self.fallback {
// on failure use the fall back system configured DNS resolver
if !e.is_no_records_found() {
warn!("primary DNS failed w/ error {e}: using system fallback");
}
let resolver = fallback.get_or_try_init(|| self.new_resolver_system())?;
resolver.lookup_ip(name).await?
} else {
return Err(e.into());
}
}
};
Ok(lookup)
pub async fn resolve_str(
&self,
name: &str,
) -> Result<impl Iterator<Item = IpAddr> + use<>, ResolveError> {
let n =
Name::from_str(name).map_err(|_| ResolveError::InvalidNameError(name.to_string()))?;
resolve(
n,
self.state.clone(),
self.fallback.clone(),
self.static_base.clone(),
self.dont_use_shared,
self.overall_dns_timeout,
)
.await
.map(|addrs| addrs.map(|socket_addr| socket_addr.ip()))
}
/// Create a (lazy-initialized) resolver that is not shared across threads.
@@ -194,16 +246,20 @@ impl HickoryDnsResolver {
}
}
fn new_resolver(&self) -> Result<TokioResolver, HickoryDnsError> {
if self.dont_use_shared {
fn new_resolver(dont_use_shared: bool) -> Result<TokioResolver, ResolveError> {
// using a closure here is slightly gross, but this makes sure that if the
// lazy-init returns an error it can be handled by the client
if dont_use_shared {
new_resolver()
} else {
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
}
}
fn new_resolver_system(&self) -> Result<TokioResolver, HickoryDnsError> {
if self.dont_use_shared || SHARED_RESOLVER.fallback.is_none() {
fn new_resolver_system(dont_use_shared: bool) -> Result<TokioResolver, ResolveError> {
// using a closure here is slightly gross, but this makes sure that if the
// lazy-init returns an error it can be handled by the client
if dont_use_shared || SHARED_RESOLVER.fallback.is_none() {
new_resolver_system()
} else {
Ok(SHARED_RESOLVER
@@ -215,8 +271,18 @@ impl HickoryDnsResolver {
}
}
fn new_static_fallback(dont_use_shared: bool) -> StaticResolver {
if !dont_use_shared && let Some(ref shared_resolver) = SHARED_RESOLVER.static_base {
shared_resolver
.get_or_init(new_default_static_fallback)
.clone()
} else {
new_default_static_fallback()
}
}
/// Enable fallback to the system default resolver if the primary (DoX) resolver fails
pub fn enable_system_fallback(&mut self) -> Result<(), HickoryDnsError> {
pub fn enable_system_fallback(&mut self) -> Result<(), ResolveError> {
self.fallback = Some(Default::default());
let _ = self
.fallback
@@ -231,22 +297,52 @@ impl HickoryDnsResolver {
pub fn disable_system_fallback(&mut self) {
self.fallback = None;
}
/// Get the current map of hostname to address in use by the fallback static lookup if one
/// exists.
pub fn get_static_fallbacks(&self) -> Option<HashMap<String, Vec<IpAddr>>> {
Some(self.static_base.as_ref()?.get()?.get_addrs())
}
/// Set (or overwrite) the map of addresses used in the fallback static hostname lookup
pub fn set_static_fallbacks(&mut self, addrs: HashMap<String, Vec<IpAddr>>) {
let cell = OnceCell::new();
cell.set(StaticResolver::new(addrs))
.expect("infallible assign");
self.static_base = Some(Arc::new(cell));
}
}
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
/// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm.
fn new_resolver() -> Result<TokioResolver, HickoryDnsError> {
///
/// Timeout Defaults to 5 seconds
/// Number of retries after lookup failure before giving up Defaults to 2
///
/// Caches successfully resolved addresses for 30 minutes to prevent continual use of remote lookup.
/// This resolver is intended to be used for OUR API endpoints that do not rapidly rotate IPs.
fn new_resolver() -> Result<TokioResolver, ResolveError> {
info!("building new configured resolver");
let mut name_servers = NameServerConfigGroup::quad9_tls();
name_servers.merge(NameServerConfigGroup::quad9_https());
name_servers.merge(NameServerConfigGroup::cloudflare_tls());
name_servers.merge(NameServerConfigGroup::cloudflare_https());
configure_and_build_resolver(name_servers)
}
fn configure_and_build_resolver(
name_servers: NameServerConfigGroup,
) -> Result<TokioResolver, ResolveError> {
let config = ResolverConfig::from_parts(None, Vec::new(), name_servers);
let mut resolver_builder =
TokioResolver::builder_with_config(config, TokioConnectionProvider::default());
resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
resolver_builder.options_mut().ip_strategy = get_ip_strategy();
resolver_builder.options_mut().server_ordering_strategy = ServerOrderingStrategy::RoundRobin;
// Cache successful responses for queries received by this resolver for 30 min minimum.
resolver_builder.options_mut().positive_min_ttl = Some(Duration::from_secs(1800));
Ok(resolver_builder.build())
}
@@ -254,20 +350,54 @@ fn new_resolver() -> Result<TokioResolver, HickoryDnsError> {
/// Create a new resolver with the default configuration, which reads from the system DNS config
/// (i.e. `/etc/resolve.conf` in unix). The options are overridden to look up for both IPv4 and IPv6
/// addresses to work with "happy eyeballs" algorithm.
fn new_resolver_system() -> Result<TokioResolver, HickoryDnsError> {
fn new_resolver_system() -> Result<TokioResolver, ResolveError> {
let mut resolver_builder = TokioResolver::builder_tokio()?;
resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
resolver_builder.options_mut().ip_strategy = get_ip_strategy();
Ok(resolver_builder.build())
}
fn new_default_static_fallback() -> StaticResolver {
StaticResolver::new(constants::default_static_addrs())
}
/// Check if IPv6 stack is available for DNS resolution.
fn should_use_ipv6_dns() -> bool {
use std::net::UdpSocket;
match UdpSocket::bind("[::]:0") {
Ok(_) => {
debug!("IPv6 stack available - enabling dual-stack DNS");
true
}
Err(e) => {
debug!("IPv6 unavailable ({}), using IPv4-only DNS", e);
false
}
}
}
/// Get DNS lookup strategy based on IPv6 availability.
fn get_ip_strategy() -> LookupIpStrategy {
if should_use_ipv6_dns() {
debug!("Using dual-stack DNS (IPv4 + IPv6)");
LookupIpStrategy::Ipv4AndIpv6
} else {
debug!("Using IPv4-only DNS");
LookupIpStrategy::Ipv4Only
}
}
#[cfg(test)]
mod test {
use super::*;
use itertools::Itertools;
use std::collections::HashMap;
#[tokio::test]
async fn reqwest_hickory_doh() {
let resolver = HickoryDnsResolver::default();
async fn reqwest_with_custom_dns() {
let var_name = HickoryDnsResolver::default();
let resolver = var_name;
let client = reqwest::ClientBuilder::new()
.dns_resolver(resolver.into())
.build()
@@ -286,7 +416,7 @@ mod test {
}
#[tokio::test]
async fn dns_lookup() -> Result<(), HickoryDnsError> {
async fn dns_lookup() -> Result<(), ResolveError> {
let resolver = HickoryDnsResolver::default();
let domain = "ifconfig.me";
@@ -296,4 +426,153 @@ mod test {
Ok(())
}
#[tokio::test]
async fn static_resolver_as_fallback() -> Result<(), ResolveError> {
let example_domain = "non-existent.nymvpn.com";
let mut resolver = HickoryDnsResolver {
..Default::default()
};
let result = resolver.resolve_str(example_domain).await;
assert!(result.is_err()); // should be NXDomain
resolver.static_base = Some(Default::default());
let mut addr_map = HashMap::new();
let example_ip4: IpAddr = "10.10.10.10".parse().unwrap();
let example_ip6: IpAddr = "dead::beef".parse().unwrap();
addr_map.insert(example_domain.to_string(), vec![example_ip4, example_ip6]);
resolver.set_static_fallbacks(addr_map);
let mut addrs = resolver.resolve_str(example_domain).await?;
assert!(addrs.contains(&example_ip4));
assert!(addrs.contains(&example_ip6));
Ok(())
}
#[test]
fn default_resolver_fallback_config() {
let resolver = HickoryDnsResolver::default();
assert!(
resolver.fallback.is_none(),
"system fallback should be disabled by default for VPN environments"
);
assert!(
resolver.static_base.is_some(),
"static fallback should be enabled by default"
);
}
#[test]
fn ipv6_detection_returns_valid_strategy() {
let strategy = get_ip_strategy();
match strategy {
LookupIpStrategy::Ipv4Only | LookupIpStrategy::Ipv4AndIpv6 => {}
_ => panic!("Unexpected IP strategy returned: {:?}", strategy),
}
}
#[test]
fn ipv6_dns_detection_is_consistent() {
let first_result = should_use_ipv6_dns();
let second_result = should_use_ipv6_dns();
assert_eq!(first_result, second_result);
}
}
#[cfg(test)]
mod failure_test {
use super::*;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
/// IP addresses guaranteed to fail attempts to resolve
///
/// Addresses drawn from blocks set off by RFC5737 (ipv4) and RFC3849 (ipv6)
const GUARANTEED_BROKEN_IPS_1: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)),
IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1)),
IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1111)),
IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1001)),
];
// Create a resolver that behaves the same as the custom configured router, except for the fact
// that it is guaranteed to fail.
fn build_broken_resolver() -> Result<TokioResolver, ResolveError> {
info!("building new faulty resolver");
let mut broken_ns_group = NameServerConfigGroup::from_ips_tls(
GUARANTEED_BROKEN_IPS_1,
853,
"cloudflare-dns.com".to_string(),
true,
);
let broken_ns_https = NameServerConfigGroup::from_ips_https(
GUARANTEED_BROKEN_IPS_1,
443,
"cloudflare-dns.com".to_string(),
true,
);
broken_ns_group.merge(broken_ns_https);
configure_and_build_resolver(broken_ns_group)
}
#[tokio::test]
async fn dns_lookup_failures() -> Result<(), ResolveError> {
let time_start = std::time::Instant::now();
let r = OnceCell::new();
r.set(build_broken_resolver().expect("failed to build resolver"))
.expect("broken resolver init error");
// create a new resolver that won't mess with the shared resolver used by other tests
let resolver = HickoryDnsResolver {
dont_use_shared: true,
state: Arc::new(r),
overall_dns_timeout: Duration::from_secs(5),
..Default::default()
};
build_broken_resolver()?;
let domain = "ifconfig.me";
let result = resolver.resolve_str(domain).await;
assert!(result.is_err_and(|e| matches!(e, ResolveError::Timeout)));
let duration = time_start.elapsed();
assert!(duration < resolver.overall_dns_timeout + Duration::from_secs(1));
Ok(())
}
#[tokio::test]
async fn fallback_to_static() -> Result<(), ResolveError> {
let r = OnceCell::new();
r.set(build_broken_resolver().expect("failed to build resolver"))
.expect("broken resolver init error");
// create a new resolver that won't mess with the shared resolver used by other tests
let resolver = HickoryDnsResolver {
dont_use_shared: true,
state: Arc::new(r),
static_base: Some(Default::default()),
overall_dns_timeout: Duration::from_secs(5),
..Default::default()
};
build_broken_resolver()?;
// successful lookup using fallback to static resolver
let domain = "nymvpn.com";
let _ = resolver
.resolve_str(domain)
.await
.expect("failed to resolve address in static lookup");
// unsuccessful lookup - primary times out, and not in
let domain = "non-existent.nymtech.net";
let result = resolver.resolve_str(domain).await;
assert!(result.is_err_and(|e| matches!(e, ResolveError::Timeout)));
Ok(())
}
}
@@ -0,0 +1,73 @@
#![allow(missing_docs)]
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr};
pub const NYM_API_DOMAIN: &str = "validator.nymtech.net";
pub const NYM_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(212, 71, 233, 232))];
pub const NYM_VPN_API_DOMAIN: &str = "nymvpn.com";
pub const NYM_VPN_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 21))];
pub const NYM_FRONTDOOR_VERCEL_DOMAIN: &str = "nym-frontdoor.vercel.app";
pub const NYM_FRONTDOOR_VERCEL_IPS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(64, 29, 17, 195)),
IpAddr::V4(Ipv4Addr::new(216, 198, 79, 195)),
];
pub const NYM_FRONTDOOR_FASTLY_DOMAIN: &str = "nym-frontdoor.global.ssl.fastly.net";
pub const NYM_FRONTDOOR_FASTLY_IPS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(151, 101, 193, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 129, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 1, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 65, 194)),
];
pub const NYMVPN_FRONTDOOR_FASTLY_DOMAIN: &str = "nymvpn-frontdoor.global.ssl.fastly.net";
pub const NYMVPN_FRONTDOOR_FASTLY_IPS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(151, 101, 193, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 129, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 1, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 65, 194)),
];
pub const VERCEL_APP_DOMAIN: &str = "vercel.app";
pub const VERCEL_APP_IPS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(64, 29, 17, 195)),
IpAddr::V4(Ipv4Addr::new(216, 198, 79, 195)),
];
pub const VERCEL_COM_DOMAIN: &str = "vercel.com";
pub const VERCEL_COM_IPS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(198, 169, 2, 129)),
IpAddr::V4(Ipv4Addr::new(198, 169, 1, 193)),
];
pub const NYM_COM_DOMAIN: &str = "nym.com";
pub const NYM_COM_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 22))];
pub const NYM_STATS_API_DOMAIN: &str = "nym-statistics-api.nymtech.cc";
pub const NYM_STATS_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(91, 92, 153, 96))];
pub fn default_static_addrs() -> HashMap<String, Vec<IpAddr>> {
let mut m = HashMap::new();
m.insert(NYM_API_DOMAIN.to_string(), NYM_API_IPS.to_vec());
m.insert(NYM_VPN_API_DOMAIN.to_string(), NYM_VPN_API_IPS.to_vec());
m.insert(
NYM_FRONTDOOR_VERCEL_DOMAIN.to_string(),
NYM_FRONTDOOR_VERCEL_IPS.to_vec(),
);
m.insert(
NYM_FRONTDOOR_FASTLY_DOMAIN.to_string(),
NYM_FRONTDOOR_FASTLY_IPS.to_vec(),
);
m.insert(
NYMVPN_FRONTDOOR_FASTLY_DOMAIN.to_string(),
NYMVPN_FRONTDOOR_FASTLY_IPS.to_vec(),
);
m.insert(VERCEL_APP_DOMAIN.to_string(), VERCEL_APP_IPS.to_vec());
m.insert(VERCEL_COM_DOMAIN.to_string(), VERCEL_COM_IPS.to_vec());
m.insert(NYM_COM_DOMAIN.to_string(), NYM_COM_IPS.to_vec());
m.insert(NYM_STATS_API_DOMAIN.to_string(), NYM_STATS_API_IPS.to_vec());
m
}
@@ -0,0 +1,89 @@
use crate::dns::ResolveError;
use std::{
collections::HashMap,
net::{IpAddr, SocketAddr},
sync::{Arc, Mutex},
};
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
use tracing::*;
#[derive(Debug, Default, Clone)]
pub struct StaticResolver {
static_addr_map: Arc<Mutex<HashMap<String, Vec<IpAddr>>>>,
}
impl StaticResolver {
pub fn new(static_entries: HashMap<String, Vec<IpAddr>>) -> StaticResolver {
debug!("building static resolver");
Self {
static_addr_map: Arc::new(Mutex::new(static_entries)),
}
}
pub fn get_addrs(&self) -> HashMap<String, Vec<IpAddr>> {
self.static_addr_map.lock().unwrap().clone()
}
}
impl Resolve for StaticResolver {
fn resolve(&self, name: Name) -> Resolving {
debug!("looking up {name:?} in static resolver");
let addr_map = self.static_addr_map.clone();
Box::pin(async move {
let addr_map = addr_map.lock().unwrap();
let lookup = match addr_map.get(name.as_str()) {
None => return Err(ResolveError::StaticLookupMiss.into()),
Some(addrs) => addrs,
};
let addrs: Addrs = Box::new(
lookup
.clone()
.into_iter()
.map(|ip_addr| SocketAddr::new(ip_addr, 0)),
);
Ok(addrs)
})
}
}
#[cfg(test)]
mod test {
use itertools::Itertools;
use super::*;
use std::error::Error as StdError;
use std::str::FromStr;
#[tokio::test]
async fn lookup_using_static_resolver() -> Result<(), Box<dyn StdError + Send + Sync>> {
let example_domain = String::from("static.nymvpn.com");
// lookup for domain for which there is no entry
let resolver = StaticResolver::new(HashMap::new());
let url = reqwest::dns::Name::from_str(&example_domain).unwrap();
let result = resolver.resolve(url).await;
assert!(result.is_err());
match result {
Ok(_) => panic!("lookup with empty map should fail"),
Err(e) => assert_eq!(e.to_string(), ResolveError::StaticLookupMiss.to_string()),
}
// Successful lookup
let mut addr_map = HashMap::new();
let example_ip4: IpAddr = "10.10.10.10".parse().unwrap();
let example_ip6: IpAddr = "dead::beef".parse().unwrap();
addr_map.insert(example_domain.clone(), vec![example_ip4, example_ip6]);
let url = reqwest::dns::Name::from_str(&example_domain).unwrap();
let resolver = StaticResolver::new(addr_map);
let mut addrs = resolver.resolve(url).await?;
assert!(addrs.contains(&SocketAddr::new(example_ip4, 0)));
assert!(addrs.contains(&SocketAddr::new(example_ip6, 0)));
Ok(())
}
}
+1 -1
View File
@@ -179,7 +179,7 @@ mod dns;
mod path;
#[cfg(not(target_arch = "wasm32"))]
pub use dns::{HickoryDnsError, HickoryDnsResolver};
pub use dns::{HickoryDnsResolver, ResolveError};
// helper for generating user agent based on binary information
#[cfg(not(target_arch = "wasm32"))]
+1 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.67"
version = "1.1.69"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
+13 -4
View File
@@ -2,7 +2,7 @@ use nym_credentials_interface::TicketType;
use nym_sdk::mixnet::InputMessage;
#[derive(thiserror::Error, Debug)]
pub enum Error {
pub enum AuthenticationClientError {
#[error("mixnet client stopped returning responses")]
NoMixnetMessagesReceived,
@@ -42,10 +42,19 @@ pub enum Error {
#[error("unknown authenticator version number")]
UnsupportedAuthenticatorVersion,
}
#[error("failed to wait on AuthenticatorClientListener")]
FailedToJoinOnTask(#[from] tokio::task::JoinError),
#[derive(thiserror::Error, Debug)]
pub enum RegistrationError {
#[error(transparent)]
NoCredentialSent(AuthenticationClientError), // This intentionnally doesn't use `from` to avoid random ? operator to land here when they shouldn't
#[error("an error occured after a credential was sent : {source}")]
CredentialSent {
#[source]
source: AuthenticationClientError,
},
}
// Result type based on our error type
pub type Result<T> = std::result::Result<T, Error>;
pub(crate) type Result<T> = std::result::Result<T, AuthenticationClientError>;
+57 -36
View File
@@ -6,11 +6,11 @@ use nym_bandwidth_controller::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND
use nym_crypto::asymmetric::x25519::KeyPair;
use nym_registration_common::GatewayData;
use std::net::{IpAddr, SocketAddr};
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use tracing::{debug, error, trace};
use crate::error::Result;
use crate::mixnet_listener::{MixnetMessageBroadcastReceiver, MixnetMessageInputSender};
use nym_authenticator_requests::{
AuthenticatorVersion, client_message::ClientMessage, response::AuthenticatorResponse,
@@ -25,7 +25,7 @@ mod error;
mod helpers;
mod mixnet_listener;
pub use crate::error::{Error, Result};
pub use crate::error::{AuthenticationClientError, RegistrationError};
pub use crate::mixnet_listener::{AuthClientMixnetListener, AuthClientMixnetListenerHandle};
pub struct AuthenticatorClient {
@@ -91,7 +91,7 @@ impl AuthenticatorClient {
self.mixnet_sender
.send(input_message)
.await
.map_err(|e| Error::SendMixnetMessage(Box::new(e)))?;
.map_err(|e| AuthenticationClientError::SendMixnetMessage(Box::new(e)))?;
Ok(request_id)
}
@@ -104,11 +104,11 @@ impl AuthenticatorClient {
tokio::select! {
_ = &mut timeout => {
error!("Timed out waiting for reply to connect request");
return Err(Error::TimeoutWaitingForConnectResponse);
return Err(AuthenticationClientError::TimeoutWaitingForConnectResponse);
}
msg = self.mixnet_listener.recv() => match msg {
Err(_) => {
return Err(Error::NoMixnetMessagesReceived);
return Err(AuthenticationClientError::NoMixnetMessagesReceived);
}
Ok(msg) => {
let Some(header) = msg.message.first_chunk::<2>() else {
@@ -131,12 +131,12 @@ impl AuthenticatorClient {
// Then we deserialize the message
debug!("AuthClient: got message while waiting for connect response with version {version:?}");
let ret: Result<AuthenticatorResponse> = match version {
AuthenticatorVersion::V1 => Err(Error::UnsupportedVersion),
AuthenticatorVersion::V1 => Err(AuthenticationClientError::UnsupportedVersion),
AuthenticatorVersion::V2 => v2::response::AuthenticatorResponse::from_reconstructed_message(&msg).map(Into::into).map_err(Into::into),
AuthenticatorVersion::V3 => v3::response::AuthenticatorResponse::from_reconstructed_message(&msg).map(Into::into).map_err(Into::into),
AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::from_reconstructed_message(&msg).map(Into::into).map_err(Into::into),
AuthenticatorVersion::V5 => v5::response::AuthenticatorResponse::from_reconstructed_message(&msg).map(Into::into).map_err(Into::into),
AuthenticatorVersion::UNKNOWN => Err(Error::UnknownVersion),
AuthenticatorVersion::UNKNOWN => Err(AuthenticationClientError::UnknownVersion),
};
let Ok(response) = ret else {
// This is ok, it's likely just one of our self-pings
@@ -158,10 +158,14 @@ impl AuthenticatorClient {
&mut self,
controller: &dyn BandwidthTicketProvider,
ticketbook_type: TicketType,
) -> Result<GatewayData> {
) -> std::result::Result<GatewayData, RegistrationError> {
debug!("Registering with the wg gateway...");
let init_message = match self.auth_version {
AuthenticatorVersion::V1 => return Err(Error::UnsupportedAuthenticatorVersion),
AuthenticatorVersion::V1 | AuthenticatorVersion::UNKNOWN => {
return Err(RegistrationError::NoCredentialSent(
AuthenticationClientError::UnsupportedAuthenticatorVersion,
));
}
AuthenticatorVersion::V2 => {
ClientMessage::Initial(Box::new(v2::registration::InitMessage {
pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()),
@@ -182,16 +186,20 @@ impl AuthenticatorClient {
pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()),
}))
}
AuthenticatorVersion::UNKNOWN => return Err(Error::UnsupportedAuthenticatorVersion),
};
trace!("sending init msg to {}: {:?}", &self.ip_addr, &init_message);
let response = self.send_and_wait_for_response(&init_message).await?;
let response = self
.send_and_wait_for_response(&init_message)
.await
.map_err(RegistrationError::NoCredentialSent)?;
let registered_data = match response {
AuthenticatorResponse::PendingRegistration(pending_registration_response) => {
// Unwrap since we have already checked that we have the keypair.
debug!("Verifying data");
if let Err(e) = pending_registration_response.verify(self.keypair.private_key()) {
return Err(Error::VerificationFailed(e));
return Err(RegistrationError::NoCredentialSent(
AuthenticationClientError::VerificationFailed(e),
));
}
trace!(
@@ -199,6 +207,7 @@ impl AuthenticatorClient {
&self.ip_addr, &pending_registration_response
);
// This call takes care of updating the credential count in storage, so failure of this must be counted as credential waste
let credential = Some(
controller
.get_ecash_ticket(
@@ -207,15 +216,21 @@ impl AuthenticatorClient {
DEFAULT_TICKETS_TO_SPEND,
)
.await
.map_err(|source| Error::GetTicket {
ticketbook_type,
source,
.map_err(|source| RegistrationError::CredentialSent {
source: AuthenticationClientError::GetTicket {
ticketbook_type,
source,
},
})?
.data,
);
let finalized_message = match self.auth_version {
AuthenticatorVersion::V1 => return Err(Error::UnsupportedAuthenticatorVersion),
AuthenticatorVersion::V1 | AuthenticatorVersion::UNKNOWN => {
return Err(RegistrationError::CredentialSent {
source: AuthenticationClientError::UnsupportedAuthenticatorVersion,
});
}
AuthenticatorVersion::V2 => {
ClientMessage::Final(Box::new(v2::registration::FinalMessage {
gateway_client: v2::registration::GatewayClient::new(
@@ -260,23 +275,29 @@ impl AuthenticatorClient {
credential,
}))
}
AuthenticatorVersion::UNKNOWN => {
return Err(Error::UnsupportedAuthenticatorVersion);
}
};
trace!(
"sending final msg to {}: {:?}",
&self.ip_addr, &finalized_message
);
let response = self.send_and_wait_for_response(&finalized_message).await?;
let response = self
.send_and_wait_for_response(&finalized_message)
.await
.map_err(|source| RegistrationError::CredentialSent { source })?;
let AuthenticatorResponse::Registered(registered_response) = response else {
return Err(Error::InvalidGatewayAuthResponse);
return Err(RegistrationError::CredentialSent {
source: AuthenticationClientError::InvalidGatewayAuthResponse,
});
};
registered_response
}
AuthenticatorResponse::Registered(registered_response) => registered_response,
_ => return Err(Error::InvalidGatewayAuthResponse),
_ => {
return Err(RegistrationError::NoCredentialSent(
AuthenticationClientError::InvalidGatewayAuthResponse,
));
}
};
trace!(
@@ -286,12 +307,7 @@ impl AuthenticatorClient {
let gateway_data = GatewayData {
public_key: registered_data.pub_key().inner().into(),
endpoint: SocketAddr::from_str(&format!(
"{}:{}",
self.ip_addr,
registered_data.wg_port()
))
.map_err(Error::FailedToParseEntryGatewaySocketAddr)?,
endpoint: SocketAddr::new(self.ip_addr, registered_data.wg_port()),
private_ipv4: registered_data.private_ips().ipv4,
private_ipv6: registered_data.private_ips().ipv6,
};
@@ -299,9 +315,12 @@ impl AuthenticatorClient {
Ok(gateway_data)
}
// This is up to the caller to know nothing is ever spent there
pub async fn query_bandwidth(&mut self) -> Result<Option<i64>> {
let query_message = match self.auth_version {
AuthenticatorVersion::V1 => return Err(Error::UnsupportedAuthenticatorVersion),
AuthenticatorVersion::V1 => {
return Err(AuthenticationClientError::UnsupportedAuthenticatorVersion);
}
AuthenticatorVersion::V2 => ClientMessage::Query(Box::new(QueryMessageImpl {
pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()),
version: AuthenticatorVersion::V2,
@@ -318,7 +337,9 @@ impl AuthenticatorClient {
pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()),
version: AuthenticatorVersion::V5,
})),
AuthenticatorVersion::UNKNOWN => return Err(Error::UnsupportedAuthenticatorVersion),
AuthenticatorVersion::UNKNOWN => {
return Err(AuthenticationClientError::UnsupportedAuthenticatorVersion);
}
};
let response = self.send_and_wait_for_response(&query_message).await?;
@@ -332,7 +353,7 @@ impl AuthenticatorClient {
return Ok(None);
}
}
_ => return Err(Error::InvalidGatewayAuthResponse),
_ => return Err(AuthenticationClientError::InvalidGatewayAuthResponse),
};
let remaining_pretty = if available_bandwidth > 1024 * 1024 {
@@ -347,13 +368,13 @@ impl AuthenticatorClient {
);
if available_bandwidth < 1024 * 1024 {
tracing::warn!(
"Remaining bandwidth is under 1 MB. The wireguard mode will get suspended after that until tomorrow, UTC time. The client might shutdown with timeout soon
"
);
"Remaining bandwidth is under 1 MB. The wireguard mode will get suspended after that until tomorrow, UTC time. The client might shutdown with timeout soon"
);
}
Ok(Some(available_bandwidth))
}
// Since the caller provides the credential, it knows it is spent
pub async fn top_up(&mut self, credential: CredentialSpendingData) -> Result<i64> {
let top_up_message = match self.auth_version {
AuthenticatorVersion::V3 => ClientMessage::TopUp(Box::new(v3::topup::TopUpMessage {
@@ -371,7 +392,7 @@ impl AuthenticatorClient {
credential,
})),
AuthenticatorVersion::V1 | AuthenticatorVersion::V2 | AuthenticatorVersion::UNKNOWN => {
return Err(Error::UnsupportedAuthenticatorVersion);
return Err(AuthenticationClientError::UnsupportedAuthenticatorVersion);
}
};
let response = self.send_and_wait_for_response(&top_up_message).await?;
@@ -380,7 +401,7 @@ impl AuthenticatorClient {
AuthenticatorResponse::TopUpBandwidth(top_up_bandwidth_response) => {
top_up_bandwidth_response.available_bandwidth()
}
_ => return Err(Error::InvalidGatewayAuthResponse),
_ => return Err(AuthenticationClientError::InvalidGatewayAuthResponse),
};
Ok(remaining_bandwidth)
@@ -50,7 +50,7 @@ impl AuthClientMixnetListener {
}
}
async fn run(mut self) -> Self {
async fn run(mut self) {
let mixnet_cancel_token = self.mixnet_client.cancellation_token();
self.shutdown_token.run_until_cancelled(async {
loop {
@@ -95,12 +95,8 @@ impl AuthClientMixnetListener {
tracing::debug!("AuthClientMixnetListener is shutting down");
}).await;
self
}
// Disconnects the mixnet client and effectively drop itself, since it doesn't work without one, and reconnecting isn't supported
pub async fn disconnect_mixnet_client(self) {
if !self.mixnet_client.cancellation_token().is_cancelled() {
tracing::debug!("AuthClientMixnetListener: Disconnect mixnet client");
if !mixnet_cancel_token.is_cancelled() {
self.mixnet_client.disconnect().await;
}
}
@@ -128,7 +124,7 @@ pub struct AuthClientMixnetListenerHandle {
message_sender: MixnetMessageInputSender,
cancellation_token: CancellationToken,
mixnet_cancellation_token: CancellationToken,
handle: JoinHandle<AuthClientMixnetListener>,
handle: JoinHandle<()>,
}
impl AuthClientMixnetListenerHandle {
@@ -148,13 +144,8 @@ impl AuthClientMixnetListenerHandle {
// If shutdown was externally called, that call is a no-op
// If we're only stopping this, it is very much needed
self.cancellation_token.cancel();
match self.handle.await {
Ok(auth_client_mixnet_listener) => {
auth_client_mixnet_listener.disconnect_mixnet_client().await;
}
Err(e) => {
tracing::error!("Error waiting for auth clients mixnet listener to stop: {e}");
}
if let Err(e) = self.handle.await {
tracing::error!("Error waiting for auth clients mixnet listener to stop: {e}")
}
}
}
+1 -1
View File
@@ -765,7 +765,7 @@ async fn connect_exit(
);
// The IPR supports cancellation, but it's unused in the gateway probe
let cancel_token = CancellationToken::new();
let mut ipr_client = IprClientConnect::new(mixnet_client, cancel_token).await;
let mut ipr_client = IprClientConnect::new(mixnet_client, cancel_token);
let maybe_ip_pair = ipr_client.connect(exit_router_address).await;
let mixnet_client = ipr_client.into_mixnet_client();
+1 -1
View File
@@ -43,7 +43,7 @@ pub struct IprClientConnect {
}
impl IprClientConnect {
pub async fn new(mixnet_client: MixnetClient, cancel_token: CancellationToken) -> Self {
pub fn new(mixnet_client: MixnetClient, cancel_token: CancellationToken) -> Self {
Self {
mixnet_client,
connected: ConnectionState::Disconnected,
@@ -1,21 +1,17 @@
# this will only work with VPN, otherwise remove the harbor part
FROM harbor.nymte.ch/dockerhub/rust:latest AS builder
ARG GIT_REF=main
RUN apt update && apt install -yy libdbus-1-dev pkg-config libclang-dev
# Install go
RUN wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz -O go.tar.gz
RUN tar -xzvf go.tar.gz -C /usr/local
RUN git clone https://github.com/nymtech/nym-vpn-client /usr/src/nym-vpn-client
RUN cd /usr/src/nym-vpn-client && git checkout $GIT_REF
ENV PATH=/go/bin:/usr/local/go/bin:$PATH
WORKDIR /usr/src/nym-vpn-client/nym-vpn-core
RUN cargo build --release --package nym-gateway-probe
COPY ./ /usr/src/nym
WORKDIR /usr/src/nym
RUN cargo build --release --package nym-gateway-probe
WORKDIR /usr/src/nym/nym-node-status-api/nym-node-status-agent
RUN cargo build --release
@@ -35,7 +31,7 @@ RUN apt-get update && apt-get install -y ca-certificates
WORKDIR /nym
COPY --from=builder /usr/src/nym/target/release/nym-node-status-agent ./
COPY --from=builder /usr/src/nym-vpn-client/nym-vpn-core/target/release/nym-gateway-probe ./
COPY --from=builder /usr/src/nym/target/release/nym-gateway-probe ./
COPY --from=builder /usr/src/nym/nym-node-status-api/nym-node-status-agent/entrypoint.sh ./
RUN chmod +x /nym/entrypoint.sh
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node"
version = "1.19.0"
version = "1.21.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+72 -6
View File
@@ -35,19 +35,85 @@ pub enum RegistrationClientError {
#[error("timeout connecting the mixnet client")]
Timeout(#[from] tokio::time::error::Elapsed),
#[error("failed to register wireguard with the gateway for {gateway_id}")]
EntryGatewayRegisterWireguard {
#[error(
"failed to register wireguard with the gateway for {gateway_id}, no credential was sent"
)]
WireguardEntryRegistration {
gateway_id: String,
authenticator_address: Box<nym_sdk::mixnet::Recipient>,
#[source]
source: Box<nym_authenticator_client::Error>,
source: Box<nym_authenticator_client::AuthenticationClientError>,
},
#[error("failed to register wireguard with the gateway for {gateway_id}")]
ExitGatewayRegisterWireguard {
#[error(
"failed to register wireguard with the gateway for {gateway_id}, no credential was sent"
)]
WireguardExitRegistration {
gateway_id: String,
authenticator_address: Box<nym_sdk::mixnet::Recipient>,
#[source]
source: Box<nym_authenticator_client::Error>,
source: Box<nym_authenticator_client::AuthenticationClientError>,
},
#[error(
"failed to register wireguard with the gateway for {gateway_id}, a credential was sent"
)]
WireguardEntryRegistrationCredentialSent {
gateway_id: String,
authenticator_address: Box<nym_sdk::mixnet::Recipient>,
#[source]
source: Box<nym_authenticator_client::AuthenticationClientError>,
},
#[error(
"failed to register wireguard with the gateway for {gateway_id}, a credential was sent"
)]
WireguardExitRegistrationCredentialSent {
gateway_id: String,
authenticator_address: Box<nym_sdk::mixnet::Recipient>,
#[source]
source: Box<nym_authenticator_client::AuthenticationClientError>,
},
}
impl RegistrationClientError {
pub fn from_authenticator_error(
error: nym_authenticator_client::RegistrationError,
gateway_id: String,
authenticator_address: nym_sdk::mixnet::Recipient,
entry: bool,
) -> Self {
match error {
nym_authenticator_client::RegistrationError::NoCredentialSent(source) => {
if entry {
Self::WireguardEntryRegistration {
gateway_id,
authenticator_address: Box::new(authenticator_address),
source: Box::new(source),
}
} else {
Self::WireguardExitRegistration {
gateway_id,
authenticator_address: Box::new(authenticator_address),
source: Box::new(source),
}
}
}
nym_authenticator_client::RegistrationError::CredentialSent { source } => {
if entry {
Self::WireguardEntryRegistrationCredentialSent {
gateway_id,
authenticator_address: Box::new(authenticator_address),
source: Box::new(source),
}
} else {
Self::WireguardExitRegistrationCredentialSent {
gateway_id,
authenticator_address: Box::new(authenticator_address),
source: Box::new(source),
}
}
}
}
}
}
+102 -49
View File
@@ -9,6 +9,7 @@ use nym_credentials_interface::TicketType;
use nym_ip_packet_client::IprClientConnect;
use nym_registration_common::AssignedAddresses;
use nym_sdk::mixnet::{EventReceiver, MixnetClient, Recipient};
use tracing::debug;
use crate::config::RegistrationClientConfig;
@@ -34,23 +35,49 @@ pub struct RegistrationClient {
event_rx: EventReceiver,
}
// Bundle of an actual error and the underlying mixnet client so it can be shutdown correctly if needed
struct RegistrationError {
mixnet_client: Option<MixnetClient>,
source: crate::RegistrationClientError,
}
impl RegistrationClient {
async fn register_mix_exit(self) -> Result<RegistrationResult, RegistrationClientError> {
async fn register_mix_exit(self) -> Result<RegistrationResult, RegistrationError> {
let entry_mixnet_gateway_ip = self.config.entry.node.ip_address;
let exit_mixnet_gateway_ip = self.config.exit.node.ip_address;
let ipr_address = self.config.exit.node.ipr_address.ok_or(
RegistrationClientError::NoIpPacketRouterAddress {
node_id: self.config.exit.node.identity.to_base58_string(),
},
)?;
let Some(ipr_address) = self.config.exit.node.ipr_address else {
return Err(RegistrationError {
mixnet_client: Some(self.mixnet_client),
source: RegistrationClientError::NoIpPacketRouterAddress {
node_id: self.config.exit.node.identity.to_base58_string(),
},
});
};
let mut ipr_client =
IprClientConnect::new(self.mixnet_client, self.cancel_token.clone()).await;
let interface_addresses = ipr_client
.connect(ipr_address)
IprClientConnect::new(self.mixnet_client, self.cancel_token.child_token());
let interface_addresses = match self
.cancel_token
.run_until_cancelled(ipr_client.connect(ipr_address))
.await
.map_err(RegistrationClientError::ConnectToIpPacketRouter)?;
{
Some(Ok(addr)) => addr,
Some(Err(e)) => {
return Err(RegistrationError {
mixnet_client: Some(ipr_client.into_mixnet_client()),
source: RegistrationClientError::ConnectToIpPacketRouter(e),
});
}
None => {
return Err(RegistrationError {
mixnet_client: Some(ipr_client.into_mixnet_client()),
source: RegistrationClientError::Cancelled,
});
}
};
Ok(RegistrationResult::Mixnet(Box::new(
MixnetRegistrationResult {
@@ -67,18 +94,24 @@ impl RegistrationClient {
)))
}
async fn register_wg(self) -> Result<RegistrationResult, RegistrationClientError> {
let entry_auth_address = self.config.entry.node.authenticator_address.ok_or(
RegistrationClientError::AuthenticationNotPossible {
node_id: self.config.entry.node.identity.to_base58_string(),
},
)?;
async fn register_wg(self) -> Result<RegistrationResult, RegistrationError> {
let Some(entry_auth_address) = self.config.entry.node.authenticator_address else {
return Err(RegistrationError {
mixnet_client: Some(self.mixnet_client),
source: RegistrationClientError::AuthenticationNotPossible {
node_id: self.config.entry.node.identity.to_base58_string(),
},
});
};
let exit_auth_address = self.config.exit.node.authenticator_address.ok_or(
RegistrationClientError::AuthenticationNotPossible {
node_id: self.config.exit.node.identity.to_base58_string(),
},
)?;
let Some(exit_auth_address) = self.config.exit.node.authenticator_address else {
return Err(RegistrationError {
mixnet_client: Some(self.mixnet_client),
source: RegistrationClientError::AuthenticationNotPossible {
node_id: self.config.exit.node.identity.to_base58_string(),
},
});
};
let entry_version = self.config.entry.node.version;
tracing::debug!("Entry gateway version: {entry_version}");
@@ -87,8 +120,10 @@ impl RegistrationClient {
// Start the auth client mixnet listener, which will listen for incoming messages from the
// mixnet and rebroadcast them to the auth clients.
// From this point on, we don't need to care about the mixnet client anymore
let mixnet_listener =
AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.clone()).start();
AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.child_token())
.start();
let mut entry_auth_client = AuthenticatorClient::new(
mixnet_listener.subscribe(),
@@ -115,24 +150,35 @@ impl RegistrationClient {
let exit_fut = exit_auth_client
.register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardExit);
let (entry, exit) = Box::pin(async { tokio::join!(entry_fut, exit_fut) }).await;
let (entry, exit) = Box::pin(
self.cancel_token
.run_until_cancelled(async { tokio::join!(entry_fut, exit_fut) }),
)
.await
.ok_or(RegistrationError {
mixnet_client: None,
source: RegistrationClientError::Cancelled,
})?;
let entry =
entry.map_err(
|source| RegistrationClientError::EntryGatewayRegisterWireguard {
gateway_id: self.config.entry.node.identity.to_base58_string(),
authenticator_address: Box::new(entry_auth_address),
source: Box::new(source),
},
)?;
let exit =
exit.map_err(
|source| RegistrationClientError::ExitGatewayRegisterWireguard {
gateway_id: self.config.exit.node.identity.to_base58_string(),
authenticator_address: Box::new(exit_auth_address),
source: Box::new(source),
},
)?;
let entry = entry.map_err(|source| RegistrationError {
mixnet_client: None,
source: RegistrationClientError::from_authenticator_error(
source,
self.config.entry.node.identity.to_base58_string(),
entry_auth_address,
true,
),
})?;
let exit = exit.map_err(|source| RegistrationError {
mixnet_client: None,
source: RegistrationClientError::from_authenticator_error(
source,
self.config.exit.node.identity.to_base58_string(),
exit_auth_address,
false,
),
})?;
Ok(RegistrationResult::Wireguard(Box::new(
WireguardRegistrationResult {
@@ -147,16 +193,23 @@ impl RegistrationClient {
}
pub async fn register(self) -> Result<RegistrationResult, RegistrationClientError> {
self.cancel_token
.clone()
.run_until_cancelled(async {
if self.config.two_hops {
self.register_wg().await
} else {
self.register_mix_exit().await
let registration_result = if self.config.two_hops {
self.register_wg().await
} else {
self.register_mix_exit().await
};
// If we failed to register, and we were the owner of the mixnet client, shut it down
match registration_result {
Ok(result) => Ok(result),
Err(error) => {
debug!("Registration failed");
if let Some(mixnet_client) = error.mixnet_client {
debug!("Shutting down mixnet client");
mixnet_client.disconnect().await;
}
})
.await
.ok_or(RegistrationClientError::Cancelled)?
Err(error.source)
}
}
}
}
@@ -4,7 +4,7 @@
[package]
name = "nym-network-requester"
license = "GPL-3.0"
version = "1.1.65"
version = "1.1.67"
authors.workspace = true
edition.workspace = true
rust-version = "1.85"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-cli"
version = "1.1.64"
version = "1.1.66"
authors.workspace = true
edition = "2021"
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nymvisor"
version = "0.1.29"
version = "0.1.31"
authors.workspace = true
repository.workspace = true
homepage.workspace = true