JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 15956
  • Score
    100M100P100Q164857F
  • License MIT

Streaming TFTP client and server

Package Exports

  • tftp

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (tftp) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

tftp

Streaming TFTP client and Server

NPM version Dependency Status

NPM installation

WARNING

The implementation it's already done, you can GET and PUT files correctly. However, some optimizations must still be done, so for your safety don't use it in production or development, just for testing purposes. It will be ready when it reaches the version 0.1.0.


Full-featured streaming TFTP client and server. It supports most of the RFCs:

CLIENT | SERVER | Error codes

Per se, the TFTP is a lock-step protocol built on top of UDP for transferring files between two machines. It was useful in the past but nowadays it's practically an obsolete legacy protocol useful in a very few scenarios. Without the extensions support, the RFC says that a file bigger than 32MB cannot be sent. This limit can be incremented to 91.74MB if both machines agree to use a block size of 1468 bytes, the MTU size before IP fragmentation in Ethernet networks. Also, the transfer speed is pretty slow due to the lock-step mechanism, one acknowledgement for each packet.

However, there are two de facto extensions that can boost the transfer speed and remove the size limit: the rollover and the window.

This module it's perfectly integrated with Node.js, providing an streaming interface for GETting and PUTing files very easily. No configuration is needed. By default the client tries to negotiate with the server the best possible configuration. If that's not possible it simply fallbacks to the original lock-step TFTP implementation. The server also supports both the enhanced features and the classic lock-step implementations.

It can be installed locally and used programmatically, but it can be also installed globally and used directly from the console as a CLI utility (client only).

Special thanks

Patrick Masotta (author of the Serva application and the internet draft about the windowsize option).

Warning! UDP packet loss in Windows

