I must be missing something obvious here: I've got my packet filter running (yay), but every UDP packet it gets has a destination port of 0. Also I am confused by this other behaviour:
let udpHeader = (bytes + etherHeaderSize + ip4HeaderSize).bindMemory(to: udphdr.self, capacity: udpHeaderSize)
switch Int(udpHeader.pointee.uh_dport).bigEndian {
case 80, 443:
return true
case 0:
os_log(.debug, log: Self.log, "UDP port 0: ip_dst = %{public}s", ReadableIPAddr(ipPacket.pointee.ip_dst))
return false
default:
os_log(.debug, log: Self.log, "Got UDP packet dest port %#x, ip_dst = %{public}s", Int(udpHeader.pointee.uh_dport).bigEndian, ReadableIPAddr(ipPacket.pointee.ip_dst))
return false
}
The case 0
is not used, even though the default
prints out a value of 0.
This is working for me:
let packet: [UInt8] = [
0x2c, 0x91, 0xab, 0x54, 0x3c, 0xbe, 0x3c, 0x22,
0xfb, 0x0c, 0x7b, 0x27, 0x08, 0x00, 0x45, 0x00,
0x00, 0x39, 0x07, 0x3a, 0x00, 0x00, 0x40, 0x11,
0xaf, 0x89, 0xc0, 0xa8, 0x01, 0x47, 0x01, 0x01,
0x01, 0x01, 0xfc, 0x0f, 0x00, 0x35, 0x00, 0x25,
0x8c, 0xf0, 0xe3, 0x0f, 0x01, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x65,
0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63,
0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
]
let etherHeaderSize = 14
let ip4HeaderSize = 20
packet.withUnsafeBytes { buf in
let bytes = buf.baseAddress!
let udpHeader = (bytes + etherHeaderSize + ip4HeaderSize).bindMemory(to: udphdr.self, capacity: 1)
print(UInt16(bigEndian: udpHeader.pointee.uh_dport))
}
It prints 53, as expected.
In creating this test I saw two problems with your code:
-
You pass
udpHeaderSize
to thecapacity
parameter, not 1. That parameter is meant to be the number of items at that location, not the size of the item in bytes. IMO this is unlikely to be the cause of your problem. -
You had
Int(udpHeader.pointee.uh_dport).bigEndian
, which is backwards. You wantUInt16(bigEndian: udpHeader.pointee.uh_dport)
.
Having said that, I generally discourage folks from doing this sort of pointer manipulation, in Swift specifically but also in C. It’s very easy to break the rules and end up relying on undefined behaviour.
IMO you’re much better off parsing packets byte-by-byte. That might be too slow in a packet filter provider, but I recommend that you start by assuming it’s not too slow and then only mess around with pointers once you’ve proved that you have a performance problem.
Pasted in below is an example of how I’d approach this. You’d be amazed at how efficient this can be once you let the optimiser loose on it (-:
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
extension Collection where Self.SubSequence == Self {
mutating func consume(count: Int) -> Self.SubSequence? {
let result = self.prefix(count)
guard result.count == count else { return nil }
self = self.dropFirst(count)
return result
}
}
extension Collection where Self.SubSequence == Self, Self.Element == UInt8 {
mutating func consume<T>(bigEndian _: T.Type) -> T? where T: UnsignedInteger {
guard let bytes = self.consume(count: MemoryLayout<T>.size) else { return nil }
return bytes.reduce(0, { $0 << 8 | T($1) } )
}
}
packet.withUnsafeBytes { buf in
let etherHeaderSize = 14
let ip4HeaderSize = 20
var remainder = buf[...]
guard let ethernetHeader = remainder.consume(count: etherHeaderSize) else { fatalError() }
guard let ipHeader = remainder.consume(count: ip4HeaderSize) else { fatalError() }
guard
let srcPort = remainder.consume(bigEndian: UInt16.self),
let dstPort = remainder.consume(bigEndian: UInt16.self),
let length = remainder.consume(bigEndian: UInt16.self),
let checksum = remainder.consume(bigEndian: UInt16.self)
else { fatalError() }
print(srcPort) // 64527
print(dstPort) // 53
print(length) // 37
print(checksum) // 36080
}