Cerberus FTP Client

Hello all,

I have recently been working with the Ethernet ENC28 Module and the mIP code. So far I have been able to host a basic webpage, get http sites from the net, and blink a led via UDP packets sent from a PC, all from the example code given in the project. This is awesome!

My next step would be to try and upload a text file to a FTP server. I am fairly ignorant of FTP protocol and what it takes to do so past basic FTP examples in .NET.

I have seen several examples including this one for the FEZ panda, but from my understanding it requires the use of WIZnet W5100 code, which I don’t think is native on the Cerberus. So far, this seems like my best bet though.

I guess what I’m asking is if this is a good spot to start fiddling with a very simple FTP client (only want to be able to connect and disconnect, enter USER/PASS, and upload small text files from the Cerberus to a remote FTP server). Am I embarking on something very difficult? Or maybe I’m looking in the wrong place altogether and it has been done before. Any help or advice would be appreciated!

Thanks. You guys have always been very helpful.
Sivat

typically, the WIZNet implementation on the GHI devices was reasonably close to the full NETMF socket features. You may well find the challenge of porting the client code is not that significant (I haven’t dug into mIP but expect that this is reasonably close to the same core socket communications; it’s then just a matter of handling the communications via the mIP socket calls rather than the Wiznet ones). I can’t say I’ve heard anyone do this on a modern device but can’t say I’d expect this to be too hard to figure out for someone who wanted to :slight_smile:

Alright, I’m back at it trying to get some sort of FTP client working.

The good news is that I can access my FileZilla FTP server using some code I’ve written (below) with the emulator. The bad news is that when I deploy it to my board it no longer works.

I’ve also quoted the wireshark packets that are sent from my Cerb at the socketA.Connect(ipep) line. This seems to be where everything breaks down.

I’m just putting this up here to see if I get any bites. Thanks all.

  • Sivat

First the code (shortened version, reads the welcome message from the FTP server in emulator mode):


InitializeNetworkStatic(); // Commented out when using emulator

string receiveBuffer = "";
NetworkStream netStream = null;
Socket socketA = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            
// Right now I just want to use socketA to try to make a connection to an FTP server running on my PC and get the welcome message
IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("192.168.1.43"), 21);
socketA.Connect(ipep);
Debug.Print("Got past the socketA.Connect() line"); // Never gets here on Cerb

// Poll the socket to get the welcome message
if (socketA.Poll(1000, SelectMode.SelectRead))
{
    int availableToRead = socketA.Available;
    if (availableToRead > 0)
    {
        byte[] buffer = new byte[availableToRead];
        int value = socketA.Receive(buffer, availableToRead, SocketFlags.None);
        receiveBuffer = new string(Encoding.UTF8.GetChars(buffer));
        Debug.Print(receiveBuffer);
        if (value != availableToRead)
        {
            Debug.Print("Value did not equal availableToRead");
        }
    }
}

Next the initialization code. I pulled this off the GHI forums and it seems to be working for me.


static string myIP = "192.168.1.250"; 
        static string subnetMask = "255.255.255.0";
        static string gatewayAddress = "192.168.1.1";
        static string dnsAddresses = "192.168.1.1";

        static byte[] myMAC = new byte[] { 0x00, 0x30, 0xFB, 0x87, 0xD2, 0x3C };
            Debug.Print("Initializing network...");
            NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
            if (interfaces != null && interfaces.Length > 0)
            {
                NetworkInterface networkInterface = interfaces[0];
                Boolean isDhcpWorked = false;

                Debug.Print("Setting static IP...");
                networkInterface.EnableStaticIP(myIP, subnetMask, gatewayAddress);
                networkInterface.PhysicalAddress = myMAC;
                networkInterface.EnableStaticDns(new[] { dnsAddresses });

                Debug.Print("Network ready.");
                Debug.Print(" IP Address: " + networkInterface.IPAddress);
                Debug.Print(" Subnet Mask: " + networkInterface.SubnetMask);
                Debug.Print(" Default Gateway: " + networkInterface.GatewayAddress);
                Debug.Print(" DNS Server: " + networkInterface.DnsAddresses[0]);
            }
            else
            {
                Debug.Print("No network device found.");
            }

And last, the wireshark log. Basically a single packet with several retransmission tries. Also put on as image since this is hard to read.

Funny story: My windows firewall was blocking the traffic. I added a firewall rule for port 21 and awesomeness followed.

