This is a simple but complete TFTP server, It can receive and send files and listens on port 69. It is part of the LaOS (Laser Open Source) project: http://www.laoslaser.org/

Dependents:   TFTPServerTest RMFWeb

Files at this revision

API Documentation at this revision

Comitter:
tuxic
Date:
Mon Dec 26 18:18:32 2011 +0000
Commit message:
First version

Changed in this revision

TFTPServer.cpp Show annotated file Show diff for this revision Revisions of this file
TFTPServer.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TFTPServer.cpp	Mon Dec 26 18:18:32 2011 +0000
@@ -0,0 +1,403 @@
+/*
+ * TFTPServer.cpp
+ * Simple TFTP server
+ *
+ * Copyright (c) 2011 Jaap Vermaas
+ *
+ *   This file is part of the LaOS project (see: http://wiki.laoslaser.org)
+ *
+ *   LaOS is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   LaOS is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with LaOS.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "TFTPServer.h"
+
+// create a new tftp server, with file directory dir and
+// listening on port
+TFTPServer::TFTPServer(char* dir) {
+    ListenSock = new UDPSocket();
+    ListenSock->setOnEvent(this, &TFTPServer::onListenUDPSocketEvent);
+    state = listen;
+    if (ListenSock->bind(Host(IpAddr(), TFTP_PORT)))
+        state = error;
+    TFTPServerTimer.attach(this, &TFTPServer::cleanUp, 5000);
+    sprintf(workdir, "%s", dir);
+    filecnt = 0;
+}
+
+// destroy this instance of the tftp server
+TFTPServer::~TFTPServer() {
+    ListenSock->resetOnEvent();
+    delete(ListenSock);
+    delete(remote);
+    state = deleted;
+}
+
+void TFTPServer::reset() {
+    ListenSock->resetOnEvent();
+    delete(ListenSock);
+    delete(remote);
+    TFTPServerTimer.detach();
+    ListenSock = new UDPSocket();
+    ListenSock->setOnEvent(this, &TFTPServer::onListenUDPSocketEvent);
+    state = listen;
+    if (ListenSock->bind(Host(IpAddr(), TFTP_PORT)))
+        state = error;
+    TFTPServerTimer.attach(this, &TFTPServer::cleanUp, 5000);
+    sprintf(filename, "");
+    filecnt = 0;
+}
+
+// get current tftp status
+TFTPServerState TFTPServer::State() {
+    return state;
+}
+
+// Temporarily disable incoming TFTP connections
+void TFTPServer::suspend() {
+    state = suspended;
+}
+
+// Resume after suspension
+void TFTPServer::resume() {
+    if (state == suspended)
+        state = listen;
+}
+
+// During read and write, this gives you the filename
+void TFTPServer::getFilename(char* name) {
+    sprintf(name, "%s", filename);
+}
+
+// return number of received files
+int TFTPServer::fileCnt() {
+    return filecnt;
+}
+
+// create a new connection reading a file from server
+void TFTPServer::ConnectRead(char* buff, Host* client) {
+    IpAddr clientIp = client->getIp();
+    int clientPort = client->getPort();
+    remote = new Host(clientIp, clientPort);
+    Ack(0);
+    blockcnt = 0;
+    dupcnt = 0;
+
+    sprintf(filename, "%s%s", workdir, &buff[2]);
+    
+    fp = fopen(filename, "rb");
+    if (fp == NULL) {
+        state  = listen;
+        delete(remote);
+        Err("Could not read file", client);
+    } else {
+        // file ready for reading
+        blockcnt = 0;
+        state = reading;
+        #ifdef TFTP_DEBUG
+            char debugmsg[256];
+            sprintf(debugmsg, "Listen: Requested file %s from TFTP connection %d.%d.%d.%d port %d",
+                filename, clientIp[0], clientIp[1], clientIp[2], clientIp[3], clientPort);
+            TFTP_DEBUG(debugmsg);
+        #endif
+        getBlock();
+        sendBlock();
+    }
+}
+
+// create a new connection writing a file to the server
+void TFTPServer::ConnectWrite(char* buff, Host* client) {
+    IpAddr clientIp = client->getIp();
+    int clientPort = client->getPort();
+    remote = new Host(clientIp, clientPort);
+    Ack(0);
+    blockcnt = 0;
+    dupcnt = 0;
+
+    sprintf(filename, "%s%s", workdir, &buff[2]);
+
+    fp = fopen(filename, "wb");
+    if (fp == NULL) {
+        Err("Could not open file to write", client);
+        state  = listen;
+        delete(remote);
+    } else {
+        // file ready for writing
+        blockcnt = 0;
+        state = writing;
+        #ifdef TFTP_DEBUG 
+            char debugmsg[256];
+            sprintf(debugmsg, "Listen: Incoming file %s on TFTP connection from %d.%d.%d.%d clientPort %d",
+                filename, clientIp[0], clientIp[1], clientIp[2], clientIp[3], clientPort);
+            TFTP_DEBUG(debugmsg);
+        #endif
+    }
+}
+
+// get DATA block from file on disk into memory
+void TFTPServer::getBlock() {
+    blockcnt++;
+    char *p;
+    p = &sendbuff[4];
+    int len = fread(p, 1, 512, fp);
+    sendbuff[0] = 0x00;
+    sendbuff[1] = 0x03;
+    sendbuff[2] = blockcnt >> 8;
+    sendbuff[3] = blockcnt & 255;
+    blocksize = len+4;
+}
+
+// send DATA block to the client
+void TFTPServer::sendBlock() {
+    ListenSock->sendto(sendbuff, blocksize, remote);
+}
+
+
+// compare host IP and Port with connected remote machine
+int TFTPServer::cmpHost(Host* client) {
+    IpAddr clientIp = client->getIp();
+    IpAddr remoteIp = remote->getIp();
+    int clientPort = client->getPort();
+    int remotePort = remote->getPort();
+    return ((clientIp == remoteIp) && (clientPort == remotePort));
+}
+
+// send ACK to remote
+void TFTPServer::Ack(int val) {
+    char ack[4];
+    ack[0] = 0x00;
+    ack[1] = 0x04;
+    if ((val>603135) || (val<0)) val = 0;
+    ack[2] = val >> 8;
+    ack[3] = val & 255;
+    ListenSock->sendto(ack, 4, remote);
+}
+
+// send ERR message to named client
+void TFTPServer::Err(char* msg, Host* client) {
+    char message[32];
+    strncpy(message, msg, 32);
+    char err[37];
+    sprintf(err, "0000%s0", message);
+    err[0] = 0x00;
+    err[1] = 0x05;
+    err[2]=0x00;
+    err[3]=0x00;
+    int len = strlen(err);
+    err[len-1] = 0x00;
+    ListenSock->sendto(err, len, client);
+    #ifdef TFTP_DEBUG
+        char debugmsg[256];
+        sprintf(debugmsg, "Error: %s", message);
+        TFTP_DEBUG(debugmsg);
+    #endif
+}
+
+// check if connection mode of client is octet/binary
+int TFTPServer::modeOctet(char* buff) {
+    int x = 2;
+    while (buff[x++] != 0); // get beginning of mode field
+    int y = x;
+    while (buff[y] != 0) {
+        buff[y] = tolower(buff[y]);
+        y++;
+    } // make mode field lowercase
+    return (strcmp(&buff[x++], "octet") == 0);
+}
+
+// timed routine to avoid hanging after interrupted transfers
+void TFTPServer::cleanUp() {
+    static int lastcnt;
+    switch (state) {
+        case reading:
+            if (lastcnt == blockcnt) {
+                fclose(fp);
+                state=listen;
+                delete(remote);
+            }
+            break;
+        case writing:
+            if (lastcnt == blockcnt) {
+                fclose(fp);
+                state = listen;
+                remove(filename);
+                delete(remote);
+            }
+            break;
+    } // state
+    lastcnt = blockcnt;
+}
+
+// event driven routines to handle incoming packets
+void TFTPServer::onListenUDPSocketEvent(UDPSocketEvent e) {
+    extern SDFileSystem sd;
+    Host* client = new Host();
+    char buff[516];
+    if (e == UDPSOCKET_READABLE) {
+        switch (state) {
+            case listen:
+                if (int len = ListenSock->recvfrom(buff, 516, client)) {
+                    switch (buff[1]) {
+                        case 0x01: // RRQ
+                            if (modeOctet(buff))
+                                ConnectRead(buff, client);
+                            else
+                                Err("Not in octet mode", client);
+                            break;
+                        case 0x02: // WRQ
+                            if (modeOctet(buff))
+                                ConnectWrite(buff, client);
+                            else
+                                Err("Not in octet mode", client);
+                            break;
+                        case 0x03: // DATA before connection established
+                            Err("No data expected", client);
+                            break;
+                        case 0x04:  // ACK before connection established
+                            Err("No ack expected", client);
+                            break;
+                        case 0x05: // ERROR packet received
+                            #ifdef TFTP_DEBUG
+                                TFTP_DEBUG("TFTP Eror received\n\r");
+                            #endif
+                            break;
+                        default:    // unknown TFTP packet type
+                            Err("Unknown TFTP packet type", client);
+                            break;
+                    } // switch buff[1]
+                } // recvfrom
+                break; // case listen
+            case reading:
+                while (int len = ListenSock->recvfrom(buff, 516, client) ) {
+                    switch (buff[1]) {
+                        case 0x01:
+                            // if this is the receiving host, send first packet again
+                            if ((cmpHost(client) && blockcnt==1)) {
+                                sendBlock();
+                                dupcnt++;
+                            }
+                            if (dupcnt>10) { // too many dups, stop sending
+                                fclose(fp);
+                                state=listen;
+                                delete(remote);
+                            }
+                            break;
+                        case 0x02:
+                            // this should never happen, ignore
+                            break; // case 0x02
+                        case 0x03:
+                            // we are the sending side, ignore
+                            break;
+                        case 0x04:
+                            // last packet received, send next if there is one
+                            dupcnt = 0;
+                            if (blocksize == 516) {
+                                getBlock();
+                                sendBlock();
+                            } else { //EOF
+                                fclose(fp);
+                                state = listen;
+                                delete(remote);
+                            }
+                            break;
+                        default:  // this includes 0x05 errors
+                            fclose(fp);
+                            state = listen;
+                            delete(remote);
+                            break;
+                    } // switch (buff[1])
+                } // while
+                break; // reading
+            case writing:
+                while (int len = ListenSock->recvfrom(buff, 516, client) ) {
+                    switch (buff[1]) {
+                        case 0x02:
+                            // if this is a returning host, send ack again
+                            if (cmpHost(client)) {
+                                Ack(0);
+                                #ifdef TFTP_DEBUG
+                                    TFTP_DEBUG("Resending Ack on WRQ");
+                                #endif
+                            }
+                            break; // case 0x02
+                        case 0x03:
+                            if (cmpHost(client)) { // check if this is our partner
+                                int block = (buff[2] << 8) + buff[3];
+                                if ((blockcnt+1) == block) {
+                                    Ack(block);
+                                    // new packet
+                                    char *data = &buff[4];
+                                    fwrite(data, 1,len-4, fp);
+                                    blockcnt++;
+                                    dupcnt = 0;
+                                } else {
+                                    if ((blockcnt+1) < block) { // high block nr
+                                        // we missed a packet, error
+                                        #ifdef TFTP_DEBUG
+                                            TFTP_DEBUG("Missed packet!");
+                                        #endif
+                                        fclose(fp);
+                                        state = listen;
+                                        remove(filename);
+                                        delete(remote);
+                                    } else { // duplicate packet, do nothing
+                                        #ifdef TFTP_DEBUG
+                                            char debugmsg[256];
+                                            sprintf(debugmsg, "Dupblicate packet %d", blockcnt);
+                                            TFTP_DEBUG(debugmsg);
+                                        #endif
+                                        if (dupcnt > 10) {
+                                            Err("Too many dups", client);
+                                            fclose(fp);
+                                            remove(filename);
+                                            state = listen;
+                                        } else {
+                                            Ack(blockcnt);
+                                        }
+                                        dupcnt++;
+                                    }
+                                }
+                                //printf ("Read packet %d with blocksize = %d\n\r", blockcnt, len);
+                                if (len<516) {
+                                    #ifdef TFTP_DEBUG
+                                        char debugmsg[256];
+                                        sprintf(debugmsg, "Read last block %d", len);
+                                        TFTP_DEBUG(debugmsg);
+                                    #endif
+                                    fclose(fp);
+                                    state = listen;
+                                    delete(remote);
+                                    filecnt++;
+                                }
+                                break; // case 0x03
+                            }  else // if cmpHost
+                                Err("Wrong IP/Port: Not your connection!", client);
+                            break; // case 0x03
+                        default:
+                            Err("No idea why you're sending me this!", client);
+                            break; // default
+                    } // switch (buff[1])
+                } // while
+                break; // writing
+            case suspended:
+                if (int len = ListenSock->recvfrom(buff, 516, client))
+                    Err("Packet received on suspended socket, discarded", client);
+                break;
+        } // state
+    } else {
+        #ifdef TFTP_DEBUG
+            TFTP_DEBUG("TFTP unknown UDP status");
+        #endif
+    }
+    delete(client);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TFTPServer.h	Mon Dec 26 18:18:32 2011 +0000
@@ -0,0 +1,113 @@
+/**
+ * TFTPServer.h
+ * Simple TFTP server 
+ *
+ * Copyright (c) 2011 Jaap Vermaas
+ *
+ *   This file is part of the LaOS project (see: http://wiki.laoslaser.org
+ *
+ *   LaOS is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   LaOS is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with LaOS.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Minimal TFTP Server
+ *      * Receive and send files via TFTP
+ *      * Server handles only one transfer at a time
+ *      * Supports only binary mode transfers, no (net)ascii
+ *      * fixed block size: 512 bytes
+ *
+ * http://spectral.mscs.mu.edu/RFC/rfc1350.html
+ *
+ * Example:
+ * @code 
+ * TFTPServer *srv;
+ * ...
+ * srv = new TFTPServer();
+ * ...
+ * @endcode
+ *
+ */
+
+#ifndef _TFTPSERVER_H_
+#define _TFTPSERVER_H_
+
+#include <stdio.h>
+#include <ctype.h>
+#include "mbed.h"
+#include "SDFileSystem.h"   // http://mbed.org/users/NeoBelerophon/libraries/ChaNFSSD
+#include "UDPSocket.h"      // http://mbed.org/users/donatien/programs/EthernetNetIf
+
+#define TFTP_PORT 69
+//#define TFTP_DEBUG(x) printf("%s\n\r", x);
+
+enum TFTPServerState { listen, reading, writing, error, suspended, deleted }; 
+
+class TFTPServer {
+
+public:
+    // create a new tftp server, with file directory dir and 
+    // listening on port
+    TFTPServer(char* dir);
+    // destroy this instance of the tftp server
+    void reset();
+    // reset socket
+    ~TFTPServer();
+    // get current tftp status
+    TFTPServerState State();
+    // Temporarily disable incoming TFTP connections
+    void suspend();
+    // Resume after suspension
+    void resume();
+    // During read and write, this gives you the filename
+    void getFilename(char* name);
+    // Return number of received files
+    int fileCnt();
+
+private:
+    // create a new connection reading a file from server
+    void ConnectRead(char* infile, Host* client);
+    // create a new connection writing a file to the server
+    void ConnectWrite(char* infile, Host* client);
+    // get DATA block from file on disk into memory
+    void getBlock();
+    // send DATA block to the client
+    void sendBlock();
+    // handle special files
+    // anything called *.bin is written to /local/FIRMWARE.BIN
+    // anything called config.txt is written to /local/config.txt
+    // even if workdir is not /local
+    int cmpHost(Host* client);
+    // send ACK to remote
+    void Ack(int val);
+    // send ERR message to named client
+    void Err(char* msg, Host* client);
+    // check if connection mode of client is octet/binary
+    int modeOctet(char* buff);
+    // timed routine to avoid hanging after interrupted transfers
+    void cleanUp();
+    // event driven routines to handle incoming packets
+    void onListenUDPSocketEvent(UDPSocketEvent e);
+    
+    UDPSocket* ListenSock;      // main listening socket (dflt: UDP port 69)
+    char workdir[256];          // file working directory
+    TFTPServerState state;      // current TFTP server state
+    Host* remote;               // connected remote Host IP and Port
+    int blockcnt, dupcnt;       // block counter, and DUP counter
+    FILE* fp;                   // current file to read or write
+    char sendbuff[516];         // current DATA block;
+    int blocksize;              // last DATA block size while sending
+    char filename[256];         // current (or most recent) filename
+    Ticker TFTPServerTimer;     // timeout timer
+    int filecnt;                // received file counter
+};
+
+#endif