Вычисление контрольной суммы TCP из массива байтов

Я работаю над этим уже некоторое время, но не могу получить правильное значение контрольной суммы. Здесь мой метод принимает 3-байтовые массивы. Первый содержит байты IPv4Header, второй — байты TCPHeader и, наконец, полезную нагрузку. Я уже проверил, что эти байты верны, и пытаюсь использовать перекрестные ссылки по мере продвижения, но после стольких попыток мне все время не удается. Я также не нашел ни одного руководства с реальным кодом, который бы делал это из массива байтов.

        public override ushort ComputeChecksum(byte[] ipv4Header, byte[] transportHeader, byte[] payload)
        {
            int pseudoHeaderLength = 12; // Pseudo-header is 12 bytes
            int tcpLength = transportHeader.Length;
            int payloadLength = payload.Length;
            int totalLength = pseudoHeaderLength + tcpLength + payloadLength;
            bool isOddLength = (totalLength % 2 != 0);

            byte[] pseudoHeaderAndTcpSegment = new byte[totalLength + (isOddLength ? 1 : 0)];
            Console.WriteLine($"pseudoHeaderLength:{pseudoHeaderLength} transportHeader.Length:{tcpLength} payloadLength:{payloadLength} totalLength:{totalLength} newTotalLength:{pseudoHeaderAndTcpSegment.Length} OddLength:{isOddLength}");

            // Copy Source IP address (4 bytes)
            Buffer.BlockCopy(ipv4Header, 12, pseudoHeaderAndTcpSegment, 0, 4);
            Console.WriteLine((BitConverter.ToString(pseudoHeaderAndTcpSegment).Replace("-", " ") + ", "));
            Console.WriteLine("");

            // Copy Destination IP address (4 bytes)
            Buffer.BlockCopy(ipv4Header, 16, pseudoHeaderAndTcpSegment, 4, 4);
            Console.WriteLine((BitConverter.ToString(pseudoHeaderAndTcpSegment).Replace("-", " ") + ", "));
            Console.WriteLine("");

            // Zero byte (1 byte)
            pseudoHeaderAndTcpSegment[8] = 0;

            // Protocol (1 byte)
            pseudoHeaderAndTcpSegment[9] = ipv4Header[9];
            Console.WriteLine((BitConverter.ToString(pseudoHeaderAndTcpSegment).Replace("-", " ") + ", ") + " Protocol:" + ipv4Header[9]); // ipv4Header[9] prints 6 like it should
            Console.WriteLine("");

            // TCP length (2 bytes)
            pseudoHeaderAndTcpSegment[10] = (byte)((tcpLength + payloadLength) >> 8); // try <<
            pseudoHeaderAndTcpSegment[11] = (byte)(tcpLength & 0xFF);
            Console.WriteLine((BitConverter.ToString(pseudoHeaderAndTcpSegment).Replace("-", " ") + ", "));
            Console.WriteLine("");

            // Make a copy of the transport header to zero out the checksum
            byte[] transportHeaderCopy = new byte[tcpLength];
            Buffer.BlockCopy(transportHeader, 0, transportHeaderCopy, 0, tcpLength);
            transportHeaderCopy[16] = 0; // Zero out the checksum field (16th byte)
            transportHeaderCopy[17] = 0; // Zero out the checksum field (17th byte)
            Console.WriteLine((BitConverter.ToString(transportHeaderCopy).Replace("-", " ") + ", "));
            Console.WriteLine("");

            // Copy the modified TCP header with zeroed checksum field
            Buffer.BlockCopy(transportHeaderCopy, 0, pseudoHeaderAndTcpSegment, 12, tcpLength);
            Console.WriteLine("TCPHeader:" + (BitConverter.ToString(pseudoHeaderAndTcpSegment).Replace("-", " ") + ", "));
            Console.WriteLine("");

            // Copy the payload
            Buffer.BlockCopy(payload, 0, pseudoHeaderAndTcpSegment, 12 + tcpLength, payload.Length);
            if (isOddLength)
            {
                Console.WriteLine("Odd Length adding a zero");
                pseudoHeaderAndTcpSegment[totalLength] = 0;
            }
            Console.WriteLine("Payload:" + (BitConverter.ToString(pseudoHeaderAndTcpSegment).Replace("-", " ") + ", "));
            Console.WriteLine("");

            return ComputeChecksum(pseudoHeaderAndTcpSegment);
        }

        protected static ushort ComputeChecksum(byte[] data)
        {
            int length = data.Length;
            int i = 0;
            long sum = 0;

            while (length > 1)
            {
                sum += BinaryPrimitives.ReadUInt16BigEndian(data.AsSpan(i, 2));

                i += 2;
                length -= 2;
            }

            if (length > 0)
            {
                sum += data[i] << 8;
            }

            while ((sum >> 16) != 0)
            {
                sum = (sum & 0xFFFF) + (sum >> 16);
            }

            return (ushort)(~sum);
        }

