TFTP server and client library for Golang
=========================================

[![GoDoc](https://godoc.org/github.com/pin/tftp?status.svg)](https://godoc.org/github.com/pin/tftp)

Implements:
 * [RFC 1350](https://tools.ietf.org/html/rfc1350) - The TFTP Protocol (Revision 2)
 * [RFC 2347](https://tools.ietf.org/html/rfc2347) - TFTP Option Extension
 * [RFC 2348](https://tools.ietf.org/html/rfc2348) - TFTP Blocksize Option

Partially implements (tsize server side only):
 * [RFC 2349](https://tools.ietf.org/html/rfc2349) - TFTP Timeout Interval and Transfer Size Options

Set of features is sufficient for PXE boot support.

``` go
import "github.com/pin/tftp/v3"
```

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

```go

// 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:

```go
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:

```go
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.(tftp.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:

```go

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.

```go

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.

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

or, for no backoff

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