Having issues connecting to iPv6 hosts with the CocoaAsyncSocket library
I successfully had GCDUDPAsyncSocket working but realized TCP was more appropriate for my use case.
Unfortunately - I can never successfully connect with a bonjour published and discovered NSNetService. The service is discovered and the address is discovered as well. A connection attempt without failure happens but the connection is never secured.
I can connect using "connectWithHost" and passing in the IP address assigned to my mac but this the only way i can get that ip is by hard coding it. Is there a way to obtain this IP through NSNetService?
I'm using swift, Xcode 7.1.1 and iOS 9.1. I am connecting between an iPhone and a Mac running an Apple TV Simulator. This works fine with UDP.
No matter what - the connection attempt times out even though an appropriate address is supplied!
Socket is Disconnecting - Error Domain=NSPOSIXErrorDomain Code=60 "Operation timed out" UserInfo={NSLocalizedDescription=Operation timed out, NSLocalizedFailureReason=Error in connect() function}
Anyone run into this before? Here is my connection code:
func connectToAddress(sender: NSNetService) {
if let addresses = sender.addresses {
for address in addresses {
print(address)
}
self.serverAddresses = addresses
var done = false
while !done && (self.serverAddresses.count > 0) {
let address = self.serverAddresses[0]
self.socket = GCDAsyncSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
do {
try self.socket.connectToAddress(address)
done = true
} catch {
print("Unable to Connect")
}
}
if !done {
print("Could Not Connect To Address")
}
}
}
Please update your CocoaAsyncSocket library. The issue was fixed in May 2nd commit. So this should work with the following flag set to false
socket.IPv4PreferredOverIPv6 = NO;
This will allow your app / game to connect in both IPv4 and IPv6. As of June 1 Apple is rejecting the apps which are not compliant with IPv6 networks. You app should work without any problems in an IPv6 network.
You can setting
socket.IPv4PreferredOverIPv6 = NO;
as #Kumar C mentioned, it will works well in IPv6, but if you still need work with IP address(use IP address as Host), you can update the code as below in GCDAsyncSocket.m(IPv4PreferredOverIPv6 should be set to NO first):
+ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr
{
......
for (res = res0; res; res = res->ai_next)
{
if (res->ai_family == AF_INET)
{
// Found IPv4 address.
// Wrap the native address structure, and add to results.
if (((struct sockaddr_in *)res->ai_addr)->sin_port == 0)
((struct sockaddr_in *)res->ai_addr)->sin_port = htons(port);
NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
[addresses addObject:address4];
}
else if (res->ai_family == AF_INET6)
{
// Found IPv6 address.
// Wrap the native address structure, and add to results.
if (((struct sockaddr_in6 *)res->ai_addr)->sin6_port == 0)
((struct sockaddr_in6 *)res->ai_addr)->sin6_port = htons(port);
NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
[addresses addObject:address6];
}
}
.......
}
This will allows you app work well in IPv4/IPv6 whatever the HOST is IP address or domain.
Related
I'm developing an application where devices can connect and interact with each other via common wi-fi network and for the purpose of file exchange I'm using GCDWebServer.
Everything is working great when I use usual wi-fi network or devices are connected to hotspot network with 3rd party host. But I encounter a strange issue when one of devices with the launched app is actually a host of a Hotspot.
I have this code:
- (void)startStreamHLSServer
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!_webServer.isRunning)
{
_webServer = [GCDWebServer new];
[_webServer addGETHandlerForBasePath:#"/" directoryPath:[_fileManager videosURL].path indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
[_webServer startWithPort:1000 bonjourName:nil];
NSLog(#"URL: %#", _webServer.serverURL.absoluteString);
}
});
}
The problem is that serverURL is nil. Which actually seems logical because I checked a function GCDWebServerGetPrimaryIPAddress which is supposed to tell the address and this function is only looking for addresses in the en0 interface when Hotspot network is actually bridge100.
So question is - Is there a "normal" way to make GCDWebServer work with bridge100?
SECOND PART:
Although serverURL is nil, method startWithPort returns true. So I thought maybe server is running, it just can not tell me its address. So I got device's address with my custom method (if you're interested, I can attach it here, but I'm 100% sure it gives a correct address) and tried to use it in order to "speak" with web server, but no luck with that - server doesn't respond. So maybe startWithPort returns a false result after all.
Very interesting observation - when I change primaryInterface to bridge100 in GCDWebServerGetPrimaryIPAddress method, it fixes the issue. GCDWebServer shows a correct address and it is definitely running since I can have an access to the device folder.
Any help would be appreciated!
So question is - Is there a "normal" way to make GCDWebServer work with bridge100?
No. You would need to fork GCDWebServer and patch this function.
Although serverURL is nil, method startWithPort returns true.
The server is certainly running if this method returns true. The ports are open and listening (and by default are bound to all interfaces). The problem is that you need to figure out what IP to use to reach the server from outside the iPhone.
In order to summarize:
GCDWebServer can be used in the hotspot network although serverURL is nil.
What you need to do is next:
Define IP address of your device on your own. Here's a method you can use:
- (void)getDeviceAddress
{
NSString *address = #"";
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)
{
temp_addr = interfaces;
while(temp_addr != NULL)
{
if(temp_addr->ifa_addr->sa_family == AF_INET)
{
NSString *interfaceName = [NSString stringWithUTF8String:temp_addr->ifa_name];
if([interfaceName isEqualToString:#"bridge100"] || [interfaceName isEqualToString:#"en0"])
{
//fetch ip address
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
break;
}
}
temp_addr = temp_addr->ifa_next;
}
}
freeifaddrs(interfaces);
return address;
}
2) Remember the port you used in order to start GCDWebServer
3) Build your serverURL:
- (NSString *)serverURL {
NSString *serverURL = [NSString stringWithFormat:#"http:/%#:%d", [self getDeviceAddress], serverPort]; //serverPort is the port your GCDWebServer is running on
return serverURL;
}
I have a Raspberry Pi connected to my Wifi LAN that responds to mDNS as mqtt-broker.local.
I can find it on my laptop with this command:
$ avahi-resolve-host-name -4 mqtt-broker.local
mqtt-broker.local 192.168.XXX.YYY
I have an ESP32 DOIT DevKit device that can send messages to the Raspberry Pi via Wifi if I use the IP address 192.168.XXX.YYY, however I would like my ESP32 to resolve the host using mDNS.
I am not able to get the mDNS working, the code at the bottom prints:
Finding the mDNS details...
No services found...
Done finding the mDNS details...
What's wrong with this code?
What should I put as service in MDNS.queryService("mqtt-broker", "tcp")? I have tried even with service mqtt with no luck, however this shouldn't matter, the mDNS stuff should work regardless what's exposed from the Raspberry Pi (HTTP server, MQTT, FTP whatever...)
Checking here https://github.com/espressif/arduino-esp32/blob/master/libraries/ESPmDNS/src/ESPmDNS.h#L98 there is not that much information about this "service" and "proto", and I am not that much familiar with low-level C/C++, what are these things?
This is the code I am using:
// import the headers
#include <ESPmDNS.h>
void findMyPi() {
Serial.println("Finding the mDNS details...");
// make sure we are connected to the Wifi
while (WiFi.status() != WL_CONNECTED) {
delay(250);
Serial.println("Not yet connected to Wifi...");
}
if (!MDNS.begin("whatever_this_could_be_anything")) {
Serial.println("Error setting up MDNS responder!");
}
// what should I put in here as "service"?
int n = MDNS.queryService("mqtt-broker", "tcp");
if (n == 0) {
Serial.println("No services found...");
}
else {
for (int i = 0; i < n; ++i) {
// Print details for each service found
Serial.print(" ");
Serial.print(i + 1);
Serial.print(": ");
Serial.print(MDNS.hostname(i)); // "mqtt-broker" ??? How can I find it???
Serial.print(" (");
Serial.print(MDNS.IP(i));
Serial.print(":");
Serial.print(MDNS.port(i));
Serial.println(")");
}
}
Serial.println("Done finding the mDNS details...");
}
This function has been inspired by this example:
https://github.com/espressif/arduino-esp32/blob/master/libraries/ESPmDNS/examples/mDNS-SD_Extended/mDNS-SD_Extended.ino
Ended up using a different method from the class on that mDNS library provided by Espressif (ESPmDNS.h), a combination of:
IPAddress serverIp = MDNS.queryHost(mDnsHost);
while loop on this check serverIp.toString() == "0.0.0.0"
This is the code that glues up all together:
// on my laptop (Ubuntu) the equivalent command is: `avahi-resolve-host-name -4 mqtt-broker.local`
String findMDNS(String mDnsHost) {
// the input mDnsHost is e.g. "mqtt-broker" from "mqtt-broker.local"
Serial.println("Finding the mDNS details...");
// Need to make sure that we're connected to the wifi first
while (WiFi.status() != WL_CONNECTED) {
delay(250);
Serial.print(".");
}
if (!MDNS.begin("esp32whatever")) {
Serial.println("Error setting up MDNS responder!");
} else {
Serial.println("Finished intitializing the MDNS client...");
}
Serial.println("mDNS responder started");
IPAddress serverIp = MDNS.queryHost(mDnsHost);
while (serverIp.toString() == "0.0.0.0") {
Serial.println("Trying again to resolve mDNS");
delay(250);
serverIp = MDNS.queryHost(mDnsHost);
}
Serial.print("IP address of server: ");
Serial.println(serverIp.toString());
Serial.println("Done finding the mDNS details...");
return serverIp.toString();
}
I need some help understanding why I'm seeing the following.
AFNetworking 3.0.4 on iOS9.x
If I grab the reachability manager with
AFNetworkReachabilityManager.sharedManager()
and then add a setReachabilityStatusChangeBlock then I get exactly what I expect.
Airplane mode: NotReachable
WiFi off: ReachableViaWWAN
WiFi on: ReachableViaWiFi
So - the general case all is working as expected.
But I'd like to be more precise - we know what IP address we're talking to so I'd like to use managerForAddress instead. Here's the code I've cargo-culted from the net (most examples out there are for obj-c not swift) - in the following - ip is a string of the address (dotted quad form):
let isLittleEndian = Int(OSHostByteOrder()) == OSLittleEndian
let htons = isLittleEndian ? _OSSwapInt16 : { $0 }
var address = sockaddr_in()
address.sin_len = UInt8(sizeofValue(address))
address.sin_family = sa_family_t(AF_INET)
address.sin_port = htons(443);
address.sin_addr.s_addr = inet_addr(ip);
addressReachability = withUnsafePointer(&address) {
AFNetworkReachabilityManager(forAddress: UnsafePointer($0))
}
I can then add a setReachabilityStatusChangeBlock to this manager and start monitoring.
Now - it doesn't matter what I send in as IP - an empty string, a valid IP address where 443 is listening, a valid IP address where 443 is not listening, an invalid IP address - the results match exactly that of the sharedManager - if I'm on wifi - then it's ReachableViaWiFi, if I'm only on 4G then it's ReachableViaWWAN, if I'm in airplane mode it's NotReachable.
So - either I've totally misunderstood how managerForAddress is used or I have a code bug :) In either case I'd love to hear what I've misunderstood/got wrong.
Calling 'GetFlags' on NetworkReachability always returns 'Reachable' even if
the domain doesn't exist. I'll attach a full test solution but the following
code should result in "Failed..." but results in "Success = True".
The code is based on the Reachability sample.
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Perform any additional setup after loading the view, typically from a nib.
string host = "http://nonsense.test12345679.com";
using (var r = new SystemConfiguration.NetworkReachability (host)) {
NetworkReachabilityFlags flags;
if (r.TryGetFlags (out flags)) {
_label.Text = "Success = " + IsReachableWithoutRequiringConnection (flags);
} else {
_label.Text = "Failed to get flags ";
}
}
}
public bool IsReachableWithoutRequiringConnection(NetworkReachabilityFlags flags)
{
// Is it reachable with the current network configuration?
bool isReachable = (flags & NetworkReachabilityFlags.Reachable) != 0;
// Do we need a connection to reach it?
bool noConnectionRequired = (flags & NetworkReachabilityFlags.ConnectionRequired) == 0
|| (flags & NetworkReachabilityFlags.IsWWAN) != 0;
return isReachable && noConnectionRequired;
}
If you switch the device into airplane mode then reachability is correctly
returned as false.
I'm testing on an iPad Air, iOS 9.0.2 and the latest stable Xamarin.
iOS System Configuration framework's Reachability does not actually check to see if the host is alive (i.e. it is not a ping/icmp/http/... based test).
A remote host is considered reachable when a data packet, sent by an
application into the network stack, can leave the local device.
Reachability does not guarantee that the data packet will actually be
received by the host.
iOS SCNetworkReachability Reference
Also a good SO A/Q on about the Reachability flags : How to interpret NetworkReachabilityFlags in Xamarin.iOS?
I haven't used that functionality directly; but have you tried the nuget package for connectivity? https://github.com/jamesmontemagno/Xamarin.Plugins/tree/master/Connectivity. I have tried it and it seems to work pretty well. It has methods for testing if a specific endpoint is reachable.
bool canConnect = CrossConnectivity.Current.IsConnected
&& await CrossConnectivity.Current.IsRemoteReachable(host, portInt));
I'm making an iOS app where I'd need to check if a host, for example, google.com, is available (or is up/down). I've been using this code:
NSString *host = theTextField.text;
bool success = false;
const char *host_name = [host
cStringUsingEncoding:NSASCIIStringEncoding];
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL,
host_name);
SCNetworkReachabilityFlags flags;
success = SCNetworkReachabilityGetFlags(reachability, &flags);
bool isAvailable = success && (flags & kSCNetworkFlagsReachable) &&
!(flags & kSCNetworkFlagsConnectionRequired);
if (isAvailable) {
status.text = #"The host is up! :)";
} else {
status.text = #"The host is down! :(";
}
}
But I've a felling it doesn't works like it should. For example if I enter google.com it says it's up, okay, but if I enter the url of my webpage (which is down ATM) says it's up as well (the address is zad0xsis.net). If I enter a fake address it says it's down, so it may work right and I may be thinking odd things IDK.
zad0xsis.net is up for me.
The Reachability use a the same principle as the ping command line (DNS Resolution + Ping in fact).
I think you should use NSURLConnection to see if the webserver is up.
Have you tried ping zad0xsis.net from terminal? If you receive a successful response, then i believe it is about some networking stuff which i am not good at all.