tftp

package module
v0.0.0-...-766a6dd Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2022 License: MIT Imports: 15 Imported by: 0

README

pack.ag/tftp

Go Report Card Coverage Status Build Status Build status GoDoc MIT licensed

pack.ag/tftp is a cross-platform, concurrent TFTP client and server implementation for Go.

Standards Implemented
Unique Features
  • Single Port Mode

    TL;DR: It allows TFTP to work through firewalls.

    A standard TFTP server implementation receives requests on port 69 and allocates a new high port (over 1024) dedicated to that request. In single port mode, the same port is used for transmit and receive. If the server is started on port 69, all communication will be done on port 69.

    The primary use case of this feature is to play nicely with firewalls. Most firewalls will prevent the typical case where the server responds back on a random port because they have no way of knowing that it is in response to a request that went out on port 69. In single port mode, the firewall will see a request go out to a server on port 69 and that server respond back on the same port, which most firewalls will allow.

    Of course if the firewall in question is configured to block TFTP connections, this setting won't help you.

    Enable single port mode with the --single-port flag. This is currently marked experimental as is diverges from the TFTP standard.

Installation

go get -u pack.ag/tftp

API

The API was inspired by Go's well-known net/http API. If you can write a net/http handler or middleware, you should have no problem doing the same with pack.ag/tftp.

Configuration Functions

One area that is noticeably different from net/http is the configuration of clients and servers. pack.ag/tftp uses "configuration functions" rather than the direct modification of the Client/Server struct or a configuration struct passed into the factory functions.

A few explanations of this pattern:

If this sounds complicated, don't worry, the public API is quiet simple. The NewClient and NewServer functions take zero or more configuration functions.

Want all defaults? Don't pass anything.

Want a Client configured for blocksize 9000 and windowsize 16? Pass in ClientBlocksize(9000) and ClientWindowsize(16).

// Default Client
tftp.NewClient()

// Client with blocksize 9000, windowsize 16
tftp.NewClient(tftp.ClientBlocksize(9000), tftp.ClientWindowsize(16))

// Configuring with a slice of options
opts := []tftp.ClientOpt{
    tftp.ClientMode(tftp.ModeOctet),
    tftp.ClientBlocksize(9000),
    tftp.ClientWindowsize(16),
    tftp.ClientTimeout(1),
    tftp.ClientTransferSize(true),
    tftp.ClientRetransmit(3),
}

tftp.NewClient(opts...)
Examples
Read File From Server, Print to stdout
client := tftp.NewClient()
resp, err := client.Get("myftp.local/myfile")
if err != nil {
    log.Fatalln(err)
}

err := io.Copy(os.Stdout, resp)
if err != nil {
    log.Fatalln(err)
}
Write File to Server

file, err := os.Open("myfile")
if err != nil {
    log.Fatalln(err)
}
defer file.Close()

// Get the file info se we can send size (not required)
fileInfo, err := file.Stat()
if err != nil {
    log.Println("error getting file size:", err)
}

client := tftp.NewClient()
err := client.Put("myftp.local/myfile", file, fileInfo.Size())
if err != nil {
    log.Fatalln(err)
}
HTTP Proxy

This rather contrived example proxies an incoming GET request to GitHub's public API. A more realistic use case might be proxying to PXE boot files on an HTTP server.

const baseURL = "https://api.github.com/"

func proxyTFTP(w tftp.ReadRequest) {
	// Append the requested path to the baseURL
	url := baseURL + w.Name()

	// Send the HTTP request
	resp, err := http.DefaultClient.Get(url)
	if err != nil {
		// This could send more specific errors, but here we'read
		// choosing to simply send "file not found"" with the error
		// message from the HTTP client back to the TFTP client.
		w.WriteError(tftp.ErrCodeFileNotFound, err.Error())
		return
	}
	defer resp.Body.Close()

	// Copy the body of the response to the TFTP client.
	if _, err := io.Copy(w, resp.Body); err != nil {
		log.Println(err)
	}
}

This function doesn't itself implement the required ReadHandler interface, but we can make it a ReadHandler with the ReadHandlerFunc adapter (much like http.HandlerFunc).

readHandler := tftp.ReadHandlerFunc(proxyTFTP)

server.ReadHandler(readHandler)

