Morten Møller Riis 66c5f6130a Added tftp datasource for cloud config. 5 年之前
..
netascii 66c5f6130a Added tftp datasource for cloud config. 5 年之前
.gitignore 66c5f6130a Added tftp datasource for cloud config. 5 年之前
.travis.yml 66c5f6130a Added tftp datasource for cloud config. 5 年之前
CONTRIBUTORS 66c5f6130a Added tftp datasource for cloud config. 5 年之前
LICENSE 66c5f6130a Added tftp datasource for cloud config. 5 年之前
README.md 66c5f6130a Added tftp datasource for cloud config. 5 年之前
backoff.go 66c5f6130a Added tftp datasource for cloud config. 5 年之前
client.go 66c5f6130a Added tftp datasource for cloud config. 5 年之前
packet.go 66c5f6130a Added tftp datasource for cloud config. 5 年之前
receiver.go 66c5f6130a Added tftp datasource for cloud config. 5 年之前
sender.go 66c5f6130a Added tftp datasource for cloud config. 5 年之前
server.go 66c5f6130a Added tftp datasource for cloud config. 5 年之前
tftp_test.go 66c5f6130a Added tftp datasource for cloud config. 5 年之前

README.md

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).

Remote Address

The OutgoingTransfer and IncomingTransfer interfaces also provide the RemoteAddr method which returns the peer IP address and port as a net.UDPAddr. This can be used for detailed logging in a server handler.


func readHandler(filename string, rf io.ReaderFrom) error {
        ...
        raddr := rf.(tftp.OutgoingTransfer).RemoteAddr()
        log.Println("RRQ from", raddr.String())
        ...
        // 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 })