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

Server factory integration #5

Merged
merged 25 commits into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6098aeb
update dependencies
getlarge Oct 14, 2020
cd5b668
refactor protocol decoder and export extractSocketDetails
getlarge Oct 14, 2020
efeef3d
update example, tests and docs
getlarge Oct 14, 2020
8ab1350
update types
getlarge Oct 14, 2020
3a5a81e
change lib path and update test
getlarge Oct 14, 2020
a98ee24
quick fix
getlarge Oct 14, 2020
fa6a946
fix port getters for Proxy v2
getlarge Oct 14, 2020
7fc3cbc
Update createServer signature
getlarge Oct 14, 2020
e43176b
Fix test end
getlarge Oct 14, 2020
0ae0871
update doc
getlarge Oct 15, 2020
5306908
fix test issues with node 10
getlarge Oct 15, 2020
81f6521
chore: drop nodejs 8, added nodejs 14
robertsLando Oct 26, 2020
ace99b9
refactor tests
getlarge Oct 26, 2020
27a1d23
minor improvements
getlarge Oct 26, 2020
0780d46
Merge branch 'server-factory-integration' of https://github.com/getla…
getlarge Oct 26, 2020
07e7f88
chore(ci): disable parallel tests
robertsLando Oct 26, 2020
bea03af
chore(ci): removed parallel from coverall
robertsLando Oct 26, 2020
31d027e
Fix test not ending
getlarge Oct 26, 2020
835a412
Merge branch 'server-factory-integration' of https://github.com/getla…
getlarge Oct 26, 2020
c789d19
Reenable parallel tests
getlarge Oct 26, 2020
7310ac2
test fix
robertsLando Oct 26, 2020
ddfabb9
udpate aedes
getlarge Oct 26, 2020
18f9212
replace spread operator
getlarge Oct 27, 2020
52ca790
Merge branch 'server-factory-integration' of https://github.com/getla…
getlarge Oct 27, 2020
0982081
fix package.json
getlarge Oct 27, 2020
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
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)
}
robertsLando marked this conversation as resolved.
Show resolved Hide resolved

return proto
}

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

This file was deleted.

Loading