tftp

package module
v2.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 22, 2021 License: MIT Imports: 12 Imported by: 0

README

TFTP server and client library for Golang

GoDoc Build Status

Implements:

Partially implements (tsize server side only):

  • RFC 2349 - TFTP Timeout Interval and Transfer Size Options

Set of features is sufficient for PXE boot support.

import "github.com/pin/tftp"

The package is cohesive to Golang io. Particularly it implements io.ReaderFrom and io.WriterTo interfaces. That allows efficient data transmission without unnecessary memory copying and allocations.

TFTP Server


// readHandler is called when client starts file download from server
func readHandler(filename string, rf io.ReaderFrom) error {
	file, err := os.Open(filename)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return err
	}
	n, err := rf.ReadFrom(file)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return err
	}
	fmt.Printf("%d bytes sent\n", n)
	return nil
}

// writeHandler is called when client starts file upload to server
func writeHandler(filename string, wt io.WriterTo) error {
	file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return err
	}
	n, err := wt.WriteTo(file)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return err
	}
	fmt.Printf("%d bytes received\n", n)
	return nil
}

func main() {
	// use nil in place of handler to disable read or write operations
	s := tftp.NewServer(readHandler, writeHandler)
	s.SetTimeout(5 * time.Second) // optional
	err := s.ListenAndServe(":69") // blocks until s.Shutdown() is called
	if err != nil {
		fmt.Fprintf(os.Stdout, "server: %v\n", err)
		os.Exit(1)
	}
}

TFTP Client

Upload file to server:

c, err := tftp.NewClient("172.16.4.21:69")
file, err := os.Open(path)
c.SetTimeout(5 * time.Second) // optional
rf, err := c.Send("foobar.txt", "octet")
n, err := rf.ReadFrom(file)
fmt.Printf("%d bytes sent\n", n)

Download file from server:

c, err := tftp.NewClient("172.16.4.21:69")
wt, err := c.Receive("foobar.txt", "octet")
file, err := os.Create(path)
// Optionally obtain transfer size before actual data.
if n, ok := wt.(IncomingTransfer).Size(); ok {
	fmt.Printf("Transfer size: %d\n", n)
}
n, err := wt.WriteTo(file)
fmt.Printf("%d bytes received\n", n)

Note: please handle errors better :)

TSize option

PXE boot ROM often expects tsize option support from a server: client (e.g. computer that boots over the network) wants to know size of a download before the actual data comes. Server has to obtain stream size and send it to a client.

Often it will happen automatically because TFTP library tries to check if io.Reader provided to ReadFrom method also satisfies io.Seeker interface (os.File for instance) and uses Seek to determine file size.

In case io.Reader you provide to ReadFrom in read handler does not satisfy io.Seeker interface or you do not want TFTP library to call Seek on your reader but still want to respond with tsize option during outgoing request you can use an OutgoingTransfer interface:


