我找不到IPv4头校验和计算的实现。所以,我决定按照RFC 791中的定义来做一个。
我使用了“更新的”ReadOnlySpan类来对事物进行一些优化。感谢你的反馈!
注意:这是两种扩展方法,用于说明在读取IPv4数据包中包含的字节的上下文中只需要计算校验和的两个用例。它们生成API来公开私有方法(如下所示)。它们只是通过忽略第10字节(头校验和)来区别。
public static ushort GetInternetChecksum(this ReadOnlySpan bytes)
=> CalculateChecksum(bytes, ignoreHeaderChecksum: true);public static bool IsValidChecksum(this ReadOnlySpan bytes)
// Should equal zero (valid)
=> CalculateChecksum(bytes, ignoreHeaderChecksum: false) == 0;using System.Buffers.Binary;
///
/// An implementation of the IPv4 header checksum calculation,
/// as defined in RFC 791.
///
private static ushort CalculateChecksum(
ReadOnlySpan bytes,
bool ignoreHeaderChecksum)
{
ushort checksum = 0;
for (int i = 0; i <= 18; i += 2)
{
// Ignore the Header Checksum?
if (ignoreHeaderChecksum && i == 10) continue;
// Use network byte order
ushort value = BinaryPrimitives.ReadUInt16BigEndian(bytes[i..(i + 2)]);
// Each time a carry occurs, we must add a 1 to the sum
if (checksum + value > ushort.MaxValue)
{
checksum++;
}
checksum += value;
}
// One’s complement
return (ushort)~checksum;
}关于上述循环的详细说明:
i = 0 e.g. bytes[0..2] // Version and Internal Header Length
i = 2 e.g. bytes[2..4] // Total Length
i = 4 e.g. bytes[4..6] // Identification
i = 6 e.g. bytes[6..8] // Flags and Fragmentation Offset
i = 8 e.g. bytes[8..10] // TTL and Protocol
i = 10 e.g. bytes[10..12] // ***Header Checksum***
i = 12 e.g. bytes[12..14] // Source Address #1
i = 14 e.g. bytes[14..16] // Source Address #2
i = 16 e.g. bytes[16..18] // Destination Address #1
i = 18 e.g. bytes[18..20] // Destination Address #2
// Note: We should ignore the Header Checksum value
// when calculating the checksum, and include
// it back in when verifying.<#>EDIT:我在扩展方法部分(上面)添加了一些额外的上下文,我还添加了一个用例(下面)。
CapturePacketsAsync()片段
PipeOptions options = new(
pauseWriterThreshold: 65535, // 64kB
resumeWriterThreshold: 1400); // or MTU - TCP/IP Headers
Pipe pipe = new(options);
Task writing = WriteToPipeAsync(pipe.Writer, _socket, cancellationToken);
Task reading = ReadFromPipeAsync(pipe.Reader, cancellationToken);
await Task.WhenAll(reading, writing).ConfigureAwait(true);WriteToPipeAsync()片段
while(!cancellationToken.IsCancellationRequested)
{
const int minimumBufferSize = 65535;
Memory buffer = writer.GetMemory(minimumBufferSize);
_ = await socket.ReceiveAsync(buffer, socketFlags, cancellationToken);
// We can perform either of the checksum methods here
// - use GetInternetChecksum() if we are sending
// - use IsValidChecksum() if we are receiving
// Then handle any invalid packets
writer.Advance(bytesReceived);
// try/finally, mark pipe complete via CompleteAsync(), etc.
}ReadFromPipeAsync()片段
while(!cancellationToken.IsCancellationRequested)
{
ReadResult result = await reader.ReadAsync(cancellationToken).ConfigureAwait(true);
ReadOnlySequence buffer = result.Buffer;
// We can perform either of the checksum methods here
// - use GetInternetChecksum() if we are sending
// - use IsValidChecksum() if we are receiving
// Then handle any invalid packets
reader.AdvanceTo(consumed, examined);
// try/finally, mark pipe complete via CompleteAsync(), etc.
}在上述情况下,进行校验和计算的选择将取决于您希望如何管理管道两侧的压力。
发布于 2022-07-02 16:13:18
这是一段有趣的代码,我很高兴它适用于您的特定用法,但在一般情况下,我发现它缺乏。
首先,您有零检查来验证输入的bytes表单(一个有效的IPv4报头)。一个人所要做的就是传递一个任意长度的ReadOnlySpan,包括0长度。
我还发现它有一定的限制性,因为您将输入限制为ReadOnlySpan。如果某人有一个数组或一个列表,他们就倒霉了。我倾向于这样一种理念,即公共投入参数应尽可能广泛地提供。这意味着IEnumerable的投入将具有最广泛的吸引力。但是,无论输入是ReadOnlySpan、byte[]、List还是任何可枚举的byte集合,都需要对输入进行验证,以确定它是否形成了合适的D7头。
虽然主要的计算是大量的数学运算,并且应该是快速的,但是我发现关于IsValidChecksum的一些效率很低。我想弄清楚你会怎么用它。以下是两种可能的方法:
Branch 1:
var checksum = GetInternetChecksum(bytes);
if (IsValidChecksum(bytes)) // <== calculated twice
{
// do something
}Branch 2:
if (IsValidChecksum(bytes))
{
var checksum = GetInternetChecksum(bytes); // <== calculated twice
// do something
}你看到的是,在这2种可能的用法中,你必须执行两次计算,这只是看起来不对。
发布于 2022-07-03 09:06:19
除了“里克-达文”,
GetInternetChecksum、IsValidChecksum和CalculateChecksum,它们不知道哪个type of data将验证或计算它的校验和。如果您只让它们是私有的,并且只为IPAddress提供了在内部使用它们的公共扩展方法,那么它可能会更好,因为将范围缩小到特定类型,并且还可以节省验证IP的工作和时间,您可能只需要根据特定的IP方案进行验证,如果需要的话,它还将打开许多其他受.NET支持的方案。
而且,由于字节大小和位置是固定的,所以可能使用一个以可读形式表示地址的类将使使用起来更加容易,并且只需要验证一次。
就像这样:
public class IPAddressChecksumValidator
{
private readonly IPAddressEntry _entry;
private class IPAddressEntry
{
public byte Version { get; set; }
public byte TotalLength { get; set; }
public byte Identification { get; set; }
// ..etc.
public IPAddressEntry(byte[] entry)
{
/// assign bytes to each property
}
}
public IPAddressChecksumValidator(IPAddress ipAddress)
{
// initialize and validate IPAddress version
}
public ushort GetInternetChecksum() {
// use _entry to get the result
}
public bool IsValid() {
// use _entry to get the result
}
}
public static class IPAddressExtension
{
public static IPAddressClass GetValidator(this IPAddress ipAddress)
=> new IPAddressChecksumValidator(ipAddress);
}用法示例:
var ipAddress = new IPAddress(bytes);
var checksumValidator = ipAddress.GetValidator();
// or new IPAddressChecksumValidator(ipAddress);
if(checksumValidator.IsValid())
{
// do stuff
}https://codereview.stackexchange.com/questions/277811
复制相似问题