server.ListenAndServe()
# trivialt get localhost:6900 repos/golang/go -o - | jq
{
  "id": 23096959,
  "name": "go",
  "full_name": "golang/go",
  ...
}

Full example in examples/httpproxy/httpproxy.go.

Save Files to Database

Here tftpDB implements the WriteHandler interface directly.

// tftpDB embeds a *sql.DB and implements the tftp.ReadHandler interface.
type tftpDB struct {
	*sql.DB
}

func (db *tftpDB) ReceiveTFTP(w tftp.WriteRequest) {
	// Read the data from the client into memory
	data, err := ioutil.ReadAll(w)
	if err != nil {
		log.Println(err)
		return
	}

	// Insert the IP address of the client and the data into the database
	res, err := db.Exec("INSERT INTO tftplogs (ip, log) VALUES (?, ?)", w.Addr().IP.String(), string(data))
	if err != nil {
		log.Println(err)
		return
	}

	// Log a message with the details
	id, _ := res.LastInsertId()
	log.Printf("Inserted %d bytes of data from %s. (ID=%d)", len(data), w.Addr().IP, id)
}
# go run examples/database/database.go
2016/04/30 11:20:27 Inserted 32 bytes of data from 127.0.0.1. (ID=13)

Full example including checking the size before accepting the request in examples/database/database.go.

Documentation

Overview

Package tftp provides TFTP client and server implementations.

Index

Constants

View Source
const (

	// ErrCodeNotDefined - Not defined, see error message (if any).
	ErrCodeNotDefined ErrorCode = 0x0
	// ErrCodeFileNotFound - File not found.
	ErrCodeFileNotFound ErrorCode = 0x1
	// ErrCodeAccessViolation - Access violation.
	ErrCodeAccessViolation ErrorCode = 0x2
	// ErrCodeDiskFull - Disk full or allocation exceeded.
	ErrCodeDiskFull ErrorCode = 0x3
	// ErrCodeIllegalOperation - Illegal TFTP operation.
	ErrCodeIllegalOperation ErrorCode = 0x4
	// ErrCodeUnknownTransferID - Unknown transfer ID.
	ErrCodeUnknownTransferID ErrorCode = 0x5
	// ErrCodeFileAlreadyExists - File already exists.
	ErrCodeFileAlreadyExists ErrorCode = 0x6
	// ErrCodeNoSuchUser - No such user.
	ErrCodeNoSuchUser ErrorCode = 0x7

	// ModeNetASCII is the string for netascii transfer mode
	ModeNetASCII TransferMode = "netascii"
	// ModeOctet is the string for octet/binary transfer mode
	ModeOctet TransferMode = "octet"
)

Variables

View Source
var (

	// ErrInvalidURL indicates that the URL passed to Get or Put is invalid.
	ErrInvalidURL = errors.New("invalid URL")
	// ErrInvalidHostIP indicates an empty or invalid host.
	ErrInvalidHostIP = errors.New("invalid host/IP")
	// ErrInvalidFile indicates an empty or invalid file.
	ErrInvalidFile = errors.New("invalid file")
	// ErrSizeNotReceived indicates tsize was not negotiated.
	ErrSizeNotReceived = errors.New("size not received")
	// ErrAddressNotAvailable indicates the server address was requested before
	// the server had been started.
	ErrAddressNotAvailable = errors.New("address not available until server has been started")
	// ErrNoRegisteredHandlers indicates no handlers were registered before starting the server.
	ErrNoRegisteredHandlers = errors.New("no handlers registered")
	// ErrInvalidNetwork indicates that a network other than udp, udp4, or udp6 was configured.
	ErrInvalidNetwork = errors.New("invalid network: must be udp, udp4, or udp6")
	// ErrInvalidBlocksize indicates that a blocksize outside the range 8 to 65464 was configured.
	ErrInvalidBlocksize = errors.New("invalid blocksize: must be between 8 and 65464")
	// ErrInvalidTimeout indicates that a timeout outside the range 1 to 255 was configured.
	ErrInvalidTimeout = errors.New("invalid timeout: must be between 1 and 255")
	// ErrInvalidWindowsize indicates that a windowsize outside the range 1 to 65535 was configured.
	ErrInvalidWindowsize = errors.New("invalid windowsize: must be between 1 and 65535")
	// ErrInvalidMode indicates that a mode other than ModeNetASCII or ModeOctet was configured.
	ErrInvalidMode = errors.New("invalid transfer mode: must be ModeNetASCII or ModeOctet")
	// ErrInvalidRetransmit indicates that the retransmit limit was configured with a negative value.
	ErrInvalidRetransmit = errors.New("invalid retransmit: cannot be negative")
	// ErrMaxRetries indicates that the maximum number of retries has been reached.
	ErrMaxRetries = errors.New("max retries reached")
)

