Кратко

  • RFC 768 установил два разных значения нуля в поле checksum UDP: вычисленный нулевой результат передавался как 0xFFFF, а 0x0000 означал, что checksum не вычислялся.
  • Требования к хостам 1989 года сохранили возможность пропуска, но сделали вычисление настройкой по умолчанию. IPv6 позднее сделал checksum обязательным, а обновление 2013 года допустило узкий случай для туннелей.

Два нуля в одном поле

В августе 1980 года RFC 768, написанный Джоном Постелом, определил User Datagram Protocol как способ обмена датаграммами с минимумом протокольных механизмов. UDP не гарантировал порядок доставки, защиту от дубликатов или надёжность. Его заголовок занимал всего восемь октетов: порт источника, порт назначения, длина и checksum, за которыми следовали данные.

Последнее поле кажется простым, пока арифметика не создаёт особый случай. UDP использует 16-битную контрольную сумму в дополнительном коде до единицы — такую же, как TCP. В вычисление входят заголовок UDP и данные, а также псевдозаголовок с IP-адресами источника и назначения, номером протокола и длиной UDP. Постел пояснял, что псевдозаголовок помогает защититься от ошибочной доставки датаграмм.

В арифметике с дополнением до единицы у нуля два битовых представления: все нули и все единицы. RFC 768 назначил им разные значения при передаче. Если результат вычисления равнялся нулю, отправитель должен был передать все единицы. Поле, состоящее из нулей, напротив, означало, что отправитель не вычислял checksum. Формат переносил одновременно результат и небольшой признак состояния, не добавляя отдельный бит флага.

В этом и состоит узкий исторический сюжет статьи. «Нулевой checksum UDP» не был вычисленной контрольной суммой со значением ноль. Так кодировалось отсутствие вычисления. Если спутать эти состояния, реализация может отбросить корректную датаграмму или принять отсутствие обнаружения ошибок за успешную проверку.

Возможность не означала настройку по умолчанию

Исходная спецификация разрешала пропуск checksum, например при отладке или если протокол верхнего уровня не нуждался в нём. Через девять лет RFC 1122, требования к интернет-хостам, уточнил эксплуатационное правило. Хост должен был реализовать вычисление и проверку; приложение могло управлять вычислением, но по умолчанию оно должно было быть включено. Получатель мог выбирать, передавать ли датаграммы без checksum приложениям.

RFC 1122 также указал на распространённую ошибку реализации: вычисленный ноль передаётся как 65535, а полностью нулевое поле означает «отсутствует». Документ сообщал, что некоторые приложения только для локальных сетей отключали checksum ради эффективности и что было зарегистрировано много необнаруженных ошибок. В документе нет числа, конкретных инцидентов или знаменателя. Это свидетельство беспокойства авторов стандарта, а не измерение распространённости практики.

Checksum никогда не был цифровой подписью. Псевдозаголовок помогает обнаруживать некоторые случаи ошибочной доставки, а вычисление — часть случайных повреждений. Но checksum не удостоверяет отправителя, не предотвращает намеренное изменение и не доказывает смысловую корректность данных приложения. «Checksum присутствует» и «пакету можно доверять» — разные утверждения.

IPv6 сузил границу

Спецификация IPv6 1998 года, RFC 2460, сделала checksum UDP обязательным. Если вычисленный результат равнялся нулю, его также следовало записать как 0xFFFF; получатель должен был отбросить датаграмму UDP для IPv6 с нулевым полем checksum. Таким образом, прежний необязательный признак отсутствия стал неприемлемым для обычного трафика UDP в IPv6.

Позднее возник конкретный случай, связанный с производительностью. В 2013 году RFC 6935 и RFC 6936 обновили обработку checksum IPv6 для некоторых UDP-туннелей. Такие туннели могли переносить пакеты со своими средствами контроля целостности, а вычисление внешнего checksum требовало ресурсов. Исключение зависело от условий применимости, поведения обеих конечных точек и совместимости промежуточных устройств; оно не сделало нулевой checksum общим вариантом IPv6.

История стандартов показывает: битовый шаблон остался прежним, но разрешённый контекст менялся. В IPv4 ноль мог обозначать отсутствие вычисления, хотя требования к хостам включали его по умолчанию. В IPv6 отсутствие сначала запретили, а затем условно разрешили для конкретной роли внешней туннельной инкапсуляции. Само значение не сообщало получателю, допустим ли пропуск: требовались версия IP и контекст применения.

Источники