func readHandler(filename string, rf io.ReaderFrom) error {
	...
	// Set transfer size before calling ReadFrom.
	rf.(tftp.OutgoingTransfer).SetSize(myFileSize)
	...
	// ReadFrom ...

Similarly, it is possible to obtain size of a file that is about to be received using IncomingTransfer interface (see Size method).

Local and Remote Address

The OutgoingTransfer and IncomingTransfer interfaces also provide the RemoteAddr method which returns the peer IP address and port as a net.UDPAddr. The RequestPacketInfo interface provides a LocalIP method with returns the local IP address as a net.IP that the request is being handled on. These can be used for detailed logging in a server handler, among other things.

Note that LocalIP may return nil or an unspecified IP address if finding that is not supported on a particular operating system by the Go net libraries, or if you call it as a TFTP client.


func readHandler(filename string, rf io.ReaderFrom) error {
        ...
        raddr := rf.(tftp.OutgoingTransfer).RemoteAddr()
        laddr := rf.(tftp.RequestPacketInfo).LocalIP()
        log.Println("RRQ from", raddr.String(), "To ",laddr.String())
        log.Println("")
        ...
        // ReadFrom ...

Backoff

The default backoff before retransmitting an unacknowledged packet is a random duration between 0 and 1 second. This behavior can be overridden in clients and servers by providing a custom backoff calculation function.

	s := tftp.NewServer(readHandler, writeHandler)
	s.SetBackoff(func (attempts int) time.Duration {
		return time.Duration(attempts) * time.Second
	})

or, for no backoff

	s.SetBackoff(func (int) time.Duration { return 0 })

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client stores data about a single TFTP client

func NewClient

func NewClient(addr string) (*Client, error)

NewClient creates TFTP client for server on address provided.

func (Client) Receive

func (c Client) Receive(filename string, mode string) (io.WriterTo, error)

Receive starts incoming file transmission. It returns io.WriterTo or error.

func (*Client) RequestTSize

func (c *Client) RequestTSize(s bool)

RequestTSize sets flag to indicate if tsize should be requested.

func (Client) Send

func (c Client) Send(filename string, mode string) (io.ReaderFrom, error)

Send starts outgoing file transmission. It returns io.ReaderFrom or error.

func (*Client) SetBackoff

func (c *Client) SetBackoff(h backoffFunc)

SetBackoff sets a user provided function that is called to provide a backoff duration prior to retransmitting an unacknowledged packet.

func (*Client) SetBlockSize

func (c *Client) SetBlockSize(s int)

SetBlockSize sets a custom block size used in the transmission.

func (*Client) SetRetries

func (c *Client) SetRetries(count int)

SetRetries sets maximum number of attempts client made to transmit a packet. Default is 5 attempts.

func (*Client) SetTimeout

func (c *Client) SetTimeout(t time.Duration)

SetTimeout sets maximum time client waits for single network round-trip to succeed. Default is 5 seconds.

type Hook

type Hook interface {
	OnSuccess(stats TransferStats)
	OnFailure(stats TransferStats, err error)
}

Hook is an interface used to provide the server with success and failure hooks

type IncomingTransfer

type IncomingTransfer interface {
	// Size returns the size of an incoming file if the request included the
	// tsize option (see RFC2349).  To differentiate a zero-sized file transfer
	// from a request without tsize use the second boolean "ok" return value.
	Size() (n int64, ok bool)

	// RemoteAddr returns the remote peer's IP address and port.
	RemoteAddr() net.UDPAddr
}

IncomingTransfer provides methods that expose information associated with an incoming transfer.

type OutgoingTransfer

type OutgoingTransfer interface {
	// SetSize is used to set the outgoing transfer size (tsize option: RFC2349)
	// manually in a server write transfer handler.
	//
	// It is not necessary in most cases; when the io.Reader provided to
	// ReadFrom also satisfies io.Seeker (e.g. os.File) the transfer size will
	// be determined automatically. Seek will not be attempted when the
	// transfer size option is set with SetSize.
	//
	// The value provided will be used only if SetSize is called before ReadFrom
	// and only on in a server read handler.
	SetSize(n int64)

	// RemoteAddr returns the remote peer's IP address and port.
	RemoteAddr() net.UDPAddr
}

OutgoingTransfer provides methods to set the outgoing transfer size and retrieve the remote address of the peer.

type RequestPacketInfo

type RequestPacketInfo interface {
	// LocalAddr returns the IP address we are servicing the request on.
	// If it is unable to determine what address that is, the returned
	// net.IP will be nil.
	LocalIP() net.IP
}

RequestPacketInfo provides a method of getting the local IP address that is handling a UDP request. It relies for its accuracy on the OS providing methods to inspect the underlying UDP and IP packets directly.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is an instance of a TFTP server

func NewServer

func NewServer(readHandler func(filename string, rf io.ReaderFrom) error,
	writeHandler func(filename string, wt io.WriterTo) error) *Server

NewServer creates TFTP server. It requires two functions to handle read and write requests. In case nil is provided for read or write handler the respective operation is disabled.

func (*Server) EnableSinglePort

func (s *Server) EnableSinglePort()

EnableSinglePort enables an experimental mode where the server will serve all connections on port 69 only. There will be no random TIDs on the server side.

Enabling this will negatively impact performance

func (*Server) ListenAndServe

func (s *Server) ListenAndServe(addr string) error

ListenAndServe binds to address provided and start the server. ListenAndServe returns when Shutdown is called.

func (*Server) Serve

func (s *Server) Serve(conn net.PacketConn) error

Serve starts server provided already opened UDP connecton. It is useful for the case when you want to run server in separate goroutine but still want to be able to handle any errors opening connection. Serve returns when Shutdown is called or connection is closed.

func (*Server) SetAnticipate

func (s *Server) SetAnticipate(winsz uint)

SetAnticipate provides an experimental feature in which when a packets is requested the server will keep sending a number of packets before checking whether an ack has been received. It improves tftp downloading speed by a few times. The argument winsz specifies how many packets will be sent before waiting for an ack packet. When winsz is bigger than 1, the feature is enabled, and the server runs through a different experimental code path. When winsz is 0 or 1, the feature is disabled.

func (*Server) SetBackoff

func (s *Server) SetBackoff(h backoffFunc)

SetBackoff sets a user provided function that is called to provide a backoff duration prior to retransmitting an unacknowledged packet.

func (*Server) SetBlockSize

func (s *Server) SetBlockSize(i int)

SetBlockSize sets the maximum size of an individual data block. This must be a value between 512 (the default block size for TFTP) and 65456 (the max size a UDP packet payload can be).

This is an advisory value -- it will be clamped to the smaller of the block size the client wants and the MTU of the interface being communicated over munis overhead.

func (*Server) SetHook

func (s *Server) SetHook(hook Hook)

SetHook sets the Hook for success and failure of transfers

func (*Server) SetRetries

func (s *Server) SetRetries(count int)

SetRetries sets maximum number of attempts server made to transmit a packet. Default is 5 attempts.

func (*Server) SetTimeout

func (s *Server) SetTimeout(t time.Duration)

SetTimeout sets maximum time server waits for single network round-trip to succeed. Default is 5 seconds.

func (*Server) Shutdown

func (s *Server) Shutdown()

Shutdown make server stop listening for new requests, allows server to finish outstanding transfers and stops server.

type TransferStats

type TransferStats struct {
	RemoteAddr              net.IP
	Filename                string
	Tid                     int
	SenderAnticipateEnabled bool
	Mode                    string
	Opts                    options
	Duration                time.Duration
	DatagramsSent           int
	DatagramsAcked          int
}

TransferStats contains details about a single TFTP transfer

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL