Embedded HandbookПриложения
Все разделы › XII. Сети и DevOps для инженера
STM32F103, STM32F407 · уровень средний · 10 мин чтения
Открыть в интерактивном справочнике

105. Сеть снизу вверх: Ethernet, MAC, IP, подсети, ARP

Сокеты в разделе 78 пользовались сетью как готовой: адрес, порт, и данные пошли. Здесь — что под ними. Прибор шлёт в провод кадр Ethernet, а чтобы отправить его соседу, нужен MAC-адрес соседа — знает же прибор только его IP. Между ними стоят маска подсети, шлюз и ARP. Все кадры раздела собирает переносимый код на C, и на компьютере он же разговаривает с настоящим ядром Linux в соседнем контейнере: ARP-запрос и ping уходят через сырой сокет, ответы разбирают те же функции. Кадр с неверной контрольной суммой ядро выбрасывает молча — раз оно ответило, суммы посчитаны верно.

Сокеты — раздел 78, типы сетей — раздел 86, контейнеры — раздел 87.

Как это называется в документации

Оригинал Перевод
frame кадр
preamble, start frame delimiter (SFD) преамбула, начало кадра
MAC address физический адрес сетевого интерфейса
organizationally unique identifier (OUI) код производителя в MAC-адресе
locally administered address локально назначенный адрес
EtherType тип содержимого кадра
frame check sequence (FCS) контрольная сумма кадра, CRC-32
interframe gap межкадровая пауза
subnet mask, prefix length маска подсети, длина префикса
default gateway шлюз по умолчанию
broadcast address широковещательный адрес
address resolution protocol (ARP) поиск MAC-адреса по IP-адресу
ones' complement checksum контрольная сумма в обратном коде
time to live (TTL) время жизни пакета: сколько маршрутизаторов он ещё пройдёт
raw socket сырой сокет: кадры целиком, мимо стека

105.1 Кадр Ethernet

Кадр Ethernet: поля и длины, под ним — ARP-запрос в поле данных

Поле Байт Что это
преамбула и начало кадра 8 семь байт 0x55 и 0xD5: приёмник подстраивает часы
адрес назначения 6 MAC получателя или FF:FF:FF:FF:FF:FF — всем
адрес отправителя 6 свой MAC
тип 2 0x0800 — IPv4, 0x0806 — ARP
данные 46 … 1500 короче 46 — дополняют нулями
FCS 4 CRC-32 от адреса назначения до конца данных
пауза 12 тишина перед следующим кадром

Кадр — от 64 до 1518 байт, а в проводе с преамбулой и паузой занимает от 84 до 1538 байтовых интервалов. На 100 Мбит/с это 148 810 самых коротких кадров в секунду или 8127 самых длинных. Полезные данные занимают в длинном кадре — 97,53 % времени линии, в коротком — 54,76 %. Самый короткий кадр длится 5,12 мкс.

У F407 MAC встроен, а приёмопередатчик (PHY) — отдельная микросхема. Между ними RMII — 50 МГц по 2 бита или MII — 25 МГц по 4 бита: оба дают 100 Мбит/с. У F103 MAC нет — сеть подключают внешним контроллером на SPI.

Собрать кадр — значит сложить поля по порядку:

unsigned eth_frame(uint8_t *f, const uint8_t dst[6], const uint8_t src[6], uint32_t type,
                   const uint8_t *data, unsigned n)
{
    memcpy(f, dst, 6);
    memcpy(f + 6, src, 6);
    put16(f + 12, type);
    memcpy(f + ETH_HDR, data, n);
    unsigned len = ETH_HDR + n;
    if (len < ETH_MIN) {
        memset(f + len, 0, ETH_MIN - len);
        len = ETH_MIN;
    }
    uint32_t fcs = eth_crc32(f, len);
    for (unsigned i = 0; i < ETH_FCS; i++)
        f[len++] = (uint8_t)(fcs >> (8 * i));     /* FCS — младшим байтом вперёд */
    return len;
}

Числа в полях идут старшим байтом вперёд, а Cortex-M хранит их младшим — put16 раскладывает по байтам явно. На плате FCS и дополнение до 60 байт MAC F407 делает сам, а преамбулу добавляет PHY.


105.2 FCS: CRC-32

uint32_t eth_crc32(const uint8_t *p, unsigned n)
{
    uint32_t c = 0xFFFFFFFFu;
    while (n--) {
        c ^= *p++;
        for (unsigned k = 0; k < 8; k++)
            c = (c >> 1) ^ (0xEDB88320u & (0u - (c & 1u)));
    }
    return ~c;
}