Hopefully I’ll write some short, concise FTP client code and make another post in a few days with an update in case anyone else cares ;D

Here is some very basic code that logs in, gets a list of files, downloads a file, uploads a file, and then quits.

Hope it helps someone somewhere.

Written in NETMF 4.2 on a Cerberus board to connect over a LAN to a FileZilla Server. Assumes that the firewall is not blocking the static IP of the Cerb at all and that the user has read/write access.


using System;
using System.Net;
using System.Net.Sockets;
using Microsoft.SPOT.Net.NetworkInformation;
using System.Text;
using Microsoft.SPOT;

namespace AnotherFTPClientAttempt
{
    // This program opens a connection to a FTP server at 192.168.1.43 using port 21
    // It then logs in with 'myUserName' and 'myPassword'.
    // Three commands are issued: NLST, RETR madeOnCPU.txt, and STOR madeOnMCU.txt
    // The program then quits using the QUIT command and enters an endless loop.
    //
    // Debug information is printed to the output window. Almost no checks on returned
    // strings/bytes written/bytes read/etc. are done. This is mostly a proof of
    // concept. It has been tested using NETMF 4.2, a Cerberus board, a FileZilla
    // server, all of which is running on a local area network. Note that the static
    // IP (192.168.1.250) of the Cerberus had to be allowed in the inbound rules
    // of the PC's firewall for any connection to be made.
    public class Program
    {
        static string myIP = "192.168.1.250"; // Enter whatever static IP you want to use here
        static string subnetMask = "255.255.255.0"; // I think this is a default/common mask
        static string gatewayAddress = "192.168.1.1"; // I believe this needs to be the router IP, but could be wrong
        static string dnsAddresses = "192.168.1.1"; // I believe this needs to be the router IP, but could be wrong
        static byte[] myMAC = new byte[] { 0x00, 0x30, 0xFB, 0x87, 0xD2, 0x3C }; // Works for me! But not sure of its importance/significance

        public static void Main()
        {
            Debug.Print("STARTING...");

            InitializeNetworkStatic();

            string rString = "";
            string serverIP = "192.168.1.43"; // Change for your server's IP
            int serverPort = 21; // Usually 21, but dependent on your server
            Socket socketA = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Socket socketB = null;
            int passivePort = -1; // Used with socketB when using the data connection

            rString = connectToServer(socketA, serverIP, serverPort);

            //NetworkStream netStream = null;
            //netStream = new NetworkStream(socketA); // Look up what a network stream does!
            //Debug.Print("Don't forget to try stuff with the NetworkStream to see if it works better/easier!!!");

            // Login to the server
            sendToSocket(socketA, "USER myUserName\r\n");
            rString = readFromSocket(socketA);
            sendToSocket(socketA, "PASS myPassword\r\n");
            rString = readFromSocket(socketA);
          
            // List the directory using NLST
            sendToSocket(socketA, "PASV\r\n");
            rString = readFromSocket(socketA);
            passivePort = parseForPasvPort(rString); // Server sends port to connect on

            // Now we try to connect on the passive port
            socketB = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socketB.Connect(new IPEndPoint(IPAddress.Parse(serverIP), passivePort));

            sendToSocket(socketA, "NLST\r\n");
            rString = readFromSocket(socketB); // READING FROM SOCKET B NOW!
            socketB.Close();
            
            // I already have a test file on my FTP server 'madeOnCPU.txt'
            // Download this file next
            sendToSocket(socketA, "TYPE A\r\n"); // Not sure if necessary, but FileZilla client does this
            rString = readFromSocket(socketA);
            sendToSocket(socketA, "PASV\r\n"); // Need new passive/data connection
            rString = readFromSocket(socketA);
            passivePort = parseForPasvPort(rString);
            socketB = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socketB.Connect(new IPEndPoint(IPAddress.Parse(serverIP), passivePort));
            sendToSocket(socketA, "RETR madeOnCPU.txt\r\n");
            rString = readFromSocket(socketB);
            socketB.Close();

            // Create a file to upload to the server called madeOnMCU.txt
            sendToSocket(socketA, "TYPE A\r\n"); // Not sure if necessary, but FileZilla client does this
            rString = readFromSocket(socketA);
            sendToSocket(socketA, "PASV\r\n"); // Need new passive/data connection
            rString = readFromSocket(socketA);
            passivePort = parseForPasvPort(rString);
            socketB = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socketB.Connect(new IPEndPoint(IPAddress.Parse(serverIP), passivePort));
            sendToSocket(socketA, "STOR madeOnMCU.txt\r\n");
            rString = readFromSocket(socketA);
            sendToSocket(socketB, "This was made on the MCU.");
            socketB.Close();

            // All done so send the QUIT command to log out
            sendToSocket(socketA, "QUIT\r\n");
            rString = readFromSocket(socketA);
            socketA.Close();

            // All done. Wait forever so we can read debug output
            Debug.Print("DONE!!!!!");

            while (true)
            {
                System.Threading.Thread.Sleep(100);
            }
        }

