STM Урок 95. LAN. W5500. FTP Server. Часть 1



После того, как мы научились организовывать при помощи нашей микросхемы W5500 несколько одновременно открытых соединений, можно подумать и о следующем протоколе, который предназначен для передачи файлов, а также управления файловой системой по сети — это протокол FTP ( File Transfer Protocol — протокол передачи файлов).

Данный протокол, хотя он и имеет некоторые проблемы с безопасностью, так как логин и пароль при авторизации здесь передаются в открытом виде, также имеет популярность и в наше время. Например, в домашних условиях при передаче данных нам не обязательно скрывать от самих себя наши учетные данные, а также вполне можно поверх данного протокола воспользоваться ещё протоколом для шифрования.

Но наряду с тем, что протокол FTP кажется простым на первый взгляд, на самом деле это вовсе не так. Клиент с сервером постоянно обмениваются сообщениями, мало того для передачи данных необходимо вовремя открыть ещё одно соединение, а, следовательно, и вовремя его закрыть. Конечно, данный протокол проще, нежели, например протоколы на основе протокола NetBios, которые вместе с передачей данных также предусматривают поддержку шифрования, назначения имён узлам и прочие прелести. Но, тем не менее, повозиться с ним придётся, что я, собственно, и проделал. Было много проблем, которые встретились мне на пути освоения данного протокола. Например, мы давно научились передавать из наших модулей LAN большие объёмы данных, а вот принимать ещё не научились. Оказалось это не так-то просто. Но, не будем бояться трудностей, иначе так мы ничему не научимся, и пойдём по проторённой мной дорожке, обходя эти трудности как-то стороной.

Немного поговорим о самом протоколе. Хотя особо разжёвывать мы его не будем, так как потом всё равно мы должны будем заняться этим повторно при сочинении алгоритма программы и написании кода, который будет претворять в жизнь данный алгоритм.

Поэтому познакомимся с протоколом FTP как-то вкратце.

Как я уже говорил, обмен между клиентом и сервером для того, чтобы потом передать какие-то данные, происходит посредством сообщений. Данные сообщения являются текстовыми, тем не менее начинаются они с цифры, которая и несёт в себе основную информацию.

Также ещё раз повторюсь, что для полноценной работы протокола нам потребуется два соединения. Одно — управляющее, а другое — для передачи данных.

Для управляющего соединения по FTP было принято на сервере использовать порт 21. На стороне клиента порт назначается автоматически, то есть его номер не так важен.

Для передачи данных существует два вида назначения портов, так как существует два режима работы клиента и сервера по FTP — активный и пассивный.

В активном режиме соединения FTP для передачи данных инициатором соединения является сервер. Тогда для соединения передачи данных на сервере используется порт 20, а на стороне клиента порт с номером, большим 1024. Но в таком случае, если на стороне клиента существует какой-то файервол (сетевой экран) или транслятор NAT, то сервер установить с клиентом соединение не сможет.

Для этого существует также пассивный режим. Тогда уже для передачи данных клиента с сервером инициатором соединения будет выступать клиент. В этом случае и на стороне сервера и на стороне клиента при создании соединения присваиваются случайные номера портов, только с большим номером, чем 1024. Данное соединение создаётся следующим образом. Клиент сообщает серверу, что он желает использовать пассивный режим, передав определённую команду, а сервер отвечает ему номером своего адреса IP, а также номером порта, с которым и будет клиент создавать соединение для передачи данных.

Ну а остальные тонкости протокола я предлагаю постигать при написании нашего исходного кода.

Для этого мы создадим проект на основе проекта из урока 93 W5500_HTTPS_SOCKETS назовём его W5500_FTPS.

Запустим наш проект в Cube MX.

Зайдем в настройки проекта и выберем там в качестве среды программирования System Workbench, так как размер файла прошивки у нас сегодня рискует увеличиться до размера более 32 килобайта, что недопустимо для бесплатной версии Keil

Image00

Cгенерируем проект и подключим его в среде System Workbench.

Зайдём в настройки проекта и установим уровень оптимизации 1

Image01

