diff --git a/app/lib/dto/PeerDTO.ts b/app/lib/dto/PeerDTO.ts index 4d735bd3129cd582a9cabcbb059e338d8d33388c..c83759132465d99b333146099895a816e9536f41 100644 --- a/app/lib/dto/PeerDTO.ts +++ b/app/lib/dto/PeerDTO.ts @@ -5,6 +5,7 @@ import {Cloneable} from "./Cloneable" import { WS2PConstants } from '../../modules/ws2p/lib/constants'; export interface WS2PEndpoint { + api?:string version:number uuid:string host:string @@ -14,6 +15,18 @@ export interface WS2PEndpoint { export class PeerDTO implements Cloneable { + static ws2pEndpointToString(endpoint :WS2PEndpoint) { + let stringEndpoint = (endpoint.api !== undefined) ? endpoint.api:'WS2P' + if (endpoint.version > 1) { + stringEndpoint += ' ' + endpoint.version + } + stringEndpoint += ' ' + endpoint.uuid + ' ' + endpoint.host + ' ' + endpoint.port + if (endpoint.path.length > 0) { + stringEndpoint += ' ' + endpoint.path + } + return stringEndpoint + } + clone(): any { return PeerDTO.fromJSONObject(this) } diff --git a/app/modules/ws2p/index.ts b/app/modules/ws2p/index.ts index a54d265f1a8b3368f99b05333d5e8f83b929ecaa..d5a59634348a204a4b8e1f2440226b54a54ddfdb 100644 --- a/app/modules/ws2p/index.ts +++ b/app/modules/ws2p/index.ts @@ -1,4 +1,5 @@ "use strict"; +import { WS2PEndpoint, PeerDTO } from '../../lib/dto/PeerDTO'; import { WS2PConstants } from './lib/constants'; import {ConfDTO, WS2PConfDTO} from "../../lib/dto/ConfDTO" import {Server} from "../../../server" @@ -112,6 +113,7 @@ export const WS2PDependency = { input: (server:Server, conf:WS2PConfDTO, logger:any) => { const api = new WS2PAPI(server, conf, logger) server.ws2pCluster = api.getCluster() + server.addEndpointsDefinitions(async () => api.getV1Endpoint()) server.addEndpointsDefinitions(async () => api.getEndpoint()) server.addWrongEndpointFilter((endpoints:string[]) => getWrongEndpoints(endpoints, conf)) return api @@ -245,35 +247,49 @@ export class WS2PAPI extends stream.Transform { } } - async getEndpoint() { + async getV1Endpoint():Promise<string> { + let endpointV1 = await this.getWS2PEndpoint() + if (endpointV1 !== undefined) { + endpointV1.version = 1 + return PeerDTO.ws2pEndpointToString(endpointV1) + } + return '' + } + + async getEndpoint():Promise<string> { + let endpoint = await this.getWS2PEndpoint() + if (endpoint !== undefined) { + return PeerDTO.ws2pEndpointToString(endpoint) + } + return '' + } + + async getWS2PEndpoint():Promise<WS2PEndpoint | undefined> { // If WS2P defined and enabled - if (this.server.conf.ws2p !== undefined && (this.server.conf.ws2p.publicAccess || this.server.conf.ws2p.privateAccess)) + if (this.server.conf.ws2p !== undefined && this.server.conf.ws2p.uuid && this.server.conf.ws2p.publicAccess) { - let endpointType = "WS2P" + let api = "WS2P" + let remotehost = this.server.conf.ws2p.remotehost + let remoteport = this.server.conf.ws2p.remoteport if (this.server.conf.upnp && this.upnpAPI) { const config = this.upnpAPI.getCurrentConfig() - if (config) { - if (config.remotehost.match(WS2PConstants.HOST_ONION_REGEX)) { endpointType += "TOR"; } - return [endpointType, this.server.conf.ws2p.uuid, config.remotehost, config.port].join(' ') - } else { - return '' + if (config && this.server.conf.ws2p.uuid) { + remotehost = config.remotehost + remoteport = config.port } } - else if (this.server.conf.ws2p.uuid - && this.server.conf.ws2p.remotehost - && this.server.conf.ws2p.remoteport) { - if (this.server.conf.ws2p.remotehost.match(WS2PConstants.HOST_ONION_REGEX)) { endpointType += "TOR"; } - let ep = [endpointType, - this.server.conf.ws2p.uuid, - this.server.conf.ws2p.remotehost, - this.server.conf.ws2p.remoteport - ].join(' ') - if (this.server.conf.ws2p.remotepath) { - ep += ` ${this.server.conf.ws2p.remotepath}` - } - return ep + if (remotehost != undefined && remotehost != null && remoteport!= undefined && remoteport != null) { + if (remotehost.match(WS2PConstants.HOST_ONION_REGEX)) { api += "TOR"; } + return { + api: api, + version: WS2PConstants.WS2P_VERSION, + uuid: this.server.conf.ws2p.uuid, + host: remotehost, + port: remoteport, + path: (this.server.conf.ws2p.remotepath) ? this.server.conf.ws2p.remotepath:"" + } } } - return '' + return undefined } } \ No newline at end of file diff --git a/app/modules/ws2p/lib/WS2PClient.ts b/app/modules/ws2p/lib/WS2PClient.ts index 830b35722ca7079d861ee8a0dbf1b62bf0c202e7..40466a504ef587cbe05c77391637476fcab89cfe 100644 --- a/app/modules/ws2p/lib/WS2PClient.ts +++ b/app/modules/ws2p/lib/WS2PClient.ts @@ -13,7 +13,7 @@ export class WS2PClient { private constructor(public connection:WS2PConnection) {} - static async connectTo(server:Server, fullEndpointAddress:string, endpointVersion:number, expectedWS2PUID:string, messageHandler:WS2PMessageHandler, expectedPub:string, allowKey:(pub:string)=>Promise<boolean> ) { + static async connectTo(server:Server, fullEndpointAddress:string, endpointVersion:number, expectedWS2PUID:string, messageHandler:WS2PMessageHandler, expectedPub:string, allowKey:(pub:string, ws2pID?:string)=>Promise<boolean> ) { const k2 = new Key(server.conf.pair.pub, server.conf.pair.sec) const myWs2pId = (server.conf.ws2p && server.conf.ws2p.uuid) ? server.conf.ws2p.uuid:"" const c = WS2PConnection.newConnectionToAddress( diff --git a/app/modules/ws2p/lib/WS2PCluster.ts b/app/modules/ws2p/lib/WS2PCluster.ts index 8bcfd4fbcb6d99b88bd9ccc27416216761652936..ed19a18ec4cddf1e3e47a71683d17da991384755 100644 --- a/app/modules/ws2p/lib/WS2PCluster.ts +++ b/app/modules/ws2p/lib/WS2PCluster.ts @@ -313,11 +313,11 @@ export class WS2PCluster { const api:string = (host.match(WS2PConstants.HOST_ONION_REGEX) !== null) ? 'WS2PTOR':'WS2P' try { const fullEndpointAddress = WS2PCluster.getFullAddress(host, port, path) - const ws2pc = await WS2PClient.connectTo(this.server, fullEndpointAddress, endpointVersion, ws2pEndpointUUID, messageHandler, expectedPub, (pub:string) => { + const ws2pc = await WS2PClient.connectTo(this.server, fullEndpointAddress, endpointVersion, ws2pEndpointUUID, messageHandler, expectedPub, (pub:string, remoteWs2pId?:string) => { const connectedPubkeys = this.getConnectedPubkeys() const connectedWS2PUID = this.getConnectedWS2PUID() const preferedNodes = (this.server.conf.ws2p && this.server.conf.ws2p.preferedNodes) ? this.server.conf.ws2p.preferedNodes:[] - return this.acceptPubkey(expectedPub, connectedPubkeys, connectedWS2PUID, () => this.clientsCount(), this.maxLevel1Size, preferedNodes, (this.server.conf.ws2p && this.server.conf.ws2p.preferedOnly) || false, ws2pEndpointUUID) + return this.acceptPubkey(expectedPub, connectedPubkeys, connectedWS2PUID, () => this.clientsCount(), this.maxLevel1Size, preferedNodes, (this.server.conf.ws2p && this.server.conf.ws2p.preferedOnly) || false, (remoteWs2pId !== undefined) ? remoteWs2pId:ws2pEndpointUUID) }) this.ws2pClients[uuid] = ws2pc pub = ws2pc.connection.pubkey @@ -394,9 +394,11 @@ export class WS2PCluster { } }) const canReachClearEndpoint = ProxiesConf.canReachClearEndpoint(this.server.conf.proxiesConf) - let i = 0 let countPublicNodesWithSameKey:number = 1 // Necessary if maxPrivate = 0 let endpointsNodesWithSameKey:WS2PEndpoint[] = [] + const preferedNodes = (this.server.conf.ws2p && this.server.conf.ws2p.preferedNodes) ? this.server.conf.ws2p.preferedNodes:[] + const preferedOnly = (this.server.conf.ws2p && this.server.conf.ws2p.preferedOnly === true) ? true:false + let i = 0 while (i < peers.length && (this.clientsCount() < this.maxLevel1Size || this.numberOfConnectedPublicNodesWithSameKey() < countPublicNodesWithSameKey) ) { const p = peers[i] if (p.pubkey === this.server.conf.pair.pub) { @@ -404,8 +406,10 @@ export class WS2PCluster { countPublicNodesWithSameKey = endpointsNodesWithSameKey.length for (const api of endpointsNodesWithSameKey) { try { - // We do not connect to local host - if (api.uuid !== myUUID) { + + let allow = this.acceptPubkey(p.pubkey, this.getConnectedPubkeys(), this.getConnectedWS2PUID(), () => this.clientsCount(), this.maxLevel1Size, preferedNodes, preferedOnly, api.uuid) + // We do not connect to local host or not allowed key + if (api.uuid !== myUUID && allow) { await this.connectToRemoteWS(api.version, api.host, api.port, api.path, this.messageHandler, p.pubkey, api.uuid) } } catch (e) { @@ -664,15 +668,15 @@ export class WS2PCluster { maxConcurrentConnexionsSize:number, priorityKeys:string[], priorityKeysOnly:boolean, - targetWS2PUID = "" + remoteWS2PUID = "" ) { if (this.server.conf.pair.pub === pub) { // We do not accept oneself connetion - if (this.server.conf.ws2p && this.server.conf.ws2p.uuid === targetWS2PUID || targetWS2PUID === '11111111') { + if (this.server.conf.ws2p && this.server.conf.ws2p.uuid === remoteWS2PUID || remoteWS2PUID === '11111111') { return false } else { // We always accept self nodes, and they have a supreme priority (these are siblings) - if (targetWS2PUID === "" || this.isNewSiblingNode(pub, targetWS2PUID, connectedWS2PUID) ) { + if (remoteWS2PUID === "" || connectedWS2PUID.indexOf(remoteWS2PUID) === -1) { return true } else { // We are already connected to this self node (same WS2PUID) @@ -724,15 +728,6 @@ export class WS2PCluster { return false } - isNewSiblingNode(pub:string, targetWS2PUID:string, connectedWS2PUID:string[]) { - for (const uuid of connectedWS2PUID) { - if (uuid === targetWS2PUID) { - return false - } - } - return true - } - async getLevel1Connections() { const all:WS2PConnection[] = [] for (const uuid of Object.keys(this.ws2pClients)) { diff --git a/app/modules/ws2p/lib/WS2PConnection.ts b/app/modules/ws2p/lib/WS2PConnection.ts index f968773443bde4fc9ee13186ff81910bc8978995..7eba699edc2d4673070b03ce14612f0aeb82a3c2 100644 --- a/app/modules/ws2p/lib/WS2PConnection.ts +++ b/app/modules/ws2p/lib/WS2PConnection.ts @@ -6,7 +6,7 @@ import {CertificationDTO} from "../../../lib/dto/CertificationDTO" import {MembershipDTO} from "../../../lib/dto/MembershipDTO" import {TransactionDTO} from "../../../lib/dto/TransactionDTO" import {PeerDTO} from "../../../lib/dto/PeerDTO" -import {WS2PConstants} from "./constants" +import { WS2PConstants } from './constants'; import { ProxiesConf } from '../../../lib/proxy'; const ws = require('ws') const SocksProxyAgent = require('socks-proxy-agent'); @@ -31,7 +31,8 @@ enum WS2P_ERR { MUST_BE_AUTHENTICATED_FIRST, REQUEST_FAILED, MESSAGE_MUST_BE_AN_OBJECT, - ANSWER_TO_UNDEFINED_REQUEST + ANSWER_TO_UNDEFINED_REQUEST, + NOT_SUPPORTED_VERSION } export enum WS2P_PUSH { @@ -80,7 +81,7 @@ export class WS2PPubkeyRemoteAuth implements WS2PRemoteAuth { constructor( protected currency:string, protected pair:Key, - protected tellIsAuthorizedPubkey:(pub: string) => Promise<boolean> = () => Promise.resolve(true) + protected tellIsAuthorizedPubkey:(pub: string, ws2pId?:string) => Promise<boolean> = () => Promise.resolve(true) ) { this.challenge = nuuid.v4() + nuuid.v4() this.serverAuth = new Promise((resolve, reject) => { @@ -104,19 +105,19 @@ export class WS2PPubkeyRemoteAuth implements WS2PRemoteAuth { })) } - async registerCONNECT(ws2pVersion:number, challenge:string, sig: string, pub: string, ws2pId:string = ""): Promise<boolean> { - const allow = await this.tellIsAuthorizedPubkey(pub) + async registerCONNECT(ws2pVersion:number, challenge:string, sig: string, pub: string, remoteWs2pId:string = ""): Promise<boolean> { + const allow = await this.tellIsAuthorizedPubkey(pub, remoteWs2pId) if (!allow) { return false } - const challengeMessage = (ws2pVersion > 1) ? `WS2P:CONNECT:${this.currency}:${pub}:${ws2pId}:${challenge}`:`WS2P:CONNECT:${this.currency}:${pub}:${challenge}` + const challengeMessage = (ws2pVersion > 1) ? `WS2P:${ws2pVersion}:CONNECT:${this.currency}:${pub}:${remoteWs2pId}:${challenge}`:`WS2P:CONNECT:${this.currency}:${pub}:${challenge}` Logger.log('registerCONNECT >>> ' + challengeMessage) const verified = verify(challengeMessage, sig, pub) if (verified) { this.remoteVersion = ws2pVersion this.challenge = challenge this.remotePub = pub - this.remoteWs2pId = ws2pId + this.remoteWs2pId = remoteWs2pId } return verified } @@ -195,10 +196,6 @@ export class WS2PPubkeyLocalAuth implements WS2PLocalAuth { } async registerACK(sig: string, pub: string): Promise<boolean> { - const allow = await this.tellIsAuthorizedPubkey(pub) - if (!allow) { - return false - } const challengeMessage = `WS2P:ACK:${this.currency}:${pub}:${this.challenge}` Logger.log('registerACK >>> ' + challengeMessage) this.authenticated = verify(challengeMessage, sig, pub) @@ -422,6 +419,8 @@ export class WS2PConnection { if (data.version) { if (typeof data.version !== "number") { await this.errorDetected(WS2P_ERR.AUTH_INVALID_ASK_FIELDS) + } else if (data.version > WS2PConstants.WS2P_VERSION) { + await this.errorDetected(WS2P_ERR.NOT_SUPPORTED_VERSION) } else { this.ws2pVersion = data.version } diff --git a/app/modules/ws2p/lib/constants.ts b/app/modules/ws2p/lib/constants.ts index 0c9836fb4fdda6201471cfe8b6ef34c250dc621b..b13299f47d58f1044283fa740d9c7955e041782e 100644 --- a/app/modules/ws2p/lib/constants.ts +++ b/app/modules/ws2p/lib/constants.ts @@ -2,7 +2,7 @@ import {CommonConstants} from "../../../lib/common-libs/constants" export const WS2PConstants = { WS2P_DEFAULT_VERSION:1, - WS2P_VERSION: 1, + WS2P_VERSION: 2, WS2P_UPNP_TTL: 600, WS2P_PORTS_START: 20900,