URLSessionWebSocketTask handles binary data frames differently

As the client I'm sending binary data to a websocket server using URLSessionWebSocketTask. I've found that certain binaries causes the server to close the connection.

I reproduced the issue in two ways
1) Setup a local websocket server and listened to websocket messages from my application. This results in a bad frame that causes the connection to close.
2) Sent binary data to wss://echo.websocket.org , which I expect to echo the binary. The connection closes, which leads me to believe that URLSessionWebSocketTask handles binary sequences differently.

I tested two different instances, one binary that causes connection closed (sendBuggedFrame) and another that is working perfectly (sendGoodFrame). The contents are almost identical except for the 28th byte, where 109 is replaced with 113.

Code Block Swift
  func sendBuggedFrame() {
    let bytes: [UInt8] = [0, 0, 0, 77, 162, 225, 53, 4, 69, 83, 85, 48, 170, 225, 53, 3, 67, 77, 69, 145, 238, 53, 0, 0, 0, 0, 0, 109, 167, 64, 152, 216, 54, 1, 160, 216, 54, 1, 130, 217, 54, 9, 115, 105, 109, 117, 108, 97, 116, 111, 114, 194, 153, 75, 13, 51, 55, 49, 48, 54, 55, 51, 55, 95, 68, 69, 77, 79, 234, 153, 75, 0, 242, 153, 75, 0, 152, 182, 75, 184, 2]
    var data = Data()
    data.append(contentsOf: bytes)
    socket.send(URLSessionWebSocketTask.Message.data(data)) { error in
      if let error = error {
        print("Error send: \(error)")
      }
    }
  }

Code Block Swift
  func sendGoodFrame() {
    let bytes: [UInt8] = [0, 0, 0, 77, 162, 225, 53, 4, 69, 83, 85, 48, 170, 225, 53, 3, 67, 77, 69, 145, 238, 53, 0, 0, 0, 0, 0, 113, 167, 64, 152, 216, 54, 2, 160, 216, 54, 1, 130, 217, 54, 9, 115, 105, 109, 117, 108, 97, 116, 111, 114, 194, 153, 75, 13, 51, 55, 49, 48, 54, 55, 51, 55, 95, 68, 69, 77, 79, 234, 153, 75, 0, 242, 153, 75, 0, 152, 182, 75, 184, 2]
    var data = Data()
    data.append(contentsOf: bytes)
    socket.send(URLSessionWebSocketTask.Message.data(data)) { error in
      if let error = error {
        print("Error send: \(error)")
      }
    }
  }


There's little visibility of how the framework implements the websocket protocol. Any suggestions on what's going on and how I can investigate further?
The connection close code is 1002, termination due to protocol error
Does your test server support ws URLs? That is, without the TLS? If so, you should try to reproduce the problem with that. If you still see the problem, you can use a packet trace to see exactly what’s happening on the wire.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Thanks for the suggestion Quinn, I reproduced the problem without TLS using ws and observed the specific frames that the server receives during frame handling.

The first byte of the problematic frame is 192, which is 1100 0010. This sets the RSV1 bit, and is invalid unless an extension is negotiated. My websocket frame handler reports that this as a bad frame because the RSV bit is set with no extension.

The first byte of the good frame is 130 which is 1000 0010. Here the RSV bits aren't set. I haven't looked further than here, but it looks like the websocket framing is handled differently for these two binaries.
I’m reading the above correctly it sounds like the Apple client is relying on extensions that it hasn’t negotiated. If so, that seems like a bug to me, and I encourage you to file it as such. Please post your bug number, just for the record.

Two other things:
  • Try this with the current beta seeds (macOS 11 beta and friends) to see if it’s fix there.

  • Try using the Network framework’s WebSocket client. I suspect that it will have the same problem but, either way, it’s a useful factoid (if it also has the problem, you can file a more focused bug; it it doesn’t, then you get to file a more focused bug and have a workaround :-).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Thanks for the bug report (r. 65668399). Is there any chance you can attach your packet trace to it? We’re not seeing fail the same way you’re seeing it fail )-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Ahh, I didn't include that the environment is running on macOS 11 beta with macOS 10.16.

Let me get back to you on these:
  • I'll try setting a websocket client using Network.framework directly and seeing how it goes. I'm unfamiliar with Network.framework so it will take some time.

  • Attaching a packet trace - I originally debugged on my server to figure out how what frames are being sent. I'll find out how to capture a packet trace and follow up.

Yeah... understanding and debugging at the networking layer is quite new to me so thanks again for guidance.
I took a little field trip and I came back with some packet trace files. Updated the bug report with files.

I highly recommend learning how to inspect packets if you're an application developer and haven't had much exposure to the networking stack.
Regarding Network.framework, I ran into segmentation faults when setting up a client connection. Am I missing something?
  • Create and configure NWParameters.

Code Block swift
let parameters = NWParameters(tls: nil)
let wsOptions = NWProtocolWebSocket.Options()
parameters.defaultProtocolStack.applicationProtocols.insert(wsOptions, at: 0)
  • Specify NWEndpoint host and port.

  • Create the NWConnection using NWEndpoint and NWParameters.

  • Set stateUpdateHandler in NWConnection instance

  • Set receive() callback in NWConnection instance

  • Start the connection

This forum post brought up the fact that websockets with longer message lengths cause connection close.

It seems related to this extension, Sec-WebSocket-Extensions permessage-deflate. The packet capture shows the client does try to negotiate permessage-deflate extension, but the server does not respond with extension accepted. It seems like the RSV bit gets set regardless if the server accepts the extension.

The workaround (if you own the server-side logic) is to enable the permessage-deflate option on the server side. Unfortunately while I have a local server to test on, my application relies on an external service where I don't own and cannot configure the server.

Quinn, is it possible to configure URLSessionWebSockets to opt out of this particular extension?

Thanks for the bug report (r. 65668399).

Just a quick status update. We think we know what’s going on with this bug and, while I can’t make any promises, you should definitely retest on any futures seeds of macOS 11 beta that come along.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Amazing, thanks! I've updated to macOS 11 beta 3 and the bug was resolved.
URLSessionWebSocketTask handles binary data frames differently
 
 
Q