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

Недавно я задал аналогичный вопрос, но отметил его как решенный, так как думал, что нашел ответ, хотя на самом деле мой код работал только с конкретными случаями, пропуская при этом большую часть случаев. В любом случае я использую WinDivert для захвата TCP-пакетов с целью вычисления контрольных сумм и поиска некорректных пакетов. Массивы байтов имеют обратный порядок байтов, поскольку они извлекаются непосредственно из WinDivert. Я также прочитал RFC о контрольной сумме TCP и не могу понять, что я делаю неправильно. Я даже добавил проверку длины, так как заметил следующее... If a segment contains an odd number of header and text octets to be checksummed, the last octet is padded on the right with zeros. Но даже в этом случае мои расчеты контрольной суммы терпят неудачу, даже когда полезной нагрузки нет. Например, пакет, который содержал только 40 байт (что означает отсутствие полезной нагрузки) и имел адрес источника 172.64.151.73 и адрес назначения 192.168.50.134, и мой код дал правильную контрольную сумму, однако, когда полезная нагрузка все еще равна 40, но с обратными адресами (это означает, что источник теперь 192.168.50.134) я получаю совершенно неправильную контрольную сумму. Кажется, мой код также выдает неверный ответ для большинства пакетов с любой полезной нагрузкой.

        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);
            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 (sum > 0xFFFF)
                {
                    sum = (sum & 0xFFFF) + (sum >> 16);
                }
            }

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

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

            return (ushort)(~sum);
        }

В приведенном выше коде я должен обрабатывать нечетные длины, обнулять исходные поля контрольной суммы и правильно вычислять дополняющие поля. Например, когда я запускаю метод ComputeChecksum(byte[] data) для вычисления контрольной суммы IPv4, он всегда верен, поэтому я не думаю, что это проблема. Кто-то также упомянул, что мне следует поменять порядок 10 и 11 байт, и я также пробовал просто использовать tcpLength >> 8, но все эти попытки также не увенчались успехом. На самом деле, я понятия не имею, как это понять, поскольку не существует руководства, которое действительно делает это с кодом, по крайней мере, из того, что я могу найти. Вот как я вызываю вышеуказанные методы, но понятия не имею, в чем может быть проблема. Возможно, проблема с порядком байтов, поскольку C# использует прямой порядок байтов, а пакеты используют обратный порядок байтов, но я не понимаю, что может быть не так в моем методе ComputeChecksum(byte[] ipv4Header, byte[] transportHeader, byte[] payload), и, как я уже сказал, мой метод ComputeChecksum(byte[] data) вычисляет правильные контрольные суммы IPv4, поэтому я действительно в растерянности.

                        ushort computedTcpChecksum = PacketData.TCPHeader.ComputeChecksum(ipv4HeaderBytes, transportHeaderBytes, payloadBytes);
                        string newTcpChecksumHex = computedTcpChecksum.ToString("X4");

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

PacketDetailsForm SrcIp:192.168.50.134 DstIp:192.168.50.1 Length:94 ClonedIpHeaderLength:24064 ThreadId:2
pseudoHeaderLength:12 transportHeader.Length:20 payloadLength:54 totalLength:86 newTotalLength:86 OddLength:False
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 00 00 00 00 00 00 00 00 00 00 00 00,

C0 A8 32 86 C0 A8 32 01 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 00 00 00 00,

C0 A8 32 86 C0 A8 32 01 00 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 00 00 00 00 00 00 00 00 00,  Protocol:6

C0 A8 32 86 C0 A8 32 01 00 06 00 14 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,

E2 B6 00 35 38 F9 6A 6F 2D F7 BC D5 50 18 04 02 00 00 00 00,

TCPHeader:C0 A8 32 86 C0 A8 32 01 00 06 00 14 E2 B6 00 35 38 F9 6A 6F 2D F7 BC D5 50 18 04 02 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,

Payload:C0 A8 32 86 C0 A8 32 01 00 06 00 14 E2 B6 00 35 38 F9 6A 6F 2D F7 BC D5 50 18 04 02 00 00 00 00 76 37 01 00 00 01 00 00 00 00 00 00 0A 66 75 6E 63 74 69 6F 6E 61 6C 06 65 76 65 6E 74 73 04 64 61 74 61 09 6D 69 63 72 6F 73 6F 66 74 03 63 6F 6D 00 00 01 00 01,