Байты, которые печатаются, кажутся правильными, и я прочитал эту статью расчет контрольной суммы TCP, в которой мне говорилось, что мне нужно указать IP-адрес источника, IP-адрес назначения, длину сегмента, протокол, фиксированные 8 бит, за которыми следует TCP-заголовок и полезная нагрузка. Итак, я думаю, что мой порядок правильный, хотя я не уверен, правильно ли я выполнил фиксированные 8 бит и часть длины. Я также не уверен, правильно ли я вычисляю фактический 2-байтовый ответ, используя дополнение. Любые исправления будут очень признательны, спасибо!

Обновлено: я подумал, что могу также добавить сообщения отладки, если кто-то найдет это полезным.

A2 9F 82 EA 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00,

A2 9F 82 EA C0 A8 32 86 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00,

A2 9F 82 EA C0 A8 32 86 00 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00,

A2 9F 82 EA C0 A8 32 86 00 42 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00,  Protocol:6

A2 9F 82 EA C0 A8 32 86 00 42 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00,

A2 9F 82 EA C0 A8 32 86 00 42 06 00 01 BB F0 49 FD 02 5E BB 35 90 17 70 50 18 00 04 83 57 00 00 17 03 03 00 29 E0 63 19 68 7C 3F 19 3F 33 A6 9E 0B CD 54 47 8A 97 1B CC 97 1A 45 CD 5C EF 75 3E 75 A0 32 A3 46 2D 3F FD 37 DC F3 DB 34 AF,

CorrectCheckSumCheck:22403 0x8357  OurCheck:64005 0x05FA 

Порядок байтов доставляет немало неудобств, поскольку исходное значение нашей контрольной суммы — 22403, а поскольку C# в Windows использует прямой порядок байтов, мне нужно было преобразовать его в обратный порядок байтов, прежде чем затем отображать его в шестнадцатеричном виде, что дает 0x8357, то есть правильное значение (я знаю его правильно, поскольку оно соответствует тому, что я вижу в полном байтовом массиве пакетов). Затем для нашего я напечатал оба, и это настолько далеко, что очевидно, что это не проблема с порядком байтов. Для контекста вот массив байтов TCP-заголовка без изменений 01 BB F0 49 FD 02 5E BB 35 90 17 70 50 18 00 04 83 57 00 00

Это просто для обучения или у вас есть какое-то практическое применение?

JonasH 30.05.2024 08:46

@JonasH Ну, я изучаю различные протоколы, и обычно мне нравится узнавать что-то, выполняя проекты, а не просто читая об этом и притворяясь, будто я это знаю. Итак, я создал базовое приложение на C#, которое перехватывает пакеты, и сейчас пытаюсь проверить заголовки на обнаружение поврежденных пакетов. Я уже создал анализатор заголовков для IPv4, TCP, UDP и ICMP, и это был следующий логический шаг в моем пути обучения. Точно так же я узнал о формате PE, создав двоичный анализатор файлов exe, dll и sys. Я также сделал этот проект для анализа PNG-файлов, что было довольно круто.

UnSure 30.05.2024 08:54

Не забывайте, что поле контрольной суммы должно быть нулевым — когда вы проверяете захваченный TCP-пакет, вам необходимо обнулить его.

Luaan 30.05.2024 09:08

@Luaan Вы имеете в виду, что когда я копирую массив байтов заголовков TCP в свой новый массив, мне нужно сначала отредактировать массив заголовков TCP и обнулить его контрольную сумму, а затем скопировать его? Это имело бы смысл, учитывая, что это еще не известно.

UnSure 30.05.2024 09:10

Ага. И если вы используете захваченный заголовок, вы неправильно читаете слова — они с прямым порядком байтов, но вы читаете их с прямым порядком байтов. Вместо этого я бы рекомендовал использовать BinaryPrimitives.ReadUInt16BigEndian(data.AsSpan(index, 2)).

Luaan 30.05.2024 09:14

@Luaan Я внесу эти изменения, но я не думаю, что Endianess неправильный, потому что он печатает байты из массива в правильном порядке (все массивы байтов были напечатаны и сохранены с прямым порядком байтов). Если только BinaryPrimitives.ReadUInt16BigEndian(data.AsSpan(index, 2)) не относится к чему-то другому, кроме операторов печати. Я также внес эти изменения, но все равно получаю неправильную контрольную сумму.

