Merge branch 'develop' into feature/explorer-delegation
This commit is contained in:
@@ -14,12 +14,20 @@ inputs:
|
||||
description: 'The tag/release to process. Uses the release id when trigger from a release.'
|
||||
required: false
|
||||
default: ''
|
||||
repo:
|
||||
description: 'The repo to use. Defaults to "nym".'
|
||||
required: false
|
||||
default: 'nym'
|
||||
owner:
|
||||
description: 'The repo owner to use. Defaults to "nymtech".'
|
||||
required: false
|
||||
default: 'nymtech'
|
||||
outputs:
|
||||
hashes:
|
||||
description: 'A string containing JSON with the release asset hashes and signatures'
|
||||
runs:
|
||||
using: 'node16'
|
||||
main: 'index.js'
|
||||
using: 'node20'
|
||||
main: 'dist/index.js'
|
||||
branding:
|
||||
icon: 'hash'
|
||||
color: 'green'
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
export const id = 37;
|
||||
export const ids = [37];
|
||||
export const modules = {
|
||||
|
||||
/***/ 4037:
|
||||
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||||
/* harmony export */ "toFormData": () => (/* binding */ toFormData)
|
||||
/* harmony export */ });
|
||||
/* harmony import */ var fetch_blob_from_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2777);
|
||||
/* harmony import */ var formdata_polyfill_esm_min_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8010);
|
||||
|
||||
|
||||
|
||||
let s = 0;
|
||||
const S = {
|
||||
START_BOUNDARY: s++,
|
||||
HEADER_FIELD_START: s++,
|
||||
HEADER_FIELD: s++,
|
||||
HEADER_VALUE_START: s++,
|
||||
HEADER_VALUE: s++,
|
||||
HEADER_VALUE_ALMOST_DONE: s++,
|
||||
HEADERS_ALMOST_DONE: s++,
|
||||
PART_DATA_START: s++,
|
||||
PART_DATA: s++,
|
||||
END: s++
|
||||
};
|
||||
|
||||
let f = 1;
|
||||
const F = {
|
||||
PART_BOUNDARY: f,
|
||||
LAST_BOUNDARY: f *= 2
|
||||
};
|
||||
|
||||
const LF = 10;
|
||||
const CR = 13;
|
||||
const SPACE = 32;
|
||||
const HYPHEN = 45;
|
||||
const COLON = 58;
|
||||
const A = 97;
|
||||
const Z = 122;
|
||||
|
||||
const lower = c => c | 0x20;
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
class MultipartParser {
|
||||
/**
|
||||
* @param {string} boundary
|
||||
*/
|
||||
constructor(boundary) {
|
||||
this.index = 0;
|
||||
this.flags = 0;
|
||||
|
||||
this.onHeaderEnd = noop;
|
||||
this.onHeaderField = noop;
|
||||
this.onHeadersEnd = noop;
|
||||
this.onHeaderValue = noop;
|
||||
this.onPartBegin = noop;
|
||||
this.onPartData = noop;
|
||||
this.onPartEnd = noop;
|
||||
|
||||
this.boundaryChars = {};
|
||||
|
||||
boundary = '\r\n--' + boundary;
|
||||
const ui8a = new Uint8Array(boundary.length);
|
||||
for (let i = 0; i < boundary.length; i++) {
|
||||
ui8a[i] = boundary.charCodeAt(i);
|
||||
this.boundaryChars[ui8a[i]] = true;
|
||||
}
|
||||
|
||||
this.boundary = ui8a;
|
||||
this.lookbehind = new Uint8Array(this.boundary.length + 8);
|
||||
this.state = S.START_BOUNDARY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
*/
|
||||
write(data) {
|
||||
let i = 0;
|
||||
const length_ = data.length;
|
||||
let previousIndex = this.index;
|
||||
let {lookbehind, boundary, boundaryChars, index, state, flags} = this;
|
||||
const boundaryLength = this.boundary.length;
|
||||
const boundaryEnd = boundaryLength - 1;
|
||||
const bufferLength = data.length;
|
||||
let c;
|
||||
let cl;
|
||||
|
||||
const mark = name => {
|
||||
this[name + 'Mark'] = i;
|
||||
};
|
||||
|
||||
const clear = name => {
|
||||
delete this[name + 'Mark'];
|
||||
};
|
||||
|
||||
const callback = (callbackSymbol, start, end, ui8a) => {
|
||||
if (start === undefined || start !== end) {
|
||||
this[callbackSymbol](ui8a && ui8a.subarray(start, end));
|
||||
}
|
||||
};
|
||||
|
||||
const dataCallback = (name, clear) => {
|
||||
const markSymbol = name + 'Mark';
|
||||
if (!(markSymbol in this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (clear) {
|
||||
callback(name, this[markSymbol], i, data);
|
||||
delete this[markSymbol];
|
||||
} else {
|
||||
callback(name, this[markSymbol], data.length, data);
|
||||
this[markSymbol] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
for (i = 0; i < length_; i++) {
|
||||
c = data[i];
|
||||
|
||||
switch (state) {
|
||||
case S.START_BOUNDARY:
|
||||
if (index === boundary.length - 2) {
|
||||
if (c === HYPHEN) {
|
||||
flags |= F.LAST_BOUNDARY;
|
||||
} else if (c !== CR) {
|
||||
return;
|
||||
}
|
||||
|
||||
index++;
|
||||
break;
|
||||
} else if (index - 1 === boundary.length - 2) {
|
||||
if (flags & F.LAST_BOUNDARY && c === HYPHEN) {
|
||||
state = S.END;
|
||||
flags = 0;
|
||||
} else if (!(flags & F.LAST_BOUNDARY) && c === LF) {
|
||||
index = 0;
|
||||
callback('onPartBegin');
|
||||
state = S.HEADER_FIELD_START;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (c !== boundary[index + 2]) {
|
||||
index = -2;
|
||||
}
|
||||
|
||||
if (c === boundary[index + 2]) {
|
||||
index++;
|
||||
}
|
||||
|
||||
break;
|
||||
case S.HEADER_FIELD_START:
|
||||
state = S.HEADER_FIELD;
|
||||
mark('onHeaderField');
|
||||
index = 0;
|
||||
// falls through
|
||||
case S.HEADER_FIELD:
|
||||
if (c === CR) {
|
||||
clear('onHeaderField');
|
||||
state = S.HEADERS_ALMOST_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
if (c === HYPHEN) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c === COLON) {
|
||||
if (index === 1) {
|
||||
// empty header field
|
||||
return;
|
||||
}
|
||||
|
||||
dataCallback('onHeaderField', true);
|
||||
state = S.HEADER_VALUE_START;
|
||||
break;
|
||||
}
|
||||
|
||||
cl = lower(c);
|
||||
if (cl < A || cl > Z) {
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case S.HEADER_VALUE_START:
|
||||
if (c === SPACE) {
|
||||
break;
|
||||
}
|
||||
|
||||
mark('onHeaderValue');
|
||||
state = S.HEADER_VALUE;
|
||||
// falls through
|
||||
case S.HEADER_VALUE:
|
||||
if (c === CR) {
|
||||
dataCallback('onHeaderValue', true);
|
||||
callback('onHeaderEnd');
|
||||
state = S.HEADER_VALUE_ALMOST_DONE;
|
||||
}
|
||||
|
||||
break;
|
||||
case S.HEADER_VALUE_ALMOST_DONE:
|
||||
if (c !== LF) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = S.HEADER_FIELD_START;
|
||||
break;
|
||||
case S.HEADERS_ALMOST_DONE:
|
||||
if (c !== LF) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback('onHeadersEnd');
|
||||
state = S.PART_DATA_START;
|
||||
break;
|
||||
case S.PART_DATA_START:
|
||||
state = S.PART_DATA;
|
||||
mark('onPartData');
|
||||
// falls through
|
||||
case S.PART_DATA:
|
||||
previousIndex = index;
|
||||
|
||||
if (index === 0) {
|
||||
// boyer-moore derrived algorithm to safely skip non-boundary data
|
||||
i += boundaryEnd;
|
||||
while (i < bufferLength && !(data[i] in boundaryChars)) {
|
||||
i += boundaryLength;
|
||||
}
|
||||
|
||||
i -= boundaryEnd;
|
||||
c = data[i];
|
||||
}
|
||||
|
||||
if (index < boundary.length) {
|
||||
if (boundary[index] === c) {
|
||||
if (index === 0) {
|
||||
dataCallback('onPartData', true);
|
||||
}
|
||||
|
||||
index++;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
} else if (index === boundary.length) {
|
||||
index++;
|
||||
if (c === CR) {
|
||||
// CR = part boundary
|
||||
flags |= F.PART_BOUNDARY;
|
||||
} else if (c === HYPHEN) {
|
||||
// HYPHEN = end boundary
|
||||
flags |= F.LAST_BOUNDARY;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
} else if (index - 1 === boundary.length) {
|
||||
if (flags & F.PART_BOUNDARY) {
|
||||
index = 0;
|
||||
if (c === LF) {
|
||||
// unset the PART_BOUNDARY flag
|
||||
flags &= ~F.PART_BOUNDARY;
|
||||
callback('onPartEnd');
|
||||
callback('onPartBegin');
|
||||
state = S.HEADER_FIELD_START;
|
||||
break;
|
||||
}
|
||||
} else if (flags & F.LAST_BOUNDARY) {
|
||||
if (c === HYPHEN) {
|
||||
callback('onPartEnd');
|
||||
state = S.END;
|
||||
flags = 0;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
// when matching a possible boundary, keep a lookbehind reference
|
||||
// in case it turns out to be a false lead
|
||||
lookbehind[index - 1] = c;
|
||||
} else if (previousIndex > 0) {
|
||||
// if our boundary turned out to be rubbish, the captured lookbehind
|
||||
// belongs to partData
|
||||
const _lookbehind = new Uint8Array(lookbehind.buffer, lookbehind.byteOffset, lookbehind.byteLength);
|
||||
callback('onPartData', 0, previousIndex, _lookbehind);
|
||||
previousIndex = 0;
|
||||
mark('onPartData');
|
||||
|
||||
// reconsider the current character even so it interrupted the sequence
|
||||
// it could be the beginning of a new sequence
|
||||
i--;
|
||||
}
|
||||
|
||||
break;
|
||||
case S.END:
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unexpected state entered: ${state}`);
|
||||
}
|
||||
}
|
||||
|
||||
dataCallback('onHeaderField');
|
||||
dataCallback('onHeaderValue');
|
||||
dataCallback('onPartData');
|
||||
|
||||
// Update properties for the next call
|
||||
this.index = index;
|
||||
this.state = state;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
end() {
|
||||
if ((this.state === S.HEADER_FIELD_START && this.index === 0) ||
|
||||
(this.state === S.PART_DATA && this.index === this.boundary.length)) {
|
||||
this.onPartEnd();
|
||||
} else if (this.state !== S.END) {
|
||||
throw new Error('MultipartParser.end(): stream ended unexpectedly');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _fileName(headerValue) {
|
||||
// matches either a quoted-string or a token (RFC 2616 section 19.5.1)
|
||||
const m = headerValue.match(/\bfilename=("(.*?)"|([^()<>@,;:\\"/[\]?={}\s\t]+))($|;\s)/i);
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
|
||||
const match = m[2] || m[3] || '';
|
||||
let filename = match.slice(match.lastIndexOf('\\') + 1);
|
||||
filename = filename.replace(/%22/g, '"');
|
||||
filename = filename.replace(/&#(\d{4});/g, (m, code) => {
|
||||
return String.fromCharCode(code);
|
||||
});
|
||||
return filename;
|
||||
}
|
||||
|
||||
async function toFormData(Body, ct) {
|
||||
if (!/multipart/i.test(ct)) {
|
||||
throw new TypeError('Failed to fetch');
|
||||
}
|
||||
|
||||
const m = ct.match(/boundary=(?:"([^"]+)"|([^;]+))/i);
|
||||
|
||||
if (!m) {
|
||||
throw new TypeError('no or bad content-type header, no multipart boundary');
|
||||
}
|
||||
|
||||
const parser = new MultipartParser(m[1] || m[2]);
|
||||
|
||||
let headerField;
|
||||
let headerValue;
|
||||
let entryValue;
|
||||
let entryName;
|
||||
let contentType;
|
||||
let filename;
|
||||
const entryChunks = [];
|
||||
const formData = new formdata_polyfill_esm_min_js__WEBPACK_IMPORTED_MODULE_1__/* .FormData */ .Ct();
|
||||
|
||||
const onPartData = ui8a => {
|
||||
entryValue += decoder.decode(ui8a, {stream: true});
|
||||
};
|
||||
|
||||
const appendToFile = ui8a => {
|
||||
entryChunks.push(ui8a);
|
||||
};
|
||||
|
||||
const appendFileToFormData = () => {
|
||||
const file = new fetch_blob_from_js__WEBPACK_IMPORTED_MODULE_0__/* .File */ .$B(entryChunks, filename, {type: contentType});
|
||||
formData.append(entryName, file);
|
||||
};
|
||||
|
||||
const appendEntryToFormData = () => {
|
||||
formData.append(entryName, entryValue);
|
||||
};
|
||||
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
decoder.decode();
|
||||
|
||||
parser.onPartBegin = function () {
|
||||
parser.onPartData = onPartData;
|
||||
parser.onPartEnd = appendEntryToFormData;
|
||||
|
||||
headerField = '';
|
||||
headerValue = '';
|
||||
entryValue = '';
|
||||
entryName = '';
|
||||
contentType = '';
|
||||
filename = null;
|
||||
entryChunks.length = 0;
|
||||
};
|
||||
|
||||
parser.onHeaderField = function (ui8a) {
|
||||
headerField += decoder.decode(ui8a, {stream: true});
|
||||
};
|
||||
|
||||
parser.onHeaderValue = function (ui8a) {
|
||||
headerValue += decoder.decode(ui8a, {stream: true});
|
||||
};
|
||||
|
||||
parser.onHeaderEnd = function () {
|
||||
headerValue += decoder.decode();
|
||||
headerField = headerField.toLowerCase();
|
||||
|
||||
if (headerField === 'content-disposition') {
|
||||
// matches either a quoted-string or a token (RFC 2616 section 19.5.1)
|
||||
const m = headerValue.match(/\bname=("([^"]*)"|([^()<>@,;:\\"/[\]?={}\s\t]+))/i);
|
||||
|
||||
if (m) {
|
||||
entryName = m[2] || m[3] || '';
|
||||
}
|
||||
|
||||
filename = _fileName(headerValue);
|
||||
|
||||
if (filename) {
|
||||
parser.onPartData = appendToFile;
|
||||
parser.onPartEnd = appendFileToFormData;
|
||||
}
|
||||
} else if (headerField === 'content-type') {
|
||||
contentType = headerValue;
|
||||
}
|
||||
|
||||
headerValue = '';
|
||||
headerField = '';
|
||||
};
|
||||
|
||||
for await (const chunk of Body) {
|
||||
parser.write(chunk);
|
||||
}
|
||||
|
||||
parser.end();
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
|
||||
/***/ })
|
||||
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const {parentPort} = require('worker_threads');
|
||||
|
||||
const handlers = {
|
||||
hashFile: (algorithm, filePath) => new Promise((resolve, reject) => {
|
||||
const hasher = crypto.createHash(algorithm);
|
||||
fs.createReadStream(filePath)
|
||||
// TODO: Use `Stream.pipeline` when targeting Node.js 12.
|
||||
.on('error', reject)
|
||||
.pipe(hasher)
|
||||
.on('error', reject)
|
||||
.on('finish', () => {
|
||||
const {buffer} = new Uint8Array(hasher.read());
|
||||
resolve({value: buffer, transferList: [buffer]});
|
||||
});
|
||||
}),
|
||||
hash: async (algorithm, input) => {
|
||||
const hasher = crypto.createHash(algorithm);
|
||||
|
||||
if (Array.isArray(input)) {
|
||||
for (const part of input) {
|
||||
hasher.update(part);
|
||||
}
|
||||
} else {
|
||||
hasher.update(input);
|
||||
}
|
||||
|
||||
const {buffer} = new Uint8Array(hasher.digest());
|
||||
return {value: buffer, transferList: [buffer]};
|
||||
}
|
||||
};
|
||||
|
||||
parentPort.on('message', async message => {
|
||||
try {
|
||||
const {method, args} = message;
|
||||
const handler = handlers[method];
|
||||
|
||||
if (handler === undefined) {
|
||||
throw new Error(`Unknown method '${method}'`);
|
||||
}
|
||||
|
||||
const {value, transferList} = await handler(...args);
|
||||
parentPort.postMessage({id: message.id, value}, transferList);
|
||||
} catch (error) {
|
||||
const newError = {message: error.message, stack: error.stack};
|
||||
|
||||
for (const [key, value] of Object.entries(error)) {
|
||||
if (typeof value !== 'object') {
|
||||
newError[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
parentPort.postMessage({id: message.id, error: newError});
|
||||
}
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import core from "@actions/core";
|
||||
import github from "@actions/github";
|
||||
import { createHashesFromReleaseTagOrNameOrId } from './create-hashes.mjs';
|
||||
|
||||
const algorithm = core.getInput('hash-type');
|
||||
const filename = core.getInput("file-name");
|
||||
|
||||
// use the release id from the payload if it is set
|
||||
const releaseTagOrNameOrId = core.getInput("release-tag-or-name-or-id") || github.context.payload.release?.id;
|
||||
|
||||
try {
|
||||
await createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrId, algorithm, filename })
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
@@ -2,17 +2,6 @@
|
||||
"name": "nym-hash-release",
|
||||
"version": "1.0.0",
|
||||
"description": "Generate hashes and signatures for assets in Nym releases",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"local": "node run-local.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/github": "^5.1.1",
|
||||
"@octokit/auth-action": "^4.0.0",
|
||||
"@octokit/rest": "^20.0.1",
|
||||
"hasha": "^5.2.0",
|
||||
"node-fetch": "^3.2.10"
|
||||
}
|
||||
"main": "dist/index.js",
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# nym-hash-release
|
||||
|
||||
This is the source code for the custom GitHub Action to calculate hashes.
|
||||
|
||||
It is in a subdirectory to avoid issues with `package.json`.
|
||||
|
||||
## Build
|
||||
|
||||
The following will bundle all code and dependencies into the `dist` folder, and copy it into place for GitHub Actions.
|
||||
|
||||
```
|
||||
npm run build
|
||||
npm run dist:copy
|
||||
```
|
||||
+8
-4
@@ -142,7 +142,7 @@ export async function createHashes({ assets, algorithm, filename, cache }) {
|
||||
return output;
|
||||
}
|
||||
|
||||
export async function createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrId, algorithm = 'sha256', filename = 'hashes.json', cache = false, upload = true }) {
|
||||
export async function createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrId, algorithm = 'sha256', filename = 'hashes.json', cache = false, upload = true, owner = 'nymtech', repo = 'nym' }) {
|
||||
console.log("🚀🚀🚀 Getting releases");
|
||||
|
||||
let auth;
|
||||
@@ -157,8 +157,6 @@ export async function createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrI
|
||||
auth: process.env.GITHUB_TOKEN,
|
||||
request: { fetch }
|
||||
});
|
||||
const owner = "nymtech";
|
||||
const repo = "nym";
|
||||
|
||||
let releases;
|
||||
if(cache) {
|
||||
@@ -212,7 +210,13 @@ export async function createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrI
|
||||
|
||||
releasesToProcess.forEach(release => {
|
||||
const {tag_name, name} = release;
|
||||
const tagComponents = tag_name.split('-v');
|
||||
const matches = tag_name.match(/(\S+)-v([0-9]+\.[0-9]+\.\S+)/);
|
||||
|
||||
if(!matches || matches.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tagComponents = matches.slice(1);
|
||||
const componentName = tagComponents[0];
|
||||
const componentVersion = 'v' + tagComponents[1];
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import core from "@actions/core";
|
||||
import github from "@actions/github";
|
||||
import { createHashesFromReleaseTagOrNameOrId } from './create-hashes.mjs';
|
||||
|
||||
const algorithm = core.getInput('hash-type');
|
||||
const filename = core.getInput("file-name");
|
||||
const owner = core.getInput("owner");
|
||||
const repo = core.getInput("repo");
|
||||
|
||||
async function main() {
|
||||
// use the release id from the payload if it is set
|
||||
const releaseTagOrNameOrId = core.getInput("release-tag-or-name-or-id") || github.context.payload.release?.id;
|
||||
|
||||
try {
|
||||
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId, algorithm, filename, owner, repo})
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => core.setFailed(error.message));
|
||||
+72
-35
@@ -1,26 +1,28 @@
|
||||
{
|
||||
"name": "ghaction-generate-release-hashes",
|
||||
"name": "nym-hash-release",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ghaction-generate-release-hashes",
|
||||
"name": "nym-hash-release",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^5.1.1",
|
||||
"@octokit/auth-action": "^4.0.0",
|
||||
"@octokit/rest": "^20.0.1",
|
||||
"@octokit/auth-action": "^4.0.1",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"hasha": "^5.2.0",
|
||||
"node-fetch": "^3.2.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vercel/ncc": "^0.38.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz",
|
||||
"integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
@@ -46,12 +48,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-action": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-action/-/auth-action-4.0.0.tgz",
|
||||
"integrity": "sha512-sMm9lWZdiX6e89YFaLrgE9EFs94k58BwIkvjOtozNWUqyTmsrnWFr/M5LolaRzZ7Kmb5FbhF9hi7FEeE274SoQ==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-action/-/auth-action-4.0.1.tgz",
|
||||
"integrity": "sha512-mJLOcFFafIivLZ7BEkGDCTFoHPJv7BeL5Zwy7j5qMDU0b/DKshhi6GCU9tw3vmKhOxTNquYfvwqsEfPpemaaxg==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/types": "^11.0.0"
|
||||
"@octokit/types": "^12.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
@@ -66,16 +68,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-action/node_modules/@octokit/openapi-types": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz",
|
||||
"integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw=="
|
||||
"version": "20.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
|
||||
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="
|
||||
},
|
||||
"node_modules/@octokit/auth-action/node_modules/@octokit/types": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz",
|
||||
"integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==",
|
||||
"version": "12.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
|
||||
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^18.0.0"
|
||||
"@octokit/openapi-types": "^20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
@@ -191,14 +193,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest": {
|
||||
"version": "20.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.1.tgz",
|
||||
"integrity": "sha512-wROV21RwHQIMNb2Dgd4+pY+dVy1Dwmp85pBrgr6YRRDYRBu9Gb+D73f4Bl2EukZSj5hInq2Tui9o7gAQpc2k2Q==",
|
||||
"version": "20.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.2.tgz",
|
||||
"integrity": "sha512-Ux8NDgEraQ/DMAU1PlAohyfBBXDwhnX2j33Z1nJNziqAfHi70PuxkFYIcIt8aIAxtRE7KVuKp8lSR8pA0J5iOQ==",
|
||||
"dependencies": {
|
||||
"@octokit/core": "^5.0.0",
|
||||
"@octokit/plugin-paginate-rest": "^8.0.0",
|
||||
"@octokit/plugin-paginate-rest": "^9.0.0",
|
||||
"@octokit/plugin-request-log": "^4.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^9.0.0"
|
||||
"@octokit/plugin-rest-endpoint-methods": "^10.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
@@ -261,17 +263,30 @@
|
||||
"integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw=="
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-8.0.0.tgz",
|
||||
"integrity": "sha512-2xZ+baZWUg+qudVXnnvXz7qfrTmDeYPCzangBVq/1gXxii/OiS//4shJp9dnCCvj1x+JAm9ji1Egwm1BA47lPQ==",
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz",
|
||||
"integrity": "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^11.0.0"
|
||||
"@octokit/types": "^12.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=5"
|
||||
"@octokit/core": "5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": {
|
||||
"version": "20.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
|
||||
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": {
|
||||
"version": "12.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
|
||||
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": {
|
||||
@@ -286,17 +301,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-9.0.0.tgz",
|
||||
"integrity": "sha512-KquMF/VB1IkKNiVnzJKspY5mFgGyLd7HzdJfVEGTJFzqu9BRFNWt+nwTCMuUiWc72gLQhRWYubTwOkQj+w/1PA==",
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz",
|
||||
"integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^11.0.0"
|
||||
"@octokit/types": "^12.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=5"
|
||||
"@octokit/core": "5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": {
|
||||
"version": "20.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
|
||||
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": {
|
||||
"version": "12.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
|
||||
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/request": {
|
||||
@@ -343,6 +371,15 @@
|
||||
"@octokit/openapi-types": "^12.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/ncc": {
|
||||
"version": "0.38.1",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.1.tgz",
|
||||
"integrity": "sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"ncc": "dist/ncc/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/before-after-hook": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "nym-hash-release",
|
||||
"version": "1.0.0",
|
||||
"description": "Generate hashes and signatures for assets in Nym releases",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"local": "node run-local.mjs",
|
||||
"build": "ncc build index.js -o dist",
|
||||
"dist:copy": "mkdir -p ../dist && cp dist/*.js ../dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^5.1.1",
|
||||
"@octokit/auth-action": "^4.0.1",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"hasha": "^5.2.0",
|
||||
"node-fetch": "^3.2.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vercel/ncc": "^0.38.1"
|
||||
}
|
||||
}
|
||||
Generated
+32
-10
@@ -4280,7 +4280,7 @@ dependencies = [
|
||||
"rw-stream-sink",
|
||||
"soketto",
|
||||
"url",
|
||||
"webpki-roots 0.22.6",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5128,6 +5128,7 @@ dependencies = [
|
||||
"nym-credentials",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-id",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"nym-name-service-common",
|
||||
@@ -5168,6 +5169,7 @@ dependencies = [
|
||||
"nym-credentials",
|
||||
"nym-crypto",
|
||||
"nym-gateway-requests",
|
||||
"nym-id",
|
||||
"nym-network-defaults",
|
||||
"nym-pemstore",
|
||||
"nym-sphinx",
|
||||
@@ -5626,6 +5628,32 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-id"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-credential-storage",
|
||||
"nym-credentials",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-id-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58 0.5.0",
|
||||
"clap 4.4.7",
|
||||
"nym-bin-common",
|
||||
"nym-credential-storage",
|
||||
"nym-id",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-inclusion-probability"
|
||||
version = "0.1.0"
|
||||
@@ -5864,6 +5892,7 @@ dependencies = [
|
||||
"nym-credentials",
|
||||
"nym-crypto",
|
||||
"nym-exit-policy",
|
||||
"nym-id",
|
||||
"nym-network-defaults",
|
||||
"nym-ordered-buffer",
|
||||
"nym-sdk",
|
||||
@@ -6151,6 +6180,7 @@ dependencies = [
|
||||
"nym-credentials",
|
||||
"nym-crypto",
|
||||
"nym-gateway-requests",
|
||||
"nym-id",
|
||||
"nym-network-defaults",
|
||||
"nym-ordered-buffer",
|
||||
"nym-pemstore",
|
||||
@@ -8014,7 +8044,6 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots 0.25.2",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
@@ -9177,7 +9206,7 @@ dependencies = [
|
||||
"time",
|
||||
"tokio-stream",
|
||||
"url",
|
||||
"webpki-roots 0.22.6",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9824,7 +9853,6 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"rustls 0.21.10",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
]
|
||||
@@ -10842,12 +10870,6 @@ dependencies = [
|
||||
"webpki 0.22.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
|
||||
|
||||
[[package]]
|
||||
name = "webrtc"
|
||||
version = "0.6.0"
|
||||
|
||||
+5
-3
@@ -56,6 +56,7 @@ members = [
|
||||
"common/node-tester-utils",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
"common/nymcoconut",
|
||||
"common/nym-id",
|
||||
"common/nymsphinx",
|
||||
"common/nymsphinx/acknowledgements",
|
||||
"common/nymsphinx/addressing",
|
||||
@@ -106,6 +107,7 @@ members = [
|
||||
"tools/internal/ssl-inject",
|
||||
# "tools/internal/sdk-version-bump",
|
||||
"tools/nym-cli",
|
||||
"tools/nym-id-cli",
|
||||
"tools/nym-nr-query",
|
||||
"tools/nymvisor",
|
||||
"tools/ts-rs-cli",
|
||||
@@ -159,7 +161,7 @@ log = "0.4"
|
||||
once_cell = "1.7.2"
|
||||
parking_lot = "0.12.1"
|
||||
rand = "0.8.5"
|
||||
reqwest = { version = "0.11.22", default_features = false, features = ["rustls-tls"] }
|
||||
reqwest = { version = "0.11.22", default_features = false }
|
||||
schemars = "0.8.1"
|
||||
serde = "1.0.152"
|
||||
serde_json = "1.0.91"
|
||||
@@ -169,9 +171,9 @@ time = "0.3.30"
|
||||
thiserror = "1.0.48"
|
||||
tokio = "1.33.0"
|
||||
tokio-util = "0.7.10"
|
||||
tokio-tungstenite = { version = "0.20.1", features = ["rustls"] }
|
||||
tokio-tungstenite = { version = "0.20.1" }
|
||||
tracing = "0.1.37"
|
||||
tungstenite = { version = "0.20.1", default-features = false, features = ["rustls"] }
|
||||
tungstenite = { version = "0.20.1", default-features = false }
|
||||
ts-rs = "7.0.0"
|
||||
utoipa = "3.5.0"
|
||||
utoipa-swagger-ui = "3.1.5"
|
||||
|
||||
@@ -51,5 +51,6 @@ nym-task = { path = "../../common/task" }
|
||||
nym-topology = { path = "../../common/topology" }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client", features = ["http-client"] }
|
||||
nym-client-websocket-requests = { path = "websocket-requests" }
|
||||
nym-id = { path = "../../common/nym-id" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -2160,9 +2160,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
@@ -6157,9 +6157,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"ip": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ipaddr.js": {
|
||||
|
||||
@@ -4,14 +4,10 @@
|
||||
use crate::commands::try_load_current_config;
|
||||
use crate::error::ClientError;
|
||||
use clap::ArgGroup;
|
||||
use log::{error, info};
|
||||
use nym_credential_storage::models::StorableIssuedCredential;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||
use nym_credentials::IssuedBandwidthCredential;
|
||||
|
||||
use nym_id::import_credential;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
|
||||
bs58::decode(raw).into_vec()
|
||||
@@ -33,8 +29,8 @@ pub(crate) struct Args {
|
||||
pub(crate) credential_path: Option<PathBuf>,
|
||||
|
||||
// currently hidden as there exists only a single serialization standard
|
||||
#[clap(long, hide = true, default_value_t = 1)]
|
||||
pub(crate) version: u8,
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) version: Option<u8>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), ClientError> {
|
||||
@@ -52,50 +48,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), ClientError> {
|
||||
fs::read(args.credential_path.unwrap())?
|
||||
}
|
||||
};
|
||||
let raw_credential = Zeroizing::new(raw_credential);
|
||||
|
||||
// we're unpacking the data in order to make sure it's valid
|
||||
// and to extract relevant metadata for storage purposes
|
||||
let credential = match args.version {
|
||||
1 => Zeroizing::new(
|
||||
IssuedBandwidthCredential::unpack_v1(&raw_credential).map_err(|source| {
|
||||
ClientError::CredentialDeserializationFailure {
|
||||
storage_revision: 1,
|
||||
source,
|
||||
}
|
||||
})?,
|
||||
),
|
||||
other => panic!("unknown credential serialization version {other}"),
|
||||
};
|
||||
|
||||
info!("importing {}", credential.typ());
|
||||
match credential.variant_data() {
|
||||
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
|
||||
info!("with value of {}", voucher_info.value())
|
||||
}
|
||||
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
|
||||
info!("with expiry at {}", freepass_info.expiry_date());
|
||||
if freepass_info.expired() {
|
||||
error!("the free pass has already expired!");
|
||||
|
||||
// technically we can import it, but the gateway will just reject it so what's the point
|
||||
return Err(ClientError::ExpiredCredentialImport {
|
||||
expiration: freepass_info.expiry_date(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let storable = StorableIssuedCredential {
|
||||
serialization_revision: args.version,
|
||||
credential_data: &raw_credential,
|
||||
credential_type: credential.typ().to_string(),
|
||||
epoch_id: credential
|
||||
.epoch_id()
|
||||
.try_into()
|
||||
.expect("our epoch is has run over u32::MAX!"),
|
||||
};
|
||||
|
||||
credentials_store.insert_issued_credential(storable).await?;
|
||||
import_credential(credentials_store, raw_credential, args.version).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_credential_storage::error::StorageError;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use nym_id::NymIdError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientError {
|
||||
@@ -23,21 +23,6 @@ pub enum ClientError {
|
||||
#[error("Attempted to start the client in invalid socket mode")]
|
||||
InvalidSocketMode,
|
||||
|
||||
#[error("failed to store credential: {source}")]
|
||||
CredentialStorageFailure {
|
||||
#[from]
|
||||
source: StorageError,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"failed to deserialize provided credential using revision {storage_revision}: {source}"
|
||||
)]
|
||||
CredentialDeserializationFailure {
|
||||
storage_revision: u8,
|
||||
#[source]
|
||||
source: nym_credentials::error::Error,
|
||||
},
|
||||
|
||||
#[error("attempted to import an expired credential (it expired on {expiration})")]
|
||||
ExpiredCredentialImport { expiration: OffsetDateTime },
|
||||
#[error(transparent)]
|
||||
NymIdError(#[from] NymIdError),
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
|
||||
nym-pemstore = { path = "../../common/pemstore" }
|
||||
nym-topology = { path = "../../common/topology" }
|
||||
nym-socks5-client-core = { path = "../../common/socks5-client-core" }
|
||||
nym-id = { path = "../../common/nym-id" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -4,14 +4,10 @@
|
||||
use crate::commands::try_load_current_config;
|
||||
use crate::error::Socks5ClientError;
|
||||
use clap::ArgGroup;
|
||||
use log::{error, info};
|
||||
use nym_credential_storage::models::StorableIssuedCredential;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||
use nym_credentials::IssuedBandwidthCredential;
|
||||
|
||||
use nym_id::import_credential;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
|
||||
bs58::decode(raw).into_vec()
|
||||
@@ -33,8 +29,8 @@ pub(crate) struct Args {
|
||||
pub(crate) credential_path: Option<PathBuf>,
|
||||
|
||||
// currently hidden as there exists only a single serialization standard
|
||||
#[clap(long, hide = true, default_value_t = 1)]
|
||||
pub(crate) version: u8,
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) version: Option<u8>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> {
|
||||
@@ -52,50 +48,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> {
|
||||
fs::read(args.credential_path.unwrap())?
|
||||
}
|
||||
};
|
||||
let raw_credential = Zeroizing::new(raw_credential);
|
||||
|
||||
// we're unpacking the data in order to make sure it's valid
|
||||
// and to extract relevant metadata for storage purposes
|
||||
let credential = match args.version {
|
||||
1 => Zeroizing::new(
|
||||
IssuedBandwidthCredential::unpack_v1(&raw_credential).map_err(|source| {
|
||||
Socks5ClientError::CredentialDeserializationFailure {
|
||||
storage_revision: 1,
|
||||
source,
|
||||
}
|
||||
})?,
|
||||
),
|
||||
other => panic!("unknown credential serialization version {other}"),
|
||||
};
|
||||
|
||||
info!("importing {}", credential.typ());
|
||||
match credential.variant_data() {
|
||||
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
|
||||
info!("with value of {}", voucher_info.value())
|
||||
}
|
||||
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
|
||||
info!("with expiry at {}", freepass_info.expiry_date());
|
||||
if freepass_info.expired() {
|
||||
error!("the free pass has already expired!");
|
||||
|
||||
// technically we can import it, but the gateway will just reject it so what's the point
|
||||
return Err(Socks5ClientError::ExpiredCredentialImport {
|
||||
expiration: freepass_info.expiry_date(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let storable = StorableIssuedCredential {
|
||||
serialization_revision: args.version,
|
||||
credential_data: &raw_credential,
|
||||
credential_type: credential.typ().to_string(),
|
||||
epoch_id: credential
|
||||
.epoch_id()
|
||||
.try_into()
|
||||
.expect("our epoch is has run over u32::MAX!"),
|
||||
};
|
||||
|
||||
credentials_store.insert_issued_credential(storable).await?;
|
||||
import_credential(credentials_store, raw_credential, args.version).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_credential_storage::error::StorageError;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use nym_id::NymIdError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Socks5ClientError {
|
||||
@@ -23,21 +23,6 @@ pub enum Socks5ClientError {
|
||||
#[error(transparent)]
|
||||
ClientCoreError(#[from] ClientCoreError),
|
||||
|
||||
#[error("failed to store credential: {source}")]
|
||||
CredentialStorageFailure {
|
||||
#[from]
|
||||
source: StorageError,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"failed to deserialize provided credential using revision {storage_revision}: {source}"
|
||||
)]
|
||||
CredentialDeserializationFailure {
|
||||
storage_revision: u8,
|
||||
#[source]
|
||||
source: nym_credentials::error::Error,
|
||||
},
|
||||
|
||||
#[error("attempted to import an expired credential (it expired on {expiration})")]
|
||||
ExpiredCredentialImport { expiration: OffsetDateTime },
|
||||
#[error(transparent)]
|
||||
NymIdError(#[from] NymIdError),
|
||||
}
|
||||
|
||||
@@ -69,6 +69,35 @@ impl BinaryBuildInformation {
|
||||
}
|
||||
}
|
||||
|
||||
// Varient where we want to use the metadata generated by vergen in the consuming crate.
|
||||
pub const fn new_with_local_vergen(
|
||||
binary_name: &'static str,
|
||||
build_timestamp: &'static str,
|
||||
build_version: &'static str,
|
||||
commit_sha: &'static str,
|
||||
commit_timestamp: &'static str,
|
||||
commit_branch: &'static str,
|
||||
) -> Self {
|
||||
let cargo_debug = env!("VERGEN_CARGO_DEBUG");
|
||||
let cargo_profile = if const_str::equal!(cargo_debug, "true") {
|
||||
"debug"
|
||||
} else {
|
||||
"release"
|
||||
};
|
||||
|
||||
BinaryBuildInformation {
|
||||
binary_name,
|
||||
build_timestamp,
|
||||
build_version,
|
||||
commit_sha,
|
||||
commit_timestamp,
|
||||
commit_branch,
|
||||
rustc_version: env!("VERGEN_RUSTC_SEMVER"),
|
||||
rustc_channel: env!("VERGEN_RUSTC_CHANNEL"),
|
||||
cargo_profile,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_owned(&self) -> BinaryBuildInformationOwned {
|
||||
BinaryBuildInformationOwned {
|
||||
binary_name: self.binary_name.to_owned(),
|
||||
@@ -187,3 +216,33 @@ macro_rules! bin_info_owned {
|
||||
.to_owned()
|
||||
};
|
||||
}
|
||||
|
||||
// variant that picks up the vergen build information generated by the build.rs in the consumer
|
||||
// crate.
|
||||
#[macro_export]
|
||||
macro_rules! bin_info_local_vergen {
|
||||
() => {
|
||||
$crate::build_information::BinaryBuildInformation::new_with_local_vergen(
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bin_info_local_vergen_owned {
|
||||
() => {
|
||||
$crate::build_information::BinaryBuildInformation::new_with_local_vergen(
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,14 +33,14 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tungstenite::protocol::Message;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::RawFd;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::sleep;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::connect_async;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(unix))]
|
||||
use std::os::raw::c_int as RawFd;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::os::raw::c_int as RawFd;
|
||||
use std::sync::Arc;
|
||||
use tungstenite::Message;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::AsRawFd;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::net::TcpStream;
|
||||
@@ -41,14 +41,14 @@ type WsConn = JSWebsocket;
|
||||
type SplitStreamReceiver = oneshot::Receiver<Result<SplitStream<WsConn>, GatewayClientError>>;
|
||||
|
||||
pub(crate) fn ws_fd(_conn: &WsConn) -> Option<RawFd> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(unix)]
|
||||
match _conn.get_ref() {
|
||||
MaybeTlsStream::Plain(stream) => Some(stream.as_raw_fd()),
|
||||
&_ => unreachable!(
|
||||
"If tls features are enabled, the inner stream needs to be unpacked into raw fd"
|
||||
),
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(unix))]
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ nym-credentials = { path = "../../common/credentials" }
|
||||
nym-credentials-interface = { path = "../../common/credentials-interface" }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
nym-credential-utils = { path = "../../common/credential-utils" }
|
||||
nym-id = { path = "../nym-id" }
|
||||
|
||||
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
|
||||
nym-types = { path = "../../common/types" }
|
||||
|
||||
@@ -5,15 +5,10 @@ use crate::utils::CommonConfigsWrapper;
|
||||
use anyhow::bail;
|
||||
use clap::ArgGroup;
|
||||
use clap::Parser;
|
||||
use log::{error, info};
|
||||
use nym_credential_storage::initialise_persistent_storage;
|
||||
use nym_credential_storage::models::StorableIssuedCredential;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||
use nym_credentials::IssuedBandwidthCredential;
|
||||
use nym_id::import_credential;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
|
||||
bs58::decode(raw).into_vec()
|
||||
@@ -35,8 +30,8 @@ pub struct Args {
|
||||
pub(crate) credential_path: Option<PathBuf>,
|
||||
|
||||
// currently hidden as there exists only a single serialization standard
|
||||
#[clap(long, hide = true, default_value_t = 1)]
|
||||
pub(crate) version: u8,
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) version: Option<u8>,
|
||||
}
|
||||
|
||||
pub async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
@@ -54,6 +49,7 @@ pub async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
"using credentials store at '{}'",
|
||||
credentials_store.display()
|
||||
);
|
||||
let credentials_store = initialise_persistent_storage(credentials_store).await;
|
||||
|
||||
let raw_credential = match args.credential_data {
|
||||
Some(data) => data,
|
||||
@@ -62,44 +58,7 @@ pub async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
fs::read(args.credential_path.unwrap())?
|
||||
}
|
||||
};
|
||||
let raw_credential = Zeroizing::new(raw_credential);
|
||||
|
||||
// we're unpacking the data in order to make sure it's valid
|
||||
// and to extract relevant metadata for storage purposes
|
||||
let credential = match args.version {
|
||||
1 => Zeroizing::new(IssuedBandwidthCredential::unpack_v1(&raw_credential)?),
|
||||
other => panic!("unknown credential serialization version {other}"),
|
||||
};
|
||||
let persistent_storage = initialise_persistent_storage(credentials_store).await;
|
||||
|
||||
info!("importing {}", credential.typ());
|
||||
match credential.variant_data() {
|
||||
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
|
||||
info!("with value of {}", voucher_info.value())
|
||||
}
|
||||
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
|
||||
info!("with expiry at {}", freepass_info.expiry_date());
|
||||
if freepass_info.expired() {
|
||||
error!("the free pass has already expired!");
|
||||
|
||||
// technically we can, but the gateway will just reject it so what's the point
|
||||
bail!("can't import an expired free pass")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let storable = StorableIssuedCredential {
|
||||
serialization_revision: args.version,
|
||||
credential_data: &raw_credential,
|
||||
credential_type: credential.typ().to_string(),
|
||||
epoch_id: credential
|
||||
.epoch_id()
|
||||
.try_into()
|
||||
.expect("our epoch is has run over u32::MAX!"),
|
||||
};
|
||||
|
||||
persistent_storage
|
||||
.insert_issued_credential(storable)
|
||||
.await?;
|
||||
import_credential(credentials_store, raw_credential, args.version).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -315,9 +315,12 @@ impl IssuanceBandwidthCredential {
|
||||
}
|
||||
|
||||
// TODO: is that actually needed?
|
||||
// idea: make it consistent with the issued credential and its vX serde
|
||||
pub fn try_from_recovered_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
||||
use bincode::Options;
|
||||
Ok(make_recovery_bincode_serializer().deserialize(bytes)?)
|
||||
make_recovery_bincode_serializer()
|
||||
.deserialize(bytes)
|
||||
.map_err(|source| Error::RecoveryCredentialDeserializationFailure { source })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,3 +330,18 @@ fn make_recovery_bincode_serializer() -> impl bincode::Options {
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||
|
||||
fn assert_zeroize<T: Zeroize>() {}
|
||||
|
||||
#[test]
|
||||
fn credential_is_zeroized() {
|
||||
assert_zeroize::<IssuanceBandwidthCredential>();
|
||||
assert_zeroize_on_drop::<IssuanceBandwidthCredential>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,15 @@ impl IssuedBandwidthCredential {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_unpack(bytes: &[u8], revision: impl Into<Option<u8>>) -> Result<Self, Error> {
|
||||
let revision = revision.into().unwrap_or(CURRENT_SERIALIZATION_REVISION);
|
||||
|
||||
match revision {
|
||||
1 => Self::unpack_v1(bytes),
|
||||
_ => Err(Error::UnknownSerializationRevision { revision }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn epoch_id(&self) -> EpochId {
|
||||
self.epoch_id
|
||||
}
|
||||
@@ -138,7 +147,12 @@ impl IssuedBandwidthCredential {
|
||||
/// Unpack (deserialize) the credential data from the given bytes using v1 serializer.
|
||||
pub fn unpack_v1(bytes: &[u8]) -> Result<Self, Error> {
|
||||
use bincode::Options;
|
||||
Ok(make_storable_bincode_serializer().deserialize(bytes)?)
|
||||
make_storable_bincode_serializer()
|
||||
.deserialize(bytes)
|
||||
.map_err(|source| Error::SerializationFailure {
|
||||
source,
|
||||
revision: 1,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn randomise_signature(&mut self) {
|
||||
@@ -191,3 +205,18 @@ fn make_storable_bincode_serializer() -> impl bincode::Options {
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||
|
||||
fn assert_zeroize<T: Zeroize>() {}
|
||||
|
||||
#[test]
|
||||
fn credential_is_zeroized() {
|
||||
assert_zeroize::<IssuedBandwidthCredential>();
|
||||
assert_zeroize_on_drop::<IssuedBandwidthCredential>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use nym_credentials_interface::CoconutError;
|
||||
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
|
||||
use crate::coconut::bandwidth::issued::CURRENT_SERIALIZATION_REVISION;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -12,8 +13,18 @@ pub enum Error {
|
||||
#[error("IO error")]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[error("failed to (de)serialize credential structure: {0}")]
|
||||
SerializationFailure(#[from] bincode::Error),
|
||||
#[error("failed to deserialize a recovery credential: {source}")]
|
||||
RecoveryCredentialDeserializationFailure { source: bincode::Error },
|
||||
|
||||
#[error("failed to (de)serialize provided credential using revision {revision}: {source}")]
|
||||
SerializationFailure {
|
||||
#[source]
|
||||
source: bincode::Error,
|
||||
revision: u8,
|
||||
},
|
||||
|
||||
#[error("unknown credential serializatio revision {revision}. the current (and max supported) version is {CURRENT_SERIALIZATION_REVISION}")]
|
||||
UnknownSerializationRevision { revision: u8 },
|
||||
|
||||
#[error("The detailed description is yet to be determined")]
|
||||
BandwidthCredentialError,
|
||||
|
||||
@@ -9,3 +9,4 @@ pub use coconut::bandwidth::{
|
||||
IssuedBandwidthCredential,
|
||||
};
|
||||
pub use coconut::utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
|
||||
pub use error::Error;
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
pub mod codec;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
pub const CURRENT_VERSION: u8 = 3;
|
||||
// version 3: initial version
|
||||
// version 4: IPv6 support
|
||||
// version 5: Add severity level to info response
|
||||
pub const CURRENT_VERSION: u8 = 5;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IpPair {
|
||||
pub ipv4: Ipv4Addr,
|
||||
pub ipv6: Ipv6Addr,
|
||||
}
|
||||
|
||||
impl IpPair {
|
||||
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
|
||||
IpPair { ipv4, ipv6 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IpPair {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "IPv4: {}, IPv6: {}", self.ipv4, self.ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, CURRENT_VERSION};
|
||||
use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION};
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
@@ -19,7 +17,7 @@ pub struct IpPacketRequest {
|
||||
|
||||
impl IpPacketRequest {
|
||||
pub fn new_static_connect_request(
|
||||
ip: IpAddr,
|
||||
ips: IpPair,
|
||||
reply_to: Recipient,
|
||||
reply_to_hops: Option<u8>,
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
@@ -31,7 +29,7 @@ impl IpPacketRequest {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::StaticConnect(StaticConnectRequest {
|
||||
request_id,
|
||||
ip,
|
||||
ips,
|
||||
reply_to,
|
||||
reply_to_hops,
|
||||
reply_to_avg_mix_delays,
|
||||
@@ -137,7 +135,7 @@ pub enum IpPacketRequestData {
|
||||
pub struct StaticConnectRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
pub ip: IpAddr,
|
||||
pub ips: IpPair,
|
||||
|
||||
// The nym-address the response should be sent back to
|
||||
pub reply_to: Recipient,
|
||||
@@ -210,6 +208,8 @@ pub struct HealthRequest {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn check_size_of_request() {
|
||||
@@ -218,15 +218,15 @@ mod tests {
|
||||
data: IpPacketRequestData::StaticConnect(
|
||||
StaticConnectRequest {
|
||||
request_id: 123,
|
||||
ip: IpAddr::from([10, 0, 0, 1]),
|
||||
ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("2001:db8:a160::1").unwrap()),
|
||||
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
|
||||
reply_to_hops: None,
|
||||
reply_to_avg_mix_delays: None,
|
||||
buffer_timeout: None,
|
||||
},
|
||||
)
|
||||
),
|
||||
};
|
||||
assert_eq!(connect.to_bytes().unwrap().len(), 108);
|
||||
assert_eq!(connect.to_bytes().unwrap().len(), 123);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, CURRENT_VERSION};
|
||||
use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct IpPacketResponse {
|
||||
@@ -38,13 +36,13 @@ impl IpPacketResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ip: IpAddr) -> Self {
|
||||
pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ips: IpPair) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ip }),
|
||||
reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ips }),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -118,24 +116,30 @@ impl IpPacketResponse {
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Error(ErrorResponse {
|
||||
data: IpPacketResponseData::Info(InfoResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: ErrorResponseReply::VersionMismatch {
|
||||
reply: InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version: our_version,
|
||||
},
|
||||
level: InfoLevel::Error,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_data_error_response(reply_to: Recipient, reply: ErrorResponseReply) -> Self {
|
||||
pub fn new_data_info_response(
|
||||
reply_to: Recipient,
|
||||
reply: InfoResponseReply,
|
||||
level: InfoLevel,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Error(ErrorResponse {
|
||||
data: IpPacketResponseData::Info(InfoResponse {
|
||||
request_id: 0,
|
||||
reply_to,
|
||||
reply,
|
||||
level,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -149,7 +153,7 @@ impl IpPacketResponse {
|
||||
IpPacketResponseData::Data(_) => None,
|
||||
IpPacketResponseData::Pong(response) => Some(response.request_id),
|
||||
IpPacketResponseData::Health(response) => Some(response.request_id),
|
||||
IpPacketResponseData::Error(response) => Some(response.request_id),
|
||||
IpPacketResponseData::Info(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +166,7 @@ impl IpPacketResponse {
|
||||
IpPacketResponseData::Data(_) => None,
|
||||
IpPacketResponseData::Pong(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Health(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Error(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Info(response) => Some(&response.reply_to),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,8 +207,8 @@ pub enum IpPacketResponseData {
|
||||
// Response for a health request
|
||||
Health(HealthResponse),
|
||||
|
||||
// Error response
|
||||
Error(ErrorResponse),
|
||||
// Info response. This can be anything from informative messages to errors
|
||||
Info(InfoResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -263,7 +267,7 @@ impl DynamicConnectResponseReply {
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DynamicConnectSuccess {
|
||||
pub ip: IpAddr,
|
||||
pub ips: IpPair,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
@@ -340,14 +344,15 @@ pub struct HealthResponseReply {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ErrorResponse {
|
||||
pub struct InfoResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: ErrorResponseReply,
|
||||
pub reply: InfoResponseReply,
|
||||
pub level: InfoLevel,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum ErrorResponseReply {
|
||||
pub enum InfoResponseReply {
|
||||
#[error("{msg}")]
|
||||
Generic { msg: String },
|
||||
#[error(
|
||||
@@ -360,3 +365,10 @@ pub enum ErrorResponseReply {
|
||||
#[error("destination failed exit policy filter check: {dst}")]
|
||||
ExitPolicyFilterCheckFailed { dst: String },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum InfoLevel {
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "nym-id"
|
||||
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]
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tracing.workspace = true
|
||||
zeroize.workspace = true
|
||||
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
nym-credentials = { path = "../credentials" }
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error::Error;
|
||||
use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NymIdError {
|
||||
#[error("failed to deserialize provided credential: {source}")]
|
||||
CredentialDeserializationFailure { source: nym_credentials::Error },
|
||||
|
||||
#[error("attempted to import an expired credential (it expired on {expiration})")]
|
||||
ExpiredCredentialImport { expiration: OffsetDateTime },
|
||||
|
||||
#[error("failed to store credential in the provided store: {source}")]
|
||||
StorageError {
|
||||
source: Box<dyn Error + Send + Sync>,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::NymIdError;
|
||||
use nym_credential_storage::models::StorableIssuedCredential;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||
use nym_credentials::IssuedBandwidthCredential;
|
||||
use tracing::{debug, warn};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub async fn import_credential<S>(
|
||||
credentials_store: S,
|
||||
raw_credential: Vec<u8>,
|
||||
credential_version: impl Into<Option<u8>>,
|
||||
) -> Result<(), NymIdError>
|
||||
where
|
||||
S: Storage,
|
||||
<S as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let raw_credential = Zeroizing::new(raw_credential);
|
||||
|
||||
// note: the type itself implements ZeroizeOnDrop
|
||||
let credential = IssuedBandwidthCredential::try_unpack(&raw_credential, credential_version)
|
||||
.map_err(|source| NymIdError::CredentialDeserializationFailure { source })?;
|
||||
|
||||
debug!(
|
||||
"attempting to import credential of type {}",
|
||||
credential.typ()
|
||||
);
|
||||
|
||||
match credential.variant_data() {
|
||||
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
|
||||
debug!("with value of {}", voucher_info.value())
|
||||
}
|
||||
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
|
||||
debug!("with expiry at {}", freepass_info.expiry_date());
|
||||
if freepass_info.expired() {
|
||||
warn!("the free pass has already expired!");
|
||||
|
||||
// technically we can import it, but the gateway will just reject it so what's the point
|
||||
return Err(NymIdError::ExpiredCredentialImport {
|
||||
expiration: freepass_info.expiry_date(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY:
|
||||
// for the epoch to run over u32::MAX, we'd have to advance it for few centuries every block...
|
||||
// the alternative is a very particularly malformed serialized data, but at that point blowing up is the right call
|
||||
// because we can't rely on it anyway
|
||||
#[allow(clippy::expect_used)]
|
||||
let storable = StorableIssuedCredential {
|
||||
serialization_revision: credential.current_serialization_revision(),
|
||||
credential_data: &raw_credential,
|
||||
credential_type: credential.typ().to_string(),
|
||||
epoch_id: credential
|
||||
.epoch_id()
|
||||
.try_into()
|
||||
.expect("our epoch is has run over u32::MAX!"),
|
||||
};
|
||||
|
||||
credentials_store
|
||||
.insert_issued_credential(storable)
|
||||
.await
|
||||
.map_err(|source| NymIdError::StorageError {
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub mod error;
|
||||
pub mod import_credential;
|
||||
|
||||
pub use error::NymIdError;
|
||||
pub use import_credential::import_credential;
|
||||
@@ -23,6 +23,7 @@ use nym_client_core::init::types::GatewaySetup;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::manager::TaskStatus;
|
||||
use nym_task::{TaskClient, TaskHandle};
|
||||
|
||||
use anyhow::anyhow;
|
||||
@@ -177,7 +178,9 @@ where
|
||||
))?;
|
||||
|
||||
// Listen to status messages from task, that we forward back to the caller
|
||||
shutdown.start_status_listener(sender).await;
|
||||
shutdown
|
||||
.start_status_listener(sender, TaskStatus::Ready)
|
||||
.await;
|
||||
|
||||
let res = tokio::select! {
|
||||
biased;
|
||||
|
||||
@@ -46,6 +46,8 @@ enum TaskError {
|
||||
pub enum TaskStatus {
|
||||
#[error("Ready")]
|
||||
Ready,
|
||||
#[error("Ready and connected to gateway: {0}")]
|
||||
ReadyWithGateway(String),
|
||||
}
|
||||
|
||||
/// Listens to status and error messages from tasks, as well as notifying them to gracefully
|
||||
@@ -161,10 +163,14 @@ impl TaskManager {
|
||||
self.notify_tx.send(())
|
||||
}
|
||||
|
||||
pub async fn start_status_listener(&mut self, mut sender: StatusSender) {
|
||||
pub async fn start_status_listener(
|
||||
&mut self,
|
||||
mut sender: StatusSender,
|
||||
start_status: TaskStatus,
|
||||
) {
|
||||
// Announce that we are operational. This means that in the application where this is used,
|
||||
// everything is up and running and ready to go.
|
||||
if let Err(msg) = sender.send(Box::new(TaskStatus::Ready)).await {
|
||||
if let Err(msg) = sender.send(Box::new(start_status)).await {
|
||||
log::error!("Error sending status message: {}", msg);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::net::Ipv6Addr;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
@@ -19,6 +20,12 @@ const TUN_WRITE_TIMEOUT_MS: u64 = 1000;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum TunDeviceError {
|
||||
#[error("{0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
|
||||
#[error("{0}")]
|
||||
TokioTun(#[from] tokio_tun::Error),
|
||||
|
||||
#[error("timeout writing to tun device, dropping packet")]
|
||||
TunWriteTimeout,
|
||||
|
||||
@@ -44,14 +51,18 @@ pub enum TunDeviceError {
|
||||
FailedToLockPeer,
|
||||
}
|
||||
|
||||
fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> tokio_tun::Tun {
|
||||
fn setup_tokio_tun_device(
|
||||
name: &str,
|
||||
address: Ipv4Addr,
|
||||
netmask: Ipv4Addr,
|
||||
) -> Result<tokio_tun::Tun, TunDeviceError> {
|
||||
log::info!("Creating TUN device with: address={address}, netmask={netmask}");
|
||||
// Read MTU size from env variable NYM_MTU_SIZE, else default to 1420.
|
||||
let mtu = std::env::var("NYM_MTU_SIZE")
|
||||
.map(|mtu| mtu.parse().expect("NYM_MTU_SIZE must be a valid integer"))
|
||||
.unwrap_or(1420);
|
||||
log::info!("Using MTU size: {mtu}");
|
||||
tokio_tun::Tun::builder()
|
||||
Ok(tokio_tun::Tun::builder()
|
||||
.name(name)
|
||||
.tap(false)
|
||||
.packet_info(false)
|
||||
@@ -59,8 +70,7 @@ fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> t
|
||||
.up()
|
||||
.address(address)
|
||||
.netmask(netmask)
|
||||
.try_build()
|
||||
.expect("Failed to setup tun device, do you have permission?")
|
||||
.try_build()?)
|
||||
}
|
||||
|
||||
pub struct TunDevice {
|
||||
@@ -103,16 +113,18 @@ pub struct NatInner {
|
||||
|
||||
pub struct TunDeviceConfig {
|
||||
pub base_name: String,
|
||||
pub ip: Ipv4Addr,
|
||||
pub netmask: Ipv4Addr,
|
||||
pub ipv4: Ipv4Addr,
|
||||
pub netmaskv4: Ipv4Addr,
|
||||
pub ipv6: Ipv6Addr,
|
||||
pub netmaskv6: String,
|
||||
}
|
||||
|
||||
impl TunDevice {
|
||||
pub fn new(
|
||||
routing_mode: RoutingMode,
|
||||
config: TunDeviceConfig,
|
||||
) -> (Self, TunTaskTx, TunTaskResponseRx) {
|
||||
let tun = Self::new_device_only(config);
|
||||
) -> Result<(Self, TunTaskTx, TunTaskResponseRx), TunDeviceError> {
|
||||
let tun = Self::new_device_only(config)?;
|
||||
|
||||
// Channels to communicate with the other tasks
|
||||
let (tun_task_tx, tun_task_rx) = tun_task_channel();
|
||||
@@ -125,20 +137,32 @@ impl TunDevice {
|
||||
routing_mode,
|
||||
};
|
||||
|
||||
(tun_device, tun_task_tx, tun_task_response_rx)
|
||||
Ok((tun_device, tun_task_tx, tun_task_response_rx))
|
||||
}
|
||||
|
||||
pub fn new_device_only(config: TunDeviceConfig) -> tokio_tun::Tun {
|
||||
pub fn new_device_only(config: TunDeviceConfig) -> Result<tokio_tun::Tun, TunDeviceError> {
|
||||
let TunDeviceConfig {
|
||||
base_name,
|
||||
ip,
|
||||
netmask,
|
||||
ipv4,
|
||||
netmaskv4,
|
||||
ipv6,
|
||||
netmaskv6,
|
||||
} = config;
|
||||
let name = format!("{base_name}%d");
|
||||
|
||||
let tun = setup_tokio_tun_device(&name, ip, netmask);
|
||||
let tun = setup_tokio_tun_device(&name, ipv4, netmaskv4)?;
|
||||
log::info!("Created TUN device: {}", tun.name());
|
||||
tun
|
||||
std::process::Command::new("ip")
|
||||
.args([
|
||||
"-6",
|
||||
"addr",
|
||||
"add",
|
||||
&format!("{}/{}", ipv6, netmaskv6),
|
||||
"dev",
|
||||
&tun.name(),
|
||||
])
|
||||
.output()?;
|
||||
Ok(tun)
|
||||
}
|
||||
|
||||
// Send outbound packets out on the wild internet
|
||||
|
||||
@@ -31,9 +31,13 @@ assets_version = "3.0.0" # do not edit: managed by `mdbook-admonish install`
|
||||
minimum_rust_version = "1.66"
|
||||
wallet_release_version = "1.2.8"
|
||||
# nym-vpn related variables
|
||||
nym_vpn_latest_binary_url = "https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.4"
|
||||
nym_vpn_releases = "https://github.com/nymtech/nym-vpn-client/releases"
|
||||
nym_vpn_form_url = "https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko-2"
|
||||
|
||||
# versions are pulled by cmdrun now
|
||||
# nym_vpn_gui_version = "0.0.6"
|
||||
# nym_vpn_cli_version = "0.0.4"
|
||||
|
||||
[preprocessor.last-changed]
|
||||
command = "mdbook-last-changed"
|
||||
renderer = ["html"]
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
- [CLI](nymvpn/cli.md)
|
||||
- [Linux](nymvpn/cli-linux.md)
|
||||
- [MacOS](nymvpn/cli-mac.md)
|
||||
- [Testing](nymvpn/testing.md)
|
||||
- [Troubleshooting](nymvpn/troubleshooting.md)
|
||||
- [NymVPN FAQ](nymvpn/faq.md)
|
||||
- [NymConnect X Monero](tutorials/monero.md)
|
||||
|
||||
@@ -8,25 +8,29 @@ NymVPN is an experimental software and it's for [testing](./testing.md) purposes
|
||||
|
||||
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
1. Open Github [releases page]({{nym_vpn_latest_binary_url}}) and download the binary for Debian based Linux
|
||||
2. Verify sha hash of your downloaded binary with the one listed on the [releases page]({{nym_vpn_latest_binary_url}}). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
1. Open Github [releases page]({{nym_vpn_releases}}) and download the binary for Debian based Linux
|
||||
|
||||
2. Verify sha hash of your downloaded binary with the one listed on the [releases page]({{nym_vpn_releases}}). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
```sh
|
||||
echo "<SHA_STRING>" | shasum -a 256 -c
|
||||
|
||||
# choose a correct one according to your binary, this is just an example
|
||||
# echo "0e4abb461e86b2c168577e0294112a3bacd3a24bf8565b49783bfebd9b530e23 nym-vpn-cli_0.1.0_ubuntu-22.04_amd64.zip" | shasum -a 256 -c
|
||||
# echo "0e4abb461e86b2c168577e0294112a3bacd3a24bf8565b49783bfebd9b530e23 nym-vpn-cli_<!-- cmdrun scripts/nym_vpn_cli_version.sh -->_ubuntu-22.04_amd64.tar.gz" | shasum -a 256 -c
|
||||
```
|
||||
1. Extract files:
|
||||
|
||||
3. Extract files:
|
||||
```sh
|
||||
tar -xvf <BINARY>
|
||||
tar -xvf <BINARY>.tar.gz
|
||||
# for example
|
||||
# tar -xvf nym-vpn-cli_0.0.2_ubuntu-22.04_x86_64.tar.gz
|
||||
# tar -xvf nym-vpn-cli_<!-- cmdrun scripts/nym_vpn_cli_version.sh -->_ubuntu-22.04_x86_64.tar.gz
|
||||
```
|
||||
2. Make executable by running:
|
||||
|
||||
4. Make executable by running:
|
||||
```sh
|
||||
# possibly you may have to cd into a sub-directory
|
||||
# make sure you are in the right sub-directory
|
||||
chmod u+x ./nym-vpn-cli
|
||||
```
|
||||
|
||||
5. Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries by running:
|
||||
```sh
|
||||
curl -o sandbox.env -L https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env
|
||||
|
||||
@@ -8,19 +8,19 @@ NymVPN is an experimental software and it's for [testing](./testing.md) purposes
|
||||
|
||||
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
1. Open Github [releases page]({{nym_vpn_latest_binary_url}}) and download the binary for MacOS
|
||||
2. Verify sha hash of your downloaded binary with the one listed on the [releases page]({{nym_vpn_latest_binary_url}}). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
1. Open Github [releases page]({{nym_vpn_releases}}) and download the binary for MacOS
|
||||
2. Verify sha hash of your downloaded binary with the one listed on the [releases page]({{nym_vpn_releases}}). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
```sh
|
||||
echo "<SHA_STRING>" | shasum -a 256 -c
|
||||
|
||||
# choose a correct one according to your binary, this is just an example
|
||||
# echo "96623ccc69bc4cc0e4e3e18528b6dae6be69f645d0a592d926a3158ce2d0c269 nym-vpn-cli_0.1.0_macos_x86_64.zip" | shasum -a 256 -c
|
||||
# echo "96623ccc69bc4cc0e4e3e18528b6dae6be69f645d0a592d926a3158ce2d0c269 nym-vpn-cli_<!-- cmdrun scripts/nym_vpn_cli_version.sh -->_macos_x86_64.zip" | shasum -a 256 -c
|
||||
```
|
||||
3. Extract files:
|
||||
```sh
|
||||
tar -xvf <BINARY>
|
||||
# for example
|
||||
# tar -xvf nym-vpn-cli_0.0.2_macos_aarch64.tar.gz
|
||||
# tar -xvf nym-vpn-cli_<!-- cmdrun scripts/nym_vpn_cli_version.sh -->_macos_aarch64.tar.gz
|
||||
```
|
||||
4. Make executable by running:
|
||||
```sh
|
||||
|
||||
@@ -5,33 +5,22 @@ Our alpha testing round is done with participants at live workshop events. This
|
||||
|
||||
**If you commit to test NymVPN alpha, please start with the [user research form]({{nym_vpn_form_url}}) where all the steps will be provided**. If you disagree with any of the conditions listed, please leave this page.
|
||||
```
|
||||
|
||||
NymVPN CLI is a fundamental way to run the client for different purposes, currently it is a must for users who want to run the [testing scripts](testing.md).
|
||||
|
||||
Follow the simple [automated script](#automated-script-for-cli-installation) below to install and run NymVPN CLI. If you prefer to do a manual setup follow the steps in the guide for [Linux](cli-linux.md) or [MacOS](cli-mac.md).
|
||||
|
||||
Visit NymVPN alpha latest [release page]({{nym_vpn_releases}}) to check sha sums or download the binaries directly.
|
||||
|
||||
## Automated Script for CLI Installation
|
||||
|
||||
We wrote a [script](https://gist.github.com/serinko/d65450653d6bbafacbcee71c9cb8fb31) which does download of the CLI, sha256 verification, extraction, installation and configuration for Linux and MacOS users automatically following the steps below:
|
||||
|
||||
1. Open a terminal window in a directory where you want the script and NymVPN CLI binary be downloaded and run
|
||||
```sh
|
||||
curl -o execute-nym-vpn-cli-binary.sh -L https://gist.githubusercontent.com/serinko/d65450653d6bbafacbcee71c9cb8fb31/raw/0cbcdd18f7ee94f559692b936061248ebbbf2773/execute-nym-vpn-cli-binary.sh
|
||||
curl -o execute-nym-vpn-cli-binary.sh -L https://gist.githubusercontent.com/serinko/d65450653d6bbafacbcee71c9cb8fb31/raw/4b70371fb000fd08910c0f778e78566d002e1319/execute-nym-vpn-cli-binary.sh && chmod u+x execute-nym-vpn-cli-binary.sh && sudo -E ./execute-nym-vpn-cli-binary.sh
|
||||
```
|
||||
|
||||
2. Make the script executable
|
||||
```sh
|
||||
chmod u+x execute-nym-vpn-cli-binary.sh
|
||||
```
|
||||
2. Follow the prompts in the program
|
||||
|
||||
3. Start the script, turn off any VPN and run
|
||||
```sh
|
||||
sudo -E ./execute-nym-vpn-cli-binary.sh
|
||||
```
|
||||
|
||||
4. Follow the prompts in the program
|
||||
|
||||
5. The script will automatically start the client. Make sure to **turn off any other VPNs** and follow the prompts:
|
||||
3. The script will automatically start the client. Make sure to **turn off any other VPNs** and follow the prompts:
|
||||
|
||||
* It prints a JSON view of existing Gateways and prompt you to:
|
||||
- *Make sure to use two different Gateways for entry and exit!*
|
||||
|
||||
@@ -12,33 +12,35 @@ NymVPN is an experimental software and it's for [testing](./testing.md) purposes
|
||||
|
||||
### Installation
|
||||
|
||||
1. Open Github [releases page]({{nym_vpn_latest_binary_url}}) and download the binary for Debian based Linux
|
||||
2. Required (if you don't want to check shasum, skip this point): Verify sha hash of your downloaded binary with the one listed on the [releases page]({{nym_vpn_latest_binary_url}}). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
1. Open Github [releases page]({{nym_vpn_releases}}) and download the binary for Debian based Linux
|
||||
|
||||
2. Required (if you don't want to check shasum, skip this point): Verify sha hash of your downloaded binary with the one listed on the [releases page]({{nym_vpn_releases}}). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
```sh
|
||||
echo "<SHA_STRING>" | shasum -a 256 -c
|
||||
|
||||
# choose a correct one according to your binary, this is just an example
|
||||
# echo "a5f91f20d587975e30b6a75d3a9e195234cf1269eac278139a5b9c39b039e807 nym-vpn-desktop_0.0.3_ubuntu-22.04_x86_64.zip" | shasum -a 256 -c
|
||||
# echo "a5f91f20d587975e30b6a75d3a9e195234cf1269eac278139a5b9c39b039e807 nym-vpn-desktop_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_ubuntu-22.04_x86_64.tar.gz" | shasum -a 256 -c
|
||||
```
|
||||
|
||||
3. Extract files:
|
||||
```sh
|
||||
tar -xvf <BINARY>
|
||||
tar -xvf <BINARY>.tar.gz
|
||||
# for example
|
||||
# tar -xvf nym-vpn-desktop_0.0.4_ubuntu-22.04_x86_64.tar.gz
|
||||
# tar -xvf nym-vpn-desktop_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_ubuntu-22.04_x86_64.tar.gz
|
||||
```
|
||||
|
||||
4. If you prefer to run `.AppImage` make executable by running:
|
||||
```sh
|
||||
# make sure you cd into the right sub-directory after extraction
|
||||
chmod u+x ./appimage/nym-vpn_0.0.4_amd64.AppImage
|
||||
chmod u+x ./nym-vpn_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_amd64.AppImage
|
||||
```
|
||||
|
||||
5. If you prefer to use the `.deb` version for installation (works on Debian based Linux only), open terminal in the same directory and run:
|
||||
```sh
|
||||
cd deb
|
||||
|
||||
sudo dpkg -i ./nym-vpn_0.0.4_amd64.deb
|
||||
# make sure you cd into the right sub-directory after extraction
|
||||
sudo dpkg -i ./nym-vpn_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_amd64.deb
|
||||
# or
|
||||
sudo apt-get install -f ./nym-vpn_0.0.4_amd64.deb
|
||||
sudo apt-get install -f ./nym-vpn_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_amd64.deb
|
||||
```
|
||||
|
||||
NymVPN alpha version runs over Nym testnet (called sandbox), a little extra configuration is needed for the application to work.
|
||||
@@ -72,7 +74,7 @@ Open terminal and run:
|
||||
|
||||
```sh
|
||||
# .AppImage must be run from the same directory as the binary
|
||||
sudo -E ./nym-vpn_0.0.4_amd64.AppImage
|
||||
sudo -E ./nym-vpn_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_amd64.AppImage
|
||||
|
||||
# .deb installation shall be executable from anywhere as
|
||||
sudo -E nym-vpn
|
||||
|
||||
@@ -17,21 +17,21 @@ mkdir -p "$HOME/nym-vpn-latest"
|
||||
```
|
||||
-->
|
||||
|
||||
1. Open Github [releases page]({{nym_vpn_latest_binary_url}}) and download the binary for your version of MacOS
|
||||
1. Open Github [releases page]({{nym_vpn_releases}}) and download the binary for your version of MacOS
|
||||
|
||||
2. Recommended (skip this point if you don't want to verify): Verify sha hash of your downloaded binary with the one listed on the [releases page]({{nym_vpn_latest_binary_url}}). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
2. Recommended (skip this point if you don't want to verify): Verify sha hash of your downloaded binary with the one listed on the [releases page]({{nym_vpn_releases}}). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
```sh
|
||||
echo "<SHA_STRING>" | shasum -a 256 -c
|
||||
|
||||
# choose a correct one according to your binary, this is just an example
|
||||
# echo "da4c0bf8e8b52658312d341fa3581954cfcb6efd516d9a448c76d042a454b5df nym-vpn-desktop_0.0.3_macos_x86_64.zip" | shasum -a 256 -c
|
||||
# echo "da4c0bf8e8b52658312d341fa3581954cfcb6efd516d9a448c76d042a454b5df nym-vpn-desktop_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_macos_x86_64.zip" | shasum -a 256 -c
|
||||
```
|
||||
|
||||
3. Extract the downloaded file manually or by a command:
|
||||
```sh
|
||||
tar -xvf <BINARY>
|
||||
tar -xvf <BINARY>.tar.gz
|
||||
# for example
|
||||
# tar -xvf nym-vpn-desktop_0.0.4_macos_aarch64.tar.gz
|
||||
# tar -xvf nym-vpn-desktop_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_macos_aarch64.tar.gz
|
||||
```
|
||||
<!-- seems redundant
|
||||
5. Move to the application content directory:
|
||||
@@ -71,7 +71,7 @@ env_config_file = "sandbox.env"
|
||||
```
|
||||
Alternatively do it by using this command:
|
||||
```sh
|
||||
echo "env_config_file = sandbox.env" > /Applications/nym-vpn.app/Contents/MacOS//config.toml
|
||||
echo "env_config_file = sandbox.env" > /Applications/nym-vpn.app/Contents/MacOS/config.toml
|
||||
```
|
||||
## Run NymVPN
|
||||
|
||||
|
||||
@@ -10,23 +10,26 @@ This is the alpha version of NymVPN desktop application (GUI). A demo of how the
|
||||
|
||||
Follow the simple [automated script](#automated-script-for-gui-installation) below to install and run NymVPN GUI. If the script didn't work for your distribution or you prefer to do a manual setup follow the steps in the guide for [Linux](gui-linux.md) or [MacOS](gui-mac.md) .
|
||||
|
||||
Visit NymVPN alpha latest [release page]({{nym_vpn_latest_binary_url}}) to check sha sums or download the binaries directly.
|
||||
Visit NymVPN alpha latest [release page]({{nym_vpn_releases}}) to check sha sums or download the binaries directly.
|
||||
|
||||
## Automated Script for GUI Installation
|
||||
|
||||
We wrote a [script](https://gist.github.com/serinko/e0a9f7ff3d79e974ec6f6783caa1137e) which does download of dependencies and the application, sha256 verification, extraction, installation and configuration for Linux and MacOS users automatically. Turn off all VPNs and follow the steps below.
|
||||
We wrote a [script](https://gist.github.com/tommyv1987/7d210d4daa8f7abc61f9a696d0321f19) which does download of dependencies and the application, sha256 verification, extraction, installation and configuration for Linux and MacOS users automatically. Turn off all VPNs and follow the steps below.
|
||||
|
||||
1. Open a terminal window in a directory where you want the script to be downloaded and run
|
||||
```sh
|
||||
curl -o nym-vpn-desktop-install-run.sh -L https://gist.githubusercontent.com/serinko/e0a9f7ff3d79e974ec6f6783caa1137e/raw/227c8c348a1e58f68cb500e4504b22412177c680/nym-vpn-desktop-install-run.sh && chmod u+x nym-vpn-desktop-install-run.sh && sudo -E ./nym-vpn-desktop-install-run.sh
|
||||
curl -o nym-vpn-desktop-install-run.sh -L https://gist.githubusercontent.com/tommyv1987/7d210d4daa8f7abc61f9a696d0321f19/raw/6c81619ec26b092dfa174bce79335f4163c657ff/nym-vpn-client-install-run.sh && chmod u+x nym-vpn-desktop-install-run.sh && sudo -E ./nym-vpn-desktop-install-run.sh
|
||||
```
|
||||
|
||||
2. Follow the prompts in the program
|
||||
|
||||
To start the application again, reconnect your wifi and run
|
||||
```sh
|
||||
# Linux
|
||||
sudo -E ~/nym-vpn-latest/nym-vpn_0.0.4_amd64.AppImage
|
||||
# Linux .AppImage
|
||||
sudo -E ~/nym-vpn-latest/nym-vpn-desktop_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_ubuntu-22.04_x86_64/nym-vpn_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_amd64.AppImage
|
||||
|
||||
# Linux .deb
|
||||
sudo -E nym-vpn
|
||||
|
||||
# MacOS
|
||||
sudo -E $HOME/nym-vpn-latest/nym-vpn
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
**Nym proudly presents NymVPN alpha** - a client that uses [Nym Mixnet](https://nymtech.net) to anonymise all of a user's internet traffic through either a 5-hop mixnet (for a full network privacy) or the faster 2-hop decentralised VPN (with some extra features).
|
||||
|
||||
|
||||
**You are invited to take part in the alpha testing** of this new application. The following pages provide a how-to guide, explaining steps to install and run NymVPN [CLI](cli.md) and [GUI](gui.md) on the Sandbox testnet environment as well as provide some scripts for [qualitative testing](testing.md).
|
||||
**You are invited to take part in the alpha testing** of this new application. The following pages provide a how-to guide, explaining steps to install and run NymVPN [CLI](cli.md) and [GUI](gui.md) on the Sandbox testnet environment.
|
||||
|
||||
**Here is how**
|
||||
|
||||
@@ -13,13 +13,12 @@
|
||||
2. Please consent to the GDPR so we can use the results
|
||||
3. To test the GUI, [go here](gui.md)
|
||||
4. To test the CLI, [go here](cli.md)
|
||||
5. Run [qualitative testing script](testing.md)
|
||||
6. Fill and submit the [form!]({{nym_vpn_form_url}})
|
||||
7. Join the [NymVPN matrix channel](https://matrix.to/#/#NymVPN:nymtech.chat) if you have any questions, comments or blockers
|
||||
5. Fill and submit the [form!]({{nym_vpn_form_url}})
|
||||
6. Join the [NymVPN matrix channel](https://matrix.to/#/#NymVPN:nymtech.chat) if you have any questions, comments or blockers
|
||||
|
||||
***NymVPN alpha testing will last from 15th of January - 15th of February.***
|
||||
|
||||
*NOTE: NymVPN alpha is experimental software for [testing purposes](testing.md) only.*
|
||||
*NOTE: NymVPN alpha is experimental software for testing purposes only.*
|
||||
|
||||
|
||||
## NymVPN Overview
|
||||
@@ -45,10 +44,9 @@ The client can optionally do the first hop (local client to Entry Gateway) using
|
||||
## NymVPN Resources & Guides
|
||||
|
||||
* [NymVPN webpage](https://nymvpn.com)
|
||||
* [Alpha release page]({{nym_vpn_latest_binary_url}})
|
||||
* [Alpha release page]({{nym_vpn_releases}})
|
||||
* [NymVPN application (GUI) guide](gui.md)
|
||||
* [NymVPN Command Line Interface (CLI) guide](cli.md)
|
||||
* [Testing scripts](testing.md)
|
||||
* [Troubleshooting](troubleshooting.md)
|
||||
* [NymVPN FAQ](faq.md)
|
||||
* [NymVPN matrix channel](https://matrix.to/#/#NymVPN:nymtech.chat)
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# NymVPN alpha - Desktop: Guide for Mac OS
|
||||
|
||||
```admonish info
|
||||
NymVPN is an experimental software and it's for [testing](./testing.md) purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the workshop) so we use their results to improve the client, and submit the form [*NymVPN User research*]({{nym_vpn_form_url}}) with the testing results.
|
||||
```
|
||||
|
||||
## Preparation
|
||||
|
||||
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
### Installation
|
||||
|
||||
1. Create a directory `~/nym-vpn-latest`
|
||||
```sh
|
||||
mkdir -p "$HOME/nym-vpn-latest"
|
||||
```
|
||||
2. Open Github [releases page]({{nym_vpn_latest_binary_url}}) and download the binary for MacOS
|
||||
3. Verify sha hash of your downloaded binary with the one listed on the [releases page]({{nym_vpn_latest_binary_url}}). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
```sh
|
||||
echo "<SHA_STRING>" | shasum -a 256 -c
|
||||
|
||||
# choose a correct one according to your binary, this is just an example
|
||||
# echo "da4c0bf8e8b52658312d341fa3581954cfcb6efd516d9a448c76d042a454b5df nym-vpn-desktop_0.0.3_macos_x86_64.zip" | shasum -a 256 -c
|
||||
```
|
||||
4. Extract files:
|
||||
```sh
|
||||
tar -xvf <BINARY>
|
||||
# for example
|
||||
# tar -xvf nym-vpn-desktop_0.0.4_macos_aarch64.tar.gz
|
||||
```
|
||||
5. Move to the application directory and make executable
|
||||
```sh
|
||||
cd "macos/nym-vpn.app/Contents/MacOS"
|
||||
|
||||
chmod u+x nym-vpn
|
||||
```
|
||||
6. Move `nym-vpn` to your `~/nym-vpn-latest` directory
|
||||
```sh
|
||||
mv nym-vpn "$HOME/nym-vpn-latest"
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
7. Create the configuration file by opening a text editor and saving the lines below as `config.toml` in the same directory `~/nym-vpn-latest`
|
||||
```toml
|
||||
env_config_file = ".env"
|
||||
```
|
||||
8. Create testnet configuration file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `.env` in the same directory `~/nym-vpn-latest`
|
||||
```sh
|
||||
curl -L "https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env" -o "$HOME/nym-vpn-latest/.env"
|
||||
```
|
||||
## Run NymVPN
|
||||
|
||||
**For NymVPN to work, all other VPNs must be switched off!** At this alpha stage of NymVPN, the network connection (wifi) must be reconnected after or in between the testing rounds.
|
||||
|
||||
Run:
|
||||
```sh
|
||||
sudo -E $HOME/nym-vpn-latest/nym-vpn
|
||||
```
|
||||
|
||||
In case of errors check out the [troubleshooting](troubleshooting.html#installing-gui-on-macos-not-working) section.
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
release_url="https://api.github.com/repos/nymtech/nym-vpn-client/releases"
|
||||
current_cli_version=$(curl -s $release_url | jq -r '.[].tag_name' | grep '^nym-vpn-cli-v' | sort -Vr | head -n 1 | awk -F'-v' '{print $NF}')
|
||||
|
||||
echo "${current_cli_version}"
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
release_url="https://api.github.com/repos/nymtech/nym-vpn-client/releases"
|
||||
version=$(curl -s $release_url | jq -r '.[].tag_name' | grep '^nym-vpn-desktop-v' | sort -Vr | head -n 1 | awk -F'-v' '{print $NF}')
|
||||
|
||||
echo "${version}"
|
||||
@@ -14,7 +14,7 @@ One of the main aims of NymVPN alpha release is testing; your results will help
|
||||
|
||||
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
1. Create a directory called `nym-vpn-tests` and copy your `nym-vpn-cli` binary ([download here]({{nym_vpn_latest_binary_url}}))
|
||||
1. Create a directory called `nym-vpn-tests` and copy your `nym-vpn-cli` binary ([download here]({{nym_vpn_releases}}))
|
||||
2. Copy or download [`sandbox.env`](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) testnet config file to the same directory
|
||||
```sh
|
||||
curl -o sandbox.env -L https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env
|
||||
|
||||
@@ -46,6 +46,8 @@ If you are running NymVPN on mac OS for the first time, you may see this alert m
|
||||
|
||||
3. Possibly you may have to confirm again upon running the application
|
||||
|
||||
<!--
|
||||
|
||||
#### Missing `jq` error
|
||||
|
||||
In case of missing `jq` on Linux (Debian) install it with:
|
||||
@@ -84,3 +86,4 @@ NEW_ENDPOINT="http://localhost:8000/data.json"
|
||||
python3 -m http.server 8000
|
||||
```
|
||||
6. Continue with the steps listed in [testing section](testing.md)
|
||||
-->
|
||||
|
||||
@@ -6,7 +6,9 @@ use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
// Re-export request types
|
||||
pub use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond};
|
||||
pub use nym_explorer_api_requests::{
|
||||
Location, PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond,
|
||||
};
|
||||
|
||||
// Paths
|
||||
const API_VERSION: &str = "v1";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { Delegation, PendingEpochEvent, PendingEpochEventKind } from '@nymproject/contract-clients/Mixnet.types';
|
||||
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate';
|
||||
import { Delegation, PendingEpochEventKind } from '@nymproject/contract-clients/Mixnet.types';
|
||||
import { useWalletContext } from './wallet';
|
||||
import { useMainContext } from './main';
|
||||
|
||||
@@ -70,7 +70,7 @@ export const DelegationsProvider = ({ children }: { children: React.ReactNode })
|
||||
const response = await nymQueryClient.getPendingEpochEvents({});
|
||||
const pendingEvents: PendingEvent[] = [];
|
||||
|
||||
response.events.forEach((e) => {
|
||||
response.events.forEach((e: PendingEpochEvent) => {
|
||||
const event = getEventsByAddress(e.event.kind, address);
|
||||
if (event) {
|
||||
pendingEvents.push(event);
|
||||
@@ -113,7 +113,7 @@ export const DelegationsProvider = ({ children }: { children: React.ReactNode })
|
||||
|
||||
// Get rewards for each delegation
|
||||
const rewardsResponse = await Promise.all(
|
||||
delegationsResponse.delegations.map((d) => handleGetDelegationRewards(d.mix_id)),
|
||||
delegationsResponse.delegations.map((d: Delegation) => handleGetDelegationRewards(d.mix_id)),
|
||||
);
|
||||
|
||||
// Get all pending events
|
||||
@@ -122,10 +122,10 @@ export const DelegationsProvider = ({ children }: { children: React.ReactNode })
|
||||
const delegationsWithRewards: DelegationWithRewards[] = [];
|
||||
|
||||
// Merge delegations with rewards and pending events
|
||||
delegationsResponse.delegations.forEach((d, index) => {
|
||||
delegationsResponse.delegations.forEach((d: Delegation, index: number) => {
|
||||
delegationsWithRewards.push({
|
||||
...d,
|
||||
pending: pendingEvents?.find((e) => (e?.mixId === d.mix_id ? e.kind : undefined)),
|
||||
pending: pendingEvents?.find((e: PendingEvent) => (e?.mixId === d.mix_id ? e.kind : undefined)),
|
||||
identityKey: mixnodes?.find((m) => m.mix_id === d.mix_id)?.mix_node.identity_key || '',
|
||||
rewards: rewardsResponse[index]?.amount_earned_detailed || '0',
|
||||
});
|
||||
@@ -133,7 +133,7 @@ export const DelegationsProvider = ({ children }: { children: React.ReactNode })
|
||||
|
||||
// Add pending events that are not in the delegations list
|
||||
pendingEvents?.forEach((e) => {
|
||||
if (e && !delegationsWithRewards.find((d) => d.mix_id === e.mixId)) {
|
||||
if (e && !delegationsWithRewards.find((d: DelegationWithRewards) => d.mix_id === e.mixId)) {
|
||||
delegationsWithRewards.push({
|
||||
mix_id: e.mixId,
|
||||
height: 0,
|
||||
@@ -165,7 +165,7 @@ export const DelegationsProvider = ({ children }: { children: React.ReactNode })
|
||||
try {
|
||||
const tx = await nymClient?.delegateToMixnode({ mixId }, fee, 'Delegation from Nym Explorer', uNymFunds);
|
||||
|
||||
return tx;
|
||||
return tx as unknown as ExecuteResult;
|
||||
} catch (e) {
|
||||
console.error('Failed to delegate to mixnode', e);
|
||||
throw e;
|
||||
@@ -175,7 +175,7 @@ export const DelegationsProvider = ({ children }: { children: React.ReactNode })
|
||||
const handleUndelegate = async (mixId: number) => {
|
||||
const tx = await nymClient?.undelegateFromMixnode({ mixId }, fee);
|
||||
|
||||
return tx;
|
||||
return tx as unknown as ExecuteResult;
|
||||
};
|
||||
|
||||
const contextValue: DelegationsState = useMemo(
|
||||
|
||||
@@ -179,7 +179,9 @@ const DelegationsPage = () => {
|
||||
{...confirmationModalProps}
|
||||
open={Boolean(confirmationModalProps)}
|
||||
onClose={async () => {
|
||||
await handleGetDelegations();
|
||||
if (confirmationModalProps.status === 'success') {
|
||||
await handleGetDelegations();
|
||||
}
|
||||
setConfirmationModalProps(undefined);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -19,6 +19,12 @@ module.exports = mergeWithRules({
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.m?js/,
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader'],
|
||||
|
||||
Generated
+1
-10
@@ -5516,7 +5516,6 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots 0.25.4",
|
||||
"winreg 0.50.0",
|
||||
]
|
||||
|
||||
@@ -6483,7 +6482,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio-stream",
|
||||
"url",
|
||||
"webpki-roots 0.22.6",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7272,7 +7271,6 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"rustls 0.21.7",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
]
|
||||
@@ -7454,7 +7452,6 @@ dependencies = [
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"rustls 0.21.7",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"url",
|
||||
@@ -7898,12 +7895,6 @@ dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com"
|
||||
version = "0.19.1"
|
||||
|
||||
@@ -39,7 +39,7 @@ pub async fn handle_task_status(
|
||||
window: &tauri::Window,
|
||||
) {
|
||||
match task_status {
|
||||
TaskStatus::Ready => {
|
||||
TaskStatus::Ready | TaskStatus::ReadyWithGateway(_) => {
|
||||
{
|
||||
let mut state_w = state.write().await;
|
||||
state_w.mark_connected(window);
|
||||
|
||||
Generated
-7
@@ -4444,7 +4444,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
@@ -6272,12 +6271,6 @@ dependencies = [
|
||||
"system-deps 6.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com"
|
||||
version = "0.19.1"
|
||||
|
||||
Generated
+2
-2
@@ -2300,9 +2300,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.10"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
|
||||
@@ -650,7 +650,9 @@ where
|
||||
|
||||
// TODO: more graceful handling here, surely both variants should work... I think?
|
||||
if let TaskHandle::Internal(task_manager) = &mut started_client.task_handle {
|
||||
task_manager.start_status_listener(socks5_status_tx).await;
|
||||
task_manager
|
||||
.start_status_listener(socks5_status_tx, TaskStatus::Ready)
|
||||
.await;
|
||||
match socks5_status_rx
|
||||
.next()
|
||||
.await
|
||||
@@ -661,6 +663,9 @@ where
|
||||
TaskStatus::Ready => {
|
||||
log::debug!("Socks5 connected");
|
||||
}
|
||||
TaskStatus::ReadyWithGateway(gateway) => {
|
||||
log::debug!("Socks5 connected to {gateway}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::new_unsupported(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { FC } from 'react';
|
||||
import { ChainProvider, useChain } from '@cosmos-kit/react';
|
||||
import { assets, chains } from 'chain-registry';
|
||||
import { wallets as keplr } from '@cosmos-kit/keplr';
|
||||
import { wallets as keplr } from '@cosmos-kit/keplr-extension';
|
||||
import { wallets as ledger } from '@cosmos-kit/ledger';
|
||||
import Button from '@mui/material/Button';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
|
||||
@@ -12,16 +12,17 @@
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/amino": "^0.31.1",
|
||||
"@cosmjs/amino": "^0.32.2",
|
||||
"@cosmjs/cosmwasm-launchpad": "^0.25.6",
|
||||
"@cosmjs/cosmwasm-stargate": "^0.31.1",
|
||||
"@cosmjs/encoding": "^0.31.1",
|
||||
"@cosmjs/proto-signing": "^0.31.1",
|
||||
"@cosmjs/stargate": "^0.31.0",
|
||||
"@cosmos-kit/core": "^2.6.2",
|
||||
"@cosmos-kit/keplr": "^2.3.9",
|
||||
"@cosmos-kit/ledger": "^2.4.3",
|
||||
"@cosmos-kit/react": "^2.8.0",
|
||||
"@cosmjs/cosmwasm-stargate": "^0.32.2",
|
||||
"@cosmjs/encoding": "^0.32.2",
|
||||
"@cosmjs/proto-signing": "^0.32.2",
|
||||
"@cosmjs/stargate": "^0.32.2",
|
||||
"@cosmos-kit/core": "^2.8.9",
|
||||
"@cosmos-kit/keplr": "^2.6.9",
|
||||
"@cosmos-kit/keplr-extension": "^2.7.9",
|
||||
"@cosmos-kit/ledger": "^2.6.9",
|
||||
"@cosmos-kit/react": "^2.10.11",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@interchain-ui/react": "^1.8.0",
|
||||
@@ -32,7 +33,7 @@
|
||||
"@nymproject/mix-fetch-full-fat": ">=1.2.4-rc.2 || ^1",
|
||||
"@nymproject/sdk-full-fat": ">=1.2.4-rc.2 || ^1",
|
||||
"chain-registry": "^1.19.0",
|
||||
"cosmjs-types": "^0.8.0",
|
||||
"cosmjs-types": "^0.9.0",
|
||||
"next": "^13.4.19",
|
||||
"nextra": "latest",
|
||||
"nextra-theme-docs": "latest",
|
||||
|
||||
@@ -32,7 +32,6 @@ If you're unsure where to start, the following set of questions should help you
|
||||
|
||||
### Use one of our standalone Nym clients
|
||||
If you've answered 'No' to all of the above, you may need to use one of our [standalone clients](https://nymtech.net/docs/clients/overview.html). 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 construct Sphinx packets. While setting up those, and depending on your usecase, you may need to set-up and run a Service Provider. Read below the "How to deal with Service Providers" section for more details.
|
||||
You can find more information about the different standalone clients and the ways to interact with them [in this page](https://nymtech.net/developers/integrations/mixnet-integration.html).
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -621,10 +621,10 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@nymproject/mix-fetch-wasm-node@>=1.2.0-rc.10 || ^1":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@nymproject/mix-fetch-wasm-node/-/mix-fetch-wasm-node-1.2.0.tgz#76439db4eb5fd4b95dcf6b6883cb5a059aeb5ad2"
|
||||
integrity sha512-vdEO4WfY1ql+DXIMR4nHvIlTB9tzhALiVjzbbf7UBgrQLxPSFTD2oGwGOVfgpNvXv0F92rDj3AHRommKKGa5pw==
|
||||
"@nymproject/mix-fetch-wasm-node@>=1.2.4-rc.2 || ^1":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@nymproject/mix-fetch-wasm-node/-/mix-fetch-wasm-node-1.2.3.tgz#f600df714782e6eb691faa14683e44c2507a8e53"
|
||||
integrity sha512-J9mj52WSpsGpuCeW65zEj8RWJ3GvWG0VqVWIDD6w1RK4SesXiGb7ghD7F1rChRMlSbl9rP9reYkmAHz63Sb6Cw==
|
||||
|
||||
"@rollup/plugin-commonjs@^24.0.1":
|
||||
version "24.1.0"
|
||||
@@ -1648,13 +1648,14 @@ es-to-primitive@^1.2.1:
|
||||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.2"
|
||||
|
||||
es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.61, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
|
||||
version "0.10.62"
|
||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5"
|
||||
integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==
|
||||
es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.61, es5-ext@^0.10.62, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
|
||||
version "0.10.64"
|
||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714"
|
||||
integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==
|
||||
dependencies:
|
||||
es6-iterator "^2.0.3"
|
||||
es6-symbol "^3.1.3"
|
||||
esniff "^2.0.1"
|
||||
next-tick "^1.1.0"
|
||||
|
||||
es6-iterator@^2.0.3:
|
||||
@@ -1887,6 +1888,16 @@ eslint@^8.10.0:
|
||||
strip-ansi "^6.0.1"
|
||||
text-table "^0.2.0"
|
||||
|
||||
esniff@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308"
|
||||
integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==
|
||||
dependencies:
|
||||
d "^1.0.1"
|
||||
es5-ext "^0.10.62"
|
||||
event-emitter "^0.3.5"
|
||||
type "^2.7.2"
|
||||
|
||||
espree@^9.6.0, espree@^9.6.1:
|
||||
version "9.6.1"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
|
||||
|
||||
@@ -65,46 +65,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
||||
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.0":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
|
||||
integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
|
||||
dependencies:
|
||||
"@jridgewell/set-array" "^1.0.1"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@jridgewell/resolve-uri@^3.1.0":
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
|
||||
integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
|
||||
|
||||
"@jridgewell/set-array@^1.0.1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
|
||||
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
|
||||
|
||||
"@jridgewell/source-map@^0.3.3":
|
||||
version "0.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91"
|
||||
integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15":
|
||||
"@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.15":
|
||||
version "1.4.15"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
||||
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||
|
||||
"@jridgewell/trace-mapping@^0.3.9":
|
||||
version "0.3.19"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811"
|
||||
integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
@@ -126,10 +91,10 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@nymproject/nym-client-wasm-node@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@nymproject/nym-client-wasm-node/-/nym-client-wasm-node-1.2.0.tgz#46abe6b7535e80107d837bc6cc47edc1734aa773"
|
||||
integrity sha512-L2hvMuudTQJgS/ZGGCSCD2oroccf7jWYXrq/E0Qqu0IxLlyjnTJ1xWWwXzUuwzBs+V5YZb0VIdB0kAovGLA+VA==
|
||||
"@nymproject/nym-client-wasm-node@>=1.2.4-rc.2 || ^1":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@nymproject/nym-client-wasm-node/-/nym-client-wasm-node-1.2.3.tgz#e75e234714494fafffb86115cdee6759dcede366"
|
||||
integrity sha512-fUGld4MJOgnvyqk5/KIpFePIXn8Nsl/7T/jh9a9WdTWntECnnJ/DBqoO+6NzDkyXaLYhByqR7reO8ZApNR0YCw==
|
||||
|
||||
"@rollup/plugin-commonjs@^24.0.1":
|
||||
version "24.1.0"
|
||||
@@ -143,14 +108,12 @@
|
||||
is-reference "1.2.1"
|
||||
magic-string "^0.27.0"
|
||||
|
||||
"@rollup/plugin-inject@^5.0.3":
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz#616f3a73fe075765f91c5bec90176608bed277a3"
|
||||
integrity sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==
|
||||
"@rollup/plugin-json@^6.0.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-6.1.0.tgz#fbe784e29682e9bb6dee28ea75a1a83702e7b805"
|
||||
integrity sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^5.0.1"
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.30.3"
|
||||
"@rollup/pluginutils" "^5.1.0"
|
||||
|
||||
"@rollup/plugin-node-resolve@^15.0.1":
|
||||
version "15.2.3"
|
||||
@@ -172,15 +135,6 @@
|
||||
"@rollup/pluginutils" "^5.0.1"
|
||||
magic-string "^0.30.3"
|
||||
|
||||
"@rollup/plugin-terser@^0.2.1":
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-terser/-/plugin-terser-0.2.1.tgz#dcf0b163216dafb64611b170a7667e76a7f03d2b"
|
||||
integrity sha512-hV52c8Oo6/cXZZxVVoRNBb4zh+EKSHS4I1sedWV5pf0O+hTLSkrf6w86/V0AZutYtwBguB6HLKwz89WDBfwGOA==
|
||||
dependencies:
|
||||
serialize-javascript "^6.0.0"
|
||||
smob "^0.0.6"
|
||||
terser "^5.15.1"
|
||||
|
||||
"@rollup/plugin-typescript@^10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-10.0.1.tgz#270b515b116ea28320e6bb62451c4767d49072d6"
|
||||
@@ -223,6 +177,15 @@
|
||||
estree-walker "^2.0.2"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
"@rollup/pluginutils@^5.1.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0"
|
||||
integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==
|
||||
dependencies:
|
||||
"@types/estree" "^1.0.0"
|
||||
estree-walker "^2.0.2"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
"@types/estree@*", "@types/estree@^1.0.0":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.2.tgz#ff02bc3dc8317cd668dfec247b750ba1f1d62453"
|
||||
@@ -352,7 +315,7 @@ acorn-jsx@^5.3.2:
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||
|
||||
acorn@^8.8.2, acorn@^8.9.0:
|
||||
acorn@^8.9.0:
|
||||
version "8.10.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
|
||||
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
|
||||
@@ -549,11 +512,6 @@ braces@^3.0.2, braces@~3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||
|
||||
builtin-modules@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
|
||||
@@ -623,11 +581,6 @@ comlink@^4.3.1:
|
||||
resolved "https://registry.yarnpkg.com/comlink/-/comlink-4.4.1.tgz#e568b8e86410b809e8600eb2cf40c189371ef981"
|
||||
integrity sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==
|
||||
|
||||
commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@~9.4.0:
|
||||
version "9.4.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd"
|
||||
@@ -880,13 +833,14 @@ es-to-primitive@^1.2.1:
|
||||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.2"
|
||||
|
||||
es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.61, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
|
||||
version "0.10.62"
|
||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5"
|
||||
integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==
|
||||
es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.61, es5-ext@^0.10.62, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
|
||||
version "0.10.64"
|
||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714"
|
||||
integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==
|
||||
dependencies:
|
||||
es6-iterator "^2.0.3"
|
||||
es6-symbol "^3.1.3"
|
||||
esniff "^2.0.1"
|
||||
next-tick "^1.1.0"
|
||||
|
||||
es6-iterator@^2.0.3:
|
||||
@@ -1131,6 +1085,16 @@ eslint@^8.10.0:
|
||||
strip-ansi "^6.0.1"
|
||||
text-table "^0.2.0"
|
||||
|
||||
esniff@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308"
|
||||
integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==
|
||||
dependencies:
|
||||
d "^1.0.1"
|
||||
es5-ext "^0.10.62"
|
||||
event-emitter "^0.3.5"
|
||||
type "^2.7.2"
|
||||
|
||||
espree@^9.6.0, espree@^9.6.1:
|
||||
version "9.6.1"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
|
||||
@@ -2243,13 +2207,6 @@ queue-microtask@^1.2.2:
|
||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||
|
||||
randombytes@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
|
||||
dependencies:
|
||||
safe-buffer "^5.1.0"
|
||||
|
||||
range-parser@~1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||
@@ -2400,11 +2357,6 @@ safe-array-concat@^1.0.1:
|
||||
has-symbols "^1.0.3"
|
||||
isarray "^2.0.5"
|
||||
|
||||
safe-buffer@^5.1.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
safe-regex-test@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295"
|
||||
@@ -2445,13 +2397,6 @@ send@0.18.0:
|
||||
range-parser "~1.2.1"
|
||||
statuses "2.0.1"
|
||||
|
||||
serialize-javascript@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c"
|
||||
integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serve-static@~1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
|
||||
@@ -2519,24 +2464,6 @@ slash@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
||||
|
||||
smob@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/smob/-/smob-0.0.6.tgz#09b268fea916158a2781c152044c6155adbb8aa1"
|
||||
integrity sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==
|
||||
|
||||
source-map-support@~0.5.20:
|
||||
version "0.5.21"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
|
||||
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
source-map@^0.6.0:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@^0.7.4:
|
||||
version "0.7.4"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
|
||||
@@ -2640,16 +2567,6 @@ tapable@^2.2.0:
|
||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
|
||||
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
|
||||
|
||||
terser@^5.15.1:
|
||||
version "5.22.0"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.22.0.tgz#4f18103f84c5c9437aafb7a14918273310a8a49d"
|
||||
integrity sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==
|
||||
dependencies:
|
||||
"@jridgewell/source-map" "^0.3.3"
|
||||
acorn "^8.8.2"
|
||||
commander "^2.20.0"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
text-table@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
|
||||
@@ -600,9 +600,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
|
||||
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ=="
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
@@ -976,9 +976,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/socks/node_modules/ip": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
|
||||
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
|
||||
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ=="
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::time::Duration;
|
||||
|
||||
// The interface used to route traffic
|
||||
pub const TUN_BASE_NAME: &str = "nymtun";
|
||||
pub const TUN_DEVICE_ADDRESS: &str = "10.0.0.1";
|
||||
pub const TUN_DEVICE_NETMASK: &str = "255.255.255.0";
|
||||
pub const TUN_DEVICE_ADDRESS_V4: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1);
|
||||
pub const TUN_DEVICE_NETMASK_V4: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);
|
||||
pub const TUN_DEVICE_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0x2001, 0xdb8, 0xa160, 0, 0, 0, 0, 0x1); // 2001:db8:a160::1
|
||||
|
||||
pub const TUN_DEVICE_NETMASK_V6: &str = "120";
|
||||
|
||||
// We routinely check if any clients needs to be disconnected at this interval
|
||||
pub(crate) const DISCONNECT_TIMER_INTERVAL: Duration = Duration::from_secs(10);
|
||||
|
||||
@@ -11,6 +11,10 @@ pub enum IpPacketRouterError {
|
||||
#[error("client-core error: {0}")]
|
||||
ClientCoreError(#[from] ClientCoreError),
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[error("tun device error: {0}")]
|
||||
TunDeviceError(#[from] nym_tun::tun_device::TunDeviceError),
|
||||
|
||||
#[error("failed to load configuration file: {0}")]
|
||||
FailedToLoadConfig(String),
|
||||
|
||||
|
||||
@@ -133,11 +133,13 @@ impl IpPacketRouter {
|
||||
// Create the TUN device that we interact with the rest of the world with
|
||||
let config = nym_tun::tun_device::TunDeviceConfig {
|
||||
base_name: crate::constants::TUN_BASE_NAME.to_string(),
|
||||
ip: crate::constants::TUN_DEVICE_ADDRESS.parse().unwrap(),
|
||||
netmask: crate::constants::TUN_DEVICE_NETMASK.parse().unwrap(),
|
||||
ipv4: crate::constants::TUN_DEVICE_ADDRESS_V4,
|
||||
netmaskv4: crate::constants::TUN_DEVICE_NETMASK_V4,
|
||||
ipv6: crate::constants::TUN_DEVICE_ADDRESS_V6,
|
||||
netmaskv6: crate::constants::TUN_DEVICE_NETMASK_V6.to_string(),
|
||||
};
|
||||
let (tun_reader, tun_writer) =
|
||||
tokio::io::split(nym_tun::tun_device::TunDevice::new_device_only(config));
|
||||
tokio::io::split(nym_tun::tun_device::TunDevice::new_device_only(config)?);
|
||||
|
||||
// Channel used by the IpPacketRouter to signal connected and disconnected clients to the
|
||||
// TunListener
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, net::SocketAddr};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::StreamExt;
|
||||
use nym_ip_packet_requests::response::InfoLevel;
|
||||
use nym_ip_packet_requests::{
|
||||
codec::MultiIpPacketCodec,
|
||||
request::{IpPacketRequest, IpPacketRequestData},
|
||||
response::{
|
||||
DynamicConnectFailureReason, ErrorResponseReply, IpPacketResponse,
|
||||
DynamicConnectFailureReason, InfoResponseReply, IpPacketResponse,
|
||||
StaticConnectFailureReason,
|
||||
},
|
||||
IpPair,
|
||||
};
|
||||
use nym_sdk::mixnet::{MixnetMessageSender, Recipient};
|
||||
use nym_sphinx::receiver::ReconstructedMessage;
|
||||
@@ -19,6 +20,7 @@ use nym_task::TaskHandle;
|
||||
use tap::TapFallible;
|
||||
#[cfg(target_os = "linux")]
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio_util::codec::Decoder;
|
||||
|
||||
use crate::{
|
||||
@@ -37,7 +39,8 @@ use crate::{
|
||||
|
||||
pub(crate) struct ConnectedClients {
|
||||
// The set of connected clients
|
||||
clients: HashMap<IpAddr, ConnectedClient>,
|
||||
clients_ipv4_mapping: HashMap<Ipv4Addr, ConnectedClient>,
|
||||
clients_ipv6_mapping: HashMap<Ipv6Addr, ConnectedClient>,
|
||||
|
||||
// Notify the tun listener when a new client connects or disconnects
|
||||
tun_listener_connected_client_tx: tokio::sync::mpsc::UnboundedSender<ConnectedClientEvent>,
|
||||
@@ -48,46 +51,59 @@ impl ConnectedClients {
|
||||
let (connected_client_tx, connected_client_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
(
|
||||
Self {
|
||||
clients: Default::default(),
|
||||
clients_ipv4_mapping: Default::default(),
|
||||
clients_ipv6_mapping: Default::default(),
|
||||
tun_listener_connected_client_tx: connected_client_tx,
|
||||
},
|
||||
tun_listener::ConnectedClientsListener::new(connected_client_rx),
|
||||
)
|
||||
}
|
||||
|
||||
fn is_ip_connected(&self, ip: &IpAddr) -> bool {
|
||||
self.clients.contains_key(ip)
|
||||
fn is_ip_connected(&self, ips: &IpPair) -> bool {
|
||||
self.clients_ipv4_mapping.contains_key(&ips.ipv4)
|
||||
|| self.clients_ipv6_mapping.contains_key(&ips.ipv6)
|
||||
}
|
||||
|
||||
fn get_client_from_ip_mut(&mut self, ip: &IpAddr) -> Option<&mut ConnectedClient> {
|
||||
self.clients.get_mut(ip)
|
||||
match ip {
|
||||
IpAddr::V4(ip) => self.clients_ipv4_mapping.get_mut(ip),
|
||||
IpAddr::V6(ip) => self.clients_ipv6_mapping.get_mut(ip),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_nym_address_connected(&self, nym_address: &Recipient) -> bool {
|
||||
self.clients
|
||||
self.clients_ipv4_mapping
|
||||
.values()
|
||||
.any(|client| client.nym_address == *nym_address)
|
||||
}
|
||||
|
||||
fn lookup_ip_from_nym_address(&self, nym_address: &Recipient) -> Option<IpAddr> {
|
||||
self.clients.iter().find_map(|(ip, client)| {
|
||||
if client.nym_address == *nym_address {
|
||||
Some(*ip)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
fn lookup_ip_from_nym_address(&self, nym_address: &Recipient) -> Option<IpPair> {
|
||||
self.clients_ipv4_mapping
|
||||
.iter()
|
||||
.find_map(|(ipv4, connected_client)| {
|
||||
if connected_client.nym_address == *nym_address {
|
||||
Some(IpPair::new(*ipv4, connected_client.ipv6))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn lookup_client_from_nym_address(&self, nym_address: &Recipient) -> Option<&ConnectedClient> {
|
||||
self.clients
|
||||
.values()
|
||||
.find(|client| client.nym_address == *nym_address)
|
||||
self.clients_ipv4_mapping
|
||||
.iter()
|
||||
.find_map(|(_, connected_client)| {
|
||||
if connected_client.nym_address == *nym_address {
|
||||
Some(connected_client)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn connect(
|
||||
&mut self,
|
||||
ip: IpAddr,
|
||||
ips: IpPair,
|
||||
nym_address: Recipient,
|
||||
mix_hops: Option<u8>,
|
||||
forward_from_tun_tx: tokio::sync::mpsc::UnboundedSender<Vec<u8>>,
|
||||
@@ -96,21 +112,25 @@ impl ConnectedClients {
|
||||
) {
|
||||
// The map of connected clients that the mixnet listener keeps track of. It monitors
|
||||
// activity and disconnects clients that have been inactive for too long.
|
||||
self.clients.insert(
|
||||
ip,
|
||||
ConnectedClient {
|
||||
let client = ConnectedClient {
|
||||
nym_address,
|
||||
ipv6: ips.ipv6,
|
||||
mix_hops,
|
||||
last_activity: Arc::new(RwLock::new(std::time::Instant::now())),
|
||||
_close_tx: Arc::new(CloseTx {
|
||||
nym_address,
|
||||
mix_hops,
|
||||
last_activity: std::time::Instant::now(),
|
||||
close_tx: Some(close_tx),
|
||||
handle,
|
||||
},
|
||||
);
|
||||
inner: Some(close_tx),
|
||||
}),
|
||||
handle: Arc::new(handle),
|
||||
};
|
||||
log::info!("Inserting {} and {}", ips.ipv4, ips.ipv6);
|
||||
self.clients_ipv4_mapping.insert(ips.ipv4, client.clone());
|
||||
self.clients_ipv6_mapping.insert(ips.ipv6, client);
|
||||
// Send the connected client info to the tun listener, which will use it to forward packets
|
||||
// to the connected client handler.
|
||||
self.tun_listener_connected_client_tx
|
||||
.send(ConnectedClientEvent::Connect(Box::new(ConnectEvent {
|
||||
ip,
|
||||
ips,
|
||||
forward_from_tun_tx,
|
||||
})))
|
||||
.tap_err(|err| {
|
||||
@@ -119,9 +139,9 @@ impl ConnectedClients {
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn update_activity(&mut self, ip: &IpAddr) -> Result<()> {
|
||||
if let Some(client) = self.clients.get_mut(ip) {
|
||||
client.last_activity = std::time::Instant::now();
|
||||
async fn update_activity(&mut self, ips: &IpPair) -> Result<()> {
|
||||
if let Some(client) = self.clients_ipv4_mapping.get(&ips.ipv4) {
|
||||
*client.last_activity.write().await = std::time::Instant::now();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(IpPacketRouterError::FailedToUpdateClientActivity)
|
||||
@@ -129,12 +149,15 @@ impl ConnectedClients {
|
||||
}
|
||||
|
||||
// Identify connected client handlers that have stopped without being told to stop
|
||||
fn get_finished_client_handlers(&mut self) -> Vec<(IpAddr, Recipient)> {
|
||||
self.clients
|
||||
fn get_finished_client_handlers(&mut self) -> Vec<(IpPair, Recipient)> {
|
||||
self.clients_ipv4_mapping
|
||||
.iter_mut()
|
||||
.filter_map(|(ip, client)| {
|
||||
if client.handle.is_finished() {
|
||||
Some((*ip, client.nym_address))
|
||||
.filter_map(|(ip, connected_client)| {
|
||||
if connected_client.handle.is_finished() {
|
||||
Some((
|
||||
IpPair::new(*ip, connected_client.ipv6),
|
||||
connected_client.nym_address,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -142,26 +165,29 @@ impl ConnectedClients {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_inactive_clients(&mut self) -> Vec<(IpAddr, Recipient)> {
|
||||
async fn get_inactive_clients(&mut self) -> Vec<(IpPair, Recipient)> {
|
||||
let now = std::time::Instant::now();
|
||||
self.clients
|
||||
.iter()
|
||||
.filter_map(|(ip, client)| {
|
||||
if now.duration_since(client.last_activity) > CLIENT_MIXNET_INACTIVITY_TIMEOUT {
|
||||
Some((*ip, client.nym_address))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
let mut ret = vec![];
|
||||
for (ip, connected_client) in self.clients_ipv4_mapping.iter() {
|
||||
if now.duration_since(*connected_client.last_activity.read().await)
|
||||
> CLIENT_MIXNET_INACTIVITY_TIMEOUT
|
||||
{
|
||||
ret.push((
|
||||
IpPair::new(*ip, connected_client.ipv6),
|
||||
connected_client.nym_address,
|
||||
))
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn disconnect_stopped_client_handlers(&mut self, stopped_clients: Vec<(IpAddr, Recipient)>) {
|
||||
for (ip, _) in &stopped_clients {
|
||||
log::info!("Disconnect stopped client: {ip}");
|
||||
self.clients.remove(ip);
|
||||
fn disconnect_stopped_client_handlers(&mut self, stopped_clients: Vec<(IpPair, Recipient)>) {
|
||||
for (ips, _) in &stopped_clients {
|
||||
log::info!("Disconnect stopped client: {ips}");
|
||||
self.clients_ipv4_mapping.remove(&ips.ipv4);
|
||||
self.clients_ipv6_mapping.remove(&ips.ipv6);
|
||||
self.tun_listener_connected_client_tx
|
||||
.send(ConnectedClientEvent::Disconnect(DisconnectEvent(*ip)))
|
||||
.send(ConnectedClientEvent::Disconnect(DisconnectEvent(*ips)))
|
||||
.tap_err(|err| {
|
||||
log::error!("Failed to send disconnect event: {err}");
|
||||
})
|
||||
@@ -169,12 +195,13 @@ impl ConnectedClients {
|
||||
}
|
||||
}
|
||||
|
||||
fn disconnect_inactive_clients(&mut self, inactive_clients: Vec<(IpAddr, Recipient)>) {
|
||||
for (ip, _) in &inactive_clients {
|
||||
log::info!("Disconnect inactive client: {ip}");
|
||||
self.clients.remove(ip);
|
||||
fn disconnect_inactive_clients(&mut self, inactive_clients: Vec<(IpPair, Recipient)>) {
|
||||
for (ips, _) in &inactive_clients {
|
||||
log::info!("Disconnect inactive client: {ips}");
|
||||
self.clients_ipv4_mapping.remove(&ips.ipv4);
|
||||
self.clients_ipv6_mapping.remove(&ips.ipv6);
|
||||
self.tun_listener_connected_client_tx
|
||||
.send(ConnectedClientEvent::Disconnect(DisconnectEvent(*ip)))
|
||||
.send(ConnectedClientEvent::Disconnect(DisconnectEvent(*ips)))
|
||||
.tap_err(|err| {
|
||||
log::error!("Failed to send disconnect event: {err}");
|
||||
})
|
||||
@@ -182,40 +209,49 @@ impl ConnectedClients {
|
||||
}
|
||||
}
|
||||
|
||||
fn find_new_ip(&self) -> Option<IpAddr> {
|
||||
generate_new_ip::find_new_ip(&self.clients)
|
||||
fn find_new_ip(&self) -> Option<IpPair> {
|
||||
generate_new_ip::find_new_ips(&self.clients_ipv4_mapping, &self.clients_ipv6_mapping)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CloseTx {
|
||||
pub(crate) nym_address: Recipient,
|
||||
// Send to connected clients listener to stop. This is option only because we need to take
|
||||
// ownership of it when the client is dropped.
|
||||
pub(crate) inner: Option<tokio::sync::oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ConnectedClient {
|
||||
// The nym address of the connected client that we are communicating with on the other side of
|
||||
// the mixnet
|
||||
pub(crate) nym_address: Recipient,
|
||||
|
||||
// The assigned IPv6 address of this client
|
||||
pub(crate) ipv6: Ipv6Addr,
|
||||
|
||||
// Number of mix node hops that the client has requested to use
|
||||
pub(crate) mix_hops: Option<u8>,
|
||||
|
||||
// Keep track of last activity so we can disconnect inactive clients
|
||||
pub(crate) last_activity: std::time::Instant,
|
||||
pub(crate) last_activity: Arc<RwLock<std::time::Instant>>,
|
||||
|
||||
// Send to connected clients listener to stop. This is option only because we need to take
|
||||
// ownership of it when the client is dropped.
|
||||
pub(crate) close_tx: Option<tokio::sync::oneshot::Sender<()>>,
|
||||
pub(crate) _close_tx: Arc<CloseTx>,
|
||||
|
||||
// Handle for the connected client handler
|
||||
pub(crate) handle: tokio::task::JoinHandle<()>,
|
||||
pub(crate) handle: Arc<tokio::task::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl ConnectedClient {
|
||||
fn update_activity(&mut self) {
|
||||
self.last_activity = std::time::Instant::now();
|
||||
async fn update_activity(&self) {
|
||||
*self.last_activity.write().await = std::time::Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ConnectedClient {
|
||||
impl Drop for CloseTx {
|
||||
fn drop(&mut self) {
|
||||
log::debug!("signal to close client: {}", self.nym_address);
|
||||
if let Some(close_tx) = self.close_tx.take() {
|
||||
if let Some(close_tx) = self.inner.take() {
|
||||
close_tx.send(()).ok();
|
||||
}
|
||||
}
|
||||
@@ -259,7 +295,7 @@ impl MixnetListener {
|
||||
);
|
||||
|
||||
let request_id = connect_request.request_id;
|
||||
let requested_ip = connect_request.ip;
|
||||
let requested_ips = connect_request.ips;
|
||||
let reply_to = connect_request.reply_to;
|
||||
let reply_to_hops = connect_request.reply_to_hops;
|
||||
// TODO: add to connect request
|
||||
@@ -267,7 +303,7 @@ impl MixnetListener {
|
||||
// TODO: ignoring reply_to_avg_mix_delays for now
|
||||
|
||||
// Check that the IP is available in the set of connected clients
|
||||
let is_ip_taken = self.connected_clients.is_ip_connected(&requested_ip);
|
||||
let is_ip_taken = self.connected_clients.is_ip_connected(&requested_ips);
|
||||
|
||||
// Check that the nym address isn't already registered
|
||||
let is_nym_address_taken = self.connected_clients.is_nym_address_connected(&reply_to);
|
||||
@@ -277,7 +313,8 @@ impl MixnetListener {
|
||||
log::info!("Connecting an already connected client");
|
||||
if self
|
||||
.connected_clients
|
||||
.update_activity(&requested_ip)
|
||||
.update_activity(&requested_ips)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Failed to update activity for client");
|
||||
@@ -300,7 +337,7 @@ impl MixnetListener {
|
||||
|
||||
// Register the new client in the set of connected clients
|
||||
self.connected_clients.connect(
|
||||
requested_ip,
|
||||
requested_ips,
|
||||
reply_to,
|
||||
reply_to_hops,
|
||||
forward_from_tun_tx,
|
||||
@@ -350,11 +387,12 @@ impl MixnetListener {
|
||||
// TODO: this is problematic. Until we sign connect requests this means you can spam people
|
||||
// with return traffic
|
||||
|
||||
if let Some(existing_ip) = self.connected_clients.lookup_ip_from_nym_address(&reply_to) {
|
||||
if let Some(existing_ips) = self.connected_clients.lookup_ip_from_nym_address(&reply_to) {
|
||||
log::info!("Found existing client for nym address");
|
||||
if self
|
||||
.connected_clients
|
||||
.update_activity(&existing_ip)
|
||||
.update_activity(&existing_ips)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Failed to update activity for client");
|
||||
@@ -362,11 +400,11 @@ impl MixnetListener {
|
||||
return Ok(Some(IpPacketResponse::new_dynamic_connect_success(
|
||||
request_id,
|
||||
reply_to,
|
||||
existing_ip,
|
||||
existing_ips,
|
||||
)));
|
||||
}
|
||||
|
||||
let Some(new_ip) = self.connected_clients.find_new_ip() else {
|
||||
let Some(new_ips) = self.connected_clients.find_new_ip() else {
|
||||
log::info!("No available IP address");
|
||||
return Ok(Some(IpPacketResponse::new_dynamic_connect_failure(
|
||||
request_id,
|
||||
@@ -386,7 +424,7 @@ impl MixnetListener {
|
||||
|
||||
// Register the new client in the set of connected clients
|
||||
self.connected_clients.connect(
|
||||
new_ip,
|
||||
new_ips,
|
||||
reply_to,
|
||||
reply_to_hops,
|
||||
forward_from_tun_tx,
|
||||
@@ -394,7 +432,7 @@ impl MixnetListener {
|
||||
handle,
|
||||
);
|
||||
Ok(Some(IpPacketResponse::new_dynamic_connect_success(
|
||||
request_id, reply_to, new_ip,
|
||||
request_id, reply_to, new_ips,
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -428,7 +466,7 @@ impl MixnetListener {
|
||||
|
||||
if let Some(connected_client) = self.connected_clients.get_client_from_ip_mut(&src_addr) {
|
||||
// Keep track of activity so we can disconnect inactive clients
|
||||
connected_client.update_activity();
|
||||
connected_client.update_activity().await;
|
||||
|
||||
// For packets without a port, use 0.
|
||||
let dst = dst.unwrap_or_else(|| SocketAddr::new(dst_addr, 0));
|
||||
@@ -443,11 +481,12 @@ impl MixnetListener {
|
||||
Ok(None)
|
||||
} else {
|
||||
log::info!("Denied filter check: {dst}");
|
||||
Ok(Some(IpPacketResponse::new_data_error_response(
|
||||
Ok(Some(IpPacketResponse::new_data_info_response(
|
||||
connected_client.nym_address,
|
||||
ErrorResponseReply::ExitPolicyFilterCheckFailed {
|
||||
InfoResponseReply::ExitPolicyFilterCheckFailed {
|
||||
dst: dst.to_string(),
|
||||
},
|
||||
InfoLevel::Warn,
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
@@ -539,9 +578,9 @@ impl MixnetListener {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_disconnect_timer(&mut self) {
|
||||
async fn handle_disconnect_timer(&mut self) {
|
||||
let stopped_clients = self.connected_clients.get_finished_client_handlers();
|
||||
let inactive_clients = self.connected_clients.get_inactive_clients();
|
||||
let inactive_clients = self.connected_clients.get_inactive_clients().await;
|
||||
|
||||
// TODO: Send disconnect responses to all disconnected clients
|
||||
//for (ip, nym_address) in stopped_clients.iter().chain(disconnected_clients.iter()) {
|
||||
@@ -571,10 +610,14 @@ impl MixnetListener {
|
||||
})?;
|
||||
|
||||
// We could avoid this lookup if we check this when we create the response.
|
||||
let mix_hops = self
|
||||
let mix_hops = if let Some(c) = self
|
||||
.connected_clients
|
||||
.lookup_client_from_nym_address(recipient)
|
||||
.and_then(|c| c.mix_hops);
|
||||
{
|
||||
c.mix_hops
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let input_message = create_input_message(*recipient, response_packet, mix_hops);
|
||||
self.mixnet_client
|
||||
@@ -613,7 +656,7 @@ impl MixnetListener {
|
||||
log::debug!("IpPacketRouter [main loop]: received shutdown");
|
||||
},
|
||||
_ = disconnect_timer.tick() => {
|
||||
self.handle_disconnect_timer();
|
||||
self.handle_disconnect_timer().await;
|
||||
},
|
||||
msg = self.mixnet_client.next() => {
|
||||
if let Some(msg) = msg {
|
||||
@@ -642,9 +685,9 @@ pub(crate) enum ConnectedClientEvent {
|
||||
Connect(Box<ConnectEvent>),
|
||||
}
|
||||
|
||||
pub(crate) struct DisconnectEvent(pub(crate) IpAddr);
|
||||
pub(crate) struct DisconnectEvent(pub(crate) IpPair);
|
||||
|
||||
pub(crate) struct ConnectEvent {
|
||||
pub(crate) ip: IpAddr,
|
||||
pub(crate) ips: IpPair,
|
||||
pub(crate) forward_from_tun_tx: tokio::sync::mpsc::UnboundedSender<Vec<u8>>,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::{collections::HashMap, net::IpAddr};
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use nym_ip_packet_requests::IpPair;
|
||||
use nym_task::TaskClient;
|
||||
#[cfg(target_os = "linux")]
|
||||
use tokio::io::AsyncReadExt;
|
||||
@@ -15,10 +17,12 @@ use crate::{
|
||||
// It's even ok if this is slightly out of date
|
||||
pub(crate) struct ConnectedClientMirror {
|
||||
pub(crate) forward_from_tun_tx: tokio::sync::mpsc::UnboundedSender<Vec<u8>>,
|
||||
pub(crate) ips: IpPair,
|
||||
}
|
||||
|
||||
pub(crate) struct ConnectedClientsListener {
|
||||
clients: HashMap<IpAddr, ConnectedClientMirror>,
|
||||
clients_ipv4: HashMap<Ipv4Addr, ConnectedClientMirror>,
|
||||
clients_ipv6: HashMap<Ipv6Addr, ConnectedClientMirror>,
|
||||
connected_client_rx:
|
||||
tokio::sync::mpsc::UnboundedReceiver<mixnet_listener::ConnectedClientEvent>,
|
||||
}
|
||||
@@ -30,35 +34,48 @@ impl ConnectedClientsListener {
|
||||
>,
|
||||
) -> Self {
|
||||
ConnectedClientsListener {
|
||||
clients: HashMap::new(),
|
||||
clients_ipv4: HashMap::new(),
|
||||
clients_ipv6: HashMap::new(),
|
||||
connected_client_rx,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, ip: &IpAddr) -> Option<&ConnectedClientMirror> {
|
||||
self.clients.get(ip)
|
||||
match ip {
|
||||
IpAddr::V4(ip) => self.clients_ipv4.get(ip),
|
||||
IpAddr::V6(ip) => self.clients_ipv6.get(ip),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update(&mut self, event: mixnet_listener::ConnectedClientEvent) {
|
||||
match event {
|
||||
mixnet_listener::ConnectedClientEvent::Connect(connected_event) => {
|
||||
let mixnet_listener::ConnectEvent {
|
||||
ip,
|
||||
ips,
|
||||
forward_from_tun_tx,
|
||||
} = *connected_event;
|
||||
log::trace!("Connect client: {ip}");
|
||||
self.clients.insert(
|
||||
ip,
|
||||
log::trace!("Connect client: {ips}");
|
||||
self.clients_ipv4.insert(
|
||||
ips.ipv4,
|
||||
ConnectedClientMirror {
|
||||
forward_from_tun_tx: forward_from_tun_tx.clone(),
|
||||
ips,
|
||||
},
|
||||
);
|
||||
self.clients_ipv6.insert(
|
||||
ips.ipv6,
|
||||
ConnectedClientMirror {
|
||||
forward_from_tun_tx,
|
||||
ips,
|
||||
},
|
||||
);
|
||||
}
|
||||
mixnet_listener::ConnectedClientEvent::Disconnect(
|
||||
mixnet_listener::DisconnectEvent(ip),
|
||||
mixnet_listener::DisconnectEvent(ips),
|
||||
) => {
|
||||
log::trace!("Disconnect client: {ip}");
|
||||
self.clients.remove(&ip);
|
||||
log::trace!("Disconnect client: {ips}");
|
||||
self.clients_ipv4.remove(&ips.ipv4);
|
||||
self.clients_ipv6.remove(&ips.ipv6);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +99,7 @@ impl TunListener {
|
||||
|
||||
if let Some(ConnectedClientMirror {
|
||||
forward_from_tun_tx,
|
||||
ips,
|
||||
}) = self.connected_clients.get(&dst_addr)
|
||||
{
|
||||
let packet = buf[..len].to_vec();
|
||||
@@ -89,7 +107,7 @@ impl TunListener {
|
||||
log::warn!("Failed to forward packet to connected client {dst_addr}: disconnecting it from tun listener");
|
||||
self.connected_clients
|
||||
.update(mixnet_listener::ConnectedClientEvent::Disconnect(
|
||||
mixnet_listener::DisconnectEvent(dst_addr),
|
||||
mixnet_listener::DisconnectEvent(*ips),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,38 +1,52 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
};
|
||||
use nym_ip_packet_requests::IpPair;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::{collections::HashMap, net::Ipv4Addr};
|
||||
|
||||
use crate::{constants::TUN_DEVICE_ADDRESS, mixnet_listener::ConnectedClient};
|
||||
use crate::constants::{TUN_DEVICE_ADDRESS_V4, TUN_DEVICE_ADDRESS_V6};
|
||||
|
||||
// Find an available IP address in self.connected_clients
|
||||
// TODO: make this nicer
|
||||
fn generate_random_ip_within_subnet() -> Ipv4Addr {
|
||||
fn generate_random_ips_within_subnet() -> IpPair {
|
||||
let mut rng = rand::thread_rng();
|
||||
// Generate a random number in the range 1-254
|
||||
let last_octet = rand::Rng::gen_range(&mut rng, 1..=254);
|
||||
Ipv4Addr::new(10, 0, 0, last_octet)
|
||||
let ipv4 = Ipv4Addr::new(10, 0, 0, last_octet);
|
||||
let ipv6 = Ipv6Addr::new(0x2001, 0x0db8, 0xa160, 0, 0, 0, 0, last_octet as u16);
|
||||
IpPair::new(ipv4, ipv6)
|
||||
}
|
||||
|
||||
fn is_ip_taken(
|
||||
connected_clients: &HashMap<IpAddr, ConnectedClient>,
|
||||
tun_ip: Ipv4Addr,
|
||||
ip: Ipv4Addr,
|
||||
fn is_ip_taken<T>(
|
||||
connected_clients_ipv4: &HashMap<Ipv4Addr, T>,
|
||||
connected_clients_ipv6: &HashMap<Ipv6Addr, T>,
|
||||
tun_ips: IpPair,
|
||||
ips: IpPair,
|
||||
) -> bool {
|
||||
connected_clients.contains_key(&ip.into()) || ip == tun_ip
|
||||
connected_clients_ipv4.contains_key(&ips.ipv4)
|
||||
|| connected_clients_ipv6.contains_key(&ips.ipv6)
|
||||
|| ips.ipv4 == tun_ips.ipv4
|
||||
|| ips.ipv6 == tun_ips.ipv6
|
||||
}
|
||||
|
||||
// TODO: brute force approach. We could consider using a more efficient algorithm
|
||||
pub(crate) fn find_new_ip(connected_clients: &HashMap<IpAddr, ConnectedClient>) -> Option<IpAddr> {
|
||||
let mut new_ip = generate_random_ip_within_subnet();
|
||||
pub(crate) fn find_new_ips<T>(
|
||||
connected_clients_ipv4: &HashMap<Ipv4Addr, T>,
|
||||
connected_clients_ipv6: &HashMap<Ipv6Addr, T>,
|
||||
) -> Option<IpPair> {
|
||||
let mut new_ips = generate_random_ips_within_subnet();
|
||||
let mut tries = 0;
|
||||
let tun_ip = TUN_DEVICE_ADDRESS.parse::<Ipv4Addr>().unwrap();
|
||||
while is_ip_taken(connected_clients, tun_ip, new_ip) {
|
||||
new_ip = generate_random_ip_within_subnet();
|
||||
let tun_ips = IpPair::new(TUN_DEVICE_ADDRESS_V4, TUN_DEVICE_ADDRESS_V6);
|
||||
|
||||
while is_ip_taken(
|
||||
connected_clients_ipv4,
|
||||
connected_clients_ipv6,
|
||||
tun_ips,
|
||||
new_ips,
|
||||
) {
|
||||
new_ips = generate_random_ips_within_subnet();
|
||||
tries += 1;
|
||||
if tries > 100 {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(new_ip.into())
|
||||
Some(new_ips)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ nym-statistics-common = { path = "../../common/statistics" }
|
||||
nym-task = { path = "../../common/task" }
|
||||
nym-types = { path = "../../common/types" }
|
||||
nym-exit-policy = { path = "../../common/exit-policy", features = ["client"] }
|
||||
|
||||
nym-id = { path = "../../common/nym-id" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.5.0"
|
||||
|
||||
@@ -4,14 +4,10 @@
|
||||
use crate::cli::try_load_current_config;
|
||||
use crate::error::NetworkRequesterError;
|
||||
use clap::ArgGroup;
|
||||
use log::{error, info};
|
||||
use nym_credential_storage::models::StorableIssuedCredential;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||
use nym_credentials::IssuedBandwidthCredential;
|
||||
|
||||
use nym_id::import_credential;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
|
||||
bs58::decode(raw).into_vec()
|
||||
@@ -33,8 +29,8 @@ pub(crate) struct Args {
|
||||
pub(crate) credential_path: Option<PathBuf>,
|
||||
|
||||
// currently hidden as there exists only a single serialization standard
|
||||
#[clap(long, hide = true, default_value_t = 1)]
|
||||
pub(crate) version: u8,
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) version: Option<u8>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), NetworkRequesterError> {
|
||||
@@ -52,50 +48,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), NetworkRequesterError> {
|
||||
fs::read(args.credential_path.unwrap())?
|
||||
}
|
||||
};
|
||||
let raw_credential = Zeroizing::new(raw_credential);
|
||||
|
||||
// we're unpacking the data in order to make sure it's valid
|
||||
// and to extract relevant metadata for storage purposes
|
||||
let credential = match args.version {
|
||||
1 => Zeroizing::new(
|
||||
IssuedBandwidthCredential::unpack_v1(&raw_credential).map_err(|source| {
|
||||
NetworkRequesterError::CredentialDeserializationFailure {
|
||||
storage_revision: 1,
|
||||
source,
|
||||
}
|
||||
})?,
|
||||
),
|
||||
other => panic!("unknown credential serialization version {other}"),
|
||||
};
|
||||
|
||||
info!("importing {}", credential.typ());
|
||||
match credential.variant_data() {
|
||||
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
|
||||
info!("with value of {}", voucher_info.value())
|
||||
}
|
||||
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
|
||||
info!("with expiry at {}", freepass_info.expiry_date());
|
||||
if freepass_info.expired() {
|
||||
error!("the free pass has already expired!");
|
||||
|
||||
// technically we can import it, but the gateway will just reject it so what's the point
|
||||
return Err(NetworkRequesterError::ExpiredCredentialImport {
|
||||
expiration: freepass_info.expiry_date(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let storable = StorableIssuedCredential {
|
||||
serialization_revision: args.version,
|
||||
credential_data: &raw_credential,
|
||||
credential_type: credential.typ().to_string(),
|
||||
epoch_id: credential
|
||||
.epoch_id()
|
||||
.try_into()
|
||||
.expect("our epoch is has run over u32::MAX!"),
|
||||
};
|
||||
|
||||
credentials_store.insert_issued_credential(storable).await?;
|
||||
import_credential(credentials_store, raw_credential, args.version).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub use nym_client_core::error::ClientCoreError;
|
||||
use nym_credential_storage::error::StorageError;
|
||||
|
||||
use nym_exit_policy::policy::PolicyError;
|
||||
use nym_id::NymIdError;
|
||||
use nym_socks5_requests::{RemoteAddress, Socks5RequestError};
|
||||
use std::net::SocketAddr;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum NetworkRequesterError {
|
||||
@@ -70,21 +70,6 @@ pub enum NetworkRequesterError {
|
||||
#[error("can't setup an exit policy without any upstream urls")]
|
||||
NoUpstreamExitPolicy,
|
||||
|
||||
#[error("failed to store credential: {source}")]
|
||||
CredentialStorageFailure {
|
||||
#[from]
|
||||
source: StorageError,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"failed to deserialize provided credential using revision {storage_revision}: {source}"
|
||||
)]
|
||||
CredentialDeserializationFailure {
|
||||
storage_revision: u8,
|
||||
#[source]
|
||||
source: nym_credentials::error::Error,
|
||||
},
|
||||
|
||||
#[error("attempted to import an expired credential (it expired on {expiration})")]
|
||||
ExpiredCredentialImport { expiration: OffsetDateTime },
|
||||
#[error(transparent)]
|
||||
NymIdError(#[from] NymIdError),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "nym-id-cli"
|
||||
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]
|
||||
anyhow.workspace = true
|
||||
bs58.workspace = true
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
tracing.workspace = true
|
||||
|
||||
nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "basic_tracing"] }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
nym-id = { path = "../../common/nym-id" }
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_bin_common::bin_info_owned;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
#[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: Args) {
|
||||
println!("{}", args.output.format(&bin_info_owned!()))
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::ArgGroup;
|
||||
use nym_id::import_credential;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
|
||||
bs58::decode(raw).into_vec()
|
||||
}
|
||||
|
||||
#[derive(clap::Args)]
|
||||
#[clap(group(ArgGroup::new("cred_data").required(true)))]
|
||||
pub(crate) struct Args {
|
||||
/// Explicitly provide the encoded credential data (as base58)
|
||||
#[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)]
|
||||
pub(crate) credential_data: Option<Vec<u8>>,
|
||||
|
||||
/// Specifies the path to file containing binary credential data
|
||||
#[clap(long, group = "cred_data")]
|
||||
pub(crate) credential_path: Option<PathBuf>,
|
||||
|
||||
/// Specifies path to the credentials storage
|
||||
#[clap(long)]
|
||||
pub credentials_store_path: PathBuf,
|
||||
|
||||
// currently hidden as there exists only a single serialization standard
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) version: Option<u8>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
let credentials_store =
|
||||
nym_credential_storage::initialise_persistent_storage(args.credentials_store_path).await;
|
||||
|
||||
let raw_credential = match args.credential_data {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
// SAFETY: one of those arguments must have been set
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fs::read(args.credential_path.unwrap())?
|
||||
}
|
||||
};
|
||||
|
||||
import_credential(credentials_store, raw_credential, args.version).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod build_info;
|
||||
mod import_credential;
|
||||
mod setup;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use nym_bin_common::bin_info;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
|
||||
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub async fn execute(self) -> anyhow::Result<()> {
|
||||
match self.command {
|
||||
Commands::ImportCredential(args) => import_credential::execute(args).await?,
|
||||
Commands::BuildInfo(args) => build_info::execute(args),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
// TODO: to be determined how it's going to work in nymvpn et al.
|
||||
// ///
|
||||
// Setup,
|
||||
/// Attempt to import a bandwidth credential into the provided storage.
|
||||
ImportCredential(import_credential::Args),
|
||||
|
||||
/// Show build information of this binary
|
||||
BuildInfo(build_info::Args),
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
use crate::commands::Cli;
|
||||
use clap::Parser;
|
||||
use nym_bin_common::logging::setup_tracing_logger;
|
||||
|
||||
mod commands;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
setup_tracing_logger();
|
||||
let cli = Cli::parse();
|
||||
|
||||
cli.execute().await
|
||||
}
|
||||
@@ -376,9 +376,9 @@ inherits@2, inherits@^2.0.3:
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ip@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da"
|
||||
integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105"
|
||||
integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==
|
||||
|
||||
is-fullwidth-code-point@^3.0.0:
|
||||
version "3.0.0"
|
||||
|
||||
+4
-6
@@ -17,8 +17,7 @@
|
||||
},
|
||||
"../../../dist/node/wasm/mix-fetch": {
|
||||
"name": "@nymproject/mix-fetch-wasm",
|
||||
"version": "1.2.0-rc.2",
|
||||
"license": "Apache-2.0"
|
||||
"version": "1.2.0-rc.2"
|
||||
},
|
||||
"node_modules/@gar/promisify": {
|
||||
"version": "1.1.3",
|
||||
@@ -643,10 +642,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
|
||||
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==",
|
||||
"license": "MIT",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
|
||||
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
|
||||
@@ -396,9 +396,9 @@ inherits@2, inherits@^2.0.3:
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ip@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz"
|
||||
integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105"
|
||||
integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==
|
||||
|
||||
is-fullwidth-code-point@^3.0.0:
|
||||
version "3.0.0"
|
||||
|
||||
@@ -13765,9 +13765,9 @@ ioredis@^5.3.2:
|
||||
standard-as-callback "^2.1.0"
|
||||
|
||||
ip@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da"
|
||||
integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105"
|
||||
integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==
|
||||
|
||||
ipaddr.js@1.9.1:
|
||||
version "1.9.1"
|
||||
|
||||
Reference in New Issue
Block a user