iPhone: IPv4 Wi-Fi access fails with cellular radio enabled - ios

I've got an iPhone app that makes UPnP queries over Wi-Fi to locate a Wi-Fi-connected camera, using GCDAsyncSocket. All the communications use IPv4 addresses over an ad-hoc network created by the cameras themselves, and the app is working properly with over 2500 field deployments.
However I have one customer for whom it fails on his iPhone, unless Airplane Mode is on, or cellular data is turned off. The same camera works fine with his iPad mini, so it's not the camera's fault as best we can determine.
The app sets up a listening socket:
_listenSocket = [[GCDAsyncSocket alloc] initWithDelegate:self
delegateQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)];
_listenSocket.IPv6Enabled = NO;
accepted = [_listenSocket acceptOnInterface:getWirelessInterface()
port:_upnpPort error:&error];
It then sets up a sending socket, also with IPV6 disabled; connects to the host (at a known or suspected IP address) over the wireless interface; and sends a UPnP SUBSCRIBE message:
_sendSocket = [[GCDAsyncSocket alloc] initWithDelegate:self
delegateQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)];
if (!_sendSocket) {
NSLog(#"sendMsg: unable to allocate sendSocket");
return;
}
_sendSocket.IPv6Enabled = NO;
NSError *err = nil;
if (![_sendSocket connectToHost:hostParts[0]
onPort:[hostParts[1] intValue]
viaInterface:getWirelessInterface()
withTimeout:TIMEOUT
error:&err]) // Asynchronous!
{
// If there was an error, it's likely something like
// "already connected" or "no delegate set"
NSLog(#"socket connectToHost error: %#", err);
return;
}
[_sendSocket writeData:[msg dataUsingEncoding:NSUTF8StringEncoding] withTimeout:TIMEOUT tag:1];
Normally, this works as expected. However, in this one case, the send fails; the delegate method socketDidDisconnect:withError: gets called:
sendSocket disconnected with error: Error Domain=GCDAsyncSocketErrorDomain
Code=8 "IPv6 has been disabled and DNS lookup found no IPv4 address."
If I delete the "xxxSocket.IPv6Enabled = NO;" lines, I get this instead:
listenSocket disconnected with error: Error Domain=NSPOSIXErrorDomain
Code=65 "No route to host"
Various Googlings indicate that "no route to host" indicates an IPv4 address can't be resolved in an IPv6 environment, and the "IPv6 has been disabled and DNS lookup found no IPv4 address" message is self-explanatory.
Why is this happening on this one iPhone, and not on all the others out there (including my own test instruments)? How might I fix it, aside from saying "turn cellular data off"? Why is DNS even involved, if I'm using the IPv4 address of the very router (ad-hoc access point) I've already connected to to get the network running in the first place?
(One other stackoverflow post (GCDAsyncUdpSocket immediately closes when sending to an IPv6 address) suggested retrying until things worked, but I haven't had any success with that.)

I haven't been able to figure out why this is happening: it affects at least two people on T-Mobile in Florida and California, but I can't replicate it using T-Mobile in Washington.
I did find a fix: replace connectToHost with connectToAddress, which connects to the IPv4 address directly without a DNS resolution step. While connectToHost allows either a hostname or an IP address, it runs a DNS lookup step even with an IP address, and that lookup was getting confounded by whatever was occurring when the affected iDevices were connected to T-Mobile.

Related

NEPacketTunnelProvider Sniffer iOS

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.

IOS boost asio connect from ipv6 network

I am trying to connect a dvr using boost asio library in ios. The application works fine in emulator with in ipv4 network. But when I submit the application on Appstore apple rejected the application as it's not work on ipv6 network. And I can see in the apple site that application should support ipv6 network. https://developer.apple.com/news/?id=05042016a
So I think the problem comes at the section where I am trying to connect to DVR using boost library, where the ip address of the DVR is pulled from DB(hard-coded) and below is the relevant part of the code.
boost::asio::io_service io_service_;
tcp::resolver::iterator endpoint_iter_;
tcp::resolver resolver_; //to healp resolving hostname and ip
stringstream strstream;//create a stringstream
strstream << port;//add number to the stream
endpoint_iter_ = resolver_.resolve(tcp::resolver::query(ip.c_str(),strstream.str()));
start_connect(endpoint_iter_);
// Start the deadline actor. You will note that we're not setting any
// particular deadline here. Instead, the connect and input actors will
// update the deadline prior to each asynchronous operation.
deadline_timer_.async_wait(boost::bind(&dvr_obj::check_deadline, this));
//starting thread for dvr connection
io_service_.reset();
thread_ = new boost::thread(boost::bind(&boost::asio::io_service::run, &io_service_));
start_connect method
void start_connect(tcp::resolver::iterator endpoint_iter)
{
try
{
if (endpoint_iter != tcp::resolver::iterator())
{
drill_debug_info("trying to connect %s \n",name.c_str());
// Set a deadline for the connect operation.
deadline_timer_.expires_from_now(boost::posix_time::seconds(10));
// Start the asynchronous connect operation.
socket_.async_connect(endpoint_iter->endpoint(),
boost::bind(&dvr_obj::handle_connect,
this, _1, endpoint_iter));
}
else
{
// There are no more endpoints to try. Shut down the client.
connectivity = false;
}
} catch (int e) {
connectivity = false;
}
}
So I am confused how to change above code to work on IPV6 network. Could not find any solution in Internet.
You can iterate through the endpoints to find an IPv6 endpoint using the code below:
endpoint_iter_ = resolver_.resolve(tcp::resolver::query(ip.c_str(),strstream.str()));
while (endpoint_iter_ != tcp::resolver::iterator())
{
if (endpoint_iter_->endpoint().protocol() == tcp::v6())
break;
++endpoint_iter_;
}
if (endpoint_iter_ != tcp::resolver::iterator())
{
start_connect(endpoint_iter_);
...
}
else
std::cerr << "IPv6 host not found" << std::endl;

Connect to Acquisition unit(WIFI device) from iPhone/iPad programmatically

I have a hardware device Acquisition unit. This device itself act as wifi router and it will send data. I want to connect it from iPhone/iPad.
I heard that using IP address and port number we can connect to the wifi router using socket programming. But I don't have any idea about this socket program to connect to the wifi using IP address and port number.
For this I have tried to get the IP address of a connected wifi router using following code.
NSString *address = #"error";
struct ifaddrs *interfaces = NULL;
struct ifaddrs *temp_addr = NULL;
int success = 0;
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(&interfaces);
if (success == 0) {
// Loop through linked list of interfaces
temp_addr = interfaces;
while(temp_addr != NULL) {
if(temp_addr->ifa_addr->sa_family == AF_INET) {
// Check if interface is en0 which is the wifi connection on the iPhone
if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:#"en0"]) {
// Get NSString from C String
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
}
}
temp_addr = temp_addr->ifa_next;
}
}
// Free memory
freeifaddrs(interfaces);
return address;
If it is possible to connect to the wifi router using ip address and port number could you please help me with some code or idea.
Please help me..
Thanks
The code you pasted basically returns the device's own IP address, which does not help at all, unless you want to guess router's IPs. You actually can get correct router IP address, see questions below:
Objective-C : How to fetch the router address?
How to get Wi-Fi router IP address on iOS?
Is it possible to get the SSID & MAC Address of Currently connected WiFi Network in an App
After you have this, you can easily connect to it via a normal TCP/IP connection socket, see the following links for more information:
Socket Connection in IOS
http://www.raywenderlich.com/3932/networking-tutorial-for-ios-how-to-create-a-socket-based-iphone-app-and-server
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/UsingSocketsandSocketStreams.html
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Streams/Articles/NetworkStreams.html
Or to make things even easier for you, there are a couple of community libraries that will make connecting to a server like a breeze:
https://github.com/pkyeck/socket.IO-objc
https://github.com/dreese/FastSocket
My suggestion is to read through those documents first, so you understand the basics of networking in iOS / OS X (which is actually fairly similar to Unix in this aspect). After try the libraries and wrappers you can find on CocoaPods and GitHub. If you are stopped by some specific issue at that point, we might be able to help you. We cannot do all the work for you.
So to answer: Yes, it is possible.

Connection problems from a netty client in Android

I am using a Netty server in linux with one netty client in Android. (Netty 4.0.23).
It works perfect most of the time. However, sometimes, my client is not able to connect to server.
The ChannelFuture received when trying to connect tells me that connection is done but not success (with cause not null) so it is completed with failure.
About the failure I received it is a java.net.socketException - EHOSTUNREACH error (No route to host).
I am quite sure that the problem is not in server.
Important:
I always run the server and the client in the same lan wifi network
with local ip addresses.
I have tested it in two different wifi
networks. In one of them, the reported error occurs rarely and in the
other occurs quite often.
CONNECT_TIMEOUT_MILLIS is configured with enough time in both contexts
I attach the way I do the connection from client:
.............
final ChannelFuture futureConnection = bootstrap.connect(HOST, PORT);
futureConnection.awaitUninterruptibly();
if (futureConnection.isDone()){
if (futureConnection.isCancelled()) {
// log error
}
else if (futureConnection.isDone() && futureConnection.isSuccess()){
// do something
}
else if (futureConnection.isDone() && (futureConnection.cause() != null)) {
// log error THIS IS THE ERROR REPORTED
}
else {
// log error
}
}
............

On iOS, how do you use GCDAsyncUdpSocket to listen to two ports?

I'm using GCDAsyncUdpSocket on iOS (successfully) to send and receive data using UDP starting it as follows:
udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate: self delegateQueue: dispatch_get_main_queue()];
NSError *error;
BOOL result = [udpSocket bindToPort: 3054 error: &error];
The device sends data back on port 3054, which I see using the delegate call udpSocket:didReceiveData: fromAddress:withFilterContext:. The device also sends information on port 9750, and I would like to see that, too. I've tried not binding to a specific port, but that fails for other reasons. I've also tried starting a separate socket using
udpDataSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate: self delegateQueue: dispatch_get_main_queue()];
NSError *error;
BOOL result = [udpDataSocket bindToPort: 9750 error: &error];
While these calls succeed with no error, I don't get the data from port 9750.
I do know for sure that the data is being sent; this is an issue on the iOS side. I know this because:
A logic analyzer sees the data arrive at the device for transmission on port 9750; this is suggestive but not conclusive.
I can switch the port the device uses to send the second data stream to 3054, in which case my app sees the data. This is undesirable, though.
So, how does one read data from two UDP ports front he same IP address on iOS using GCDAsyncUdpSocket?

Resources