Подключение микроконтроллера к локальной сети: Широковещательные сообщения и DHCP

В этой части мы поговрим про широковещательные сообщения и, наконец-то, закончим с UDP.

Краткое содержание:

  • Широковещательные сообщения
  • Отправка и приём
  • DHCP
  • Заключение

Широковещательные сообщения


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

Для широковещательных пакетов используется последний адрес подсети. Скажем, для подсети 192.168.0.0 с маской 255.255.255.0 широковещательный адрес — 192.168.0.255.

Широковещательный адрес

Пакет, отправленный на этот адрес придёт всем узлам, подключенным к подсети и поддерживающим широковещательные пакеты.

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

Также, существует «широковещательный адрес для всего интернета» 255.255.255.255. Но чаще всего, этот адрес просто считается невалидным. В форточках он даже юзается в качестве индикатора ошибки (в возвращаемых значениях, etc.)

Пишем поддержку широковещательных сообщений


Чтобы научить наш стек отправлять и принимать широковещательные сообщения, достаточно дописать всего несколько строчек кода.

Во-первых, определим сам широковещательный адрес для нашей сети:

#define ip_broadcast (ip_addr | ~ip_mask)


Отправка пакетов:

// Отправка IP-пакета
uint8_t ip_send(eth_frame_t *frame, uint16_t len)
{
    ip_packet_t *ip = (void*)(frame->data);
    uint32_t route_ip;
    uint8_t *mac_addr_to;

    // Вот здесь мы допишем:
    //  если адрес получателя - широковещательный
    //  адрес нашей подсети, то юзаем широковещательный MAC
    if(ip->to_addr == ip_broadcast)
    {
        // use broadcast MAC
        memset(frame->to_addr, 0xff, 6);
    }
    
    // Иначе, делаем всё как обычно
    else
    {
        // Смотрим, уходит ли пакет за пределы сети
        //  если так - пересылаем его гейту
        if( ((ip->to_addr ^ ip_addr) & ip_mask) == 0 )
            route_ip = ip->to_addr;
        else
            route_ip = ip_gateway;

        // Определяем MAC-адрес узла
        if(!(mac_addr_to = arp_resolve(route_ip)))
            return 0;
        memcpy(frame->to_addr, mac_addr_to, 6);
    }

    // Отправляем IP-пакет
    frame->type = ETH_TYPE_IP;

    len += sizeof(ip_packet_t);

    ip->ver_head_len = 0x45;
    ip->tos = 0;
    ip->total_len = htons(len);
    ip->fragment_id = 0;
    ip->flags_framgent_offset = 0;
    ip->ttl = IP_PACKET_TTL;
    ip->cksum = 0;
    ip->from_addr = ip_addr;
    ip->cksum = ip_cksum(0, (void*)ip, sizeof(ip_packet_t));

    eth_send(frame, len);

    return 1;
}


Приём пакетов:

// Обработчик приходящих IP-пакетов
void ip_filter(eth_frame_t *frame, uint16_t len)
{
    uint16_t hcs;
    ip_packet_t *packet = (void*)(frame->data);
    
    //if(len >= sizeof(ip_packet_t))
    //{
        // Здесь тоже мало изменилось
        //  Добавили "|| (packet->to_addr == ip_broadcast)",
        //  чтобы также принимать широковещательные пакеты
        hcs = packet->cksum;
        packet->cksum = 0;

        if( (packet->ver_head_len == 0x45) &&
            (ip_cksum(0, (void*)packet, sizeof(ip_packet_t)) == hcs) &&
            ((packet->to_addr == ip_addr) || (packet->to_addr == ip_broadcast)) )
        {
            len = ntohs(packet->total_len) - 
                sizeof(ip_packet_t);

            switch(packet->protocol)
            {
            case IP_PROTOCOL_ICMP:
                icmp_filter(frame, len);
                break;

            case IP_PROTOCOL_UDP:
                udp_filter(frame, len);
                break;
            }
        }
    //}
}


