STM Урок 82. LAN. ENC28J60. Удалённый доступ

 

 

 

 

Урок 82

 

LAN. ENC28J60. Удалённый доступ

 

 

До сих пор мы при помощи нашего модуля ENC28J60 могли соединяться, принимать и передавать данные только с узлов, находящихся в локальной сети. Я думаю, что настал тот момент, когда мы должны попробовать с помощью него "пообщаться" с сетью внешней — с сетью "Интернет". Также внешняя сеть может быть не только интернетом, но и какой-то другой сетью. Самое главное то, что мы будем уже обращаться к узлам, находящимся за пределами нашего маршрутизатора. Я думаю, все знают, каки образом происходит маршрутизация и опрос узлов, находящихся вне сети, ну или может узнать.. Данной информации очень предостаточно на просторах глобальной сети. А вот как соединиться с ними при помощи нашей микросхемы и как это организовать в коде — знают не все, Хотя информация все равно есть, но везде разная. Я хочу дать свой вариант ответа на этот вопрос.

Тем не менее, я немного все равно поведаю о том, как происходит опрос внешней сети. Например, мы назначили нашему модулю адрес IP 192.168.1.197, а подключен он в сеть с маской 255.255.255.0, у маршрутизатора адрес 192.168.1.1. В этом случае, если мы обратимся к устройству в нашей сети, имеющему адрес от 192.168.1.1 до адреса 192.168.1.254, то будет считаться, что мы обратились к локальному сетевому устройству и мы имеем полное право знать его MAC-адрес, узнать который мы можем, послав ARP-запрос. А если устройство будет иметь IP-адрес, отличный от вышеуказанных (например 8.143.111.23), то будет считаться, что мы уже обратились к устройству внешней сети и маршрутизатор будет обязан узнать маршрут к данному устройству и предоставить нам определённый к нему доступ. Только MAC-адрес внешнего устройства маршрутизатор нам сообщать уже не будет обязан, поэтому на канальном уровне мы будем работать уже с MAC-адресом маршрутизатора. Поэтому мы должны организовать наш код так, чтобы этот код сначала выяснил, что устройство находится за пределами нашей сети и уже после этого выяснения вернул нам MAC-адрес маршрутизатора, или как принято называть — шлюза или гейта (Gateway), Кратко вообщем вот так.

Конечная цель нашего сегодняшнего урока — послать ICMP-запрос или PING внешнему узлу и дождаться от него ответа, тем самым подтвердить, что мы можем с этим узлом нормально общаться на разных сетевых уровнях.

Поэтому давайте сразу же от теории перейдём к делу.

Создадим проект с именем ENC28J60_REMOTE, переработав его из проекта прошлого занятия с именем ENC28J60_UDPC.

Запустим проект в генераторе Cube MX и, ничего там не трогая, сгенерируем проект для среды программирования Keil, откроем его, произведём настройки программатора на автоперезагрузку, а также подключим наши библиотеки в дерево проекта.

Первым делом в файле net.h добавим две макроподстановки для маски подсети и адреса маршрутизатора (шлюза). У Вас они могут отличаться от моих. Думаю, свои сетевые параметры вы знаете, если не знаете, то вы их можете увидеть в свойствах ваших сетевых адаптеров. Только это будет иметь место в случае использования статической адресации, в случае использования динамической адресации (DHCP), можно всё это узнать, введя команду ipconfig в командной строке

 

Image00

 

Прочитав свои параметры, добавим макросы в вышеуказанное место

 

#include "enc28j60.h"

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

#define IP_ADDR {192,168,1,197}

#define IP_GATE {192,168,1,1}

#define IP_MASK {255,255,255,0}

 

Также добавим глобальные массивы для наших сетевых адресов в файле net.c

 

uint8_t ipaddr[4]=IP_ADDR;

uint8_t ipgate[4]=IP_GATE;

uint8_t ipmask[4]=IP_MASK;

 

Затем подключим эти массивы в файле arp.c

 

extern uint8_t ipaddr[4];

extern uint8_t ipgate[4];

extern uint8_t ipmask[4];

 

Далее при попытке послать запрос ARP сетевому узлу мы обязаны определить, к какой сети данный узел принадлежит — к локальной или внешней (удалённой). Поэтому перейдём в файл arp.c и в функции arp_request сначала добавим локальный массив для хранения IP-адреса, по которому мы будем определять MAC-адрес с помощью ARP-запроса, а также целочисленную переменную, которая будет аккумулировать в себе результаты логических операций над адресами и маской

 

uint8_t arp_request(uint8_t *ip_addr)