UnSure 30.05.2024 09:17

@UnSure Речь идет о вычислении суммы в методе ComputeChecksum. Предполагается, что данные добавляются в виде последовательности 16-битных слов в обратном порядке; вы добавляете их как последовательность 16-битных слов в порядке с прямым порядком байтов (за исключением нечетного слова, если оно есть, которое получается из-за того, как должно выполняться заполнение).

Luaan 30.05.2024 09:25
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
7
107
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вот рабочая версия в LINQPad, включая два примера заголовков.

Следует отметить две очень важные вещи: слова должны читаться с прямым порядком байтов (сетевой порядок), а контрольная сумма в заголовке должна быть обнулена, если вы проверяете захваченные заголовки.

void Main()
{
    var pseudoHeader =
        CreatePseudoHeader
        (
            new byte[] { 0xac, 0x18, 0x0b, 0x9f },
            new byte[] { 0xac, 0x18, 0x7e, 0x34 },
            6,
            FromHex("0d3df160ea5e5c54e0e45e585018fa0000000000170303002e0000000000002015cd7f0a9400f87f031162f4de4118d5670f885cea116c630a416883d8569bedc94f2df41d5e20")
        );


    ComputeChecksum(pseudoHeader).ToString("X4").Dump();

    pseudoHeader =
            CreatePseudoHeader
            (
                new byte[] { 0xac, 0x18, 0x7e, 0x34 },
                new byte[] { 0xac, 0x18, 0x0b, 0x9f },
                6,
                FromHex("f1600d3de0e45e58ea5e5c875010101100000000")
            );


    ComputeChecksum(pseudoHeader).ToString("X4").Dump();
}

private static byte[] FromHex(string v)
{
    var buffer = new byte[v.Length / 2];

    for (var i = 0; i < buffer.Length; i++)
    {
        buffer[i] = Convert.ToByte(v.Substring(i * 2, 2), 16);
    }

    return buffer;
}

public static ushort ComputeChecksum(byte[] data)
{
    int length = data.Length;
    int i = 0;
    long sum = 0;

    while (length > 1)
    {
        sum += BinaryPrimitives.ReadUInt16BigEndian(data.AsSpan(i, 2));

        i += 2;
        length -= 2;
    }

    if (length > 0)
    {
        sum += data[i] << 8;
    }

    while ((sum >> 16) != 0)
    {
        sum = (sum & 0xFFFF) + (sum >> 16);
    }

    return (ushort)(~sum);
}

public byte[] CreatePseudoHeader(byte[] sourceIp, byte[] destIp, byte protocol, byte[] tcpHeader)
{
    var tcpLength = tcpHeader.Length;
    var pseudoHeader = new byte[12 + tcpLength];
    Buffer.BlockCopy(sourceIp, 0, pseudoHeader, 0, 4);
    Buffer.BlockCopy(destIp, 0, pseudoHeader, 4, 4);
    pseudoHeader[8] = 0;
    pseudoHeader[9] = protocol;
    pseudoHeader[10] = (byte)(tcpLength >> 8);
    pseudoHeader[11] = (byte)(tcpLength & 0xff);
    Buffer.BlockCopy(tcpHeader, 0, pseudoHeader, 12, tcpLength);

    return pseudoHeader;
}

Конечно, это очень простая реализация; достаточно хорош для обучения, но уж точно не для реального использования :)

Почему ваша длина ниже протокола оказалась неправильной в статье, на которую я дал ссылку? И почему вы не скопировали полезную нагрузку?

UnSure 30.05.2024 09:24

@UnSure Я пошел прямо из RFC по TCP; Длина указана после протокола в заголовке, поэтому она вычисляется именно в этом порядке. Однако это не имеет значения, поскольку мы просто делаем сложение :)

Luaan 30.05.2024 09:28

Вау, это действительно сработало, ты потрясающий!! Я также только что просмотрел RFC и увидел эту огромную статью, мне обязательно нужно ее прочитать, спасибо за вашу помощь!

UnSure 30.05.2024 09:36

Оказывается, мне это не подходит. По какой-то причине я только иногда получаю правильную контрольную сумму, и понятия не имею, почему. Например, источник 192.168.50.1 и IP-адрес назначения 192.168.50.134 работают (это входящий пакет), но для его исходящей версии, которая имеет обратный IP-адрес, то есть источник 192.168.50.134 и пункт назначения 192.168.50.1, это не удается. . Я обновил свой вопрос своим текущим кодом. Также я знаю, что вы упомянули о возможной проблеме с порядком байтов, но я не понимаю, как это происходит, поскольку я читаю байты непосредственно из байтового буфера пакетов.