Попробуем собрать проет и начнём работу с ним.

Создадим новую библиотеку для FTP, состоящую из двух файлов — ftpd.h и ftpd.c со следующим содержимым

ftpd.h:

#ifndef FTPD_H_

#define FTPD_H_

//--------------------------------------------------

#include "stm32f4xx_hal.h"

#include <string.h>

#include <stdlib.h>

#include <stdint.h>

#include <ctype.h>

#include "fatfs.h"

#include "w5500.h"

//--------------------------------------------------

//--------------------------------------------------

#endif /* FTPD_H_ */

ftpd.c:

#include "ftpd.h"

//-----------------------------------------------

extern UART_HandleTypeDef huart2;

//-----------------------------------------------

extern char str1[60];

extern uint8_t sect[515];

extern uint8_t ipaddr[4];

extern volatile uint16_t tcp_size_wnd;

//-----------------------------------------------

В заголовочном файле мы подключили новый для нас файл ctype.h, который несёт в себе несколько функций по работе не именно со строками, а с отдельными строчными символами. Это нам потом пригодится.

Подключим также нашу библиотеку в файл w5500.h внизу

#include "httpd.h"

#include "ftpd.h"

Работать мы будем с протоколом только в режиме пассивном. Но номер порта для активного соединения мы всё же добавим.

Поэтому зайдём в файл net.h и добавим макросы для номеров портов

#define LOCAL_PORT 80

#define LOCAL_PORT_FTP_CONTROL 21

#define LOCAL_PORT_FTP_DATA 20

#define LOCAL_PORT_FTP_DATA_PASSIV 35500

А в файле net.c добавим глобальные переменные, воспользовавшись этими макросами

uint16_t local_port = LOCAL_PORT;

uint16_t local_port_ftp_control = LOCAL_PORT_FTP_CONTROL;

uint16_t local_port_ftp_data = LOCAL_PORT_FTP_DATA;

uint16_t local_port_ftp_data_passiv = LOCAL_PORT_FTP_DATA_PASSIV;

Также подключим данные переменные в файле w5500.c

extern uint16_t local_port;

extern uint16_t local_port_ftp_control;

extern uint16_t local_port_ftp_data;

extern uint16_t local_port_ftp_data_passiv;

В файле w5500.h добавим макросы для сокетов

#define MAC_ADDR {0x00,0x15,0x42,0xBF,0xF0,0x51}

//--------------------------------------------------

#define SOCKET_MAX 8

#define FTP_SOCKET_MAX 2

#define FTP_SOCKET_CTRL 0

#define FTP_SOCKET_DATA 1

//--------------------------------------------------

Думаю, назначение макросов понятно из их имён.

В файле net.c в функции packet_receive изменим параметр в цикле

for(i=0;i<SOCKET_MAX;i++)

В функции  в файле w5500.c между комментарием «//настраиваем сокеты» и задержкой будет теперь вот такой код

//Настраиваем сокеты

//Для FTP

//Открываем управляющий сокет

SetSockPort(0, local_port_ftp_control);

OpenSocket(0,Mode_TCP);

SocketInitWait(0);

//Начинаем слушать сокет

ListenSocket(0);

SocketListenWait(0);

//Открываем сокет для данных

SetSockPort(1, local_port_ftp_data_passiv);

OpenSocket(1,Mode_TCP);

SocketInitWait(1);

//Начинаем слушать сокет

ListenSocket(1);

SocketListenWait(1);

//Для HTTP

for(i=FTP_SOCKET_MAX;i<SOCKET_MAX;i++)

{

  SetSockPort(i, local_port);

  //Открываем сокет

  OpenSocket(i,Mode_TCP);

  SocketInitWait(i);

  //Начинаем слушать сокет

  ListenSocket(i);

  SocketListenWait(i);

}

HAL_Delay(500);

То есть, как видно из кода, для управляющего соединения FTP мы используем сокет 0, для соединения передачи данных FTP — сокет 1, а остальные 6 сокетов остаются для целей HTTP. Поэтому сокеты для FTP мы настроили раздельно каждый ввиду разницы их портов, а для сокетов HTTP используем цикл.

 

 

