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



 

Урок 46

 

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

 

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

Тем не менее, я немного все равно поведаю о том, как происходит опрос внешней сети. Например, мы назначили нашему модулю адрес IP 192.168.1.193, а подключен он в сеть с маской 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.

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

 

Image00

 

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

 

#define IP_ADDR ip_join(192,168,1,193)

#define IP_GATE ip_join(192,168,1,1)

#define IP_MASK ip_join(255,255,255,0)

 

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

 

uint8_t arp_request(uint32_t ip_addr)

{

  uint8_t i, j;

  uint32_t ip;

 

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

 

enc28j60_frame_ptr *frame=(void*) net_buf;

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

if( ((ip_addr ^ IP_ADDR) & IP_MASK) == 0 ) ip=ip_addr;

else ip = IP_GATE;

 

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

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

 

if (arp_rec[j].ipaddr==ip)

...

msg->ipaddr_dst = ip;

 

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

 

Image01

 

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

 

Image02

 

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

 

 

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

 

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

uint8_t icmp_request(uint32_t ip_addr)

{

  uint8_t res=0;

  return res;

}

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

 

Также, чтобы контролировать работу данной функции в процессе написания в её тело кода, надо её где-то вызвать. Для этого мы в обработчик от USART в файле usart.c добавим ещё одно условие, с помощью которого мы будем посылать 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=ip_extract((char*)usartprop.usart_buf,usartprop.usart_cnt);

    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;

ip_pkt->ipaddr_dst = ip_addr;

ip_pkt->ipaddr_src = IP_ADDR;

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

 

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

 

Image04_0500

 

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

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

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

 

//Отфильтруем пакет по длине и типу сообщения - эхо-запрос

if (len>=sizeof(icmp_pkt_ptr))

{

  if (icmp_pkt->msg_tp==ICMP_REQ)

  {

    icmp_pkt->msg_tp=ICMP_REPLY;

 

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

 

      USART_TX((uint8_t*)str1,strlen(str1));

    }

    else if (icmp_pkt->msg_tp==ICMP_REPLY)

    {

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

      ip_pkt->ipaddr_src & 0x000000FF,(ip_pkt->ipaddr_src>>8) & 0x000000FF,

      (ip_pkt->ipaddr_src>>16) & 0x000000FF, ip_pkt->ipaddr_src>>24,

      ip_pkt->ipaddr_dst & 0x000000FF,(ip_pkt->ipaddr_dst>>8) & 0x000000FF,

      (ip_pkt->ipaddr_dst>>16) & 0x000000FF, ip_pkt->ipaddr_dst>>24);

    USART_TX((uint8_t*)str1,strlen(str1));

    }

  }

  return res;

}

 

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

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

 

Image05

Image06

 

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

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

 

 

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

 

Исходный код

 

 

Приобрести плату Arduino UNO R3 можно здесь.

Приобрести программатор USBASP USBISP с адаптером можно здесь USBASP USBISP 3.3 с адаптером

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

 

 

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

 

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

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

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

*