Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: exchange signed peer records in identify #682

Merged
merged 6 commits into from
Jul 15, 2020
Merged
Show file tree
Hide file tree
Changes from 3 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
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 @@ -2,5 +2,9 @@

module.exports.PROTOCOL_VERSION = 'ipfs/0.1.0'
module.exports.AGENT_VERSION = 'js-libp2p/0.1.0'
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'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are the versions listed, I was having trouble finding the updates. Is this in the specs repo or in go-libp2p?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit added them: libp2p/go-libp2p@077a818#diff-98be6dba669aa4123b7ef190fe7113e8

However, now that I looked further, the same protocol is now being used and go reverted this change per libp2p/go-libp2p#907 (comment)

I am doing the same


// Legacy
module.exports.MULTICODEC_IDENTIFY_1_0_0 = '/ipfs/id/1.0.0'
module.exports.MULTICODEC_IDENTIFY_PUSH_1_0_0 = '/ipfs/id/push/1.0.0'
134 changes: 120 additions & 14 deletions src/identify/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
'use strict'

const { Buffer } = require('buffer')
const debug = require('debug')
const log = debug('libp2p:identify')
log.error = debug('libp2p:identify:error')

const errCode = require('err-code')
const { Buffer } = require('buffer')
const pb = require('it-protocol-buffers')
const lp = require('it-length-prefixed')
const pipe = require('it-pipe')
Expand All @@ -13,18 +17,19 @@ const { toBuffer } = require('it-buffer')

const Message = require('./message')

const log = debug('libp2p:identify')
log.error = debug('libp2p:identify:error')
const Envelope = require('../record/envelope')
const PeerRecord = require('../record/peer-record')

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

const errCode = require('err-code')
const { codes } = require('../errors')
const { messages, codes } = require('../errors')

class IdentifyService {
/**
Expand Down Expand Up @@ -79,6 +84,9 @@ class IdentifyService {
this._protocols = protocols

this.handleMessage = this.handleMessage.bind(this)

// TODO: this should be stored in the certified AddressBook in follow up PR
this._selfRecord = undefined
}

/**
Expand All @@ -89,11 +97,13 @@ class IdentifyService {
push (connections) {
const pushes = connections.map(async connection => {
try {
const { stream } = await connection.newStream(MULTICODEC_IDENTIFY_PUSH)
const { stream } = await connection.newStream([MULTICODEC_IDENTIFY_PUSH, MULTICODEC_IDENTIFY_PUSH_1_0_0])
const signedPeerRecord = await this._getSelfPeerRecord()
jacobheun marked this conversation as resolved.
Show resolved Hide resolved

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 +145,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_1_0_0])
const [data] = await pipe(
[],
stream,
Expand All @@ -160,7 +170,8 @@ class IdentifyService {
publicKey,
listenAddrs,
protocols,
observedAddr
observedAddr,
signedPeerRecord
} = message

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

// LEGACY: differentiate message with SignedPeerRecord
if (protocol === MULTICODEC_IDENTIFY_1_0_0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to check the protocol, I'd just check the existence of signedPeerRecord. I think it might be better to catch and log the errors unmarshalling the signed peer record and then fallback to adding the listenAddrs. If for some reason the payload is malformed this will allow us to easily fallback.

// 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)

// TODO: Track our observed address so that we can score it
Expand All @@ -192,17 +235,19 @@ class IdentifyService {
handleMessage ({ connection, stream, protocol }) {
switch (protocol) {
case MULTICODEC_IDENTIFY:
case MULTICODEC_IDENTIFY_1_0_0:
return this._handleIdentify({ connection, stream })
case MULTICODEC_IDENTIFY_PUSH:
case MULTICODEC_IDENTIFY_PUSH_1_0_0:
return this._handlePush({ 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 @@ -214,11 +259,14 @@ class IdentifyService {
publicKey = this.peerId.pubKey.bytes
}

const signedPeerRecord = await this._getSelfPeerRecord()

const message = Message.encode({
protocolVersion: PROTOCOL_VERSION,
agentVersion: AGENT_VERSION,
publicKey,
listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.buffer),
signedPeerRecord,
observedAddr: connection.remoteAddr.buffer,
protocols: Array.from(this._protocols.keys())
})
Expand Down Expand Up @@ -258,17 +306,73 @@ class IdentifyService {
return log.error('received invalid message', err)
}

// Update peers data in PeerStore
const id = connection.remotePeer

// Legacy
if (!message.signedPeerRecord) {
try {
this.peerStore.addressBook.set(id, message.listenAddrs.map((addr) => multiaddr(addr)))
} catch (err) {
return log.error('received invalid listen addrs', err)
}

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

return
}
jacobheun marked this conversation as resolved.
Show resolved Hide resolved

// 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
try {
this.peerStore.addressBook.set(id, message.listenAddrs.map((addr) => multiaddr(addr)))
// 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)
}

/**
* Get self signed peer record raw envelope.
* @return {Buffer}
*/
async _getSelfPeerRecord () {
jacobheun marked this conversation as resolved.
Show resolved Hide resolved
// TODO: Verify if updated
jacobheun marked this conversation as resolved.
Show resolved Hide resolved
if (this._selfRecord) {
return this._selfRecord
}

const peerRecord = new PeerRecord({
peerId: this.peerId,
multiaddrs: this._libp2p.multiaddrs
})
const envelope = await Envelope.seal(peerRecord, this.peerId)

this._selfRecord = envelope.marshal()

return this._selfRecord
}
}

module.exports.IdentifyService = IdentifyService
Expand All @@ -278,6 +382,8 @@ module.exports.IdentifyService = IdentifyService
*/
module.exports.multicodecs = {
IDENTIFY: MULTICODEC_IDENTIFY,
IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH
IDENTIFY_1_0_0: MULTICODEC_IDENTIFY_1_0_0,
IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH,
IDENTIFY_PUSH_1_0_0: MULTICODEC_IDENTIFY_PUSH_1_0_0
}
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
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