Commit 02e39f5d authored by Cédric Moreau's avatar Cédric Moreau

[enh] #1084 WS2P: plug as a module

parent ab70e76a
......@@ -18,8 +18,9 @@ app/lib/rules/*.js
app/lib/system/*.js
app/lib/streams/*.js
app/lib/helpers/*.js
app/lib/ws2p/*.js
app/lib/ws2p/*/*.js
app/modules/ws2p/*.js
app/modules/ws2p/lib/*.js
app/modules/ws2p/lib/*/*.js
app/lib/*.js
app/modules/wizard.js
app/modules/router.js
......
import {CommonConstants} from "../common-libs/constants";
import {CommonConstants} from "../common-libs/constants"
const _ = require('underscore');
const constants = require('../constants');
......@@ -58,6 +58,16 @@ export interface NetworkConfDTO {
httplogs:boolean
}
export interface WS2PConfDTO {
ws2p?: {
upnp?: boolean
remotehost?: string|null
remoteport?: number|null
port?: number
host?: string
}
}
export class ConfDTO implements CurrencyConfDTO, KeypairConfDTO, NetworkConfDTO, BranchingDTO {
constructor(
......
import * as stream from "stream"
import {WS2PConnection} from "../ws2p/WS2PConnection"
import {WS2PConnection} from "../../modules/ws2p/lib/WS2PConnection"
export class WS2PStreamer extends stream.Transform {
......
"use strict";
import {WS2PConfDTO} from "../../lib/dto/ConfDTO"
import {Server} from "../../../server"
import * as stream from "stream"
import {WS2PCluster} from "./lib/WS2PCluster"
import {WS2PUpnp} from "./lib/ws2p-upnp"
export const WS2PDependency = {
duniter: {
cliOptions: [
{ value: '--ws2p-upnp', desc: 'Use UPnP to open remote port.' },
{ value: '--ws2p-noupnp', desc: 'Do not use UPnP to open remote port.' },
{ value: '--ws2p-port <port>', desc: 'Host to listen to' },
{ value: '--ws2p-host <host>', desc: 'Port to listen to', parser: (val:string) => parseInt(val) },
{ value: '--ws2p-remote-host <address>', desc: 'Availabily host' },
{ value: '--ws2p-remote-port <port>', desc: 'Availabily port', parser: (val:string) => parseInt(val) },
],
config: {
onLoading: async (conf:WS2PConfDTO, program:any, logger:any) => {
conf.ws2p = conf.ws2p || {}
if (program.ws2pHost !== undefined) conf.ws2p.host = program.ws2pHost
if (program.ws2pPort !== undefined) conf.ws2p.port = parseInt(program.ws2pPort)
if (program.ws2pRemotePort !== undefined) conf.ws2p.remoteport = program.ws2pRemotePort
if (program.ws2pRemoteHost !== undefined) conf.ws2p.remotehost = program.ws2pRemoteHost
if (program.ws2pUpnp !== undefined) conf.ws2p.upnp = true
if (program.ws2pNoupnp !== undefined) conf.ws2p.upnp = false
// Default value
if (conf.ws2p.upnp === undefined || conf.ws2p.upnp === null) {
conf.ws2p.upnp = true; // Defaults to true
}
},
beforeSave: async (conf:WS2PConfDTO) => {
if (conf.ws2p && !conf.ws2p.host) delete conf.ws2p.host
if (conf.ws2p && !conf.ws2p.port) delete conf.ws2p.port
if (conf.ws2p && !conf.ws2p.remoteport) delete conf.ws2p.remoteport
if (conf.ws2p && !conf.ws2p.remotehost) delete conf.ws2p.remotehost
}
},
service: {
input: (server:Server, conf:WS2PConfDTO, logger:any) => {
return new WS2PAPI(server, conf, logger)
}
}
}
}
export class WS2PAPI extends stream.Transform {
// Public http interface
private cluster:WS2PCluster
private upnpAPI:WS2PUpnp
constructor(
private server:Server,
private conf:WS2PConfDTO,
private logger:any) {
super({ objectMode: true })
this.cluster = new WS2PCluster(server)
}
startService = async () => {
/***************
* MANUAL
**************/
if (this.conf.ws2p
&& !this.conf.ws2p.upnp
&& this.conf.ws2p.host
&& this.conf.ws2p.port) {
await this.cluster.listen(this.conf.ws2p.host, this.conf.ws2p.port)
}
/***************
* UPnP
**************/
else if (!this.conf.ws2p || this.conf.ws2p.upnp !== false) {
if (this.upnpAPI) {
this.upnpAPI.stopRegular();
}
try {
this.upnpAPI = await new WS2PUpnp(this.logger)
const { host, port } = await this.upnpAPI.startRegular()
await this.cluster.listen(host, port)
} catch (e) {
this.logger.warn(e);
}
}
}
stopService = async () => {
if (this.cluster) {
await this.cluster.close()
}
if (this.upnpAPI) {
this.upnpAPI.stopRegular();
}
}
}
\ No newline at end of file
import {BlockDTO} from "../dto/BlockDTO"
import {AbstractDAO} from "../../modules/crawler/lib/pulling"
import {Server} from "../../../server"
import {DBBlock} from "../db/DBBlock"
import {PeerDTO} from "../dto/PeerDTO"
import {CrawlerConstants} from "../../modules/crawler/lib/constants"
import {tx_cleaner} from "../../modules/crawler/lib/tx_cleaner"
import {BlockDTO} from "../../../lib/dto/BlockDTO"
import {AbstractDAO} from "../../crawler/lib/pulling"
import {Server} from "../../../../server"
import {DBBlock} from "../../../lib/db/DBBlock"
import {PeerDTO} from "../../../lib/dto/PeerDTO"
import {CrawlerConstants} from "../../crawler/lib/constants"
import {tx_cleaner} from "../../crawler/lib/tx_cleaner"
import {WS2PConnection} from "./WS2PConnection"
import {WS2PRequester} from "./WS2PRequester"
......
import {Server} from "../../../server"
import {Server} from "../../../../server"
import {WS2PConnection, WS2PPubkeyLocalAuth, WS2PPubkeyRemoteAuth} from "./WS2PConnection"
import {WS2PServerMessageHandler} from "./interface/WS2PServerMessageHandler"
import {WS2PStreamer} from "../streams/WS2PStreamer"
import {Key} from "../common-libs/crypto/keyring"
import {WS2PStreamer} from "../../../lib/streams/WS2PStreamer"
import {Key} from "../../../lib/common-libs/crypto/keyring"
export class WS2PClient {
......
import {WS2PServer} from "./WS2PServer"
import {Server} from "../../../server"
import {Server} from "../../../../server"
import {WS2PClient} from "./WS2PClient"
import {WS2PConnection} from "./WS2PConnection"
import {randomPick} from "../common-libs/randomPick"
import {CrawlerConstants} from "../../modules/crawler/lib/constants"
import {randomPick} from "../../../lib/common-libs/randomPick"
import {CrawlerConstants} from "../../crawler/lib/constants"
import {WS2PBlockPuller} from "./WS2PBlockPuller"
import {WS2PDocpoolPuller} from "./WS2PDocpoolPuller"
......@@ -24,6 +24,14 @@ export class WS2PCluster {
return this.ws2pServer
}
async close() {
if (this.ws2pServer) {
await this.ws2pServer.close()
}
const connections = await this.getAllConnections()
await Promise.all(connections.map(c => c.close()))
}
clientsCount() {
return Object.keys(this.ws2pClients).length
}
......
import {Key, verify} from "../common-libs/crypto/keyring"
import {Key, verify} from "../../../lib/common-libs/crypto/keyring"
import {WS2PMessageHandler} from "./impl/WS2PMessageHandler"
import {BlockDTO} from "../dto/BlockDTO"
import {IdentityDTO} from "../dto/IdentityDTO"
import {CertificationDTO} from "../dto/CertificationDTO"
import {MembershipDTO} from "../dto/MembershipDTO"
import {TransactionDTO} from "../dto/TransactionDTO"
import {PeerDTO} from "../dto/PeerDTO"
import {BlockDTO} from "../../../lib/dto/BlockDTO"
import {IdentityDTO} from "../../../lib/dto/IdentityDTO"
import {CertificationDTO} from "../../../lib/dto/CertificationDTO"
import {MembershipDTO} from "../../../lib/dto/MembershipDTO"
import {TransactionDTO} from "../../../lib/dto/TransactionDTO"
import {PeerDTO} from "../../../lib/dto/PeerDTO"
const ws = require('ws')
const nuuid = require('node-uuid');
......
import {Server} from "../../../server"
import {Server} from "../../../../server"
import {WS2PConnection} from "./WS2PConnection"
import {WS2PRequester} from "./WS2PRequester"
import {pullSandboxToLocalServer} from "../../modules/crawler/lib/sandbox"
import {pullSandboxToLocalServer} from "../../crawler/lib/sandbox"
export class WS2PDocpoolPuller {
......
import {WS2PConnection} from "./WS2PConnection"
import {BlockDTO} from "../dto/BlockDTO"
import {BlockDTO} from "../../../lib/dto/BlockDTO"
export enum WS2P_REQ {
WOT_REQUIREMENTS_OF_PENDING,
......
import {Server} from "../../../server"
import {Server} from "../../../../server"
import {WS2PConnection, WS2PPubkeyLocalAuth, WS2PPubkeyRemoteAuth} from "./WS2PConnection"
import {WS2PServerMessageHandler} from "./interface/WS2PServerMessageHandler"
import {WS2PStreamer} from "../streams/WS2PStreamer"
import {Key} from "../common-libs/crypto/keyring"
import {WS2PStreamer} from "../../../lib/streams/WS2PStreamer"
import {Key} from "../../../lib/common-libs/crypto/keyring"
const WebSocketServer = require('ws').Server
......@@ -79,6 +79,7 @@ export class WS2PServer {
static async bindOn(server:Server, host:string, port:number) {
const ws2ps = new WS2PServer(server, host, port)
await ws2ps.listenToWebSocketConnections()
server.logger.info('WS2P server listening on %s:%s', host, port)
return ws2ps
}
}
\ No newline at end of file
export const WS2PConstants = {
WS2P_UPNP_TTL: 600,
WS2P_PORTS_START: 20900,
WS2P_PORTS_END: 20999,
WS2P_UPNP_INTERVAL: 300
}
\ No newline at end of file
import {Server} from "../../../../server"
import {Server} from "../../../../../server"
import {WS2PReqMapper} from "../interface/WS2PReqMapper"
import {BlockDTO} from "../../dto/BlockDTO"
import {BlockDTO} from "../../../../lib/dto/BlockDTO"
export class WS2PReqMapperByServer implements WS2PReqMapper {
......
import {BlockDTO} from "../../dto/BlockDTO"
import {BlockDTO} from "../../../../lib/dto/BlockDTO"
export interface WS2PReqMapper {
......
import {WS2PMessageHandler} from "../impl/WS2PMessageHandler"
import {WS2PResponse} from "../impl/WS2PResponse"
import {Server} from "../../../../server"
import {Server} from "../../../../../server"
import {WS2PReqMapperByServer} from "../impl/WS2PReqMapperByServer"
import {WS2PReqMapper} from "./WS2PReqMapper"
import {BlockDTO} from "../../dto/BlockDTO"
import {IdentityDTO} from "../../dto/IdentityDTO"
import {CertificationDTO} from "../../dto/CertificationDTO"
import {MembershipDTO} from "../../dto/MembershipDTO"
import {TransactionDTO} from "../../dto/TransactionDTO"
import {PeerDTO} from "../../dto/PeerDTO"
import {BlockDTO} from "../../../../lib/dto/BlockDTO"
import {IdentityDTO} from "../../../../lib/dto/IdentityDTO"
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 {WS2P_REQ} from "../WS2PRequester"
export enum WS2P_REQERROR {
......
import {WS2PConstants} from "./constants"
const upnp = require('nnupnp');
const Q = require('q');
export class WS2PUpnp {
private interval:NodeJS.Timer|null
private client = upnp.createClient()
constructor(
private logger:any
) {}
async checkUPnPisAvailable() {
try {
await Q.nbind(this.client.externalIp, this.client)()
return true
} catch (err) {
return false
}
}
openPort() {
return Q.Promise(async (resolve:any, reject:any) => {
const upnpBinding = await WS2PUpnp.getAvailablePort(this.client)
this.logger.trace('WS2P: mapping external port %s to local %s using UPnP...', upnpBinding.host, upnpBinding.port)
const client = upnp.createClient()
client.portMapping({
'public': upnpBinding.port,
'private': upnpBinding.port,
'ttl': WS2PConstants.WS2P_UPNP_TTL
}, (err:any) => {
client.close()
if (err) {
this.logger.warn(err)
return reject(err)
}
resolve(upnpBinding)
})
})
}
async startRegular() {
this.stopRegular();
if (await this.checkUPnPisAvailable()) {
// Update UPnP IGD every INTERVAL seconds
this.interval = setInterval(() => this.openPort(), 1000 * WS2PConstants.WS2P_UPNP_INTERVAL)
}
return this.openPort()
}
stopRegular() {
if (this.interval) {
clearInterval(this.interval)
}
}
static async getLocalIP(client:any) {
return await new Promise((resolve:any, reject:any) => {
client.findGateway((err:any, res:any, localIP:any) => {
if (err) return reject(err)
resolve(localIP)
})
})
}
static async getAvailablePort(client:any) {
const localIP = await WS2PUpnp.getLocalIP(client)
const mappings:{
private: {
host:string
}
public: {
port:number
}
}[] = await WS2PUpnp.getUPnPMappings(client)
const ipOfPort:string[] = []
const externalPortsUsed = mappings.map((m) => {
ipOfPort.push(m.private.host)
return m.public.port
})
let availablePort = WS2PConstants.WS2P_PORTS_START
while (externalPortsUsed.indexOf(availablePort) !== -1
&& ipOfPort[externalPortsUsed.indexOf(availablePort)] !== localIP
&& availablePort <= WS2PConstants.WS2P_PORTS_END) {
availablePort++
}
if (availablePort > WS2PConstants.WS2P_PORTS_END) {
throw "No port available for UPnP"
}
return {
host: localIP,
port: availablePort
}
}
static async getUPnPMappings(client:any): Promise<any> {
return new Promise((resolve, reject) => {
client.getMappings((err:any, res:any) => {
if (err) {
reject(err)
}
else {
resolve(res)
}
})
})
}
}
\ No newline at end of file
......@@ -5,7 +5,8 @@ import {ConfDTO} from "./app/lib/dto/ConfDTO"
import {ProverDependency} from "./app/modules/prover/index"
import {KeypairDependency} from "./app/modules/keypair/index"
import {CrawlerDependency} from "./app/modules/crawler/index"
import {BmaDependency} from "./app/modules/bma/index";
import {BmaDependency} from "./app/modules/bma/index"
import {WS2PDependency} from "./app/modules/ws2p/index"
const path = require('path');
const _ = require('underscore');
......@@ -104,7 +105,8 @@ const DEFAULT_DEPENDENCIES = MINIMAL_DEPENDENCIES.concat([
{ name: 'duniter-prover', required: ProverDependency },
{ name: 'duniter-keypair', required: KeypairDependency },
{ name: 'duniter-crawler', required: CrawlerDependency },
{ name: 'duniter-bma', required: BmaDependency }
{ name: 'duniter-bma', required: BmaDependency },
{ name: 'duniter-ws2p', required: WS2PDependency }
]);
const PRODUCTION_DEPENDENCIES = DEFAULT_DEPENDENCIES.concat([
......
......@@ -16,11 +16,11 @@ import {FileDAL} from "../../../app/lib/dal/fileDAL"
import {MembershipDTO} from "../../../app/lib/dto/MembershipDTO"
import {TransactionDTO} from "../../../app/lib/dto/TransactionDTO"
import {Key} from "../../../app/lib/common-libs/crypto/keyring"
import {WS2PConnection, WS2PPubkeyLocalAuth, WS2PPubkeyRemoteAuth} from "../../../app/lib/ws2p/WS2PConnection"
import {WS2PResponse} from "../../../app/lib/ws2p/impl/WS2PResponse"
import {WS2PMessageHandler} from "../../../app/lib/ws2p/impl/WS2PMessageHandler"
import {WS2PCluster} from "../../../app/lib/ws2p/WS2PCluster"
import {WS2PServer} from "../../../app/lib/ws2p/WS2PServer"
import {WS2PConnection, WS2PPubkeyLocalAuth, WS2PPubkeyRemoteAuth} from "../../../app/modules/ws2p/lib/WS2PConnection"
import {WS2PResponse} from "../../../app/modules/ws2p/lib/impl/WS2PResponse"
import {WS2PMessageHandler} from "../../../app/modules/ws2p/lib/impl/WS2PMessageHandler"
import {WS2PCluster} from "../../../app/modules/ws2p/lib/WS2PCluster"
import {WS2PServer} from "../../../app/modules/ws2p/lib/WS2PServer"
const assert = require('assert');
const _ = require('underscore');
......
......@@ -4,11 +4,11 @@ import {
WS2PPubkeyLocalAuth,
WS2PPubkeyRemoteAuth,
WS2PRemoteAuth
} from "../../app/lib/ws2p/WS2PConnection"
} from "../../app/modules/ws2p/lib/WS2PConnection"
import {Key, verify} from "../../app/lib/common-libs/crypto/keyring"
import {assertThrows} from "./tools/toolbox"
import {WS2PMessageHandler} from "../../app/lib/ws2p/impl/WS2PMessageHandler"
import {WS2PResponse} from "../../app/lib/ws2p/impl/WS2PResponse"
import {WS2PMessageHandler} from "../../app/modules/ws2p/lib/impl/WS2PMessageHandler"
import {WS2PResponse} from "../../app/modules/ws2p/lib/impl/WS2PResponse"
const assert = require('assert');
const WebSocketServer = require('ws').Server
......
import {simpleTestingConf, simpleTestingServer, simpleUser, simpleWS2PNetwork, TestingServer} from "./tools/toolbox"
import {WS2PCluster} from "../../app/lib/ws2p/WS2PCluster"
import {WS2PCluster} from "../../app/modules/ws2p/lib/WS2PCluster"
import {ProverDependency} from "../../app/modules/prover/index"
const assert = require('assert')
......
import {WS2PConnection} from "../../app/lib/ws2p/WS2PConnection"
import {WS2PConnection} from "../../app/modules/ws2p/lib/WS2PConnection"
import {Key} from "../../app/lib/common-libs/crypto/keyring"
import {newWS2PBidirectionnalConnection} from "./tools/toolbox"
import {WS2PRequester} from "../../app/lib/ws2p/WS2PRequester"
import {WS2PReqMapper} from "../../app/lib/ws2p/WS2PReqMapper"
import {WS2PRequester} from "../../app/modules/ws2p/lib/WS2PRequester"
import {WS2PReqMapper} from "../../app/modules/ws2p/WS2PReqMapper"
import {BlockDTO} from "../../app/lib/dto/BlockDTO"
import {WS2PMessageHandler} from "../../app/lib/ws2p/impl/WS2PMessageHandler"
import {WS2PResponse} from "../../app/lib/ws2p/impl/WS2PResponse"
import {WS2PMessageHandler} from "../../app/modules/ws2p/lib/impl/WS2PMessageHandler"
import {WS2PResponse} from "../../app/modules/ws2p/lib/impl/WS2PResponse"
const assert = require('assert');
describe('WS2P exchange', () => {
......
import {simpleTestingConf, simpleTestingServer, simpleUser, simpleWS2PNetwork, TestingServer} from "./tools/toolbox"
import {WS2PCluster} from "../../app/lib/ws2p/WS2PCluster"
import {WS2PCluster} from "../../app/modules/ws2p/lib/WS2PCluster"
const assert = require('assert')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment