Receive NWProtocolIP metadata

I'm attempting to get the receiveTime on each received UDP packet from a NWConnection provided by a NWListener.

I've found that @eskimo talks about using this property in this post - "Use receive timestamps to probe point E. In BSD Sockets, set the SO_TIMESTAMP option and access the timestamps by looking at the SCM_TIMESTAMP value returned from recvmsg. In Network framework, set the shouldCalculateReceiveTime property and access the timestamps using the receiveTime property."

I have the listeners parameters set up like so:

let parameters = NWParameters.udp
if let options = parameters.defaultProtocolStack.internetProtocol as? NWProtocolIP.Options {
    options.shouldCalculateReceiveTime = true
}

I do have a custom protocol framer involved and working in the network stack.

I have been trying the following but the only metadata available is the custom protocol framers:

connection.receiveMessage { data, context, isComplete, error in
    if let metadata = context?.protocolMetadata(definition: NWProtocolIP.definition) {
        print(metadata)
    }
}

Where in a framer implementation can I grab the IP metadata and how, if i do indeed need to, pass it up to the application?

Answered by SammySmallman in 773867022

It does indeed look like the Listener is not passing the shouldCalculateReceiveTime through to the connection. I tried setting it manually within the listeners newConnectionHandler handler:

listener?.newConnectionHandler = { connection in
  if let options = connection.parameters.defaultProtocolStack.internetProtocol as? NWProtocolIP.Options {
    options.shouldCalculateReceiveTime = true
  }
...

But that doesn't work either...

This is working for me on the outgoing side. Specifically, I used this program as the client:

import Foundation
import Network

func startReceive(c: NWConnection) {
    print("will receive")
    c.receiveMessage { content, context, _, error in
        if let content {
            let meta = context?.protocolMetadata(definition: NWProtocolIP.definition) as? NWProtocolIP.Metadata
            let receiveTime = meta?.receiveTime ?? 0
            print("did receive, count: \(content.count), received: \(receiveTime)")
        }
        if let error {
            print("did receive, error: \(error)")
        }
        startReceive(c: c)
    }
}

func main() {
    let p = NWParameters.udp
    if let options = p.defaultProtocolStack.internetProtocol as? NWProtocolIP.Options {
        options.shouldCalculateReceiveTime = true
    }
    let c = NWConnection(host: "127.0.0.1", port: 12345, using: p)
    c.send(content: Data("Hello Cruel World!\r\n".utf8), completion: .idempotent)
    startReceive(c: c)
    c.start(queue: .main)
    withExtendedLifetime(c) {
        dispatchMain()
    }
}

main()

I then ran a server like this:

% nc -l 12345

Once the server gets the Hello Cruel World! greeting, I start typing text into nc. On the client side I see this:

will receive
did receive, count: 12, received: 2837316254759691
will receive
did receive, count: 14, received: 2837319371605273
will receive

and those timestamps look pretty reasonable.

Note Testing this on macOS 13.6.1 with Xcode 15.0.

I suspect that the issue here is that NWListener is not passing the shouldCalculateReceiveTime setting through to the NWConnection objects that it vends. I don’t have time to test that today though.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

It does indeed look like the Listener is not passing the shouldCalculateReceiveTime through to the connection. I tried setting it manually within the listeners newConnectionHandler handler:

listener?.newConnectionHandler = { connection in
  if let options = connection.parameters.defaultProtocolStack.internetProtocol as? NWProtocolIP.Options {
    options.shouldCalculateReceiveTime = true
  }
...

But that doesn't work either...

Receive NWProtocolIP metadata
 
 
Q