Недавно я задал аналогичный вопрос, но отметил его как решенный, так как думал, что нашел ответ, хотя на самом деле мой код работал только с конкретными случаями, пропуская при этом большую часть случаев. В любом случае я использую 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
Никто не может гарантировать, что полученные байты представляют собой целое сообщение. Сообщение можно разделить на множество TCP-пакетов. Поэтому при передаче длина сообщения обычно передается первой. В процессе получения сообщения вы должны получить ровно столько байт, сколько указано в начале. При сетевом обмене может возникнуть ситуация, когда у одного из участников возникают проблемы с сетью. Подобные ситуации следует выявлять и периодически пинговать друг друга (не ICMP!).
Скажите, вы действительно хотите все это программировать, как это делали многие поколения программистов до вас? Вы не используете чистый C; ваши потребности включают огромное количество классов с их методами для упрощения всего, что связано с сетевым обменом. Вам не нужно подсчитывать контрольные суммы TCP-пакетов.
Упрощенная задача сетевого обмена через сокеты сводится к передаче структуры, класса или записи, предварительно сериализовав ее в бинарный вид.
Если это именно то, что вам нужно, могу порекомендовать прочитать эту статью, в ней достаточно подробно описаны различные варианты взаимодействия с постепенным усложнением, чтобы читатель имел четкое представление о том, что происходит «под капотом».: https: //www.codeproject.com/articles/884583/advanced-tcp-socket-programming-with-net
@UnSure Я хочу сказать, что вы используете инструмент, предназначенный для того, чтобы скрыть все это и не требующий такой кропотливой работы. Кстати, на мой взгляд, C тоже не лучший кандидат для отвода, тогда как eBPF можно использовать.
@UnSure Возвращаясь к C#, возможно, вам стоит проверить реализацию методов прямого получения байтов, которые вы здесь не предоставили, поскольку описанный метод работает с готовыми массивами байтов.
В основном я программирую драйверы для Windows, которые были разработаны с учетом совместимости с C. Но язык программирования не является проблемой, поскольку C# также имеет доступ к функциям «низкого уровня». Все, что вам нужно сделать, это использовать ключевое слово «unsafe», и вы можете работать с управляемой памятью, которая необходима при выполнении подобных действий. Дело в том, что должна быть возможность правильно вычислить контрольные суммы по массиву байтов всего пакета. Есть ли у вас идеи, где мой метод мог пойти не так? Я уже приводил байты пакета в качестве примера в своем исходном вопросе.
@UnSure Я думаю, вам следует проверить в других частях вашего кода, как они взаимодействуют, используя TcpClient
или прямой Socket
, например, есть много параметров и опций, которые могут получить недопустимую сумму, т. е. Socket.Blocking
, Socket.DontFragment
, Socket.NoDelay
, Socket.LingerState
и так далее.
Вы уверены, что что-то из этого влияет на фактические вычисления контрольной суммы? Потому что в RFC TCP нет упоминания об этом.
Однако вы почти все сделали правильно, вы допустили одну небольшую ошибку и забыли добавить + payloadLength
к байту с индексом 11. Решение состоит в том, чтобы обновить эту строку до следующей.
pseudoHeaderAndTcpSegment[11] = (byte)((tcpLength + payloadLength) & 0xFF);
Да, это сработало! Я не могу поверить, что это было так просто.
Я не совсем понимаю, о чем вы? Я мог бы запрограммировать его на C, но это заняло бы гораздо больше времени. Обычно я предпочитаю программировать на C только тогда, когда мне это нужно при создании драйверов ядра. Но в моем сценарии использование WinDivert для фильтрации пакетов и двойной проверки их контрольных сумм должно быть возможно на C#. Я отключил выгрузку контрольной суммы на уровне сетевой карты и могу использовать такие программы, как Wireshark, для проверки контрольных сумм. Ничто не должно мешать мне проверять контрольные суммы, учитывая, что у меня есть массив байтов, особенно что-то, что не приводит к их сбою в 90% случаев.