UnSure 31.05.2024 01:39

Порядок байтов всегда является проблемой, если вы переполняете отдельные октеты. Добавление BB00 + BB00 и 00BB + 00BB даст разные результаты. Если переполнения нет, вы не заметите, что у вас неправильный порядок байтов. Опять же, дело не в том, что находится в буфере, а в том, как вы выполняете сложение - для этого вам нужно правильно интерпретировать байты в буфере - если они имеют обратный порядок байтов, вам нужно добавлять их как обратный порядок байтов; если они имеют прямой порядок байтов, вам нужно добавить их с прямым порядком байтов. TCP использует обратный порядок байтов (сетевой порядок), это то, что находится в вашем буфере; ваш код использует прямой порядок байтов.

Luaan 31.05.2024 20:08

Но где я их неправильно складываю, потому что все массивы байтов должны быть в форме с прямым порядком байтов. Я предполагаю, что вы, должно быть, имеете в виду метод дополнения, поскольку другой метод не делает никаких дополнений, а просто копирует массивы. Имеет смысл, что C# изначально использует прямой порядок байтов для своих вычислений, но как мне исправить метод ComputeChecksum, чтобы вычисления выполнялись правильно?

UnSure 31.05.2024 23:20

Вы по-прежнему неправильно собираете длину заголовка TCP - большой порядковый номер означает, что более значимый октет находится в конце. Просто поменяйте местами pseudoHeaderAndTcpSegment байты 10 и 11.

Luaan 01.06.2024 15:10

Это все равно не решает проблему

UnSure 01.06.2024 20:10

Можете ли вы добавить пример пакета, в котором не удалось вычислить контрольную сумму? И я предполагаю, что вы проверили, что контрольная сумма действительно верна (например, в Wireshark)? :D Не забывайте, что существуют такие вещи, как выгрузка контрольной суммы; контрольная сумма, которую вы собираете, не обязательно верна.

Luaan 03.06.2024 11:39

Есть ли какая-нибудь программа, которая может принимать массив байтов и затем выводить правильную контрольную сумму? Кроме того, я проверил выгрузку контрольной суммы, и это также может повлиять на проволочную акулу. Что вы получаете за следующее: я вычисляю 9826, а там написано 97E0... 45 00 00 6E 5B 7D 40 00 37 06 CC 54 A2 9F 85 EA C0 A8 32 86 01 BB D4 5A 27 3B 46 F8 72 7B 54 D1 50 18 00 05 97 E0 00 00 17 03 03 00 41 5C 07 93 EF 58 B0 BC 23 33 FC 12 3E EA D6 BA 13 37 C3 30 5B E7 F1 A3 55 6F 5C D6 EC 84 D2 7B 4E 5B CF 2 2 29 С1 24 68 BB 14 B2 C4 D7 1E 10 5F 60 67 A4 E7 EF 6E 89 2B 48 25 BD A9 C2 54 9E 5E 7D BB

UnSure 03.06.2024 17:55

@UnSure «Это также может повлиять на Wireshark» - именно в этом суть: если Wireshark не может проверить контрольную сумму, это не проблема в вашем коде - это просто означает, что контрольная сумма в захваченном пакете не является фактической контрольной суммой.

Luaan 05.06.2024 10:16

Можете ли вы отправить реальный дамп пакета Ethernet (или хотя бы IP) вместо псевдозаголовка?

Luaan 05.06.2024 10:27

Да, я понимаю это, но как я могу проверить правильность вычислений CheckSum, а не просто предполагать это? Где мне следует захватить «дамп пакетов Ethernet (или хотя бы IP)»?

UnSure 05.06.2024 19:48

@UnSure Просто попробуйте захватить пакет в Wireshark и получить полный дамп, и позвольте Wireshark вычислить контрольную сумму, чтобы увидеть, действительно ли она верна - это то, что вы можете сравнить со своим алгоритмом.

Luaan 06.06.2024 08:23

Как я отключил выгрузку контрольной суммы напрямую со своей сетевой карты, а затем использовал WireShark для проверки своих контрольных сумм и фактически могу подтвердить, что они неверны. Что мне следует попробовать сделать сейчас?

UnSure 08.06.2024 01:17

Наконец-то я смог это исправить! Мне просто нужно было изменить одну строку на pseudoHeaderAndTcpSegment[11] = (byte)((tcpLength + payloadLength) & 0xFF);

UnSure 09.06.2024 02:40

Другие вопросы по теме