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