Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ options:
--seed SEED : seed (command-line)
--compress : enable chunk compression
--private : make the proxy private (do not leak the access capability to the DHT)
--key-file : load the key pair from the secure-key file at the specified location (https://github.com/holepunchto/secure-key)
--key-file-password : pass in the secure-key password (the default is to read it from stdin)

```

```sh
Expand Down Expand Up @@ -106,6 +109,8 @@ options:
-i keypair.json : keypair file
--compress : enable chunk compression
--private : access a private hypertele server (expects -s to contain the server's seed instead of the public key)
--key-file : load the key pair from the secure-key file at the specified location (https://github.com/holepunchto/secure-key). In private mode, this should be the same file as used to launch the server.
--key-file-password : pass in the secure-key password (the default is to read it from stdin)
```

Read more about using identities here: https://github.com/prdn/hyper-cmd-docs/blob/main/identity.md
Expand Down
199 changes: 117 additions & 82 deletions client.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,112 +8,147 @@ const libUtils = require('@hyper-cmd/lib-utils')
const libKeys = require('@hyper-cmd/lib-keys')
const goodbye = require('graceful-goodbye')
const connPiper = libNet.connPiper
const SecureKey = require('secure-key')

const helpMsg = 'Usage:\nhypertele -p port_listen -u unix_socket ?--address service_address ?-c conf.json ?-i identity.json ?-s peer_key ?--private'
async function main () {
const helpMsg = 'Usage:\nhypertele -p port_listen -u unix_socket ?--address service_address ?-c conf.json ?-i identity.json ?-s peer_key ?--private ?--key-file ?--key-file-password'

if (argv.help) {
console.log(helpMsg)
process.exit(-1)
}
if (argv.help) {
console.log(helpMsg)
process.exit(-1)
}

if (!argv.u && argv.p == null) {
console.error('Error: proxy port invalid')
process.exit(-1)
}
if (!argv.u && argv.p == null) {
console.error('Error: proxy port invalid')
process.exit(-1)
}

if (argv.u && argv.p) {
console.error('Error: cannot listen to both a port and a Unix domain socket')
process.exit(-1)
}
const conf = {}
if (argv.u && argv.p) {
console.error('Error: cannot listen to both a port and a Unix domain socket')
process.exit(-1)
}
const conf = {}
conf.private = argv.private != null

const target = argv.u ? argv.u : +argv.p

const keyPair = await getKeyPair(argv, conf)

const target = argv.u ? argv.u : +argv.p
// Unofficial opt, only used for tests
let bootstrap = null
if (argv.bootstrap) {
bootstrap = [{ host: '127.0.0.1', port: argv.bootstrap }]
}

if (conf.private) {
conf.peer = keyPair.publicKey
} else if (argv.s) {
conf.peer = libUtils.resolveHostToKey([], argv.s)
}

let keyPair = null
if (argv.i) {
keyPair = libUtils.resolveIdentity([], argv.i)
if (argv.c) {
libUtils.readConf(conf, argv.c)
}

if (!conf.keepAlive) {
conf.keepAlive = 5000
}

if (!keyPair) {
console.error('Error: identity file invalid')
if (argv.compress) {
conf.compress = true
}

const peer = conf.peer
if (!peer) {
console.error('Error: peer is invalid')
process.exit(-1)
}

keyPair = libKeys.parseKeyPair(keyPair)
}
const debug = argv.debug

conf.private = argv.private != null
if (conf.private) {
if (keyPair != null) throw new Error('The --private flag is not compatible with the -i(dentity) flag, since the identity is derived from the peer key')
const seed = argv.s
keyPair = HyperDHT.keyPair(b4a.from(seed, 'hex'))
}
const stats = {}

// Unofficial opt, only used for tests
let bootstrap = null
if (argv.bootstrap) {
bootstrap = [{ host: '127.0.0.1', port: argv.bootstrap }]
}
const dht = new HyperDHT({
bootstrap,
keyPair
})

if (argv.s) {
conf.peer = conf.private
? keyPair.publicKey
: libUtils.resolveHostToKey([], argv.s)
}
const proxy = net.createServer({ allowHalfOpen: true }, c => {
return connPiper(c, () => {
const stream = dht.connect(Buffer.from(peer, 'hex'), { reusableSocket: true })
stream.setKeepAlive(conf.keepAlive)

if (argv.c) {
libUtils.readConf(conf, argv.c)
}
return stream
}, { compress: conf.comgetKeyPairpress }, stats)
})

if (!conf.keepAlive) {
conf.keepAlive = 5000
}
if (debug) {
setInterval(() => {
console.log('connection stats', stats)
}, 5000)
}

if (argv.compress) {
conf.compress = true
}
if (argv.u) {
proxy.listen(target, () => {
console.log(`Server ready @${target}`)
})
} else {
const targetHost = argv.address || '127.0.0.1'
proxy.listen(target, targetHost, () => {
const { address, port } = proxy.address()
console.log(`Server ready @${address}:${port}`)
})
}

const peer = conf.peer
if (!peer) {
console.error('Error: peer is invalid')
process.exit(-1)
goodbye(async () => {
await dht.destroy()
})
}

const debug = argv.debug
async function getKeyPair (argv, conf) {
if (argv['key-file']) {
if (argv.s && conf.private) throw new Error('key-file is not compatible with -s(eed) in private mode, since it uses the keys in the key-file instead of the seed')

const stats = {}
const password = argv['key-file-password']
? b4a.from(argv['key-file-password'])
: null // read from stdin if not specified

const dht = new HyperDHT({
bootstrap,
keyPair
})
const secureKeyPair = await SecureKey.open(argv['key-file'], { password })

const proxy = net.createServer({ allowHalfOpen: true }, c => {
return connPiper(c, () => {
const stream = dht.connect(Buffer.from(peer, 'hex'), { reusableSocket: true })
stream.setKeepAlive(conf.keepAlive)
secureKeyPair.unlock()
const keyPair = {
publicKey: b4a.from(secureKeyPair.publicKey),
secretKey: b4a.from(secureKeyPair.secretKey)
}
secureKeyPair.lock()
secureKeyPair.clear()

return stream
}, { compress: conf.compress }, stats)
})
return keyPair
}

if (debug) {
setInterval(() => {
console.log('connection stats', stats)
}, 5000)
}
if (argv.i && conf.private) {
throw new Error('The --private flag is not compatible with the -i(dentity) flag, since the identity is derived from the peer key')
}

if (argv.u) {
proxy.listen(target, () => {
console.log(`Server ready @${target}`)
})
} else {
const targetHost = argv.address || '127.0.0.1'
proxy.listen(target, targetHost, () => {
const { address, port } = proxy.address()
console.log(`Server ready @${address}:${port}`)
})
let keyPair = null

if (argv.i) {
keyPair = libUtils.resolveIdentity([], argv.i)

if (!keyPair) {
console.error('Error: identity file invalid')
process.exit(-1)
}

keyPair = libKeys.parseKeyPair(keyPair)
}

if (conf.private) {
const seed = argv.s
keyPair = HyperDHT.keyPair(b4a.from(seed, 'hex'))
}

return keyPair
}

goodbye(async () => {
await dht.destroy()
})
main().catch(console.error)
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@hyper-cmd/lib-keys": "https://github.com/holepunchto/hyper-cmd-lib-keys#v0.0.2",
"@hyper-cmd/lib-net": "https://github.com/holepunchto/hyper-cmd-lib-net#v0.0.8",
"@hyper-cmd/lib-utils": "https://github.com/holepunchto/hyper-cmd-lib-utils#v0.0.2",
"secure-key": "^1.0.0",
"b4a": "^1.6.4",
"graceful-goodbye": "^1.3.0",
"hyperdht": "^6.11.0",
Expand All @@ -28,7 +29,8 @@
"homepage": "https://github.com/bitfinexcom/hypertele",
"devDependencies": {
"brittle": "^3.3.2",
"standard": "^17.1.0"
"standard": "^17.1.0",
"test-tmp": "^1.2.0"
},
"scripts": {
"test": "standard && brittle test/end-to-end-tests.js"
Expand Down
Loading