NEFilterPacketContext is Empty

I am implementing a NetworkExtension, based on NEFilterPacketProvider. My issue is that the NEFilterPacketContext is always empty. I assume that's not expected, but my code is as simple as it gets. What's the issue?

Maybe loking at attributeKeys is the wrong thing to do, but in that case, how to access context info? Specifically, I'd like to know things like which applications generated the packet, on which port, what external IP, etc.


class FilterPacketProvider: NEFilterPacketProvider { 
    override init() {
          super.init()
          os_log("FilterPacketProvider init")
      }
 
    override func startFilter(completionHandler: @escaping (Error?) -> Void) {
        os_log("FilterPacketProvider startFilter")
   
        packetHandler = { (context:NEFilterPacketContext,
                           interface:nw_interface_t,
                           direction:NETrafficDirection,
                           packetBytes:UnsafeRawPointer,
                           packetLength:Int)
                                in
                os_log("FilterPacketProvider packet context=%{public}s  %{public}s interface=%{public}s dir=%d length=%d",
                       context.attributeKeys.description,
                       context.debugDescription,
                       interface.description,
                       direction.rawValue,
                       packetLength
                       )
                return .allow //.allow, .drop or .delay
        }
        completionHandler(nil)
    }
 
    override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
        os_log("FilterPacketProvider stopFilter")
        completionHandler()
    }
}
Answered by DTS Engineer in 403273022

Do you have a recommendation for a library that would help me in that regard ?

No, sorry. Parsing IP packets is fairly straightforward. In situations where I need to do this, I generally just write the code. Pasted in below is some code I wrote recently as part of some sample code work.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
import Foundation

struct PacketInfo {

    let addressLength: Int

    let headerRange: Range<Data.Index>
    let headerChecksumRange: Range<Data.Index>?
    let sourceAddressRange: Range<Data.Index>
    let destinationAddressRange: Range<Data.Index>

    let tcpHeaderRange: Range<Data.Index>
    let tcpSourcePortRange: Range<Data.Index>
    let tcpDestinationPortRange: Range<Data.Index>
    let tcpChecksumRange: Range<Data.Index>

    let tcpPayloadRange: Range<Data.Index>

    enum AddressKind {
        case src
        case dst
    }

    func addressRange(kind: AddressKind) -> Range<Data.Index> {
        switch kind {
        case .src: return self.sourceAddressRange
        case .dst: return self.destinationAddressRange
        }
    }

    func portRange(kind: AddressKind) -> Range<Data.Index> {
        switch kind {
        case .src: return self.tcpSourcePortRange
        case .dst: return self.tcpDestinationPortRange
        }
    }

    init?(protocolFamily: sa_family_t, packetData: Data) {
        switch Int32(protocolFamily) {
        case AF_INET:
            try? self.init(packetData4: packetData)
        case AF_INET6:
            // Leaving out IPv6 support for the moment because I’m not set up to
            // test it, and writing all the code without testing it would be a
            // mistake.
            return nil
        default:
            return nil
        }
    }

    private init(packetData4 packetData: Data) throws {
        self.addressLength = 4

        var p = PacketParser(packetData: packetData)

        let versionIHL = try p.parseUInt8()
        try p.skip(1)                       // DSCP | ECN
        let totalLength = try Int(p.parseUInt16())
        try p.skip(2)                       // Identification
        try p.skip(2)                       // Flags | Fragment Offset
        try p.skip(1)                       // TTL
        let proto = try p.parseUInt8()
        self.headerChecksumRange = try p.skip(2)
        self.sourceAddressRange = try p.skip(4)
        self.destinationAddressRange = try p.skip(4)

        guard
            versionIHL & 0xf0 == 0x40,
            versionIHL & 0x0f >= 5,
            totalLength <= packetData.count,
            proto == 6
        else { throw ParseError.unexpectedValue }
        let ipHeaderLength = Int(versionIHL & 0x0f) * 4

        let optionsRange = try p.skip(ipHeaderLength - 20)
        self.headerRange = packetData.startIndex..<optionsRange.upperBound

        self.tcpSourcePortRange = try p.skip(2)
        self.tcpDestinationPortRange = try p.skip(2)
        try p.skip(4)                       // Sequence Number
        try p.skip(4)                       // Acknowledgment Number
        let tcpDataOffsetFlags = try p.parseUInt8()
        try p.skip(1)                       // Flags
        try p.skip(2)                       // Window
        self.tcpChecksumRange = try p.skip(2)
        try p.skip(2)                       // Urgent Pointer

        guard (tcpDataOffsetFlags >> 4) >= 5 else { throw ParseError.unexpectedValue }
        let tcpHeaderLength = Int(tcpDataOffsetFlags >> 4) * 4

        let tcpOptionsRange = try p.skip(tcpHeaderLength - 20)
        self.tcpHeaderRange = optionsRange.upperBound..<tcpOptionsRange.upperBound

        let payloadLength = totalLength - (ipHeaderLength + tcpHeaderLength)
        self.tcpPayloadRange = try p.skip(payloadLength)
    }

    private struct PacketParser {
        let packetData: Data

        init(packetData: Data) {
            self.packetData = packetData
            self.currentIndex = packetData.startIndex
        }

        var currentIndex: Data.Index

        @discardableResult
        mutating func skip(_ count: Int) throws -> Range<Data.Index> {
            let old = self.currentIndex
            guard let newIndex = packetData.index(self.currentIndex, offsetBy: count, limitedBy: packetData.endIndex) else {
                throw ParseError.dataExpected
            }
            defer { self.currentIndex = newIndex }
            return self.currentIndex..<newIndex
        }

        mutating func parseUInt8() throws -> UInt8 {
            let r = try skip(1)
            return self.packetData[r.lowerBound]
        }

        mutating func parseUInt16() throws -> UInt16 {
            let r = try skip(2)
            return UInt16(self.packetData[r.lowerBound]) << 8 | UInt16(self.packetData[r.lowerBound + 1])
        }
    }

    private enum ParseError: Error {
        case dataExpected
        case unexpectedValue
    }
}

Makes sense.


Swift should be autoreleasing the packet on its own then.



Thank you!

I dont believe this answers the question.


The original question says `Specifically, I'd like to know things like which applications generated the packet...`


How would you link this to a pid or exe ?

How would you link this to a pid or exe ?

I’m not entirely sure which of devfunshark’s many posts you’re addressing here, so let’s discuss both of the relevant steps:

  • Getting packet metadata

  • Mapping that metadata to actionable info

You can get metadata from an

NEPacket
via its
metadata
property.

IMPORTANT The

metadata
property is only populated in specific scenarios, for example, when you’re running a packet tunnel provider in per-app VPN mode.

The metadata includes:

How you map the metadata to actionable info depends on your platform:

  • On iOS you can pretty much rely on

    sourceAppSigningIdentifier
    . That corresponds to the
    application-identifier
    entitlement, which is subject to iOS’s provisioning mechanism.
  • For macOS the starting point is the

    sourceAppAuditToken
    . You can map that to a pid using
    audit_token_to_pid
    but in most cases you’ll want to map it to a
    SecCode
    object using
    kSecGuestAttributeAudit
    . I’ve posted about that extensively in the past, so search forums for more.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
NEFilterPacketContext is Empty
 
 
Q