blob: 38bdbb2fbc5a4241604d08178d374f141e3a2dd6 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
//
// ICMP.swift
// PacketTunnelCore
//
// Created by Andrew Bulhak on 2024-07-03.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
import Foundation
struct ICMP {
public enum Error: LocalizedError {
case malformedResponse(MalformedResponseReason)
public var errorDescription: String? {
switch self {
case let .malformedResponse(reason):
return "Malformed response: \(reason)."
}
}
}
public enum MalformedResponseReason {
case ipv4PacketTooSmall
case icmpHeaderTooSmall
case invalidIPVersion
case checksumMismatch(UInt16, UInt16)
}
private static func in_chksum(_ data: some Sequence<UInt8>) -> UInt16 {
var iterator = data.makeIterator()
var words = [UInt16]()
while let byte = iterator.next() {
let nextByte = iterator.next() ?? 0
let word = UInt16(byte) << 8 | UInt16(nextByte)
words.append(word)
}
let sum = words.reduce(0, &+)
return ~sum
}
static func createICMPPacket(identifier: UInt16, sequenceNumber: UInt16) -> Data {
var header = ICMPHeader(
type: UInt8(ICMP_ECHO),
code: 0,
checksum: 0,
identifier: identifier.bigEndian,
sequenceNumber: sequenceNumber.bigEndian
)
header.checksum = withUnsafeBytes(of: &header) { in_chksum($0).bigEndian }
return withUnsafeBytes(of: &header) { Data($0) }
}
static func parseICMPResponse(buffer: inout [UInt8], length: Int) throws -> ICMPHeader {
try buffer.withUnsafeMutableBytes { bufferPointer in
// Check IP packet size.
guard length >= MemoryLayout<IPv4Header>.size else {
throw Error.malformedResponse(.ipv4PacketTooSmall)
}
// Verify IPv4 header.
let ipv4Header = bufferPointer.load(as: IPv4Header.self)
let payloadLength = length - ipv4Header.headerLength
guard payloadLength >= MemoryLayout<ICMPHeader>.size else {
throw Error.malformedResponse(.icmpHeaderTooSmall)
}
guard ipv4Header.isIPv4Version else {
throw Error.malformedResponse(.invalidIPVersion)
}
// Parse ICMP header.
let icmpHeaderPointer = bufferPointer.baseAddress!
.advanced(by: ipv4Header.headerLength)
.assumingMemoryBound(to: ICMPHeader.self)
// Copy server checksum.
let serverChecksum = icmpHeaderPointer.pointee.checksum.bigEndian
// Reset checksum field before calculating checksum.
icmpHeaderPointer.pointee.checksum = 0
// Verify ICMP checksum.
let payloadPointer = UnsafeRawBufferPointer(
start: icmpHeaderPointer,
count: payloadLength
)
let clientChecksum = ICMP.in_chksum(payloadPointer)
if clientChecksum != serverChecksum {
throw Error.malformedResponse(.checksumMismatch(clientChecksum, serverChecksum))
}
// Ensure endianness before returning ICMP packet to delegate.
var icmpHeader = icmpHeaderPointer.pointee
icmpHeader.identifier = icmpHeader.identifier.bigEndian
icmpHeader.sequenceNumber = icmpHeader.sequenceNumber.bigEndian
icmpHeader.checksum = serverChecksum
return icmpHeader
}
}
}
private extension IPv4Header {
/// Returns IPv4 header length.
var headerLength: Int {
Int(versionAndHeaderLength & 0x0F) * MemoryLayout<UInt32>.size
}
/// Returns `true` if version header indicates IPv4.
var isIPv4Version: Bool {
(versionAndHeaderLength & 0xF0) == 0x40
}
}
|