        private static void InitializeNetworkStatic()
        {
            // Works for me, but is a copy/paste from GHI forum
            // here: https://www.ghielectronics.com/community/forum/topic?id=10140&page=1
            Debug.Print("Initializing network...");
            NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
            if (interfaces != null && interfaces.Length > 0)
            {
                NetworkInterface networkInterface = interfaces[0];
                Boolean isDhcpWorked = false;

                Debug.Print("Setting static IP...");
                networkInterface.EnableStaticIP(myIP, subnetMask, gatewayAddress);
                networkInterface.PhysicalAddress = myMAC;
                networkInterface.EnableStaticDns(new[] { dnsAddresses });

                Debug.Print("Network ready.");
                Debug.Print(" IP Address: " + networkInterface.IPAddress);
                Debug.Print(" Subnet Mask: " + networkInterface.SubnetMask);
                Debug.Print(" Default Gateway: " + networkInterface.GatewayAddress);
                Debug.Print(" DNS Server: " + networkInterface.DnsAddresses[0]);
            }
            else
            {
                Debug.Print("No network device found.");
            }
        }

        public static string readFromSocket(Socket sock)
        {
            string receivedString = "";
            int bytesRead = 0;
            int bytesAvailToRead = 0;
            byte[] buffer = null;

            // DEBUG STUFF
            int readCount = 0;
            int pollCount = 0;

            int waitCount = 0;
            // The socket did not always have bytes immediately
            // available to read, so I wait a bit until it is
            // ready. This could be changed/shortened. Almost no
            // testing was done here. to optimize.
            while (waitCount < 10 && sock.Available == 0)
            {
                System.Threading.Thread.Sleep(100);
                waitCount++;
            }

            while (sock.Available > 0)
            {
                if (sock.Poll(1000, SelectMode.SelectRead))
                {
                    bytesAvailToRead = sock.Available;
                    buffer = new byte[bytesAvailToRead];
                    bytesRead += sock.Receive(buffer, bytesAvailToRead, SocketFlags.None);
                    receivedString += new string(Encoding.UTF8.GetChars(buffer));
                    readCount++;
                }
                else
                {
                    pollCount++;
                }
            }

            // DEBUG STUFF
            Debug.Print("## DEBUG STATISTICS-----------");
            Debug.Print("Waited " + waitCount + " times");
            Debug.Print("Read " + receivedString);
            Debug.Print("Took " + readCount + " Receive calls");
            Debug.Print("Poll count = " + pollCount);
            Debug.Print("----------------------------");

            return receivedString;
        }

        public static void sendToSocket(Socket sock, string sendMe)
        {
            // Write it here
            if (sock.Poll(1000, SelectMode.SelectWrite))
            {
                int numSent = sock.Send(Encoding.UTF8.GetBytes(sendMe));
                Debug.Print("Sent " + numSent + " bytes");
            }
            else
            {
                Debug.Print("Poll said not ready to write");
            }
        }

        public static string connectToServer(Socket sock, string ip, int port)
        {
            IPEndPoint ipep = new IPEndPoint(IPAddress.Parse(ip), port);
            sock.Connect(ipep);

            // Read welcome message and return
            return readFromSocket(sock);
        }

        public static int parseForPasvPort(string s)
        {
            // Probably only works for FileZilla servers.
            // There are suggestions online for how to read/parse
            // for a passive port number online. For example,
            // here: http://cr.yp.to/ftp/retr.html
            int i, j;
            i = s.IndexOf('(');
            j = s.IndexOf(')');
            string ip = s.Substring(i + 1, j - i - 1);
            string[] pieces = ip.Split(',');

            return System.Convert.ToInt32(pieces[pieces.Length - 2]) * 256 + System.Convert.ToInt32(pieces[pieces.Length - 1]);
        }
    }
}


Also, using the Cerberus Ethernet firmware and the ENC28 module.