├── cmd └── rotftpd │ ├── README.md │ └── main.go ├── .travis.yml ├── netascii.go ├── string.go ├── README.md ├── LICENSE.md ├── tftp_test.go ├── request.go ├── netascii_test.go ├── server.go ├── tftp.go ├── packet.go ├── packet_test.go └── response.go /cmd/rotftpd/README.md: -------------------------------------------------------------------------------- 1 | rotftpd 2 | ======= 3 | 4 | Command `rotftpd` is a simple, read-only, TFTP server, which can be used to 5 | serve files from a single directory over TFTP. 6 | 7 | Usage 8 | ===== 9 | 10 | ``` 11 | $ ./rotftpd -h 12 | Usage of ./rotftpd: 13 | -addr=":69": host:port pair for TFTP server to listen on 14 | -dir=".": directory containing files to be served over TFTP 15 | ``` 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.4.2 4 | - tip 5 | before_install: 6 | - go get github.com/axw/gocov/gocov 7 | - go get github.com/mattn/goveralls 8 | - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi 9 | before_script: 10 | - go get -d ./... 11 | script: 12 | - go test -v ./... 13 | - $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN 14 | -------------------------------------------------------------------------------- /netascii.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | // netASCIIResponseWriter is a ResponseWriter which seamlessly writes input 8 | // data in netascii format to the embedded ResponseWriter. 9 | type netASCIIResponseWriter struct { 10 | ResponseWriter 11 | } 12 | 13 | // Write converts data to netascii format and writes it to the embedded 14 | // ResponseWriter. 15 | func (w *netASCIIResponseWriter) Write(p []byte) (int, error) { 16 | b := make([]byte, len(p)) 17 | copy(b, p) 18 | 19 | // If using netascii mode, some conversions must be made to 20 | // input data: 21 | // - LF -> CR+LF 22 | // - CR -> CR+NULL 23 | b = bytes.Replace(b, []byte{'\r'}, []byte{'\r', 0}, -1) 24 | b = bytes.Replace(b, []byte{'\n'}, []byte{'\r', '\n'}, -1) 25 | 26 | _, err := w.ResponseWriter.Write(b) 27 | return len(p), err 28 | } 29 | -------------------------------------------------------------------------------- /string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -output=string.go -type=ErrorCode,Opcode; DO NOT EDIT 2 | 3 | package tftp 4 | 5 | import "fmt" 6 | 7 | const _ErrorCode_name = "ErrorCodeUndefinedErrorCodeFileNotFoundErrorCodeAccessViolationErrorCodeDiskFullErrorCodeIllegalOperationErrorCodeUnknownTransferIDErrorCodeFileExistsErrorCodeNoSuchUser" 8 | 9 | var _ErrorCode_index = [...]uint8{0, 18, 39, 63, 80, 105, 131, 150, 169} 10 | 11 | func (i ErrorCode) String() string { 12 | if i >= ErrorCode(len(_ErrorCode_index)-1) { 13 | return fmt.Sprintf("ErrorCode(%d)", i) 14 | } 15 | return _ErrorCode_name[_ErrorCode_index[i]:_ErrorCode_index[i+1]] 16 | } 17 | 18 | const _Opcode_name = "OpcodeReadOpcodeWriteopcodeDATAopcodeACKOpcodeError" 19 | 20 | var _Opcode_index = [...]uint8{0, 10, 21, 31, 40, 51} 21 | 22 | func (i Opcode) String() string { 23 | i -= 1 24 | if i >= Opcode(len(_Opcode_index)-1) { 25 | return fmt.Sprintf("Opcode(%d)", i+1) 26 | } 27 | return _Opcode_name[_Opcode_index[i]:_Opcode_index[i+1]] 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tftp [![Build Status](https://travis-ci.org/mdlayher/tftp.svg?branch=master)](https://travis-ci.org/mdlayher/tftp) [![Coverage Status](https://coveralls.io/repos/mdlayher/tftp/badge.svg?branch=master)](https://coveralls.io/r/mdlayher/tftp?branch=master) [![GoDoc](http://godoc.org/github.com/mdlayher/tftp?status.svg)](http://godoc.org/github.com/mdlayher/tftp) 2 | ==== 3 | 4 | Package `tftp` implements a TFTP server, as described in IETF RFC 1350. MIT Licensed. 5 | 6 | A basic, read-only, TFTP server (`rotftpd`) which demonstrates the library is 7 | available at [cmd/rotftpd](https://github.com/mdlayher/tftp/blob/master/cmd/rotftpd). 8 | 9 | At this time, the API is not stable, and may change over time. The eventual 10 | goal is to implement a server, client, and testing facilities for consumers 11 | of this package. 12 | 13 | The design of this package is inspired by Go's `net/http` package. The Go 14 | standard library is Copyright (c) 2012 The Go Authors. All rights reserved. 15 | The Go license can be found at https://golang.org/LICENSE. 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | =========== 3 | 4 | Copyright (C) 2015 Matt Layher 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /tftp_test.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | // Test_fromNetASCII verifies that fromNetASCII properly converts a byte slice 9 | // from netascii transfer mode form. 10 | func Test_fromNetASCII(t *testing.T) { 11 | var tests = []struct { 12 | in []byte 13 | out []byte 14 | }{ 15 | { 16 | in: nil, 17 | out: nil, 18 | }, 19 | { 20 | in: []byte{'a', 'b', 'c'}, 21 | out: []byte{'a', 'b', 'c'}, 22 | }, 23 | { 24 | in: []byte{'a', '\r', '\n', 'b', '\r', '\n', 'c'}, 25 | out: []byte{'a', '\n', 'b', '\n', 'c'}, 26 | }, 27 | { 28 | in: []byte{'a', '\r', 0, 'b', '\r', 0, 'c'}, 29 | out: []byte{'a', '\r', 'b', '\r', 'c'}, 30 | }, 31 | { 32 | in: []byte{'a', '\r', 0, 'b', '\r', '\n', 'c'}, 33 | out: []byte{'a', '\r', 'b', '\n', 'c'}, 34 | }, 35 | // TODO(mdlayher): determine if it possible for a carriage return to 36 | // be the last character in a buffer. For the time being, we perform 37 | // no conversion if this is the case. 38 | { 39 | in: []byte{'a', '\r'}, 40 | out: []byte{'a', '\r'}, 41 | }, 42 | } 43 | 44 | for i, tt := range tests { 45 | if want, got := tt.out, fromNetASCII(tt.in); !bytes.Equal(want, got) { 46 | t.Fatalf("[%02d] unexpected fromNetASCII conversion:\n- want: %v\n- got: %v", 47 | i, want, got) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | // Request represents a processed TFTP request received by a server. 8 | // Its struct members contain information regarding the request type, filename, 9 | // transfer mode, etc. 10 | type Request struct { 11 | // Opcode specifies the requested operation, such as read or write. 12 | Opcode Opcode 13 | 14 | // Filename specifies the requested filename for a read request, or where a 15 | // file should be written on the server for a write request. 16 | Filename string 17 | 18 | // Mode specifies the transfer mode for this request, such as netascii or 19 | // octet. 20 | Mode Mode 21 | 22 | // Length of the TFTP request, in bytes. 23 | Length int64 24 | 25 | // Network address which was used to contact the TFTP server. The server 26 | // will automatically set up a socket to communicate with this address. 27 | RemoteAddr string 28 | } 29 | 30 | // parseRequest creates a new Request from an input byte slice and UDP address. 31 | // It populates the basic struct members which can be used in a TFTP handler. 32 | // 33 | // If the input byte slice is not a valid TFTP request packet, errInvalidRequestPacket 34 | // is returned. 35 | func parseRequest(b []byte, remoteAddr net.Addr) (*Request, error) { 36 | p, err := parseRequestPacket(b) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return &Request{ 42 | Opcode: p.Opcode, 43 | Filename: p.Filename, 44 | Mode: p.Mode, 45 | Length: int64(len(b)), 46 | RemoteAddr: remoteAddr.String(), 47 | }, nil 48 | } 49 | -------------------------------------------------------------------------------- /netascii_test.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | // Test_netASCIIResponseWriterWrite verifies that netASCIIResponseWriter.Write 9 | // properly converts a byte slice to netascii transfer mode form. 10 | func Test_netASCIIResponseWriterWrite(t *testing.T) { 11 | var tests = []struct { 12 | in []byte 13 | out []byte 14 | }{ 15 | { 16 | in: nil, 17 | out: nil, 18 | }, 19 | { 20 | in: []byte{'a', 'b', 'c'}, 21 | out: []byte{'a', 'b', 'c'}, 22 | }, 23 | { 24 | in: []byte{'a', '\n', 'b', '\n', 'c'}, 25 | out: []byte{'a', '\r', '\n', 'b', '\r', '\n', 'c'}, 26 | }, 27 | { 28 | in: []byte{'a', '\r', 'b', '\r', 'c'}, 29 | out: []byte{'a', '\r', 0, 'b', '\r', 0, 'c'}, 30 | }, 31 | { 32 | in: []byte{'a', '\r', 'b', '\n', 'c'}, 33 | out: []byte{'a', '\r', 0, 'b', '\r', '\n', 'c'}, 34 | }, 35 | } 36 | 37 | for i, tt := range tests { 38 | b := bytes.NewBuffer(nil) 39 | w := &netASCIIResponseWriter{&captureResponseWriter{ 40 | buf: b, 41 | }} 42 | w.Write(tt.in) 43 | 44 | if want, got := tt.out, b.Bytes(); !bytes.Equal(want, got) { 45 | t.Fatalf("[%02d] unexpected toNetASCII conversion:\n- want: %v\n- got: %v", 46 | i, want, got) 47 | } 48 | } 49 | } 50 | 51 | // captureResponseWriter captures any data written to it using a buffer. 52 | type captureResponseWriter struct { 53 | buf *bytes.Buffer 54 | } 55 | 56 | func (w *captureResponseWriter) Write(p []byte) (int, error) { return w.buf.Write(p) } 57 | func (w *captureResponseWriter) Close() error { return nil } 58 | func (w *captureResponseWriter) Flush() error { return nil } 59 | -------------------------------------------------------------------------------- /cmd/rotftpd/main.go: -------------------------------------------------------------------------------- 1 | // Command rotftpd is a simple, read-only, TFTP server, which can be used to 2 | // serve files from a single directory over TFTP. 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "io" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | "time" 12 | 13 | "github.com/mdlayher/tftp" 14 | ) 15 | 16 | var ( 17 | // addr specifies the address the TFTP server will listen on. 18 | addr = flag.String("addr", ":69", "host:port pair for TFTP server to listen on") 19 | 20 | // dir specifies the directory the TFTP server will serve. 21 | dir = flag.String("dir", ".", "directory containing files to be served over TFTP") 22 | ) 23 | 24 | func main() { 25 | flag.Parse() 26 | 27 | d := filepath.Clean(*dir) 28 | h := &Handler{ 29 | Directory: d, 30 | } 31 | 32 | log.Printf("serving TFTP directory %q on %s", d, *addr) 33 | if err := tftp.ListenAndServe(*addr, h); err != nil { 34 | log.Fatal(err) 35 | } 36 | } 37 | 38 | // Handler is a simple tftp.Handler implementation. 39 | type Handler struct { 40 | Directory string 41 | } 42 | 43 | // ServeTFTP serves incoming TFTP using the directory specified in Handler. 44 | func (h *Handler) ServeTFTP(w tftp.ResponseWriter, r *tftp.Request) { 45 | // Strip any directories from filename 46 | r.Filename = filepath.Base(r.Filename) 47 | 48 | // Ignore write requests 49 | if r.Opcode == tftp.OpcodeWrite { 50 | log.Printf("ignoring: [%s] %q (server is read-only)", r.RemoteAddr, r.Filename) 51 | 52 | // BUG(mdlayher): should report an error of some kind to the client so 53 | // it does not hang forever. 54 | return 55 | } 56 | 57 | // Open file to begin write 58 | f, err := os.Open(filepath.Join(h.Directory, r.Filename)) 59 | if err != nil { 60 | log.Println(err) 61 | return 62 | } 63 | defer f.Close() 64 | 65 | // Check file's size 66 | s, err := f.Stat() 67 | if err != nil { 68 | log.Println(err) 69 | return 70 | } 71 | 72 | log.Printf(" serving: [%s] %q, %d bytes", r.RemoteAddr, r.Filename, s.Size()) 73 | start := time.Now() 74 | 75 | // Begin copying file to client 76 | if _, err := io.Copy(w, f); err != nil && err != io.EOF { 77 | log.Println(err) 78 | return 79 | } 80 | 81 | // Flush any remaining buffered bytes 82 | if err := w.Flush(); err != nil { 83 | log.Println(err) 84 | return 85 | } 86 | 87 | // Close server's socket for this client 88 | _ = w.Close() 89 | log.Printf("complete: [%s] %q, %d bytes in %s", r.RemoteAddr, r.Filename, s.Size(), time.Now().Sub(start)) 90 | } 91 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | // Server represents a TFTP server, and is used to configure a TFTP server's 8 | // behavior. 9 | type Server struct { 10 | // Addr is the network address which this server should bind to. The 11 | // default value is :69, as specified in RFC 1350, Section 4. 12 | Addr string 13 | 14 | // Handler is the handler to use while serving TFP requests. 15 | // Handler must not be nil. 16 | Handler Handler 17 | } 18 | 19 | // ListenAndServe listens for UDP connections on the specified address, using 20 | // the default Server configuration and specified handler to handle TFTP 21 | // connections. 22 | func ListenAndServe(addr string, handler Handler) error { 23 | return (&Server{ 24 | Addr: addr, 25 | Handler: handler, 26 | }).ListenAndServe() 27 | } 28 | 29 | // ListenAndServe listens on the address specified by s.Addr. Serve is called 30 | // to handle serving TFTP traffic once ListenAndServe opens a UDP packet 31 | // connection. 32 | func (s *Server) ListenAndServe() error { 33 | conn, err := net.ListenPacket("udp", s.Addr) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | defer conn.Close() 39 | return s.Serve(conn) 40 | } 41 | 42 | // Serve configures and accepts incoming connections on PacketConn p, creating a 43 | // new goroutine for each. 44 | // 45 | // The service goroutine reads requests, generate the appropriate Request and 46 | // ResponseWriter values, then calls s.Handler to handle the request. 47 | func (s *Server) Serve(p net.PacketConn) error { 48 | // RRQ and WRQ packets are received here before creating a goroutine to 49 | // handle data transfer. There appears to be no maximum limit for the 50 | // size of one of these packets, so we will go with the Ethernet MTU, 51 | // since TFTP packets must fit inside one, unfragmented IP packet. 52 | buf := make([]byte, 1500) 53 | for { 54 | n, addr, err := p.ReadFrom(buf) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | go s.newConn(addr, n, buf).serve() 60 | } 61 | } 62 | 63 | // conn represents an in-flight TFTP connection, and contains information about 64 | // the connection and server. 65 | type conn struct { 66 | conn net.PacketConn 67 | remoteAddr net.Addr 68 | server *Server 69 | buf []byte 70 | } 71 | 72 | // newConn creates a new conn using information received in a single TFTP 73 | // request. newConn makes a copy of the input buffer for use in handling 74 | // a single request. 75 | // 76 | // BUG(mdlayher): consider using a sync.Pool with many buffers available to avoid 77 | // allocating a new one on each request. 78 | func (s *Server) newConn(addr net.Addr, n int, buf []byte) *conn { 79 | c := &conn{ 80 | remoteAddr: addr, 81 | server: s, 82 | buf: make([]byte, n), 83 | } 84 | copy(c.buf, buf[:n]) 85 | 86 | return c 87 | } 88 | 89 | // serve handles serving an individual TFTP request, and is invoked in a 90 | // goroutine. 91 | func (c *conn) serve() { 92 | // Attempt to parse a Request from a raw packet, providing a nicer 93 | // API for callers to implement their own TFTP request handlers 94 | r, err := parseRequest(c.buf, c.remoteAddr) 95 | if err != nil { 96 | // BUG(mdlayher): send ERROR response on invalid request 97 | if err == errInvalidRequestPacket { 98 | return 99 | } 100 | 101 | return 102 | } 103 | 104 | // Set up response by binding a new UDP socket to handle this request 105 | w, err := newResponse(c.server.Addr, c.remoteAddr, r.Mode) 106 | if err != nil { 107 | return 108 | } 109 | 110 | // This will panic if Handler is nil. 111 | // TODO(mdlayher): determine if a ServeMux type would be useful. 112 | c.server.Handler.ServeTFTP(w, r) 113 | } 114 | -------------------------------------------------------------------------------- /tftp.go: -------------------------------------------------------------------------------- 1 | // Package tftp implements a TFTP server, as described in RFC 1350. 2 | package tftp 3 | 4 | import ( 5 | "bytes" 6 | ) 7 | 8 | //go:generate stringer -output=string.go -type=ErrorCode,Opcode 9 | 10 | // Opcode represents a TFTP opcode, as defined in RFC 1350, Section 5. 11 | // Opcodes are used to send different types of messages between a client and 12 | // a server. 13 | type Opcode uint16 14 | 15 | // Opcode constants taken from RFC 1350, Section 5. 16 | const ( 17 | OpcodeRead Opcode = 1 18 | OpcodeWrite Opcode = 2 19 | OpcodeError Opcode = 5 20 | 21 | // Opcode types only used for internal communication 22 | opcodeDATA Opcode = 3 23 | opcodeACK Opcode = 4 24 | ) 25 | 26 | // Mode represents a TFTP transfer mode, as defined in RFC 1350, Section 1. 27 | // Modes are used to negotiate different types of transfer methods between a 28 | // client and a server. 29 | type Mode string 30 | 31 | // Mode constants taken from RFC 1350, Section 1. 32 | // 33 | // The mail mode is intentionally omitted, per RFC 1350: 34 | // The mail mode is obsolete and should not be implemented or used. 35 | const ( 36 | ModeNetASCII Mode = "netascii" 37 | ModeOctet Mode = "octet" 38 | ) 39 | 40 | // ErrorCode represents a TFTP error code, as defined in RFC 1350, Appendix I. 41 | // ErrorCodes are used to communicate different types of errors between a 42 | // client and a server. 43 | type ErrorCode uint16 44 | 45 | // ErrorCode constants taken from RFC 1350, Appendix I. 46 | const ( 47 | ErrorCodeUndefined ErrorCode = 0 48 | ErrorCodeFileNotFound ErrorCode = 1 49 | ErrorCodeAccessViolation ErrorCode = 2 50 | ErrorCodeDiskFull ErrorCode = 3 51 | ErrorCodeIllegalOperation ErrorCode = 4 52 | ErrorCodeUnknownTransferID ErrorCode = 5 53 | ErrorCodeFileExists ErrorCode = 6 54 | ErrorCodeNoSuchUser ErrorCode = 7 55 | ) 56 | 57 | // Handler provides an interface which allows structs to act as TFTP server 58 | // handlers. ServeTFTP implementations receive a copy of the incoming TFTP 59 | // request via the Request parameter, and allow outgoing communication via 60 | // the ResponseWriter. 61 | type Handler interface { 62 | ServeTFTP(ResponseWriter, *Request) 63 | } 64 | 65 | // HandlerFunc is an adapter type which allows the use of normal functions as 66 | // TFTP handlers. If f is a function with the appropriate signature, 67 | // HandlerFunc(f) is a Handler struct that calls f. 68 | type HandlerFunc func(ResponseWriter, *Request) 69 | 70 | // ServeTFTP calls f(w, r), allowing regular functions to implement Handler. 71 | func (f HandlerFunc) ServeTFTP(w ResponseWriter, r *Request) { 72 | f(w, r) 73 | } 74 | 75 | // ResponseWriter provides an interface which allows a TFTP handler to write 76 | // TFTP data packets to a client. The default ResponseWriter binds a new UDP 77 | // socket to communicate with a client, and closes it when Close is called. 78 | // 79 | // ResponseWriter implementations should buffer some data internally, in order 80 | // to send 512 byte blocks in a "lock-step" fashion to a client. 81 | type ResponseWriter interface { 82 | // Write implements io.Writer, and allows raw data to be sent to a client. 83 | Write([]byte) (int, error) 84 | 85 | // Close closes the underlying UDP socket used to communicate with a 86 | // client. 87 | Close() error 88 | 89 | // Flush flushes any buffered data to a client, signaling the end of data 90 | // transfer. 91 | Flush() error 92 | } 93 | 94 | // fromNetASCII performs the necessary conversions from an input buffer 95 | // needed when a client is using netascii mode. 96 | // 97 | // BUG(mdlayher): this could be heavily optimized in the future to not read 98 | // a single byte at a time 99 | func fromNetASCII(p []byte) []byte { 100 | b := make([]byte, len(p)) 101 | copy(b, p) 102 | 103 | // If using netascii mode, some conversions must be made from 104 | // input data: 105 | // - CR+LF -> LF 106 | // - CR+NULL -> CR 107 | b = bytes.Replace(b, []byte{'\r', '\n'}, []byte{'\n'}, -1) 108 | b = bytes.Replace(b, []byte{'\r', 0}, []byte{'\r'}, -1) 109 | 110 | return b 111 | } 112 | -------------------------------------------------------------------------------- /packet.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | var ( 12 | // errInvalidRequestPacket is returned when an invalid TFTP request is 13 | // received, allowing the server to reject the request. 14 | errInvalidRequestPacket = errors.New("invalid request packet") 15 | 16 | // errInvalidACKPacket is returned when an invalid TFTP ACK packet is received. 17 | errInvalidACKPacket = errors.New("invalid ACK packet") 18 | 19 | // errInvalidERRORPacket is returned when an invalid TFTP ERROR packet is 20 | // received. 21 | errInvalidERRORPacket = errors.New("invalid ERROR packet") 22 | ) 23 | 24 | // ErrorPacket represents an ERROR packet, as defined in RFC 1350, Section 5. 25 | // ErrorPacket implements the error interface, and may be returned during a 26 | // read or write operation with a client. 27 | type ErrorPacket struct { 28 | Opcode Opcode 29 | ErrorCode ErrorCode 30 | ErrorMsg string 31 | } 32 | 33 | // Error returns the string representation of an ErrorPacket. 34 | func (e *ErrorPacket) Error() string { 35 | return fmt.Sprintf("%s (%02d): %s", e.ErrorCode.String(), e.ErrorCode, e.ErrorMsg) 36 | } 37 | 38 | // requestPacket represents a raw request to a TFTP server. It is used to 39 | // construct a Request for client consumption. 40 | type requestPacket struct { 41 | Opcode Opcode 42 | Filename string 43 | Mode Mode 44 | } 45 | 46 | // parseRequestPacket attempts to parse a TFTP read or write request as a 47 | // requestPacket. 48 | func parseRequestPacket(b []byte) (*requestPacket, error) { 49 | // At a minimum, requests must contain a 2 byte opcode and 50 | // 2 NULL bytes 51 | if len(b) < 4 { 52 | return nil, errInvalidRequestPacket 53 | } 54 | 55 | opcode := Opcode(binary.BigEndian.Uint16(b[0:2])) 56 | 57 | // Only accept read and write requests to start transactions 58 | if opcode != OpcodeRead && opcode != OpcodeWrite { 59 | return nil, errInvalidRequestPacket 60 | } 61 | 62 | // Locate first NULL byte to determine filename offset 63 | idx := bytes.IndexByte(b[2:], 0) 64 | if idx == -1 { 65 | return nil, errInvalidRequestPacket 66 | } 67 | 68 | filename := string(b[2 : idx+2]) 69 | 70 | // Locate second NULL byte to determine mode offset 71 | offset := idx + 3 72 | idx = bytes.IndexByte(b[offset:], 0) 73 | if idx == -1 { 74 | return nil, errInvalidRequestPacket 75 | } 76 | 77 | // Mode can be any combination of uppercase and lowercase, but for our 78 | // purposes, we just want to deal with lowercase letters 79 | mode := Mode(strings.ToLower(string(b[offset : offset+idx]))) 80 | 81 | // Only accept netascii or octet modes 82 | if mode != ModeNetASCII && mode != ModeOctet { 83 | return nil, errInvalidRequestPacket 84 | } 85 | 86 | // BUG(mdlayher): implement options extension from RFC 2347. 87 | 88 | // Trailing NULL byte must be present to end packet 89 | if b[len(b)-1] != 0 { 90 | return nil, errInvalidRequestPacket 91 | } 92 | 93 | return &requestPacket{ 94 | Opcode: opcode, 95 | Filename: filename, 96 | Mode: mode, 97 | }, nil 98 | } 99 | 100 | // ackPacket represents an ACK packet, as defined in RFC 1350, Section 5. 101 | // An ACK packet is used to confirm acknowledgement of receipt of a DATA 102 | // packet. 103 | type ackPacket struct { 104 | Opcode Opcode 105 | Block uint16 106 | } 107 | 108 | // parseACKPacket attempts to parse an ackPacket from a byte slice, but may 109 | // also return an ErrorPacket as the error value, if an error occurs. 110 | func parseACKPacket(b []byte) (*ackPacket, error) { 111 | // At a minimum, ACK packet must contain a 2 byte opcode and a 2 byte 112 | // block number 113 | if len(b) < 4 { 114 | return nil, errInvalidACKPacket 115 | } 116 | 117 | opcode := Opcode(binary.BigEndian.Uint16(b[0:2])) 118 | n := binary.BigEndian.Uint16(b[2:4]) 119 | 120 | // If length is exactly 4 and opcode is correct, return an ACK packet 121 | if len(b) == 4 && opcode == opcodeACK { 122 | return &ackPacket{ 123 | Opcode: opcode, 124 | Block: n, 125 | }, nil 126 | } 127 | 128 | // Verify packet is an ERROR packet 129 | if opcode != OpcodeError { 130 | return nil, errInvalidERRORPacket 131 | } 132 | 133 | // At a minimum, ERROR packet must contain: 134 | // - 2 bytes: opcode 135 | // - 2 bytes: error code 136 | // - 1 byte : NULL 137 | if len(b) < 5 { 138 | return nil, errInvalidERRORPacket 139 | } 140 | 141 | msg := string(b[4 : len(b)-1]) 142 | 143 | // Trailing NULL byte mut be present to end packet 144 | if b[len(b)-1] != 0 { 145 | return nil, errInvalidERRORPacket 146 | } 147 | 148 | return nil, &ErrorPacket{ 149 | Opcode: opcode, 150 | ErrorCode: ErrorCode(n), 151 | ErrorMsg: msg, 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /packet_test.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | // Test_parseRequestPacket verifies that parseRequestPacket returns a correct 9 | // requestPacket or error for an input byte slice. 10 | func Test_parseRequestPacket(t *testing.T) { 11 | var tests = []struct { 12 | description string 13 | buf []byte 14 | rp *requestPacket 15 | err error 16 | }{ 17 | { 18 | description: "nil buffer, invalid request packet", 19 | err: errInvalidRequestPacket, 20 | }, 21 | { 22 | description: "length 3 buffer, invalid request packet", 23 | buf: []byte{0, 0, 0}, 24 | err: errInvalidRequestPacket, 25 | }, 26 | { 27 | description: "invalid opcode, invalid request packet", 28 | buf: []byte{0, 3, 0, 0}, 29 | err: errInvalidRequestPacket, 30 | }, 31 | { 32 | description: "opcode, filename, no trailing NULL, invalid request packet", 33 | buf: []byte{0, 1, 'a', 255}, 34 | err: errInvalidRequestPacket, 35 | }, 36 | { 37 | description: "opcode, filename, octet mode, no trailing NULL, invalid request packet", 38 | buf: []byte{0, 1, 'a', 0, 'o', 'c', 't', 'e', 't'}, 39 | err: errInvalidRequestPacket, 40 | }, 41 | { 42 | description: "opcode, filename, invalid mode, invalid request packet", 43 | buf: []byte{0, 1, 'a', 0, 'o', 'c', 't', 'e', 'x', 0}, 44 | err: errInvalidRequestPacket, 45 | }, 46 | { 47 | description: "opcode, filename, netascii mode, last byte not NULL, invalid request packet", 48 | buf: []byte{0, 1, 'a', 0, 'N', 'e', 't', 'A', 'S', 'C', 'I', 'I', 0, 255}, 49 | err: errInvalidRequestPacket, 50 | }, 51 | { 52 | description: "opcode, filename, netascii mode, OK", 53 | buf: []byte{0, 1, 'a', 0, 'N', 'e', 't', 'A', 'S', 'C', 'I', 'I', 0}, 54 | rp: &requestPacket{ 55 | Opcode: OpcodeRead, 56 | Filename: "a", 57 | Mode: ModeNetASCII, 58 | }, 59 | }, 60 | { 61 | description: "opcode, filename, octet mode, OK", 62 | buf: []byte{0, 2, 'b', 0, 'O', 'c', 'T', 'e', 'T', 0}, 63 | rp: &requestPacket{ 64 | Opcode: OpcodeWrite, 65 | Filename: "b", 66 | Mode: ModeOctet, 67 | }, 68 | }, 69 | } 70 | 71 | for i, tt := range tests { 72 | rp, err := parseRequestPacket(tt.buf) 73 | if err != nil { 74 | if want, got := tt.err, err; want != got { 75 | t.Fatalf("[%02d] test %q, unexpected error: %v != %v", 76 | i, tt.description, want, got) 77 | } 78 | 79 | continue 80 | } 81 | 82 | if want, got := tt.rp, rp; !reflect.DeepEqual(want, got) { 83 | t.Fatalf("[%02d] test %q, unexpected packet:\n- want: %v\n- got: %v", 84 | i, tt.description, want, got) 85 | } 86 | } 87 | } 88 | 89 | // Test_parseACKPacket verifies that parseACKPacket returns a correct 90 | // ackPacket or error (possibly an ErrorPacket) for an input byte slice. 91 | func Test_parseACKPacket(t *testing.T) { 92 | var tests = []struct { 93 | description string 94 | buf []byte 95 | ack *ackPacket 96 | err error 97 | }{ 98 | { 99 | description: "nil buffer, invalid ACK packet", 100 | err: errInvalidACKPacket, 101 | }, 102 | { 103 | description: "length 3 buffer, invalid ACK packet", 104 | buf: []byte{0, 0, 0}, 105 | err: errInvalidACKPacket, 106 | }, 107 | { 108 | description: "ACK packet, block 1, OK", 109 | buf: []byte{0, 4, 0, 1}, 110 | ack: &ackPacket{ 111 | Opcode: opcodeACK, 112 | Block: 1, 113 | }, 114 | }, 115 | { 116 | description: "wrong opcode, invalid ERROR packet", 117 | buf: []byte{0, 1, 0, 0}, 118 | err: errInvalidERRORPacket, 119 | }, 120 | { 121 | description: "length 4 buffer, invalid ERROR packet", 122 | buf: []byte{0, 5, 0, 0}, 123 | err: errInvalidERRORPacket, 124 | }, 125 | { 126 | description: "ERROR packet, undefined code, empty message, no trailing NULL, invalid ERROR packet", 127 | buf: []byte{0, 5, 0, 0, 255}, 128 | err: errInvalidERRORPacket, 129 | }, 130 | { 131 | description: "ERROR packet, file not found, no message, OK", 132 | buf: []byte{0, 5, 0, 1, 0}, 133 | err: &ErrorPacket{ 134 | Opcode: OpcodeError, 135 | ErrorCode: ErrorCodeFileNotFound, 136 | }, 137 | }, 138 | { 139 | description: "ERROR packet, disk full, 'abc' message, OK", 140 | buf: []byte{0, 5, 0, 3, 'a', 'b', 'c', 0}, 141 | err: &ErrorPacket{ 142 | Opcode: OpcodeError, 143 | ErrorCode: ErrorCodeDiskFull, 144 | ErrorMsg: "abc", 145 | }, 146 | }, 147 | } 148 | 149 | for i, tt := range tests { 150 | ack, err := parseACKPacket(tt.buf) 151 | if err != nil { 152 | if want, got := tt.err.Error(), err.Error(); want != got { 153 | t.Fatalf("[%02d] test %q, unexpected error: %v != %v", 154 | i, tt.description, want, got) 155 | } 156 | 157 | continue 158 | } 159 | 160 | if want, got := tt.ack, ack; !reflect.DeepEqual(want, got) { 161 | t.Fatalf("[%02d] test %q, unexpected packet:\n- want: %v\n- got: %v", 162 | i, tt.description, want, got) 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | "math" 8 | "net" 9 | "time" 10 | ) 11 | 12 | const ( 13 | // blockSize is the RFC 1350 specified DATA packet size for a 14 | // single read or write. 15 | blockSize = 512 16 | ) 17 | 18 | // response is the default ResponseWriter implementation. It performs some 19 | // internal buffering, and if needed, netascii conversions, to write DATA 20 | // packets to a client. 21 | type response struct { 22 | ResponseWriter 23 | } 24 | 25 | // newResponse creates a new response, setting up a UDP socket to perform 26 | // communication for a single client. 27 | func newResponse(serverAddr string, remoteAddr net.Addr, mode Mode) (*response, error) { 28 | host, _, err := net.SplitHostPort(serverAddr) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | // Bind to a system-assigned UDP port using the server's address 34 | conn, err := net.ListenPacket("udp", net.JoinHostPort(host, "0")) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | // Set up writer which communicates via socket and buffers input 40 | // appropriately for TFTP 41 | bsw := &bufferedSocketResponseWriter{ 42 | conn: conn, 43 | remoteAddr: remoteAddr, 44 | 45 | buf: bytes.NewBuffer(nil), 46 | 47 | rb: make([]byte, blockSize+4), 48 | wb: make([]byte, blockSize+4), 49 | } 50 | 51 | // If using netascii mode, wrap with ResponseWriter which seamlessly 52 | // converts writes to netascii 53 | var rw ResponseWriter = bsw 54 | if mode == ModeNetASCII { 55 | rw = &netASCIIResponseWriter{bsw} 56 | } 57 | 58 | return &response{ 59 | ResponseWriter: rw, 60 | }, nil 61 | } 62 | 63 | // bufferedSocketResponseWriter is a ResponseWriter which communicates with a 64 | // TFTP client over a socket, and buffers data internally. 65 | type bufferedSocketResponseWriter struct { 66 | // Connection and address used to communicate with a client 67 | conn net.PacketConn 68 | remoteAddr net.Addr 69 | 70 | // Reusable read and write buffers 71 | rb []byte 72 | wb []byte 73 | 74 | // Buffer to store blocks which are not large enough to be written 75 | buf *bytes.Buffer 76 | 77 | // Current block number 78 | block uint16 79 | } 80 | 81 | // Write implements io.Writer, and performs internal buffering of data to 82 | // communicate with a client. Write attempts to send as many available blocks 83 | // as possible when called, buffering any excess data for future writes. 84 | func (w *bufferedSocketResponseWriter) Write(p []byte) (int, error) { 85 | // Store data in buffer to be output in blocks 86 | // (never returns an error, per documentation) 87 | n, _ := w.buf.Write(p) 88 | 89 | // If buffer and input bytes cannot create an entire block, wait until 90 | // next call or flush before performing any writes 91 | if w.buf.Len() < blockSize { 92 | return n, nil 93 | } 94 | 95 | // Calculate how many blocks we can send with the data currently 96 | // in the buffer 97 | available := int(math.Floor( 98 | float64(w.buf.Len()) / float64(blockSize), 99 | )) 100 | 101 | // Flush as many available blocks as possible 102 | for i := 0; i < available; i++ { 103 | if err := w.writeOneBlock(); err != nil { 104 | return n, err 105 | } 106 | } 107 | 108 | return n, nil 109 | } 110 | 111 | // Close closes the underlying socket used to communicate with a client. 112 | func (w *bufferedSocketResponseWriter) Close() error { 113 | return w.conn.Close() 114 | } 115 | 116 | // Flush writes up to a single block of data to a client. Flush should only 117 | // be called once an io.Reader returns EOF, in order to ensure that every byte 118 | // from the Reader is flushed to the client. 119 | func (w *bufferedSocketResponseWriter) Flush() error { 120 | return w.writeOneBlock() 121 | } 122 | 123 | // writeOneBlock attempts to write a single block of data to a client, and 124 | // waits for acknowledgement or an error in reply. 125 | // 126 | // BUG(mdlayher): break this up into smaller methods 127 | func (w *bufferedSocketResponseWriter) writeOneBlock() error { 128 | // Write data header with incremented block number and send 129 | // one block to client 130 | w.block++ 131 | binary.BigEndian.PutUint16(w.wb[0:2], uint16(opcodeDATA)) 132 | binary.BigEndian.PutUint16(w.wb[2:4], w.block) 133 | 134 | // Copy up to blockSize bytes into write buffer for a single write 135 | // transaction 136 | cn := copy(w.wb[4:], w.buf.Next(blockSize)) 137 | 138 | for { 139 | // Set timeouts for a reasonable amount of time before retrying 140 | if err := w.conn.SetDeadline(time.Now().Add(2 * time.Second)); err != nil { 141 | return err 142 | } 143 | 144 | // Write block to client using its connection, ensure that the 145 | // correct number of bytes were written 146 | wn, err := w.conn.WriteTo(w.wb[:cn+4], w.remoteAddr) 147 | if err != nil { 148 | // Allow retries on timeout 149 | oerr, ok := err.(*net.OpError) 150 | if !ok { 151 | return err 152 | } 153 | 154 | if oerr.Timeout() { 155 | continue 156 | } 157 | 158 | return err 159 | } 160 | if wn != cn+4 { 161 | return io.ErrShortWrite 162 | } 163 | 164 | // Wait for ACK or ERROR response from client 165 | var readN int 166 | for { 167 | rn, addr, err := w.conn.ReadFrom(w.rb) 168 | if err != nil { 169 | // Allow retries on timeout 170 | oerr, ok := err.(*net.OpError) 171 | if !ok { 172 | return err 173 | } 174 | 175 | if oerr.Timeout() { 176 | continue 177 | } 178 | 179 | return err 180 | } 181 | 182 | // BUG(mdlayher): send errors for wrong TID if an unknown 183 | // client starts communicating on this port 184 | _ = addr 185 | 186 | readN = rn 187 | break 188 | } 189 | 190 | // Parse ACK or ERROR packet 191 | ack, err := parseACKPacket(w.rb[:readN]) 192 | if err != nil { 193 | return err 194 | } 195 | 196 | // If client reports the previous block as acknowledged again, we 197 | // must repeat the process 198 | if ack.Block == w.block-1 { 199 | continue 200 | } 201 | 202 | return nil 203 | } 204 | } 205 | --------------------------------------------------------------------------------