Теперь можно посылать широковещательный пинг и смотреть как девайс(ы) на него отзываются)

Широковещательный пинг

DHCP


До этого момента мы прописывали IP-адрес и другие параметры, нужные для работы в сети в явном виде. Что очень неудобно — требуется отдельно конфигурировать каждый девайс перед использованием. Совершенно не годится если ты захочешь, например, подарить кому-нибудь готовый девайс.

К счастью, существует специальный протокол DHCP (Dynamic Host Configuration Protocol), основанный на широковещательных сообщениях, позволяющий узлу все нужные для конфигурирования самого себя данные получить с работающего в сети DHCP-сервера.

DHCP-сервер есть почти во всех сетях. В домашних роутерах он, как правило, включен по умолчанию, etc. То, что нужно!

Вот, в кратце, алгоритм получения узлом конфигурационных данных:

  • Узел посылает широковещательное сообщение DISCOVER, на которое откликаются все имеющиеся в сети DHCP-серверы.
  • Узел получает от каждого сервера сообщение OFFER, в котором содержится адрес и другие параметры, которые сервер предлагает узлу для использования.
  • Узел выбирает сервер который ему нравится и отправляет сообщение REQUEST.
  • Сервер возвращает узлу сообщение ACK для подтверждения.

IP-адрес выдаётся узлу не навсегда — узел должен время от времени повторять запрос REQUEST. Этот интервал и время на которое выдаётся IP-адрес, указываются сервером.

Если DHCP-сервер вдруг перестанет работать, узел может попробовать найти другой DHCP-сервер и запросить у него такой же адрес.

Если срок использования IP-адреса истечёт (и продлить его не удасться), узел возвращается в исходное состояние и пробует снова найти DHCP-сервер и выделить адрес.

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

Формат пакета DHCP:

DHCP-пакет

DHCP является расширенной версией протокола загрузки по сети BOOTP, и формат пакета в общем-то унсаледован от него. Так что почти вся полезная информация лежит в опциях.

DHCP-опция

Между опциями могут вставляться нули для выравнивания опций на 2 байта. После всех опций вставляется байт 0xff — индикатор конца поля.

В общем, всё понятно из кода

// Порт DHCP-сервера
#define DHCP_SERVER_PORT        htons(67)
// Порт DHCP-клиента
#define DHCP_CLIENT_PORT        htons(68)

// Тип операции (запрос/ответ)
#define DHCP_OP_REQUEST            1
#define DHCP_OP_REPLY            2

// Тип аппаратного адреса (Ethernet)
#define DHCP_HW_ADDR_TYPE_ETH    1

// Флаг - широковещательный пакет
//  (если флаг установлен, ответ 
//  тоже будет широковещательным)
#define DHCP_FLAG_BROADCAST        htons(0x8000)

// Волшебная кука
#define DHCP_MAGIC_COOKIE        htonl(0x63825363)

// Формат DHCP-сообщения
typedef struct dhcp_message {
    uint8_t operation; // Операция (запрос или ответ)
    uint8_t hw_addr_type; // Тип адреса = 1 (Ethernet)
    uint8_t hw_addr_len; // Длина адреса = 6 (MAC-адрес)
    uint8_t unused1; // Не юзаем
    uint32_t transaction_id; // Идентификатор транзакции
    uint16_t second_count; // Время с начала операции
    uint16_t flags; // Флаги
    uint32_t client_addr; // Адрес клиента (при обновлении)
    uint32_t offered_addr; // Предложенный адрес
    uint32_t server_addr; // Адрес сервера
    uint32_t unused2; // Не юзаем
    uint8_t hw_addr[16]; // Наш MAC-адрес
    uint8_t unused3[192]; // Не юзаем
    uint32_t magic_cookie; // Волшебная кука
    uint8_t options[]; // Опции
} dhcp_message_t;

