├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── main.go └── vendor ├── github.com └── pin │ └── tftp │ ├── CONTRIBUTORS │ ├── LICENSE │ ├── README.md │ ├── backoff.go │ ├── client.go │ ├── netascii │ └── netascii.go │ ├── packet.go │ ├── receiver.go │ ├── sender.go │ └── server.go └── vendor.json /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | 4 | [._]*.s[a-w][a-z] 5 | [._]s[a-w][a-z] 6 | 7 | _obj 8 | _test 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 1.9 3 | script: make 4 | deploy: 5 | provider: releases 6 | api_key: 7 | secure: GZylHhs8BlOgM+cOW4rggi5R+sbiIQb8TyESV1DaEzXtpRQr9M5MBZmEs6WwxYQcOfyITIa2X7edmVJWr+r0Y2M/XVeaWUAOjH8OKtiPHbVUZQrW/unNusz53xTk/arZFcuYXQW3SznktGq3tzPATDgpLu+UhRZSEL/W3R4G8cFBdws9MiTEqJoAvFRLAiGa3UWlIoIOrYgZyRcryV+LNPsFdATTR+RMAvAOQVhVjWeAazALXMhzdOMc+Ehs0CZWoj4IdS+85UoFM9TNRBP9SdLvPMIFY9FdbldlEPbgeYf4sQjgpl3FfLteZKluKsVMzGeIp1p9k474q4Rx8HZ2x1BR5W26dQO3VXnsfKUiTbYgj5SGZZriKTWrrVZlDNXFOu19JJ96QacSBYhDkLXoAQBpxvo1WuAH0XQqNnrUZTYVzI1Y+eSECDAxriQeE6XLzU/7lcJZVjVgyhtN/YeAy/C2zKEAd+6cmXAOgbFaSEwJHWD1AiVpqPShuu4TsCbkjHfM21mXkuurjv+uUqOoknJimrP5EfwIFNBjQzHqOfmy1R9i0NJ6ThQECzYlTz4jjMpFK0w1e8bxXe2wsnfLUo6oar/5OS3+A0FrqCNoIRtbRUrQZCz9S98iXmA4npIpJ2W5k5Yphc0h1U56qYQ6UZ01Zr/DitZxIG6eejf1PYY= 8 | file: dist/tftp-http-proxy 9 | skip_cleanup: true 10 | on: 11 | tags: true 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alex Hornung 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROG=tftp-http-proxy 2 | PACKAGE=bwalex/$(PROG) 3 | SOURCEDIR=. 4 | 5 | GO?=go 6 | GOPATH = $(CURDIR)/.gopath 7 | BASE = $(GOPATH)/src/$(PACKAGE) 8 | 9 | SOURCES := $(shell find $(SOURCEDIR) -name '*.go') 10 | 11 | dist/$(PROG): $(SOURCES) | $(BASE) 12 | cd $(BASE) && GOPATH=$(GOPATH) $(GO) build -o $@ 13 | 14 | $(BASE): 15 | @mkdir -p $(dir $@) 16 | @ln -sf $(CURDIR) $@ 17 | 18 | .PHONY: clean 19 | clean: 20 | rm -f $(PROG) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tftp-http-proxy 2 | 3 | [![Build Status](https://api.travis-ci.org/bwalex/tftp-http-proxy.svg?branch=master)](https://travis-ci.org/bwalex/tftp-http-proxy) 4 | 5 | tftp-http-proxy is a simple TFTP server that proxies all read requests to a backing HTTP server, and serves the response. 6 | 7 | ## Get it 8 | 9 | Release binaries for linux amd64 platforms are built by default and can be downloaded from the [Releases page](https://github.com/bwalex/tftp-http-proxy/releases). 10 | 11 | For other platforms or to build from source, clone the repository and just run `make`. 12 | 13 | ## Usage 14 | 15 | Usage of dist/tftp-http-proxy: 16 | -http-base-url string 17 | HTTP base URL (default "http://127.0.0.1/tftp") 18 | -tftp-timeout duration 19 | TFTP timeout (default 5s) 20 | 21 | ## Details 22 | 23 | When tftp-http-proxy is started, it will listen on (UDP) port 69 as a normal TFTP server. Whenever a new TFTP read request is received, the request will be forwarded as an HTTP request to the configured HTTP URL (`-http-base-url` flag). The HTTP request will have some additional HTTP headers containing information about the TFTP request: 24 | 25 | - `X-TFTP-IP`: The IP of the requesting TFTP client 26 | - `X-TFTP-Port`: The port used by the requesting TFTP client 27 | - `X-TFTP-File`: The filename requested by the TFTP client 28 | 29 | If the HTTP request returns a status 200 response, the contents of the response will be sent as the file contents for the TFTP read request. The HTTP response should contain an accurate ContentLength header, as it will be used to set the TFTP TSize option on the read response. 30 | 31 | If the HTTP response status is not 200, an error response will be sent to the TFTP client instead. 32 | 33 | ## License 34 | 35 | tftp-http-proxy is released under the [MIT License](http://www.opensource.org/licenses/MIT). 36 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | "time" 12 | "github.com/pin/tftp" 13 | ) 14 | 15 | const httpBaseUrlDefault = "http://127.0.0.1/tftp" 16 | const tftpTimeoutDefault = 5 * time.Second 17 | const tftpBindAddrDefault = ":69" 18 | const appendPathDefault = false 19 | 20 | var globalState = struct { 21 | httpBaseUrl string 22 | httpClient *http.Client 23 | appendPath bool 24 | }{ 25 | httpBaseUrl: httpBaseUrlDefault, 26 | httpClient: nil, 27 | appendPath: appendPathDefault, 28 | } 29 | 30 | func urlJoin(base string, other string) (string, error) { 31 | if !strings.HasSuffix(base, "/") { 32 | base = base + "/" 33 | } 34 | 35 | b, err := url.Parse(base) 36 | if err != nil { 37 | return "", err 38 | } 39 | 40 | o, err := url.Parse(strings.TrimPrefix(other, "/")) 41 | if err != nil { 42 | return "", err 43 | } 44 | 45 | u := b.ResolveReference(o) 46 | return u.String(), nil 47 | } 48 | 49 | func tftpReadHandler(filename string, rf io.ReaderFrom) error { 50 | raddr := rf.(tftp.OutgoingTransfer).RemoteAddr() // net.UDPAddr 51 | 52 | log.Printf("INFO: New TFTP request (%s) from %s", filename, raddr.IP.String()) 53 | 54 | uri := globalState.httpBaseUrl 55 | if globalState.appendPath { 56 | var err error 57 | uri, err = urlJoin(uri, filename) 58 | if err != nil { 59 | log.Printf("ERR: error building URL: %v", err) 60 | return err 61 | } 62 | } 63 | 64 | req, err := http.NewRequest("GET", uri, nil) 65 | if err != nil { 66 | log.Printf("ERR: http request setup failed: %v", err) 67 | return err 68 | } 69 | req.Header.Add("X-TFTP-IP", raddr.IP.String()) 70 | req.Header.Add("X-TFTP-Port", fmt.Sprintf("%d", raddr.Port)) 71 | req.Header.Add("X-TFTP-File", filename) 72 | 73 | resp, err := globalState.httpClient.Do(req) 74 | if err != nil { 75 | log.Printf("ERR: http request failed: %v", err) 76 | return err 77 | } 78 | defer resp.Body.Close() 79 | 80 | if resp.StatusCode == http.StatusNotFound { 81 | log.Printf("INFO: http FileNotFound response: %s", resp.Status) 82 | return fmt.Errorf("File not found") 83 | } else if resp.StatusCode != http.StatusOK { 84 | log.Printf("ERR: http request returned status %s", resp.Status) 85 | return fmt.Errorf("HTTP request error: %s", resp.Status) 86 | } 87 | 88 | // Use ContentLength, if provided, to set TSize option 89 | if resp.ContentLength >= 0 { 90 | rf.(tftp.OutgoingTransfer).SetSize(resp.ContentLength) 91 | } 92 | 93 | _, err = rf.ReadFrom(resp.Body) 94 | if err != nil { 95 | log.Printf("ERR: ReadFrom failed: %v", err) 96 | return err 97 | } 98 | 99 | return nil 100 | } 101 | 102 | func main() { 103 | httpBaseUrlPtr := flag.String("http-base-url", httpBaseUrlDefault, "HTTP base URL") 104 | appendPathPtr := flag.Bool("http-append-path", appendPathDefault, "append TFTP filename to URL") 105 | tftpTimeoutPtr := flag.Duration("tftp-timeout", tftpTimeoutDefault, "TFTP timeout") 106 | bindAddrPtr := flag.String("tftp-bind-address", tftpBindAddrDefault, "TFTP addr to bind to") 107 | 108 | flag.Parse() 109 | 110 | globalState.httpBaseUrl = *httpBaseUrlPtr 111 | globalState.httpClient = &http.Client{} 112 | globalState.appendPath = *appendPathPtr 113 | 114 | s := tftp.NewServer(tftpReadHandler, nil) 115 | s.SetTimeout(*tftpTimeoutPtr) 116 | log.Printf("Listening TFTP requests on: %s", *bindAddrPtr) 117 | err := s.ListenAndServe(*bindAddrPtr) 118 | if err != nil { 119 | log.Panicf("FATAL: tftp server: %v\n", err) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /vendor/github.com/pin/tftp/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Dmitri Popov 2 | Mojo Talantikite 3 | Giovanni Bajo 4 | Andrew Danforth 5 | -------------------------------------------------------------------------------- /vendor/github.com/pin/tftp/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Dmitri Popov 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/pin/tftp/README.md: -------------------------------------------------------------------------------- 1 | TFTP server and client library for Golang 2 | ========================================= 3 | 4 | [![GoDoc](https://godoc.org/github.com/pin/tftp?status.svg)](https://godoc.org/github.com/pin/tftp) 5 | [![Build Status](https://travis-ci.org/pin/tftp.svg?branch=master)](https://travis-ci.org/pin/tftp) 6 | 7 | Implements: 8 | * [RFC 1350](https://tools.ietf.org/html/rfc1350) - The TFTP Protocol (Revision 2) 9 | * [RFC 2347](https://tools.ietf.org/html/rfc2347) - TFTP Option Extension 10 | * [RFC 2348](https://tools.ietf.org/html/rfc2348) - TFTP Blocksize Option 11 | 12 | Partially implements (tsize server side only): 13 | * [RFC 2349](https://tools.ietf.org/html/rfc2349) - TFTP Timeout Interval and Transfer Size Options 14 | 15 | Set of features is sufficient for PXE boot support. 16 | 17 | ``` go 18 | import "github.com/pin/tftp" 19 | ``` 20 | 21 | The package is cohesive to Golang `io`. Particularly it implements 22 | `io.ReaderFrom` and `io.WriterTo` interfaces. That allows efficient data 23 | transmission without unnecessary memory copying and allocations. 24 | 25 | 26 | TFTP Server 27 | ----------- 28 | 29 | ```go 30 | 31 | // readHandler is called when client starts file download from server 32 | func readHandler(filename string, rf io.ReaderFrom) error { 33 | file, err := os.Open(filename) 34 | if err != nil { 35 | fmt.Fprintf(os.Stderr, "%v\n", err) 36 | return err 37 | } 38 | n, err := rf.ReadFrom(file) 39 | if err != nil { 40 | fmt.Fprintf(os.Stderr, "%v\n", err) 41 | return err 42 | } 43 | fmt.Printf("%d bytes sent\n", n) 44 | return nil 45 | } 46 | 47 | // writeHandler is called when client starts file upload to server 48 | func writeHandler(filename string, wt io.WriterTo) error { 49 | file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) 50 | if err != nil { 51 | fmt.Fprintf(os.Stderr, "%v\n", err) 52 | return err 53 | } 54 | n, err := wt.WriteTo(file) 55 | if err != nil { 56 | fmt.Fprintf(os.Stderr, "%v\n", err) 57 | return err 58 | } 59 | fmt.Printf("%d bytes received\n", n) 60 | return nil 61 | } 62 | 63 | func main() { 64 | // use nil in place of handler to disable read or write operations 65 | s := tftp.NewServer(readHandler, writeHandler) 66 | s.SetTimeout(5 * time.Second) // optional 67 | err := s.ListenAndServe(":69") // blocks until s.Shutdown() is called 68 | if err != nil { 69 | fmt.Fprintf(os.Stdout, "server: %v\n", err) 70 | os.Exit(1) 71 | } 72 | } 73 | ``` 74 | 75 | TFTP Client 76 | ----------- 77 | Upload file to server: 78 | 79 | ```go 80 | c, err := tftp.NewClient("172.16.4.21:69") 81 | file, err := os.Open(path) 82 | c.SetTimeout(5 * time.Second) // optional 83 | rf, err := c.Send("foobar.txt", "octet") 84 | n, err := rf.ReadFrom(file) 85 | fmt.Printf("%d bytes sent\n", n) 86 | ``` 87 | 88 | Download file from server: 89 | 90 | ```go 91 | c, err := tftp.NewClient("172.16.4.21:69") 92 | wt, err := c.Receive("foobar.txt", "octet") 93 | file, err := os.Create(path) 94 | // Optionally obtain transfer size before actual data. 95 | if n, ok := wt.(IncomingTransfer).Size(); ok { 96 | fmt.Printf("Transfer size: %d\n", n) 97 | } 98 | n, err := wt.WriteTo(file) 99 | fmt.Printf("%d bytes received\n", n) 100 | ``` 101 | 102 | Note: please handle errors better :) 103 | 104 | TSize option 105 | ------------ 106 | 107 | PXE boot ROM often expects tsize option support from a server: client 108 | (e.g. computer that boots over the network) wants to know size of a 109 | download before the actual data comes. Server has to obtain stream 110 | size and send it to a client. 111 | 112 | Often it will happen automatically because TFTP library tries to check 113 | if `io.Reader` provided to `ReadFrom` method also satisfies 114 | `io.Seeker` interface (`os.File` for instance) and uses `Seek` to 115 | determine file size. 116 | 117 | In case `io.Reader` you provide to `ReadFrom` in read handler does not 118 | satisfy `io.Seeker` interface or you do not want TFTP library to call 119 | `Seek` on your reader but still want to respond with tsize option 120 | during outgoing request you can use an `OutgoingTransfer` interface: 121 | 122 | ```go 123 | 124 | func readHandler(filename string, rf io.ReaderFrom) error { 125 | ... 126 | // Set transfer size before calling ReadFrom. 127 | rf.(tftp.OutgoingTransfer).SetSize(myFileSize) 128 | ... 129 | // ReadFrom ... 130 | 131 | ``` 132 | 133 | Similarly, it is possible to obtain size of a file that is about to be 134 | received using `IncomingTransfer` interface (see `Size` method). 135 | 136 | Remote Address 137 | -------------- 138 | 139 | The `OutgoingTransfer` and `IncomingTransfer` interfaces also provide the 140 | `RemoteAddr` method which returns the peer IP address and port as a 141 | `net.UDPAddr`. This can be used for detailed logging in a server handler. 142 | 143 | ```go 144 | 145 | func readHandler(filename string, rf io.ReaderFrom) error { 146 | ... 147 | raddr := rf.(tftp.OutgoingTransfer).RemoteAddr() 148 | log.Println("RRQ from", raddr.String()) 149 | ... 150 | // ReadFrom ... 151 | ``` 152 | 153 | Backoff 154 | ------- 155 | 156 | The default backoff before retransmitting an unacknowledged packet is a 157 | random duration between 0 and 1 second. This behavior can be overridden 158 | in clients and servers by providing a custom backoff calculation function. 159 | 160 | ```go 161 | s := tftp.NewServer(readHandler, writeHandler) 162 | s.SetBackoff(func (attempts int) time.Duration { 163 | return time.Duration(attempts) * time.Second 164 | }) 165 | ``` 166 | 167 | or, for no backoff 168 | 169 | ```go 170 | s.SetBackoff(func (int) time.Duration { return 0 }) 171 | ``` 172 | -------------------------------------------------------------------------------- /vendor/github.com/pin/tftp/backoff.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | const ( 9 | defaultTimeout = 5 * time.Second 10 | defaultRetries = 5 11 | ) 12 | 13 | type backoffFunc func(int) time.Duration 14 | 15 | type backoff struct { 16 | attempt int 17 | handler backoffFunc 18 | } 19 | 20 | func (b *backoff) reset() { 21 | b.attempt = 0 22 | } 23 | 24 | func (b *backoff) count() int { 25 | return b.attempt 26 | } 27 | 28 | func (b *backoff) backoff() { 29 | if b.handler == nil { 30 | time.Sleep(time.Duration(rand.Int63n(int64(time.Second)))) 31 | } else { 32 | time.Sleep(b.handler(b.attempt)) 33 | } 34 | b.attempt++ 35 | } 36 | -------------------------------------------------------------------------------- /vendor/github.com/pin/tftp/client.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | // NewClient creates TFTP client for server on address provided. 12 | func NewClient(addr string) (*Client, error) { 13 | a, err := net.ResolveUDPAddr("udp", addr) 14 | if err != nil { 15 | return nil, fmt.Errorf("resolving address %s: %v", addr, err) 16 | } 17 | return &Client{ 18 | addr: a, 19 | timeout: defaultTimeout, 20 | retries: defaultRetries, 21 | }, nil 22 | } 23 | 24 | // SetTimeout sets maximum time client waits for single network round-trip to succeed. 25 | // Default is 5 seconds. 26 | func (c *Client) SetTimeout(t time.Duration) { 27 | if t <= 0 { 28 | c.timeout = defaultTimeout 29 | } 30 | c.timeout = t 31 | } 32 | 33 | // SetRetries sets maximum number of attempts client made to transmit a packet. 34 | // Default is 5 attempts. 35 | func (c *Client) SetRetries(count int) { 36 | if count < 1 { 37 | c.retries = defaultRetries 38 | } 39 | c.retries = count 40 | } 41 | 42 | // SetBackoff sets a user provided function that is called to provide a 43 | // backoff duration prior to retransmitting an unacknowledged packet. 44 | func (c *Client) SetBackoff(h backoffFunc) { 45 | c.backoff = h 46 | } 47 | 48 | type Client struct { 49 | addr *net.UDPAddr 50 | timeout time.Duration 51 | retries int 52 | backoff backoffFunc 53 | blksize int 54 | tsize bool 55 | } 56 | 57 | // Send starts outgoing file transmission. It returns io.ReaderFrom or error. 58 | func (c Client) Send(filename string, mode string) (io.ReaderFrom, error) { 59 | conn, err := net.ListenUDP("udp", &net.UDPAddr{}) 60 | if err != nil { 61 | return nil, err 62 | } 63 | s := &sender{ 64 | send: make([]byte, datagramLength), 65 | receive: make([]byte, datagramLength), 66 | conn: conn, 67 | retry: &backoff{handler: c.backoff}, 68 | timeout: c.timeout, 69 | retries: c.retries, 70 | addr: c.addr, 71 | mode: mode, 72 | } 73 | if c.blksize != 0 { 74 | s.opts = make(options) 75 | s.opts["blksize"] = strconv.Itoa(c.blksize) 76 | } 77 | n := packRQ(s.send, opWRQ, filename, mode, s.opts) 78 | addr, err := s.sendWithRetry(n) 79 | if err != nil { 80 | return nil, err 81 | } 82 | s.addr = addr 83 | s.opts = nil 84 | return s, nil 85 | } 86 | 87 | // Receive starts incoming file transmission. It returns io.WriterTo or error. 88 | func (c Client) Receive(filename string, mode string) (io.WriterTo, error) { 89 | conn, err := net.ListenUDP("udp", &net.UDPAddr{}) 90 | if err != nil { 91 | return nil, err 92 | } 93 | if c.timeout == 0 { 94 | c.timeout = defaultTimeout 95 | } 96 | r := &receiver{ 97 | send: make([]byte, datagramLength), 98 | receive: make([]byte, datagramLength), 99 | conn: conn, 100 | retry: &backoff{handler: c.backoff}, 101 | timeout: c.timeout, 102 | retries: c.retries, 103 | addr: c.addr, 104 | autoTerm: true, 105 | block: 1, 106 | mode: mode, 107 | } 108 | if c.blksize != 0 || c.tsize { 109 | r.opts = make(options) 110 | } 111 | if c.blksize != 0 { 112 | r.opts["blksize"] = strconv.Itoa(c.blksize) 113 | } 114 | if c.tsize { 115 | r.opts["tsize"] = "0" 116 | } 117 | n := packRQ(r.send, opRRQ, filename, mode, r.opts) 118 | l, addr, err := r.receiveWithRetry(n) 119 | if err != nil { 120 | return nil, err 121 | } 122 | r.l = l 123 | r.addr = addr 124 | return r, nil 125 | } 126 | -------------------------------------------------------------------------------- /vendor/github.com/pin/tftp/netascii/netascii.go: -------------------------------------------------------------------------------- 1 | package netascii 2 | 3 | // TODO: make it work not only on linux 4 | 5 | import "io" 6 | 7 | const ( 8 | CR = '\x0d' 9 | LF = '\x0a' 10 | NUL = '\x00' 11 | ) 12 | 13 | func ToReader(r io.Reader) io.Reader { 14 | return &toReader{ 15 | r: r, 16 | buf: make([]byte, 256), 17 | } 18 | } 19 | 20 | type toReader struct { 21 | r io.Reader 22 | buf []byte 23 | n int 24 | i int 25 | err error 26 | lf bool 27 | nul bool 28 | } 29 | 30 | func (r *toReader) Read(p []byte) (int, error) { 31 | var n int 32 | for n < len(p) { 33 | if r.lf { 34 | p[n] = LF 35 | n++ 36 | r.lf = false 37 | continue 38 | } 39 | if r.nul { 40 | p[n] = NUL 41 | n++ 42 | r.nul = false 43 | continue 44 | } 45 | if r.i < r.n { 46 | if r.buf[r.i] == LF { 47 | p[n] = CR 48 | r.lf = true 49 | } else if r.buf[r.i] == CR { 50 | p[n] = CR 51 | r.nul = true 52 | 53 | } else { 54 | p[n] = r.buf[r.i] 55 | } 56 | r.i++ 57 | n++ 58 | continue 59 | } 60 | if r.err == nil { 61 | r.n, r.err = r.r.Read(r.buf) 62 | r.i = 0 63 | } else { 64 | return n, r.err 65 | } 66 | } 67 | return n, r.err 68 | } 69 | 70 | type fromWriter struct { 71 | w io.Writer 72 | buf []byte 73 | i int 74 | cr bool 75 | } 76 | 77 | func FromWriter(w io.Writer) io.Writer { 78 | return &fromWriter{ 79 | w: w, 80 | buf: make([]byte, 256), 81 | } 82 | } 83 | 84 | func (w *fromWriter) Write(p []byte) (n int, err error) { 85 | for n < len(p) { 86 | if w.cr { 87 | if p[n] == LF { 88 | w.buf[w.i] = LF 89 | } 90 | if p[n] == NUL { 91 | w.buf[w.i] = CR 92 | } 93 | w.cr = false 94 | w.i++ 95 | } else if p[n] == CR { 96 | w.cr = true 97 | } else { 98 | w.buf[w.i] = p[n] 99 | w.i++ 100 | } 101 | n++ 102 | if w.i == len(w.buf) || n == len(p) { 103 | _, err = w.w.Write(w.buf[:w.i]) 104 | w.i = 0 105 | } 106 | } 107 | return n, err 108 | } 109 | -------------------------------------------------------------------------------- /vendor/github.com/pin/tftp/packet.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | const ( 10 | opRRQ = uint16(1) // Read request (RRQ) 11 | opWRQ = uint16(2) // Write request (WRQ) 12 | opDATA = uint16(3) // Data 13 | opACK = uint16(4) // Acknowledgement 14 | opERROR = uint16(5) // Error 15 | opOACK = uint16(6) // Options Acknowledgment 16 | ) 17 | 18 | const ( 19 | blockLength = 512 20 | datagramLength = 516 21 | ) 22 | 23 | type options map[string]string 24 | 25 | // RRQ/WRQ packet 26 | // 27 | // 2 bytes string 1 byte string 1 byte 28 | // -------------------------------------------------- 29 | // | Opcode | Filename | 0 | Mode | 0 | 30 | // -------------------------------------------------- 31 | type pRRQ []byte 32 | type pWRQ []byte 33 | 34 | // packRQ returns length of the packet in b 35 | func packRQ(p []byte, op uint16, filename, mode string, opts options) int { 36 | binary.BigEndian.PutUint16(p, op) 37 | n := 2 38 | n += copy(p[2:len(p)-10], filename) 39 | p[n] = 0 40 | n++ 41 | n += copy(p[n:], mode) 42 | p[n] = 0 43 | n++ 44 | for name, value := range opts { 45 | n += copy(p[n:], name) 46 | p[n] = 0 47 | n++ 48 | n += copy(p[n:], value) 49 | p[n] = 0 50 | n++ 51 | } 52 | return n 53 | } 54 | 55 | func unpackRQ(p []byte) (filename, mode string, opts options, err error) { 56 | bs := bytes.Split(p[2:], []byte{0}) 57 | if len(bs) < 2 { 58 | return "", "", nil, fmt.Errorf("missing filename or mode") 59 | } 60 | filename = string(bs[0]) 61 | mode = string(bs[1]) 62 | if len(bs) < 4 { 63 | return filename, mode, nil, nil 64 | } 65 | opts = make(options) 66 | for i := 2; i+1 < len(bs); i += 2 { 67 | opts[string(bs[i])] = string(bs[i+1]) 68 | } 69 | return filename, mode, opts, nil 70 | } 71 | 72 | // OACK packet 73 | // 74 | // +----------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ 75 | // | Opcode | opt1 | 0 | value1 | 0 | optN | 0 | valueN | 0 | 76 | // +----------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ 77 | type pOACK []byte 78 | 79 | func packOACK(p []byte, opts options) int { 80 | binary.BigEndian.PutUint16(p, opOACK) 81 | n := 2 82 | for name, value := range opts { 83 | n += copy(p[n:], name) 84 | p[n] = 0 85 | n++ 86 | n += copy(p[n:], value) 87 | p[n] = 0 88 | n++ 89 | } 90 | return n 91 | } 92 | 93 | func unpackOACK(p []byte) (opts options, err error) { 94 | bs := bytes.Split(p[2:], []byte{0}) 95 | opts = make(options) 96 | for i := 0; i+1 < len(bs); i += 2 { 97 | opts[string(bs[i])] = string(bs[i+1]) 98 | } 99 | return opts, nil 100 | } 101 | 102 | // ERROR packet 103 | // 104 | // 2 bytes 2 bytes string 1 byte 105 | // ------------------------------------------ 106 | // | Opcode | ErrorCode | ErrMsg | 0 | 107 | // ------------------------------------------ 108 | type pERROR []byte 109 | 110 | func packERROR(p []byte, code uint16, message string) int { 111 | binary.BigEndian.PutUint16(p, opERROR) 112 | binary.BigEndian.PutUint16(p[2:], code) 113 | n := copy(p[4:len(p)-2], message) 114 | p[4+n] = 0 115 | return n + 5 116 | } 117 | 118 | func (p pERROR) code() uint16 { 119 | return binary.BigEndian.Uint16(p[2:]) 120 | } 121 | 122 | func (p pERROR) message() string { 123 | return string(p[4:]) 124 | } 125 | 126 | // DATA packet 127 | // 128 | // 2 bytes 2 bytes n bytes 129 | // ---------------------------------- 130 | // | Opcode | Block # | Data | 131 | // ---------------------------------- 132 | type pDATA []byte 133 | 134 | func (p pDATA) block() uint16 { 135 | return binary.BigEndian.Uint16(p[2:]) 136 | } 137 | 138 | // ACK packet 139 | // 140 | // 2 bytes 2 bytes 141 | // ----------------------- 142 | // | Opcode | Block # | 143 | // ----------------------- 144 | type pACK []byte 145 | 146 | func (p pACK) block() uint16 { 147 | return binary.BigEndian.Uint16(p[2:]) 148 | } 149 | 150 | func parsePacket(p []byte) (interface{}, error) { 151 | l := len(p) 152 | if l < 2 { 153 | return nil, fmt.Errorf("short packet") 154 | } 155 | opcode := binary.BigEndian.Uint16(p) 156 | switch opcode { 157 | case opRRQ: 158 | if l < 4 { 159 | return nil, fmt.Errorf("short RRQ packet: %d", l) 160 | } 161 | return pRRQ(p), nil 162 | case opWRQ: 163 | if l < 4 { 164 | return nil, fmt.Errorf("short WRQ packet: %d", l) 165 | } 166 | return pWRQ(p), nil 167 | case opDATA: 168 | if l < 4 { 169 | return nil, fmt.Errorf("short DATA packet: %d", l) 170 | } 171 | return pDATA(p), nil 172 | case opACK: 173 | if l < 4 { 174 | return nil, fmt.Errorf("short ACK packet: %d", l) 175 | } 176 | return pACK(p), nil 177 | case opERROR: 178 | if l < 5 { 179 | return nil, fmt.Errorf("short ERROR packet: %d", l) 180 | } 181 | return pERROR(p), nil 182 | case opOACK: 183 | if l < 6 { 184 | return nil, fmt.Errorf("short OACK packet: %d", l) 185 | } 186 | return pOACK(p), nil 187 | default: 188 | return nil, fmt.Errorf("unknown opcode: %d", opcode) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /vendor/github.com/pin/tftp/receiver.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "net" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/pin/tftp/netascii" 12 | ) 13 | 14 | // IncomingTransfer provides methods that expose information associated with 15 | // an incoming transfer. 16 | type IncomingTransfer interface { 17 | // Size returns the size of an incoming file if the request included the 18 | // tsize option (see RFC2349). To differentiate a zero-sized file transfer 19 | // from a request without tsize use the second boolean "ok" return value. 20 | Size() (n int64, ok bool) 21 | 22 | // RemoteAddr returns the remote peer's IP address and port. 23 | RemoteAddr() net.UDPAddr 24 | } 25 | 26 | func (r *receiver) RemoteAddr() net.UDPAddr { return *r.addr } 27 | 28 | func (r *receiver) Size() (n int64, ok bool) { 29 | if r.opts != nil { 30 | if s, ok := r.opts["tsize"]; ok { 31 | n, err := strconv.ParseInt(s, 10, 64) 32 | if err != nil { 33 | return 0, false 34 | } 35 | return n, true 36 | } 37 | } 38 | return 0, false 39 | } 40 | 41 | type receiver struct { 42 | send []byte 43 | receive []byte 44 | addr *net.UDPAddr 45 | tid int 46 | conn *net.UDPConn 47 | block uint16 48 | retry *backoff 49 | timeout time.Duration 50 | retries int 51 | l int 52 | autoTerm bool 53 | dally bool 54 | mode string 55 | opts options 56 | } 57 | 58 | func (r *receiver) WriteTo(w io.Writer) (n int64, err error) { 59 | if r.mode == "netascii" { 60 | w = netascii.FromWriter(w) 61 | } 62 | if r.opts != nil { 63 | err := r.sendOptions() 64 | if err != nil { 65 | r.abort(err) 66 | return 0, err 67 | } 68 | } 69 | binary.BigEndian.PutUint16(r.send[0:2], opACK) 70 | for { 71 | if r.l > 0 { 72 | l, err := w.Write(r.receive[4:r.l]) 73 | n += int64(l) 74 | if err != nil { 75 | r.abort(err) 76 | return n, err 77 | } 78 | if r.l < len(r.receive) { 79 | if r.autoTerm { 80 | r.terminate() 81 | r.conn.Close() 82 | } 83 | return n, nil 84 | } 85 | } 86 | binary.BigEndian.PutUint16(r.send[2:4], r.block) 87 | r.block++ // send ACK for current block and expect next one 88 | ll, _, err := r.receiveWithRetry(4) 89 | if err != nil { 90 | r.abort(err) 91 | return n, err 92 | } 93 | r.l = ll 94 | } 95 | } 96 | 97 | func (r *receiver) sendOptions() error { 98 | for name, value := range r.opts { 99 | if name == "blksize" { 100 | err := r.setBlockSize(value) 101 | if err != nil { 102 | delete(r.opts, name) 103 | continue 104 | } 105 | } else { 106 | delete(r.opts, name) 107 | } 108 | } 109 | if len(r.opts) > 0 { 110 | m := packOACK(r.send, r.opts) 111 | r.block = 1 // expect data block number 1 112 | ll, _, err := r.receiveWithRetry(m) 113 | if err != nil { 114 | r.abort(err) 115 | return err 116 | } 117 | r.l = ll 118 | } 119 | return nil 120 | } 121 | 122 | func (r *receiver) setBlockSize(blksize string) error { 123 | n, err := strconv.Atoi(blksize) 124 | if err != nil { 125 | return err 126 | } 127 | if n < 512 { 128 | return fmt.Errorf("blkzise too small: %d", n) 129 | } 130 | if n > 65464 { 131 | return fmt.Errorf("blksize too large: %d", n) 132 | } 133 | r.receive = make([]byte, n+4) 134 | return nil 135 | } 136 | 137 | func (r *receiver) receiveWithRetry(l int) (int, *net.UDPAddr, error) { 138 | r.retry.reset() 139 | for { 140 | n, addr, err := r.receiveDatagram(l) 141 | if _, ok := err.(net.Error); ok && r.retry.count() < r.retries { 142 | r.retry.backoff() 143 | continue 144 | } 145 | return n, addr, err 146 | } 147 | } 148 | 149 | func (r *receiver) receiveDatagram(l int) (int, *net.UDPAddr, error) { 150 | err := r.conn.SetReadDeadline(time.Now().Add(r.timeout)) 151 | if err != nil { 152 | return 0, nil, err 153 | } 154 | _, err = r.conn.WriteToUDP(r.send[:l], r.addr) 155 | if err != nil { 156 | return 0, nil, err 157 | } 158 | for { 159 | c, addr, err := r.conn.ReadFromUDP(r.receive) 160 | if err != nil { 161 | return 0, nil, err 162 | } 163 | if !addr.IP.Equal(r.addr.IP) || (r.tid != 0 && addr.Port != r.tid) { 164 | continue 165 | } 166 | p, err := parsePacket(r.receive[:c]) 167 | if err != nil { 168 | return 0, addr, err 169 | } 170 | r.tid = addr.Port 171 | switch p := p.(type) { 172 | case pDATA: 173 | if p.block() == r.block { 174 | return c, addr, nil 175 | } 176 | case pOACK: 177 | opts, err := unpackOACK(p) 178 | if r.block != 1 { 179 | continue 180 | } 181 | if err != nil { 182 | r.abort(err) 183 | return 0, addr, err 184 | } 185 | for name, value := range opts { 186 | if name == "blksize" { 187 | err := r.setBlockSize(value) 188 | if err != nil { 189 | continue 190 | } 191 | } 192 | } 193 | r.block = 0 // ACK with block number 0 194 | r.opts = opts 195 | return 0, addr, nil 196 | case pERROR: 197 | return 0, addr, fmt.Errorf("code: %d, message: %s", 198 | p.code(), p.message()) 199 | } 200 | } 201 | } 202 | 203 | func (r *receiver) terminate() error { 204 | binary.BigEndian.PutUint16(r.send[2:4], r.block) 205 | if r.dally { 206 | for i := 0; i < 3; i++ { 207 | _, _, err := r.receiveDatagram(4) 208 | if err != nil { 209 | return nil 210 | } 211 | } 212 | return fmt.Errorf("dallying termination failed") 213 | } else { 214 | _, err := r.conn.WriteToUDP(r.send[:4], r.addr) 215 | if err != nil { 216 | return err 217 | } 218 | } 219 | return nil 220 | } 221 | 222 | func (r *receiver) abort(err error) error { 223 | if r.conn == nil { 224 | return nil 225 | } 226 | n := packERROR(r.send, 1, err.Error()) 227 | _, err = r.conn.WriteToUDP(r.send[:n], r.addr) 228 | if err != nil { 229 | return err 230 | } 231 | r.conn.Close() 232 | r.conn = nil 233 | return nil 234 | } 235 | -------------------------------------------------------------------------------- /vendor/github.com/pin/tftp/sender.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "net" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/pin/tftp/netascii" 12 | ) 13 | 14 | // OutgoingTransfer provides methods to set the outgoing transfer size and 15 | // retrieve the remote address of the peer. 16 | type OutgoingTransfer interface { 17 | // SetSize is used to set the outgoing transfer size (tsize option: RFC2349) 18 | // manually in a server write transfer handler. 19 | // 20 | // It is not necessary in most cases; when the io.Reader provided to 21 | // ReadFrom also satisfies io.Seeker (e.g. os.File) the transfer size will 22 | // be determined automatically. Seek will not be attempted when the 23 | // transfer size option is set with SetSize. 24 | // 25 | // The value provided will be used only if SetSize is called before ReadFrom 26 | // and only on in a server read handler. 27 | SetSize(n int64) 28 | 29 | // RemoteAddr returns the remote peer's IP address and port. 30 | RemoteAddr() net.UDPAddr 31 | } 32 | 33 | type sender struct { 34 | conn *net.UDPConn 35 | addr *net.UDPAddr 36 | tid int 37 | send []byte 38 | receive []byte 39 | retry *backoff 40 | timeout time.Duration 41 | retries int 42 | block uint16 43 | mode string 44 | opts options 45 | } 46 | 47 | func (s *sender) RemoteAddr() net.UDPAddr { return *s.addr } 48 | 49 | func (s *sender) SetSize(n int64) { 50 | if s.opts != nil { 51 | if _, ok := s.opts["tsize"]; ok { 52 | s.opts["tsize"] = strconv.FormatInt(n, 10) 53 | } 54 | } 55 | } 56 | 57 | func (s *sender) ReadFrom(r io.Reader) (n int64, err error) { 58 | if s.mode == "netascii" { 59 | r = netascii.ToReader(r) 60 | } 61 | if s.opts != nil { 62 | // check that tsize is set 63 | if ts, ok := s.opts["tsize"]; ok { 64 | // check that tsize is not set with SetSize already 65 | i, err := strconv.ParseInt(ts, 10, 64) 66 | if err == nil && i == 0 { 67 | if rs, ok := r.(io.Seeker); ok { 68 | pos, err := rs.Seek(0, 1) 69 | if err != nil { 70 | return 0, err 71 | } 72 | size, err := rs.Seek(0, 2) 73 | if err != nil { 74 | return 0, err 75 | } 76 | s.opts["tsize"] = strconv.FormatInt(size, 10) 77 | _, err = rs.Seek(pos, 0) 78 | if err != nil { 79 | return 0, err 80 | } 81 | } 82 | } 83 | } 84 | err = s.sendOptions() 85 | if err != nil { 86 | s.abort(err) 87 | return 0, err 88 | } 89 | } 90 | s.block = 1 // start data transmission with block 1 91 | binary.BigEndian.PutUint16(s.send[0:2], opDATA) 92 | for { 93 | l, err := io.ReadFull(r, s.send[4:]) 94 | n += int64(l) 95 | if err != nil && err != io.ErrUnexpectedEOF { 96 | if err == io.EOF { 97 | binary.BigEndian.PutUint16(s.send[2:4], s.block) 98 | _, err = s.sendWithRetry(4) 99 | if err != nil { 100 | s.abort(err) 101 | return n, err 102 | } 103 | s.conn.Close() 104 | return n, nil 105 | } 106 | s.abort(err) 107 | return n, err 108 | } 109 | binary.BigEndian.PutUint16(s.send[2:4], s.block) 110 | _, err = s.sendWithRetry(4 + l) 111 | if err != nil { 112 | s.abort(err) 113 | return n, err 114 | } 115 | if l < len(s.send)-4 { 116 | s.conn.Close() 117 | return n, nil 118 | } 119 | s.block++ 120 | } 121 | } 122 | 123 | func (s *sender) sendOptions() error { 124 | for name, value := range s.opts { 125 | if name == "blksize" { 126 | err := s.setBlockSize(value) 127 | if err != nil { 128 | delete(s.opts, name) 129 | continue 130 | } 131 | } else if name == "tsize" { 132 | if value != "0" { 133 | s.opts["tsize"] = value 134 | } else { 135 | delete(s.opts, name) 136 | continue 137 | } 138 | } else { 139 | delete(s.opts, name) 140 | } 141 | } 142 | if len(s.opts) > 0 { 143 | m := packOACK(s.send, s.opts) 144 | _, err := s.sendWithRetry(m) 145 | if err != nil { 146 | return err 147 | } 148 | } 149 | return nil 150 | } 151 | 152 | func (s *sender) setBlockSize(blksize string) error { 153 | n, err := strconv.Atoi(blksize) 154 | if err != nil { 155 | return err 156 | } 157 | if n < 512 { 158 | return fmt.Errorf("blkzise too small: %d", n) 159 | } 160 | if n > 65464 { 161 | return fmt.Errorf("blksize too large: %d", n) 162 | } 163 | s.send = make([]byte, n+4) 164 | return nil 165 | } 166 | 167 | func (s *sender) sendWithRetry(l int) (*net.UDPAddr, error) { 168 | s.retry.reset() 169 | for { 170 | addr, err := s.sendDatagram(l) 171 | if _, ok := err.(net.Error); ok && s.retry.count() < s.retries { 172 | s.retry.backoff() 173 | continue 174 | } 175 | return addr, err 176 | } 177 | } 178 | 179 | func (s *sender) sendDatagram(l int) (*net.UDPAddr, error) { 180 | err := s.conn.SetReadDeadline(time.Now().Add(s.timeout)) 181 | if err != nil { 182 | return nil, err 183 | } 184 | _, err = s.conn.WriteToUDP(s.send[:l], s.addr) 185 | if err != nil { 186 | return nil, err 187 | } 188 | for { 189 | n, addr, err := s.conn.ReadFromUDP(s.receive) 190 | if err != nil { 191 | return nil, err 192 | } 193 | if !addr.IP.Equal(s.addr.IP) || (s.tid != 0 && addr.Port != s.tid) { 194 | continue 195 | } 196 | p, err := parsePacket(s.receive[:n]) 197 | if err != nil { 198 | continue 199 | } 200 | s.tid = addr.Port 201 | switch p := p.(type) { 202 | case pACK: 203 | if p.block() == s.block { 204 | return addr, nil 205 | } 206 | case pOACK: 207 | opts, err := unpackOACK(p) 208 | if s.block != 0 { 209 | continue 210 | } 211 | if err != nil { 212 | s.abort(err) 213 | return addr, err 214 | } 215 | for name, value := range opts { 216 | if name == "blksize" { 217 | err := s.setBlockSize(value) 218 | if err != nil { 219 | continue 220 | } 221 | } 222 | } 223 | return addr, nil 224 | case pERROR: 225 | return nil, fmt.Errorf("sending block %d: code=%d, error: %s", 226 | s.block, p.code(), p.message()) 227 | } 228 | } 229 | } 230 | 231 | func (s *sender) abort(err error) error { 232 | if s.conn == nil { 233 | return nil 234 | } 235 | n := packERROR(s.send, 1, err.Error()) 236 | _, err = s.conn.WriteToUDP(s.send[:n], s.addr) 237 | if err != nil { 238 | return err 239 | } 240 | s.conn.Close() 241 | s.conn = nil 242 | return nil 243 | } 244 | -------------------------------------------------------------------------------- /vendor/github.com/pin/tftp/server.go: -------------------------------------------------------------------------------- 1 | package tftp 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // NewServer creates TFTP server. It requires two functions to handle 12 | // read and write requests. 13 | // In case nil is provided for read or write handler the respective 14 | // operation is disabled. 15 | func NewServer(readHandler func(filename string, rf io.ReaderFrom) error, 16 | writeHandler func(filename string, wt io.WriterTo) error) *Server { 17 | return &Server{ 18 | readHandler: readHandler, 19 | writeHandler: writeHandler, 20 | timeout: defaultTimeout, 21 | retries: defaultRetries, 22 | } 23 | } 24 | 25 | type Server struct { 26 | readHandler func(filename string, rf io.ReaderFrom) error 27 | writeHandler func(filename string, wt io.WriterTo) error 28 | backoff backoffFunc 29 | conn *net.UDPConn 30 | quit chan chan struct{} 31 | wg sync.WaitGroup 32 | timeout time.Duration 33 | retries int 34 | } 35 | 36 | // SetTimeout sets maximum time server waits for single network 37 | // round-trip to succeed. 38 | // Default is 5 seconds. 39 | func (s *Server) SetTimeout(t time.Duration) { 40 | if t <= 0 { 41 | s.timeout = defaultTimeout 42 | } else { 43 | s.timeout = t 44 | } 45 | } 46 | 47 | // SetRetries sets maximum number of attempts server made to transmit a 48 | // packet. 49 | // Default is 5 attempts. 50 | func (s *Server) SetRetries(count int) { 51 | if count < 1 { 52 | s.retries = defaultRetries 53 | } else { 54 | s.retries = count 55 | } 56 | } 57 | 58 | // SetBackoff sets a user provided function that is called to provide a 59 | // backoff duration prior to retransmitting an unacknowledged packet. 60 | func (s *Server) SetBackoff(h backoffFunc) { 61 | s.backoff = h 62 | } 63 | 64 | // ListenAndServe binds to address provided and start the server. 65 | // ListenAndServe returns when Shutdown is called. 66 | func (s *Server) ListenAndServe(addr string) error { 67 | a, err := net.ResolveUDPAddr("udp", addr) 68 | if err != nil { 69 | return err 70 | } 71 | conn, err := net.ListenUDP("udp", a) 72 | if err != nil { 73 | return err 74 | } 75 | s.Serve(conn) 76 | return nil 77 | } 78 | 79 | // Serve starts server provided already opened UDP connecton. It is 80 | // useful for the case when you want to run server in separate goroutine 81 | // but still want to be able to handle any errors opening connection. 82 | // Serve returns when Shutdown is called or connection is closed. 83 | func (s *Server) Serve(conn *net.UDPConn) { 84 | s.conn = conn 85 | s.quit = make(chan chan struct{}) 86 | for { 87 | select { 88 | case q := <-s.quit: 89 | q <- struct{}{} 90 | return 91 | default: 92 | err := s.processRequest(s.conn) 93 | if err != nil { 94 | // TODO: add logging handler 95 | } 96 | } 97 | } 98 | } 99 | 100 | // Shutdown make server stop listening for new requests, allows 101 | // server to finish outstanding transfers and stops server. 102 | func (s *Server) Shutdown() { 103 | s.conn.Close() 104 | q := make(chan struct{}) 105 | s.quit <- q 106 | <-q 107 | s.wg.Wait() 108 | } 109 | 110 | func (s *Server) processRequest(conn *net.UDPConn) error { 111 | var buffer []byte 112 | buffer = make([]byte, datagramLength) 113 | n, remoteAddr, err := conn.ReadFromUDP(buffer) 114 | if err != nil { 115 | return fmt.Errorf("reading UDP: %v", err) 116 | } 117 | p, err := parsePacket(buffer[:n]) 118 | if err != nil { 119 | return err 120 | } 121 | switch p := p.(type) { 122 | case pWRQ: 123 | filename, mode, opts, err := unpackRQ(p) 124 | if err != nil { 125 | return fmt.Errorf("unpack WRQ: %v", err) 126 | } 127 | //fmt.Printf("got WRQ (filename=%s, mode=%s, opts=%v)\n", filename, mode, opts) 128 | conn, err := net.ListenUDP("udp", &net.UDPAddr{}) 129 | if err != nil { 130 | return err 131 | } 132 | if err != nil { 133 | return fmt.Errorf("open transmission: %v", err) 134 | } 135 | wt := &receiver{ 136 | send: make([]byte, datagramLength), 137 | receive: make([]byte, datagramLength), 138 | conn: conn, 139 | retry: &backoff{handler: s.backoff}, 140 | timeout: s.timeout, 141 | retries: s.retries, 142 | addr: remoteAddr, 143 | mode: mode, 144 | opts: opts, 145 | } 146 | s.wg.Add(1) 147 | go func() { 148 | if s.writeHandler != nil { 149 | err := s.writeHandler(filename, wt) 150 | if err != nil { 151 | wt.abort(err) 152 | } else { 153 | wt.terminate() 154 | wt.conn.Close() 155 | } 156 | } else { 157 | wt.abort(fmt.Errorf("server does not support write requests")) 158 | } 159 | s.wg.Done() 160 | }() 161 | case pRRQ: 162 | filename, mode, opts, err := unpackRQ(p) 163 | if err != nil { 164 | return fmt.Errorf("unpack RRQ: %v", err) 165 | } 166 | //fmt.Printf("got RRQ (filename=%s, mode=%s, opts=%v)\n", filename, mode, opts) 167 | conn, err := net.ListenUDP("udp", &net.UDPAddr{}) 168 | if err != nil { 169 | return err 170 | } 171 | rf := &sender{ 172 | send: make([]byte, datagramLength), 173 | receive: make([]byte, datagramLength), 174 | tid: remoteAddr.Port, 175 | conn: conn, 176 | retry: &backoff{handler: s.backoff}, 177 | timeout: s.timeout, 178 | retries: s.retries, 179 | addr: remoteAddr, 180 | mode: mode, 181 | opts: opts, 182 | } 183 | s.wg.Add(1) 184 | go func() { 185 | if s.readHandler != nil { 186 | err := s.readHandler(filename, rf) 187 | if err != nil { 188 | rf.abort(err) 189 | } 190 | } else { 191 | rf.abort(fmt.Errorf("server does not support read requests")) 192 | } 193 | s.wg.Done() 194 | }() 195 | default: 196 | return fmt.Errorf("unexpected %T", p) 197 | } 198 | return nil 199 | } 200 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "checksumSHA1": "HFT0JpXvVHrb2XJjIXA6jKRaihQ=", 7 | "path": "github.com/pin/tftp", 8 | "revision": "9ea92f6b1029bc1bf3072bba195c84bb9b0370e3", 9 | "revisionTime": "2016-08-25T18:38:18Z" 10 | }, 11 | { 12 | "checksumSHA1": "HlAlkC1SX2DuDFWTGCf321tbsYg=", 13 | "path": "github.com/pin/tftp/netascii", 14 | "revision": "9ea92f6b1029bc1bf3072bba195c84bb9b0370e3", 15 | "revisionTime": "2016-08-25T18:38:18Z" 16 | } 17 | ], 18 | "rootPath": "github.com/bwalex/tftp-http-proxy" 19 | } 20 | --------------------------------------------------------------------------------