diff --git a/README.md b/README.md index d9e768c..c6b9528 100644 --- a/README.md +++ b/README.md @@ -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**. @@ -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) @@ -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"); }); @@ -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"); @@ -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. @@ -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 diff --git a/bin/findbutton b/bin/findbutton index 9b1bfc9..bd2c4d5 100755 --- a/bin/findbutton +++ b/bin/findbutton @@ -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)); } }); diff --git a/index.js b/index.js index 1183abe..231c116 100644 --- a/index.js +++ b/index.js @@ -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'"); @@ -19,9 +31,12 @@ 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") @@ -29,7 +44,7 @@ var register = function(mac_addresses, arp_interface, timeout) { //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 }); @@ -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 */ @@ -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; } diff --git a/test/lib/packets.js b/test/lib/packets.js index d7943ab..ba9cbc1 100644 --- a/test/lib/packets.js +++ b/test/lib/packets.js @@ -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, } -}; \ No newline at end of file +}; diff --git a/test/lib/pcap.js b/test/lib/pcap.js index 0e1d4e7..e6cc966 100644 --- a/test/lib/pcap.js +++ b/test/lib/pcap.js @@ -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(); @@ -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() { diff --git a/test/test.js b/test/test.js index 955d545..bfcf8d3 100644 --- a/test/test.js +++ b/test/test.js @@ -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!");