// Коды опций
#define DHCP_CODE_PAD            0        // Выравнивание
#define DHCP_CODE_END            255        // Конец
#define DHCP_CODE_SUBNETMASK    1        // Маска подсети
#define DHCP_CODE_GATEWAY        3        // Гейт
#define DHCP_CODE_REQUESTEDADDR    50        // Выбранный IP-адрес
#define DHCP_CODE_LEASETIME        51        // Время аренды адреса
#define DHCP_CODE_MESSAGETYPE    53        // Тип сообщения
#define DHCP_CODE_DHCPSERVER    54        // DHCP-сервер
#define DHCP_CODE_RENEWTIME        58        // Время до обновления адреса
#define DHCP_CODE_REBINDTIME    59        // Время до выбора другого сервера

// DHCP-опция
typedef struct dhcp_option {
    uint8_t code;
    uint8_t len;
    uint8_t data[];
} dhcp_option_t;

// Тип сообщения
#define DHCP_MESSAGE_DISCOVER    1    // Поиск
#define DHCP_MESSAGE_OFFER        2    // Предложение адреса
#define DHCP_MESSAGE_REQUEST    3    // Запрос адреса
#define DHCP_MESSAGE_ACK        5    // Подтверждение

// Состояние DHCP
typedef enum dhcp_status_code {
    DHCP_INIT, // Адрес не выделен
    DHCP_ASSIGNED, // Адрес выделен
    DHCP_WAITING_OFFER, // Ждём ответа сервера
    DHCP_WAITING_ACK // Ждём подтверждение от сервера
} dhcp_status_code_t;

// Состояние DHCP
dhcp_status_code_t dhcp_status; // стейт
static uint32_t dhcp_server; // адрес выбранного сервера
static uint32_t dhcp_renew_time; // время обновления
static uint32_t dhcp_retry_time; // время повторной попытки получения адреса
static uint32_t dhcp_transaction_id; // текущая транзакция

// Эта штука добавляет опцию в пакет и 
//  сдвигает указатель к концу опции
//   ptr - указатель на начало опции
#define dhcp_add_option(ptr, optcode, type, value) \
    ((dhcp_option_t*)ptr)->code = optcode; \
    ((dhcp_option_t*)ptr)->len = sizeof(type); \
    *(type*)(((dhcp_option_t*)ptr)->data) = value; \
    ptr += sizeof(dhcp_option_t) + sizeof(type); \
    if(sizeof(type)&1) *(ptr++) = 0; // добавляем пад

