Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 181 additions & 13 deletions Sources/SwiftNetwork/Protocols/IPProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,11 @@ public struct IPProtocol: NetworkProtocol {
}
}

mutating func writeOutboundFrames(_ frames: inout FrameArray) {
mutating func writeOutboundFrames(
_ frames: inout FrameArray,
lower: OutboundDatagramLinkage,
selfReference: ProtocolInstanceReference
) {
frames.iterateMutableFrames { frame in
guard frame.unclaim(fromStart: IPv4Instance.headerLength) else {
frame.finalize(success: false)
Expand Down Expand Up @@ -965,16 +969,42 @@ public struct IPProtocol: NetworkProtocol {
frame.finalize(success: false)
return .removeFrameAndContinue
}
// Make sure the count of fragments is correctly accounted for
let fragmentCount = (payloadLength + fragmentRoom - 1) / fragmentRoom
// Will trim down later to the actual size
let maxFragmentFrameSize = IPv4Instance.headerLength + fragmentRoom
guard
var allocatedFrames = try? lower.invokeGetDatagramsToSend(
selfReference,
maximumDatagramCount: fragmentCount,
minimumDatagramSize: maxFragmentFrameSize
)
else {
frame.finalize(success: false)
return .removeFrameAndContinue
}
var cursor = 0
var fragmentationSucceeded = true
var fragmentFrames = FrameArray()
while cursor < payloadLength {
// Determine if last or hold large the chunk length is
// Determine if last or how large the chunk length is
let remaining = payloadLength - cursor
let isLast = remaining <= fragmentRoom
let chunkLength = isLast ? remaining : fragmentRoom
// Create the fragment frame with this chunk length
var fragmentFrame = Frame(count: IPv4Instance.headerLength + chunkLength)
let fragmentFrameSize = IPv4Instance.headerLength + chunkLength
guard var fragmentFrame = allocatedFrames.popFirst() else {
fragmentationSucceeded = false
break
}
if fragmentFrameSize < maxFragmentFrameSize {
// Trim the frame allocated at the max fragment size down to this (smaller, final) fragment's actual size.
guard fragmentFrame.collapse(to: fragmentFrameSize) else {
fragmentFrame.finalize(success: false)
fragmentationSucceeded = false
break
}
}
// MF bit is always set except for the last fragment
let ipOff = UInt16(isLast ? 0 : 0x2000) | UInt16(cursor / 8)
let fragmentTotalLength = UInt16(IPv4Instance.headerLength + chunkLength)
Expand Down Expand Up @@ -1029,6 +1059,9 @@ public struct IPProtocol: NetworkProtocol {
if fragmentationSucceeded {
return .replaceWithFramesAndContinue(fragmentFrames)
}
if !allocatedFrames.isEmpty {
allocatedFrames.finalizeAllFramesAsFailed()
}
// This is a case where something went wrong on fragmentation and we need to remove any fragments that were created
fragmentFrames.finalizeAllFramesAsFailed()
return .removeFrameAndContinue
Expand Down Expand Up @@ -1663,11 +1696,15 @@ public struct IPProtocol: NetworkProtocol {
}
}

mutating func writeOutboundFrames(_ frames: inout FrameArray) {
frames.iterateMutableFrames { frame in
mutating func writeOutboundFrames(
_ frames: inout FrameArray,
lower: OutboundDatagramLinkage,
selfReference: ProtocolInstanceReference
) {
frames.iterateMutableFrames { (frame: inout Frame) -> FrameArray.FrameIterationResult in
_ = frame.unclaim(fromStart: IPv6Instance.headerLength)

let payloadLength = UInt16(frame.unclaimedLength - IPv6Instance.headerLength)
let payloadLength = frame.unclaimedLength - IPv6Instance.headerLength
let localAddressValue = self.localAddress.addressValue
let remoteAddressValue = self.remoteAddress.addressValue

Expand All @@ -1686,9 +1723,128 @@ public struct IPProtocol: NetworkProtocol {
if dscpValue != 0 {
flow |= UInt32(bigEndian: (UInt32(dscpValue) << 22) & 0x0fc0_0000) // IP6FLOW_DSCP_SHIFT
}

let enableFragmentation: Bool
if let fragmentationOverride = frame.fragmentationOverride {
enableFragmentation = fragmentationOverride
} else {
enableFragmentation = self.flags.enableFragmentation
}

// IPv6 header + Fragment Extension Header
let ipv6CompleteHeaderLength =
IPv6Instance.headerLength + IPv6Instance.fragmentExtensionHeaderLength
let mtu = self.pathProperties.mtu
var maxPayloadPerFragment = 0
if mtu > ipv6CompleteHeaderLength {
maxPayloadPerFragment = mtu - ipv6CompleteHeaderLength
}

// Handle fragmentation if payloadLength is greater than maxPayloadPerFragment and enableFragmentation is enabled
if enableFragmentation && maxPayloadPerFragment > 0 && payloadLength > maxPayloadPerFragment {
var randomNumber = SystemRandomNumberGenerator()
let fragmentID = UInt32(truncatingIfNeeded: randomNumber.next())
// Align fragment payload to blocks of 8 bytes - RFC 2460
let fragmentRoom = maxPayloadPerFragment - (maxPayloadPerFragment % 8)
guard fragmentRoom > 0 else {
frame.finalize(success: false)
return .removeFrameAndContinue
}
// Make sure the count of fragments is correctly accounted for
let fragmentCount = (payloadLength + fragmentRoom - 1) / fragmentRoom
// Will trim down later to the actual size
let maxFragmentFrameSize = ipv6CompleteHeaderLength + fragmentRoom
guard
var allocatedFrames = try? lower.invokeGetDatagramsToSend(
selfReference,
maximumDatagramCount: fragmentCount,
minimumDatagramSize: maxFragmentFrameSize
)
else {
frame.finalize(success: false)
return .removeFrameAndContinue
}
var cursor = 0
var fragmentationSucceeded = true
var fragmentFrames = FrameArray()
while cursor < payloadLength {
// Determine if last or how large the chunk length is
let remaining = payloadLength - cursor
let isLast = remaining <= fragmentRoom
let chunkLength = isLast ? remaining : fragmentRoom
// Fragment Extension Header + this chunk's payload.
let fragmentLength = UInt16(chunkLength + IPv6Instance.fragmentExtensionHeaderLength)
// Fragment offset flags
let offsetFlags = UInt16(cursor) | (isLast ? 0 : UInt16(IPv6Instance.ip6fMoreFragmentMask))
let fragmentFrameSize = ipv6CompleteHeaderLength + chunkLength
guard var fragmentFrame = allocatedFrames.popFirst() else {
fragmentationSucceeded = false
break
}
if fragmentFrameSize < maxFragmentFrameSize {
// Trim the frame allocated at the max fragment size down to this (smaller, final) fragment's actual size.
guard fragmentFrame.collapse(to: fragmentFrameSize) else {
fragmentFrame.finalize(success: false)
fragmentationSucceeded = false
break
}
}
let result = Serializer.serialize(&fragmentFrame, claim: false) {
write throws(SerializationError) in
// IPv6 base header
try write.uint32(flow)
try write.uint16NetworkByteOrder(fragmentLength)
try write.uint8(IPv6Instance.fragmentExtensionHeader)
try write.uint8(self.hopLimit)
try write.uint32(localAddressValue.0)
try write.uint32(localAddressValue.1)
try write.uint32(localAddressValue.2)
try write.uint32(localAddressValue.3)
try write.uint32(remoteAddressValue.0)
try write.uint32(remoteAddressValue.1)
try write.uint32(remoteAddressValue.2)
try write.uint32(remoteAddressValue.3)
// Fragment Extension Header
try write.uint8(self.ipProtocolNumber)
try write.uint8(0)
try write.uint16NetworkByteOrder(offsetFlags)
try write.uint32(fragmentID)
}
guard result.isValid else {
Logger.proto.error("Serializing IPv6 fragment failed with result: \(result)")
fragmentFrame.finalize(success: false)
fragmentationSucceeded = false
break
}
let copied = frame.copyInto(
&fragmentFrame,
atOffset: ipv6CompleteHeaderLength,
fromOffset: IPv6Instance.headerLength + cursor,
length: chunkLength
)
guard copied == chunkLength else {
fragmentFrame.finalize(success: false)
fragmentationSucceeded = false
break
}
self.counters.txPackets += 1
fragmentFrames.add(frame: fragmentFrame)
cursor += chunkLength
}
frame.finalize(success: fragmentationSucceeded)
if fragmentationSucceeded {
return .replaceWithFramesAndContinue(fragmentFrames)
}
if !allocatedFrames.isEmpty {
allocatedFrames.finalizeAllFramesAsFailed()
}
fragmentFrames.finalizeAllFramesAsFailed()
return .removeFrameAndContinue
}
// Standard path
let result = Serializer.serialize(&frame, claim: false) { write throws(SerializationError) in
try write.uint32(flow)
try write.uint16NetworkByteOrder(payloadLength)
try write.uint16NetworkByteOrder(UInt16(payloadLength))
try write.uint8(self.ipProtocolNumber)
try write.uint8(self.hopLimit)
try write.uint32(localAddressValue.0)
Expand All @@ -1702,10 +1858,10 @@ public struct IPProtocol: NetworkProtocol {
}
if !result.isValid {
Logger.proto.error("Serializing IPv6 packet failed with result: \(result)")
return true
return .continueIterating
}
self.counters.txPackets += 1
return true
return .continueIterating
}
}
}
Expand Down Expand Up @@ -1883,7 +2039,14 @@ public struct IPProtocol: NetworkProtocol {
}

mutating func sendDatagrams(_ datagrams: consuming FrameArray) throws(NetworkError) {
IPInstance.processOutbound(&self.instanceType, datagrams: &datagrams)
let lower = self.lower
let selfReference = self.effectiveSelfReference
IPInstance.processOutbound(
&self.instanceType,
lower: lower,
selfReference: selfReference,
datagrams: &datagrams
)
try invokeSendDatagrams(datagrams)
}

Expand All @@ -1904,13 +2067,18 @@ public struct IPProtocol: NetworkProtocol {
}

@inline(__always)
private static func processOutbound(_ instanceType: inout IPInstanceType, datagrams: inout FrameArray) {
private static func processOutbound(
_ instanceType: inout IPInstanceType,
lower: OutboundDatagramLinkage,
selfReference: ProtocolInstanceReference,
datagrams: inout FrameArray
) {
switch instanceType {
case .ipv4(var instance):
instance.writeOutboundFrames(&datagrams)
instance.writeOutboundFrames(&datagrams, lower: lower, selfReference: selfReference)
instanceType = .ipv4(instance)
case .ipv6(var instance):
instance.writeOutboundFrames(&datagrams)
instance.writeOutboundFrames(&datagrams, lower: lower, selfReference: selfReference)
instanceType = .ipv6(instance)
}
}
Expand Down
Loading
Loading