trivialt

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 30, 2016 License: MIT Imports: 13 Imported by: 1

README

trivialt

Go Report Card Coverage Status Build Status Build status GoDoc

trivialt is a cross-platform, concurrent TFTP server and client. It can be used as a standalone executable or included in a Go project as a library.

Standards Implemented

Installation

If you have the Go toolchain installed you can simply go get the packages. This will download the source into your $GOPATH and install the binary to $GOPATH/bin/trivialt.

go get -u github.com/vcabbage/trivialt/...

Binary downloads coming soon.

Command Usage

Running as a server:

# trivialt serve --help
NAME:
   trivialt serve - Serve files from the filesystem.

USAGE:
   trivialt serve [bind address] [root directory]

DESCRIPTION:
   Serves files from the local file systemd.

   Bind address is in form "ip:port". Omitting the IP will listen on all interfaces.
   If not specified the server will listen on all interfaces, port 69.app

   Files will be served from root directory. If omitted files will be served from
   the current directory.

OPTIONS:
   --writeable, -w	Enable file upload.
# trivialt serve :6900 /tftproot --writable
Starting TFTP Server on ":6900", serving "/tftproot"
Read Request from 127.0.0.1:61877 for "ubuntu-16.04-server-amd64.iso"
Write Request from 127.0.0.1:51205 for "ubuntu-16.04-server-amd64.iso"

Downloading a file:

# trivialt get --help
NAME:
   trivialt get - Download file from a server.

USAGE:
   trivialt get [command options] [server:port] [file]

OPTIONS:
   --blksize, -b "512"      Number of data bytes to send per-packet.
   --windowsize, -w "1"     Number of packets to send before requiring an acknowledgement.
   --timeout, -t "10"       Number of seconds to wait before terminating a stalled connection.
   --tsize                  Enable the transfer size option. (default)
   --retransmit, -r "10"    Maximum number of back-to-back lost packets before terminating the connection.
   --netascii               Enable netascii transfer mode.
   --binary, --octet, -i    Enable binary transfer mode. (default)
   --quiet, -q              Don't display progress.
   --output, -o             Sets the output location to write the file. If not specified the
                            file will be written in the current directory.
                            Specifying "-" will write the file to stdout. ("-" implies "--quiet")
# trivialt get localhost:6900 ubuntu-16.04-server-amd64.iso
ubuntu-16.04-server-amd64.iso:
 655.00 MB / 655.00 MB [=====================================================] 100.00% 16.76 MB/s39s

Uploading a file:

# trivialt get --help
NAME:
   trivialt get - Download file from a server.

USAGE:
   trivialt get [command options] [server:port] [file]

OPTIONS:
   --blksize, -b "512"      Number of data bytes to send per-packet.
   --windowsize, -w "1"     Number of packets to send before requiring an acknowledgement.
   --timeout, -t "10"       Number of seconds to wait before terminating a stalled connection.
   --tsize                  Enable the transfer size option. (default)
   --retransmit, -r "10"	Maximum number of back-to-back lost packets before terminating the connection.
   --netascii               Enable netascii transfer mode.
   --binary, --octet, -i	Enable binary transfer mode. (default)
   --quiet, -q              Don't display progress.
   --output, -o             Sets the output location to write the file. If not specified the
                            file will be written in the current directory.
                            Specifying "-" will write the file to stdout. ("-" implies "--quiet")
# trivialt put localhost:6900 ubuntu-16.04-server-amd64.iso --blksize 1468 --windowsize 16
ubuntu-16.04-server-amd64.iso:
 655.00 MB / 655.00 MB [=====================================================] 100.00% 178.41 MB/s3s

API

trivialt's 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 for trivialt.

Configuration Functions

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

A few explainations 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
trivialt.NewClient()

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

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

trivialt.NewClient(opts...)
Examples
Read File From Server, Print to stdout
client := trivialt.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 := trivialt.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 trivialt.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(trivialt.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 := trivialt.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 trivialt.ReadHandler interface.
type tftpDB struct {
	*sql.DB
}

func (db *tftpDB) ReceiveTFTP(w trivialt.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 trivialt 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")
)

Functions

func ErrorCause

func ErrorCause(err error) error

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

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

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

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

WriteRequest is provided to a WriteHandler's ReceiveTFTP method.

Directories

Path Synopsis
cmd
trivialt command
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