40

I would like to inspect network traffic going through web sockets, I have no control over the networking code as this is a binary lib for which I do not have the source code, so I cannot do any log/breakpoint in the networking part of the code.

I have tried using the latest version of CharlesProxy which claim to be able to sniff websockets however when I tried the url and apis using websockets were not even mentionned in the list of endpoints called from my iPhone.

Version 3.11 release notes

I have verified that CharlesProxy is configured correctly as I am able to inspect non-websocket traffic even under SSL.

So my question is: did anyone find a solution to inspect traffic going through websockets with CharlesProxy?

Note: I have ATS disabled when using iOS9

Thanks!

matanwrites
  • 782
  • 1
  • 8
  • 18
  • 4
    Did you solve the problem? I have Charles 3.11.2 and still don't see websocket traffic. – klimat Jan 13 '16 at 10:44
  • 2
    I can't see websockets that come from ios in charles, however, I can see them when initiated from javascript – Pauls Apr 13 '16 at 14:56
  • Did you ever find an answer for this question? – Andrew Paul Simmons Nov 15 '18 at 18:13
  • 2
    @AndrewPaulSimmons Yes it works fine with devices as well as simulators. In Charles go to Proxy Settings > Enable Socks Proxy. Enabling HTTP proxying over SOCKS might help as well. – matanwrites Nov 20 '18 at 04:50
  • 1
    We are using Starscream and WS data is not being picked up – daihovey Nov 22 '18 at 13:32
  • I used what @matanwrites recommended and works great with latest Charles, Apollo iOS SDK Subscriptions which is based on top of Startscream. It was a hassle to deal with certificates and other buzz in SSL Settings though. – Kristaps Grinbergs Jan 21 '20 at 12:26

4 Answers4

23

I finally found the answer.

Charles 3.11.2 works perfectly with WebSocket.

I use socketIO, so I've already seen http requests sent during the negotiation phase, but I missed websockets traffic.

In the beginning, socketIO try to use polling then switches to use websockets.

The websocket traffic is visible when you go to the request with status: "Sending request body" which is actually wss:// request.

You even have a dedicated tab for this kind of traffic. The rest of messages will appear right there.

enter image description here

PS1. Ensure you're connected to socket properly then it appears in Charles.
PS2. I suggest using socketIO it's a great enhancement for full-duplex traffic like websockets.

klimat
  • 23,063
  • 7
  • 56
  • 67
10

UPDATE: Apparently socket.io-client-swift v15.1.0 now properly supports SOCKS proxy. I have not yet tried it, but it would mean that these manual edits to Starscream are no longer required.


The accepted answer does not seem to work with Socket.IO on iOS devices.

The latest version of Socket.IO-Client-Swift (15.0.0 at the time of writing) uses Starscream for WebSockets on iOS/OS X.

The good news is that Starscream supports SOCKS proxying however:

  1. Socket.IO does not expose the Starscream websocket or provide any API for enabling the SOCKS proxying behaviour.

  2. The SOCKS proxying built into Starscream uses the OS SOCKS proxy settings which are cumbersome to setup (at least for iOS).

If I get some time I might propose a PR to address this more thoroughly, but given that it requires work to both Starscream and Socket.IO-Client-Swift, this is not entirely straightforward.

The easiest way to hack around this for temporary debugging purposes (which is the use case for Charles!), is to edit the WebSocket.swift file as part of Starscream, and replace this code:

if enableSOCKSProxy {
    let proxyDict = CFNetworkCopySystemProxySettings()
    let socksConfig = CFDictionaryCreateMutableCopy(nil, 0, proxyDict!.takeRetainedValue())
    let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
    CFWriteStreamSetProperty(outputStream, propertyKey, socksConfig)
    CFReadStreamSetProperty(inputStream, propertyKey, socksConfig)
}

with this code:

let socksConfig = CFDictionaryCreateMutableCopy(nil, 0, CFNetworkCopySystemProxySettings()!.takeRetainedValue()) as! [String: Any]
let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
let ip = socksConfig["HTTPSProxy"]
let proxySocksConfig = ["SOCKSProxy": ip, "SOCKSPort": 8889, "SOCKSEnable": true] as CFDictionary // Where 8889 is the SOCKS proxy port in Charles
CFWriteStreamSetProperty(outputStream, propertyKey, proxySocksConfig)
CFReadStreamSetProperty(inputStream, propertyKey, proxySocksConfig)

This will ensure the SOCKS proxy is enabled by default, and will route all the websockets traffic via Charles.

You then need to ensure that the HTTP proxy settings are configured in iOS (since the same IP will be used for both HTTP and SOCKS), SOCKS proxy is enabled in Charles, and that the port matches the port in the code above (by default 8889).

Jonathan Ellis
  • 4,794
  • 2
  • 32
  • 48
  • It's still not working for me after updating WebSocket.swift. Would you be able to expand on the last section? I've enabled SOCKS proxy in Charles and the port matches but I wonder if I missed anything. – Robert Gummesson Apr 05 '19 at 16:50
  • Never mind. I got it working. I missed that enableSOCKSProxy had to be true. – Robert Gummesson Apr 05 '19 at 18:49
  • If you replace the entire section *including* `if enableSOCKSProxy { ... }` then you don't need to set `enableSOCKSProxy = true`, it will just be enabled by default for all connections.... – Jonathan Ellis Apr 08 '19 at 09:02
3

Thanks for your very very helpful answer Jonathan Ellis! I'm using Pusher and this worked great!

However, I found socksConfig to not always contain valid data and didn't work or would crash the app when I pulled the IP from there. Since the only thing we are getting from there is the localhost IP I just replaced the following in WebSocket.swift

if enableSOCKSProxy {
    let proxyDict = CFNetworkCopySystemProxySettings()
    let socksConfig = CFDictionaryCreateMutableCopy(nil, 0, proxyDict!.takeRetainedValue())
    let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
    CFWriteStreamSetProperty(outputStream, propertyKey, socksConfig)
    CFReadStreamSetProperty(inputStream, propertyKey, socksConfig)
}

with this:

let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
let proxySocksConfig = ["SOCKSProxy": "127.0.0.1", "SOCKSPort": 8889, "SOCKSEnable": true] as CFDictionary // Where 8889 is the SOCKS proxy port in Charles
CFWriteStreamSetProperty(outputStream, propertyKey, proxySocksConfig)
CFReadStreamSetProperty(inputStream, propertyKey, proxySocksConfig)

And then enabled the socks proxy in Charles as you described.

Thanks Again!

Andrew Paul Simmons
  • 3,315
  • 3
  • 24
  • 31
0

Found a workaround solution: After trying, I conclude that although iOS simulators follow system proxy settings for HTTP, WebSocket is not followed. Socks5 proxy settings is not followed, either. However, we can use Reverse Proxies in Charles to force the simulator to use it. In Charles, click Proxy -> Reverse Proxies, set up a local address to be your WebSocket server's transparent proxy, then use that address in your new WebSocket(<address>) (Well in the case of React Native), then you can see your WebSocket connections appear in Charles

wang chenyu
  • 39
  • 1
  • 3