// Обработчик DHCP-пакета
void dhcp_filter(eth_frame_t *frame, uint16_t len)
{
    ip_packet_t *ip = (void*)(frame->data);
    udp_packet_t *udp = (void*)(ip->data);
    dhcp_message_t *dhcp = (void*)(udp->data);
    dhcp_option_t *option;
    uint8_t *op, optlen;
    uint32_t offered_net_mask = 0, offered_gateway = 0;
    uint32_t lease_time = 0, renew_time = 0, renew_server = 0;
    uint8_t type = 0;
    uint32_t temp;

    // Проверяем, что пакет отправлен нам
    if( (len >= sizeof(dhcp_message_t)) &&
        (dhcp->operation == DHCP_OP_REPLY) &&
        (dhcp->transaction_id == dhcp_transaction_id) &&
        (dhcp->magic_cookie == DHCP_MAGIC_COOKIE) )
    {
        len -= sizeof(dhcp_message_t);

        // Парсим опции в пакете
        op = dhcp->options;
        while(len >= sizeof(dhcp_option_t))
        {
            option = (void*)op;
            
            // Пад - пропускаем
            if(option->code == DHCP_CODE_PAD)
            {
                op++;
                len--;
            }
            
            // Конец - заканчиваем парсить
            else if(option->code == DHCP_CODE_END)
            {
                break;
            }
            
            // Обычная опция
            else
            {
                switch(option->code)
                {
                case DHCP_CODE_MESSAGETYPE:
                    type = *(option->data);
                    break;
                case DHCP_CODE_SUBNETMASK:
                    offered_net_mask = *(uint32_t*)(option->data);
                    break;
                case DHCP_CODE_GATEWAY:
                    offered_gateway = *(uint32_t*)(option->data);
                    break;
                case DHCP_CODE_DHCPSERVER:
                    renew_server = *(uint32_t*)(option->data);
                    break;
                case DHCP_CODE_LEASETIME:
                    temp = *(uint32_t*)(option->data);
                    lease_time = ntohl(temp);
                    break;
                case DHCP_CODE_RENEWTIME:
                    temp = *(uint32_t*)(option->data);
                    renew_time = ntohl(temp);
                    break;
                }

                optlen = sizeof(dhcp_option_t) + option->len;
                op += optlen;
                len -= optlen;
            }
        }

        switch(type)
        {
        // Получили OFFER (предложение IP-адреса)?
        case DHCP_MESSAGE_OFFER:
            if(dhcp_status == DHCP_WAITING_OFFER)
            {
                // Отправляем запрос на выделение адреса
                ip->to_addr = inet_addr(255,255,255,255);

                udp->to_port = DHCP_SERVER_PORT;
                udp->from_port = DHCP_CLIENT_PORT;

                op = dhcp->options;
                dhcp_add_option(op, DHCP_CODE_MESSAGETYPE, 
                    uint8_t, DHCP_MESSAGE_REQUEST);
                dhcp_add_option(op, DHCP_CODE_REQUESTEDADDR, 
                    uint32_t, dhcp->offered_addr);
                dhcp_add_option(op, DHCP_CODE_DHCPSERVER,
                    uint32_t, dhcp->server_addr);
                *(op++) = DHCP_CODE_END;

                dhcp->operation = DHCP_OP_REQUEST;
                dhcp->offered_addr = 0;
                dhcp->server_addr = 0;
                dhcp->flags = DHCP_FLAG_BROADCAST;

                udp_send(frame, (uint8_t*)op - (uint8_t*)dhcp);

                // Ждём подтверждения
                dhcp_status = DHCP_WAITING_ACK;
            }
            break;

        // Получили подтверждение?
        case DHCP_MESSAGE_ACK:
            if(dhcp_status == DHCP_WAITING_ACK)
            {
                // Устанавливаем время и сервер обновления
                if(!renew_time)
                    renew_time = lease_time/2;

                dhcp_server = renew_server;
                dhcp_renew_time = rtime() + renew_time;
                dhcp_retry_time = rtime() + lease_time;

                // Конфигурируем наш хост
                ip_addr = dhcp->offered_addr;
                ip_mask = offered_net_mask;
                ip_gateway = offered_gateway;
                
                // Всё готово
                dhcp_status = DHCP_ASSIGNED;
            }
            break;
        }
    }
}