{

  uint8_t i, j;

  uint8_t ip[4];

  uint8_t iptemp = 0;

 

Затем уже с помощью нехитрого цикла определим принадлежность к локальной сети

 

uint8_t iptemp = 0;

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

{

  iptemp += (ip_addr[i] ^ ipaddr[i]) & ipmask[i];

}

 

А затем добавим следующее условие

 

enc28j60_frame_ptr *frame=(void*)net_buf;

//проверим принадлежность адреса к локальной сети

if( iptemp == 0 ) memcpy(ip,ip_addr,4);

else memcpy(ip,ipgate,4);

 

То есть, если условие не подтвердится, то есть адрес будет не локальный, то мы будем просить MAC-адрес у маршрутизатора и все дальнейшие пакеты, предназначенные для узла мы будем отправлять на физический адрес маршрутизатора (или роутера).

Соотвественно, в дальнейшем коде в теле функции мы исправим использование массива на наш локальный

 

if(!memcmp(arp_rec[j].ipaddr,ip,4))

...

memcpy(msg->ipaddr_dst,ip,4);

 

Давайте соберём код, прошьём контроллер и проверим наш код, послав в терминальной программе сначала запрос по IP-адресу сначала в локальную сеть, а затем в удалённую. Для удалённой сети будем использовать для примера IP-адрес сайта "www.yandex.ru", узнать который мы можем, пропинговав его в командной строке

 

Image00

 

Вот что мы должны увидеть в терминальной программе после посылки ARP-запросов

 

Image01

 

Судя по отчёту в программе, мы всё сделали правильно и на внешний IP-адрес наш роутер нам вернул свой MAC-адрес.

 

 

Хорошо. Часть дела сделана. Теперь наша задача добиться того, чтобы не маршрутизатор, а внешний узел нам сам что-то вернул. Поэтому давайте попробуем его "пропинговать", послав ему ICMP-запрос, ну или несколько. Мы этого пока не делали, мы только отвечали на такие запросы, поэтому нам нужна будет отдельная функция, которую мы добавим в файле net.c после функции ip_read

 

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

uint8_t icmp_request(uint8_t* ip_addr)

{

  uint8_t res=0;

  return res;

}

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

 

Также, чтобы контролировать работу данной функции в процессе написания в её тело кода, надо её где-то вызвать. Для этого мы в обработчик от USART  добавим ещё одно условие, с помощью которого мы будем посылать ICMP запросы. Ослеживаться они будут с помощью встретившегося посе строки с ip-адресом символа 'p' от слова "ping"

 

  net_cmd();

}

else if (b=='p')

{

  usartprop.is_ip=4;//статус попытки отправить ICMP-пакет

  net_cmd();

}

 

 

Вернёмся в файл net.c, только уже в функцию net_cmd и по аналогии с посылкой запроса UDP, добавим посылку запроса ICMP

 

    usartprop.is_ip=0;

  }

  else if(usartprop.is_ip==4)//статус попытки отправить ICMP-пакет

  {

    ip_extract((char*)usartprop.usart_buf,usartprop.usart_cnt,ip);

    usartprop.is_ip=5;//статус отправки ICMP-пакета

    usartprop.usart_cnt=0;

    arp_request(ip);//узнаем mac-адрес

  }

  else if(usartprop.is_ip==5)//статус отправки ICMP-пакета

  {

    icmp_request(ip);

    usartprop.is_ip=0;

  }

}

 

Таким же образом, как обычно, мы сначала посылаем ARP-запрос, затем возвращаемся сюда со следующим статусом по факту ARP-ответа либо при обнаружении готовой пары адресов в таблице ARP. Поэтому мы должны также добавить новый статус в местах возврата в функцию net_cmd.

Сначала в функции eth_read, а затем в фунции arp_request в файле arp.c

 

if((usartprop.is_ip==3)||(usartprop.is_ip==5))//статус отправки UDP- или ICMP-пакета

 

Ну и, возвратившись теперь не с пустыми руками в функцию net_cmd, мы смело посылаем запрос ICMP, вызвав соответствующую функцию, код тела которой мы сейчас и продолжим писать.

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

 

char str1[60]={0};

uint32_t ping_cnt=0;//счетчик отправленных пингов

 

Вернёмся в функцию icmp_request и сначала, как водится, создадим переменную для рассчёта длины пакета, а затем расставим указатели на заголовки пакетов в соответствующие им места

 

uint8_t res=0;

uint16_t len;

enc28j60_frame_ptr *frame=(void*) net_buf;

ip_pkt_ptr *ip_pkt = (void*)(frame->data);

icmp_pkt_ptr *icmp_pkt = (void*)ip_pkt->data;

 