TCP OriginalCheck:58920 E628, OurCheck:48147 BC13 
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
95
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Никто не может гарантировать, что полученные байты представляют собой целое сообщение. Сообщение можно разделить на множество TCP-пакетов. Поэтому при передаче длина сообщения обычно передается первой. В процессе получения сообщения вы должны получить ровно столько байт, сколько указано в начале. При сетевом обмене может возникнуть ситуация, когда у одного из участников возникают проблемы с сетью. Подобные ситуации следует выявлять и периодически пинговать друг друга (не ICMP!).

Скажите, вы действительно хотите все это программировать, как это делали многие поколения программистов до вас? Вы не используете чистый C; ваши потребности включают огромное количество классов с их методами для упрощения всего, что связано с сетевым обменом. Вам не нужно подсчитывать контрольные суммы TCP-пакетов.

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

Если это именно то, что вам нужно, могу порекомендовать прочитать эту статью, в ней достаточно подробно описаны различные варианты взаимодействия с постепенным усложнением, чтобы читатель имел четкое представление о том, что происходит «под капотом».: https: //www.codeproject.com/articles/884583/advanced-tcp-socket-programming-with-net

Я не совсем понимаю, о чем вы? Я мог бы запрограммировать его на C, но это заняло бы гораздо больше времени. Обычно я предпочитаю программировать на C только тогда, когда мне это нужно при создании драйверов ядра. Но в моем сценарии использование WinDivert для фильтрации пакетов и двойной проверки их контрольных сумм должно быть возможно на C#. Я отключил выгрузку контрольной суммы на уровне сетевой карты и могу использовать такие программы, как Wireshark, для проверки контрольных сумм. Ничто не должно мешать мне проверять контрольные суммы, учитывая, что у меня есть массив байтов, особенно что-то, что не приводит к их сбою в 90% случаев.

UnSure 08.06.2024 05:03

@UnSure Я хочу сказать, что вы используете инструмент, предназначенный для того, чтобы скрыть все это и не требующий такой кропотливой работы. Кстати, на мой взгляд, C тоже не лучший кандидат для отвода, тогда как eBPF можно использовать.

DerSkythe 08.06.2024 06:58

@UnSure Возвращаясь к C#, возможно, вам стоит проверить реализацию методов прямого получения байтов, которые вы здесь не предоставили, поскольку описанный метод работает с готовыми массивами байтов.

DerSkythe 08.06.2024 07:04

В основном я программирую драйверы для Windows, которые были разработаны с учетом совместимости с C. Но язык программирования не является проблемой, поскольку C# также имеет доступ к функциям «низкого уровня». Все, что вам нужно сделать, это использовать ключевое слово «unsafe», и вы можете работать с управляемой памятью, которая необходима при выполнении подобных действий. Дело в том, что должна быть возможность правильно вычислить контрольные суммы по массиву байтов всего пакета. Есть ли у вас идеи, где мой метод мог пойти не так? Я уже приводил байты пакета в качестве примера в своем исходном вопросе.

UnSure 08.06.2024 07:10

@UnSure Я думаю, вам следует проверить в других частях вашего кода, как они взаимодействуют, используя TcpClient или прямой Socket, например, есть много параметров и опций, которые могут получить недопустимую сумму, т. е. Socket.Blocking, Socket.DontFragment, Socket.NoDelay, Socket.LingerState и так далее.

DerSkythe 08.06.2024 07:31

Вы уверены, что что-то из этого влияет на фактические вычисления контрольной суммы? Потому что в RFC TCP нет упоминания об этом.

UnSure 08.06.2024 18:16
Ответ принят как подходящий

Однако вы почти все сделали правильно, вы допустили одну небольшую ошибку и забыли добавить + payloadLength к байту с индексом 11. Решение состоит в том, чтобы обновить эту строку до следующей.

pseudoHeaderAndTcpSegment[11] = (byte)((tcpLength + payloadLength) & 0xFF);

Да, это сработало! Я не могу поверить, что это было так просто.

UnSure 09.06.2024 01:38

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