Functions

func ErrorCause

func ErrorCause(err error) error

ErrorCause extracts the original error from an error wrapped by tftp.

func IsOptionParsingError

func IsOptionParsingError(err error) bool

IsOptionParsingError allows a consumer to check if an error was induced during option parsing.

func IsRemoteError

func IsRemoteError(err error) bool

IsRemoteError allows a consumer to check if an error was an error by the remote client/server.

func IsUnexpectedDatagram

func IsUnexpectedDatagram(err error) bool

IsUnexpectedDatagram allows a consumer to check if an error is an unexpected datagram.

Types

type Client

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

Client makes requests to a server.

func NewClient

func NewClient(opts ...ClientOpt) (*Client, error)

NewClient returns a configured Client.

Any number of ClientOpts can be provided to modify the default client behavior.

func (*Client) Get

func (c *Client) Get(url string) (*Response, error)

Get initiates a read request a server.

URL is in the format tftp://[server]:[port]/[file]

func (*Client) Put

func (c *Client) Put(url string, r io.Reader, size int64) (err error)

Put takes an io.Reader request a server.

URL is in the format tftp://[server]:[port]/[file]

type ClientOpt

type ClientOpt func(*Client) error

ClientOpt is a function that configures a Client.

func ClientBlocksize

func ClientBlocksize(size int) ClientOpt

ClientBlocksize configures the number of data bytes that will be send in each datagram. Valid range is 8 to 65464.

Default: 512.

func ClientMode

func ClientMode(mode TransferMode) ClientOpt

ClientMode configures the mode.

Valid options are ModeNetASCII and ModeOctet. Default is ModeNetASCII.

func ClientRetransmit

func ClientRetransmit(i int) ClientOpt

ClientRetransmit configures the per-packet retransmission limit for all requests.

Default: 10.

func ClientTimeout

func ClientTimeout(seconds int) ClientOpt

ClientTimeout configures the number of seconds to wait before resending an unacknowledged datagram. Valid range is 1 to 255.

Default: 1.

func ClientTransferSize

func ClientTransferSize(enable bool) ClientOpt

ClientTransferSize requests for the server to send the file size before sending.

Default: enabled.

func ClientWindowsize

func ClientWindowsize(window int) ClientOpt

ClientWindowsize configures the number of datagrams that will be transmitted before needing an acknowledgement.

Default: 1.

type ErrorCode

type ErrorCode uint16

ErrorCode is a TFTP error code as defined in RFC 1350

func (ErrorCode) String

func (e ErrorCode) String() string

type ReadHandler

type ReadHandler interface {
	ServeTFTP(ReadRequest)
}

ReadHandler responds to a TFTP read request.

type ReadHandlerFunc

type ReadHandlerFunc func(ReadRequest)

ReadHandlerFunc is an adapter type to allow a function to serve as a ReadHandler.

func (ReadHandlerFunc) ServeTFTP

func (h ReadHandlerFunc) ServeTFTP(w ReadRequest)

ServeTFTP calls the ReadHandlerFunc function.

type ReadRequest

type ReadRequest interface {
	// Addr is the network address of the client.
	Addr() *net.UDPAddr

	// Name is the file name requested by the client.
	Name() string

	// Write write's data to the client.
	Write([]byte) (int, error)

	// WriteError sends an error to the client and terminates the
	// connection. WriteError can only be called once. Write cannot
	// be called after an error has been written.
	WriteError(ErrorCode, string)

	// WriteSize sets the transfer size (tsize) value to be sent to
	// the client. It must be called before any calls to Write.
	WriteSize(int64)

	// TransferMode returns the TFTP transfer mode requested by the client.
	TransferMode() TransferMode
}

ReadRequest is provided to a ReadHandler's ServeTFTP method.

type ReadWriteHandler