Заполним ICMP-заголовок, занеся нужные значения в его поля, не забывая также инкрементировать номера пакетов, а также про строку данных, которая имеет определённый состав, который можно прочитать в утилите анализа сетевого трафика WireShark в составе любого пакета ICMP

 

icmp_pkt_ptr *icmp_pkt = (void*)ip_pkt->data;

//Заполним заголовок пакета ICMP

icmp_pkt->msg_tp = 8;

icmp_pkt->msg_cd = 0;

icmp_pkt->id = be16toword(1);

icmp_pkt->num = be16toword(ping_cnt);

ping_cnt++;

strcpy((char*)icmp_pkt->data,"abcdefghijklmnopqrstuvwabcdefghi");

icmp_pkt->cs = 0;

len = strlen((char*)icmp_pkt->data) + sizeof(icmp_pkt_ptr);

icmp_pkt->cs=checksum((void*)icmp_pkt,len,0);

 

Далее по иерархии заполняем заголовок IP

 

icmp_pkt->cs=checksum((void*)icmp_pkt,len,0);

//Заполним заголовок пакета IP

len+=sizeof(ip_pkt_ptr);

ip_pkt->len=be16toword(len);

ip_pkt->id = 0;

ip_pkt->ts = 0;

ip_pkt->verlen = 0x45;

ip_pkt->fl_frg_of=0;

ip_pkt->ttl=128;

ip_pkt->cs = 0;

ip_pkt->prt=IP_ICMP;

memcpy(ip_pkt->ipaddr_dst,ip_addr,4);

memcpy(ip_pkt->ipaddr_src,ipaddr,4);

ip_pkt->cs = checksum((void*)ip_pkt,sizeof(ip_pkt_ptr),0);

 

И, по окончанию — заголовок пакета Ethernet, который затем отправляем в сеть

 

  ip_pkt->cs = checksum((void*)ip_pkt,sizeof(ip_pkt_ptr),0);

  //Заполним заголовок пакета Ethernet

  memcpy(frame->addr_src,macaddr,6);

  frame->type=ETH_IP;

  enc28j60_packetSend((void*)frame,len + sizeof(enc28j60_frame_ptr));

  return res;

}

 

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

Соберём код, прошьём контроллер, и пока попробуем отправить пинг нашему компьютеру, так как если мы его отправим во внешнюю сеть, толку от этого не будет, так как мы не увидим ответа, а послав компьютеру, мы его увидим хотя бы в ути лите WireShark. Пошлём несколько запросов ICMP в терминальной программе

 

Image03

 

и посмтрим результат в утилите (нажмите на картинку для увеличения изображения)

 

Image02_0500

 

Мы видим, что все наши пакеты пришли и на все на них компьютер отправил ответ.

Следующая задача — ответ этот увидеть в терминальной программе, ну и, конечно не только этот, а и ответы от удалённых узлов.

Для этого перейдём в функцию icmp_read и отделим два условия друг от друга в теле функции

 

if(len>=sizeof(icmp_pkt_ptr))

{

  if(icmp_pkt->msg_tp==ICMP_REQ)

  {

    icmp_pkt->msg_tp=ICMP_REPLY;

 

Добавим теперь в нижнем уровне вложенности условий ещё одно условие, которое будет реагировать на другой тип пакета — на PING-ответ

 

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

}

else if (icmp_pkt->msg_tp==ICMP_REPLY)

{

  sprintf(str1,"%d.%d.%d.%d-%d.%d.%d.%d icmp reply\r\n",

  ip_pkt->ipaddr_src[0],ip_pkt->ipaddr_src[1],ip_pkt->ipaddr_src[2],ip_pkt->ipaddr_src[3],

  ip_pkt->ipaddr_dst[0],ip_pkt->ipaddr_dst[1],ip_pkt->ipaddr_dst[2],ip_pkt->ipaddr_dst[3]);

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

}

 

Вот теперь мы ответ должны будем увидеть в нашей терминальной программе.

Поэтому соберём наш код, прошьём наш контроллер и пошлём сначала несколько пингов локальному узлу, а затем несколько запросов — глобальному. Несколько потому, что мы должны убедиться, что это всё рабоатет постоянно и независимо от того, посылаем ли мы запрос ARP или извлекаем готовый физический адрес из таблицы. Мы должны будем увидеть вот такие ответы в окне терминальной программы

 

Image03

 

Как мы можем наблюдать, пакеты к нам поступают как с локальных IP-адресов, так и с глобальных.

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

 

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

 

 

Исходный код

 

 

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

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

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

 

 

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

 

STM LAN. ENC28J60. Удалённый доступ

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

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

*