Дальше в цикле просмотра состояний мы немного исправим параметры

for(i=0;i<SOCKET_MAX;i++)

В файле w5500.h создадим прототипы некоторых функций

void SendSocket(uint8_t sock_num);

uint16_t GetSizeRX(uint8_t sock_num);

void w5500_readSockBuf(uint8_t sock_num, uint16_t point, uint8_t *buf, uint16_t len);

Теперь займёмся немного совершенствованием кода касательно протокола HTTP.

В файле httpd.c создадим функцию внизу страницы для обработки пакетов HTTP, перенеся в неё практически весь код обработки пакета из общей функции приема и обработки пакетов файла w5500.c

//-----------------------------------------------

void http_receive(uint8_t sn)

{

  uint16_t point;

  uint16_t len;

  if(httpsockprop[sn].data_stat == DATA_COMPLETED)

  {

    len = GetSizeRX(sn);

    //Если пришел пустой пакет, то уходим из функции

    if(!len) return;

    //Отобразим размер принятых данных

    sprintf(str1,"S%d len buf:0x%04Xrn",sn,len);

    HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

    //здесь обмениваемся информацией: на запрос документа от клиента отправляем ему запрошенный документ

    //указатель на начало чтения приёмного буфера

    point = GetReadPointer(sn);

    w5500_readSockBuf(sn, point, (uint8_t*)tmpbuf, 5);

    if (strncmp(tmpbuf,"GET /", 5) == 0)

    {

      httpsockprop[sn].prt_tp = PRT_TCP_HTTP;

      http_request(sn);

    }

  }

  else if(httpsockprop[sn].data_stat==DATA_MIDDLE)

  {

    if(httpsockprop[sn].prt_tp == PRT_TCP_HTTP)

    {

      tcp_send_http_middle(sn);

    }

  }

  else if(httpsockprop[sn].data_stat==DATA_LAST)

  {

    if(httpsockprop[sn].prt_tp == PRT_TCP_HTTP)

    {

      tcp_send_http_last(sn);

      DisconnectSocket(sn); //Разъединяемся

      SocketClosedWait(sn);

      sprintf(str1,"S%d closedrn",sn);

      HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

      OpenSocket(sn,Mode_TCP);

      //Ждём инициализации сокета (статус SOCK_INIT)

      SocketInitWait(sn);

      //Продолжаем слушать сокет

      ListenSocket(sn);

      SocketListenWait(sn);

    }

  }

}

//-----------------------------------------------

Также добавим на данную функцию прототип в заголовочном файле, а прототип функции http_request можно теперь оттуда убрать.

После того, как теперь мы уберём из функции w5500_packetReceive в файле w5500.c всё, что мы перенесли, она примет вот такой вот короткий вид

//-----------------------------------------------

void w5500_packetReceive(uint8_t sn)

{

  if(GetSocketStatus(sn)==SOCK_ESTABLISHED)

  {

    //сокет для FTP

    if(sn<FTP_SOCKET_MAX)

    {

    }

    //сокет для HTTP

    else if(sn>=FTP_SOCKET_MAX)

    {

      http_receive(sn);

    }

  }

}

//-----------------------------------------------

 

Соберём код и прошьём контроллер, для того, чтобы проверить, что у нас по-прежнему работает нормально HTTP-сервер, запросив какую-нибудь страницу с него из браузера.

 

 

Теперь начнём разбираться с FTP.

В файле ftpd.c добавим функцию приёма и обработки пакетов FTP, в которой пока что для начала только измерим размер данных в пакете

//-----------------------------------------------

void ftp_receive(uint8_t sn)

{

  uint16_t len;

  uint16_t point;

  len = GetSizeRX(sn);

}

//-----------------------------------------------

Создадим на данную функцию прототип и вызовем её в функции w5500_packetReceive в файле w5500.c

if(sn<FTP_SOCKET_MAX)

{

  ftp_receive(sn);

}