// Вызывается периодически
void dhcp_poll()
{
    eth_frame_t *frame = (void*)net_buf;
    ip_packet_t *ip = (void*)(frame->data);
    udp_packet_t *udp = (void*)(ip->data);
    dhcp_message_t *dhcp = (void*)(udp->data);
    uint8_t *op;

    // Истечение аренды адреса или 
    //   время для новой попытки получения адреса
    if(rtime() >= dhcp_retry_time)
    {
        // Придумываем идентификатор транзакции
        dhcp_transaction_id = gettc() | (gettc() << 16);

        // Опускаем интерфейс (если истекла аренда)
        ip_addr = 0;
        ip_mask = 0;
        ip_gateway = 0;

        // Отправляем DISCOVER (поиск сервера)
        ip->to_addr = inet_addr(255,255,255,255);
        
        udp->to_port = DHCP_SERVER_PORT;
        udp->from_port = DHCP_CLIENT_PORT;
        
        memset(dhcp, 0, sizeof(dhcp_message_t));
        dhcp->operation = DHCP_OP_REQUEST;
        dhcp->hw_addr_type = DHCP_HW_ADDR_TYPE_ETH;
        dhcp->hw_addr_len = 6;
        dhcp->transaction_id = dhcp_transaction_id;
        dhcp->flags = DHCP_FLAG_BROADCAST;
        memcpy(dhcp->hw_addr, mac_addr, 6);
        dhcp->magic_cookie = DHCP_MAGIC_COOKIE;
        
        op = dhcp->options;
        dhcp_add_option(op, DHCP_CODE_MESSAGETYPE, 
            uint8_t, DHCP_MESSAGE_DISCOVER);
        *(op++) = DHCP_CODE_END;
        
        udp_send(frame, (uint8_t*)op - (uint8_t*)dhcp);

        // Ждём ответа (если не будет, снова 
        //  попробуем через 15 секунд)
        dhcp_status = DHCP_WAITING_OFFER;
        dhcp_retry_time = rtime() + 15;
    }

    // Обновление адреса
    if( (rtime() >= dhcp_renew_time) &&
        (dhcp_status == DHCP_ASSIGNED) )
    {
        // Придумываем идентификатор транзакции
        dhcp_transaction_id = gettc() | (gettc() << 16);

        // Отправляем запрос
        ip->to_addr = dhcp_server;

        udp->to_port = DHCP_SERVER_PORT;
        udp->from_port = DHCP_CLIENT_PORT;

        memset(dhcp, 0, sizeof(dhcp_message_t));
        dhcp->operation = DHCP_OP_REQUEST;
        dhcp->hw_addr_type = DHCP_HW_ADDR_TYPE_ETH;
        dhcp->hw_addr_len = 6;
        dhcp->transaction_id = dhcp_transaction_id;
        dhcp->client_addr = ip_addr;
        memcpy(dhcp->hw_addr, mac_addr, 6);
        dhcp->magic_cookie = DHCP_MAGIC_COOKIE;

        op = dhcp->options;
        dhcp_add_option(op, DHCP_CODE_MESSAGETYPE, 
            uint8_t, DHCP_MESSAGE_REQUEST);
        dhcp_add_option(op, DHCP_CODE_REQUESTEDADDR, 
            uint32_t, ip_addr);
        dhcp_add_option(op, DHCP_CODE_DHCPSERVER, 
            uint32_t, dhcp_server);
        *(op++) = DHCP_CODE_END;

        if(!udp_send(frame, (uint8_t*)op - (uint8_t*)dhcp))
        {
            // Пакет не отправлен (видимо, адреса
            //  сервера ещё нет в арп-кэше)
            // попробуем снова через 5 секунд
            dhcp_renew_time = rtime() + 5;
            return;
        }

        // Ждём ответа
        dhcp_status = DHCP_WAITING_ACK;
    }
}


В коде встречаются псевдофункции gettc() и rtime() — они возвращают, соответственно количество милисекунд и секунд с момента включения питания. Удобно для отсчёта различных интервалов.

Запросы на выделение адреса отправляются на адрес 255.255.255.255. Пока хост не сконфигурирован (IP-адрес и маска = 0.0.0.0) этот адрес и служит широковещательным.

Основная прошивка девайса должна учитывать, что отправлять пакеты можно только после того, как адрес будет получен.

// Проверка готовности интерфейса
uint8_t lan_up()
{
    return ip_addr != 0;
}

// Инициализация сети
void lan_init()
{
    enc28j60_init(mac_addr);

    // Попробуем получить адрес через 5 секунд
    //  (enc28j60 нужно время чтобы поднялся линк)
    dhcp_retry_time = rtime() + 5;
}

// Вызывается периодически
void lan_poll()
{
    uint8_t len;
    eth_frame_t *frame = (void*)net_buf;
    
    while((len = enc28j60_recv_packet(net_buf, sizeof(net_buf))))
        eth_filter(frame, len);

    dhcp_poll();
}


Ещё один момент — по хорошему нам нужно следить за состоянием линка. При втыкании провода нужно обновлять адрес.

