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 2.0

Ethernet LAN Сетевой Модуль можно купить здесь (модуль SD SPI в подарок) ENC28J60 Ethernet LAN Сетевой Модуль.

 

 

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

 

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

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

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

*