iOS Resolve DNS programmatically is returning invalid IPs sometimes - ios

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;
}

Related

iOS: How to specify DNS to be used to resolve hostname to IP address?

As the title says I have hostname (eg www.example.com) that I want to resolve using specified DNS server. For example in one case I want to use google's IPv4 DNS and in other case google's IPv6 DNS.
I have browsed SO for something like this on iOS, and found questions like this one (Swift - Get device's IP Address), so I am sure it can be done, but I am unclear how?
How can I do this?
EDIT 06/07/2018
#mdeora suggested solution from http://www.software7.com/blog/programmatically-query-specific-dns-servers-on-ios/
This solution works but only if I use IPv4 DNS, for example google's "8.8.8.8". If I try to use IPv6 DNS 2001:4860:4860::8888, i get nothing.
I have managed to change conversion:
void setup_dns_server(res_state res, const char *dns_server)
{
res_ninit(res);
struct in_addr addr;
// int returnValue = inet_aton(dns_server, &addr);
inet_pton(AF_INET6, dns_server, &addr); // for IPv6 conversion
res->nsaddr_list[0].sin_addr = addr;
res->nsaddr_list[0].sin_family = AF_INET;
res->nsaddr_list[0].sin_port = htons(NS_DEFAULTPORT);
res->nscount = 1;
};
But still have trouble with this:
void query_ip(res_state res, const char *host, char ip[])
{
u_char answer[NS_PACKETSZ];//NS_IN6ADDRSZ 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);
if(ns_msg_count(handle, ns_s_an) > 0) {
ns_rr rr;
if(ns_parserr(&handle, ns_s_an, 0, &rr) == 0) {
strcpy(ip, inet_ntoa(*(struct in_addr *)ns_rr_rdata(rr)));
}
}
}
I get -1 for len. From what I gather it seems I need to configure res_state for IPv6.
Here the code from my blogpost, that was already mentioned above, just slightly adapted to use IPv6.
Adapt setup_dns_server
First we could start with the changes to setup_dns_server:
void setup_dns_server(res_state res, const char *dns_server) {
struct in6_addr addr;
inet_pton(AF_INET6, dns_server, &addr);
res->_u._ext.ext->nsaddrs[0].sin6.sin6_addr = addr;
res->_u._ext.ext->nsaddrs[0].sin6.sin6_family = AF_INET6;
res->_u._ext.ext->nsaddrs[0].sin6.sin6_port = htons(NS_DEFAULTPORT);
res->nscount = 1;
}
Add __res_state_ext
This wouldn't compile because of a missing struct __res_state_ext. This structure is unfortunately in a private header file.
But the definition of that one can be take from here:
https://opensource.apple.com/source/libresolv/libresolv-65/res_private.h.auto.html :
struct __res_state_ext {
union res_sockaddr_union nsaddrs[MAXNS];
struct sort_list {
int af;
union {
struct in_addr ina;
struct in6_addr in6a;
} addr, mask;
} sort_list[MAXRESOLVSORT];
char nsuffix[64];
char bsuffix[64];
char nsuffix2[64];
};
The struct can be added e.g. at the top of the file.
Adapt resolveHost
The changes here include the longer buffer for ip (INET6_ADDRSTRLEN). res_ninit moved from setup_dns_server into this method and is matched now with a res_ndestroy.
+ (NSString *)resolveHost:(NSString *)host usingDNSServer:(NSString *)dnsServer {
struct __res_state res;
char ip[INET6_ADDRSTRLEN];
memset(ip, '\0', sizeof(ip));
res_ninit(&res);
setup_dns_server(&res, [dnsServer cStringUsingEncoding:NSASCIIStringEncoding]);
query_ip(&res, [host cStringUsingEncoding:NSUTF8StringEncoding], ip);
res_ndestroy(&res);
return [[NSString alloc] initWithCString:ip encoding:NSASCIIStringEncoding];
}
Retrieving IPv6 addresses
The changes above are already sufficient if you just want to use a IPv6 address for your DNS server. So in query_ip there are no changes necessary if you still want to retrieve the IPv4 addresses.
In case you would like to retrieve IPv6 addresses from the DNS server also, you can do this:
void query_ip(res_state res, const char *host, char ip[]) {
u_char answer[NS_PACKETSZ];
int len = res_nquery(res, host, ns_c_in, ns_t_aaaa, answer, sizeof(answer));
ns_msg handle;
ns_initparse(answer, len, &handle);
if(ns_msg_count(handle, ns_s_an) > 0) {
ns_rr rr;
if(ns_parserr(&handle, ns_s_an, 0, &rr) == 0) {
inet_ntop(AF_INET6, ns_rr_rdata(rr), ip, INET6_ADDRSTRLEN);
}
}
}
Please note: we use here ns_t_aaaa to get AAAA resource records (quad-A record), because in DNS this specifies the mapping between IPv6 address and hostname. For many hosts, there is no such quad-A record, meaning you can just reach them via IPv4.
Call
You would call it e.g. like so:
NSString *resolved = [ResolveUtil resolveHost:#"www.google.com" usingDNSServer:#"2001:4860:4860::8888"];
NSLog(#"%#", resolved);
The result would the look like this:
Disclaimer
These are just simple example calls, that demonstrate the basic usage of the functions. There is no error handling.
You can do this using below swift code -
import Foundation
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = ["dig", "#8.8.8.8", "google.com"]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
print(output!)
In the above code use the DNS server of your choice by replacing 8.8.8.8
For Objective-C iOS refer below link -
https://www.software7.com/blog/programmatically-query-specific-dns-servers-on-ios/
Below is the revised code for setting up dns -
void setup_dns_server(res_state res, const char *dns_server)
{
res_ninit(res);
struct in_addr6 addr;
// int returnValue = inet_aton(dns_server, &addr);
inet_pton(AF_INET6, dns_server, &addr); // for IPv6 conversion
res->nsaddr_list[0].sin_addr = addr;
res->nsaddr_list[0].sin_family = AF_INET6;
res->nsaddr_list[0].sin_port = htons(NS_DEFAULTPORT);
res->nscount = 1;
};
And the query code -
void query_ip(res_state res, const char *host, char ip[])
{
u_char answer[NS_PACKETSZ];//NS_IN6ADDRSZ 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);
if(ns_msg_count(handle, ns_s_an) > 0) {
ns_rr rr;
if(ns_parserr(&handle, ns_s_an, 0, &rr) == 0) {
strcpy(ip, inet_ntoa(*(struct in_addr6 *)ns_rr_rdata(rr)));
}
}
}
PS - I have not been able to test it, but it should work for ipv6 dns.

Assigning from a Class method binds the class instead of its return value

I've noticed something strange in the behavior of code I'm currently writing and thought I would ask here to see if I'm doing something silly that would cause this to happen.
Basically, when I assign a variable to the return value of my class method, instead of the variable holding a reference to the return value, it's holding a reference to the class. See the code below:
NSArray * newAddresses = [MyHost addressesForHostname: #"google.com"];
Which has a method signature of
+ (NSArray *) addressesForHostname: (NSString *)hostname
And returns
return (__bridge_transfer NSArray *) ipAddresses; // ipAddresses is a CFMutableArrayRef
As you can see, I'm using toll-free bridging to use CoreFoundation objects as I'm collecting a list of IP addresses for some network interfaces.
After newAddresses has been assigned to, I look at the class of the newAddresses array in LLDB and get:
(lldb) po [newAddresses class]
MyHost
Am I mistaken in my assumptions about how I'm using __bridge_transfer? All of the objects use to make up ipAddresses are CFStringRefs.
EDIT: I was asked for the whole method, so here it is!
+ (NSArray *) addressesForHostname: (NSString *)hostname {
CFMutableArrayRef ipAddresses;
DLog(#"Getting addresses for host name %#", hostname);
CFHostRef hostRef = CFHostCreateWithName(kCFAllocatorDefault, (__bridge CFStringRef)(hostname));
CFStreamError error;
BOOL didResolve = CFHostStartInfoResolution(hostRef, kCFHostNames, &error); // synchronously get the host.
if (didResolve) {
CFArrayRef responseObjects = CFHostGetAddressing(hostRef, NULL);
long numberOfResponses = CFArrayGetCount(responseObjects);
ipAddresses = CFArrayCreateMutable(kCFAllocatorDefault, numberOfResponses, &kCFTypeArrayCallBacks);
for ( int i = 0 ; i < numberOfResponses; ++i ) {
char * ipAddress;
CFDataRef responseObject = CFArrayGetValueAtIndex(responseObjects, i);
struct sockaddr * currentAddress = (struct sockaddr *) CFDataGetBytePtr(responseObject); // Unwrap the CFData wrapper aound the sockaddr struct
switch (currentAddress->sa_family) {
case AF_INET: { // Internetworking AKA IPV4
DLog(#"Extracting IPV4 address");
struct sockaddr_in * socketAddress = (struct sockaddr_in *) currentAddress;
ipAddress = malloc(sizeof(INET_ADDRSTRLEN));
inet_ntop(AF_INET,
&(socketAddress->sin_addr),
ipAddress,
INET_ADDRSTRLEN);
CFStringRef ipAddressString = CFStringCreateWithCString(kCFAllocatorDefault, ipAddress, kCFStringEncodingASCII);
CFArrayInsertValueAtIndex(ipAddresses, i, ipAddressString);
free(ipAddress);
break;
}
case AF_INET6: { // IPV6
DLog(#"Extracting IPV6 address");
struct sockaddr_in6 * socketAddress = (struct sockaddr_in6 *) currentAddress;
ipAddress = malloc(sizeof(INET6_ADDRSTRLEN));
inet_ntop(AF_INET6,
&(socketAddress->sin6_addr),
ipAddress,
INET6_ADDRSTRLEN);
CFStringRef ipAddressString = CFStringCreateWithCString(kCFAllocatorDefault, ipAddress, kCFStringEncodingASCII);
CFArrayInsertValueAtIndex(ipAddresses, i, ipAddressString);
free(ipAddress);
break;
}
default:
DLog(#"Unsupported addressing protocol encountered. Gracefully ignoring and continuing.");
break;
}
}
CFRelease(responseObjects);
}
CFRelease(hostRef);
return (__bridge_transfer NSArray *) ipAddresses;
}
So I found the solution, and it lies in me forgetting to initialize ipAddresses = nil before anything happens. The way this code is written, it won't assign a value to ipAddresses if it's unable to resolve the hostRef given to CFHostStartInfoResolution. With no value in ipAddresses, it returns an uninitialized pointer that gets casted and has its ownership transferred.
I can't find formal documentation that states this, but I believe this would be undefined behavior.
I should state that if anyone is using this code as reference, I'm experiencing inconsistent crashes on the line where I release hostRef. This is unrelated to the issue that I created for this thread, but is worthwhile to point out.

iOS alternative to CFHostStartInfoResolution using public dns server?

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.

Query DNS to find NAPTR in iOS

I have been having a lot of trouble finding a way to query the DNS to find the NAPTR in iOS. There seem to be many relatively simple ways to resolve to an IP, but I specifically need to find all NAPTR records in a DNS lookup. I'd prefer to do so without having to bring in any external libraries if at all possible. If anyone has been able to do this (or something similar that I can extrapolate from) I'd appreciate any pointers.
All code must function in iOS 5.0+
I ended up using DNSServiceQueryRecord.
DNSServiceRef sdRef;
DNSServiceQueryRecord(&sdRef, 0, 0,
"google.com",
kDNSServiceType_NAPTR,
kDNSServiceClass_IN,
callback,
NULL);
DNSServiceProcessResult(sdRef);
DNSServiceRefDeallocate(sdRef);
In actual use, I found that there was an issue where the app would hang indefinitely if there were no results, so I ended up having to adjust my code to add a timeout on the result.
/*
Attempt to fetch the NAPTR from the stored server address. Since iOS will continue waiting
until told directly to stop (even if there is no result) we must set our own timeout on the
request (set to 5 seconds).
On success, the callback function is called. On timeout, the kSRVLookupComplete notification
is sent.
*/
- (void)attemptNAPTRFetch {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
DNSServiceRef sdRef;
DNSServiceErrorType err;
err = DNSServiceQueryRecord(&sdRef, 0, 0,
[server cStringUsingEncoding:[NSString defaultCStringEncoding]],
kDNSServiceType_NAPTR,
kDNSServiceClass_IN,
callback,
NULL);
// This stuff is necessary so we don't hang forever if there are no results
int dns_sd_fd = DNSServiceRefSockFD(sdRef);
int nfds = dns_sd_fd + 1;
fd_set readfds;
struct timeval tv;
int result;
int stopNow = 0;
int timeOut = 5; // Timeout in seconds
while (!stopNow) {
FD_ZERO(&readfds);
FD_SET(dns_sd_fd, &readfds);
tv.tv_sec = timeOut;
tv.tv_usec = 0;
result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv);
if (result > 0) {
if(FD_ISSET(dns_sd_fd, &readfds)) {
err = DNSServiceProcessResult(sdRef);
if (err != kDNSServiceErr_NoError){
NSLog(#"There was an error");
}
stopNow = 1;
}
}
else {
printf("select() returned %d errno %d %s\n", result, errno, strerror(errno));
if (errno != EINTR) {
stopNow = 1;
postNotification(kSRVLookupComplete, nil);
}
}
}
DNSServiceRefDeallocate(sdRef);
});
}
Then, for the callback:
static void callback(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *fullname,
uint16_t rrtype,
uint16_t rrclass,
uint16_t rdlen,
const void *rdata,
uint32_t ttl,
void *context)
{
uint16_t order, pref;
char flag;
NSMutableString *service = [[NSMutableString alloc] init];
NSMutableString *replacement = [[NSMutableString alloc] init];
const char *data = (const char*)rdata;
order = data[1];
pref = data[3];
flag = data[5];
int i = 7;
while (data[i] != 0){
[service appendString:[NSString stringWithFormat:#"%c", data[i]]];
i++;
}
i += 2;
while(data[i] != 0){
if(data[i] >= 32 && data[i] <= 127)
[replacement appendString:[NSString stringWithFormat:#"%c", data[i]]];
else
[replacement appendString:#"."];
i++;
}
NSLog(#"\nOrder: %i\nPreference: %i\nFlag: %c\nService: %#\nReplacement: %#\n", order, pref, flag, service, replacement);
}
This seems to do the trick for me. You would of course do any other necessary work using all the parsed data in the callback or store the data somewhere to be used later.

How get trap with indy TidSNMP component

I'm using c++builderXE with Indy 10.5.7 and I'm trying to receive trap from another agent snmp.
I have no info describing how to do the program to receive trap.
Below you can find the snippet of code which I'm trying to use now.
The ReceiveTrap() method always return 0, which means non data received.
I tested the PC configuration with another program I made several years ago using spare API and the trap is received so I don't this it should be a configuration problem.
Have you some suggestions of hat I'm wrong in the routine below?
void __fastcall TForm1::LabelReceiveTrapClick(TObject * Sender)
{
static bool status = false;
int ists;
String Fun = "[SimpleReceiveTrap] ";
TSNMPInfo * infoSnmp = 0;
try
{
status = !status;
if (status)
{
std::auto_ptr< TIdSNMP >clientSnmp(new TIdSNMP(NULL));
clientSnmp->Community = "public";
clientSnmp->ReceiveTimeout = 1000;
clientSnmp->Binding->Port = 162;
while (status)
{
Application->ProcessMessages();
ists = clientSnmp->ReceiveTrap();
Mylog(L"%s ReceiveTrap status = [%d]", Fun.c_str(), ists);
if (ists > 0)
{
infoSnmp = clientSnmp->Trap;
}
}
}
}
catch (Exception & ex)
{
Mylog(L"%s ERROR", Fun.c_str(), ex.Message.c_str());
}
}
That is not the correct way to set the listening Port for receiving traps. Reading the Binding property allocates and binds a socket to a local IP/Port using the TIdSNMP::BoundIP and TIdSNMP::BoundPort properties. You can't change that socket's local Port after it has already been bound, so your assignment of the Binding->Port property is effectively a no-op.
For that matter, you are trying to manipulate the wrong socket anyway. The Binding socket is used for sending queries to the remote SNMP system. TIdSNMP uses a separate socket for receiving traps. TIdSNMP has a separate TrapPort property for specifying the listening Port of that socket. When the Binding is accessed, the trap socket is allocated and bound to Binding->IP and TIdSNMP::TrapPort. The TrapPort property defaults to 162.
std::auto_ptr< TIdSNMP >clientSnmp(new TIdSNMP(NULL));
clientSnmp->Community = "public";
clientSnmp->ReceiveTimeout = 1000;
clientSnmp->TrapPort = 162; // <--
...
ists = clientSnmp->ReceiveTrap();
Looking at Indy's changelog, there have been some trap-related changes to the listening socket since 10.5.7 was released, so you may need to upgrade to a newer Indy version to get bug fixes. Or you could download the latest version and then just add IdSNMP.pas to your project directly, at least.
Using only the Indi component I can't read the trap rev 2c
But I found a solution using TWSocket and TSNMPInfo which seems to works well
Belowe the code I used:
To get the data I use a TWSocket fro FPiette components suite:
void __fastcall TForm1::LabelStartServerTracSnmpClick(TObject * Sender)
{
String Fun = "[LabelStartServerTracSnmp] ";
try
{
if (WSocket1->State == wsClosed)
{
WSocket1->Proto = "udp";
WSocket1->Addr = "0.0.0.0";
WSocket1->Port = 162;
WSocket1->Listen();
}
else
{
WSocket1->Close();
}
}
catch (Exception & ex)
{
Mylog(L"%s ERROR: [%s]", Fun.c_str(), ex.Message.c_str());
}
}
To analyze the data received I use the Indy
void __fastcall TForm1::WSocket1DataAvailable(TObject * Sender, WORD ErrCode)
{
char buffer[1024];
int len, cnt, srcLen;
TSockAddrIn srcSocket;
String rcvmsg, remHost, s1, s2, Fun = "[WSocket1DataAvailable] ";
TIdSNMP * clientSnmp = NULL;
TSNMPInfo * infoSnmp = NULL;
try
{
srcLen = sizeof(srcSocket);
len = WSocket1->ReceiveFrom(buffer, sizeof(buffer), srcSocket, srcLen);
if (len >= 0)
{
buffer[len] = 0;
rcvmsg = String(buffer, len);
__try
{
clientSnmp = new TIdSNMP(NULL);
infoSnmp = new TSNMPInfo(clientSnmp);
infoSnmp->DecodeBuf(rcvmsg);
cnt = infoSnmp->ValueCount;
if (cnt > 0)
{
// ---------------------------------------------------
for (int idx = 0; idx < cnt; ++idx)
{
s1 = infoSnmp->ValueOID[idx];
s2 = infoSnmp->Value[idx];
Mylog(L"[%s] Trap : [%s] => [%s]", s1.c_str(), s2.c_str());
}
}
}
__finally
{
if (infoSnmp)
{
delete infoSnmp;
infoSnmp = 0;
}
if (clientSnmp)
{
delete clientSnmp;
clientSnmp = 0;
}
}
}
}
catch (Exception & ex)
{
Mylog(L"%s ERROR", Fun.c_str(), ex.Message.c_str());
}
}

Resources