// Вызываем периодически
if(!(enc28j60_read_phy(PHSTAT1) & PHSTAT1_LLSTAT))
{
    // Обновим адрес через 5 секунд
    //  (после того, как линк появится)
    dhcp_status = DHCP_INIT;
    dhcp_retry_time = rtime() + 5;

    // Линка нет - опускаем интерфейс
    ip_addr = 0;
    ip_mask = 0;
    ip_gateway = 0;
}


Вот пример использования:

int main()
{
    // Тут у нас светодиодик подключен :)
    DDRA |= (1<<PA0);
    PORTA |= (1<<PA0);
    
    // Инициализируем сеть
    lan_init();

    // Инициализируем таймер (для gettc() и rtime())
    counter_init();
    sei();

    // Ловим пакеты
    while(1)
    {
        lan_poll();

        // Проверяем состояние "интерфейса"
        if(lan_up())
        {
            // готов - включаем светодиод
            PORTA &= ~(1<<PA0); 
        }
        else
        {
            // не готов - выключаем
            PORTA |= (1<<PA0);
        }
    }
    
    return 0;
}


Заключение


Заходим на роутер и, обновляя страничку, палим в реальном времени как девайс получает и обновляет адрес по DHCP.

DHCP

Время выделения адреса я для прикола установил в 2 минуты.

Конечно DHCP немного тяжеловат для МК (моя реализация отожрала больше 2 КБ прошивки). Зато избавляет от необходимости настраивать девайс вручную, что тоже довольно ценно.

Проект.

Итак, про UDP на этом всё.

В следующий раз будет обещаная статья про связь с компом. А потом перейдём к самому интересному)



Все статьи цикла

Комментарии (34)