Теперь вернёмся в функцию ftp_receive в файл ftpd.c и напишем там следующее двойное условие

  len = GetSizeRX(sn);

  if(sn == FTP_SOCKET_CTRL)

  {

  }

  else if(sn == FTP_SOCKET_DATA)

  {

  }

}

В заголовочном файле ftpd.h создадим перечисление из возможных вариантов команд FTP

#include "w5500.h"

//--------------------------------------------------

// FTP commands

enum ftp_cmd {

  USER_CMD,

  ACCT_CMD,

  PASS_CMD,

  TYPE_CMD,

  LIST_CMD,

  CWD_CMD,

  DELE_CMD,

  NAME_CMD,

  QUIT_CMD,

  RETR_CMD,

  STOR_CMD,

  PORT_CMD,

  NLST_CMD,

  PWD_CMD,

  XPWD_CMD,

  MKD_CMD,

  XMKD_CMD,

  XRMD_CMD,

  RMD_CMD,

  STRU_CMD,

  MODE_CMD,

  SYST_CMD,

  XMD5_CMD,

  XCWD_CMD,

  FEAT_CMD,

  PASV_CMD,

  SIZE_CMD,

  MLSD_CMD,

  APPE_CMD,

  NO_CMD,

};

//--------------------------------------------------

Данное перечисление я позавидовал из примера WIZNET-а.

Также ниже добавим структуру свойств FTP

//--------------------------------------------------

typedef struct ftp_prop {

  volatile uint8_t connect_stat;//статус состояния управляющего соеднинения FTP

  volatile uint8_t connect_stat_data;//статус состояния соеднинения для данных FTP

  volatile uint8_t login_stat;//статус авторизации FTP

  volatile uint8_t ftp_type;// Transfer type

  volatile uint8_t datasock_mode;// режим соединения

  volatile uint8_t datasock_state;// состояние соединения для данных

  volatile uint8_t data_stat;//статус передачи данных

  volatile uint16_t rem_bytes_dirinfo;//кличество непереданных байтов информации о каталоге

  volatile uint32_t rem_filesize;//количество непереданных байтов файла

  enum ftp_cmd current_cmd;

  char username[100];

  char work_dir[100];

  char filename[100];

  FIL my_fil;

} ftp_prop_ptr;

//--------------------------------------------------

Здесь, надеюсь, также назначение полей понятно. Что могло быть непонятным, напротив этого я написал комментарии. Также если и с этим что-то не ясно, то разберёмся во время использования данных полей.

Тажке после объявления структуры добавим несколько служебных макросов, которые нам пригодятся впоследствии

} ftp_prop_ptr;

//--------------------------------------------------

#define FTP_DISCONNECT 0

#define FTP_CONNECT 1

//--------------------------------------------------

#define FTPS_NOT_LOGIN 0

#define FTPS_LOGIN 1

//--------------------------------------------------

#define ASCII_TYPE 0

#define IMAGE_TYPE 1

#define LOGICAL_TYPE 2

//--------------------------------------------------

#define DATASOCK_IDLE 0

#define DATASOCK_READY 1

#define DATASOCK_START 2

//--------------------------------------------------

#define PASSIVE_MODE 0

#define ACTIVE_MODE 1

//--------------------------------------------------

В файле ftpd.c создадим переменную типа нашей структуры

extern uint8_t ipaddr[4];

ftp_prop_ptr ftpprop;

Вернёмся в функцию ftp_receive и создадим там переменную сектора для записи в буфер в теле условия

if(sn == FTP_SOCKET_CTRL)

{

  data_sect_ptr *datasect = (void*)sect;

}

Также в этом же теле создадим ещё одно условие проверки на факт созданного соединения

data_sect_ptr *datasect = (void*)sect;

if(!ftpprop.connect_stat)

{

}

else

{

}

В основном теле (если соединение ещё не произошло) проинициализируем текущий каталог. Пока это будет корневой каталог

if(!ftpprop.connect_stat)

{

  strcpy(ftpprop.work_dir, "/");

}

Перейдём в файл ftpd.h и добавим там два макроса — номера версии сервера и имени сервера

#include "w5500.h"

//--------------------------------------------------

