├── .gitignore ├── README.md ├── example ├── ping-packet-size.js ├── ping-response-time.js ├── ping-retries-0.js ├── ping-ttl.js ├── ping.js ├── ping6-ttl.js ├── ping6.js ├── trace-route-max-hop-timeouts.js ├── trace-route-start-ttl.js └── trace-route.js ├── index.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # net-ping 3 | 4 | This module implements ICMP Echo (ping) support for [Node.js][nodejs]. 5 | 6 | This module is installed using [node package manager (npm)][npm]: 7 | 8 | npm install net-ping 9 | 10 | It is loaded using the `require()` function: 11 | 12 | var ping = require ("net-ping"); 13 | 14 | A ping session can then be created to ping or trace route to many hosts: 15 | 16 | var session = ping.createSession (); 17 | 18 | session.pingHost (target, function (error, target) { 19 | if (error) 20 | console.log (target + ": " + error.toString ()); 21 | else 22 | console.log (target + ": Alive"); 23 | }); 24 | 25 | [nodejs]: http://nodejs.org "Node.js" 26 | [npm]: https://npmjs.org/ "npm" 27 | 28 | # Network Protocol Support 29 | 30 | This module supports IPv4 using the ICMP, and IPv6 using the ICMPv6. 31 | 32 | # Error Handling 33 | 34 | Each request exposed by this module requires one or more mandatory callback 35 | functions. Callback functions are typically provided an `error` argument. 36 | 37 | All errors are sub-classes of the `Error` class. For timed out errors the 38 | error passed to the callback function will be an instance of the 39 | `ping.RequestTimedOutError` class, with the exposed `message` attribute set 40 | to `Request timed out`. 41 | 42 | This makes it easy to determine if a host responded, a time out occurred, or 43 | whether an error response was received: 44 | 45 | session.pingHost ("1.2.3.4", function (error, target) { 46 | if (error) 47 | if (error instanceof ping.RequestTimedOutError) 48 | console.log (target + ": Not alive"); 49 | else 50 | console.log (target + ": " + error.toString ()); 51 | else 52 | console.log (target + ": Alive"); 53 | }); 54 | 55 | In addition to the the `ping.RequestTimedOutError` class, the following errors 56 | are also exported by this module to wrap ICMP error responses: 57 | 58 | * `DestinationUnreachableError` 59 | * `PacketTooBigError` 60 | * `ParameterProblemError` 61 | * `RedirectReceivedError` 62 | * `SourceQuenchError` 63 | * `TimeExceededError` 64 | 65 | These errors are typically reported by hosts other than the intended target. 66 | In all cases each class exposes a `source` attribute which will specify the 67 | host who reported the error (which could be the intended target). This will 68 | also be included in the errors `message` attribute, i.e.: 69 | 70 | $ sudo node example/ping-ttl.js 1 192.168.2.10 192.168.2.20 192.168.2.30 71 | 192.168.2.10: Alive 72 | 192.168.2.20: TimeExceededError: Time exceeded (source=192.168.1.1) 73 | 192.168.2.30: Not alive 74 | 75 | The `Session` class will emit an `error` event for any other error not 76 | directly associated with a request. This is typically an instance of the 77 | `Error` class with the errors `message` attribute specifying the reason. 78 | 79 | # Packet Size 80 | 81 | By default ICMP echo request packets sent by this module are 16 bytes in size. 82 | Some implementations cannot cope with such small ICMP echo requests. For 83 | example, some implementations will return an ICMP echo reply, but will include 84 | an incorrect ICMP checksum. 85 | 86 | This module exposes a `packetSize` option to the `createSession()` method which 87 | specifies how big ICMP echo request packets should be: 88 | 89 | var session = ping.createSession ({packetSize: 64}); 90 | 91 | # Round Trip Times 92 | 93 | Some callbacks used by methods exposed by this module provide two instances of 94 | the JavaScript `Date` class specifying when the first ping was sent for a 95 | request, and when a request completed. 96 | 97 | These parameters are typically named `sent` and `rcvd`, and are provided to 98 | help round trip time calculation. 99 | 100 | A request can complete in one of two ways. In the first, a ping response is 101 | received and `rcvd - sent` will yield the round trip time for the request in 102 | milliseconds. 103 | 104 | In the second, no ping response is received resulting in a request time out. 105 | In this case `rcvd - sent` will yield the total time spent waiting for each 106 | retry to timeout if any. For example, if the `retries` option to the 107 | `createSession()` method was specified as `2` and `timeout` as `2000` then 108 | `rcvd - sent` will yield more than `6000` milliseconds. 109 | 110 | Although this module provides instances of the `Date` class to help round trip 111 | time calculation the dates and times represented in each instance should not be 112 | considered 100% accurate. 113 | 114 | Environmental conditions can affect when a date and time is actually 115 | calculated, e.g. garbage collection introducing a delay or the receipt of many 116 | packets at once. There are also a number of functions through which received 117 | packets must pass, which can also introduce a slight variable delay. 118 | 119 | Throughout development experience has shown that, in general the smaller the 120 | round trip time the less accurate it will be - but the information is still 121 | useful nonetheless. 122 | 123 | # Constants 124 | 125 | The following sections describe constants exported and used by this module. 126 | 127 | ## ping.NetworkProtocol 128 | 129 | This object contains constants which can be used for the `networkProtocol` 130 | option to the `createSession()` function exposed by this module. This option 131 | specifies the IP protocol version to use when creating the raw socket. 132 | 133 | The following constants are defined in this object: 134 | 135 | * `IPv4` - IPv4 protocol 136 | * `IPv6` - IPv6 protocol 137 | 138 | # Using This Module 139 | 140 | The `Session` class is used to issue ping and trace route requests to many 141 | hosts. This module exports the `createSession()` function which is used to 142 | create instances of the `Session` class. 143 | 144 | ## ping.createSession ([options]) 145 | 146 | The `createSession()` function instantiates and returns an instance of the 147 | `Session` class: 148 | 149 | // Default options 150 | var options = { 151 | networkProtocol: ping.NetworkProtocol.IPv4, 152 | packetSize: 16, 153 | retries: 1, 154 | sessionId: (process.pid % 65535), 155 | timeout: 2000, 156 | ttl: 128 157 | }; 158 | 159 | var session = ping.createSession (options); 160 | 161 | The optional `options` parameter is an object, and can contain the following 162 | items: 163 | 164 | * `networkProtocol` - Either the constant `ping.NetworkProtocol.IPv4` or the 165 | constant `ping.NetworkProtocol.IPv6`, defaults to the constant 166 | `ping.NetworkProtocol.IPv4` 167 | * `packetSize` - How many bytes each ICMP echo request packet should be, 168 | defaults to `16`, if the value specified is less that `12` then the value 169 | `12` will be used (8 bytes are required for the ICMP packet itself, then 4 170 | bytes are required to encode a unique session ID in the request and response 171 | packets) 172 | * `retries` - Number of times to re-send a ping requests, defaults to `1` 173 | * `sessionId` - A unique ID used to identify request and response packets sent 174 | by this instance of the `Session` class, valid numbers are in the range of 175 | `1` to `65535`, defaults to the value of `process.pid % 65535` 176 | * `timeout` - Number of milliseconds to wait for a response before re-trying 177 | or failing, defaults to `2000` 178 | * `ttl` - Value to use for the IP header time to live field, defaults to `128` 179 | 180 | After creating the ping `Session` object an underlying raw socket will be 181 | created. If the underlying raw socket cannot be opened an exception with be 182 | thrown. The error will be an instance of the `Error` class. 183 | 184 | Seperate instances of the `Session` class must be created for IPv4 and IPv6. 185 | 186 | ## session.on ("close", callback) 187 | 188 | The `close` event is emitted by the session when the underlying raw socket 189 | is closed. 190 | 191 | No arguments are passed to the callback. 192 | 193 | The following example prints a message to the console when the underlying raw 194 | socket is closed: 195 | 196 | session.on ("close", function () { 197 | console.log ("socket closed"); 198 | }); 199 | 200 | ## session.on ("error", callback) 201 | 202 | The `error` event is emitted by the session when the underlying raw socket 203 | emits an error. 204 | 205 | The following arguments will be passed to the `callback` function: 206 | 207 | * `error` - An instance of the `Error` class, the exposed `message` attribute 208 | will contain a detailed error message. 209 | 210 | The following example prints a message to the console when an error occurs 211 | with the underlying raw socket, the session is then closed: 212 | 213 | session.on ("error", function (error) { 214 | console.log (error.toString ()); 215 | session.close (); 216 | }); 217 | 218 | ## session.close () 219 | 220 | The `close()` method closes the underlying raw socket, and cancels all 221 | outstanding requsts. 222 | 223 | The calback function for each outstanding ping requests will be called. The 224 | error parameter will be an instance of the `Error` class, and the `message` 225 | attribute set to `Socket forcibly closed`. 226 | 227 | The sessoin can be re-used simply by submitting more ping requests, a new raw 228 | socket will be created to serve the new ping requests. This is a way in which 229 | to clear outstanding requests. 230 | 231 | The following example submits a ping request and prints the target which 232 | successfully responded first, and then closes the session which will clear the 233 | other outstanding ping requests. 234 | 235 | var targets = ["1.1.1.1", "2.2.2.2", "3.3.3.3"]; 236 | 237 | for (var i = 0; i < targets.length; i++) { 238 | session.pingHost (targets[i], function (error, target) { 239 | if (! error) { 240 | console.log (target); 241 | session.close (); 242 | } 243 | }); 244 | } 245 | 246 | ## session.getSocket () 247 | 248 | The `getSocket()` method returns the underlying raw socket used by the session. 249 | This class is an instance of the `Socket` class exposed by the 250 | [raw-socket][raw-socket] module. This can be used to modify properties of the 251 | raw socket, such as specifying which network interface ICMP messages should be 252 | sent from. 253 | 254 | In the following example the network interface from which to send ICMP messages 255 | is set: 256 | 257 | var raw = require("raw-socket") // Required for access to constants 258 | 259 | var level = raw.SocketLevel.SOL_SOCKET 260 | var option = raw.SocketOption.SO_BINDTODEVICE 261 | 262 | var iface = Buffer.from("eth0") 263 | 264 | session.getSocket().setOption(level, option, iface, iface.length) 265 | 266 | [raw-socket]: https://www.npmjs.com/package/raw-socket "raw-socket" 267 | 268 | ## session.pingHost (target, callback) 269 | 270 | The `pingHost()` method sends a ping request to a remote host. 271 | 272 | The `target` parameter is the dotted quad formatted IP address of the target 273 | host for IPv4 sessions, or the compressed formatted IP address of the target 274 | host for IPv6 sessions. 275 | 276 | The `callback` function is called once the ping requests is complete. The 277 | following arguments will be passed to the `callback` function: 278 | 279 | * `error` - Instance of the `Error` class or a sub-class, or `null` if no 280 | error occurred 281 | * `target` - The target parameter as specified in the request 282 | still be the target host and NOT the responding gateway 283 | * `sent` - An instance of the `Date` class specifying when the first ping 284 | was sent for this request (refer to the Round Trip Time section for more 285 | information) 286 | * `rcvd` - An instance of the `Date` class specifying when the request 287 | completed (refer to the Round Trip Time section for more information) 288 | 289 | The following example sends a ping request to a remote host: 290 | 291 | var target = "fe80::a00:27ff:fe2a:3427"; 292 | 293 | session.pingHost (target, function (error, target, sent, rcvd) { 294 | var ms = rcvd - sent; 295 | if (error) 296 | console.log (target + ": " + error.toString ()); 297 | else 298 | console.log (target + ": Alive (ms=" + ms + ")"); 299 | }); 300 | 301 | ## session.traceRoute (target, ttlOrOptions, feedCallback, doneCallback) 302 | 303 | The `traceRoute()` method provides similar functionality to the trace route 304 | utility typically provided with most networked operating systems. 305 | 306 | The `target` parameter is the dotted quad formatted IP address of the target 307 | host for IPv4 sessions, or the compressed formatted IP address of the target 308 | host for IPv6 sessions. The optional `ttlOrOptions` parameter can be either a 309 | number which specifies the maximum number of hops used by the trace route, 310 | which defaults to the `ttl` options parameter as defined by the 311 | `createSession()` method, or an object which can contain the following 312 | parameters: 313 | 314 | * `ttl` - The maximum number of hops used by the trace route, defaults to the 315 | `ttl` options parameter as defined by the `createSession()` method 316 | * `maxHopTimeouts` - The maximum number of hop timeouts that should occur, 317 | defaults to `3` 318 | * `startTtl` - Starting ttl for the trace route, defaults to `1` 319 | 320 | Some hosts do not respond to ping requests when the time to live is `0`, that 321 | is they will not send back an time exceeded error response. Instead of 322 | stopping the trace route at the first time out this method will move on to the 323 | next hop, by increasing the time to live by 1. It will do this 2 times by 324 | default, meaning that a trace route will continue until the target host 325 | responds or at most 3 request time outs are experienced. The `maxHopTimeouts` 326 | option above can be used to control how many hop timeouts can occur. 327 | 328 | Each requst is subject to the `retries` and `timeout` option parameters to the 329 | `createSession()` method. That is, requests will be retried per hop as per 330 | these parameters. 331 | 332 | This method will not call a single callback once the trace route is complete. 333 | Instead the `feedCallback` function will be called each time a ping response is 334 | received or a time out occurs. The following arguments will be passed to the 335 | `feedCallback` function: 336 | 337 | * `error` - Instance of the `Error` class or a sub-class, or `null` if no 338 | error occurred 339 | * `target` - The target parameter as specified in the request 340 | * `ttl` - The time to live used in the request which triggered this respinse 341 | * `sent` - An instance of the `Date` class specifying when the first ping 342 | was sent for this request (refer to the Round Trip Time section for more 343 | information) 344 | * `rcvd` - An instance of the `Date` class specifying when the request 345 | completed (refer to the Round Trip Time section for more information) 346 | 347 | Once a ping response has been received from the target, or more than three 348 | request timed out errors are experienced, the `doneCallback` function will be 349 | called. The following arguments will be passed to the `doneCallback` function: 350 | 351 | * `error` - Instance of the `Error` class or a sub-class, or `null` if no 352 | error occurred 353 | * `target` - The target parameter as specified in the request 354 | 355 | Once the `doneCallback` function has been called the request is complete and 356 | the `requestCallback` function will no longer be called. 357 | 358 | If the `feedCallback` function returns a true value when called the trace route 359 | will stop and the `doneCallback` will be called. 360 | 361 | The following example initiates a trace route to a remote host: 362 | 363 | function doneCb (error, target) { 364 | if (error) 365 | console.log (target + ": " + error.toString ()); 366 | else 367 | console.log (target + ": Done"); 368 | } 369 | 370 | function feedCb (error, target, ttl, sent, rcvd) { 371 | var ms = rcvd - sent; 372 | if (error) { 373 | if (error instanceof ping.TimeExceededError) { 374 | console.log (target + ": " + error.source + " (ttl=" 375 | + ttl + " ms=" + ms +")"); 376 | } else { 377 | console.log (target + ": " + error.toString () 378 | + " (ttl=" + ttl + " ms=" + ms +")"); 379 | } 380 | } else { 381 | console.log (target + ": " + target + " (ttl=" + ttl 382 | + " ms=" + ms +")"); 383 | } 384 | } 385 | 386 | session.traceRoute ("192.168.10.10", 10, feedCb, doneCb); 387 | 388 | # Example Programs 389 | 390 | Example programs are included under the modules `example` directory. 391 | 392 | # Changes 393 | 394 | ## Version 1.0.0 - 03/02/2013 395 | 396 | * Initial release 397 | 398 | ## Version 1.0.1 - 04/02/2013 399 | 400 | * Minor corrections to the README.md 401 | * Add note to README.md about error handling 402 | * Timed out errors are now instances of the `ping.RequestTimedOutError` 403 | object 404 | 405 | ## Version 1.0.2 - 11/02/2013 406 | 407 | * The RequestTimedOutError class is not being exported 408 | 409 | ## Version 1.1.0 - 13/02/2013 410 | 411 | * Support IPv6 412 | 413 | ## Version 1.1.1 - 15/02/2013 414 | 415 | * The `ping.Session.close()` method was not undefining the sessions raw 416 | socket after closing 417 | * Return self from the `pingHost()` method to chain method calls 418 | 419 | ## Version 1.1.2 - 04/03/2013 420 | 421 | * Use the `raw.Socket.pauseRecv()` and `raw.Socket.resumeRecv()` methods 422 | instead of closing a socket when there are no more outstanding requests 423 | 424 | ## Version 1.1.3 - 07/03/2013 425 | 426 | * Sessions were limited to sending 65535 ping requests 427 | 428 | ## Version 1.1.4 - 09/04/2013 429 | 430 | * Add the `packetSize` option to the `createSession()` method to specify how 431 | many bytes each ICMP echo request packet should be 432 | 433 | ## Version 1.1.5 - 17/05/2013 434 | 435 | * Incorrectly parsing ICMP error responses resulting in responses matching 436 | the wrong request 437 | * Use a unique session ID per instance of the `Session` class to identify 438 | requests and responses sent by a session 439 | * Added the (internal) `_debugRequest()` and `_debugResponse()` methods, and 440 | the `_debug` option to the `createSession()` method 441 | * Added example programs `ping-ttl.js` and `ping6-ttl.js` 442 | * Use MIT license instead of GPL 443 | 444 | ## Version 1.1.6 - 17/05/2013 445 | 446 | * Session IDs are now 2 bytes (previously 1 byte), and request IDs are also 447 | now 2 bytes (previously 3 bytes) 448 | * Each ICMP error response now has an associated error class (e.g. the 449 | `Time exceeded` response maps onto the `ping.TimeExceededError` class) 450 | * Call request callbacks with an error when there are no free request IDs 451 | because of too many outstanding requests 452 | 453 | ## Version 1.1.7 - 19/05/2013 454 | 455 | * Added the `traceRoute()` method 456 | * Added the `ttl` option parameter to the `createSession()` method, and 457 | updated the example programs `ping-ttl.js` and `ping6-ttl.js` to use it 458 | * Response callback for `pingHost()` now includes two instances of the 459 | `Date` class to specify when a request was sent and a response received 460 | 461 | ## Version 1.1.8 - 01/07/2013 462 | 463 | * Use `raw.Socket.createChecksum()` instead of automatic checksum generation 464 | 465 | ## Version 1.1.9 - 01/07/2013 466 | 467 | * Use `raw.Socket.writeChecksum()` instead of manually rendering checksums 468 | 469 | ## Version 1.1.10 - 02/04/2014 470 | 471 | * Echo requests sent by this module are processed like responses when sent to 472 | the `127.0.0.1` and `::1` addresses 473 | 474 | ## Version 1.1.11 - 12/08/2014 475 | 476 | * Cannot specify the `retries` parameter for the `Session` class as `0` 477 | * Added example program `ping-retries-0.js` 478 | 479 | ## Version 1.1.12 - 22/09/2015 480 | 481 | * Host repository on GitHub 482 | 483 | ## Version 1.2.0 - 29/02/2016 484 | 485 | * Wrong callback called in the `traceRoute()` method when a session ID cannot 486 | be generated 487 | * Renamed the optional `ttl` parameter to the `traceRoute()` method to 488 | `ttlOrOptions`, and it is still optional 489 | * Permit users to control the number of permitted hop timeouts for the 490 | `traceRoute()` method, added the `maxHopTimeouts` parameter to the 491 | `traceRoute()` methods options 492 | * The `traceRoute()` method can start its trace at a higher ttl, added the 493 | `startTtl` parameter to the `traceRoute()` methods options 494 | * The `_expandConstantObject()` function was declaring variables with global 495 | scope 496 | 497 | ## Version 1.2.1 - 14/07/2017 498 | 499 | * Document the `Socket.getSocket()` method 500 | 501 | ## Version 1.2.2 - 06/06/2018 502 | 503 | * Set NoSpaceships Ltd to be the owner and maintainer 504 | 505 | ## Version 1.2.3 - 07/06/2018 506 | 507 | * Remove redundant sections from README.md 508 | 509 | ## Version 1.2.4 - 08/04/2024 510 | 511 | * Fix deprecation warning for `new Buffer()` 512 | 513 | # License 514 | 515 | Copyright (c) 2018 NoSpaceships Ltd 516 | 517 | Copyright (c) 2013 Stephen Vickers 518 | 519 | Permission is hereby granted, free of charge, to any person obtaining a copy 520 | of this software and associated documentation files (the "Software"), to deal 521 | in the Software without restriction, including without limitation the rights 522 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 523 | copies of the Software, and to permit persons to whom the Software is 524 | furnished to do so, subject to the following conditions: 525 | 526 | The above copyright notice and this permission notice shall be included in 527 | all copies or substantial portions of the Software. 528 | 529 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 530 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 531 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 532 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 533 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 534 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 535 | THE SOFTWARE. 536 | -------------------------------------------------------------------------------- /example/ping-packet-size.js: -------------------------------------------------------------------------------- 1 | 2 | var ping = require ("../"); 3 | 4 | if (process.argv.length < 3) { 5 | console.log ("usage: node ping [ ...]"); 6 | process.exit (-1); 7 | } 8 | 9 | var targets = []; 10 | 11 | for (var i = 2; i < process.argv.length; i++) 12 | targets.push (process.argv[i]); 13 | 14 | var options = { 15 | packetSize: 4, 16 | retries: 1, 17 | timeout: 2000 18 | }; 19 | 20 | var session = ping.createSession (options); 21 | 22 | session.on ("error", function (error) { 23 | console.trace (error.toString ()); 24 | }); 25 | 26 | for (var i = 0; i < targets.length; i++) { 27 | session.pingHost (targets[i], function (error, target) { 28 | if (error) 29 | if (error instanceof ping.RequestTimedOutError) 30 | console.log (target + ": Not alive"); 31 | else 32 | console.log (target + ": " + error.toString ()); 33 | else 34 | console.log (target + ": Alive"); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /example/ping-response-time.js: -------------------------------------------------------------------------------- 1 | 2 | var ping = require ("../"); 3 | 4 | if (process.argv.length < 3) { 5 | console.log ("usage: node ping-response-time [ ...]"); 6 | process.exit (-1); 7 | } 8 | 9 | var targets = []; 10 | 11 | for (var i = 2; i < process.argv.length; i++) 12 | targets.push (process.argv[i]); 13 | 14 | var options = { 15 | retries: 3, 16 | timeout: 2000 17 | }; 18 | 19 | var session = ping.createSession (options); 20 | 21 | session.on ("error", function (error) { 22 | console.trace (error.toString ()); 23 | }); 24 | 25 | for (var i = 0; i < targets.length; i++) { 26 | session.pingHost (targets[i], function (error, target, sent, rcvd) { 27 | var ms = rcvd - sent; 28 | if (error) 29 | if (error instanceof ping.RequestTimedOutError) 30 | console.log (target + ": Not alive (ms=" + ms + ")"); 31 | else 32 | console.log (target + ": " + error.toString () + " (ms=" 33 | + ms + ")"); 34 | else 35 | console.log (target + ": Alive alive (ms=" + ms + ")"); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /example/ping-retries-0.js: -------------------------------------------------------------------------------- 1 | 2 | var ping = require ("../"); 3 | 4 | if (process.argv.length < 4) { 5 | console.log ("usage: node ping-retries-0 "); 6 | process.exit (-1); 7 | } 8 | 9 | var target = process.argv[2]; 10 | 11 | var options = { 12 | retries: 0, 13 | timeout: parseInt(process.argv[3]) 14 | }; 15 | 16 | var session = ping.createSession (options); 17 | 18 | session.on ("error", function (error) { 19 | console.trace (error.toString ()); 20 | }); 21 | 22 | session.pingHost (target, function (error, target) { 23 | if (error) 24 | if (error instanceof ping.RequestTimedOutError) 25 | console.log (target + ": Not alive"); 26 | else 27 | console.log (target + ": " + error.toString ()); 28 | else 29 | console.log (target + ": Alive"); 30 | }); 31 | -------------------------------------------------------------------------------- /example/ping-ttl.js: -------------------------------------------------------------------------------- 1 | 2 | var ping = require ("../"); 3 | var raw = require ("raw-socket"); 4 | 5 | if (process.argv.length < 4) { 6 | console.log ("usage: node ping-ttl [ ...]"); 7 | process.exit (-1); 8 | } 9 | 10 | var ttl = parseInt (process.argv[2]); 11 | var targets = []; 12 | 13 | for (var i = 3; i < process.argv.length; i++) 14 | targets.push (process.argv[i]); 15 | 16 | var options = { 17 | packetSize: 4, 18 | retries: 1, 19 | ttl: ttl, 20 | timeout: 2000 21 | }; 22 | 23 | var session = ping.createSession (options); 24 | 25 | session.on ("error", function (error) { 26 | console.trace (error.toString ()); 27 | }); 28 | 29 | for (var i = 0; i < targets.length; i++) { 30 | session.pingHost (targets[i], function (error, target, source) { 31 | if (error) 32 | if (error instanceof ping.RequestTimedOutError) 33 | console.log (target + ": Not alive"); 34 | else 35 | console.log (target + ": " + error.toString ()); 36 | else 37 | console.log (target + ": Alive"); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /example/ping.js: -------------------------------------------------------------------------------- 1 | 2 | var ping = require ("../"); 3 | 4 | if (process.argv.length < 3) { 5 | console.log ("usage: node ping [ ...]"); 6 | process.exit (-1); 7 | } 8 | 9 | var targets = []; 10 | 11 | for (var i = 2; i < process.argv.length; i++) 12 | targets.push (process.argv[i]); 13 | 14 | var options = { 15 | retries: 1, 16 | timeout: 2000 17 | }; 18 | 19 | var session = ping.createSession (options); 20 | 21 | session.on ("error", function (error) { 22 | console.trace (error.toString ()); 23 | }); 24 | 25 | for (var i = 0; i < targets.length; i++) { 26 | session.pingHost (targets[i], function (error, target) { 27 | if (error) 28 | if (error instanceof ping.RequestTimedOutError) 29 | console.log (target + ": Not alive"); 30 | else 31 | console.log (target + ": " + error.toString ()); 32 | else 33 | console.log (target + ": Alive"); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /example/ping6-ttl.js: -------------------------------------------------------------------------------- 1 | 2 | var ping = require ("../"); 3 | var raw = require ("raw-socket"); 4 | 5 | if (process.argv.length < 4) { 6 | console.log ("usage: node ping6-ttl [ ...]"); 7 | process.exit (-1); 8 | } 9 | 10 | var ttl = parseInt (process.argv[2]); 11 | var targets = []; 12 | 13 | for (var i = 3; i < process.argv.length; i++) 14 | targets.push (process.argv[i]); 15 | 16 | var options = { 17 | networkProtocol: ping.NetworkProtocol.IPv6, 18 | packetSize: 4, 19 | retries: 1, 20 | ttl: ttl, 21 | timeout: 2000 22 | }; 23 | 24 | var session = ping.createSession (options); 25 | 26 | session.on ("error", function (error) { 27 | console.trace (error.toString ()); 28 | }); 29 | 30 | for (var i = 0; i < targets.length; i++) { 31 | session.pingHost (targets[i], function (error, target) { 32 | if (error) 33 | if (error instanceof ping.RequestTimedOutError) 34 | console.log (target + ": Not alive"); 35 | else 36 | console.log (target + ": " + error.toString ()); 37 | else 38 | console.log (target + ": Alive"); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /example/ping6.js: -------------------------------------------------------------------------------- 1 | 2 | var ping = require ("../"); 3 | 4 | if (process.argv.length < 3) { 5 | console.log ("usage: node ping [ ...]"); 6 | process.exit (-1); 7 | } 8 | 9 | var targets = []; 10 | 11 | for (var i = 2; i < process.argv.length; i++) 12 | targets.push (process.argv[i]); 13 | 14 | var options = { 15 | networkProtocol: ping.NetworkProtocol.IPv6, 16 | retries: 1, 17 | timeout: 2000 18 | }; 19 | 20 | var session = ping.createSession (options); 21 | 22 | session.on ("error", function (error) { 23 | console.trace (error.toString ()); 24 | }); 25 | 26 | for (var i = 0; i < targets.length; i++) { 27 | session.pingHost (targets[i], function (error, target) { 28 | if (error) 29 | if (error instanceof ping.RequestTimedOutError) 30 | console.log (target + ": Not alive"); 31 | else 32 | console.log (target + ": " + error.toString ()); 33 | else 34 | console.log (target + ": Alive"); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /example/trace-route-max-hop-timeouts.js: -------------------------------------------------------------------------------- 1 | 2 | var ping = require ("../"); 3 | 4 | if (process.argv.length < 4) { 5 | console.log ("usage: node trace-route [ ...]"); 6 | process.exit (-1); 7 | } 8 | 9 | var maxHopTimeouts = parseInt (process.argv[2]); 10 | var ttl = parseInt (process.argv[3]); 11 | var targets = []; 12 | 13 | for (var i = 4; i < process.argv.length; i++) 14 | targets.push (process.argv[i]); 15 | 16 | var options = { 17 | retries: 1, 18 | timeout: 2000 19 | }; 20 | 21 | var session = ping.createSession (options); 22 | 23 | session.on ("error", function (error) { 24 | console.trace (error.toString ()); 25 | }); 26 | 27 | function doneCb (error, target) { 28 | if (error) 29 | console.log (target + ": " + error.toString ()); 30 | else 31 | console.log (target + ": Done"); 32 | } 33 | 34 | function feedCb (error, target, ttl, sent, rcvd) { 35 | var ms = rcvd - sent; 36 | if (error) { 37 | if (error instanceof ping.TimeExceededError) { 38 | console.log (target + ": " + error.source + " (ttl=" + ttl 39 | + " ms=" + ms +")"); 40 | } else { 41 | console.log (target + ": " + error.toString () + " (ttl=" + ttl 42 | + " ms=" + ms +")"); 43 | } 44 | } else { 45 | console.log (target + ": " + target + " (ttl=" + ttl + " ms=" 46 | + ms +")"); 47 | } 48 | } 49 | 50 | for (var i = 0; i < targets.length; i++) { 51 | var traceOptions = { 52 | ttl: ttl, 53 | maxHopTimeouts: maxHopTimeouts 54 | }; 55 | session.traceRoute (targets[i], traceOptions, feedCb, doneCb); 56 | } 57 | -------------------------------------------------------------------------------- /example/trace-route-start-ttl.js: -------------------------------------------------------------------------------- 1 | 2 | var ping = require ("../"); 3 | 4 | if (process.argv.length < 4) { 5 | console.log ("usage: node trace-route [ ...]"); 6 | process.exit (-1); 7 | } 8 | 9 | var startTtl = parseInt (process.argv[2]); 10 | var ttl = parseInt (process.argv[3]); 11 | var targets = []; 12 | 13 | for (var i = 4; i < process.argv.length; i++) 14 | targets.push (process.argv[i]); 15 | 16 | var options = { 17 | retries: 1, 18 | timeout: 2000 19 | }; 20 | 21 | var session = ping.createSession (options); 22 | 23 | session.on ("error", function (error) { 24 | console.trace (error.toString ()); 25 | }); 26 | 27 | function doneCb (error, target) { 28 | if (error) 29 | console.log (target + ": " + error.toString ()); 30 | else 31 | console.log (target + ": Done"); 32 | } 33 | 34 | function feedCb (error, target, ttl, sent, rcvd) { 35 | var ms = rcvd - sent; 36 | if (error) { 37 | if (error instanceof ping.TimeExceededError) { 38 | console.log (target + ": " + error.source + " (ttl=" + ttl 39 | + " ms=" + ms +")"); 40 | } else { 41 | console.log (target + ": " + error.toString () + " (ttl=" + ttl 42 | + " ms=" + ms +")"); 43 | } 44 | } else { 45 | console.log (target + ": " + target + " (ttl=" + ttl + " ms=" 46 | + ms +")"); 47 | } 48 | } 49 | 50 | for (var i = 0; i < targets.length; i++) { 51 | var traceOptions = { 52 | ttl: ttl, 53 | startTtl: startTtl 54 | }; 55 | session.traceRoute (targets[i], traceOptions, feedCb, doneCb); 56 | } 57 | -------------------------------------------------------------------------------- /example/trace-route.js: -------------------------------------------------------------------------------- 1 | 2 | var ping = require ("../"); 3 | 4 | if (process.argv.length < 4) { 5 | console.log ("usage: node trace-route [ ...]"); 6 | process.exit (-1); 7 | } 8 | 9 | var ttl = parseInt (process.argv[2]); 10 | var targets = []; 11 | 12 | for (var i = 3; i < process.argv.length; i++) 13 | targets.push (process.argv[i]); 14 | 15 | var options = { 16 | retries: 1, 17 | timeout: 2000 18 | }; 19 | 20 | var session = ping.createSession (options); 21 | 22 | session.on ("error", function (error) { 23 | console.trace (error.toString ()); 24 | }); 25 | 26 | function doneCb (error, target) { 27 | if (error) 28 | console.log (target + ": " + error.toString ()); 29 | else 30 | console.log (target + ": Done"); 31 | } 32 | 33 | function feedCb (error, target, ttl, sent, rcvd) { 34 | var ms = rcvd - sent; 35 | if (error) { 36 | if (error instanceof ping.TimeExceededError) { 37 | console.log (target + ": " + error.source + " (ttl=" + ttl 38 | + " ms=" + ms +")"); 39 | } else { 40 | console.log (target + ": " + error.toString () + " (ttl=" + ttl 41 | + " ms=" + ms +")"); 42 | } 43 | } else { 44 | console.log (target + ": " + target + " (ttl=" + ttl + " ms=" 45 | + ms +")"); 46 | } 47 | } 48 | 49 | for (var i = 0; i < targets.length; i++) { 50 | session.traceRoute (targets[i], ttl, feedCb, doneCb); 51 | } 52 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | var events = require ("events"); 3 | var net = require ("net"); 4 | var raw = require ("raw-socket"); 5 | var util = require ("util"); 6 | 7 | function _expandConstantObject (object) { 8 | var keys = []; 9 | for (var key in object) 10 | keys.push (key); 11 | for (var i = 0; i < keys.length; i++) 12 | object[object[keys[i]]] = parseInt (keys[i]); 13 | } 14 | 15 | var NetworkProtocol = { 16 | 1: "IPv4", 17 | 2: "IPv6" 18 | }; 19 | 20 | _expandConstantObject (NetworkProtocol); 21 | 22 | function DestinationUnreachableError (source) { 23 | this.name = "DestinationUnreachableError"; 24 | this.message = "Destination unreachable (source=" + source + ")"; 25 | this.source = source; 26 | } 27 | util.inherits (DestinationUnreachableError, Error); 28 | 29 | function PacketTooBigError (source) { 30 | this.name = "PacketTooBigError"; 31 | this.message = "Packet too big (source=" + source + ")"; 32 | this.source = source; 33 | } 34 | util.inherits (PacketTooBigError, Error); 35 | 36 | function ParameterProblemError (source) { 37 | this.name = "ParameterProblemError"; 38 | this.message = "Parameter problem (source=" + source + ")"; 39 | this.source = source; 40 | } 41 | util.inherits (ParameterProblemError, Error); 42 | 43 | function RedirectReceivedError (source) { 44 | this.name = "RedirectReceivedError"; 45 | this.message = "Redirect received (source=" + source + ")"; 46 | this.source = source; 47 | } 48 | util.inherits (RedirectReceivedError, Error); 49 | 50 | function RequestTimedOutError () { 51 | this.name = "RequestTimedOutError"; 52 | this.message = "Request timed out"; 53 | } 54 | util.inherits (RequestTimedOutError, Error); 55 | 56 | function SourceQuenchError (source) { 57 | this.name = "SourceQuenchError"; 58 | this.message = "Source quench (source=" + source + ")"; 59 | this.source = source; 60 | } 61 | util.inherits (SourceQuenchError, Error); 62 | 63 | function TimeExceededError (source) { 64 | this.name = "TimeExceededError"; 65 | this.message = "Time exceeded (source=" + source + ")"; 66 | this.source = source; 67 | } 68 | util.inherits (TimeExceededError, Error); 69 | 70 | function Session (options) { 71 | this.retries = (options && options.retries != undefined) ? options.retries : 1; 72 | this.timeout = (options && options.timeout) ? options.timeout : 2000; 73 | 74 | this.packetSize = (options && options.packetSize) ? options.packetSize : 16; 75 | 76 | if (this.packetSize < 12) 77 | this.packetSize = 12; 78 | 79 | this.addressFamily = (options && options.networkProtocol 80 | && options.networkProtocol == NetworkProtocol.IPv6) 81 | ? raw.AddressFamily.IPv6 82 | : raw.AddressFamily.IPv4; 83 | 84 | this._debug = (options && options._debug) ? true : false; 85 | 86 | this.defaultTTL = (options && options.ttl) ? options.ttl : 128; 87 | 88 | this.sessionId = (options && options.sessionId) 89 | ? options.sessionId 90 | : process.pid; 91 | 92 | this.sessionId = this.sessionId % 65535; 93 | 94 | this.nextId = 1; 95 | 96 | this.socket = null; 97 | 98 | this.reqs = {}; 99 | this.reqsPending = 0; 100 | 101 | this.getSocket (); 102 | }; 103 | 104 | util.inherits (Session, events.EventEmitter); 105 | 106 | Session.prototype.close = function () { 107 | if (this.socket) 108 | this.socket.close (); 109 | this.flush (new Error ("Socket forcibly closed")); 110 | delete this.socket; 111 | return this; 112 | }; 113 | 114 | Session.prototype._debugRequest = function (target, req) { 115 | console.log ("request: addressFamily=" + this.addressFamily + " target=" 116 | + req.target + " id=" + req.id + " buffer=" 117 | + req.buffer.toString ("hex")); 118 | } 119 | 120 | Session.prototype._debugResponse = function (source, buffer) { 121 | console.log ("response: addressFamily=" + this.addressFamily + " source=" 122 | + source + " buffer=" + buffer.toString ("hex")); 123 | } 124 | 125 | Session.prototype.flush = function (error) { 126 | for (var id in this.reqs) { 127 | var req = this.reqRemove (id); 128 | var sent = req.sent ? req.sent : new Date (); 129 | req.callback (error, req.target, sent, new Date ()); 130 | } 131 | }; 132 | 133 | Session.prototype.getSocket = function () { 134 | if (this.socket) 135 | return this.socket; 136 | 137 | var protocol = this.addressFamily == raw.AddressFamily.IPv6 138 | ? raw.Protocol.ICMPv6 139 | : raw.Protocol.ICMP; 140 | 141 | var me = this; 142 | var options = { 143 | addressFamily: this.addressFamily, 144 | protocol: protocol 145 | }; 146 | 147 | this.socket = raw.createSocket (options); 148 | this.socket.on ("error", this.onSocketError.bind (me)); 149 | this.socket.on ("close", this.onSocketClose.bind (me)); 150 | this.socket.on ("message", this.onSocketMessage.bind (me)); 151 | 152 | this.ttl = null; 153 | this.setTTL (this.defaultTTL); 154 | 155 | return this.socket; 156 | }; 157 | 158 | Session.prototype.fromBuffer = function (buffer) { 159 | var offset, type, code; 160 | 161 | if (this.addressFamily == raw.AddressFamily.IPv6) { 162 | // IPv6 raw sockets don't pass the IPv6 header back to us 163 | offset = 0; 164 | 165 | if (buffer.length - offset < 8) 166 | return; 167 | 168 | // We don't believe any IPv6 options will be passed back to us so we 169 | // don't attempt to pass them here. 170 | 171 | type = buffer.readUInt8 (offset); 172 | code = buffer.readUInt8 (offset + 1); 173 | } else { 174 | // Need at least 20 bytes for an IP header, and it should be IPv4 175 | if (buffer.length < 20 || (buffer[0] & 0xf0) != 0x40) 176 | return; 177 | 178 | // The length of the IPv4 header is in mulitples of double words 179 | var ip_length = (buffer[0] & 0x0f) * 4; 180 | 181 | // ICMP header is 8 bytes, we don't care about the data for now 182 | if (buffer.length - ip_length < 8) 183 | return; 184 | 185 | var ip_icmp_offset = ip_length; 186 | 187 | // ICMP message too short 188 | if (buffer.length - ip_icmp_offset < 8) 189 | return; 190 | 191 | type = buffer.readUInt8 (ip_icmp_offset); 192 | code = buffer.readUInt8 (ip_icmp_offset + 1); 193 | 194 | // For error type responses the sequence and identifier cannot be 195 | // extracted in the same way as echo responses, the data part contains 196 | // the IP header from our request, followed with at least 8 bytes from 197 | // the echo request that generated the error, so we first go to the IP 198 | // header, then skip that to get to the ICMP packet which contains the 199 | // sequence and identifier. 200 | if (type == 3 || type == 4 || type == 5 || type == 11) { 201 | var ip_icmp_ip_offset = ip_icmp_offset + 8; 202 | 203 | // Need at least 20 bytes for an IP header, and it should be IPv4 204 | if (buffer.length - ip_icmp_ip_offset < 20 205 | || (buffer[ip_icmp_ip_offset] & 0xf0) != 0x40) 206 | return; 207 | 208 | // The length of the IPv4 header is in mulitples of double words 209 | var ip_icmp_ip_length = (buffer[ip_icmp_ip_offset] & 0x0f) * 4; 210 | 211 | // ICMP message too short 212 | if (buffer.length - ip_icmp_ip_offset - ip_icmp_ip_length < 8) 213 | return; 214 | 215 | offset = ip_icmp_ip_offset + ip_icmp_ip_length; 216 | } else { 217 | offset = ip_icmp_offset 218 | } 219 | } 220 | 221 | // Response is not for a request we generated 222 | if (buffer.readUInt16BE (offset + 4) != this.sessionId) 223 | return; 224 | 225 | buffer[offset + 4] = 0; 226 | 227 | var id = buffer.readUInt16BE (offset + 6); 228 | var req = this.reqs[id]; 229 | 230 | if (req) { 231 | req.type = type; 232 | req.code = code; 233 | return req; 234 | } else { 235 | return null; 236 | } 237 | }; 238 | 239 | Session.prototype.onBeforeSocketSend = function (req) { 240 | this.setTTL (req.ttl ? req.ttl : this.defaultTTL); 241 | } 242 | 243 | Session.prototype.onSocketClose = function () { 244 | this.emit ("close"); 245 | this.flush (new Error ("Socket closed")); 246 | }; 247 | 248 | Session.prototype.onSocketError = function (error) { 249 | this.emit ("error", error); 250 | }; 251 | 252 | Session.prototype.onSocketMessage = function (buffer, source) { 253 | if (this._debug) 254 | this._debugResponse (source, buffer); 255 | 256 | var req = this.fromBuffer (buffer); 257 | if (req) { 258 | /** 259 | ** If we ping'd ourself (i.e. 127.0.0.1 or ::1) then it is likely we 260 | ** will receive the echo request in addition to any corresponding echo 261 | ** responses. We discard the request packets here so that we don't 262 | ** delete the request from the from the request queue since we haven't 263 | ** actually received a response yet. 264 | **/ 265 | if (this.addressFamily == raw.AddressFamily.IPv6) { 266 | if (req.type == 128) 267 | return; 268 | } else { 269 | if (req.type == 8) 270 | return; 271 | } 272 | 273 | this.reqRemove (req.id); 274 | 275 | if (this.addressFamily == raw.AddressFamily.IPv6) { 276 | if (req.type == 1) { 277 | req.callback (new DestinationUnreachableError (source), req.target, 278 | req.sent, new Date ()); 279 | } else if (req.type == 2) { 280 | req.callback (new PacketTooBigError (source), req.target, 281 | req.sent, new Date ()); 282 | } else if (req.type == 3) { 283 | req.callback (new TimeExceededError (source), req.target, 284 | req.sent, new Date ()); 285 | } else if (req.type == 4) { 286 | req.callback (new ParameterProblemError (source), req.target, 287 | req.sent, new Date ()); 288 | } else if (req.type == 129) { 289 | req.callback (null, req.target, 290 | req.sent, new Date ()); 291 | } else { 292 | req.callback (new Error ("Unknown response type '" + req.type 293 | + "' (source=" + source + ")"), req.target, 294 | req.sent, new Date ()); 295 | } 296 | } else { 297 | if (req.type == 0) { 298 | req.callback (null, req.target, 299 | req.sent, new Date ()); 300 | } else if (req.type == 3) { 301 | req.callback (new DestinationUnreachableError (source), req.target, 302 | req.sent, new Date ()); 303 | } else if (req.type == 4) { 304 | req.callback (new SourceQuenchError (source), req.target, 305 | req.sent, new Date ()); 306 | } else if (req.type == 5) { 307 | req.callback (new RedirectReceivedError (source), req.target, 308 | req.sent, new Date ()); 309 | } else if (req.type == 11) { 310 | req.callback (new TimeExceededError (source), req.target, 311 | req.sent, new Date ()); 312 | } else { 313 | req.callback (new Error ("Unknown response type '" + req.type 314 | + "' (source=" + source + ")"), req.target, 315 | req.sent, new Date ()); 316 | } 317 | } 318 | } 319 | }; 320 | 321 | Session.prototype.onSocketSend = function (req, error, bytes) { 322 | if (! req.sent) 323 | req.sent = new Date (); 324 | if (error) { 325 | this.reqRemove (req.id); 326 | req.callback (error, req.target, req.sent, req.sent); 327 | } else { 328 | var me = this; 329 | req.timer = setTimeout (this.onTimeout.bind (me, req), req.timeout); 330 | } 331 | }; 332 | 333 | Session.prototype.onTimeout = function (req) { 334 | if (req.retries > 0) { 335 | req.retries--; 336 | this.send (req); 337 | } else { 338 | this.reqRemove (req.id); 339 | req.callback (new RequestTimedOutError ("Request timed out"), 340 | req.target, req.sent, new Date ()); 341 | } 342 | }; 343 | 344 | // Keep searching for an ID which is not in use 345 | Session.prototype._generateId = function () { 346 | var startId = this.nextId++; 347 | while (1) { 348 | if (this.nextId > 65535) 349 | this.nextId = 1; 350 | if (this.reqs[this.nextId]) { 351 | this.nextId++; 352 | } else { 353 | return this.nextId; 354 | } 355 | // No free request IDs 356 | if (this.nextId == startId) 357 | return; 358 | } 359 | } 360 | 361 | Session.prototype.pingHost = function (target, callback) { 362 | var id = this._generateId (); 363 | if (! id) { 364 | callback (new Error ("Too many requests outstanding"), target); 365 | return this; 366 | } 367 | 368 | var req = { 369 | id: id, 370 | retries: this.retries, 371 | timeout: this.timeout, 372 | callback: callback, 373 | target: target 374 | }; 375 | 376 | this.reqQueue (req); 377 | 378 | return this; 379 | }; 380 | 381 | Session.prototype.reqQueue = function (req) { 382 | req.buffer = this.toBuffer (req); 383 | 384 | if (this._debug) 385 | this._debugRequest (req.target, req); 386 | 387 | this.reqs[req.id] = req; 388 | this.reqsPending++; 389 | this.send (req); 390 | 391 | return this; 392 | } 393 | 394 | Session.prototype.reqRemove = function (id) { 395 | var req = this.reqs[id]; 396 | if (req) { 397 | clearTimeout (req.timer); 398 | delete req.timer; 399 | delete this.reqs[req.id]; 400 | this.reqsPending--; 401 | } 402 | // If we have no more outstanding requests pause readable events 403 | if (this.reqsPending <= 0) 404 | if (! this.getSocket ().recvPaused) 405 | this.getSocket ().pauseRecv (); 406 | return req; 407 | }; 408 | 409 | Session.prototype.send = function (req) { 410 | var buffer = req.buffer; 411 | var me = this; 412 | // Resume readable events if the raw socket is paused 413 | if (this.getSocket ().recvPaused) 414 | this.getSocket ().resumeRecv (); 415 | this.getSocket ().send (buffer, 0, buffer.length, req.target, 416 | this.onBeforeSocketSend.bind (me, req), 417 | this.onSocketSend.bind (me, req)); 418 | }; 419 | 420 | Session.prototype.setTTL = function (ttl) { 421 | if (this.ttl && this.ttl == ttl) 422 | return; 423 | 424 | var level = this.addressFamily == raw.AddressFamily.IPv6 425 | ? raw.SocketLevel.IPPROTO_IPV6 426 | : raw.SocketLevel.IPPROTO_IP; 427 | this.getSocket ().setOption (level, raw.SocketOption.IP_TTL, ttl); 428 | this.ttl = ttl; 429 | } 430 | 431 | Session.prototype.toBuffer = function (req) { 432 | var buffer = Buffer.alloc(this.packetSize); 433 | 434 | // Since our buffer represents real memory we should initialise it to 435 | // prevent its previous contents from leaking to the network. 436 | for (var i = 8; i < this.packetSize; i++) 437 | buffer[i] = 0; 438 | 439 | var type = this.addressFamily == raw.AddressFamily.IPv6 ? 128 : 8; 440 | 441 | buffer.writeUInt8 (type, 0); 442 | buffer.writeUInt8 (0, 1); 443 | buffer.writeUInt16BE (0, 2); 444 | buffer.writeUInt16BE (this.sessionId, 4); 445 | buffer.writeUInt16BE (req.id, 6); 446 | 447 | raw.writeChecksum (buffer, 2, raw.createChecksum (buffer)); 448 | 449 | return buffer; 450 | }; 451 | 452 | Session.prototype.traceRouteCallback = function (trace, req, error, target, 453 | sent, rcvd) { 454 | if (trace.feedCallback (error, target, req.ttl, sent, rcvd)) { 455 | trace.doneCallback (new Error ("Trace forcibly stopped"), target); 456 | return; 457 | } 458 | 459 | if (error) { 460 | if (req.ttl >= trace.ttl) { 461 | trace.doneCallback (error, target); 462 | return; 463 | } 464 | 465 | if ((error instanceof RequestTimedOutError) && ++trace.timeouts >= trace.maxHopTimeouts) { 466 | trace.doneCallback (new Error ("Too many timeouts"), target); 467 | return; 468 | } 469 | 470 | var id = this._generateId (); 471 | if (! id) { 472 | trace.doneCallback (new Error ("Too many requests outstanding"), 473 | target); 474 | return; 475 | } 476 | 477 | req.ttl++; 478 | req.id = id; 479 | var me = this; 480 | req.retries = this.retries; 481 | req.sent = null; 482 | this.reqQueue (req); 483 | } else { 484 | trace.doneCallback (null, target); 485 | } 486 | } 487 | 488 | Session.prototype.traceRoute = function (target, ttlOrOptions, feedCallback, 489 | doneCallback) { 490 | // signature was (target, feedCallback, doneCallback) 491 | if (! doneCallback) { 492 | doneCallback = feedCallback; 493 | feedCallback = ttlOrOptions; 494 | ttlOrOptions = {ttl: this.ttl}; 495 | } 496 | 497 | var maxHopTimeouts = 3; 498 | var startTtl = 1; 499 | var ttl = this.ttl; 500 | 501 | if (typeof ttlOrOptions == "object") { 502 | if (ttlOrOptions.ttl) 503 | ttl = ttlOrOptions.ttl; 504 | if (ttlOrOptions.maxHopTimeouts) 505 | maxHopTimeouts = ttlOrOptions.maxHopTimeouts; 506 | if (ttlOrOptions.startTtl) 507 | startTtl = ttlOrOptions.startTtl; 508 | } else { 509 | ttl = ttlOrOptions; 510 | } 511 | 512 | var id = this._generateId (); 513 | if (! id) { 514 | var sent = new Date (); 515 | doneCallback (new Error ("Too many requests outstanding"), target, 516 | sent, sent); 517 | return this; 518 | } 519 | 520 | var trace = { 521 | feedCallback: feedCallback, 522 | doneCallback: doneCallback, 523 | ttl: ttl, 524 | maxHopTimeouts: maxHopTimeouts, 525 | timeouts: 0 526 | }; 527 | 528 | var me = this; 529 | 530 | var req = { 531 | id: id, 532 | retries: this.retries, 533 | timeout: this.timeout, 534 | ttl: startTtl, 535 | target: target 536 | }; 537 | req.callback = me.traceRouteCallback.bind (me, trace, req); 538 | 539 | this.reqQueue (req); 540 | 541 | return this; 542 | }; 543 | 544 | exports.createSession = function (options) { 545 | return new Session (options || {}); 546 | }; 547 | 548 | exports.NetworkProtocol = NetworkProtocol; 549 | 550 | exports.Session = Session; 551 | 552 | exports.DestinationUnreachableError = DestinationUnreachableError; 553 | exports.PacketTooBigError = PacketTooBigError; 554 | exports.ParameterProblemError = ParameterProblemError; 555 | exports.RedirectReceivedError = RedirectReceivedError; 556 | exports.RequestTimedOutError = RequestTimedOutError; 557 | exports.SourceQuenchError = SourceQuenchError; 558 | exports.TimeExceededError = TimeExceededError; 559 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "net-ping", 3 | "version": "1.2.3", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "net-ping", 9 | "version": "1.2.3", 10 | "license": "MIT", 11 | "dependencies": { 12 | "raw-socket": "*" 13 | } 14 | }, 15 | "node_modules/nan": { 16 | "version": "2.19.0", 17 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", 18 | "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==" 19 | }, 20 | "node_modules/raw-socket": { 21 | "version": "1.8.0", 22 | "resolved": "https://registry.npmjs.org/raw-socket/-/raw-socket-1.8.0.tgz", 23 | "integrity": "sha512-DLhODdAJu4QhBfqY6KVk3N/hh7z/VxUQ/bDRMFHdBoHvHzb5mTd9O+Plcvl2EX9PCIwcjNtHb2XsgkmTUCVGDA==", 24 | "hasInstallScript": true, 25 | "dependencies": { 26 | "nan": "2.19.*" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "net-ping", 3 | "version": "1.2.4", 4 | "description": "Ping and trace route to many hosts at once.", 5 | "main": "index.js", 6 | "directories": { 7 | "example": "example" 8 | }, 9 | "dependencies": { 10 | "raw-socket": "*" 11 | }, 12 | "contributors": [ 13 | { 14 | "name": "Stephen Vickers", 15 | "email": "stephen.vickers@nospaceships.com" 16 | }, 17 | { 18 | "name": "NoSpaceships Ltd", 19 | "email": "hello@nospaceships.com" 20 | } 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "git://github.com/nospaceships/node-net-ping.git" 25 | }, 26 | "keywords": [ 27 | "echo", 28 | "icmp", 29 | "monitor", 30 | "monitoring", 31 | "net", 32 | "network", 33 | "ping", 34 | "trace", 35 | "trace-route", 36 | "traceroute", 37 | "tracert" 38 | ], 39 | "author": "NoSpaceships Ltd ", 40 | "license": "MIT" 41 | } 42 | --------------------------------------------------------------------------------