RSS свернуть / развернуть
Круто! Спасибо.
А потом TCP будет? =)
0
Классная серия статей, thanks.
0
Чего thanks? Уже надоело чтоли?)
0
Да не, мросто все слова восхищения уже закончились, а повторяться не хотса :)
0
А можешь потом дать табличку сколько всего в коде занимает каждая примочка. Тут ты сказал, что DHCP жрет 2кб, а остальное?
0
голый стек (ip/arp/ethernet) — 3000
icmp +136
udp +342
dhcp +2172
0
А TCP/HTTP?
На каком контроллере кстати гоняешь это все?
0
Это потом.
Мега16, т.к. у неё JTAG.)
0
имхо полезно будет в соответствующие статьи добавить :)
0
Сегодня заказал парочку сэмплов ENC28J60 на микрочипе. Страну написал Польша, к городу приписал Rus Federation. Может дойдет.
0
а продолжения когда ждать?
0
Вчера мучил DHCP.
Сказать что не работает нельзя, но и что работает тоже.
В общем IP сервером выделяется, стек его сжирает, что то делает, но на пинг не отвечает, хотя лед активности моргает. Мне кажется что проблема в ip_addr. Видимо там какая то каша или маска сети неправильная. Вчера уже было поздно и я не стал дебагить.
Если WITH_DHCP не использовать, то все гуд — хост пингуется.
У тебя светодиод на PA0 по нолику зажигается?
Дело в том что проверка lan_up всегда дает тру)))
0
блин, так и знал. вот жопа(
попробуй поставить wireshark и dhcp пакеты посниффать
ну пропишеш правило udp.port==67
они широковещательные, комп будет их ловить
может правда что-то с маской
0
Вечером буду мучить. Кстати именно поэтому другие примеры где DHCP включен не работают.
Ничего, отладим! Москва не сразу строилась.
0
Осваиваю по-порядку статьи. На этой части некоторый затык. Менял #define ENC28J60_MAXFRAME 512 на 600 — Не помогает!
В Wireshark на udp.port == 67 ловится только Discover и Offer. Дебажу ЖиТагом и вижу, что не проходит условие в ip_filter(..) {…
if( (packet->ver_head_len == 0x45) &&
(ip_cksum(0, (void*)packet, sizeof(ip_packet_t)) == hcs) &&
((packet->to_addr == ip_addr) || (packet->to_addr == ip_broadcast)) )
..}
далее в udp_filter не заходит. Пробовал коментить эту часть
/*&& ((packet->to_addr == ip_addr) || (packet->to_addr == ip_broadcast))*/
заходит в udp_filter, попадает далее в udp_packet(frame, len).
Я параллельно в Протеусе симулирую и там все в порядке дебажится (проходит по-порядку в dhcp_filter(..) и снифится в wiresharke Request и ACK. В чем ошибочка?
0
Уф-ф-ф… Только что закончил мучить DHCP.
Не хочет получать адрес и все… Посниффал пакеты: широковещательный отправляется, DHCP-сервер дает ответ, а дальше ничего не происходит.
Выяснил, что пакеты не принимает сама ENC25J60. Как оказалось, проблема была в размере пакета 590 байт. В исходнике enc28j60.h размер фрейма ограничен 512 байтами:
#define ENC28J60_MAXFRAME	512

Увеличил до 600 и все заработало!
0
Осваиваю по-порядку статьи. На этой части некоторый затык. Менял #define ENC28J60_MAXFRAME 512 на 600 — Не помогает!
В Wireshark на udp.port == 67 ловится только Discover и Offer. Дебажу ЖиТагом и вижу, что не проходит условие в ip_filter(..) {…
if( (packet->ver_head_len == 0x45) &&
(ip_cksum(0, (void*)packet, sizeof(ip_packet_t)) == hcs) &&
((packet->to_addr == ip_addr) || (packet->to_addr == ip_broadcast)) )
..}
далее в udp_filter не заходит. Пробовал коментить эту часть
/*&& ((packet->to_addr == ip_addr) || (packet->to_addr == ip_broadcast))*/
заходит в udp_filter, попадает далее в udp_packet(frame, len).
Я параллельно в Протеусе симулирую и там все в порядке дебажится (проходит по-порядку в dhcp_filter(..) и снифится в wiresharke Request и ACK. В чем ошибочка?
0
На время отладки разбейте сложное условие на последовательные простые, прошагайте и сразу станет ясно, на каком из них обламывается.
0
сорри — поспешил, вижу что уже коментировали и проходили дальше. Тогда смотрите что у Вам в этих переменных:

packet->to_addr
ip_addr
ip_broadcast
ip_mask

есть некоторый шанс, что в ip_broadcast/ip_mask ерунда сидит
0
Завел глобальную переменную volatile uint32_t packet_to_addr=0; чтобы смотреть что у меня в packet->to_addr в условии. Так вот в студии туда заносится packet_to_addr=4278298816 и не меняется. В Протеусе сначала такое же число, а потом перед тем как получить Request и ACK становится packet_to_addr=4294967295. Проблема в чем? Enca косячит или что?
0
Закоментируйте условие, чтобы провалиться в if, поставьте breakpoint на первую строку внутри:

len = ntohs(packet->total_len) —
sizeof(ip_packet_t);

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

Есть ещё момент — в реальной сети Вы запросто можете ловить совсем не тот пакет, который ожидаете — их там много гуляет. Возможно нужный ещё не пришёл, а Вы приняли за него совсем не тот.
0
По сути я уже так делал, тем не менее сейчас сделал как вы и сказали. Закоментил
/*if((packet->to_addr == ip_addr) || (packet->to_addr == ip_broadcast))*/
Провалился в условие, там в студии:
ip_addr=0
ip_mask=0
packet->to_addr = packet_to_addr=4278298816
Так дальше и остается.
В протеусе
ip_addr=0
ip_mask=0
packet->to_addr = packet_to_addr=4278298816 — перед тем как поймать в Wireshark Request и ACK, после этого меняется packet_to_addr=4294967295.
Ставил другую Enc28j60 — не помогло.
Есть ещё момент — в реальной сети Вы запросто можете ловить совсем не тот пакет, который ожидаете — их там много гуляет. Возможно нужный ещё не пришёл, а Вы приняли за него совсем не тот.
Ну я несколько минут ждал и в Wireshark периодично только Discover Offer. В таком случае что делать?
0
Понекрофилю.
Возможно не проваливается в условие из за того, что dhcp отправляет широковещательный пакет на адрес 255.255.255.255 а в условии проверяется только 192.168.0.255
0
Ещё одну вещь нужно подправить. При отправке запросов Discovery и Request необходимо указывать опцию DHCP_CODE_REQUEST_LIST (55). В ней приводится список сетевых параметров, которые нам требуются. Я здесь указываю маску подсети и основной шлюз:

  len = udpFillDataByte(len, DHCP_CODE_REQUEST_LIST);
  len = udpFillDataByte(len, 2);
  len = udpFillDataByte(len, DHCP_CODE_SUBNET_MASK);
  len = udpFillDataByte(len, DHCP_CODE_GATEWAY);


Без данной опции, например, роутеры Mikrotik возвращают в предложении Offer только IP-адрес. Соответственно, настройка сетевых параметров устройства будет неверной.
0
Хоть и прошло много времени но данная статья весьма актуально так как аналогов в интернете с подробным разжевыванием до сих пор нет.
Нашёл «баг», оставлю здесь потомкам в назидание, мб кому поможет.
---------
В этой реализации DHCP, адрес DHCP-сервера (при получении посылки OFFER) берется из поля dhcp->server_addr. Но не все роутеры передают в этом поле свой адрес. По стандарту адрес сервера передаётся в опции DHCPSERVER(54). Соответственно для ответа серверу вместо:

dhcp_add_option(op, DHCP_CODE_DHCPSERVER,
                    uint32_t, dhcp->server_addr);

нужно писать
dhcp_add_option(op, DHCP_CODE_DHCPSERVER,
                    uint32_t,renew_server);
0
Помогло. Спасибо!
0
Отличные статьи! Спасибо.
Нашел еще одну ошибку:
// Порт DHCP-сервера
#define DHCP_SERVER_PORT htons(67)
// Порт DHCP-клиента
#define DHCP_CLIENT_PORT htons(68)

Нужно писать
// Порт DHCP-сервера
#define DHCP_SERVER_PORT htons(0x0043)
// Порт DHCP-клиента
#define DHCP_CLIENT_PORT htons(0x0044)

Иначе не сработает условие с сортировкой по портам, потому что на порт в протоколе выделяется 2 байта.
0
if(lan_up())
        {
            // готов - включаем светодиод
            PORTA &= ~(1<<PA0); 
        }
        else
        {
            // не готов - выключаем
            PORTA |= (1<<PA0);
        }
Но ведь
PORTA &= ~(1<<PA0);

это очистка. Соответственно светодиод при получении адреса тухнет. Сделал вывод адреса на LCD. При включении горит светодиод и адрес показывает 0.0.0.0. При получении адреса тухнет светодиод и появляется на LCD выданный адрес.
-1
  • avatar
  • Flint
  • 07 сентября 2014, 19:29
Если светодиод включен между питанием и выводом МК — все правильно. Если между землей и пином — нужно просто поменять команды местами.
0
  • avatar
  • Vga
  • 08 сентября 2014, 00:26
Привет, привет. Вот уже неделю пытаюсь запустить дшцп на stm32. Все предыдущие успешно запущены. И. А этот не поддается. Использую связку из stm32f4discovery+enc28j60+wh1602. В качестве сервера дшцп маршрутизатор длинк дир 300.(валялся без дела). С маршрутизатора кабель идет на комп с двумя сетевухами связанными в мост и потом на девайс. На компе есесьно ваиршарк. Отладка в лоб выявила что пакет на поиск ближайшего сервера дшцп отсылается, но ваершарк говорит что он mailformed в полях ответственных за udp. В частности в поле длина. См.фотку.И конечно сервер не понимает его.Буду признателен за помощь в борьбе с этим гемором, да и я думаю я не первый такой.
0
0
0
Вот картинка. Если кому интересны статьи товарища lifelover портированные с avr на stm32f4discovery мыльте сюда iiievan@yandex.ru
0
Я говорю о кейле.:)
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.