Skip to content

Commit 0498fed

Browse files
committed
Add IPv4Header and tests for .ip_hdrincl
1 parent 9ce6089 commit 0498fed

File tree

3 files changed

+363
-83
lines changed

3 files changed

+363
-83
lines changed

Sources/NIOCore/ChannelOption.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ public struct ChannelOptions {
276276
public static let socketOption = { (name: NIOBSDSocket.Option) -> Types.SocketOption in
277277
.init(level: .socket, name: name)
278278
}
279+
280+
/// - seealso: `SocketOption`.
281+
public static let ipOption = { (name: NIOBSDSocket.Option) -> Types.SocketOption in
282+
.init(level: .ip, name: name)
283+
}
279284

280285
/// - seealso: `SocketOption`.
281286
public static let tcpOption = { (name: NIOBSDSocket.Option) -> Types.SocketOption in
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the SwiftNIO project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import NIOCore
16+
17+
struct IPv4Address: Hashable {
18+
var rawValue: UInt32
19+
}
20+
21+
extension IPv4Address {
22+
init(_ v1: UInt8, _ v2: UInt8, _ v3: UInt8, _ v4: UInt8) {
23+
rawValue = UInt32(v1) << 24 | UInt32(v2) << 16 | UInt32(v3) << 8 | UInt32(v4)
24+
}
25+
}
26+
27+
extension IPv4Address: CustomStringConvertible {
28+
var description: String {
29+
let v1 = rawValue >> 24
30+
let v2 = rawValue >> 16 & 0b1111_1111
31+
let v3 = rawValue >> 8 & 0b1111_1111
32+
let v4 = rawValue & 0b1111_1111
33+
return "\(v1).\(v2).\(v3).\(v4)"
34+
}
35+
}
36+
37+
struct IPv4Header: Hashable {
38+
static let size: Int = 20
39+
40+
private var versionAndIhl: UInt8
41+
var version: UInt8 {
42+
get {
43+
versionAndIhl >> 4
44+
}
45+
set {
46+
precondition(newValue & 0b1111_0000 == 0)
47+
versionAndIhl = newValue << 4 | (0b0000_1111 & versionAndIhl)
48+
assert(newValue == version, "\(newValue) != \(version) \(versionAndIhl)")
49+
}
50+
}
51+
var internetHeaderLength: UInt8 {
52+
get {
53+
versionAndIhl & 0b0000_1111
54+
}
55+
set {
56+
precondition(newValue & 0b1111_0000 == 0)
57+
versionAndIhl = newValue | (0b1111_0000 & versionAndIhl)
58+
assert(newValue == internetHeaderLength)
59+
}
60+
}
61+
private var dscpAndEcn: UInt8
62+
var differentiatedServicesCodePoint: UInt8 {
63+
get {
64+
dscpAndEcn >> 2
65+
}
66+
set {
67+
precondition(newValue & 0b0000_0011 == 0)
68+
dscpAndEcn = newValue << 2 | (0b0000_0011 & dscpAndEcn)
69+
assert(newValue == differentiatedServicesCodePoint)
70+
}
71+
}
72+
var explicitCongestionNotification: UInt8 {
73+
get {
74+
dscpAndEcn & 0b0000_0011
75+
}
76+
set {
77+
precondition(newValue & 0b0000_0011 == 0)
78+
dscpAndEcn = newValue | (0b1111_1100 & dscpAndEcn)
79+
assert(newValue == explicitCongestionNotification)
80+
}
81+
}
82+
var totalLength: UInt16
83+
var identification: UInt16
84+
private var flagsAndFragmentOffset: UInt16
85+
var flags: UInt8 {
86+
get {
87+
UInt8(flagsAndFragmentOffset >> 13)
88+
}
89+
set {
90+
precondition(newValue & 0b0000_0111 == 0)
91+
flagsAndFragmentOffset = UInt16(newValue) << 13 | (0b0001_1111_1111_1111 & flagsAndFragmentOffset)
92+
assert(newValue == flags)
93+
}
94+
}
95+
var fragmentOffset: UInt16 {
96+
get {
97+
flagsAndFragmentOffset & 0b0001_1111_1111_1111
98+
}
99+
set {
100+
precondition(newValue & 0b1110_0000_0000_0000 == 0)
101+
flagsAndFragmentOffset = newValue | (0b1110_0000_0000_0000 & flagsAndFragmentOffset)
102+
assert(newValue == fragmentOffset)
103+
}
104+
}
105+
var timeToLive: UInt8
106+
var `protocol`: NIOIPProtocol
107+
var headerChecksum: UInt16
108+
var sourceIpAddress: IPv4Address
109+
var destinationIpAddress: IPv4Address
110+
111+
init?(buffer: inout ByteBuffer) {
112+
#if canImport(Darwin)
113+
guard
114+
let versionAndIhl: UInt8 = buffer.readInteger(),
115+
let dscpAndEcn: UInt8 = buffer.readInteger(),
116+
// On BSD, the total length is in host byte order
117+
let totalLength: UInt16 = buffer.readInteger(endianness: .host),
118+
let identification: UInt16 = buffer.readInteger(),
119+
// fragmentOffset is in host byte order as well but it is always zero in our tests
120+
// and fragmentOffset is 13 bits in size so we can't just use readInteger(endianness: .host)
121+
let flagsAndFragmentOffset: UInt16 = buffer.readInteger(),
122+
let timeToLive: UInt8 = buffer.readInteger(),
123+
let `protocol`: UInt8 = buffer.readInteger(),
124+
let headerChecksum: UInt16 = buffer.readInteger(),
125+
let sourceIpAddress: UInt32 = buffer.readInteger(),
126+
let destinationIpAddress: UInt32 = buffer.readInteger()
127+
else { return nil }
128+
#elseif os(Linux)
129+
guard let (
130+
versionAndIhl,
131+
dscpAndEcn,
132+
totalLength,
133+
identification,
134+
flagsAndFragmentOffset,
135+
timeToLive,
136+
`protocol`,
137+
headerChecksum,
138+
sourceIpAddress,
139+
destinationIpAddress
140+
) = buffer.readMultipleIntegers(as: (
141+
UInt8,
142+
UInt8,
143+
UInt16,
144+
UInt16,
145+
UInt16,
146+
UInt8,
147+
UInt8,
148+
UInt16,
149+
UInt32,
150+
UInt32
151+
).self) else { return nil }
152+
#endif
153+
self.versionAndIhl = versionAndIhl
154+
self.dscpAndEcn = dscpAndEcn
155+
self.totalLength = totalLength
156+
self.identification = identification
157+
self.flagsAndFragmentOffset = flagsAndFragmentOffset
158+
self.timeToLive = timeToLive
159+
self.`protocol` = .init(rawValue: `protocol`)
160+
self.headerChecksum = headerChecksum
161+
self.sourceIpAddress = .init(rawValue: sourceIpAddress)
162+
self.destinationIpAddress = .init(rawValue: destinationIpAddress)
163+
}
164+
init() {
165+
self.versionAndIhl = 0
166+
self.dscpAndEcn = 0
167+
self.totalLength = 0
168+
self.identification = 0
169+
self.flagsAndFragmentOffset = 0
170+
self.timeToLive = 0
171+
self.`protocol` = .init(rawValue: 0)
172+
self.headerChecksum = 0
173+
self.sourceIpAddress = .init(rawValue: 0)
174+
self.destinationIpAddress = .init(rawValue: 0)
175+
}
176+
177+
mutating func setChecksum() {
178+
self.headerChecksum = ~[
179+
UInt16(versionAndIhl) << 8 | UInt16(dscpAndEcn),
180+
totalLength,
181+
identification,
182+
flagsAndFragmentOffset,
183+
UInt16(timeToLive) << 8 | UInt16(`protocol`.rawValue),
184+
UInt16(sourceIpAddress.rawValue >> 16),
185+
UInt16(sourceIpAddress.rawValue & 0b0000_0000_0000_0000_1111_1111_1111_1111),
186+
UInt16(destinationIpAddress.rawValue >> 16),
187+
UInt16(destinationIpAddress.rawValue & 0b0000_0000_0000_0000_1111_1111_1111_1111),
188+
].reduce(UInt16(0), onesComplementAdd)
189+
190+
assert(isValidChecksum())
191+
}
192+
193+
func isValidChecksum() -> Bool {
194+
let sum = ~[
195+
UInt16(versionAndIhl) << 8 | UInt16(dscpAndEcn),
196+
totalLength,
197+
identification,
198+
flagsAndFragmentOffset,
199+
UInt16(timeToLive) << 8 | UInt16(`protocol`.rawValue),
200+
headerChecksum,
201+
UInt16(sourceIpAddress.rawValue >> 16),
202+
UInt16(sourceIpAddress.rawValue & 0b0000_0000_0000_0000_1111_1111_1111_1111),
203+
UInt16(destinationIpAddress.rawValue >> 16),
204+
UInt16(destinationIpAddress.rawValue & 0b0000_0000_0000_0000_1111_1111_1111_1111),
205+
].reduce(UInt16(0), onesComplementAdd)
206+
return sum == 0
207+
}
208+
209+
func write(to buffer: inout ByteBuffer) {
210+
assert({
211+
var buffer = ByteBuffer()
212+
self._write(to: &buffer)
213+
let newValue = Self(buffer: &buffer)
214+
return self == newValue
215+
}())
216+
self._write(to: &buffer)
217+
}
218+
219+
private func _write(to buffer: inout ByteBuffer) {
220+
#if canImport(Darwin)
221+
buffer.writeInteger(versionAndIhl)
222+
buffer.writeInteger(dscpAndEcn)
223+
// On BSD, the total length needs to be in host byte order
224+
buffer.writeInteger(totalLength, endianness: .host)
225+
buffer.writeInteger(identification)
226+
// fragmentOffset needs to be in host byte order as well but it is always zero in our tests
227+
// and fragmentOffset is 13 bits in size so we can't just use writeInteger(endianness: .host)
228+
buffer.writeInteger(flagsAndFragmentOffset)
229+
buffer.writeInteger(timeToLive)
230+
buffer.writeInteger(`protocol`.rawValue)
231+
buffer.writeInteger(headerChecksum)
232+
buffer.writeInteger(sourceIpAddress.rawValue)
233+
buffer.writeInteger(destinationIpAddress.rawValue)
234+
#elseif os(Linux)
235+
buffer.writeMultipleIntegers(
236+
versionAndIhl,
237+
dscpAndEcn,
238+
totalLength,
239+
identification,
240+
flagsAndFragmentOffset,
241+
timeToLive,
242+
`protocol`.rawValue,
243+
headerChecksum,
244+
sourceIpAddress.rawValue,
245+
destinationIpAddress.rawValue
246+
)
247+
#endif
248+
}
249+
}
250+
251+
private func onesComplementAdd<Integer: FixedWidthInteger>(lhs: Integer, rhs: Integer) -> Integer {
252+
var (sum, overflowed) = lhs.addingReportingOverflow(rhs)
253+
if overflowed {
254+
sum &+= 1
255+
}
256+
return sum
257+
}
258+
259+
extension IPv4Header: CustomStringConvertible {
260+
var description: String {
261+
"""
262+
Version: \(version)
263+
Header Length: \(internetHeaderLength * 4) bytes
264+
Differentiated Services: \(String(differentiatedServicesCodePoint, radix: 2))
265+
Explicit Congestion Notification: \(String(explicitCongestionNotification, radix: 2))
266+
Total Length: \(totalLength) bytes
267+
Identification: \(identification)
268+
Flags: \(String(flags, radix: 2))
269+
Fragment Offset: \(fragmentOffset) Bytes
270+
Time to Live: \(timeToLive)
271+
Protocol: \(`protocol`)
272+
Header Checksum: \(headerChecksum) (\(isValidChecksum() ? "valid" : "*not* valid"))
273+
Source IP Address: \(sourceIpAddress)
274+
Destination IP Address: \(destinationIpAddress)
275+
"""
276+
}
277+
}

0 commit comments

Comments
 (0)