Skip to content

Commit

Permalink
feat: server factory integration (#5)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Lando <[email protected]>
  • Loading branch information
getlarge and robertsLando authored Oct 27, 2020
1 parent 0d35379 commit cda32b3
Show file tree
Hide file tree
Showing 8 changed files with 438 additions and 471 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ on: [push, pull_request]

jobs:
test:
timeout-minutes: 5
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [8.x, 10.x, 12.x, 13.x]
node-version: [10.x, 12.x, 14.x]

steps:
- uses: actions/checkout@v1
Expand All @@ -29,7 +30,7 @@ jobs:
- name: Coveralls Parallel
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.github_token }}
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel: true

coverage:
Expand All @@ -40,4 +41,4 @@ jobs:
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true
parallel-finished: true
20 changes: 6 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

Protocol decoder for Aedes MQTT Broker

The purpose of this module is to be used inside [aedes](https://github.com/moscajs/aedes) `decodeProtocol` hook, which is called when aedes instance receives a first valid buffer from client ( before CONNECT packet). The client object state is in default and its connected state is false.
The function extract socket details and if aedes `trustProxy` option is set to true, it will first parse http headers (x-real-ip | x-forwarded-for) and proxy protocol (v1 and v2) to retrieve information in client.connDetails.
The purpose of this module is to be used inside [aedes-server-factory](https://github.com/moscajs/aedes-server-factory) `bindConnection` function, which is called when the server receives a connection from client (before CONNECT packet). The client object state is in default and its connected state is false.
The function extract socket details and if `aedes-server-factory` `trustProxy` option is set to true, it will first parse http headers (x-real-ip | x-forwarded-for) and/or proxy protocol (v1 and v2), then passing the informations to `aedes` that will assign them to `client.connDetails`.

The function `protocolDecoder` returns [ConnectionDetails](./types/index.d.ts), if the object contains data property, it will be parsed as an mqtt-packet.
The function `protocolDecoder` and `extractSocketDetails` returns [ConnectionDetails](./types/index.d.ts), if the object contains `data` property, it will be parsed as an [mqtt-packet](https://github.com/mqttjs/mqtt-packet).

## Install

Expand All @@ -30,30 +30,22 @@ npm install aedes-protocol-decoder --save
```js
var aedes = require('aedes')
var { protocolDecoder } = require('aedes-protocol-decoder')
var net = require('net')
var { createServer } = require('aedes-server-factory')
var port = 1883

var broker = aedes({
decodeProtocol: function (client, buffer) {
var proto = protocolDecoder(client, buffer)
return proto
},
preConnect: function (client, done) {
preConnect: function (client, packet, done) {
if (client.connDetails && client.connDetails.ipAddress) {
client.ip = client.connDetails.ipAddress
}
return done(null, true)
},
trustProxy: true
})

var server = net.createServer(broker.handle)

var server = createServer(broker, { trustProxy: true, protocolDecoder })
server.listen(port, function () {
console.log('server listening on port', port)
})


```

## License
Expand Down
16 changes: 6 additions & 10 deletions example.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ var aedes = require('aedes')
var mqttPacket = require('mqtt-packet')
var net = require('net')
var proxyProtocol = require('proxy-protocol-js')
var protocolDecoder = require('./lib/protocol-decoder')
var { createServer } = require('aedes-server-factory')
var { extractSocketDetails, protocolDecoder } = require('./index')

var brokerPort = 4883

Expand Down Expand Up @@ -119,22 +120,17 @@ function sendProxyPacket (version = 1, ipFamily = 4) {

function startAedes () {
var broker = aedes({
decodeProtocol: function (client, buffer) {
var proto = protocolDecoder(client, buffer)
return proto
},
preConnect: function (client, done) {
preConnect: function (client, packet, done) {
console.log('Aedes preConnect check client ip:', client.connDetails)
if (client.connDetails && client.connDetails.ipAddress) {
client.ip = client.connDetails.ipAddress
}
client.close()
return done(null, true)
},
trustProxy: true
done(null, true)
}
})

var server = require('net').createServer(broker.handle)
var server = createServer(broker, { trustProxy: true, extractSocketDetails, protocolDecoder })

server.listen(brokerPort, function () {
console.log('Aedes listening on :', server.address())
Expand Down
129 changes: 128 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,132 @@
var protocolDecoder = require('./lib/protocol-decoder')
'use strict'

const proxyProtocol = require('proxy-protocol-js')
const forwarded = require('forwarded')

const v1ProxyProtocolSignature = Buffer.from('PROXY ', 'utf8')
const v2ProxyProtocolSignature = Buffer.from('0d0a0d0a000d0a515549540a', 'hex')

function isValidV1ProxyProtocol (buffer) {
for (let i = 0; i < v1ProxyProtocolSignature.length; i++) {
if (buffer[i] !== v1ProxyProtocolSignature[i]) {
return false
}
}
return true
}

function isValidV2ProxyProtocol (buffer) {
for (let i = 0; i < v2ProxyProtocolSignature.length; i++) {
if (buffer[i] !== v2ProxyProtocolSignature[i]) {
return false
}
}
return true
}

// from https://stackoverflow.com/questions/57077161/how-do-i-convert-hex-buffer-to-ipv6-in-javascript
function parseIpV6Array (ip) {
const ipHex = Buffer.from(ip).toString('hex')
return ipHex.match(/.{1,4}/g)
.map((val) => val.replace(/^0+/, ''))
.join(':')
.replace(/0000:/g, ':')
.replace(/:{2,}/g, '::')
}

function getProtoIpFamily (ipFamily) {
if (ipFamily && ipFamily.endsWith('4')) {
return 4
} else if (ipFamily && ipFamily.endsWith('6')) {
return 6
}
return 0
}

function extractHttpDetails (req, socket, proto = {}) {
const headers = req && req.headers ? req.headers : null
if (headers) {
if (headers['x-forwarded-for']) {
const addresses = forwarded(req)
proto.ipAddress = headers['x-real-ip'] ? headers['x-real-ip'] : addresses[addresses.length - 1]
proto.serverIpAddress = addresses[0]
}
if (headers['x-real-ip']) {
proto.ipAddress = headers['x-real-ip']
}
proto.port = socket._socket.remotePort
proto.ipFamily = getProtoIpFamily(socket._socket.remoteFamily)
proto.isWebsocket = true
}
return proto
}

function extractProxyDetails (buffer, proto = {}) {
let proxyProto
if (isValidV1ProxyProtocol(buffer)) {
proxyProto = proxyProtocol.V1BinaryProxyProtocol.parse(buffer)
if (proxyProto && proxyProto.source && proxyProto.data) {
proto.ipFamily = getProtoIpFamily(proxyProto.inetProtocol)
proto.ipAddress = proxyProto.source.ipAddress
proto.port = proxyProto.source.port
proto.serverIpAddress = proxyProto.destination.ipAddress
proto.data = proxyProto.data
proto.isProxy = 1
}
} else if (isValidV2ProxyProtocol(buffer)) {
proxyProto = proxyProtocol.V2ProxyProtocol.parse(buffer)
if (proxyProto && proxyProto.proxyAddress && proxyProto.data) {
if (proxyProto.proxyAddress instanceof proxyProtocol.IPv4ProxyAddress) {
proto.ipAddress = proxyProto.proxyAddress.sourceAddress.address.join('.')
proto.port = proxyProto.proxyAddress.sourcePort
proto.serverIpAddress = proxyProto.proxyAddress.destinationAddress.address.join('.')
proto.ipFamily = 4
} else if (proxyProto.proxyAddress instanceof proxyProtocol.IPv6ProxyAddress) {
proto.ipAddress = parseIpV6Array(proxyProto.proxyAddress.sourceAddress.address)
proto.port = proxyProto.proxyAddress.sourcePort
proto.serverIpAddress = parseIpV6Array(proxyProto.proxyAddress.destinationAddress.address)
proto.ipFamily = 6
}
proto.isProxy = 2
proto.data = Buffer.isBuffer(proxyProto.data) ? proxyProto.data : Buffer.from(proxyProto.data)
}
}
return proto
}

function extractSocketDetails (socket, proto = {}) {
if (socket._socket && socket._socket.address) {
proto.isWebsocket = true
proto.ipAddress = socket._socket.remoteAddress
proto.port = socket._socket.remotePort
proto.serverIpAddress = socket._socket.address().address
proto.ipFamily = getProtoIpFamily(socket._socket.remoteFamily)
} else if (socket.address) {
proto.ipAddress = socket.remoteAddress
proto.port = socket.remotePort
proto.serverIpAddress = socket.address().address
proto.ipFamily = getProtoIpFamily(socket.remoteFamily)
}
return proto
}

function protocolDecoder (conn, buffer, req) {
const proto = {}
if (!buffer) return proto
const socket = conn.socket || conn
proto.isProxy = 0
proto.isWebsocket = false
extractHttpDetails(req, socket, proto)
extractProxyDetails(buffer, proto)

if (!proto.ipAddress) {
extractSocketDetails(socket, proto)
}

return proto
}

module.exports = {
extractSocketDetails,
protocolDecoder
}
145 changes: 0 additions & 145 deletions lib/protocol-decoder.js

This file was deleted.

Loading

0 comments on commit cda32b3

Please sign in to comment.