Skip to content

Commit

Permalink
Merge pull request #42 from kim3er/feature/udp
Browse files Browse the repository at this point in the history
supporting UDP as well as ARP
  • Loading branch information
hortinstein authored Sep 13, 2016
2 parents f969c3a + 7f3f926 commit 44b05f2
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 47 deletions.
28 changes: 20 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ It is a simple library that will allow you to utilize a dash button to emit an e
- [To do](#to-do)
- [Contributions](#contributions)
- [License](#license)

#### Installation Instructions
The following should work for ubuntu, the main thing for any os is getting the libpcap dependancy.
``` sh
# dependancy on libpcap for reading packets
$ sudo apt-get install libpcap-dev
$ npm install node-dash-button
$ npm install node-dash-button
```
#### First Time Dash Setup
#### First Time Dash Setup

Follow Amazon's instructions to configure your button to send messages when you push them but not actually order anything. When you get a Dash button, Amazon gives you a list of setup instructions to get going. Just follow this list of instructions, but don’t complete the final step (#3 I think) **Do not select a product, just exit the app**.

Expand All @@ -39,7 +39,7 @@ $ cd node_modules/node-dash-button
$ node bin/findbutton
```

It will watch for new arp requests on your network. There may be several arp requests, so press it a few times to make sure. Copy the hardware address as shown below
It will watch for new arp and udp requests on your network. There may be several such requests, so press it a few times to make sure. Copy the hardware address as shown below, and make a note of the protocol used.

![hw address](http://i.imgur.com/BngokPC.png)

Expand All @@ -53,7 +53,7 @@ as the first argument, such as `node bin/findbutton eth6`.
//warning this may trigger multiple times for one press
//...usually triggers twice based on testing for each press
var dash_button = require('node-dash-button');
var dash = dash_button("8f:3f:20:33:54:44"); //address from step above
var dash = dash_button("8f:3f:20:33:54:44", null, null, 'all'); //address from step above
dash.on("detected", function (){
console.log("omg found");
});
Expand All @@ -62,7 +62,7 @@ dash.on("detected", function (){
**For multiple dashes**:
```js
var dash_button = require('node-dash-button');
var dash = dash_button(["8f:3f:20:33:54:44","2e:3f:20:33:54:22"]); //address from step above
var dash = dash_button(["8f:3f:20:33:54:44","2e:3f:20:33:54:22"], null, null, 'all'); //address from step above
dash.on("detected", function (dash_id){
if (dash_id === "8f:3f:20:33:54:44"){
console.log("omg found");
Expand Down Expand Up @@ -92,6 +92,18 @@ dash.on("detected", function (){
});
```

**ARP, UDP or both**:
By default the protocol monitored is ARP, which is what the earlier buttons tend to use. Newer buttons however, are using UDP to make thier request. By setting protocol to 'arp', 'udp', or 'all' (both), you can optimise the script to your setup.

Note: If your button was initially picked up using ARP, but is now not being picked up, it's possible that the button has switched to UDP.

``` js
var dash_button = require('node-dash-button');
var dash = dash_button("8f:3f:20:33:54:44", null, null, "all"); //address from step above
dash.on("detected", function (){
console.log("omg found");
});
```

#### Running Tests:
Due to the use of pcap permiscuous monitoring this was difficult to test in CI environments, so I ended up making two testing suites. One uses the live pcap library and does actual packet capturing/arp injections. The other uses [mockery](https://github.com/mfncooper/mockery) to fake pcap packets. I will have an upcoming blog post on how I did this, because it was interesting.
Expand All @@ -110,14 +122,14 @@ npm test

#### Example Projects:
I collected a few examples I found on github of how people are using this module, some projects are more mature than others
- [PizzaDash](https://github.com/bhberson/pizzadash) uses a node dash to order Domino's pizza. [The Verge](http://www.theverge.com/2015/9/28/9407669/amazon-dash-button-hack-pizza), [Gizmodo](http://gizmodo.com/an-american-hero-hacked-an-amazon-dash-button-to-order-1733347471) and [Grubstreet](http://www.grubstreet.com/2015/09/amazon-dash-button-dominos-hack.html#) did short writeups on the PizzaDash project].
- [PizzaDash](https://github.com/bhberson/pizzadash) uses a node dash to order Domino's pizza. [The Verge](http://www.theverge.com/2015/9/28/9407669/amazon-dash-button-hack-pizza), [Gizmodo](http://gizmodo.com/an-american-hero-hacked-an-amazon-dash-button-to-order-1733347471) and [Grubstreet](http://www.grubstreet.com/2015/09/amazon-dash-button-dominos-hack.html#) did short writeups on the PizzaDash project].
- [dashgong](https://github.com/danboy/dashgong) uses the node dash to send a message to slack
- [dash-listener](https://github.com/dkordik/dash-listener) performs various home automation tasks like adjusting lights and interacting with a music player
- [dasher](https://github.com/maddox/dasher) lets you map a dash button press to an HTTP request.
- [Nest-Dash](https://github.com/djrausch/Nest-Dash) toggles the Nest setting from away to home via Amazon Dash Button
- [dash-hipchat-doorbell](https://github.com/Sfeinste/dash-hipchat-doorbell) quick and dirty node app that intercepts traffic from an amazon dash button and creates a hipchat notification (think doorbell)
- [netflixandchill](https://github.com/sidho/netflixandchill) button to netflix and chill, dims the lights (no interface with netflix yet)
- [dash-rickroll](https://github.com/girliemac/dash-rickroll/blob/8f0396c7fec871427fe016a2dd5787f07b1402cc/README.md) title explains it all
- [dash-rickroll](https://github.com/girliemac/dash-rickroll/blob/8f0396c7fec871427fe016a2dd5787f07b1402cc/README.md) title explains it all

#### To do
- refactor
Expand Down
34 changes: 24 additions & 10 deletions bin/findbutton
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,39 @@ var int_array_to_hex = require('../index.js').int_array_to_hex;
var create_session = require('../index.js').create_session;
var manufacturer_directory = require('../stor.js').manufacturer_directory;
var pcap = require('pcap');
var arp_interface = undefined;
var iface = undefined;
var last_argument = process.argv[process.argv.length - 1];
if (last_argument.indexOf('findbutton') === -1) {
arp_interface = last_argument;
iface = last_argument;
}
var pcap_session = create_session(arp_interface);
var pcap_session = create_session(iface, 'all');

console.log("Watching for arp requests on your local network, please try to press your dash now\nDash buttons should appear as manufactured by 'Amazon Technologies Inc.' ");
console.log("Watching for arp & udp requests on your local network, please try to press your dash now\nDash buttons should appear as manufactured by 'Amazon Technologies Inc.' ");

pcap_session.on('packet', function(raw_packet) {
var packet = pcap.decode.packet(raw_packet); //decodes the packet
if(packet.payload.ethertype === 2054) { //ensures it is an arp packet
possible_dash = packet.payload.payload.sender_ha.addr; //getting the hardware address of the possible dash
if(packet.payload.ethertype === 2054 || packet.payload.ethertype === 2048) { //ensures it is an arp or udp packet
var protocol, possible_dash;
if (packet.payload.ethertype === 2054) {
protocol = 'arp';
possible_dash = packet.payload.payload.sender_ha.addr; //getting the hardware address of the possible dash
}
else {
protocol = 'udp';
possible_dash = packet.payload.shost.addr;
}
possible_dash = int_array_to_hex(possible_dash);
if(manufacturer_directory.hasOwnProperty(possible_dash.slice(0,8).toString().toUpperCase().split(':').join('')
)){
console.log("Possible dash hardware address detected: ", possible_dash, " Manufacturer: ", manufacturer_directory[possible_dash.slice(0,8).toString().toUpperCase().split(':').join('')]);

var log = 'Possible dash hardware address detected: {0} Manufacturer: {1} Protocol: {2}',
manufacturerKey = possible_dash.slice(0,8).toString().toUpperCase().split(':').join(''),
manufacturer;

if(manufacturer_directory.hasOwnProperty(manufacturerKey)) {
manufacturer = manufacturer_directory[manufacturerKey];
} else {
console.log("Possible dash hardware address detected: ", possible_dash, " Manufacturer: ", "unknown");
manufacturer = 'unknown';
}

console.log(log.replace('{0}', possible_dash).replace('{1}', manufacturer).replace('{2}', protocol));
}
});
66 changes: 45 additions & 21 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,21 @@ var hex_to_int_array = require('./helpers.js').hex_to_int_array;
var int_array_to_hex = require('./helpers.js').int_array_to_hex;


var create_session = function (arp_interface) {
var create_session = function (iface, protocol) {
var filter;
switch(protocol) {
case 'all':
filter = 'arp or ( udp and ( port 67 or port 68 ) )';
break;
case 'udp':
filter = 'udp and ( port 67 or port 68 )';
break;
default:
filter = 'arp';
}

try {
var session = pcap.createSession(arp_interface, 'arp');
var session = pcap.createSession(iface, filter);
} catch (err) {
console.error(err);
console.error("Failed to create pcap session: couldn't find devices to listen on.\n" + "Try running with elevated privileges via 'sudo'");
Expand All @@ -19,17 +31,20 @@ var create_session = function (arp_interface) {
};

//Function to register the node button
var register = function(mac_addresses, arp_interface, timeout) {
var register = function(mac_addresses, iface, timeout, protocol) {
if (timeout === undefined || timeout === null) {
timeout = 5000;
timeout = 5000;
}
if (protocol === undefined || protocol === null) {
protocol = 'arp';
}
if (Array.isArray(mac_addresses)){
//console.log("array detected")
} else {
//console.log("single element detected")
mac_addresses = [mac_addresses];//cast to array
}
var pcap_session = create_session(arp_interface);
var pcap_session = create_session(iface, protocol);
var readStream = new stream.Readable({
objectMode: true
});
Expand All @@ -44,7 +59,7 @@ var register = function(mac_addresses, arp_interface, timeout) {
* Perform a try/catch on packet decoding until pcap
* offers a non-throwing mechanism to listen for errors
* (We're just ignoring these errors because TCP packets with an
* unknown offset should have no impact on this application)
* unknown offset should have no impact on this application)
*
* See https://github.com/mranney/node_pcap/issues/153
*/
Expand All @@ -55,31 +70,40 @@ var register = function(mac_addresses, arp_interface, timeout) {
return;
}

if(packet.payload.ethertype === 2054) { //ensures it is an arp packet
//for element in the mac addresses array
mac_addresses.forEach(function(mac_address){
if(!just_emitted[mac_address] &&
_.isEqual(packet.payload.payload.sender_ha.addr,
hex_to_int_array(mac_address))) {
readStream.emit('detected', mac_address);
just_emitted[mac_address] = true;
setTimeout(function () { just_emitted[mac_address] = false; }, timeout);
}
});
//for element in the mac addresses array
for (var i = 0, l = mac_addresses.length; i < l; i++) {
var mac_address = mac_addresses[i];

if((packet.payload.ethertype === 2054 //ensures it is an arp packet
&& _.isEqual(packet.payload.payload.sender_ha.addr,
hex_to_int_array(mac_address)))
|| (packet.payload.ethertype === 2048
&& _.isEqual(packet.payload.shost.addr,
hex_to_int_array(mac_address)))) {
if (just_emitted[mac_address]) {
break;
}

readStream.emit('detected', mac_address);
just_emitted[mac_address] = true;
setTimeout(function () { just_emitted[mac_address] = false; }, timeout);

break;
}
}
});
return readStream;
};

if (process.env.NODE_ENV === 'test') {
module.exports = { hex_to_int_array: hex_to_int_array,


module.exports = { hex_to_int_array: hex_to_int_array,
int_array_to_hex: int_array_to_hex,
create_session: create_session,
register: register
};

} else {
module.exports = register;
}
6 changes: 5 additions & 1 deletion test/lib/packets.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ module.exports = {
"bad": {
'packet_payload_ethertype': 666,
'packet_payload_payload_sender_ha_addr': hexes.first,
},
"udp": {
'packet_payload_ethertype': 2048,
'packet_payload_shost_addr': hexes.first,
}
};
};
20 changes: 14 additions & 6 deletions test/lib/pcap.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function pcap(){
}
pcap.prototype.createSession = function() {
if (this.badMode === true) {
throw new Error("Error: pcap_findalldevs didn't find any devs [mocked]");
throw new Error("Error: pcap_findalldevs didn't find any devs [mocked]");
}
//console.log("sending reference to fake event emitter")
this.session = new events.EventEmitter();
Expand All @@ -29,13 +29,21 @@ pcap.prototype.decode.packet = function(packet) {
var mock_packet = {
"payload": {
"ethertype": packet.packet_payload_ethertype,
"payload": {
"sender_ha": {
"addr": hex_to_int_array(packet.packet_payload_payload_sender_ha_addr)
}
}
"payload": { }
}
};

if (packet.packet_payload_ethertype === 2048) {
mock_packet.payload.shost = {
addr: hex_to_int_array(packet.packet_payload_shost_addr)
};
}
else {
mock_packet.payload.payload.sender_ha = {
addr: hex_to_int_array(packet.packet_payload_payload_sender_ha_addr)
};
}

return mock_packet;
};
pcap.prototype.enableBadMode = function() {
Expand Down
14 changes: 13 additions & 1 deletion test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,24 @@ startTests = function() {
});
pcap.getSession().emit('packet', packets.first);
});
it('should recognize an arp request, when listening to \'all\' protocols', function(done) {
dash_button.register(hexes.first, null, null, 'all').on('detected', function() {
done();
});
pcap.getSession().emit('packet', packets.first);
});
it('should recognize a udp request', function(done) {
dash_button.register(hexes.first, null, null, 'udp').on('detected', function() {
done();
});
pcap.getSession().emit('packet', packets.udp);
});
it('should not fire with more than 2 arp requests in 2 seconds', function(done) {
dash_button.register(hexes.second).on('detected', function() {
setTimeout(function() {
done();
}, 50);
//console.log("should only see this once")
//console.log("should only see this once")
});
for(count = 0; count < 10; count++) {
//console.log("firing packet!");
Expand Down

0 comments on commit 44b05f2

Please sign in to comment.