1 // 2 // Copyright (c) 2010-2025 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Collections.Generic; 9 using System.Linq; 10 using System.Net.NetworkInformation; 11 using Antmicro.Renode.Core.Structure; 12 using Antmicro.Renode.Logging; 13 using Antmicro.Renode.Network; 14 using Antmicro.Renode.Utilities.Packets; 15 using MiscUtil.Conversion; 16 using PacketDotNet; 17 18 using IPProtocolType = Antmicro.Renode.Network.IPProtocolType; 19 20 namespace Antmicro.Renode.Peripherals.Network 21 { 22 public partial class SynopsysDWCEthernetQualityOfService 23 { 24 private static readonly EtherType[] checksumOffloadEngineIpHeaderTypes = 25 { 26 EtherType.IpV4, 27 }; 28 29 private static readonly IPProtocolType[] checksumOffloadEnginePseudoHeaderTypes = 30 { 31 IPProtocolType.TCP, 32 IPProtocolType.UDP, 33 IPProtocolType.ICMP, 34 }; 35 36 private class FrameAssembler 37 { FrameAssembler(IEmulationElement parent, CRCPadOperation crcPadControl, ChecksumOperation checksumControl, Action<EthernetFrame> frameReady)38 public FrameAssembler(IEmulationElement parent, CRCPadOperation crcPadControl, ChecksumOperation checksumControl, Action<EthernetFrame> frameReady) 39 : this(parent, null, crcPadControl, checksumControl, 0, frameReady, null) 40 { 41 } 42 FrameAssembler(IEmulationElement parent, byte[] header, uint defaultMaximumSegmentSize, TxDescriptor.ContextDescriptor? context, bool enableChecksumOffload, Action<EthernetFrame> frameReady, MACAddress? sourceMACAddress)43 public FrameAssembler(IEmulationElement parent, byte[] header, uint defaultMaximumSegmentSize, TxDescriptor.ContextDescriptor? context, bool enableChecksumOffload, Action<EthernetFrame> frameReady, MACAddress? sourceMACAddress) 44 : this(parent, header, CRCPadOperation.InsetCRCAndPad, enableChecksumOffload ? ChecksumOperation.InsertHeaderPayloadAndPseudoHeaderChecksum : ChecksumOperation.None, 45 context.HasValue && context.Value.oneStepTimestampCorrectionInputOrMaximumSegmentSizeValid ? context.Value.maximumSegmentSize : defaultMaximumSegmentSize, frameReady, sourceMACAddress) 46 { 47 if(header.Length < EthernetFields.HeaderLength) 48 { 49 parent.Log(LogLevel.Error, "TCP Segmentation Offload: Header is too small, the result may be unpredictable."); 50 header = null; 51 return; 52 } 53 etherType = (EtherType)EndianBitConverter.Big.ToInt16(header, EthernetFields.TypePosition); 54 if(etherType != EtherType.IpV4 && etherType != EtherType.IpV6) 55 { 56 parent.Log(LogLevel.Error, "TCP Segmentation Offload: Invalid Ethernet Type, the result may be unpredictable."); 57 return; 58 } 59 if(header.Length < EthernetFields.HeaderLength + (etherType == EtherType.IpV4 ? IPv4Fields.HeaderLength : IPv6Fields.HeaderLength)) 60 { 61 parent.Log(LogLevel.Error, "TCP Segmentation Offload: Header is too small, the result may be unpredictable."); 62 header = null; 63 return; 64 } 65 } 66 PushPayload(byte[] payloadSegment)67 public void PushPayload(byte[] payloadSegment) 68 { 69 if(payloadSegment.Length == 0) 70 { 71 return; 72 } 73 totalPayloadLength += (uint)payloadSegment.Length; 74 if(!SegmentationActive || totalPayloadLength < maximumSegmentSize || maximumSegmentSize == 0) 75 { 76 payloadSegments.Enqueue(payloadSegment); 77 return; 78 } 79 totalPayloadLength %= maximumSegmentSize; 80 var saveLast = totalPayloadLength != 0; 81 82 // Divide payload into MSS segments + optional reminding segment 83 var i = 0; 84 var segments = payloadSegments 85 .SelectMany(x => x) // flatten 86 .Concat(payloadSegment) 87 .GroupBy(_ => i++ / maximumSegmentSize) // group into MSS chunks 88 .ToArray(); 89 90 payloadSegments.Clear(); 91 92 if(saveLast) 93 { 94 payloadSegments.Enqueue(segments.Last().ToArray()); 95 } 96 97 foreach(var segment in segments.Take(segments.Length + (saveLast ? -1 : 0))) 98 { 99 FinalizeSegment(segment, maximumSegmentSize); 100 } 101 } 102 FinalizeAssembly()103 public void FinalizeAssembly() 104 { 105 if(totalPayloadLength == 0) 106 { 107 return; 108 } 109 FinalizeSegment(payloadSegments.SelectMany(x => x), totalPayloadLength, true); 110 } 111 FrameAssembler(IEmulationElement parent, byte[] header, CRCPadOperation crcPadControl, ChecksumOperation checksumControl, uint maximumSegmentSize, Action<EthernetFrame> frameReady, MACAddress? sourceMACAddress)112 private FrameAssembler(IEmulationElement parent, byte[] header, CRCPadOperation crcPadControl, ChecksumOperation checksumControl, uint maximumSegmentSize, Action<EthernetFrame> frameReady, MACAddress? sourceMACAddress) 113 { 114 this.parent = parent; 115 tcpHeader = header; 116 this.sourceMACAddress = sourceMACAddress; 117 118 padEthernetFrame = false; 119 120 switch(crcPadControl) 121 { 122 case CRCPadOperation.ReplaceCRC: 123 crcMode = CRCMode.Replace; 124 break; 125 case CRCPadOperation.None: 126 crcMode = CRCMode.Keep; 127 break; 128 case CRCPadOperation.InsetCRCAndPad: 129 padEthernetFrame = true; 130 goto case CRCPadOperation.InsertCRC; 131 case CRCPadOperation.InsertCRC: 132 crcMode = CRCMode.Add; 133 break; 134 default: 135 throw new Exception("Unreachable"); 136 } 137 138 switch(checksumControl) 139 { 140 case ChecksumOperation.None: 141 break; 142 case ChecksumOperation.InsertHeaderChecksum: 143 checksumTypes = null; 144 break; 145 case ChecksumOperation.InsertHeaderAndPayloadChecksum: 146 parent.Log(LogLevel.Warning, "Checksum Insertion Control: Calculating checksum with precalculated pseudo-header (0b10) is not supported. Falling back to calculating checksum with pseduo-header (0b11)."); 147 checksumControl = ChecksumOperation.InsertHeaderAndPayloadChecksum; 148 goto case ChecksumOperation.InsertHeaderPayloadAndPseudoHeaderChecksum; 149 case ChecksumOperation.InsertHeaderPayloadAndPseudoHeaderChecksum: 150 checksumTypes = checksumOffloadEnginePseudoHeaderTypes; 151 break; 152 default: 153 throw new Exception("Unreachable"); 154 } 155 checksumOp = checksumControl; 156 157 if(SegmentationActive && maximumSegmentSize == 0) 158 { 159 parent.Log(LogLevel.Error, "TCP Segmentation Offload: Ignoring invalid Maximum Segment Size value: {0}", maximumSegmentSize); 160 } 161 this.maximumSegmentSize = maximumSegmentSize; 162 163 this.frameReady = frameReady; 164 payloadSegments = new Queue<byte[]>(); 165 } 166 FinalizeSegment(IEnumerable<byte> frame, uint length, bool isLast = false)167 private void FinalizeSegment(IEnumerable<byte> frame, uint length, bool isLast = false) 168 { 169 if(TryCreateEthernetFrame(tcpHeader?.Concat(frame) ?? frame, length + (uint?)tcpHeader?.Length ?? 0, out var builtFrame, isLast)) 170 { 171 frameReady(builtFrame); 172 packetsFinalized += 1; 173 } 174 else 175 { 176 parent.Log(LogLevel.Error, "Failed to create EthernetFrame"); 177 } 178 } 179 TryCreateEthernetFrame(IEnumerable<byte> frame, uint length, out EthernetFrame builtFrame, bool isLast)180 private bool TryCreateEthernetFrame(IEnumerable<byte> frame, uint length, out EthernetFrame builtFrame, bool isLast) 181 { 182 if(padEthernetFrame && length < MinimalLength) 183 { 184 frame = frame.Concat(Enumerable.Repeat<byte>(0, MinimalLength - (int)length)); 185 } 186 var frameArray = frame.ToArray(); 187 if(SegmentationActive) 188 { 189 // Update length field before packet creation to workaround Packet.Net asserts 190 var ipLength = length - EthernetFields.HeaderLength; 191 if(etherType == EtherType.IpV4) 192 { 193 EndianBitConverter.Big.CopyBytes((ushort)ipLength, frameArray, EthernetFields.HeaderLength + IPv4Fields.TotalLengthPosition); 194 } 195 else if(etherType == EtherType.IpV6) 196 { 197 var ipv6length = length - EthernetFields.HeaderLength; 198 EndianBitConverter.Big.CopyBytes((ushort)ipLength, frameArray, EthernetFields.HeaderLength + IPv6Fields.PayloadLengthPosition); 199 } 200 } 201 try 202 { 203 if(!EthernetFrame.TryCreateEthernetFrame(frameArray, crcMode, out builtFrame)) 204 { 205 return false; 206 } 207 if(SegmentationActive && builtFrame.UnderlyingPacket.PayloadPacket.PayloadPacket is TcpPacket tcpPacket) 208 { 209 var isFirst = packetsFinalized == 0; 210 if(!isFirst && etherType == EtherType.IpV4) 211 { 212 ((IPv4Packet)builtFrame.UnderlyingPacket.PayloadPacket).Id += (ushort)packetsFinalized; 213 } 214 if(!isFirst) 215 { 216 tcpPacket.SequenceNumber += packetsFinalized * maximumSegmentSize; 217 } 218 if(!isLast) 219 { 220 tcpPacket.Fin = false; 221 tcpPacket.Psh = false; 222 } 223 } 224 if(checksumOp != ChecksumOperation.None) 225 { 226 builtFrame.FillWithChecksums(checksumOffloadEngineIpHeaderTypes, checksumTypes, crcMode != CRCMode.Keep); 227 } 228 if(sourceMACAddress.HasValue) 229 { 230 builtFrame.UnderlyingPacket.SourceHwAddress = (PhysicalAddress)sourceMACAddress.Value; 231 } 232 return true; 233 } 234 catch(Exception e) 235 { 236 builtFrame = null; 237 parent.Log(LogLevel.Error, "Underlying packet processing framework failed to create Ethernet frame: {0}", e); 238 } 239 return false; 240 } 241 242 private bool SegmentationActive => tcpHeader != null; 243 244 private uint totalPayloadLength; 245 private uint packetsFinalized; 246 247 private readonly IEmulationElement parent; 248 private readonly MACAddress? sourceMACAddress; 249 private readonly byte[] tcpHeader; 250 private readonly CRCMode crcMode; 251 private readonly bool padEthernetFrame; 252 private readonly ChecksumOperation checksumOp; 253 private readonly IPProtocolType[] checksumTypes; 254 private readonly uint maximumSegmentSize; 255 private readonly Action<EthernetFrame> frameReady; 256 private readonly Queue<byte[]> payloadSegments; 257 private readonly EtherType etherType; 258 259 private const int MinimalLength = 60; 260 } 261 } 262 } 263