type ReadWriteHandler interface {
	ReadHandler
	WriteHandler
}

ReadWriteHandler combines ReadHandler and WriteHandler.

func FileServer

func FileServer(dir string) ReadWriteHandler

FileServer creates a handler for sending and reciving files on the filesystem.

type Response

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

Response is an io.Reader for receiving files from a TFTP server.

func (*Response) Read

func (r *Response) Read(p []byte) (int, error)

func (*Response) Size

func (r *Response) Size() (int64, error)

Size returns the transfer size as indicated by the server in the tsize option.

ErrSizeNotReceived will be returned if tsize option was not enabled.

type Server

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

Server contains the configuration to run a TFTP server.

A ReadHandler, WriteHandler, or both can be registered to the server. If one of the handlers isn't registered, the server will return errors to clients attempting to use them.

func NewServer

func NewServer(addr string, opts ...ServerOpt) (*Server, error)

NewServer returns a configured Server.

Addr is the network address to listen on and is in the form "host:port". If a no host is given the server will listen on all interfaces.

Any number of ServerOpts can be provided to configure optional values.

func (*Server) Addr

func (s *Server) Addr() (*net.UDPAddr, error)

Addr is the network address of the server. It is available after the server has been started.

func (*Server) Close

func (s *Server) Close() error

Close stops the server and closes the network connection.

func (*Server) Connected

func (s *Server) Connected() bool

Connected is true if the server has started serving.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

ListenAndServe starts a configured server.

func (*Server) ReadHandler

func (s *Server) ReadHandler(rh ReadHandler)

ReadHandler registers a ReadHandler for the server.

func (*Server) Serve

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

Serve starts the server using an existing UDPConn.

func (*Server) WriteHandler

func (s *Server) WriteHandler(wh WriteHandler)

WriteHandler registers a WriteHandler for the server.

type ServerOpt

type ServerOpt func(*Server) error

ServerOpt is a function that configures a Server.

func ServerNet

func ServerNet(net string) ServerOpt

ServerNet configures the network a server listens on. Must be one of: udp, udp4, udp6.

Default: udp.

func ServerRetransmit

func ServerRetransmit(i int) ServerOpt

ServerRetransmit configures the per-packet retransmission limit for all requests.

Default: 10.

func ServerSingleAddress

func ServerSingleAddress(addrStr string) ServerOpt

ServerSingleAddress is like ServerSinglePort, but uses a different address than the one the server is listening on.

Using this implies ServerSinglePort(true).

This is an experimental feature.

func ServerSinglePort

func ServerSinglePort(enable bool) ServerOpt

ServerSinglePort enables the server to service all requests via a single port rather than the standard TFTP behavior of each client communicating on a separate port.

This is an experimental feature.

Default is disabled.

type TransferMode

type TransferMode string

TransferMode is a TFTP transer mode

type WriteHandler

type WriteHandler interface {
	ReceiveTFTP(WriteRequest)
}

WriteHandler responds to a TFTP write request.

type WriteHandlerFunc

type WriteHandlerFunc func(WriteRequest)

WriteHandlerFunc is an adapter type to allow a function to serve as a WriteHandler.

func (WriteHandlerFunc) ReceiveTFTP

func (h WriteHandlerFunc) ReceiveTFTP(w WriteRequest)

ReceiveTFTP calls the WriteHandlerFunc function.

type WriteRequest

type WriteRequest interface {
	// Addr is the network address of the client.
	Addr() *net.UDPAddr

	// Name is the file name provided by the client.
	Name() string

	// Read reads the request data from the client.
	Read([]byte) (int, error)

	// Size returns the transfer size (tsize) as provided by the client.
	// If the tsize option was not negotiated, an error will be returned.
	Size() (int64, error)

	// WriteError sends an error to the client and terminates the
	// connection. WriteError can only be called once. Read cannot
	// be called after an error has been written.
	WriteError(ErrorCode, string)

	// TransferMode returns the TFTP transfer mode requested by the client.
	TransferMode() TransferMode
}

WriteRequest is provided to a WriteHandler's ReceiveTFTP method.

Directories

Path Synopsis
_examples
Package netascii implements reading and writing of netascii, as defined in RFC 764.
Package netascii implements reading and writing of netascii, as defined in RFC 764.

Jump to

Keyboard shortcuts

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