iOS alternative to CFHostStartInfoResolution using public dns server? - ios

My application seems to work fine most of the time.
When it runs in China, however, it sometimes fails to give me my the IP address of our server. I use this to get the IP address, asynchronously:
//...
CFStringRef hostAddress = CFStringCreateWithCString( kCFAllocatorDefault, hostAddressStr, kCFStringEncodingASCII );
CFHostRef theHostRef = CFHostCreateWithName( kCFAllocatorDefault, hostAddress );
CFRelease(hostAddress);
CFHostClientContext context;
context.version = 0;
context.retain = nil;
context.release = nil;
context.copyDescription = nil;
context.info = self;
Boolean set_ok = CFHostSetClient( theHostRef, myCFHostClientCallBack, &context );
CFStreamError cfError;
CFHostScheduleWithRunLoop( theHostRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
Boolean is_resolving = CFHostStartInfoResolution( theHostRef, kCFHostAddresses, &cfError );
//...
void myCFHostClientCallBack( CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void* info) {
int count = CFArrayGetCount(arrayRef);
for( int index = 0; index < count; ++index ) {
CFDataRef saData = (CFDataRef)CFArrayGetValueAtIndex(arrayRef, index);
struct sockaddr_in* remoteAddr = (struct sockaddr_in*)CFDataGetBytePtr(saData);
if( remoteAddr != NULL) {
char* addressString = inet_ntoa(remoteAddr->sin_addr);
if( addressString != NULL) {
if( resolvedAddressString == NULL ) {
resolvedAddressString = addressString;
}
}
}
}
// ...
}
I suspect that iOS performs a domain name to ip address lookup using whatever DNS is locally available, but I have strong suspicions that that DNS fails for some reason, some of the time.
Is there a easy way for me to make a similar query using a public dns server, such as google, in case the ISP provided DNS server fails to return the IP address of our server?
I have not found an API for this, so if possible, I'd like to have some link to some simple sample code to acheive this.

Related

iOS Resolve DNS programmatically is returning invalid IPs sometimes

Hi,
I am working on an iOS app which requires to resolve DNS programmatically.
Consider this as a proxy to resolve all dns queries on iPhone. I receive DNS queries from each app on iPhone and send back corresponding IPList
I have tried a couple of methods but both have same kind of responses. The one I decided to move with is given below resolveHost function written in objective-c and c I am calling this method from swift code.
This is how I am calling from swift, also sharing sample host/url, value of host can be any domain ("google.com, apple.com etc") or a domain/host as a result of trails when you open a site in mkwebview
let host = "www.opera.com"
let ipArray = ResolveUtil().resolveHost(host, usingDNSServer: "8.8.8.8") as! [String]
More specifically Facebook app does not work well with IPs returned from function resolveHost
By not working well I mean app does not connect to IPs returned from the functions
Some times it returns 192.16.192.16 as part of other IPs for some hosts/domains. What is this IP?
- (NSArray*)resolveHost:(NSString *)host usingDNSServer:(NSString *)dnsServer
{
NSMutableArray* result = [[NSMutableArray alloc] init];
struct __res_state res;
setup_dns_server(&res, [dnsServer cStringUsingEncoding:NSASCIIStringEncoding]);
int count;
char** ips = query_ips(&res, [host cStringUsingEncoding:NSUTF8StringEncoding], &count);
for (int i=0; i<count; i++){
[result addObject:[[NSString alloc] initWithCString:ips[i] encoding:NSASCIIStringEncoding]];
}
for (int i=0; i<count; i++){
free(ips[i]);
}
free(ips);
ips = NULL;
return result;
}
char ** query_ips(res_state res, const char *host, int* count)
{
u_char answer[NS_PACKETSZ];
int len = res_nquery(res, host, ns_c_in, ns_t_a, answer, sizeof(answer));
ns_msg handle;
ns_initparse(answer, len, &handle);
int messageCount = ns_msg_count(handle, ns_s_an);
*count = messageCount;
char **ips = malloc(messageCount * sizeof(char *));
for (int i=0; i < messageCount; i++) {
ips[i] = malloc(16 * sizeof(char));
memset(ips[i], '\0', sizeof(16));
ns_rr rr;
if(ns_parserr(&handle, ns_s_an, i, &rr) == 0) {
strcpy(ips[i], inet_ntoa(*(struct in_addr *)ns_rr_rdata(rr)));
}
}
return ips;
}
Other Method
func resolveIp(_ hostUrl:String) -> [String]{
var ips:[String] = [String]()
let host = CFHostCreateWithName(nil,hostUrl as CFString).takeRetainedValue()
CFHostStartInfoResolution(host, .addresses, nil)
var success: DarwinBoolean = false
if let addresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as NSArray? {
for case let theAddress as NSData in addresses {
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
if getnameinfo(theAddress.bytes.assumingMemoryBound(to: sockaddr.self), socklen_t(theAddress.length),
&hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 {
let numAddress = String(cString: hostname)
ips.append(numAddress)
}
}
}
Logger.info("\(#function) validIPs:\(ips.joined(separator: "-")) url:\(hostUrl)")
return ips
}
192.16.192.16
What is this IP? Perfectly valid IPv4. It resolves back to basento.nikhef.nl.
Why is it returned?
I don't know. Maybe, see resolv.h:
* Mac OS supports a DNS query routing API (see <dns.h>) which is used by
* most system services to access DNS. The BIND-9 APIs described here are
* a lower-level that does not do query routing or search amongst multiple
* resolver clients. The results of DNS queries from this API may differ
* significantly from the results of queries sent to the <dns.h> API. We
* strongly encourage developers to use higher-level APIs where possible.
By not working well I mean app does not connect to IPs returned from the functions.
This has nothing to do with the name/ip address resolution.
The problem can be elsewhere. Your provider can block it, no service is running on the IP address, you're not allowed to access it, ... Many reasons.
More specifically Facebook app does not work well with IPs returned from function resolveHost.
This doesn't make sense to me at all. You have your own app, you're resolving IP addresses in it and then saying that it doesn't work with Facebook. Frankly, I have no idea what do you mean with this.
Why am I answering this question? Well, you shouldn't blindly copy & paste code from other Stack Overflow questions or any other sites. Did a research and it looks like a copy & paste of some other answers.
Why? The code in your question doesn't handle errors, doesn't follow documentation, ... It's a pure luck that it works for you.
What if this is your problem? Did you ever consider this option?
Here's an example of Resolver you can use / test with your conditions. It may or may not fix your issues.
#import <resolv.h>
#import Darwin.POSIX.arpa;
#interface Resolver: NSObject
- (nullable instancetype)initWithDNSServer:(nonnull NSString *)server;
- (nullable NSArray<NSString *> *)resolveHost:(nonnull NSString *)host;
#end
#implementation Resolver {
struct __res_state *state;
}
- (void)dealloc {
if (state != NULL) {
// man 3 resolver:
//
// res_ndestroy() should be call to free memory allocated by res_ninit() after last use.
if ((state->options & RES_INIT) == RES_INIT) {
res_ndestroy(state);
}
free(state);
state = NULL;
}
}
- (nullable instancetype)initWithDNSServer:(nonnull NSString *)server {
if ((self = [super init]) == nil) {
return nil;
}
// man 3 resolver:
//
// The memory referred to by statp must be set to all zeros prior
// to the first call to res_ninit(). res_ndestroy() should be call to free memory
// allocated by res_ninit() after last use.
if ((state = calloc(1, sizeof(*state))) == NULL) {
return nil;
}
// 0 success
if (res_ninit(state) != 0) {
return nil;
}
// Avoid calling inet_aton later with NULL if we can't convert it to ASCII
if (![server canBeConvertedToEncoding:NSASCIIStringEncoding]) {
return nil;
}
struct in_addr addr;
// man 3 inet_aton:
//
// It returns 1 if the string was successfully interpreted ...
if (inet_aton([server cStringUsingEncoding:NSASCIIStringEncoding], &addr) != 1) {
return nil;
}
state->nsaddr_list[0].sin_addr = addr;
state->nsaddr_list[0].sin_family = AF_INET;
state->nsaddr_list[0].sin_port = htons(NS_DEFAULTPORT);
state->nscount = 1;
return self;
}
- (nullable NSArray<NSString *> *)resolveHost:(nonnull NSString *)host {
// Avoid calling res_nquery with NULL
if (![host canBeConvertedToEncoding:NSASCIIStringEncoding]) {
return nil;
}
u_char answer[NS_PACKETSZ];
int len = res_nquery(state, [host cStringUsingEncoding:NSASCIIStringEncoding],
ns_c_in, ns_t_a, answer, sizeof(answer));
// -1 = error
if (len == -1) {
return nil;
}
ns_msg handle;
// 0 success, -1 error
if (ns_initparse(answer, len, &handle) != 0) {
return nil;
}
u_int16_t count = ns_msg_count(handle, ns_s_an);
NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
for (int i = 0 ; i < count ; i++) {
ns_rr rr;
// 0 success, -1 error
if (ns_parserr(&handle, ns_s_an, i, &rr) == 0) {
char *address = inet_ntoa(*(struct in_addr *)ns_rr_rdata(rr));
if (address == NULL) {
continue;
}
NSString *ip = [NSString stringWithCString:address
encoding:NSASCIIStringEncoding];
[result addObject:ip];
}
}
return result;
}
#end
You can use it in this way:
Resolver *resolver = [[Resolver alloc] initWithDNSServer:#"8.8.8.8"];
NSLog(#"%#", [resolver resolveHost:#"www.opera.com"]);
Output:
(
"13.102.114.111",
"52.57.141.185",
"18.196.127.98"
)
I might have not explained the problem statement in my original question but I managed to fix the bug, so I thought I should write here my findings.
My app works as a dns proxy, so its main responsibility was to resolve domains and return IPs.
I used resolveHost function to resolve the IP. This function has all the issues mentioned by zrzka so if somebody wants to use please do consider his points.
The problem I had was that the function returns a few IPs against specific hosts/domains which does not seem valid, I am saying invalid because these were not pingable IPs and from Wireshark I confirmed connection on these IPs were unsuccessful, even if returned IPList contains valid IP at some index it was still causing unnecessary delay due to first try on invalid IPs as they reside before valid IPs in the list.
On further investigation I came to know these invalid IPs were against answer type CNAME which depicts Alias in DNS record, I don't know I should still call them invalid or not but ignoring them did the job for me. Now I only accept A type or AAAA type answers from DNS response. I have achieved this by a simple check in the following function.
char ** query_ips(res_state res, const char *host, int* count)
{
u_char answer[NS_PACKETSZ];
int len = res_nquery(res, host, ns_c_in, ns_t_a, answer, sizeof(answer));
ns_msg handle;
ns_initparse(answer, len, &handle);
int messageCount = ns_msg_count(handle, ns_s_an);
*count = messageCount;
char **ips = malloc(messageCount * sizeof(char *));
for (int i=0; i < messageCount; i++) {
ips[i] = malloc(16 * sizeof(char));
memset(ips[i], '\0', sizeof(16));
ns_rr rr;
if(ns_parserr(&handle, ns_s_an, i, &rr) == 0) {
if (1 == rr.type || 28 == rr.type) // here is the new check
strcpy(ips[i], inet_ntoa(*(struct in_addr *)ns_rr_rdata(rr)));
}
}
return ips;
}

iOS - How to get the IPv6 DNS IP address?

I can use the code below to get the IPv4 DNS address. With the IPv6 DNS address, res_ninit() can not analyse IPv6 address. Like this, iOS - Get device's DNS server address on IPv6 only network, sin_family is always AF_UNSPEC.
Is there any other solutions to get the IPv6 DNS address? Thanks for help.
+ (void)outPutDNSServers {
res_state res = malloc(sizeof(struct __res_state));
int result = res_ninit(res);
if ( result == 0 ) {
for ( int i = 0; i < res->nscount; i++ ) {
NSString *s = [NSString stringWithUTF8String : inet_ntoa(res->nsaddr_list[i].sin_addr)];
NSLog(#"%#",s);
}
}
else {
NSLog(#"%#",#" res_init result != 0");
}
res_nclose(res);
}
Edit:
I have already solved the problem like below.
+ (void)outPutDNSServers {
res_state res = malloc(sizeof(struct __res_state));
int result = res_ninit(res);
if (result == 0) {
union res_9_sockaddr_union *addr_union = malloc(res->nscount * sizeof(union res_9_sockaddr_union));
res_getservers(res, addr_union, res->nscount);
for (int i = 0; i < res->nscount; i++) {
if (addr_union[i].sin.sin_family == AF_INET) {
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(addr_union[i].sin.sin_addr), ip, INET_ADDRSTRLEN);
NSString *dnsIP = [NSString stringWithUTF8String:ip];
NSLog(#"IPv4 DNS IP: %#", dnsIP);
} else if (addr_union[i].sin6.sin6_family == AF_INET6) {
char ip[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &(addr_union[i].sin6.sin6_addr), ip, INET6_ADDRSTRLEN);
NSString *dnsIP = [NSString stringWithUTF8String:ip];
NSLog(#"IPv6 DNS IP: %#", dnsIP);
} else {
NSLog(#"Undefined family.");
}
}
}
res_nclose(res);
}

SSL Certificate Details in UIWebView

I need to show SSL certificate details of displayed URL in a UIWebView something like Google's Chrome browser shows:
How to obtain this data from UIWebView.
We are intercepting the call at the network level (rather than the UIWebView), and using [NSURLConnectionDelegate connection:willSendRequestForAuthenticationChallenge:]. This gives you a NSURLAuthenticationChallenge instance, and if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust then NSURLAuthenticationChallenge.protectionSpace.serverTrust is a SecTrustRef.
Given the SecTrustRef, you can follow SecCertificateRef: How to get the certificate information? and do something like this:
#import <Security/Security.h>
#import <openssl/x509.h>
X509* X509CertificateFromSecTrust(SecTrustRef trust) {
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, 0);
CFDataRef certDataRef = SecCertificateCopyData(cert);
NSData *certData = (__bridge NSData *)certDataRef;
const void* certDataBytes = certData.bytes;
X509* result = d2i_X509(NULL, (const unsigned char**)&certDataBytes, certData.length);
CFRelease(certDataRef);
return result;
}
static NSString* X509NameField(X509_NAME* name, char* key) {
if (name == NULL)
return nil;
int nid = OBJ_txt2nid(key);
int index = X509_NAME_get_index_by_NID(name, nid, -1);
X509_NAME_ENTRY *nameEntry = X509_NAME_get_entry(name, index);
if (nameEntry == NULL)
return nil;
ASN1_STRING *nameASN1 = X509_NAME_ENTRY_get_data(nameEntry);
if (nameASN1 == NULL)
return nil;
unsigned char *issuerName = ASN1_STRING_data(nameASN1);
return [NSString stringWithUTF8String:(char *)issuerName];
}
NSString* X509CertificateGetSubjectCommonName(X509* cert) {
if (cert == NULL)
return nil;
X509_NAME *subjectName = X509_get_subject_name(cert);
return X509NameField(subjectName, "CN"); // Common name.
}
NSString* X509CertificateGetIssuerName(X509* certX509) {
if (certX509 == NULL)
return nil;
X509_NAME *issuerX509Name = X509_get_issuer_name(certX509);
if (issuerX509Name == NULL)
return nil;
return X509NameField(issuerX509Name, "O"); // organization
}
This is not a simple piece of work. You'll need to understand OpenSSL's X509 code, the Security framework, and whatever you're doing at the network layer for SSL trust checks. There might be other ways that you can get hold of a SecTrustRef or SecCertificateRef, but I'm not using them if there are.

how to resolve the hostname of a device in my LAN from its ip address on this LAN?

I have to resolve the hostname of a device in my LAN from its ip address on this LAN.
I have some code that works for external ip address but not the internally connected devices .
Below i have attached the code .
if you have any idea to get hostname of remote machine from it's IP in iOS/OSX, it'll make my day.
int error;
struct addrinfo *results = NULL;
error = getaddrinfo("173.194.34.24", NULL, NULL, &results);
if (error != 0)
{
NSLog (#"Could not get any info for the address");
}
for (struct addrinfo *r = results; r; r = r->ai_next)
{
char hostname[NI_MAXHOST] = {0};
error = getnameinfo(r->ai_addr, r->ai_addrlen, hostname, sizeof hostname, NULL, 0 , 0);
if (error != 0)
{
continue; // try next one
}
else
{
NSLog (#"Found hostname: %s", hostname);
break;
}
}
freeaddrinfo(results);
or with NSHost
NSLog(#"%# \n%#",[NSHost currentHost],[[NSHost hostWithAddress:#"172.17.241.61"] names]);
Only way to make a DNS lookup directly to a specific DNS is to implement the protocol yourself or use some library.
https://serverfault.com/questions/173187/what-does-a-dns-request-look-like
https://www.ietf.org/rfc/rfc1035.txt
https://github.com/adeboer/rfc1035lib

Port availability check on remote system

How can I tell if a given port is available or not on a local or remote system, in an NSIS page?
Abuot COM ports:
They cannot be detected directly from NSIS so write simple plug-in in C which will detect port by it's number.
This is my basic idea:
void GetListOfLocalPorts(CList<CString, CString>& o_lstPorts)
{
for( int i = 1; i <= 99; i++ )
{
DCB dcb;
HANDLE hCom = NULL;
BYTE byPort = (BYTE)i;
CString strPort;
strPort.Format("COM%d", i);
CString strCom = (CString)"\\\\.\\" + strPort;
SetErrorMode(SEM_FAILCRITICALERRORS);
try
{
hCom = CreateFile(strCom, 0, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hCom == INVALID_HANDLE_VALUE)
continue;
BOOL fSuccess = GetCommState(hCom, &dcb);
CloseHandle(hCom);
if (!fSuccess)
continue;
// Port exists on this machine
o_lstPorts.AddTail(strPort);
}
catch(...)
{
}
}
}

Resources