Контрольное значение по строке «123456789» — 0xCBF43926, как у всех реализаций этого CRC-32. Приёмник не вынимает FCS из кадра: CRC по кадру вместе с ним всегда равна 0x2144DF1C. Испорчен один бит — и сумма уже другая (для этого кадра — 0x0B6CE77E): всё, что не 0x2144DF1C, означает ошибку. Кадр выбрасывают молча, а повтор — забота протоколов выше. У блока CRC в F1, F2, F4 и L1 другой порядок бит и нет финальной инверсии (раздел 12), так что для FCS он без перестановки бит не годится. У F0, F3, F7, L4, G0, G4 и H7 порядок задают биты REV_IN и REV_OUT: с разворотом входа по байтам, разворотом выхода и инверсией результата блок считает FCS сам.


105.3 MAC-адрес

MAC — 6 байт. У купленного адреса первые три — код производителя. Два младших бита первого байта служебные: бит 0 — групповой адрес, у адреса устройства он ноль; бит 1 — адрес назначен локально, а не производителем. У STM32 своего MAC нет: адрес покупают блоком, берут из микросхемы с зашитым адресом или назначают локально. Для прибора в своей сети локальный адрес строят из уникального номера кристалла:

#define MAC_LOCAL  0x02u                /* бит 1 первого байта: адрес назначен локально */
#define MAC_GROUP  0x01u                /* бит 0: групповой; у адреса устройства — ноль */

void mac_from_uid(const uint8_t uid[12], uint8_t mac[6])
{
    uint64_t h = 0xCBF29CE484222325u;   /* FNV-1a, 64 бита: 96 бит номера → 46 бит адреса */
    for (unsigned i = 0; i < 12; i++)
        h = (h ^ uid[i]) * 0x100000001B3u;
    for (unsigned i = 0; i < 6; i++)
        mac[i] = (uint8_t)(h >> (8 * i));
    mac[0] = (uint8_t)((mac[0] & ~(MAC_LOCAL | MAC_GROUP)) | MAC_LOCAL);
}

void net_mac(uint8_t mac[6])
{
    uint8_t uid[12];
    memcpy(uid, (const void *)NET_UID_ADDR, sizeof uid);
    mac_from_uid(uid, mac);
}

Номер лежит по адресу 0x1FFFF7E8 у F103 и 0x1FFF7A10 у F407. Номер для примера 30 00 3C 00 0F 51 33 30 35 39 32 37 даёт адрес 06:42:8F:70:AA:C7: младшие биты первого байта — 10, локальный индивидуальный. Совпадение не исключено, но маловероятно: 46 бит — это 70 368 744 177 664 адресов, и вероятность совпадения в сети из 1000 приборов — около 7,1·10⁻⁹.


105.4 IPv4: заголовок и контрольная сумма

Байты Поле
0 версия 4 и длина заголовка в словах: 0x45
1 класс обслуживания, обычно 0
2–3 длина всего пакета
4–5 номер пакета
6–7 флаги (0x40 — «не дробить») и смещение фрагмента
8 TTL: сколько маршрутизаторов пакет ещё пройдёт
9 протокол: 1 — ICMP, 6 — TCP, 17 — UDP
10–11 контрольная сумма заголовка
12–15 адрес отправителя
16–19 адрес получателя

Сумма — 16-битные слова, сложенные с переносом по кругу и в конце инвертированные:

uint32_t ip_checksum(const uint8_t *p, unsigned n)
{
    uint32_t s = 0;
    for (unsigned i = 0; i + 1 < n; i += 2)
        s += get16(p + i);
    if (n & 1u)
        s += (uint32_t)p[n - 1] << 8;
    while (s >> 16)
        s = (s & 0xFFFFu) + (s >> 16);
    return ~s & 0xFFFFu;
}

Заголовок 45 00 00 73 00 00 40 00 40 11 00 00 C0 A8 00 01 C0 A8 00 C7 с нулём в поле суммы даёт 0xB861. В нём «40 00» — «не дробить» и смещение 0, «40» в байте 8 — TTL 64, «11» — UDP. Приёмник считает сумму по заголовку вместе с этим полем и получает ноль. Та же функция считает сумму ICMP, UDP и TCP — у последних двух ещё и с псевдозаголовком из адресов.


105.5 Подсети и шлюз

Адрес пишут с длиной префикса: 192.168.1.130/26 — это 26 единиц маски слева. Сеть — адрес, умноженный на маску по битам; широковещательный — сеть с единицами во всех битах узла:

Префикс Маска Сеть Широковещательный Узлов
/24 255.255.255.0 192.168.1.0 192.168.1.255 254
/26 255.255.255.192 192.168.1.128 192.168.1.191 62
/30 255.255.255.252 192.168.1.128 192.168.1.131 2
/31 255.255.255.254 192.168.1.130 192.168.1.131 2
/32 255.255.255.255 192.168.1.130 192.168.1.130 1

У /31 адресов сети и широковещательного нет — это два узла на линии точка-точка.

uint32_t ip_mask(unsigned prefix)
{
    return prefix ? 0xFFFFFFFFu << (32u - prefix) : 0u;
}

uint32_t next_hop(uint32_t me, unsigned prefix, uint32_t gw, uint32_t dst)
{
    return ((me ^ dst) & ip_mask(prefix)) ? gw : dst;
}

Решение «сам или через шлюз» — одна операция: адреса совпадают под маской — адресат рядом, и кадр идёт ему; нет — кадр уходит MAC-адресу шлюза, а в IP-заголовке остаётся адрес адресата. Прибор 192.168.1.130/26 со шлюзом .129 до .150 достаёт сам, а до .20 — через шлюз.

192.168.1.130/26: маска отрезает 26 бит сети, остальные 6 — номер узла


105.6 ARP: MAC по IP

Прежде чем послать кадр, нужен MAC. ARP спрашивает всех: «у кого 192.168.1.129? Ответьте 192.168.1.130».

unsigned arp_build(uint8_t *a, const arp_t *r)
{
    put16(a, 1);                        /* оборудование: Ethernet */
    put16(a + 2, ETH_IPV4);             /* протокол: IPv4 */
    a[4] = 6;                           /* длина MAC */
    a[5] = 4;                           /* длина адреса IPv4 */
    put16(a + 6, r->op);
    memcpy(a + 8, r->sha, 6);
    put32(a + 14, r->spa);
    memcpy(a + 18, r->tha, 6);
    put32(a + 24, r->tpa);
    return ARP_LEN;
}

ARP — 28 байт; с заголовком кадра — 42, с дополнением до 60 и FCS — 64. Весь запрос:

ff ff ff ff ff ff  06 42 8f 70 aa c7  08 06     кому — всем, от кого, тип ARP
00 01  08 00  06  04  00 01                     Ethernet, IPv4, длины 6 и 4, запрос
06 42 8f 70 aa c7  c0 a8 01 82                  MAC и IP отправителя
00 00 00 00 00 00  c0 a8 01 81                  искомый MAC — нули, искомый IP
00 × 18                                         дополнение до 60 байт
3b 6c 8b c2                                     FCS, младшим байтом вперёд

Отвечают уже не всем, а спросившему: операция 2, и MAC владельца стоит в поле отправителя. Ответ хранят в таблице ARP несколько минут, чтобы не спрашивать перед каждым пакетом.


105.7 Опыт: ARP и ping настоящему ядру

Два контейнера в своей сети 10.105.105.0/24: в .10 работает код раздела, в .20 — обычный Linux. Код открывает сырой сокет AF_PACKET на eth0 и отправляет кадры, собранные функциями раздела, — без FCS: его добавляет сетевой интерфейс. Сначала ARP-запрос о .20, потом ping по MAC-адресу из ответа. Ответы разбирают те же функции:

Ядро выбрасывает пакет с неверной суммой без ответа, так что сборка заголовка и суммы проверены самим получателем. TTL 64 — начальное значение Linux; пройди ответ через маршрутизатор, пришло бы 63.


105.8 Грабли

Кадры уходят, но никто не отвечает. Тип записан младшим байтом вперёд: 0x0608 вместо 0x0806. Числа в полях — старшим байтом вперёд.

Два прибора дерутся за адрес. Одинаковые MAC: у обоих адрес из примера. Строить адрес из номера кристалла с битом «локальный».

Прибор видит соседей, но не интернет. Нет шлюза или маска шире настоящей: чужой адрес прибор ищет ARP-ом сам, и никто не отвечает.

ARP есть, а ping молчит. Неверная сумма IP или ICMP — ядро выбрасывает пакет молча. Считать сумму при нуле в её поле.

FCS, посчитанная блоком CRC STM32, не сходится. У F1 и F4 у блока другой порядок бит и нет финальной инверсии; у семейств с REV_IN/REV_OUT их не включили.

Короткий кадр не принимают. Меньше 60 байт без FCS: дополнить нулями. MAC F407 делает это сам, программная сборка — нет.


105.9 Итоги

Дальше: раздел 106 «TCP и UDP изнутри: рукопожатие, окна, повторы, NAT» — что добавляет транспорт поверх IP и почему соединение переживает потерю пакетов.

Открыть в интерактивном справочнике