Skip to content

Commit

Permalink
feat: exchange signed peer records in identify
Browse files Browse the repository at this point in the history
  • Loading branch information
vasco-santos authored and jacobheun committed Aug 27, 2020
1 parent ee57a64 commit e50f0ee
Show file tree
Hide file tree
Showing 7 changed files with 361 additions and 28 deletions.
6 changes: 5 additions & 1 deletion src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
exports.messages = {
NOT_STARTED_YET: 'The libp2p node is not started yet',
DHT_DISABLED: 'DHT is not available',
CONN_ENCRYPTION_REQUIRED: 'At least one connection encryption module is required'
CONN_ENCRYPTION_REQUIRED: 'At least one connection encryption module is required',
ERR_INVALID_ENVELOPE: 'Invalid envelope received',
ERR_INVALID_PEER_RECORD: 'Invalid peer record received'
}

exports.codes = {
Expand All @@ -20,6 +22,8 @@ exports.codes = {
ERR_DUPLICATE_TRANSPORT: 'ERR_DUPLICATE_TRANSPORT',
ERR_ENCRYPTION_FAILED: 'ERR_ENCRYPTION_FAILED',
ERR_HOP_REQUEST_FAILED: 'ERR_HOP_REQUEST_FAILED',
ERR_INVALID_ENVELOPE: 'ERR_INVALID_ENVELOPE',
ERR_INVALID_PEER_RECORD: 'ERR_INVALID_PEER_RECORD',
ERR_INVALID_KEY: 'ERR_INVALID_KEY',
ERR_INVALID_MESSAGE: 'ERR_INVALID_MESSAGE',
ERR_INVALID_PARAMETERS: 'ERR_INVALID_PARAMETERS',
Expand Down
8 changes: 6 additions & 2 deletions src/identify/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@ const libp2pVersion = require('../../package.json').version

module.exports.PROTOCOL_VERSION = 'ipfs/0.1.0'
module.exports.AGENT_VERSION = `js-libp2p/${libp2pVersion}`
module.exports.MULTICODEC_IDENTIFY = '/ipfs/id/1.0.0'
module.exports.MULTICODEC_IDENTIFY_PUSH = '/ipfs/id/push/1.0.0'
module.exports.MULTICODEC_IDENTIFY = '/p2p/id/1.1.0'
module.exports.MULTICODEC_IDENTIFY_PUSH = '/p2p/id/push/1.1.0'

// Legacy
module.exports.MULTICODEC_IDENTIFY_LEGACY = '/ipfs/id/1.0.0'
module.exports.MULTICODEC_IDENTIFY_PUSH_LEGACY = '/ipfs/id/push/1.0.0'
168 changes: 159 additions & 9 deletions src/identify/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@ log.error = debug('libp2p:identify:error')

const {
MULTICODEC_IDENTIFY,
MULTICODEC_IDENTIFY_LEGACY,
MULTICODEC_IDENTIFY_PUSH,
MULTICODEC_IDENTIFY_PUSH_LEGACY,
AGENT_VERSION,
PROTOCOL_VERSION
} = require('./consts')

const errCode = require('err-code')
const { codes } = require('../errors')
const { messages, codes } = require('../errors')
const Envelope = require('../record-manager/envelope')
const PeerRecord = require('../record-manager/peer-record')

class IdentifyService {
/**
Expand Down Expand Up @@ -89,11 +93,27 @@ class IdentifyService {
push (connections) {
const pushes = connections.map(async connection => {
try {
const { stream } = await connection.newStream(MULTICODEC_IDENTIFY_PUSH)
const { protocol, stream } = await connection.newStream([MULTICODEC_IDENTIFY_PUSH, MULTICODEC_IDENTIFY_PUSH_LEGACY])

// Handle Legacy
if (protocol === MULTICODEC_IDENTIFY_PUSH_LEGACY) {
return pipe(
[{
listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.buffer),
protocols: Array.from(this._protocols.keys())
}],
pb.encode(Message),
stream,
consume
)
}

const envelope = this._libp2p.recordManager.getPeerRecord()
const signedPeerRecord = envelope.marshal()

await pipe(
[{
listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.buffer),
signedPeerRecord,
protocols: Array.from(this._protocols.keys())
}],
pb.encode(Message),
Expand Down Expand Up @@ -135,7 +155,7 @@ class IdentifyService {
* @returns {Promise<void>}
*/
async identify (connection) {
const { stream } = await connection.newStream(MULTICODEC_IDENTIFY)
const { protocol, stream } = await connection.newStream([MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_LEGACY])
const [data] = await pipe(
[],
stream,
Expand All @@ -160,7 +180,8 @@ class IdentifyService {
publicKey,
listenAddrs,
protocols,
observedAddr
observedAddr,
signedPeerRecord
} = message

const id = await PeerId.createFromPubKey(publicKey)
Expand All @@ -172,8 +193,40 @@ class IdentifyService {
// Get the observedAddr if there is one
observedAddr = IdentifyService.getCleanMultiaddr(observedAddr)

// LEGACY: differentiate message with SignedPeerRecord
if (protocol === MULTICODEC_IDENTIFY_LEGACY) {
// Update peers data in PeerStore
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
this.peerStore.protoBook.set(id, protocols)

// TODO: Track our observed address so that we can score it
log('received observed address of %s', observedAddr)

return
}

// Open envelope and verify if is authenticated
let envelope
try {
envelope = await Envelope.openAndCertify(signedPeerRecord, PeerRecord.DOMAIN)
} catch (err) {
log('received invalid envelope, discard it')
throw errCode(new Error(messages.ERR_INVALID_ENVELOPE), codes.ERR_INVALID_ENVELOPE)
}

// Decode peer record
let peerRecord
try {
peerRecord = await PeerRecord.createFromProtobuf(envelope.payload)
} catch (err) {
log('received invalid peer record, discard it')
throw errCode(new Error(messages.ERR_INVALID_PEER_RECORD), codes.ERR_INVALID_PEER_RECORD)
}

// TODO: Store as certified record

// Update peers data in PeerStore
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
this.peerStore.addressBook.set(id, peerRecord.multiaddrs.map((addr) => multiaddr(addr)))
this.peerStore.protoBook.set(id, protocols)
this.peerStore.metadataBook.set(id, 'AgentVersion', Buffer.from(message.agentVersion))

Expand All @@ -194,16 +247,20 @@ class IdentifyService {
switch (protocol) {
case MULTICODEC_IDENTIFY:
return this._handleIdentify({ connection, stream })
case MULTICODEC_IDENTIFY_LEGACY:
return this._handleIdentifyLegacy({ connection, stream })
case MULTICODEC_IDENTIFY_PUSH:
return this._handlePush({ connection, stream })
case MULTICODEC_IDENTIFY_PUSH_LEGACY:
return this._handlePushLegacy({ connection, stream })
default:
log.error('cannot handle unknown protocol %s', protocol)
}
}

/**
* Sends the `Identify` response to the requesting peer over the
* given `connection`
* Sends the `Identify` response with the Signed Peer Record
* to the requesting peer over the given `connection`
* @private
* @param {object} options
* @param {*} options.stream
Expand All @@ -215,6 +272,40 @@ class IdentifyService {
publicKey = this.peerId.pubKey.bytes
}

const envelope = this._libp2p.recordManager.getPeerRecord()
const signedPeerRecord = envelope.marshal()

const message = Message.encode({
protocolVersion: PROTOCOL_VERSION,
agentVersion: AGENT_VERSION,
publicKey,
signedPeerRecord,
observedAddr: connection.remoteAddr.buffer,
protocols: Array.from(this._protocols.keys())
})

pipe(
[message],
lp.encode(),
stream,
consume
)
}

/**
* Sends the `Identify` response with listen addresses (LEGACY)
* to the requesting peer over the given `connection`
* @private
* @param {object} options
* @param {*} options.stream
* @param {Connection} options.connection
*/
_handleIdentifyLegacy ({ connection, stream }) {
let publicKey = Buffer.alloc(0)
if (this.peerId.pubKey) {
publicKey = this.peerId.pubKey.bytes
}

const message = Message.encode({
protocolVersion: PROTOCOL_VERSION,
agentVersion: AGENT_VERSION,
Expand Down Expand Up @@ -259,6 +350,63 @@ class IdentifyService {
return log.error('received invalid message', err)
}

// Open envelope and verify if is authenticated
let envelope
try {
envelope = await Envelope.openAndCertify(message.signedPeerRecord, PeerRecord.DOMAIN)
} catch (err) {
log('received invalid envelope, discard it')
throw errCode(new Error(messages.ERR_INVALID_ENVELOPE), codes.ERR_INVALID_ENVELOPE)
}

// Decode peer record
let peerRecord
try {
peerRecord = await PeerRecord.createFromProtobuf(envelope.payload)
} catch (err) {
log('received invalid peer record, discard it')
throw errCode(new Error(messages.ERR_INVALID_PEER_RECORD), codes.ERR_INVALID_PEER_RECORD)
}

// Update peers data in PeerStore
const id = connection.remotePeer
try {
// TODO: Store as certified record

this.peerStore.addressBook.set(id, peerRecord.multiaddrs.map((addr) => multiaddr(addr)))
} catch (err) {
return log.error('received invalid listen addrs', err)
}

// Update the protocols
this.peerStore.protoBook.set(id, message.protocols)
}

/**
* Reads the Identify Push message from the given `connection`
* with listen addresses (LEGACY)
* @private
* @param {object} options
* @param {*} options.stream
* @param {Connection} options.connection
*/
async _handlePushLegacy ({ connection, stream }) {
const [data] = await pipe(
[],
stream,
lp.decode(),
take(1),
toBuffer,
collect
)

let message
try {
message = Message.decode(data)
} catch (err) {
return log.error('received invalid message', err)
}

// Update peers data in PeerStore
const id = connection.remotePeer
try {
Expand All @@ -279,6 +427,8 @@ module.exports.IdentifyService = IdentifyService
*/
module.exports.multicodecs = {
IDENTIFY: MULTICODEC_IDENTIFY,
IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH
IDENTIFY_LEGACY: MULTICODEC_IDENTIFY_LEGACY,
IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH,
IDENTIFY_PUSH_LEGACY: MULTICODEC_IDENTIFY_PUSH_LEGACY
}
module.exports.Message = Message
5 changes: 5 additions & 0 deletions src/identify/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ message Identify {
optional bytes observedAddr = 4;
repeated string protocols = 3;
// signedPeerRecord contains a serialized SignedEnvelope containing a PeerRecord,
// signed by the sending node. It contains the same addresses as the listenAddrs field, but
// in a form that lets us share authenticated addrs with other peers.
optional bytes signedPeerRecord = 8;
}
`

Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,9 @@ class Libp2p extends EventEmitter {
// Listen on the provided transports
await this.transportManager.listen()

// Start record Manager
await this.recordManager.start()

// Start PeerStore
await this.peerStore.start()

Expand Down
2 changes: 2 additions & 0 deletions src/record/peer-record/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,6 @@ PeerRecord.createFromProtobuf = (buf) => {
return new PeerRecord({ peerId, multiaddrs, seqNumber })
}

PeerRecord.DOMAIN = ENVELOPE_DOMAIN_PEER_RECORD

module.exports = PeerRecord
Loading

0 comments on commit e50f0ee

Please sign in to comment.