I am making chat application in swift by using socket.io and node.js.
I need to send along the user name parameter with one socket method. I tried like below without sending parameter. This works good but i need to add user name parameter to get specific data instead of total data. sorry for silly question am new to socket concept.
socket.on("updatechat") { dataArray, ack in
print(dataArray)
}
Take a look at SocketIOClientOption, there is a parameter called connectParams.
case connectParams([String: AnyObject]) // Dictionary whose contents will be passed with the connection.
So, all you need to do is to pass the parameters into the config: when your are creating your socket.
let socket = SocketIOClient(socketURL: URL(string: "http://localhost:8080")!, config: [.connectParams(["username": "whatever"])])
If you are looking for SocketManager, you can set it with SocketIOClientConfiguration connectParams.
let manager = SocketManager(socketURL: URL(string: "socket_url")!,
config: [.log(true),
.compress,
.connectParams(["token": "- your token -"])])
You have to connect via sockets using the specific parameter. And then you will receive events only based on restrictions. Like you will connect sockets with an user id, and then will receive messages only for that user.
The implementation is different depend on the library that you are using for sockets. For swift I would recomand to use Socket.IO-Client-Swift.
Related
As I recently found this paper describing a sniffing mechanism for iOS using Apple's NEPacketTunnelProvider Extension, I got curious and it made me want to understand it from a technical point of view. As I usually don't work at a deep network layer like that, I'm not able to comprehend it in the detail I'd like to. As Charles Proxy for iOS must do something very similar without requiring supervised devices, I assume the approach which the author of the paper presented in 2016 might be still working nowadays.
The author claimed that "Everything like IP packet parsing, building
an IP packet or parsing a DNS response had to be implemented ourselves." As I want to fully understand that, I tried to build it myself. I build a NetworkExtension and a message loop for the packetFlow of the NEPacketTunnelProvider. I was able to obtain the ip datagrams and tried to parse them. I used unsigned integers of the corresponding size for the source and target ip, the transport protocol and ip version, but I'm unsure how to handle the treat the payload. My parser uses the ptr.load(fromByteOffset: <offset>, as:<DataType>.self) where ptr is a UnsafeRawPointer to access the packet flow information. Since the data might exceed the storage of UInt64, I don't know how to access and store the payload in a proper way.
Furthermore, I figured that the source IP is always 192.168.20.1 (set as my interface's NEIPv4Settings address) and my target ip is always 192.168.2.1 (my dummy NEDNSSettings server). This leads me to my first questions: Are those DNS queries? Will the datagram packet claim any further information about the actual target? Would that mean that I have to somehow execute the request to the DNS server and reroute the packet to the target which I will obtain from that DNS query?
The next step would be to implement a TCP / UDP handling, right? My current parsing approach is able to distinguish between UDP, TCP and ICMP (even though I don't have investigated in the last one yet). Therefore, I'd iterate over the datagrams and lookup whether they require a UPD or TCP session/connection and transfer the datagram. The problem I currently see their from a conceptional point of view: How do I know which source/target port to use for TCP/UPD connections/sessions? As far as I know, this information is not part of the IP Packet itself (since it's rather some information we need on transport layer level, not on network layer level).
Additionally, I found a project called Specht on github. It uses a self-written library called NEKit which somehow also uses the NEPacketTunnelProvider approach. When I understand their approach correctly, they managed to somehow build a local proxy server by writing some observer mechanisms in order to handle the requests, but since I'm relatively new to networking and swift, I'm not sure whether I understand that completely correct or whether I just haven't find all those TCP/UDP and/or DNS logic. Is this project comparable to the approach of the paper and charles proxy?
One last question: Charles proxy is in most cases able to show the hostname of the target. I'm currently just able to see destination ip addresses (which aren't real destination ip addresses, but the address of my DNS server). How am I able to see the hostname as human readable text? Does Charles do a nslookup somehow? Does Charles obtain that information out of the datagrams?
I know it's quite ambitious of me with much missing knowledge in this topic, to build something similar for test reasons, but I'm still motivated to look deeper into that topic and also have the feeling that I have understand already some key points, but unfortunately not enough to solve the puzzle... Maybe you're able to give me some more hints to get a better understanding. If there might be even an easier way to archive a similar behavior (to see outgoing connections on hostname level), I'd be interested in these as well :-)
I've published a Beta Proxyman iOS (website) - a Network Sniffer by using NEPacketTunnelProvider and Network Extension, so I might have experienced to answer some of your questions.
IP Package, IP Diagram, DNS, How to parse it?
Luckily, there is another way to set up a NEPacketTunnelProvider to provide you with an HTTP Message, not IP Package (it's too low-level, and you have to deal with the Parser, DNS, ...)
HTTP Message is easier to parse because there are plenty of reliable libraries (e.g. http-parser from nodeJS)
How to build a Network Sniffer on iOS?
It's a complicated question to answer, I would break it into small chunks:
MitM / Proxy Server
Firstly, you need a working MitM Proxy Server, which is capable of proxying and intercepting the HTTP/HTTPS Traffic. You can implement it by using SwiftNIO or CocoaAsyncSocket.
How does it work?
In general, the data flow might look like this:
The Internet -> iPhone -> Your Network Extension (VPN) -> Forward to your Local Proxy Server (in the Network Extension) -> Mitm/Proxy Server starts intercepting or monitoring the traffic -> Save to a local database (in Shared Container Group) -> Forward again to the destination server.
From the main app, you can receive the data by reading the local database.
The reason why we need a local database is that the Network Extension and the Main app are two different processes, so they could not communicate directly like a normal app.
Show me the code?
In the Network extension, let start a Proxy Server at Host:Port, then init the NetworkSetting, like the sample:
private func initTunnelSettings(proxyHost: String, proxyPort: Int) -> NEPacketTunnelNetworkSettings {
let settings: NEPacketTunnelNetworkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
/* proxy settings */
let proxySettings: NEProxySettings = NEProxySettings()
proxySettings.httpServer = NEProxyServer(
address: proxyHost,
port: proxyPort
)
proxySettings.httpsServer = NEProxyServer(
address: proxyHost,
port: proxyPort
)
proxySettings.autoProxyConfigurationEnabled = false
proxySettings.httpEnabled = true
proxySettings.httpsEnabled = true
proxySettings.excludeSimpleHostnames = true
proxySettings.exceptionList = [
"192.168.0.0/16",
"10.0.0.0/8",
"172.16.0.0/12",
"127.0.0.1",
"localhost",
"*.local"
]
settings.proxySettings = proxySettings
/* ipv4 settings */
let ipv4Settings: NEIPv4Settings = NEIPv4Settings(
addresses: [settings.tunnelRemoteAddress],
subnetMasks: ["255.255.255.255"]
)
ipv4Settings.includedRoutes = [NEIPv4Route.default()]
ipv4Settings.excludedRoutes = [
NEIPv4Route(destinationAddress: "192.168.0.0", subnetMask: "255.255.0.0"),
NEIPv4Route(destinationAddress: "10.0.0.0", subnetMask: "255.0.0.0"),
NEIPv4Route(destinationAddress: "172.16.0.0", subnetMask: "255.240.0.0")
]
settings.ipv4Settings = ipv4Settings
/* MTU */
settings.mtu = 1500
return settings
}
Then start a VPN,
let networkSettings = initTunnelSettings(proxyHost: ip, proxyPort: port)
// Start
setTunnelNetworkSettings(networkSettings) { // Handle success }
Then forward the package to your local proxy server:
let endpoint = NWHostEndpoint(hostname: proxyIP, port: proxyPort)
self.connection = self.createTCPConnection(to: endpoint, enableTLS: false, tlsParameters: nil, delegate: nil)
packetFlow.readPackets {[weak self] (packets, protocols) in
guard let strongSelf = self else { return }
for packet in packets {
strongSelf.connection.write(packet, completionHandler: { (error) in
})
}
// Repeat
strongSelf.readPackets()
}
From that, your local server can receive the packages then forwarding to the destination server.
Don't forget to save all traffic log to the local database, then notifying the main app to reload it.
One last question: Charles proxy is in most cases able to show the hostname of the target. I'm currently just able to see destination ip addresses (which aren't real destination ip addresses, but the address of my DNS server). How am I able to see the hostname as human readable text? Does Charles do a nslookup somehow? Does Charles obtain that information out of the datagrams?
Since we don't deal with IP Package, we don't need to implement the DNS Resolver. If you need a DNS, you can config like the following code:
let dnsSettings = NEDNSSettings(servers: ["8.8.8.8", "1.1.1.1"])
settings.dnsSettings = dnsSettings
As we receive the HTTP Message package, you can get hostname for free (From the Request's URL or Host Header)
Hope that my answer could help you.
I've added StompClientLib in my project and I'm facing problem while unsubscribing a destination topic.
Unsubscription of destination gives following error:
"org.apache.activemq.transport.stomp.ProtocolException: No subscription matched.\r\tat org.apache.activemq.transport.stomp.ProtocolConverter.onStompUnsubscribe(ProtocolConverter.java:734)\r\tat org.apache.activemq.transport.stomp.ProtocolConverter.onStompCommand(ProtocolConverter.java:262)\r\tat org.apache.activemq.transport.ws.AbstractStompSocket.processStompFrame(AbstractStompSocket.java:151)\r\tat org.apache.activemq.transport.ws.jetty9.StompSocket.onWebSocketText(StompSocket.java:96)\r\tat org.eclipse.jetty.websocket.common.events.JettyListenerEventDriver.onTextMessage(JettyListenerEventDriver.java:128)\r\tat org.eclipse.jetty.websocket.common.message.SimpleTextMessage.messageComplete(SimpleTextMessage.java:69)\r\tat org.eclipse.jetty.websocket.common.events.AbstractEventDriver.appendMessage(AbstractEventDriver.java:64)\r\tat org.eclipse.jetty.websocket.common.events.JettyListenerEventDriver.onTextFrame(JettyListenerEventDriver.java:122)\r\tat org.eclipse.jetty.websocket.common.events.AbstractEventDriver.incomingFrame(AbstractEventDriver.java:160)\r\tat org.eclipse.jetty.websocket.common.WebSocketSession.incomingFrame(WebSocketSession.java:309)\r\tat org.eclipse.jetty.websocket.common.extensions.ExtensionStack.incomingFrame(ExtensionStack.java:214)\r\tat org.eclipse.jetty.websocket.common.Parser.notifyFrame(Parser.java:220)\r\tat org.eclipse.jetty.websocket.common.Parser.parse(Parser.java:258)\r\tat org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.readParse(AbstractWebSocketConnection.java:628)\r\tat org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.onFillable(AbstractWebSocketConnection.java:476)\r\tat org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)\r\tat org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)\r\tat org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)\r\tat java.lang.Thread.run(Unknown Source)\r")
Yes, It's an issue with subscription name but It doesn't accept the same string I used to subscribe specific channel.
For example:
```
// destination
let destinationChannelTopic = "/topic/channel_1234"
// subscribe successful
socketClient?.subscribe(destination: destinationChannelTopic)
// unsubscribe not successful with same destination
socketClient?.unsubscribe(destination: destinationChannelTopic)
```
Here unsubscribe replies me with an error: No subscription matched
Can anyone help me to understand, what is wrong? What am I doing wrong?
As I analyzed from Subscribe and receive messages, subscription (a subscribe method) returns a string (subscription channel id) from the server, that we need to store somewhere at client side (in our project/code) and we need to use the same string to unsubscribe.
This is javcScript (not an iOS - swift) code, a sample that available in this link (Subscribe and receive messages) and we implemented similar way it in web application:
```
// subscribe
var subscription = client.subscribe("/queue/test", callback);
```
The subscribe() methods return a JavaScript object with 1 attribute, id, that correspond to the client subscription ID and one method unsubscribe() that can be used later on to unsubscribe the client from this destination.
```
// unsubscribe
subscription.unsubscribe();
```
So, is this only the method/possible way, for subscription and unsubscription. If yes, then we do not have any value returning to subscribe(...) that I can use to unsubscribe. I don't get any value (destination subscription id) from subscription socketClient?.subscribe(destination: destinationChannelTopic) method
As an alternate solution to this issue, I disconnect the client from the server and reconnect again + subscribe to all other destinations again. And this is not a proper way to handle but I have only this solution at this time.
Please help to find out the solution for this problem.
Here is reference link about the issue: unsubscribe socketclient #14
Subscription methodology approach for subscribe and unsubscribe are very different.
Subscribe uses unique destination (id) to connect with server as a destination but links and remembers it with reference of subscription-id and uses subscription-id to identify itself on server while unsubcribe.
Here is code, you are looking for. Try it.
let destination = "/topic/channel_1234"
let ack = "ack_\(destination)" // It can be any unique string
let subsId = "subscription_\(destination)" // It can be any unique string
let header = ["destination": destination, "ack": ack, "id": subsId]
// subscribe
socketClient?.subscribeWithHeader(destination: destination, withHeader: header)
// unsubscribe
socketClient?.unsubscribe(destination: subsId)
The subscribe func in this StompClientLib library is not returning the subscription ID that the server will generate and return to the client. You can see the code here:
https://github.com/WrathChaos/StompClientLib/blob/master/StompClientLib/Classes/StompClientLib.swift#L356
So you will have to specify a subscription ID by using this other func from the library, and provide a stomp header that includes a subscription ID of your choosing:
public func subscribeWithHeader(destination: String, withHeader header: [String: String])
For example:
var destination = "mytopic"
var ack = StompCommands.ackAuto
var subsId = "12345"
let header = [StompCommands.commandHeaderDestination: destination, StompCommands.commandHeaderAck: ack, StompCommands.commandHeaderDestinationId: subsId]
socketClient?.subscribeWithHeader(destination, header)
...
socketClient?.unsubscribe(subsId)
I have an app which has a UIWebView inside of it with a loaded website. This website has a chart in it which is periodicly updated with data from remote server via websockets (socket.io).
Im new to websockets technology but Im trying to somehow intercept the chart data that the website is receiving from server via it.
Till now I have managed to catch http requests sent by the website of such address format: “http://website-address/socket.io/?auth_token=...”
I have the socket.io library for iOS but don’t know how to use it to somehow spoof the website connection and acquire the data downloaded by the website. Can anyone help? Is it even possible?
Switch to WKWebView if you can. Using javascript bridge is much easier there. That said, with UIWebView, you'd need to inject a script that adds a handler for events received by the socket that you are trying to listen to. You can either create an io variable by yourself but apparently the server needs auth token. If you cannot create an auth token, you can only do this if you have access to the io variable created by the website.
Then for adding a handler, you'll need to know what the event name is, that delivers the chart data. You can snoop around the website and see if you can find that. If you cannot all bets are off. Once we register a handler and get the data, we need to pass this back to your native code. This is where WKWebView would keep it clean by letting you add message handlers that can deliver messages from js to native code. For UIWebView you'll have to create a custom url scheme and spoof a navigation request to pass the data. Lets assume your custom url scheme is 'myApp'. Then the script you'd need to inject would be:
<script>
/* if you can access/create the auth token
var socket = io('http://website-address/socket.io/?auth_token=');
*/
var socket = getioReferenceCreatedByWebsite();
socket.on('<eventName>',function(){
window.location = 'myApp://<data>';
};
</script>
In your native code:
...
webView.delegate = self;
[webView stringByEvaluatingJavaScriptFromString:#"<theAboveJSAsAString>"];
....
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
if(request.URL.scheme == #"myApp"){
NSString *data = request.URL.path;
//handle the data
return NO;
}
return YES;
}
In regards to Santhosh R answer. I had the problem he mentioned where I could not get a reference to the websocket object as it was caught up in a closure.
I solved this by adding in a preload script which wraps the native Websocket object to store any instantiated websocket objects in an array and then return the newly created websocket object.
Here is the code.
in your WebView element add in a preload attribute.
<webview id="myWebview" src="http://exmple.com" preload="./interceptor.js"></webview>
and then in inteceptor.js
window.NativeWebsocket = WebSocket;
window.WebSocket = function(url, protocols){
window.interceptedWebsockets = [];
var ws = new NativeWebsocket(url, protocols);
interceptedWebsockets.push(ws);
return ws;
}
Then, inside your WebView context you can an access array of instantiated websocket objects using window.interceptedWebsockets
I have a webapp and the iOS app built in Swift. The thing is I don't know Swift and I'm trying to modify the iOS app in order to add the authorization key to the PubNub client before subscribing/publishing to channels.
Link to PubNub docs
PRE:
Access Manager is enabled
my_auth_key is hardcoded and already enabled form the server for the corresponding channel I want to subscribe.
Here is the code
What's the correct way to set the auth key?
Thanks in advance
Polak, mentioned docs code snippet refer to previously created instance to get it's pre-configured PNConfiguration instance and change required field. This practice can be used in case if you need change something at run-time.
If you have data for authKey at the moment of client initialization, you can set it:
var pubnubClient: PubNub = {
let configuration = PNConfiguration(publishKey: UCConstants.PubNub.publishKey, subscribeKey: UCConstants.PubNub.subscribeKey)
configuration.authKey = "my_auth_key"
return PubNub.clientWithConfiguration(configuration)
}()
Also, I've tried exact your code and don't have any issues with setting of authKey, because I can see it with subscribe request.
If you still will have troubles with PAM and auth key usage, please contact us on support#pubnub.com
Looks like you can:
let configuration = PNConfiguration(
authKey: "super_secret",
publishKey: UCConstants.PubNub.publishKey,
subscribeKey: UCConstants.PubNub.subscribeKey
)
Based on the Obj-C code: https://github.com/pubnub/objective-c/blob/8c1f0876b5b34176f33681d22844e8d763019635/PubNub/Data/PNConfiguration.m#L174-L181
I have a Java based server using JmDNS which is being discovered from an iOS app.
The discovery works fine and this is the callback that gets executed by the NetServiceDelegate to record the details:
public func netServiceDidResolveAddress(_ service: NetService) {
guard let hostName = service.hostName else {
return
}
mockServerUrl = "http://\(hostName):\(service.port)/analytics"
Logger.log("Using mock server at \(mockServerUrl)", forLevel: .info)
}
This results in a URL that looks like this:
http://az-mbp-ether-lan.local.:9090
Now I swear this was working, however now when I try and send data to this endpoint with Alamofire it consistently times out.
Replacing the hostname with the hardcoded IP address of the target machine makes it work again, so there is no fundamental issue with the code sending the data.
Should an address of the form above ending in ".local." be resolvable from an iOS device on the same network as the target server?
The problem was on the Java server side. It was registering by calling InetAddress.getLocalHost(). This was returning the loopback address.
I revised my code to find a non-loopback address based on this question:
IP Address not obtained in java