Currently, in Windows there is a problem concerning the buffering of the received network packets (#6696). Basically, when the buffer is full, all the subsequent incoming packets are dropped, so they are never consumed by Node.js. This scenario can be reproduced by configuring a window bigger than 6 blocks with the default block size. So the advice is: do NOT increment the default window size (4) in the Windows platform until this bug is solved.


CLIENT

module.createClient([options]) : Client

Documentation

Objects


Streams

For the sake of simplicity the following examples omit the error handling. See the streams.js example or the source code of the get() and put() functions for more information.

GET remote → local

var get = client.createGetStream ("remote-file");
var write = fs.createWriteStream ("local-file");

get.pipe (write);

PUT local → remote

var read = fs.createReadStream ("local-file");
var put = client.createPutStream ("remote-file", { size: totalSize });

read.pipe (put);

Global installation

npm install tftp -g

Then you can access to the ntftp binary.

There are basically two ways to use it: with or without a shell.

Without a shell

Best for individual transfers.

$ ntftp get [options] <rfc3617_uri> [<local>]
$ ntftp put [options] [<local>] <rfc3617_uri>

For example:

$ ntftp get tftp://localhost/remote-file
remote-file             42.2 MiB   32.6M/s 00:12 [###·····················]  13%
$ ntftp put my/local-file tftp://localhost/remote-file
my/local-file          148.8 MiB   30.9M/s 00:07 [###########·············]  45%

For more information type ntftp get|put -h.

With a shell

Best for multiple transfers, basically because the same server address and options are reused.

$ ntftp [options] <host>[:<port>]

For example:

$ ntftp localhost
> get remote-file
remote-file             42.2 MiB   32.6M/s 00:12 [###·····················]  13%
> put my/local-file remote-file
my/local-file          148.8 MiB   30.9M/s 00:07 [###########·············]  45%

For more information type ntftp -h and get|put -h.


module.createClient([options]) : Client

Returns a new Client instance.

var client = tftp.createClient ({
  host: "10.10.10.10",
  port: 1234
});

Options:

  • host - String
    The address. Both IPv4 and IPv6 are allowed as well as a domain name. Default is localhost (127.0.0.1).

  • port - Number
    The port. Default is 69.

  • blockSize - Number
    The size of the DATA blocks. Valid range: [8, 65464]. Default is 1468, the MTU size before IP fragmentation in Ethernet networks.

  • windowSize - Number
    The size of each window. The window size means the number of blocks that can be sent/received without waiting an acknowledgement. Valid range: [1, 65535]. Default is 4.

    Comparison of transfer times:

    Window sizeImprovement
    1-0%
    2-49%
    3-64%
    4-70%
    5-73%
    6-76%

    Take into account that with a bigger window more elements must be sorted (remember that UDP doesn't sort the incoming packets). This doesn't slow down the transfer very much but it requires more CPU. A window size of 4 is a good trade between transfer speed and CPU usage.

    Right now a window size of 6 is the maximum in Windows due to the packet loss issue. With a window size of 7 or greater a lot of timeouts and retransmissions begin to occur, so the recommendation is to use a window size of 4, the default value.

  • retries - Number
    How many retries must be done before emitting an error. Default is 3.

  • timeout - Number
    Milliseconds to wait before a retry. Default is 3000.


Client

Each of the following methods take an options parameter. One option available is userExtensions, an object with properties that can be sent with a GET or PUT operation. For example:

var options = {
  userExtensions: {
    foo: "bar",
    num: 2
  }
}

The server may ignore or not these extensions, this feature is server-dependent. Please note that the TFTP algorithm cannot be modified. For example, you can implement a basic authentication; the client could send the extensions user and password and the server could validate the user and accept or deny the request. The extensions are transmitted in plain text.

The extensions timeout, tsize, blksize, windowsize and rollover are reserved and cannot be used.

Methods

Client#createGetStream(remoteFile[, options]) : GetStream

Returns a new GetStream instance.

Options:

  • highWaterMark - Number
    Buffer size. Default is 16KB.
  • md5sum - String
    MD5 sum for validating the integrity of the file.
  • sha1sum - String
    SHA1 sum for validating the integrity of the file.
  • userExtensions - Object
    Custom extensions to send with the request. More information.
var get = client.createGetStream ("file");

Client#createPutStream(remoteFile, options) : PutStream

Returns a new PutStream instance.

Options:

  • highWaterMark - Number
    Buffer size. Default is 16KB.
  • size - String
    Total size of the file to upload. This option is required.
  • userExtensions - Object
    Custom extensions to send with the request. More information.
var put = client.createPutStream ("file", { size: 1234 });

Client#get(remoteFile[, localFile][, options], callback) : undefined

Downloads a file from the server. If the local filename is missing the filename of the remote file is used.

Options:

  • highWaterMark - Number
    Buffer size. Default is 16KB.
  • md5sum - String
    MD5 sum for validating the integrity of the file.
  • sha1sum - String
    SHA1 sum for validating the integrity of the file.
  • userExtensions - Object
    Custom extensions to send with the request. More information.
//tftp://<host>/file -> file
client.get ("file", function (error){
  if (error) return console.error (error);
  ...
});

Client#put(localFile[, remoteFile][, options], callback) : undefined

Uploads a file to the server. If the remote filename is missing the filename of the local file is used.

Options:

  • highWaterMark - Number
    Buffer size. Default is 16KB.
  • userExtensions - Object
    Custom extensions to send with the request. More information.
//file -> tftp://<host>/file
client.put ("file", function (error){
  if (error) return console.error (error);
  ...
});

GetStream and PutStream

The GetStream inherits from a Readable stream and the PutStream from a Writable stream.

Events

Methods


abort

Arguments: none.

Emitted when abort() is called and the transfer has been aborted.

close

Arguments: none.

Emitted when the underlying socket has been closed. It is emitted always and before any other event (error, abort, end or finish).

end

Arguments: none.

Emitted by the GetStream when the file download finishes.

error

Arguments: error.

Emitted when an error occurs or when abort() or close(). The stream is closed automatically.

finish

Arguments: none.

Emitted by the PutStream when the file upload finishes.

stats

Arguments: stats.

Emitted when the client negotiates the best possible configuration. When it is emitted the transfer still hasn't begun.

stats is an object similar to this:

{
  blockSize: 1468,
  windowSize: 4,
  size: 105757295,
  userExtensions: null,
  file: 'file',
  retries: 3,
  timeout: 3000,
  localAddress: "0.0.0.0",
  localPort: 55146,
  remoteAddress: "127.0.0.1",
  remotePort: 55147
}

When the GetStream emits a stats event, the size property is not guaranteed to be a Number because the server may not implement all the RFCs. The size of the file is obtained during the negotiation but not all the servers are able to negotiate. In these cases the size is null.

The userExtensions property holds an object with the custom extensions sent by the server in response to the custom extensions sent with the request. Most of the TFTP servers don't let you respond with custom extensions when in fact this is a feature explained in the RFCs, so unless the TFTP server allows you to respond with custom extensions, this property will be always null. Of course, the server included with this module let you set the user extensions to send back to the client.


abort([error]) : undefined

Aborts the current transfer. The optional error can be an Error instance or any type (it is stringified). If no error message is given, it sends an EABORT error. The message is sent to the server but it is not guaranteed that it will reach it because TFTP is built on top of UDP and the error messages are not retransmitted, so the packet could be lost. If the message reaches the server, then the transfer is aborted immediately.


close() : undefined

Closes the current transfer. It's the same as the abort() function but it doesn't send to the server any message, it just closes the socket. Note that this will cause the server to start the timeout. The recommended way to interrupt a transfer is using abort().


SERVER

module.createServer([options][, requestListener]) : Server

Documentation

Objects


Error handling

It's very simple. You need to attach two error listeners; one for the server and one for the request. If you don't attach an error listener, Node.js throws the error and the server just crashes, this is the way Node.js works.

var server.createServer (...);

server.on ("error", function (error){
  //Errors from the main socket
  //If this happens, it's most likely that server has closed
  console.error (error);
});

server.on ("request", function (req, res){
  req.on ("error", function (error){
    //Error from the request
    //If this happens, the connection is already closed
    console.error ("[" + req.stats.remoteAddress + ":" + req.stats.remotePort +
        "] (" + req.file + ") " + error.message);
  });
});

Global installation

npm install tftp -g

Then you can access to the ntftp binary.

Use the -l|--listen[=ROOT] option to start the server. By default the root directory is ..

$ ntftp [options] <host>[:<port>] -l|--listen=ROOT

For example:

$ ntftp localhost -l

This command starts a server listening on localhost:69 and root ..


module.createServer([options][, requestListener]) : Server

Returns a new Server instance.

var server = tftp.createServer ({
  host: "10.10.10.10",
  port: 1234,
  root: "path/to/root/dir",
  denyPUT: true
});

The requestListener is a function which is automatically added to the request event.

Options:

It has the same options as the createClient() function with the addition of:

  • root - String
    The root directory. Default is ..
  • denyGET - Boolean
    Denies all the GET operations. Default is false.
  • denyPUT - Boolean
    Denies all the PUT operations. Default is false.

Setting the options denyGET or denyPUT are more efficient than aborting the request from inside the request listener.


Server

Events

Methods

Properties

Error codes


close

Arguments: none.

Emitted when the server closes. No new requests are accepted. Note that the current transfers are not aborted. If you need to close the server gracefully, look at this example.

error

Arguments: error.

Emitted when an error occurs receiving data. The server is not closed.

listening

Arguments: none.

Emitted when the server has been bound after calling listen().

request

Arguments: req, res.

Emitted when a new request has been received. All the connection objects that are emitted can be aborted at any time.

req is an instance of a GetStream and res is an instance of a PutStream.

Requests trying to access a path outside the root directory (eg.: ../file) are automatically denied.

Note: If you don't need do anything with the req or res arguments, that is, you don't need to write or read data, then you must abort() or close() the connection, otherwise you'll have an open connection for the rest of the server's lifetime. The client will timeout because it won't receive any packet, that's for sure, but the connection in the server will remain open and won't timeout. The timeout retransmissions at the server-side begins when the transfer starts but if you don't read/write/close, the connection won't timeout because it is simply waiting to the user to do something with it.


close() : undefined

Closes the server and stops accepting new connections.


listen() : undefined

Starts accepting new connections.


requestListener(req, res) : undefined

This function must NOT be called from outside a request listener. This function is the default request listener, it automatically handles the GET and PUT requests.


root

The root path.


GetStream and PutStream

When the request event is emitted, a new GetStream and PutStream instances are created. These streams are similar to the streams used in the client but with one difference, the GetStream (req) acts like a "connection" object. All the events from the PutStream (res) are forwarded to the req object, so you don't need to attach any event to the res object.

The GetStream has two additional properties:

  • file - String
    The path of the file. The directories are not created recursively if they don't exist.
  • method - String
    The transfer's method: GET or PUT.
  • stats - Object
    An object holding some stats from the current request. More information.

The PutStream has two additional methods:

  • setSize(size) : undefined

    Sets the size of the file to send. You need to call this method only with GET requests when you're using a custom request listener for the GET transfers, otherwise the request will just wait. Look at the examples no-pipe.js and user-extensions-resume.js for more details.

  • setUserExtensions(userExtensions) : undefined

    Sets the user extensions to send back to the client in response to the received ones. You cannot send extensions different from the ones that are sent by the client. This method must be called before setSize().

    As said previously, the TFTP protocol doesn't have any built-in authentication mechanism but thanks to the user extensions you can implement a simple authentication as showed here.

    Look at the examples for more details.


Error codes

The following errors are used internally but they are exposed in case you need reuse any of them.

The errors emitted by any error event of this module can contain a property named code. It has the name of the error.

module.ENOENT - File not found
module.EACCESS - Access violation
module.ENOSPC - Disk full or allocation exceeded
module.EBADOP - Illegal TFTP operation
module.ETID - Unknown transfer ID
module.EEXIST - File already exists
module.ENOUSER - No such user
module.EDENY - The request has been denied
module.ESOCKET - Invalid remote socket
module.EBADMSG - Malformed TFTP message
module.EABORT - Aborted
module.EFBIG - File too big
module.ETIME - Timed out
module.EBADMODE - Invalid transfer mode
module.EBADNAME - Invalid filename
module.EIO - I/O error
module.ENOGET - Cannot GET files
module.ENOPUT - Cannot PUT files
module.ERBIG - Request bigger than 512 bytes
module.ECONPUT - Concurrent PUT request over the same file