Index: src/OFDNSResolver.h ================================================================== --- src/OFDNSResolver.h +++ src/OFDNSResolver.h @@ -233,12 +233,73 @@ recordType: (of_dns_resource_record_type_t)recordType target: (id)target selector: (SEL)selector context: (nullable id)context; +/*! + * @brief Asynchronously resolves the specified host to socket addresses. + * + * @param host The host to resolve + * @param target The target to call with the result once resolving is done + * @param selector The selector to call on the target. The signature must be + * the following: + * @parblock + * + * void (OFDNSResolver *resolver, OFString *domainName, + * OFData *_Nullable, socketAddresses, + * id _Nullable context, id _Nullable exception) + * + * `resolver` is the acting resolver.@n + * `domainName` is the fully qualified domain name used to + * resolve the host.@n + * `socketAddresses` is OFData containing several + * of_socket_address_t.@n + * `context` is the context object originally passed.@n + * `exception` is an exception that happened during resolving, + * otherwise nil. + * @endparblock + * @param context A context object to pass along to the target + */ +- (void)asyncResolveSocketAddressesForHost: (OFString *)host + target: (id)target + selector: (SEL)selector + context: (nullable id)context; + +/*! + * @brief Asynchronously resolves the specified host to socket addresses. + * + * @param host The host to resolve + * @param addressFamily The desired socket address family + * @param target The target to call with the result once resolving is done + * @param selector The selector to call on the target. The signature must be + * the following: + * @parblock + * + * void (OFDNSResolver *resolver, OFString *domainName, + * OFData *_Nullable socketAddresses, + * id _Nullable context, id _Nullable exception) + * + * `resolver` is the acting resolver.@n + * `domainName` is the fully qualified domain name used to + * resolve the host.@n + * `socketAddresses` is OFData containing several + * of_socket_address_t.@n + * `context` is the context object originally passed.@n + * `exception` is an exception that happened during resolving, + * otherwise nil. + * @endparblock + * @param context A context object to pass along to the target + */ +- (void)asyncResolveSocketAddressesForHost: (OFString *)host + addressFamily: (of_socket_address_family_t) + addressFamily + target: (id)target + selector: (SEL)selector + context: (nullable id)context; + /*! * @brief Closes all sockets and cancels all ongoing requests. */ - (void)close; @end OF_ASSUME_NONNULL_END Index: src/OFDNSResolver.m ================================================================== --- src/OFDNSResolver.m +++ src/OFDNSResolver.m @@ -154,10 +154,34 @@ searchDomainsIndex: (size_t)searchDomainsIndex target: (id)target selector: (SEL)selector context: (id)context; @end + +@interface OFDNSResolver_ResolveSocketAddressContext: OFObject +{ + OFString *_host; + id _target; + SEL _selector; + id _context; + OFMutableArray OF_GENERIC(OF_KINDOF(OFDNSResourceRecord *)) *_records; +@public + unsigned int _expectedResponses; +} + +- (instancetype)initWithHost: (OFString *)host + target: (id)target + selector: (SEL)selector + context: (id)context; +- (void)resolver: (OFDNSResolver *)resolver + didResolveDomainName: (OFString *)domainName + answerRecords: (OFArray *)answerRecords + authorityRecords: (OFArray *)authorityRecords + additionalRecords: (OFArray *)additionalRecords + context: (id)context + exception: (id)exception; +@end @interface OFDNSResolver () - (void)of_setDefaults; - (void)of_obtainSystemConfig; #if defined(OF_HAVE_FILES) && !defined(OF_NINTENDO_3DS) @@ -752,10 +776,131 @@ [_context release]; [_queryData release]; [_cancelTimer release]; [super dealloc]; +} +@end + +@implementation OFDNSResolver_ResolveSocketAddressContext +- (instancetype)initWithHost: (OFString *)host + target: (id)target + selector: (SEL)selector + context: (id)context +{ + self = [super init]; + + @try { + _host = [host copy]; + _target = [target retain]; + _selector = selector; + _context = [context retain]; + + _records = [[OFMutableArray alloc] init]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_host release]; + [_target release]; + [_context release]; + [_records release]; + + [super dealloc]; +} + +- (void)resolver: (OFDNSResolver *)resolver + didResolveDomainName: (OFString *)domainName + answerRecords: (OFArray *)answerRecords + authorityRecords: (OFArray *)authorityRecords + additionalRecords: (OFArray *)additionalRecords + context: (id)context + exception: (id)exception +{ + /* + * TODO: Error handling could be improved. Ignore error if there are + * responses, otherwise propagate error. + */ + + of_socket_address_family_t addressFamily = [context intValue]; + + _expectedResponses--; + + if (exception == nil) { + for (OFDNSResourceRecord *record in answerRecords) { + if (![[record name] isEqual: domainName]) + continue; + + if ([record recordClass] != + OF_DNS_RESOURCE_RECORD_CLASS_IN) + continue; + + switch ([record recordType]) { + case OF_DNS_RESOURCE_RECORD_TYPE_A: + if (addressFamily == + OF_SOCKET_ADDRESS_FAMILY_IPV4 || + addressFamily == + OF_SOCKET_ADDRESS_FAMILY_ANY) + [_records addObject: record]; + + break; + case OF_DNS_RESOURCE_RECORD_TYPE_AAAA: + if (addressFamily == + OF_SOCKET_ADDRESS_FAMILY_IPV6 || + addressFamily == + OF_SOCKET_ADDRESS_FAMILY_ANY) + [_records addObject: record]; + + break; + /* + * TODO: Add CNAMEs and replace them with addresses in + * a later stage. + */ + default: + break; + } + } + } + + if (_expectedResponses == 0) { + void (*method)(id, SEL, OFDNSResolver *, OFString *, OFData *, + id, id) = (void (*)(id, SEL, OFDNSResolver *, OFString *, + OFData *, id, id))[_target methodForSelector: _selector]; + OFMutableData *addresses = nil; + + if ([_records count] > 0) { + addresses = [OFMutableData + dataWithItemSize: sizeof(of_socket_address_t) + capacity: [_records count]]; + + for (OF_KINDOF(OFDNSResourceRecord *) record in + _records) + [addresses addItem: [record address]]; + + [addresses makeImmutable]; + + method(_target, _selector, resolver, domainName, + addresses, _context, nil); + } else { + OFResolveHostFailedException *e; + + e = [OFResolveHostFailedException + exceptionWithHost: _host + recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN + recordType: 0 + error: OF_DNS_RESOLVER_ERROR_UNKNOWN]; + + method(_target, _selector, resolver, domainName, nil, + _context, e); + } + } } @end @implementation OFDNSResolver @synthesize staticHosts = _staticHosts, nameServers = _nameServers; @@ -1607,10 +1752,88 @@ answerRecords, authorityRecords, additionalRecords, query->_context, nil); return false; } + +- (void)asyncResolveSocketAddressesForHost: (OFString *)host + target: (id)target + selector: (SEL)selector + context: (nullable id)context +{ +#ifdef OF_HAVE_IPV6 + [self asyncResolveSocketAddressesForHost: host + addressFamily: OF_SOCKET_ADDRESS_FAMILY_ANY + target: target + selector: selector + context: context]; +#else + [self asyncResolveSocketAddressesForHost: host + addressFamily: OF_SOCKET_ADDRESS_FAMILY_IPV4 + target: target + selector: selector + context: context]; +#endif +} + +- (void)asyncResolveSocketAddressesForHost: (OFString *)host + addressFamily: (of_socket_address_family_t) + addressFamily + target: (id)target + selector: (SEL)selector + context: (id)userContext +{ + void *pool = objc_autoreleasePoolPush(); + OFDNSResolver_ResolveSocketAddressContext *context; + OFNumber *addressFamilyNumber; + + context = [[[OFDNSResolver_ResolveSocketAddressContext alloc] + initWithHost: host + target: target + selector: selector + context: userContext] autorelease]; + + switch (addressFamily) { + case OF_SOCKET_ADDRESS_FAMILY_IPV4: + case OF_SOCKET_ADDRESS_FAMILY_IPV6: + context->_expectedResponses = 1; + break; + case OF_SOCKET_ADDRESS_FAMILY_ANY: + context->_expectedResponses = 2; + break; + default: + @throw [OFInvalidArgumentException exception]; + } + + addressFamilyNumber = [OFNumber numberWithInt: addressFamily]; + + if (addressFamily == OF_SOCKET_ADDRESS_FAMILY_IPV6 || + addressFamily == OF_SOCKET_ADDRESS_FAMILY_ANY) + [self asyncResolveHost: host + recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN + recordType: OF_DNS_RESOURCE_RECORD_TYPE_AAAA + target: context + selector: @selector(resolver:didResolveDomainName: + answerRecords:authorityRecords: + additionalRecords:context: + exception:) + context: addressFamilyNumber]; + + if (addressFamily == OF_SOCKET_ADDRESS_FAMILY_IPV4 || + addressFamily == OF_SOCKET_ADDRESS_FAMILY_ANY) + [self asyncResolveHost: host + recordClass: OF_DNS_RESOURCE_RECORD_CLASS_IN + recordType: OF_DNS_RESOURCE_RECORD_TYPE_A + target: context + selector: @selector(resolver:didResolveDomainName: + answerRecords:authorityRecords: + additionalRecords:context: + exception:) + context: addressFamilyNumber]; + + objc_autoreleasePoolPop(pool); +} - (void)close { void *pool = objc_autoreleasePoolPush(); OFEnumerator OF_GENERIC(OFDNSResolverQuery *) *enumerator; Index: src/socket.h ================================================================== --- src/socket.h +++ src/socket.h @@ -81,13 +81,18 @@ /*! * @brief A socket address family. */ typedef enum { + /** An unknown address family. */ OF_SOCKET_ADDRESS_FAMILY_UNKNOWN, + /** IPv4 */ OF_SOCKET_ADDRESS_FAMILY_IPV4, + /** IPv6 */ OF_SOCKET_ADDRESS_FAMILY_IPV6, + /** Any address family */ + OF_SOCKET_ADDRESS_FAMILY_ANY = 255 } of_socket_address_family_t; #ifndef OF_HAVE_IPV6 struct sockaddr_in6 { sa_family_t sin6_family;