mirror of
https://github.com/rancher/os.git
synced 2025-07-15 07:41:39 +00:00
172 lines
4.8 KiB
Markdown
172 lines
4.8 KiB
Markdown
TFTP server and client library for Golang
|
|
=========================================
|
|
|
|
[](https://godoc.org/github.com/pin/tftp)
|
|
[](https://travis-ci.org/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"
|
|
```
|
|
|
|
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.(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).
|
|
|
|
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.
|
|
|
|
```go
|
|
|
|
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.
|
|
|
|
```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 })
|
|
```
|