#define HOSTNAME "w5500Chip"

#define VERSION "1.0"

//--------------------------------------------------

Вернёмся в функцию ftp_receive файла ftpd.c и в том же теле условия отсутствия соединения занесём в буфер сообщение вместе с именем и версией сервера, которую должен ответить наш сервер клиенту на попытку соединения с 21 портом

strcpy(ftpprop.work_dir, "/");

sprintf((char*)datasect->data, "220 %s FTP version %s ready.rn", HOSTNAME, VERSION);

Далее мы должны данное сообщение отправить серверу. Для этого у нас пока ещё нет функции. Давайте её создадим чуть выше

extern volatile uint16_t tcp_size_wnd;

//-----------------------------------------------

void tcp_send_ftp_one(uint8_t sn, uint8_t * buf, uint16_t len)

{

  uint16_t end_point;

  end_point = GetWritePointer(sn);

  end_point+=len;

  //Заполним данными буфер для отправки пакета

  SetWritePointer(sn, end_point);

  end_point = GetWritePointer(sn);

  w5500_writeSockBuf(sn, end_point, buf, len);

  //отправим данные

  RecvSocket(sn);

  SendSocket(sn);

}

//-----------------------------------------------

Данная функция очень похожа на функцию отправки однобуферного пакета HTTP, поэтому рассказывать тут нечего, она даже намного проще. Мы здесь ничего не читаем с карты.

Вернёмся в функцию ftp_receive в то же тело, где мы только что были, и передадим сообщение клиенту, а затем установим состояние «соединено»

sprintf((char*)datasect->data, "220 %s FTP version %s ready.rn", HOSTNAME, VERSION);

tcp_send_ftp_one(sn, (uint8_t *)sect, strlen((char*)datasect->data));

ftpprop.connect_stat = FTP_CONNECT;

Давайте соберём наш код, прошьём контроллер и посмотрим, вернётся ли наше сообщение при попытке соединиться с нашим сервером клиента, и посмотрим, какую команду нам затем пошлёт клиент.

В качестве сервера FTP я использовал файл-менеджер Total Commander, которым я пользуюсь давно для различных файловых операций на ПК. Имя пользователя и пароль мы будем исопльзовать для анонимного соединения, для этого есть специальный логин, а в качестве пароля используется любой адрес электронной почты. Total Commander у меня версии 6.51. Это довольно-таки старая версия, но меня устраивает.

Также запустим и WireShark.

В Total Commander вызовем пункт меню FTP -> Соединиться с FTP-сервером, в открывшемся диалоге выберем «Добавить» и заполним следующие поля, не забывая также по галочку для пассивного режима, так как мы используем именно такой режим

Image02

Также нажмём на кнопочку «Анонимное соединение…» и заполним какой-нибудь адрес электронной почты (я заполнил адрес давно не существующий)

Image03

Соглашаемся, и в диалоге с учетными данными тоже соглашаемся (жмём «ОК»).

У нас появится учетная запись FTP. Жмём соединиться, а затем жмём «Прервать», так как соединения мы никакого не дождёмся, до него ещё далеко. Нам важен процесс

Image04

Идём в Wireshark и смотрим, что там происходит (по FTP там не фильтруемся, иначе мы увидим только пакеты FTP, а пакеты на создание соединения и прочие служебные пакеты мы не увидим) (нажмите на картинку для увеличения изображения)

Image05_0500

Мы видим, что наше сообщение о сервере клиенту пришло, что оно распозналось как FTP и клиент нам послал команду USER с именем пользователя, на которую мы также должны теперь сформировать ответ.

В следующей части занятия мы напишем функцию разбора строки команды FTP, а также обработаем несколько команд для адкеватного ответа на них клиенту.

 

 

Предыдущий урок Программирование МК STM32 Следующая часть

 

 

Отладочную плату можно приобрести здесь Nucleo STM32F401RE

Ethernet LAN Сетевой Модуль можно купить здесь W5500 Ethernet LAN

Переходник USB to TTL можно приобрести здесь ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

STM LAN. W5500. FTP Server

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*