diff --git a/src/libs/libs.pro b/src/libs/libs.pro index ee3b92d3239..4ee52958741 100644 --- a/src/libs/libs.pro +++ b/src/libs/libs.pro @@ -16,7 +16,8 @@ SUBDIRS = \ qmljsdebugclient \ glsl \ qmleditorwidgets \ - qtcomponents/styleitem + qtcomponents/styleitem \ + zeroconf win32:SUBDIRS += utils/process_ctrlc_stub.pro # Windows: Compile Qt Creator CDB extension if Debugging tools can be detected. diff --git a/src/libs/zeroconf/dns_sd_funct.h b/src/libs/zeroconf/dns_sd_funct.h new file mode 100644 index 00000000000..da5a5f827c5 --- /dev/null +++ b/src/libs/zeroconf/dns_sd_funct.h @@ -0,0 +1,1364 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003-2004, Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/*! @header DNS Service Discovery + * + * @discussion This section describes the functions, callbacks, and data structures + * that make up the DNS Service Discovery API. + * + * The DNS Service Discovery API is part of Bonjour, Apple's implementation + * of zero-configuration networking (ZEROCONF). + * + * Bonjour allows you to register a network service, such as a + * printer or file server, so that it can be found by name or browsed + * for by service type and domain. Using Bonjour, applications can + * discover what services are available on the network, along with + * all the information -- such as name, IP address, and port -- + * necessary to access a particular service. + * + * In effect, Bonjour combines the functions of a local DNS server and + * AppleTalk. Bonjour allows applications to provide user-friendly printer + * and server browsing, among other things, over standard IP networks. + * This behavior is a result of combining protocols such as multicast and + * DNS to add new functionality to the network (such as multicast DNS). + * + * Bonjour gives applications easy access to services over local IP + * networks without requiring the service or the application to support + * an AppleTalk or a Netbeui stack, and without requiring a DNS server + * for the local network. + */ + + +/* _DNS_SD_H contains the mDNSResponder version number for this header file, formatted as follows: + * Major part of the build number * 10000 + + * minor part of the build number * 100 + * For example, Mac OS X 10.4.9 has mDNSResponder-108.4, which would be represented as + * version 1080400. This allows C code to do simple greater-than and less-than comparisons: + * e.g. an application that requires the DNSServiceGetProperty() call (new in mDNSResponder-126) can check: + * + * #if _DNS_SD_H+0 >= 1260000 + * ... some C code that calls DNSServiceGetProperty() ... + * #endif + * + * The version defined in this header file symbol allows for compile-time + * checking, so that C code building with earlier versions of the header file + * can avoid compile errors trying to use functions that aren't even defined + * in those earlier versions. Similar checks may also be performed at run-time: + * => weak linking -- to avoid link failures if run with an earlier + * version of the library that's missing some desired symbol, or + * => DNSServiceGetProperty(DaemonVersion) -- to verify whether the running daemon + * ("system service" on Windows) meets some required minimum functionality level. + */ + +#ifndef _DNS_SD_H +#define _DNS_SD_H 3200500 + +#include "dns_sd_types.h" +/********************************************************************************************* + * + * Version checking + * + *********************************************************************************************/ + +/* DNSServiceGetProperty() Parameters: + * + * property: The requested property. + * Currently the only property defined is kDNSServiceProperty_DaemonVersion. + * + * result: Place to store result. + * For retrieving DaemonVersion, this should be the address of a uint32_t. + * + * size: Pointer to uint32_t containing size of the result location. + * For retrieving DaemonVersion, this should be sizeof(uint32_t). + * On return the uint32_t is updated to the size of the data returned. + * For DaemonVersion, the returned size is always sizeof(uint32_t), but + * future properties could be defined which return variable-sized results. + * + * return value: Returns kDNSServiceErr_NoError on success, or kDNSServiceErr_ServiceNotRunning + * if the daemon (or "system service" on Windows) is not running. + */ + +DNSServiceErrorType DNSSD_API DNSServiceGetProperty + ( + const char *property, /* Requested property (i.e. kDNSServiceProperty_DaemonVersion) */ + void *result, /* Pointer to place to store result */ + uint32_t *size /* size of result location */ + ); + + +/********************************************************************************************* + * + * Unix Domain Socket access, DNSServiceRef deallocation, and data processing functions + * + *********************************************************************************************/ + +/* DNSServiceRefSockFD() + * + * Access underlying Unix domain socket for an initialized DNSServiceRef. + * The DNS Service Discovery implementation uses this socket to communicate between the client and + * the mDNSResponder daemon. The application MUST NOT directly read from or write to this socket. + * Access to the socket is provided so that it can be used as a kqueue event source, a CFRunLoop + * event source, in a select() loop, etc. When the underlying event management subsystem (kqueue/ + * select/CFRunLoop etc.) indicates to the client that data is available for reading on the + * socket, the client should call DNSServiceProcessResult(), which will extract the daemon's + * reply from the socket, and pass it to the appropriate application callback. By using a run + * loop or select(), results from the daemon can be processed asynchronously. Alternatively, + * a client can choose to fork a thread and have it loop calling "DNSServiceProcessResult(ref);" + * If DNSServiceProcessResult() is called when no data is available for reading on the socket, it + * will block until data does become available, and then process the data and return to the caller. + * When data arrives on the socket, the client is responsible for calling DNSServiceProcessResult(ref) + * in a timely fashion -- if the client allows a large backlog of data to build up the daemon + * may terminate the connection. + * + * sdRef: A DNSServiceRef initialized by any of the DNSService calls. + * + * return value: The DNSServiceRef's underlying socket descriptor, or -1 on + * error. + */ + +int DNSSD_API DNSServiceRefSockFD(DNSServiceRef sdRef); + + +/* DNSServiceProcessResult() + * + * Read a reply from the daemon, calling the appropriate application callback. This call will + * block until the daemon's response is received. Use DNSServiceRefSockFD() in + * conjunction with a run loop or select() to determine the presence of a response from the + * server before calling this function to process the reply without blocking. Call this function + * at any point if it is acceptable to block until the daemon's response arrives. Note that the + * client is responsible for ensuring that DNSServiceProcessResult() is called whenever there is + * a reply from the daemon - the daemon may terminate its connection with a client that does not + * process the daemon's responses. + * + * sdRef: A DNSServiceRef initialized by any of the DNSService calls + * that take a callback parameter. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns + * an error code indicating the specific failure that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceProcessResult(DNSServiceRef sdRef); + + +/* DNSServiceRefDeallocate() + * + * Terminate a connection with the daemon and free memory associated with the DNSServiceRef. + * Any services or records registered with this DNSServiceRef will be deregistered. Any + * Browse, Resolve, or Query operations called with this reference will be terminated. + * + * Note: If the reference's underlying socket is used in a run loop or select() call, it should + * be removed BEFORE DNSServiceRefDeallocate() is called, as this function closes the reference's + * socket. + * + * Note: If the reference was initialized with DNSServiceCreateConnection(), any DNSRecordRefs + * created via this reference will be invalidated by this call - the resource records are + * deregistered, and their DNSRecordRefs may not be used in subsequent functions. Similarly, + * if the reference was initialized with DNSServiceRegister, and an extra resource record was + * added to the service via DNSServiceAddRecord(), the DNSRecordRef created by the Add() call + * is invalidated when this function is called - the DNSRecordRef may not be used in subsequent + * functions. + * + * Note: This call is to be used only with the DNSServiceRef defined by this API. It is + * not compatible with dns_service_discovery_ref objects defined in the legacy Mach-based + * DNSServiceDiscovery.h API. + * + * sdRef: A DNSServiceRef initialized by any of the DNSService calls. + * + */ + +void DNSSD_API DNSServiceRefDeallocate(DNSServiceRef sdRef); + + +/********************************************************************************************* + * + * Domain Enumeration + * + *********************************************************************************************/ + + +/* DNSServiceEnumerateDomains() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the enumeration operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Possible values are: + * kDNSServiceFlagsBrowseDomains to enumerate domains recommended for browsing. + * kDNSServiceFlagsRegistrationDomains to enumerate domains recommended + * for registration. + * + * interfaceIndex: If non-zero, specifies the interface on which to look for domains. + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Most applications will pass 0 to enumerate domains on + * all interfaces. See "Constants for specifying an interface index" for more details. + * + * callBack: The function to be called when a domain is found or the call asynchronously + * fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is not invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceEnumerateDomains + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceDomainEnumReply callBack, + void *context /* may be NULL */ + ); + + +/********************************************************************************************* + * + * Service Registration + * + *********************************************************************************************/ + +/* DNSServiceRegister() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the registration will remain active indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * interfaceIndex: If non-zero, specifies the interface on which to register the service + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Most applications will pass 0 to register on all + * available interfaces. See "Constants for specifying an interface index" for more details. + * + * flags: Indicates the renaming behavior on name conflict (most applications + * will pass 0). See flag definitions above for details. + * + * name: If non-NULL, specifies the service name to be registered. + * Most applications will not specify a name, in which case the computer + * name is used (this name is communicated to the client via the callback). + * If a name is specified, it must be 1-63 bytes of UTF-8 text. + * If the name is longer than 63 bytes it will be automatically truncated + * to a legal length, unless the NoAutoRename flag is set, + * in which case kDNSServiceErr_BadParam will be returned. + * + * regtype: The service type followed by the protocol, separated by a dot + * (e.g. "_ftp._tcp"). The service type must be an underscore, followed + * by 1-15 characters, which may be letters, digits, or hyphens. + * The transport protocol must be "_tcp" or "_udp". New service types + * should be registered at . + * + * Additional subtypes of the primary service type (where a service + * type has defined subtypes) follow the primary service type in a + * comma-separated list, with no additional spaces, e.g. + * "_primarytype._tcp,_subtype1,_subtype2,_subtype3" + * Subtypes provide a mechanism for filtered browsing: A client browsing + * for "_primarytype._tcp" will discover all instances of this type; + * a client browsing for "_primarytype._tcp,_subtype2" will discover only + * those instances that were registered with "_subtype2" in their list of + * registered subtypes. + * + * The subtype mechanism can be illustrated with some examples using the + * dns-sd command-line tool: + * + * % dns-sd -R Simple _test._tcp "" 1001 & + * % dns-sd -R Better _test._tcp,HasFeatureA "" 1002 & + * % dns-sd -R Best _test._tcp,HasFeatureA,HasFeatureB "" 1003 & + * + * Now: + * % dns-sd -B _test._tcp # will find all three services + * % dns-sd -B _test._tcp,HasFeatureA # finds "Better" and "Best" + * % dns-sd -B _test._tcp,HasFeatureB # finds only "Best" + * + * Subtype labels may be up to 63 bytes long, and may contain any eight- + * bit byte values, including zero bytes. However, due to the nature of + * using a C-string-based API, conventional DNS escaping must be used for + * dots ('.'), commas (','), backslashes ('\') and zero bytes, as shown below: + * + * % dns-sd -R Test '_test._tcp,s\.one,s\,two,s\\three,s\000four' local 123 + * + * domain: If non-NULL, specifies the domain on which to advertise the service. + * Most applications will not specify a domain, instead automatically + * registering in the default domain(s). + * + * host: If non-NULL, specifies the SRV target host name. Most applications + * will not specify a host, instead automatically using the machine's + * default host name(s). Note that specifying a non-NULL host does NOT + * create an address record for that host - the application is responsible + * for ensuring that the appropriate address record exists, or creating it + * via DNSServiceRegisterRecord(). + * + * port: The port, in network byte order, on which the service accepts connections. + * Pass 0 for a "placeholder" service (i.e. a service that will not be discovered + * by browsing, but will cause a name conflict if another client tries to + * register that same name). Most clients will not use placeholder services. + * + * txtLen: The length of the txtRecord, in bytes. Must be zero if the txtRecord is NULL. + * + * txtRecord: The TXT record rdata. A non-NULL txtRecord MUST be a properly formatted DNS + * TXT record, i.e. ... + * Passing NULL for the txtRecord is allowed as a synonym for txtLen=1, txtRecord="", + * i.e. it creates a TXT record of length one containing a single empty string. + * RFC 1035 doesn't allow a TXT record to contain *zero* strings, so a single empty + * string is the smallest legal DNS TXT record. + * As with the other parameters, the DNSServiceRegister call copies the txtRecord + * data; e.g. if you allocated the storage for the txtRecord parameter with malloc() + * then you can safely free that memory right after the DNSServiceRegister call returns. + * + * callBack: The function to be called when the registration completes or asynchronously + * fails. The client MAY pass NULL for the callback - The client will NOT be notified + * of the default values picked on its behalf, and the client will NOT be notified of any + * asynchronous errors (e.g. out of memory errors, etc.) that may prevent the registration + * of the service. The client may NOT pass the NoAutoRename flag if the callback is NULL. + * The client may still deregister the service at any time via DNSServiceRefDeallocate(). + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceRegister + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, /* may be NULL */ + const char *regtype, + const char *domain, /* may be NULL */ + const char *host, /* may be NULL */ + uint16_t port, /* In network byte order */ + uint16_t txtLen, + const void *txtRecord, /* may be NULL */ + DNSServiceRegisterReply callBack, /* may be NULL */ + void *context /* may be NULL */ + ); + + +/* DNSServiceAddRecord() + * + * Add a record to a registered service. The name of the record will be the same as the + * registered service's name. + * The record can later be updated or deregistered by passing the RecordRef initialized + * by this function to DNSServiceUpdateRecord() or DNSServiceRemoveRecord(). + * + * Note that the DNSServiceAddRecord/UpdateRecord/RemoveRecord are *NOT* thread-safe + * with respect to a single DNSServiceRef. If you plan to have multiple threads + * in your program simultaneously add, update, or remove records from the same + * DNSServiceRef, then it's the caller's responsibility to use a mutext lock + * or take similar appropriate precautions to serialize those calls. + * + * Parameters; + * + * sdRef: A DNSServiceRef initialized by DNSServiceRegister(). + * + * RecordRef: A pointer to an uninitialized DNSRecordRef. Upon succesfull completion of this + * call, this ref may be passed to DNSServiceUpdateRecord() or DNSServiceRemoveRecord(). + * If the above DNSServiceRef is passed to DNSServiceRefDeallocate(), RecordRef is also + * invalidated and may not be used further. + * + * flags: Currently ignored, reserved for future use. + * + * rrtype: The type of the record (e.g. kDNSServiceType_TXT, kDNSServiceType_SRV, etc) + * + * rdlen: The length, in bytes, of the rdata. + * + * rdata: The raw rdata to be contained in the added resource record. + * + * ttl: The time to live of the resource record, in seconds. + * Most clients should pass 0 to indicate that the system should + * select a sensible default value. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns an + * error code indicating the error that occurred (the RecordRef is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceAddRecord + ( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint16_t rrtype, + uint16_t rdlen, + const void *rdata, + uint32_t ttl + ); + + +/* DNSServiceUpdateRecord + * + * Update a registered resource record. The record must either be: + * - The primary txt record of a service registered via DNSServiceRegister() + * - A record added to a registered service via DNSServiceAddRecord() + * - An individual record registered by DNSServiceRegisterRecord() + * + * Parameters: + * + * sdRef: A DNSServiceRef that was initialized by DNSServiceRegister() + * or DNSServiceCreateConnection(). + * + * RecordRef: A DNSRecordRef initialized by DNSServiceAddRecord, or NULL to update the + * service's primary txt record. + * + * flags: Currently ignored, reserved for future use. + * + * rdlen: The length, in bytes, of the new rdata. + * + * rdata: The new rdata to be contained in the updated resource record. + * + * ttl: The time to live of the updated resource record, in seconds. + * Most clients should pass 0 to indicate that the system should + * select a sensible default value. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns an + * error code indicating the error that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceUpdateRecord + ( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, /* may be NULL */ + DNSServiceFlags flags, + uint16_t rdlen, + const void *rdata, + uint32_t ttl + ); + + +/* DNSServiceRemoveRecord + * + * Remove a record previously added to a service record set via DNSServiceAddRecord(), or deregister + * an record registered individually via DNSServiceRegisterRecord(). + * + * Parameters: + * + * sdRef: A DNSServiceRef initialized by DNSServiceRegister() (if the + * record being removed was registered via DNSServiceAddRecord()) or by + * DNSServiceCreateConnection() (if the record being removed was registered via + * DNSServiceRegisterRecord()). + * + * recordRef: A DNSRecordRef initialized by a successful call to DNSServiceAddRecord() + * or DNSServiceRegisterRecord(). + * + * flags: Currently ignored, reserved for future use. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns an + * error code indicating the error that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceRemoveRecord + ( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags + ); + + +/********************************************************************************************* + * + * Service Discovery + * + *********************************************************************************************/ + +/* DNSServiceBrowse() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the browse operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Currently ignored, reserved for future use. + * + * interfaceIndex: If non-zero, specifies the interface on which to browse for services + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Most applications will pass 0 to browse on all available + * interfaces. See "Constants for specifying an interface index" for more details. + * + * regtype: The service type being browsed for followed by the protocol, separated by a + * dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". + * A client may optionally specify a single subtype to perform filtered browsing: + * e.g. browsing for "_primarytype._tcp,_subtype" will discover only those + * instances of "_primarytype._tcp" that were registered specifying "_subtype" + * in their list of registered subtypes. + * + * domain: If non-NULL, specifies the domain on which to browse for services. + * Most applications will not specify a domain, instead browsing on the + * default domain(s). + * + * callBack: The function to be called when an instance of the service being browsed for + * is found, or if the call asynchronously fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is not invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceBrowse + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *regtype, + const char *domain, /* may be NULL */ + DNSServiceBrowseReply callBack, + void *context /* may be NULL */ + ); + +/* DNSServiceResolve() Parameters + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the resolve operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Specifying kDNSServiceFlagsForceMulticast will cause query to be + * performed with a link-local mDNS query, even if the name is an + * apparently non-local name (i.e. a name not ending in ".local.") + * + * interfaceIndex: The interface on which to resolve the service. If this resolve call is + * as a result of a currently active DNSServiceBrowse() operation, then the + * interfaceIndex should be the index reported in the DNSServiceBrowseReply + * callback. If this resolve call is using information previously saved + * (e.g. in a preference file) for later use, then use interfaceIndex 0, because + * the desired service may now be reachable via a different physical interface. + * See "Constants for specifying an interface index" for more details. + * + * name: The name of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * regtype: The type of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * domain: The domain of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * callBack: The function to be called when a result is found, or if the call + * asynchronously fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceResolve + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + const char *regtype, + const char *domain, + DNSServiceResolveReply callBack, + void *context /* may be NULL */ + ); + + +/********************************************************************************************* + * + * Querying Individual Specific Records + * + *********************************************************************************************/ + +/* DNSServiceQueryRecord() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the query operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: kDNSServiceFlagsForceMulticast or kDNSServiceFlagsLongLivedQuery. + * Pass kDNSServiceFlagsLongLivedQuery to create a "long-lived" unicast + * query in a non-local domain. Without setting this flag, unicast queries + * will be one-shot - that is, only answers available at the time of the call + * will be returned. By setting this flag, answers (including Add and Remove + * events) that become available after the initial call is made will generate + * callbacks. This flag has no effect on link-local multicast queries. + * + * interfaceIndex: If non-zero, specifies the interface on which to issue the query + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Passing 0 causes the name to be queried for on all + * interfaces. See "Constants for specifying an interface index" for more details. + * + * fullname: The full domain name of the resource record to be queried for. + * + * rrtype: The numerical type of the resource record to be queried for + * (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN). + * + * callBack: The function to be called when a result is found, or if the call + * asynchronously fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceQueryRecord + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + DNSServiceQueryRecordReply callBack, + void *context /* may be NULL */ + ); + + +/********************************************************************************************* + * + * Unified lookup of both IPv4 and IPv6 addresses for a fully qualified hostname + * + *********************************************************************************************/ + +/* DNSServiceGetAddrInfo() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds then it + * initializes the DNSServiceRef, returns kDNSServiceErr_NoError, and the query + * begins and will last indefinitely until the client terminates the query + * by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: kDNSServiceFlagsForceMulticast or kDNSServiceFlagsLongLivedQuery. + * Pass kDNSServiceFlagsLongLivedQuery to create a "long-lived" unicast + * query in a non-local domain. Without setting this flag, unicast queries + * will be one-shot - that is, only answers available at the time of the call + * will be returned. By setting this flag, answers (including Add and Remove + * events) that become available after the initial call is made will generate + * callbacks. This flag has no effect on link-local multicast queries. + * + * interfaceIndex: The interface on which to issue the query. Passing 0 causes the query to be + * sent on all active interfaces via Multicast or the primary interface via Unicast. + * + * protocol: Pass in kDNSServiceProtocol_IPv4 to look up IPv4 addresses, or kDNSServiceProtocol_IPv6 + * to look up IPv6 addresses, or both to look up both kinds. If neither flag is + * set, the system will apply an intelligent heuristic, which is (currently) + * that it will attempt to look up both, except: + * + * * If "hostname" is a wide-area unicast DNS hostname (i.e. not a ".local." name) + * but this host has no routable IPv6 address, then the call will not try to + * look up IPv6 addresses for "hostname", since any addresses it found would be + * unlikely to be of any use anyway. Similarly, if this host has no routable + * IPv4 address, the call will not try to look up IPv4 addresses for "hostname". + * + * hostname: The fully qualified domain name of the host to be queried for. + * + * callBack: The function to be called when the query succeeds or fails asynchronously. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceGetAddrInfo + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceProtocol protocol, + const char *hostname, + DNSServiceGetAddrInfoReply callBack, + void *context /* may be NULL */ + ); + + +/********************************************************************************************* + * + * Special Purpose Calls: + * DNSServiceCreateConnection(), DNSServiceRegisterRecord(), DNSServiceReconfirmRecord() + * (most applications will not use these) + * + *********************************************************************************************/ + +/* DNSServiceCreateConnection() + * + * Create a connection to the daemon allowing efficient registration of + * multiple individual records. + * + * Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. Deallocating + * the reference (via DNSServiceRefDeallocate()) severs the + * connection and deregisters all records registered on this connection. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns + * an error code indicating the specific failure that occurred (in which + * case the DNSServiceRef is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceCreateConnection(DNSServiceRef *sdRef); + +/* DNSServiceRegisterRecord() Parameters: + * + * sdRef: A DNSServiceRef initialized by DNSServiceCreateConnection(). + * + * RecordRef: A pointer to an uninitialized DNSRecordRef. Upon succesfull completion of this + * call, this ref may be passed to DNSServiceUpdateRecord() or DNSServiceRemoveRecord(). + * (To deregister ALL records registered on a single connected DNSServiceRef + * and deallocate each of their corresponding DNSServiceRecordRefs, call + * DNSServiceRefDeallocate()). + * + * flags: Possible values are kDNSServiceFlagsShared or kDNSServiceFlagsUnique + * (see flag type definitions for details). + * + * interfaceIndex: If non-zero, specifies the interface on which to register the record + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Passing 0 causes the record to be registered on all interfaces. + * See "Constants for specifying an interface index" for more details. + * + * fullname: The full domain name of the resource record. + * + * rrtype: The numerical type of the resource record (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN) + * + * rdlen: Length, in bytes, of the rdata. + * + * rdata: A pointer to the raw rdata, as it is to appear in the DNS record. + * + * ttl: The time to live of the resource record, in seconds. + * Most clients should pass 0 to indicate that the system should + * select a sensible default value. + * + * callBack: The function to be called when a result is found, or if the call + * asynchronously fails (e.g. because of a name conflict.) + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSRecordRef is + * not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceRegisterRecord + ( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + DNSServiceRegisterRecordReply callBack, + void *context /* may be NULL */ + ); + + +/* DNSServiceReconfirmRecord + * + * Instruct the daemon to verify the validity of a resource record that appears + * to be out of date (e.g. because TCP connection to a service's target failed.) + * Causes the record to be flushed from the daemon's cache (as well as all other + * daemons' caches on the network) if the record is determined to be invalid. + * Use this routine conservatively. Reconfirming a record necessarily consumes + * network bandwidth, so this should not be done indiscriminately. + * + * Parameters: + * + * flags: Pass kDNSServiceFlagsForce to force immediate deletion of record, + * instead of after some number of reconfirmation queries have gone unanswered. + * + * interfaceIndex: Specifies the interface of the record in question. + * The caller must specify the interface. + * This API (by design) causes increased network traffic, so it requires + * the caller to be precise about which record should be reconfirmed. + * It is not possible to pass zero for the interface index to perform + * a "wildcard" reconfirmation, where *all* matching records are reconfirmed. + * + * fullname: The resource record's full domain name. + * + * rrtype: The resource record's type (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN). + * + * rdlen: The length, in bytes, of the resource record rdata. + * + * rdata: The raw rdata of the resource record. + * + */ + +DNSServiceErrorType DNSSD_API DNSServiceReconfirmRecord + ( + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata + ); + + +/********************************************************************************************* + * + * NAT Port Mapping + * + *********************************************************************************************/ + +/* DNSServiceNATPortMappingCreate() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds then it + * initializes the DNSServiceRef, returns kDNSServiceErr_NoError, and the nat + * port mapping will last indefinitely until the client terminates the port + * mapping request by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Currently ignored, reserved for future use. + * + * interfaceIndex: The interface on which to create port mappings in a NAT gateway. Passing 0 causes + * the port mapping request to be sent on the primary interface. + * + * protocol: To request a port mapping, pass in kDNSServiceProtocol_UDP, or kDNSServiceProtocol_TCP, + * or (kDNSServiceProtocol_UDP | kDNSServiceProtocol_TCP) to map both. + * The local listening port number must also be specified in the internalPort parameter. + * To just discover the NAT gateway's external IP address, pass zero for protocol, + * internalPort, externalPort and ttl. + * + * internalPort: The port number in network byte order on the local machine which is listening for packets. + * + * externalPort: The requested external port in network byte order in the NAT gateway that you would + * like to map to the internal port. Pass 0 if you don't care which external port is chosen for you. + * + * ttl: The requested renewal period of the NAT port mapping, in seconds. + * If the client machine crashes, suffers a power failure, is disconnected from + * the network, or suffers some other unfortunate demise which causes it to vanish + * unexpectedly without explicitly removing its NAT port mappings, then the NAT gateway + * will garbage-collect old stale NAT port mappings when their lifetime expires. + * Requesting a short TTL causes such orphaned mappings to be garbage-collected + * more promptly, but consumes system resources and network bandwidth with + * frequent renewal packets to keep the mapping from expiring. + * Requesting a long TTL is more efficient on the network, but in the event of the + * client vanishing, stale NAT port mappings will not be garbage-collected as quickly. + * Most clients should pass 0 to use a system-wide default value. + * + * callBack: The function to be called when the port mapping request succeeds or fails asynchronously. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred. + * + * If you don't actually want a port mapped, and are just calling the API + * because you want to find out the NAT's external IP address (e.g. for UI + * display) then pass zero for protocol, internalPort, externalPort and ttl. + */ + +DNSServiceErrorType DNSSD_API DNSServiceNATPortMappingCreate + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceProtocol protocol, /* TCP and/or UDP */ + uint16_t internalPort, /* network byte order */ + uint16_t externalPort, /* network byte order */ + uint32_t ttl, /* time to live in seconds */ + DNSServiceNATPortMappingReply callBack, + void *context /* may be NULL */ + ); + + +/********************************************************************************************* + * + * General Utility Functions + * + *********************************************************************************************/ + +/* DNSServiceConstructFullName() + * + * Concatenate a three-part domain name (as returned by the above callbacks) into a + * properly-escaped full domain name. Note that callbacks in the above functions ALREADY ESCAPE + * strings where necessary. + * + * Parameters: + * + * fullName: A pointer to a buffer that where the resulting full domain name is to be written. + * The buffer must be kDNSServiceMaxDomainName (1009) bytes in length to + * accommodate the longest legal domain name without buffer overrun. + * + * service: The service name - any dots or backslashes must NOT be escaped. + * May be NULL (to construct a PTR record name, e.g. + * "_ftp._tcp.apple.com."). + * + * regtype: The service type followed by the protocol, separated by a dot + * (e.g. "_ftp._tcp"). + * + * domain: The domain name, e.g. "apple.com.". Literal dots or backslashes, + * if any, must be escaped, e.g. "1st\. Floor.apple.com." + * + * return value: Returns kDNSServiceErr_NoError (0) on success, kDNSServiceErr_BadParam on error. + * + */ + +DNSServiceErrorType DNSSD_API DNSServiceConstructFullName + ( + char * const fullName, + const char * const service, /* may be NULL */ + const char * const regtype, + const char * const domain + ); + + +/********************************************************************************************* + * + * TXT Record Construction Functions + * + *********************************************************************************************/ + +/* + * A typical calling sequence for TXT record construction is something like: + * + * Client allocates storage for TXTRecord data (e.g. declare buffer on the stack) + * TXTRecordCreate(); + * TXTRecordSetValue(); + * TXTRecordSetValue(); + * TXTRecordSetValue(); + * ... + * DNSServiceRegister( ... TXTRecordGetLength(), TXTRecordGetBytesPtr() ... ); + * TXTRecordDeallocate(); + * Explicitly deallocate storage for TXTRecord data (if not allocated on the stack) + */ + + +/* TXTRecordCreate() + * + * Creates a new empty TXTRecordRef referencing the specified storage. + * + * If the buffer parameter is NULL, or the specified storage size is not + * large enough to hold a key subsequently added using TXTRecordSetValue(), + * then additional memory will be added as needed using malloc(). + * + * On some platforms, when memory is low, malloc() may fail. In this + * case, TXTRecordSetValue() will return kDNSServiceErr_NoMemory, and this + * error condition will need to be handled as appropriate by the caller. + * + * You can avoid the need to handle this error condition if you ensure + * that the storage you initially provide is large enough to hold all + * the key/value pairs that are to be added to the record. + * The caller can precompute the exact length required for all of the + * key/value pairs to be added, or simply provide a fixed-sized buffer + * known in advance to be large enough. + * A no-value (key-only) key requires (1 + key length) bytes. + * A key with empty value requires (1 + key length + 1) bytes. + * A key with non-empty value requires (1 + key length + 1 + value length). + * For most applications, DNS-SD TXT records are generally + * less than 100 bytes, so in most cases a simple fixed-sized + * 256-byte buffer will be more than sufficient. + * Recommended size limits for DNS-SD TXT Records are discussed in + * + * + * Note: When passing parameters to and from these TXT record APIs, + * the key name does not include the '=' character. The '=' character + * is the separator between the key and value in the on-the-wire + * packet format; it is not part of either the key or the value. + * + * txtRecord: A pointer to an uninitialized TXTRecordRef. + * + * bufferLen: The size of the storage provided in the "buffer" parameter. + * + * buffer: Optional caller-supplied storage used to hold the TXTRecord data. + * This storage must remain valid for as long as + * the TXTRecordRef. + */ + +void DNSSD_API TXTRecordCreate + ( + TXTRecordRef *txtRecord, + uint16_t bufferLen, + void *buffer + ); + + +/* TXTRecordDeallocate() + * + * Releases any resources allocated in the course of preparing a TXT Record + * using TXTRecordCreate()/TXTRecordSetValue()/TXTRecordRemoveValue(). + * Ownership of the buffer provided in TXTRecordCreate() returns to the client. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + */ + +void DNSSD_API TXTRecordDeallocate + ( + TXTRecordRef *txtRecord + ); + + +/* TXTRecordSetValue() + * + * Adds a key (optionally with value) to a TXTRecordRef. If the "key" already + * exists in the TXTRecordRef, then the current value will be replaced with + * the new value. + * Keys may exist in four states with respect to a given TXT record: + * - Absent (key does not appear at all) + * - Present with no value ("key" appears alone) + * - Present with empty value ("key=" appears in TXT record) + * - Present with non-empty value ("key=value" appears in TXT record) + * For more details refer to "Data Syntax for DNS-SD TXT Records" in + * + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * key: A null-terminated string which only contains printable ASCII + * values (0x20-0x7E), excluding '=' (0x3D). Keys should be + * 9 characters or fewer (not counting the terminating null). + * + * valueSize: The size of the value. + * + * value: Any binary value. For values that represent + * textual data, UTF-8 is STRONGLY recommended. + * For values that represent textual data, valueSize + * should NOT include the terminating null (if any) + * at the end of the string. + * If NULL, then "key" will be added with no value. + * If non-NULL but valueSize is zero, then "key=" will be + * added with empty value. + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_Invalid if the "key" string contains + * illegal characters. + * Returns kDNSServiceErr_NoMemory if adding this key would + * exceed the available storage. + */ + +DNSServiceErrorType DNSSD_API TXTRecordSetValue + ( + TXTRecordRef *txtRecord, + const char *key, + uint8_t valueSize, /* may be zero */ + const void *value /* may be NULL */ + ); + + +/* TXTRecordRemoveValue() + * + * Removes a key from a TXTRecordRef. The "key" must be an + * ASCII string which exists in the TXTRecordRef. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * key: A key name which exists in the TXTRecordRef. + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_NoSuchKey if the "key" does not + * exist in the TXTRecordRef. + */ + +DNSServiceErrorType DNSSD_API TXTRecordRemoveValue + ( + TXTRecordRef *txtRecord, + const char *key + ); + + +/* TXTRecordGetLength() + * + * Allows you to determine the length of the raw bytes within a TXTRecordRef. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * return value: Returns the size of the raw bytes inside a TXTRecordRef + * which you can pass directly to DNSServiceRegister() or + * to DNSServiceUpdateRecord(). + * Returns 0 if the TXTRecordRef is empty. + */ + +uint16_t DNSSD_API TXTRecordGetLength + ( + const TXTRecordRef *txtRecord + ); + + +/* TXTRecordGetBytesPtr() + * + * Allows you to retrieve a pointer to the raw bytes within a TXTRecordRef. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * return value: Returns a pointer to the raw bytes inside the TXTRecordRef + * which you can pass directly to DNSServiceRegister() or + * to DNSServiceUpdateRecord(). + */ + +const void * DNSSD_API TXTRecordGetBytesPtr + ( + const TXTRecordRef *txtRecord + ); + + +/********************************************************************************************* + * + * TXT Record Parsing Functions + * + *********************************************************************************************/ + +/* + * A typical calling sequence for TXT record parsing is something like: + * + * Receive TXT record data in DNSServiceResolve() callback + * if (TXTRecordContainsKey(txtLen, txtRecord, "key")) then do something + * val1ptr = TXTRecordGetValuePtr(txtLen, txtRecord, "key1", &len1); + * val2ptr = TXTRecordGetValuePtr(txtLen, txtRecord, "key2", &len2); + * ... + * memcpy(myval1, val1ptr, len1); + * memcpy(myval2, val2ptr, len2); + * ... + * return; + * + * If you wish to retain the values after return from the DNSServiceResolve() + * callback, then you need to copy the data to your own storage using memcpy() + * or similar, as shown in the example above. + * + * If for some reason you need to parse a TXT record you built yourself + * using the TXT record construction functions above, then you can do + * that using TXTRecordGetLength and TXTRecordGetBytesPtr calls: + * TXTRecordGetValue(TXTRecordGetLength(x), TXTRecordGetBytesPtr(x), key, &len); + * + * Most applications only fetch keys they know about from a TXT record and + * ignore the rest. + * However, some debugging tools wish to fetch and display all keys. + * To do that, use the TXTRecordGetCount() and TXTRecordGetItemAtIndex() calls. + */ + +/* TXTRecordContainsKey() + * + * Allows you to determine if a given TXT Record contains a specified key. + * + * txtLen: The size of the received TXT Record. + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * key: A null-terminated ASCII string containing the key name. + * + * return value: Returns 1 if the TXT Record contains the specified key. + * Otherwise, it returns 0. + */ + +int DNSSD_API TXTRecordContainsKey + ( + uint16_t txtLen, + const void *txtRecord, + const char *key + ); + + +/* TXTRecordGetValuePtr() + * + * Allows you to retrieve the value for a given key from a TXT Record. + * + * txtLen: The size of the received TXT Record + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * key: A null-terminated ASCII string containing the key name. + * + * valueLen: On output, will be set to the size of the "value" data. + * + * return value: Returns NULL if the key does not exist in this TXT record, + * or exists with no value (to differentiate between + * these two cases use TXTRecordContainsKey()). + * Returns pointer to location within TXT Record bytes + * if the key exists with empty or non-empty value. + * For empty value, valueLen will be zero. + * For non-empty value, valueLen will be length of value data. + */ + +const void * DNSSD_API TXTRecordGetValuePtr + ( + uint16_t txtLen, + const void *txtRecord, + const char *key, + uint8_t *valueLen + ); + + +/* TXTRecordGetCount() + * + * Returns the number of keys stored in the TXT Record. The count + * can be used with TXTRecordGetItemAtIndex() to iterate through the keys. + * + * txtLen: The size of the received TXT Record. + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * return value: Returns the total number of keys in the TXT Record. + * + */ + +uint16_t DNSSD_API TXTRecordGetCount + ( + uint16_t txtLen, + const void *txtRecord + ); + + +/* TXTRecordGetItemAtIndex() + * + * Allows you to retrieve a key name and value pointer, given an index into + * a TXT Record. Legal index values range from zero to TXTRecordGetCount()-1. + * It's also possible to iterate through keys in a TXT record by simply + * calling TXTRecordGetItemAtIndex() repeatedly, beginning with index zero + * and increasing until TXTRecordGetItemAtIndex() returns kDNSServiceErr_Invalid. + * + * On return: + * For keys with no value, *value is set to NULL and *valueLen is zero. + * For keys with empty value, *value is non-NULL and *valueLen is zero. + * For keys with non-empty value, *value is non-NULL and *valueLen is non-zero. + * + * txtLen: The size of the received TXT Record. + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * itemIndex: An index into the TXT Record. + * + * keyBufLen: The size of the string buffer being supplied. + * + * key: A string buffer used to store the key name. + * On return, the buffer contains a null-terminated C string + * giving the key name. DNS-SD TXT keys are usually + * 9 characters or fewer. To hold the maximum possible + * key name, the buffer should be 256 bytes long. + * + * valueLen: On output, will be set to the size of the "value" data. + * + * value: On output, *value is set to point to location within TXT + * Record bytes that holds the value data. + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_NoMemory if keyBufLen is too short. + * Returns kDNSServiceErr_Invalid if index is greater than + * TXTRecordGetCount()-1. + */ + +DNSServiceErrorType DNSSD_API TXTRecordGetItemAtIndex + ( + uint16_t txtLen, + const void *txtRecord, + uint16_t itemIndex, + uint16_t keyBufLen, + char *key, + uint8_t *valueLen, + const void **value + ); + +#if _DNS_SD_LIBDISPATCH +/* +* DNSServiceSetDispatchQueue +* +* Allows you to schedule a DNSServiceRef on a serial dispatch queue for receiving asynchronous +* callbacks. It's the clients responsibility to ensure that the provided dispatch queue is running. +* +* A typical application that uses CFRunLoopRun or dispatch_main on its main thread will +* usually schedule DNSServiceRefs on its main queue (which is always a serial queue) +* using "DNSServiceSetDispatchQueue(sdref, dispatch_get_main_queue());" +* +* If there is any error during the processing of events, the application callback will +* be called with an error code. For shared connections, each subordinate DNSServiceRef +* will get its own error callback. Currently these error callbacks only happen +* if the mDNSResponder daemon is manually terminated or crashes, and the error +* code in this case is kDNSServiceErr_ServiceNotRunning. The application must call +* DNSServiceRefDeallocate to free the DNSServiceRef when it gets such an error code. +* These error callbacks are rare and should not normally happen on customer machines, +* but application code should be written defensively to handle such error callbacks +* gracefully if they occur. +* +* After using DNSServiceSetDispatchQueue on a DNSServiceRef, calling DNSServiceProcessResult +* on the same DNSServiceRef will result in undefined behavior and should be avoided. +* +* Once the application successfully schedules a DNSServiceRef on a serial dispatch queue using +* DNSServiceSetDispatchQueue, it cannot remove the DNSServiceRef from the dispatch queue, or use +* DNSServiceSetDispatchQueue a second time to schedule the DNSServiceRef onto a different serial dispatch +* queue. Once scheduled onto a dispatch queue a DNSServiceRef will deliver events to that queue until +* the application no longer requires that operation and terminates it using DNSServiceRefDeallocate. +* +* service: DNSServiceRef that was allocated and returned to the application, when the +* application calls one of the DNSService API. +* +* queue: dispatch queue where the application callback will be scheduled +* +* return value: Returns kDNSServiceErr_NoError on success. +* Returns kDNSServiceErr_NoMemory if it cannot create a dispatch source +* Returns kDNSServiceErr_BadParam if the service param is invalid or the +* queue param is invalid +*/ + +DNSServiceErrorType DNSSD_API DNSServiceSetDispatchQueue + ( + DNSServiceRef service, + dispatch_queue_t queue + ); +#endif //_DNS_SD_LIBDISPATCH + +#ifdef __APPLE_API_PRIVATE + +#define kDNSServiceCompPrivateDNS "PrivateDNS" +#define kDNSServiceCompMulticastDNS "MulticastDNS" + +#endif //__APPLE_API_PRIVATE + +/* Some C compiler cleverness. We can make the compiler check certain things for us, + * and report errors at compile-time if anything is wrong. The usual way to do this would + * be to use a run-time "if" statement or the conventional run-time "assert" mechanism, but + * then you don't find out what's wrong until you run the software. This way, if the assertion + * condition is false, the array size is negative, and the complier complains immediately. + */ + +struct CompileTimeAssertionChecks_DNS_SD + { + char assert0[(sizeof(union _TXTRecordRef_t) == 16) ? 1 : -1]; + }; + +#endif /* _DNS_SD_H */ diff --git a/src/libs/zeroconf/dns_sd_types.h b/src/libs/zeroconf/dns_sd_types.h new file mode 100644 index 00000000000..7ca5dc5d8b8 --- /dev/null +++ b/src/libs/zeroconf/dns_sd_types.h @@ -0,0 +1,1154 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003-2004, Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/*! @header DNS Service Discovery + * + * @discussion This section describes the functions, callbacks, and data structures + * that make up the DNS Service Discovery API. + * + * The DNS Service Discovery API is part of Bonjour, Apple's implementation + * of zero-configuration networking (ZEROCONF). + * + * Bonjour allows you to register a network service, such as a + * printer or file server, so that it can be found by name or browsed + * for by service type and domain. Using Bonjour, applications can + * discover what services are available on the network, along with + * all the information -- such as name, IP address, and port -- + * necessary to access a particular service. + * + * In effect, Bonjour combines the functions of a local DNS server and + * AppleTalk. Bonjour allows applications to provide user-friendly printer + * and server browsing, among other things, over standard IP networks. + * This behavior is a result of combining protocols such as multicast and + * DNS to add new functionality to the network (such as multicast DNS). + * + * Bonjour gives applications easy access to services over local IP + * networks without requiring the service or the application to support + * an AppleTalk or a Netbeui stack, and without requiring a DNS server + * for the local network. + */ + + +/* _DNS_SD_H contains the mDNSResponder version number for this header file, formatted as follows: + * Major part of the build number * 10000 + + * minor part of the build number * 100 + * For example, Mac OS X 10.4.9 has mDNSResponder-108.4, which would be represented as + * version 1080400. This allows C code to do simple greater-than and less-than comparisons: + * e.g. an application that requires the DNSServiceGetProperty() call (new in mDNSResponder-126) can check: + * + * #if _DNS_SD_H+0 >= 1260000 + * ... some C code that calls DNSServiceGetProperty() ... + * #endif + * + * The version defined in this header file symbol allows for compile-time + * checking, so that C code building with earlier versions of the header file + * can avoid compile errors trying to use functions that aren't even defined + * in those earlier versions. Similar checks may also be performed at run-time: + * => weak linking -- to avoid link failures if run with an earlier + * version of the library that's missing some desired symbol, or + * => DNSServiceGetProperty(DaemonVersion) -- to verify whether the running daemon + * ("system service" on Windows) meets some required minimum functionality level. + */ + +#ifndef _DNS_SD_TYPES_H +#define _DNS_SD_TYPES_H 3200500 + +#ifdef __cplusplus + extern "C" { +#endif + +/* Set to 1 if libdispatch is supported + * Note: May also be set by project and/or Makefile + */ +#ifndef _DNS_SD_LIBDISPATCH +#define _DNS_SD_LIBDISPATCH 0 +#endif /* ndef _DNS_SD_LIBDISPATCH */ + +/* standard calling convention under Win32 is __stdcall */ +/* Note: When compiling Intel EFI (Extensible Firmware Interface) under MS Visual Studio, the */ +/* _WIN32 symbol is defined by the compiler even though it's NOT compiling code for Windows32 */ +#if defined(_WIN32) && !defined(EFI32) && !defined(EFI64) +#define DNSSD_API __stdcall +#else +#define DNSSD_API +#endif + +/* stdint.h does not exist on FreeBSD 4.x; its types are defined in sys/types.h instead */ +#if defined(__FreeBSD__) && (__FreeBSD__ < 5) +#include + +/* Likewise, on Sun, standard integer types are in sys/types.h */ +#elif defined(__sun__) +#include + +/* EFI does not have stdint.h, or anything else equivalent */ +#elif defined(EFI32) || defined(EFI64) || defined(EFIX64) +#include "Tiano.h" +#if !defined(_STDINT_H_) +typedef UINT8 uint8_t; +typedef INT8 int8_t; +typedef UINT16 uint16_t; +typedef INT16 int16_t; +typedef UINT32 uint32_t; +typedef INT32 int32_t; +#endif +/* Windows has its own differences */ +#elif defined(_WIN32) +#include +#define _UNUSED +#ifndef _MSL_STDINT_H +typedef UINT8 uint8_t; +typedef INT8 int8_t; +typedef UINT16 uint16_t; +typedef INT16 int16_t; +typedef UINT32 uint32_t; +typedef INT32 int32_t; +#endif + +/* All other Posix platforms use stdint.h */ +#else +#include +#endif + +#if _DNS_SD_LIBDISPATCH +#include +#endif + +/* DNSServiceRef, DNSRecordRef + * + * Opaque internal data types. + * Note: client is responsible for serializing access to these structures if + * they are shared between concurrent threads. + */ + +typedef struct _DNSServiceRef_t *DNSServiceRef; +typedef struct _DNSRecordRef_t *DNSRecordRef; + +struct sockaddr; + +/*! @enum General flags + * Most DNS-SD API functions and callbacks include a DNSServiceFlags parameter. + * As a general rule, any given bit in the 32-bit flags field has a specific fixed meaning, + * regardless of the function or callback being used. For any given function or callback, + * typically only a subset of the possible flags are meaningful, and all others should be zero. + * The discussion section for each API call describes which flags are valid for that call + * and callback. In some cases, for a particular call, it may be that no flags are currently + * defined, in which case the DNSServiceFlags parameter exists purely to allow future expansion. + * In all cases, developers should expect that in future releases, it is possible that new flag + * values will be defined, and write code with this in mind. For example, code that tests + * if (flags == kDNSServiceFlagsAdd) ... + * will fail if, in a future release, another bit in the 32-bit flags field is also set. + * The reliable way to test whether a particular bit is set is not with an equality test, + * but with a bitwise mask: + * if (flags & kDNSServiceFlagsAdd) ... + */ +enum + { + kDNSServiceFlagsMoreComing = 0x1, + /* MoreComing indicates to a callback that at least one more result is + * queued and will be delivered following immediately after this one. + * When the MoreComing flag is set, applications should not immediately + * update their UI, because this can result in a great deal of ugly flickering + * on the screen, and can waste a great deal of CPU time repeatedly updating + * the screen with content that is then immediately erased, over and over. + * Applications should wait until until MoreComing is not set, and then + * update their UI when no more changes are imminent. + * When MoreComing is not set, that doesn't mean there will be no more + * answers EVER, just that there are no more answers immediately + * available right now at this instant. If more answers become available + * in the future they will be delivered as usual. + */ + + kDNSServiceFlagsAdd = 0x2, + kDNSServiceFlagsDefault = 0x4, + /* Flags for domain enumeration and browse/query reply callbacks. + * "Default" applies only to enumeration and is only valid in + * conjunction with "Add". An enumeration callback with the "Add" + * flag NOT set indicates a "Remove", i.e. the domain is no longer + * valid. + */ + + kDNSServiceFlagsNoAutoRename = 0x8, + /* Flag for specifying renaming behavior on name conflict when registering + * non-shared records. By default, name conflicts are automatically handled + * by renaming the service. NoAutoRename overrides this behavior - with this + * flag set, name conflicts will result in a callback. The NoAutorename flag + * is only valid if a name is explicitly specified when registering a service + * (i.e. the default name is not used.) + */ + + kDNSServiceFlagsShared = 0x10, + kDNSServiceFlagsUnique = 0x20, + /* Flag for registering individual records on a connected + * DNSServiceRef. Shared indicates that there may be multiple records + * with this name on the network (e.g. PTR records). Unique indicates that the + * record's name is to be unique on the network (e.g. SRV records). + */ + + kDNSServiceFlagsBrowseDomains = 0x40, + kDNSServiceFlagsRegistrationDomains = 0x80, + /* Flags for specifying domain enumeration type in DNSServiceEnumerateDomains. + * BrowseDomains enumerates domains recommended for browsing, RegistrationDomains + * enumerates domains recommended for registration. + */ + + kDNSServiceFlagsLongLivedQuery = 0x100, + /* Flag for creating a long-lived unicast query for the DNSServiceQueryRecord call. */ + + kDNSServiceFlagsAllowRemoteQuery = 0x200, + /* Flag for creating a record for which we will answer remote queries + * (queries from hosts more than one hop away; hosts not directly connected to the local link). + */ + + kDNSServiceFlagsForceMulticast = 0x400, + /* Flag for signifying that a query or registration should be performed exclusively via multicast + * DNS, even for a name in a domain (e.g. foo.apple.com.) that would normally imply unicast DNS. + */ + + kDNSServiceFlagsForce = 0x800, + /* Flag for signifying a "stronger" variant of an operation. + * Currently defined only for DNSServiceReconfirmRecord(), where it forces a record to + * be removed from the cache immediately, instead of querying for a few seconds before + * concluding that the record is no longer valid and then removing it. This flag should + * be used with caution because if a service browsing PTR record is indeed still valid + * on the network, forcing its removal will result in a user-interface flap -- the + * discovered service instance will disappear, and then re-appear moments later. + */ + + kDNSServiceFlagsReturnIntermediates = 0x1000, + /* Flag for returning intermediate results. + * For example, if a query results in an authoritative NXDomain (name does not exist) + * then that result is returned to the client. However the query is not implicitly + * cancelled -- it remains active and if the answer subsequently changes + * (e.g. because a VPN tunnel is subsequently established) then that positive + * result will still be returned to the client. + * Similarly, if a query results in a CNAME record, then in addition to following + * the CNAME referral, the intermediate CNAME result is also returned to the client. + * When this flag is not set, NXDomain errors are not returned, and CNAME records + * are followed silently without informing the client of the intermediate steps. + * (In earlier builds this flag was briefly calledkDNSServiceFlagsReturnCNAME) + */ + + kDNSServiceFlagsNonBrowsable = 0x2000, + /* A service registered with the NonBrowsable flag set can be resolved using + * DNSServiceResolve(), but will not be discoverable using DNSServiceBrowse(). + * This is for cases where the name is actually a GUID; it is found by other means; + * there is no end-user benefit to browsing to find a long list of opaque GUIDs. + * Using the NonBrowsable flag creates SRV+TXT without the cost of also advertising + * an associated PTR record. + */ + + kDNSServiceFlagsShareConnection = 0x4000, + /* For efficiency, clients that perform many concurrent operations may want to use a + * single Unix Domain Socket connection with the background daemon, instead of having a + * separate connection for each independent operation. To use this mode, clients first + * call DNSServiceCreateConnection(&MainRef) to initialize the main DNSServiceRef. + * For each subsequent operation that is to share that same connection, the client copies + * the MainRef, and then passes the address of that copy, setting the ShareConnection flag + * to tell the library that this DNSServiceRef is not a typical uninitialized DNSServiceRef; + * it's a copy of an existing DNSServiceRef whose connection information should be reused. + * + * For example: + * + * DNSServiceErrorType error; + * DNSServiceRef MainRef; + * error = DNSServiceCreateConnection(&MainRef); + * if (error) ... + * DNSServiceRef BrowseRef = MainRef; // Important: COPY the primary DNSServiceRef first... + * error = DNSServiceBrowse(&BrowseRef, kDNSServiceFlagsShareConnection, ...); // then use the copy + * if (error) ... + * ... + * DNSServiceRefDeallocate(BrowseRef); // Terminate the browse operation + * DNSServiceRefDeallocate(MainRef); // Terminate the shared connection + * + * Notes: + * + * 1. Collective kDNSServiceFlagsMoreComing flag + * When callbacks are invoked using a shared DNSServiceRef, the + * kDNSServiceFlagsMoreComing flag applies collectively to *all* active + * operations sharing the same parent DNSServiceRef. If the MoreComing flag is + * set it means that there are more results queued on this parent DNSServiceRef, + * but not necessarily more results for this particular callback function. + * The implication of this for client programmers is that when a callback + * is invoked with the MoreComing flag set, the code should update its + * internal data structures with the new result, and set a variable indicating + * that its UI needs to be updated. Then, later when a callback is eventually + * invoked with the MoreComing flag not set, the code should update *all* + * stale UI elements related to that shared parent DNSServiceRef that need + * updating, not just the UI elements related to the particular callback + * that happened to be the last one to be invoked. + * + * 2. Canceling operations and kDNSServiceFlagsMoreComing + * Whenever you cancel any operation for which you had deferred UI updates + * waiting because of a kDNSServiceFlagsMoreComing flag, you should perform + * those deferred UI updates. This is because, after cancelling the operation, + * you can no longer wait for a callback *without* MoreComing set, to tell + * you do perform your deferred UI updates (the operation has been canceled, + * so there will be no more callbacks). An implication of the collective + * kDNSServiceFlagsMoreComing flag for shared connections is that this + * guideline applies more broadly -- any time you cancel an operation on + * a shared connection, you should perform all deferred UI updates for all + * operations sharing that connection. This is because the MoreComing flag + * might have been referring to events coming for the operation you canceled, + * which will now not be coming because the operation has been canceled. + * + * 3. Only share DNSServiceRef's created with DNSServiceCreateConnection + * Calling DNSServiceCreateConnection(&ref) creates a special shareable DNSServiceRef. + * DNSServiceRef's created by other calls like DNSServiceBrowse() or DNSServiceResolve() + * cannot be shared by copying them and using kDNSServiceFlagsShareConnection. + * + * 4. Don't Double-Deallocate + * Calling DNSServiceRefDeallocate(ref) for a particular operation's DNSServiceRef terminates + * just that operation. Calling DNSServiceRefDeallocate(ref) for the main shared DNSServiceRef + * (the parent DNSServiceRef, originally created by DNSServiceCreateConnection(&ref)) + * automatically terminates the shared connection and all operations that were still using it. + * After doing this, DO NOT then attempt to deallocate any remaining subordinate DNSServiceRef's. + * The memory used by those subordinate DNSServiceRef's has already been freed, so any attempt + * to do a DNSServiceRefDeallocate (or any other operation) on them will result in accesses + * to freed memory, leading to crashes or other equally undesirable results. + * + * 5. Thread Safety + * The dns_sd.h API does not presuppose any particular threading model, and consequently + * does no locking of its own (which would require linking some specific threading library). + * If client code calls API routines on the same DNSServiceRef concurrently + * from multiple threads, it is the client's responsibility to use a mutext + * lock or take similar appropriate precautions to serialize those calls. + */ + + kDNSServiceFlagsSuppressUnusable = 0x8000, + /* + * This flag is meaningful only in DNSServiceQueryRecord which suppresses unusable queries on the + * wire. If "hostname" is a wide-area unicast DNS hostname (i.e. not a ".local." name) + * but this host has no routable IPv6 address, then the call will not try to look up IPv6 addresses + * for "hostname", since any addresses it found would be unlikely to be of any use anyway. Similarly, + * if this host has no routable IPv4 address, the call will not try to look up IPv4 addresses for + * "hostname". + */ + + kDNSServiceFlagsTimeout = 0x10000, + /* + * When kDNServiceFlagsTimeout is passed to DNSServiceQueryRecord or DNSServiceGetAddrInfo, the query is + * stopped after a certain number of seconds have elapsed. The time at which the query will be stopped + * is determined by the system and cannot be configured by the user. The query will be stopped irrespective + * of whether a response was given earlier or not. When the query is stopped, the callback will be called + * with an error code of kDNSServiceErr_Timeout and a NULL sockaddr will be returned for DNSServiceGetAddrInfo + * and zero length rdata will be returned for DNSServiceQueryRecord. + */ + + kDNSServiceFlagsIncludeP2P = 0x20000, + /* + * Include P2P interfaces when kDNSServiceInterfaceIndexAny is specified. + * By default, specifying kDNSServiceInterfaceIndexAny does not include P2P interfaces. + */ + kDNSServiceFlagsWakeOnResolve = 0x40000 + /* + * This flag is meaningful only in DNSServiceResolve. When set, it tries to send a magic packet + * to wake up the client. + */ + }; + +/* Possible protocols for DNSServiceNATPortMappingCreate(). */ +enum + { + kDNSServiceProtocol_IPv4 = 0x01, + kDNSServiceProtocol_IPv6 = 0x02, + /* 0x04 and 0x08 reserved for future internetwork protocols */ + + kDNSServiceProtocol_UDP = 0x10, + kDNSServiceProtocol_TCP = 0x20 + /* 0x40 and 0x80 reserved for future transport protocols, e.g. SCTP [RFC 2960] + * or DCCP [RFC 4340]. If future NAT gateways are created that support port + * mappings for these protocols, new constants will be defined here. + */ + }; + +/* + * The values for DNS Classes and Types are listed in RFC 1035, and are available + * on every OS in its DNS header file. Unfortunately every OS does not have the + * same header file containing DNS Class and Type constants, and the names of + * the constants are not consistent. For example, BIND 8 uses "T_A", + * BIND 9 uses "ns_t_a", Windows uses "DNS_TYPE_A", etc. + * For this reason, these constants are also listed here, so that code using + * the DNS-SD programming APIs can use these constants, so that the same code + * can compile on all our supported platforms. + */ + +enum + { + kDNSServiceClass_IN = 1 /* Internet */ + }; + +enum + { + kDNSServiceType_A = 1, /* Host address. */ + kDNSServiceType_NS = 2, /* Authoritative server. */ + kDNSServiceType_MD = 3, /* Mail destination. */ + kDNSServiceType_MF = 4, /* Mail forwarder. */ + kDNSServiceType_CNAME = 5, /* Canonical name. */ + kDNSServiceType_SOA = 6, /* Start of authority zone. */ + kDNSServiceType_MB = 7, /* Mailbox domain name. */ + kDNSServiceType_MG = 8, /* Mail group member. */ + kDNSServiceType_MR = 9, /* Mail rename name. */ + kDNSServiceType_NULL = 10, /* Null resource record. */ + kDNSServiceType_WKS = 11, /* Well known service. */ + kDNSServiceType_PTR = 12, /* Domain name pointer. */ + kDNSServiceType_HINFO = 13, /* Host information. */ + kDNSServiceType_MINFO = 14, /* Mailbox information. */ + kDNSServiceType_MX = 15, /* Mail routing information. */ + kDNSServiceType_TXT = 16, /* One or more text strings (NOT "zero or more..."). */ + kDNSServiceType_RP = 17, /* Responsible person. */ + kDNSServiceType_AFSDB = 18, /* AFS cell database. */ + kDNSServiceType_X25 = 19, /* X_25 calling address. */ + kDNSServiceType_ISDN = 20, /* ISDN calling address. */ + kDNSServiceType_RT = 21, /* Router. */ + kDNSServiceType_NSAP = 22, /* NSAP address. */ + kDNSServiceType_NSAP_PTR = 23, /* Reverse NSAP lookup (deprecated). */ + kDNSServiceType_SIG = 24, /* Security signature. */ + kDNSServiceType_KEY = 25, /* Security key. */ + kDNSServiceType_PX = 26, /* X.400 mail mapping. */ + kDNSServiceType_GPOS = 27, /* Geographical position (withdrawn). */ + kDNSServiceType_AAAA = 28, /* IPv6 Address. */ + kDNSServiceType_LOC = 29, /* Location Information. */ + kDNSServiceType_NXT = 30, /* Next domain (security). */ + kDNSServiceType_EID = 31, /* Endpoint identifier. */ + kDNSServiceType_NIMLOC = 32, /* Nimrod Locator. */ + kDNSServiceType_SRV = 33, /* Server Selection. */ + kDNSServiceType_ATMA = 34, /* ATM Address */ + kDNSServiceType_NAPTR = 35, /* Naming Authority PoinTeR */ + kDNSServiceType_KX = 36, /* Key Exchange */ + kDNSServiceType_CERT = 37, /* Certification record */ + kDNSServiceType_A6 = 38, /* IPv6 Address (deprecated) */ + kDNSServiceType_DNAME = 39, /* Non-terminal DNAME (for IPv6) */ + kDNSServiceType_SINK = 40, /* Kitchen sink (experimental) */ + kDNSServiceType_OPT = 41, /* EDNS0 option (meta-RR) */ + kDNSServiceType_APL = 42, /* Address Prefix List */ + kDNSServiceType_DS = 43, /* Delegation Signer */ + kDNSServiceType_SSHFP = 44, /* SSH Key Fingerprint */ + kDNSServiceType_IPSECKEY = 45, /* IPSECKEY */ + kDNSServiceType_RRSIG = 46, /* RRSIG */ + kDNSServiceType_NSEC = 47, /* Denial of Existence */ + kDNSServiceType_DNSKEY = 48, /* DNSKEY */ + kDNSServiceType_DHCID = 49, /* DHCP Client Identifier */ + kDNSServiceType_NSEC3 = 50, /* Hashed Authenticated Denial of Existence */ + kDNSServiceType_NSEC3PARAM = 51, /* Hashed Authenticated Denial of Existence */ + + kDNSServiceType_HIP = 55, /* Host Identity Protocol */ + + kDNSServiceType_SPF = 99, /* Sender Policy Framework for E-Mail */ + kDNSServiceType_UINFO = 100, /* IANA-Reserved */ + kDNSServiceType_UID = 101, /* IANA-Reserved */ + kDNSServiceType_GID = 102, /* IANA-Reserved */ + kDNSServiceType_UNSPEC = 103, /* IANA-Reserved */ + + kDNSServiceType_TKEY = 249, /* Transaction key */ + kDNSServiceType_TSIG = 250, /* Transaction signature. */ + kDNSServiceType_IXFR = 251, /* Incremental zone transfer. */ + kDNSServiceType_AXFR = 252, /* Transfer zone of authority. */ + kDNSServiceType_MAILB = 253, /* Transfer mailbox records. */ + kDNSServiceType_MAILA = 254, /* Transfer mail agent records. */ + kDNSServiceType_ANY = 255 /* Wildcard match. */ + }; + +/* possible error code values */ +enum + { + kDNSServiceErr_NoError = 0, + kDNSServiceErr_Unknown = -65537, /* 0xFFFE FFFF */ + kDNSServiceErr_NoSuchName = -65538, + kDNSServiceErr_NoMemory = -65539, + kDNSServiceErr_BadParam = -65540, + kDNSServiceErr_BadReference = -65541, + kDNSServiceErr_BadState = -65542, + kDNSServiceErr_BadFlags = -65543, + kDNSServiceErr_Unsupported = -65544, + kDNSServiceErr_NotInitialized = -65545, + kDNSServiceErr_AlreadyRegistered = -65547, + kDNSServiceErr_NameConflict = -65548, + kDNSServiceErr_Invalid = -65549, + kDNSServiceErr_Firewall = -65550, + kDNSServiceErr_Incompatible = -65551, /* client library incompatible with daemon */ + kDNSServiceErr_BadInterfaceIndex = -65552, + kDNSServiceErr_Refused = -65553, + kDNSServiceErr_NoSuchRecord = -65554, + kDNSServiceErr_NoAuth = -65555, + kDNSServiceErr_NoSuchKey = -65556, + kDNSServiceErr_NATTraversal = -65557, + kDNSServiceErr_DoubleNAT = -65558, + kDNSServiceErr_BadTime = -65559, /* Codes up to here existed in Tiger */ + kDNSServiceErr_BadSig = -65560, + kDNSServiceErr_BadKey = -65561, + kDNSServiceErr_Transient = -65562, + kDNSServiceErr_ServiceNotRunning = -65563, /* Background daemon not running */ + kDNSServiceErr_NATPortMappingUnsupported = -65564, /* NAT doesn't support NAT-PMP or UPnP */ + kDNSServiceErr_NATPortMappingDisabled = -65565, /* NAT supports NAT-PMP or UPnP but it's disabled by the administrator */ + kDNSServiceErr_NoRouter = -65566, /* No router currently configured (probably no network connectivity) */ + kDNSServiceErr_PollingMode = -65567, + kDNSServiceErr_Timeout = -65568 + + /* mDNS Error codes are in the range + * FFFE FF00 (-65792) to FFFE FFFF (-65537) */ + }; + +/* Maximum length, in bytes, of a service name represented as a */ +/* literal C-String, including the terminating NULL at the end. */ + +#define kDNSServiceMaxServiceName 64 + +/* Maximum length, in bytes, of a domain name represented as an *escaped* C-String */ +/* including the final trailing dot, and the C-String terminating NULL at the end. */ + +#define kDNSServiceMaxDomainName 1009 + +/* + * Notes on DNS Name Escaping + * -- or -- + * "Why is kDNSServiceMaxDomainName 1009, when the maximum legal domain name is 256 bytes?" + * + * All strings used in the DNS-SD APIs are UTF-8 strings. Apart from the exceptions noted below, + * the APIs expect the strings to be properly escaped, using the conventional DNS escaping rules: + * + * '\\' represents a single literal '\' in the name + * '\.' represents a single literal '.' in the name + * '\ddd', where ddd is a three-digit decimal value from 000 to 255, + * represents a single literal byte with that value. + * A bare unescaped '.' is a label separator, marking a boundary between domain and subdomain. + * + * The exceptions, that do not use escaping, are the routines where the full + * DNS name of a resource is broken, for convenience, into servicename/regtype/domain. + * In these routines, the "servicename" is NOT escaped. It does not need to be, since + * it is, by definition, just a single literal string. Any characters in that string + * represent exactly what they are. The "regtype" portion is, technically speaking, + * escaped, but since legal regtypes are only allowed to contain letters, digits, + * and hyphens, there is nothing to escape, so the issue is moot. The "domain" + * portion is also escaped, though most domains in use on the public Internet + * today, like regtypes, don't contain any characters that need to be escaped. + * As DNS-SD becomes more popular, rich-text domains for service discovery will + * become common, so software should be written to cope with domains with escaping. + * + * The servicename may be up to 63 bytes of UTF-8 text (not counting the C-String + * terminating NULL at the end). The regtype is of the form _service._tcp or + * _service._udp, where the "service" part is 1-15 characters, which may be + * letters, digits, or hyphens. The domain part of the three-part name may be + * any legal domain, providing that the resulting servicename+regtype+domain + * name does not exceed 256 bytes. + * + * For most software, these issues are transparent. When browsing, the discovered + * servicenames should simply be displayed as-is. When resolving, the discovered + * servicename/regtype/domain are simply passed unchanged to DNSServiceResolve(). + * When a DNSServiceResolve() succeeds, the returned fullname is already in + * the correct format to pass to standard system DNS APIs such as res_query(). + * For converting from servicename/regtype/domain to a single properly-escaped + * full DNS name, the helper function DNSServiceConstructFullName() is provided. + * + * The following (highly contrived) example illustrates the escaping process. + * Suppose you have an service called "Dr. Smith\Dr. Johnson", of type "_ftp._tcp" + * in subdomain "4th. Floor" of subdomain "Building 2" of domain "apple.com." + * The full (escaped) DNS name of this service's SRV record would be: + * Dr\.\032Smith\\Dr\.\032Johnson._ftp._tcp.4th\.\032Floor.Building\0322.apple.com. + */ + + +/* + * Constants for specifying an interface index + * + * Specific interface indexes are identified via a 32-bit unsigned integer returned + * by the if_nametoindex() family of calls. + * + * If the client passes 0 for interface index, that means "do the right thing", + * which (at present) means, "if the name is in an mDNS local multicast domain + * (e.g. 'local.', '254.169.in-addr.arpa.', '{8,9,A,B}.E.F.ip6.arpa.') then multicast + * on all applicable interfaces, otherwise send via unicast to the appropriate + * DNS server." Normally, most clients will use 0 for interface index to + * automatically get the default sensible behaviour. + * + * If the client passes a positive interface index, then for multicast names that + * indicates to do the operation only on that one interface. For unicast names the + * interface index is ignored unless kDNSServiceFlagsForceMulticast is also set. + * + * If the client passes kDNSServiceInterfaceIndexLocalOnly when registering + * a service, then that service will be found *only* by other local clients + * on the same machine that are browsing using kDNSServiceInterfaceIndexLocalOnly + * or kDNSServiceInterfaceIndexAny. + * If a client has a 'private' service, accessible only to other processes + * running on the same machine, this allows the client to advertise that service + * in a way such that it does not inadvertently appear in service lists on + * all the other machines on the network. + * + * If the client passes kDNSServiceInterfaceIndexLocalOnly when browsing + * then it will find *all* records registered on that same local machine. + * Clients explicitly wishing to discover *only* LocalOnly services can + * accomplish this by inspecting the interfaceIndex of each service reported + * to their DNSServiceBrowseReply() callback function, and discarding those + * where the interface index is not kDNSServiceInterfaceIndexLocalOnly. + * + * kDNSServiceInterfaceIndexP2P is meaningful only in Browse, QueryRecord, + * and Resolve operations. It should not be used in other DNSService APIs. + * + * - If kDNSServiceInterfaceIndexP2P is passed to DNSServiceBrowse or + * DNSServiceQueryRecord, it restricts the operation to P2P. + * + * - If kDNSServiceInterfaceIndexP2P is passed to DNSServiceResolve, it is + * mapped internally to kDNSServiceInterfaceIndexAny, because resolving + * a P2P service may create and/or enable an interface whose index is not + * known a priori. The resolve callback will indicate the index of the + * interface via which the service can be accessed. + * + * If applications pass kDNSServiceInterfaceIndexAny to DNSServiceBrowse + * or DNSServiceQueryRecord, they must set the kDNSServiceFlagsIncludeP2P flag + * to include P2P. In this case, if a service instance or the record being queried + * is found over P2P, the resulting ADD event will indicate kDNSServiceInterfaceIndexP2P + * as the interface index. + */ + +#define kDNSServiceInterfaceIndexAny 0 +#define kDNSServiceInterfaceIndexLocalOnly ((uint32_t)-1) +#define kDNSServiceInterfaceIndexUnicast ((uint32_t)-2) +#define kDNSServiceInterfaceIndexP2P ((uint32_t)-3) + +typedef uint32_t DNSServiceFlags; +typedef uint32_t DNSServiceProtocol; +typedef int32_t DNSServiceErrorType; + +/* + * When requesting kDNSServiceProperty_DaemonVersion, the result pointer must point + * to a 32-bit unsigned integer, and the size parameter must be set to sizeof(uint32_t). + * + * On return, the 32-bit unsigned integer contains the version number, formatted as follows: + * Major part of the build number * 10000 + + * minor part of the build number * 100 + * + * For example, Mac OS X 10.4.9 has mDNSResponder-108.4, which would be represented as + * version 1080400. This allows applications to do simple greater-than and less-than comparisons: + * e.g. an application that requires at least mDNSResponder-108.4 can check: + * + * if (version >= 1080400) ... + * + * Example usage: + * + * uint32_t version; + * uint32_t size = sizeof(version); + * DNSServiceErrorType err = DNSServiceGetProperty(kDNSServiceProperty_DaemonVersion, &version, &size); + * if (!err) printf("Bonjour version is %d.%d\n", version / 10000, version / 100 % 100); + */ + +#define kDNSServiceProperty_DaemonVersion "DaemonVersion" + + +/* DNSServiceEnumerateDomains() + * + * Asynchronously enumerate domains available for browsing and registration. + * + * The enumeration MUST be cancelled via DNSServiceRefDeallocate() when no more domains + * are to be found. + * + * Note that the names returned are (like all of DNS-SD) UTF-8 strings, + * and are escaped using standard DNS escaping rules. + * (See "Notes on DNS Name Escaping" earlier in this file for more details.) + * A graphical browser displaying a hierarchical tree-structured view should cut + * the names at the bare dots to yield individual labels, then de-escape each + * label according to the escaping rules, and then display the resulting UTF-8 text. + * + * DNSServiceDomainEnumReply Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceEnumerateDomains(). + * + * flags: Possible values are: + * kDNSServiceFlagsMoreComing + * kDNSServiceFlagsAdd + * kDNSServiceFlagsDefault + * + * interfaceIndex: Specifies the interface on which the domain exists. (The index for a given + * interface is determined via the if_nametoindex() family of calls.) + * + * errorCode: Will be kDNSServiceErr_NoError (0) on success, otherwise indicates + * the failure that occurred (other parameters are undefined if errorCode is nonzero). + * + * replyDomain: The name of the domain. + * + * context: The context pointer passed to DNSServiceEnumerateDomains. + * + */ + +typedef void (DNSSD_API *DNSServiceDomainEnumReply) + ( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *replyDomain, + void *context + ); + + +/* Register a service that is discovered via Browse() and Resolve() calls. + * + * DNSServiceRegisterReply() Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceRegister(). + * + * flags: When a name is successfully registered, the callback will be + * invoked with the kDNSServiceFlagsAdd flag set. When Wide-Area + * DNS-SD is in use, it is possible for a single service to get + * more than one success callback (e.g. one in the "local" multicast + * DNS domain, and another in a wide-area unicast DNS domain). + * If a successfully-registered name later suffers a name conflict + * or similar problem and has to be deregistered, the callback will + * be invoked with the kDNSServiceFlagsAdd flag not set. The callback + * is *not* invoked in the case where the caller explicitly terminates + * the service registration by calling DNSServiceRefDeallocate(ref); + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred (including name conflicts, + * if the kDNSServiceFlagsNoAutoRename flag was used when registering.) + * Other parameters are undefined if errorCode is nonzero. + * + * name: The service name registered (if the application did not specify a name in + * DNSServiceRegister(), this indicates what name was automatically chosen). + * + * regtype: The type of service registered, as it was passed to the callout. + * + * domain: The domain on which the service was registered (if the application did not + * specify a domain in DNSServiceRegister(), this indicates the default domain + * on which the service was registered). + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceRegisterReply) + ( + DNSServiceRef sdRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + const char *name, + const char *regtype, + const char *domain, + void *context + ); + + +/* Browse for instances of a service. + * + * DNSServiceBrowseReply() Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceBrowse(). + * + * flags: Possible values are kDNSServiceFlagsMoreComing and kDNSServiceFlagsAdd. + * See flag definitions for details. + * + * interfaceIndex: The interface on which the service is advertised. This index should + * be passed to DNSServiceResolve() when resolving the service. + * + * errorCode: Will be kDNSServiceErr_NoError (0) on success, otherwise will + * indicate the failure that occurred. Other parameters are undefined if + * the errorCode is nonzero. + * + * serviceName: The discovered service name. This name should be displayed to the user, + * and stored for subsequent use in the DNSServiceResolve() call. + * + * regtype: The service type, which is usually (but not always) the same as was passed + * to DNSServiceBrowse(). One case where the discovered service type may + * not be the same as the requested service type is when using subtypes: + * The client may want to browse for only those ftp servers that allow + * anonymous connections. The client will pass the string "_ftp._tcp,_anon" + * to DNSServiceBrowse(), but the type of the service that's discovered + * is simply "_ftp._tcp". The regtype for each discovered service instance + * should be stored along with the name, so that it can be passed to + * DNSServiceResolve() when the service is later resolved. + * + * domain: The domain of the discovered service instance. This may or may not be the + * same as the domain that was passed to DNSServiceBrowse(). The domain for each + * discovered service instance should be stored along with the name, so that + * it can be passed to DNSServiceResolve() when the service is later resolved. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceBrowseReply) + ( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *serviceName, + const char *regtype, + const char *replyDomain, + void *context + ); + + + +/* DNSServiceResolve() + * + * Resolve a service name discovered via DNSServiceBrowse() to a target host name, port number, and + * txt record. + * + * Note: Applications should NOT use DNSServiceResolve() solely for txt record monitoring - use + * DNSServiceQueryRecord() instead, as it is more efficient for this task. + * + * Note: When the desired results have been returned, the client MUST terminate the resolve by calling + * DNSServiceRefDeallocate(). + * + * Note: DNSServiceResolve() behaves correctly for typical services that have a single SRV record + * and a single TXT record. To resolve non-standard services with multiple SRV or TXT records, + * DNSServiceQueryRecord() should be used. + * + * DNSServiceResolveReply Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceResolve(). + * + * flags: Possible values: kDNSServiceFlagsMoreComing + * + * interfaceIndex: The interface on which the service was resolved. + * + * errorCode: Will be kDNSServiceErr_NoError (0) on success, otherwise will + * indicate the failure that occurred. Other parameters are undefined if + * the errorCode is nonzero. + * + * fullname: The full service domain name, in the form ... + * (This name is escaped following standard DNS rules, making it suitable for + * passing to standard system DNS APIs such as res_query(), or to the + * special-purpose functions included in this API that take fullname parameters. + * See "Notes on DNS Name Escaping" earlier in this file for more details.) + * + * hosttarget: The target hostname of the machine providing the service. This name can + * be passed to functions like gethostbyname() to identify the host's IP address. + * + * port: The port, in network byte order, on which connections are accepted for this service. + * + * txtLen: The length of the txt record, in bytes. + * + * txtRecord: The service's primary txt record, in standard txt record format. + * + * context: The context pointer that was passed to the callout. + * + * NOTE: In earlier versions of this header file, the txtRecord parameter was declared "const char *" + * This is incorrect, since it contains length bytes which are values in the range 0 to 255, not -128 to +127. + * Depending on your compiler settings, this change may cause signed/unsigned mismatch warnings. + * These should be fixed by updating your own callback function definition to match the corrected + * function signature using "const unsigned char *txtRecord". Making this change may also fix inadvertent + * bugs in your callback function, where it could have incorrectly interpreted a length byte with value 250 + * as being -6 instead, with various bad consequences ranging from incorrect operation to software crashes. + * If you need to maintain portable code that will compile cleanly with both the old and new versions of + * this header file, you should update your callback function definition to use the correct unsigned value, + * and then in the place where you pass your callback function to DNSServiceResolve(), use a cast to eliminate + * the compiler warning, e.g.: + * DNSServiceResolve(sd, flags, index, name, regtype, domain, (DNSServiceResolveReply)MyCallback, context); + * This will ensure that your code compiles cleanly without warnings (and more importantly, works correctly) + * with both the old header and with the new corrected version. + * + */ + +typedef void (DNSSD_API *DNSServiceResolveReply) + ( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *fullname, + const char *hosttarget, + uint16_t port, /* In network byte order */ + uint16_t txtLen, + const unsigned char *txtRecord, + void *context + ); + + +/* DNSServiceQueryRecord + * + * Query for an arbitrary DNS record. + * + * DNSServiceQueryRecordReply() Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceQueryRecord(). + * + * flags: Possible values are kDNSServiceFlagsMoreComing and + * kDNSServiceFlagsAdd. The Add flag is NOT set for PTR records + * with a ttl of 0, i.e. "Remove" events. + * + * interfaceIndex: The interface on which the query was resolved (the index for a given + * interface is determined via the if_nametoindex() family of calls). + * See "Constants for specifying an interface index" for more details. + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred. Other parameters are undefined if + * errorCode is nonzero. + * + * fullname: The resource record's full domain name. + * + * rrtype: The resource record's type (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN). + * + * rdlen: The length, in bytes, of the resource record rdata. + * + * rdata: The raw rdata of the resource record. + * + * ttl: If the client wishes to cache the result for performance reasons, + * the TTL indicates how long the client may legitimately hold onto + * this result, in seconds. After the TTL expires, the client should + * consider the result no longer valid, and if it requires this data + * again, it should be re-fetched with a new query. Of course, this + * only applies to clients that cancel the asynchronous operation when + * they get a result. Clients that leave the asynchronous operation + * running can safely assume that the data remains valid until they + * get another callback telling them otherwise. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceQueryRecordReply) + ( + 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 + ); + +/* DNSServiceGetAddrInfo + * + * Queries for the IP address of a hostname by using either Multicast or Unicast DNS. + * + * DNSServiceGetAddrInfoReply() parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceGetAddrInfo(). + * + * flags: Possible values are kDNSServiceFlagsMoreComing and + * kDNSServiceFlagsAdd. + * + * interfaceIndex: The interface to which the answers pertain. + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred. Other parameters are + * undefined if errorCode is nonzero. + * + * hostname: The fully qualified domain name of the host to be queried for. + * + * address: IPv4 or IPv6 address. + * + * ttl: If the client wishes to cache the result for performance reasons, + * the TTL indicates how long the client may legitimately hold onto + * this result, in seconds. After the TTL expires, the client should + * consider the result no longer valid, and if it requires this data + * again, it should be re-fetched with a new query. Of course, this + * only applies to clients that cancel the asynchronous operation when + * they get a result. Clients that leave the asynchronous operation + * running can safely assume that the data remains valid until they + * get another callback telling them otherwise. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceGetAddrInfoReply) + ( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *hostname, + const struct sockaddr *address, + uint32_t ttl, + void *context + ); + + + +/* DNSServiceRegisterRecord + * + * Register an individual resource record on a connected DNSServiceRef. + * + * Note that name conflicts occurring for records registered via this call must be handled + * by the client in the callback. + * + * DNSServiceRegisterRecordReply() parameters: + * + * sdRef: The connected DNSServiceRef initialized by + * DNSServiceCreateConnection(). + * + * RecordRef: The DNSRecordRef initialized by DNSServiceRegisterRecord(). If the above + * DNSServiceRef is passed to DNSServiceRefDeallocate(), this DNSRecordRef is + * invalidated, and may not be used further. + * + * flags: Currently unused, reserved for future use. + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred (including name conflicts.) + * Other parameters are undefined if errorCode is nonzero. + * + * context: The context pointer that was passed to the callout. + * + */ + + typedef void (DNSSD_API *DNSServiceRegisterRecordReply) + ( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + void *context + ); + + +/* DNSServiceNATPortMappingCreate + * + * Request a port mapping in the NAT gateway, which maps a port on the local machine + * to an external port on the NAT. The NAT should support either the NAT-PMP or the UPnP IGD + * protocol for this API to create a successful mapping. + * + * The port mapping will be renewed indefinitely until the client process exits, or + * explicitly terminates the port mapping request by calling DNSServiceRefDeallocate(). + * The client callback will be invoked, informing the client of the NAT gateway's + * external IP address and the external port that has been allocated for this client. + * The client should then record this external IP address and port using whatever + * directory service mechanism it is using to enable peers to connect to it. + * (Clients advertising services using Wide-Area DNS-SD DO NOT need to use this API + * -- when a client calls DNSServiceRegister() NAT mappings are automatically created + * and the external IP address and port for the service are recorded in the global DNS. + * Only clients using some directory mechanism other than Wide-Area DNS-SD need to use + * this API to explicitly map their own ports.) + * + * It's possible that the client callback could be called multiple times, for example + * if the NAT gateway's IP address changes, or if a configuration change results in a + * different external port being mapped for this client. Over the lifetime of any long-lived + * port mapping, the client should be prepared to handle these notifications of changes + * in the environment, and should update its recorded address and/or port as appropriate. + * + * NOTE: There are two unusual aspects of how the DNSServiceNATPortMappingCreate API works, + * which were intentionally designed to help simplify client code: + * + * 1. It's not an error to request a NAT mapping when the machine is not behind a NAT gateway. + * In other NAT mapping APIs, if you request a NAT mapping and the machine is not behind a NAT + * gateway, then the API returns an error code -- it can't get you a NAT mapping if there's no + * NAT gateway. The DNSServiceNATPortMappingCreate API takes a different view. Working out + * whether or not you need a NAT mapping can be tricky and non-obvious, particularly on + * a machine with multiple active network interfaces. Rather than make every client recreate + * this logic for deciding whether a NAT mapping is required, the PortMapping API does that + * work for you. If the client calls the PortMapping API when the machine already has a + * routable public IP address, then instead of complaining about it and giving an error, + * the PortMapping API just invokes your callback, giving the machine's public address + * and your own port number. This means you don't need to write code to work out whether + * your client needs to call the PortMapping API -- just call it anyway, and if it wasn't + * necessary, no harm is done: + * + * - If the machine already has a routable public IP address, then your callback + * will just be invoked giving your own address and port. + * - If a NAT mapping is required and obtained, then your callback will be invoked + * giving you the external address and port. + * - If a NAT mapping is required but not obtained from the local NAT gateway, + * or the machine has no network connectivity, then your callback will be + * invoked giving zero address and port. + * + * 2. In other NAT mapping APIs, if a laptop computer is put to sleep and woken up on a new + * network, it's the client's job to notice this, and work out whether a NAT mapping + * is required on the new network, and make a new NAT mapping request if necessary. + * The DNSServiceNATPortMappingCreate API does this for you, automatically. + * The client just needs to make one call to the PortMapping API, and its callback will + * be invoked any time the mapping state changes. This property complements point (1) above. + * If the client didn't make a NAT mapping request just because it determined that one was + * not required at that particular moment in time, the client would then have to monitor + * for network state changes to determine if a NAT port mapping later became necessary. + * By unconditionally making a NAT mapping request, even when a NAT mapping not to be + * necessary, the PortMapping API will then begin monitoring network state changes on behalf of + * the client, and if a NAT mapping later becomes necessary, it will automatically create a NAT + * mapping and inform the client with a new callback giving the new address and port information. + * + * DNSServiceNATPortMappingReply() parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceNATPortMappingCreate(). + * + * flags: Currently unused, reserved for future use. + * + * interfaceIndex: The interface through which the NAT gateway is reached. + * + * errorCode: Will be kDNSServiceErr_NoError on success. + * Will be kDNSServiceErr_DoubleNAT when the NAT gateway is itself behind one or + * more layers of NAT, in which case the other parameters have the defined values. + * For other failures, will indicate the failure that occurred, and the other + * parameters are undefined. + * + * externalAddress: Four byte IPv4 address in network byte order. + * + * protocol: Will be kDNSServiceProtocol_UDP or kDNSServiceProtocol_TCP or both. + * + * internalPort: The port on the local machine that was mapped. + * + * externalPort: The actual external port in the NAT gateway that was mapped. + * This is likely to be different than the requested external port. + * + * ttl: The lifetime of the NAT port mapping created on the gateway. + * This controls how quickly stale mappings will be garbage-collected + * if the client machine crashes, suffers a power failure, is disconnected + * from the network, or suffers some other unfortunate demise which + * causes it to vanish without explicitly removing its NAT port mapping. + * It's possible that the ttl value will differ from the requested ttl value. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceNATPortMappingReply) + ( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + uint32_t externalAddress, /* four byte IPv4 address in network byte order */ + DNSServiceProtocol protocol, + uint16_t internalPort, /* In network byte order */ + uint16_t externalPort, /* In network byte order and may be different than the requested port */ + uint32_t ttl, /* may be different than the requested ttl */ + void *context + ); + + + +/* TXTRecordRef + * + * Opaque internal data type. + * Note: Represents a DNS-SD TXT record. + */ + +typedef union _TXTRecordRef_t { char PrivateData[16]; char *ForceNaturalAlignment; } TXTRecordRef; + + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/src/libs/zeroconf/embed/dnssd_clientlib.c b/src/libs/zeroconf/embed/dnssd_clientlib.c new file mode 100644 index 00000000000..2a5dd956abe --- /dev/null +++ b/src/libs/zeroconf/embed/dnssd_clientlib.c @@ -0,0 +1,370 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2004, Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "dns_sd_types.h" + +#if MDNS_BUILDINGSHAREDLIBRARY || MDNS_BUILDINGSTUBLIBRARY +//#pragma export on +#endif + +#if defined(_WIN32) +// disable warning "conversion from to uint16_t" +#pragma warning(disable:4244) +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + +/********************************************************************************************* + * + * Supporting Functions + * + *********************************************************************************************/ +namespace ZeroConf { namespace embeddedLib { + +#include "dns_sd_funct.h" + +#define mDNSIsDigit(X) ((X) >= '0' && (X) <= '9') + +// DomainEndsInDot returns 1 if name ends with a dot, 0 otherwise +// (DNSServiceConstructFullName depends this returning 1 for true, rather than any non-zero value meaning true) + +static int DomainEndsInDot(const char *dom) + { + while (dom[0] && dom[1]) + { + if (dom[0] == '\\') // advance past escaped byte sequence + { + if (mDNSIsDigit(dom[1]) && mDNSIsDigit(dom[2]) && mDNSIsDigit(dom[3])) + dom += 4; // If "\ddd" then skip four + else dom += 2; // else if "\x" then skip two + } + else dom++; // else goto next character + } + return (dom[0] == '.'); + } + +static uint8_t *InternalTXTRecordSearch + ( + uint16_t txtLen, + const void *txtRecord, + const char *key, + unsigned long *keylen + ) + { + uint8_t *p = (uint8_t*)txtRecord; + uint8_t *e = p + txtLen; + *keylen = (unsigned long) strlen(key); + while (p= lim) goto fail; + *fn++ = '\\'; + *fn++ = '0' + (c / 100); + *fn++ = '0' + (c / 10) % 10; + c = '0' + (c ) % 10; + } + else if (c == '.' || (c == '\\')) // Escape dot and backslash literals + { + if (fn+2 >= lim) goto fail; + *fn++ = '\\'; + } + else + if (fn+1 >= lim) goto fail; + *fn++ = (char)c; + } + *fn++ = '.'; + } + + while (*r) if (fn+1 >= lim) goto fail; else *fn++ = *r++; + if (!DomainEndsInDot(regtype)) { if (fn+1 >= lim) goto fail; else *fn++ = '.'; } + + while (*d) if (fn+1 >= lim) goto fail; else *fn++ = *d++; + if (!DomainEndsInDot(domain)) { if (fn+1 >= lim) goto fail; else *fn++ = '.'; } + + *fn = '\0'; + return kDNSServiceErr_NoError; + +fail: + *fn = '\0'; + return kDNSServiceErr_BadParam; + } + +/********************************************************************************************* + * + * TXT Record Construction Functions + * + *********************************************************************************************/ + +typedef struct _TXTRecordRefRealType + { + uint8_t *buffer; // Pointer to data + uint16_t buflen; // Length of buffer + uint16_t datalen; // Length currently in use + uint16_t malloced; // Non-zero if buffer was allocated via malloc() + } TXTRecordRefRealType; + +#define txtRec ((TXTRecordRefRealType*)txtRecord) + +// The opaque storage defined in the public dns_sd.h header is 16 bytes; +// make sure we don't exceed that. +struct CompileTimeAssertionCheck_dnssd_clientlib + { + char assert0[(sizeof(TXTRecordRefRealType) <= 16) ? 1 : -1]; + }; + +void DNSSD_API TXTRecordCreate + ( + TXTRecordRef *txtRecord, + uint16_t bufferLen, + void *buffer + ) + { + txtRec->buffer = static_cast(buffer); + txtRec->buflen = buffer ? bufferLen : (uint16_t)0; + txtRec->datalen = 0; + txtRec->malloced = 0; + } + +void DNSSD_API TXTRecordDeallocate(TXTRecordRef *txtRecord) + { + if (txtRec->malloced) free(txtRec->buffer); + } + +DNSServiceErrorType DNSSD_API TXTRecordSetValue + ( + TXTRecordRef *txtRecord, + const char *key, + uint8_t valueSize, + const void *value + ) + { + uint8_t *start, *p; + const char *k; + unsigned long keysize, keyvalsize; + + for (k = key; *k; k++) if (*k < 0x20 || *k > 0x7E || *k == '=') return(kDNSServiceErr_Invalid); + keysize = (unsigned long)(k - key); + keyvalsize = 1 + keysize + (value ? (1 + valueSize) : 0); + if (keysize < 1 || keyvalsize > 255) return(kDNSServiceErr_Invalid); + (void)TXTRecordRemoveValue(txtRecord, key); + if (txtRec->datalen + keyvalsize > txtRec->buflen) + { + unsigned char *newbuf; + unsigned long newlen = txtRec->datalen + keyvalsize; + if (newlen > 0xFFFF) return(kDNSServiceErr_Invalid); + newbuf = static_cast(malloc((size_t)newlen)); + if (!newbuf) return(kDNSServiceErr_NoMemory); + memcpy(newbuf, txtRec->buffer, txtRec->datalen); + if (txtRec->malloced) free(txtRec->buffer); + txtRec->buffer = newbuf; + txtRec->buflen = (uint16_t)(newlen); + txtRec->malloced = 1; + } + start = txtRec->buffer + txtRec->datalen; + p = start + 1; + memcpy(p, key, keysize); + p += keysize; + if (value) + { + *p++ = '='; + memcpy(p, value, valueSize); + p += valueSize; + } + *start = (uint8_t)(p - start - 1); + txtRec->datalen += p - start; + return(kDNSServiceErr_NoError); + } + +DNSServiceErrorType DNSSD_API TXTRecordRemoveValue + ( + TXTRecordRef *txtRecord, + const char *key + ) + { + unsigned long keylen, itemlen, remainder; + uint8_t *item = InternalTXTRecordSearch(txtRec->datalen, txtRec->buffer, key, &keylen); + if (!item) return(kDNSServiceErr_NoSuchKey); + itemlen = (unsigned long)(1 + item[0]); + remainder = (unsigned long)((txtRec->buffer + txtRec->datalen) - (item + itemlen)); + // Use memmove because memcpy behaviour is undefined for overlapping regions + memmove(item, item + itemlen, remainder); + txtRec->datalen -= itemlen; + return(kDNSServiceErr_NoError); + } + +uint16_t DNSSD_API TXTRecordGetLength (const TXTRecordRef *txtRecord) { return(txtRec->datalen); } +const void * DNSSD_API TXTRecordGetBytesPtr(const TXTRecordRef *txtRecord) { return(txtRec->buffer); } + +/********************************************************************************************* + * + * TXT Record Parsing Functions + * + *********************************************************************************************/ + +int DNSSD_API TXTRecordContainsKey + ( + uint16_t txtLen, + const void *txtRecord, + const char *key + ) + { + unsigned long keylen; + return (InternalTXTRecordSearch(txtLen, txtRecord, key, &keylen) ? 1 : 0); + } + +const void * DNSSD_API TXTRecordGetValuePtr + ( + uint16_t txtLen, + const void *txtRecord, + const char *key, + uint8_t *valueLen + ) + { + unsigned long keylen; + uint8_t *item = InternalTXTRecordSearch(txtLen, txtRecord, key, &keylen); + if (!item || item[0] <= keylen) return(NULL); // If key not found, or found with no value, return NULL + *valueLen = (uint8_t)(item[0] - (keylen + 1)); + return (item + 1 + keylen + 1); + } + +uint16_t DNSSD_API TXTRecordGetCount + ( + uint16_t txtLen, + const void *txtRecord + ) + { + uint16_t count = 0; + uint8_t *p = (uint8_t*)txtRecord; + uint8_t *e = p + txtLen; + while (pe) ? (uint16_t)0 : count); + } + +DNSServiceErrorType DNSSD_API TXTRecordGetItemAtIndex + ( + uint16_t txtLen, + const void *txtRecord, + uint16_t itemIndex, + uint16_t keyBufLen, + char *key, + uint8_t *valueLen, + const void **value + ) + { + uint16_t count = 0; + uint8_t *p = (uint8_t*)txtRecord; + uint8_t *e = p + txtLen; + while (p= keyBufLen) return(kDNSServiceErr_NoMemory); + memcpy(key, x, len); + key[len] = 0; + if (x+len +#include + +#include "dnssd_ipc.h" + +namespace ZeroConf { namespace embeddedLib { +#include "dns_sd_funct.h" +static int gDaemonErr = kDNSServiceErr_NoError; + }} + +extern "C" { + +#if defined(_WIN32) + + #define _SSIZE_T + #include + #include + #include + #include + #include + #include + + #define sockaddr_mdns sockaddr_in + #define AF_MDNS AF_INET + + // Disable warning: "'type cast' : from data pointer 'void *' to function pointer" + #pragma warning(disable:4055) + + // Disable warning: "nonstandard extension, function/data pointer conversion in expression" + #pragma warning(disable:4152) + + extern BOOL IsSystemServiceDisabled(); + + #define sleep(X) Sleep((X) * 1000) + #define NOT_HAVE_SA_LEN +namespace ZeroConf { namespace embeddedLib { + static int g_initWinsock = 0; + #define LOG_WARNING kDebugLevelWarning + #define LOG_INFO kDebugLevelInfo + static void syslog( int priority, const char * message, ...) + { + va_list args; + int len; + char * buffer; + DWORD err = WSAGetLastError(); + (void) priority; + va_start( args, message ); + len = _vscprintf( message, args ) + 1; + buffer = reinterpret_cast(malloc( len * sizeof(char) )); + if ( buffer ) { vsprintf( buffer, message, args ); OutputDebugStringA( buffer ); free( buffer ); } + WSASetLastError( err ); + } + }} +#else + + #include // For O_RDWR etc. + #include + #include + #include + + #define sockaddr_mdns sockaddr_un + #define AF_MDNS AF_LOCAL +#endif + +#ifdef Q_OS_LINUX +#define NOT_HAVE_SA_LEN +#endif + +// Specifies how many times we'll try and connect to the server. + +#define DNSSD_CLIENT_MAXTRIES 4 + +// Uncomment the line below to use the old error return mechanism of creating a temporary named socket (e.g. in /var/tmp) +//#define USE_NAMED_ERROR_RETURN_SOCKET 1 + +#define DNSSD_CLIENT_TIMEOUT 10 // In seconds + +#ifndef CTL_PATH_PREFIX +#define CTL_PATH_PREFIX "/var/tmp/dnssd_result_socket." +#endif + +typedef struct + { + ipc_msg_hdr ipc_hdr; + DNSServiceFlags cb_flags; + uint32_t cb_interface; + DNSServiceErrorType cb_err; + } CallbackHeader; + +typedef struct _DNSServiceRef_t DNSServiceOp; +typedef struct _DNSRecordRef_t DNSRecord; + +// client stub callback to process message from server and deliver results to client application +typedef void (*ProcessReplyFn)(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *msg, const char *const end); + +#define ValidatorBits 0x12345678 +#define DNSServiceRefValid(X) (dnssd_SocketValid((X)->sockfd) && (((X)->sockfd ^ (X)->validator) == ValidatorBits)) + +// When using kDNSServiceFlagsShareConnection, there is one primary _DNSServiceOp_t, and zero or more subordinates +// For the primary, the 'next' field points to the first subordinate, and its 'next' field points to the next, and so on. +// For the primary, the 'primary' field is NULL; for subordinates the 'primary' field points back to the associated primary +// +// _DNS_SD_LIBDISPATCH is defined where libdispatch/GCD is available. This does not mean that the application will use the +// DNSServiceSetDispatchQueue API. Hence any new code guarded with _DNS_SD_LIBDISPATCH should still be backwards compatible. +struct _DNSServiceRef_t + { + DNSServiceOp *next; // For shared connection + DNSServiceOp *primary; // For shared connection + dnssd_sock_t sockfd; // Connected socket between client and daemon + dnssd_sock_t validator; // Used to detect memory corruption, double disposals, etc. + client_context_t uid; // For shared connection requests, each subordinate DNSServiceRef has its own ID, + // unique within the scope of the same shared parent DNSServiceRef + uint32_t op; // request_op_t or reply_op_t + uint32_t max_index; // Largest assigned record index - 0 if no additional records registered + uint32_t logcounter; // Counter used to control number of syslog messages we write + int *moreptr; // Set while DNSServiceProcessResult working on this particular DNSServiceRef + ProcessReplyFn ProcessReply; // Function pointer to the code to handle received messages + void *AppCallback; // Client callback function and context + void *AppContext; + DNSRecord *rec; +#if _DNS_SD_LIBDISPATCH + dispatch_source_t disp_source; + dispatch_queue_t disp_queue; +#endif + }; + +struct _DNSRecordRef_t + { + DNSRecord *recnext; + void *AppContext; + DNSServiceRegisterRecordReply AppCallback; + DNSRecordRef recref; + uint32_t record_index; // index is unique to the ServiceDiscoveryRef + DNSServiceOp *sdr; + }; +} +namespace ZeroConf { namespace embeddedLib { + +// Write len bytes. Return 0 on success, -1 on error +static int write_all(dnssd_sock_t sd, char *buf, size_t len) + { + // Don't use "MSG_WAITALL"; it returns "Invalid argument" on some Linux versions; use an explicit while () loop instead. + //if (send(sd, buf, len, MSG_WAITALL) != len) return -1; + while (len) + { + ssize_t num_written = send(sd, buf, (long)len, 0); + if (num_written < 0 || (size_t)num_written > len) + { + // Should never happen. If it does, it indicates some OS bug, + // or that the mDNSResponder daemon crashed (which should never happen). + #if !defined(__ppc__) && defined(SO_ISDEFUNCT) + int defunct; + socklen_t dlen = sizeof (defunct); + if (getsockopt(sd, SOL_SOCKET, SO_ISDEFUNCT, &defunct, &dlen) < 0) + syslog(LOG_WARNING, "dnssd_clientstub write_all: SO_ISDEFUNCT failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); + if (!defunct) + syslog(LOG_WARNING, "dnssd_clientstub write_all(%d) failed %ld/%ld %d %s", sd, + (long)num_written, (long)len, + (num_written < 0) ? dnssd_errno : 0, + (num_written < 0) ? dnssd_strerror(dnssd_errno) : ""); + else + syslog(LOG_INFO, "dnssd_clientstub write_all(%d) DEFUNCT", sd); + #else + syslog(LOG_WARNING, "dnssd_clientstub write_all(%d) failed %ld/%ld %d %s", sd, + (long)num_written, (long)len, + (num_written < 0) ? dnssd_errno : 0, + (num_written < 0) ? dnssd_strerror(dnssd_errno) : ""); + #endif + return -1; + } + buf += num_written; + len -= num_written; + } + return 0; + } + +enum { read_all_success = 0, read_all_fail = -1, read_all_wouldblock = -2 }; + +// Read len bytes. Return 0 on success, read_all_fail on error, or read_all_wouldblock for +static int read_all(dnssd_sock_t sd, char *buf, int len) + { + // Don't use "MSG_WAITALL"; it returns "Invalid argument" on some Linux versions; use an explicit while () loop instead. + //if (recv(sd, buf, len, MSG_WAITALL) != len) return -1; + + while (len) + { + ssize_t num_read = recv(sd, buf, len, 0); + // It is valid to get an interrupted system call error e.g., somebody attaching + // in a debugger, retry without failing + if ((num_read < 0) && (errno == EINTR)) { syslog(LOG_INFO, "dnssd_clientstub read_all: EINTR continue"); continue; } + if ((num_read == 0) || (num_read < 0) || (num_read > len)) + { + int printWarn = 0; + int defunct = 0; + // Should never happen. If it does, it indicates some OS bug, + // or that the mDNSResponder daemon crashed (which should never happen). +#if defined(WIN32) + // Suppress logs for "A non-blocking socket operation + // could not be completed immediately" + if (WSAGetLastError() != WSAEWOULDBLOCK) + printWarn = 1; +#endif +#if !defined(__ppc__) && defined(SO_ISDEFUNCT) + { + socklen_t dlen = sizeof (defunct); + if (getsockopt(sd, SOL_SOCKET, SO_ISDEFUNCT, &defunct, &dlen) < 0) + syslog(LOG_WARNING, "dnssd_clientstub read_all: SO_ISDEFUNCT failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); + } + if (!defunct) + printWarn = 1; +#endif + if (printWarn) + syslog(LOG_WARNING, "dnssd_clientstub read_all(%d) failed %ld/%ld %d %s", sd, + (long)num_read, (long)len, + (num_read < 0) ? dnssd_errno : 0, + (num_read < 0) ? dnssd_strerror(dnssd_errno) : ""); + else if (defunct) + syslog(LOG_INFO, "dnssd_clientstub read_all(%d) DEFUNCT", sd); + return (num_read < 0 && dnssd_errno == dnssd_EWOULDBLOCK) ? read_all_wouldblock : read_all_fail; + } + buf += num_read; + len -= num_read; + } + return read_all_success; + } + +// Returns 1 if more bytes remain to be read on socket descriptor sd, 0 otherwise +static int more_bytes(dnssd_sock_t sd) + { + struct timeval tv = { 0, 0 }; + fd_set readfds; + fd_set *fs; + int ret; + + if (sd < FD_SETSIZE) + { + fs = &readfds; + FD_ZERO(fs); + } + else + { + // Compute the number of integers needed for storing "sd". Internally fd_set is stored + // as an array of ints with one bit for each fd and hence we need to compute + // the number of ints needed rather than the number of bytes. If "sd" is 32, we need + // two ints and not just one. + int nfdbits = sizeof (int) * 8; + int nints = (sd/nfdbits) + 1; + fs = (fd_set *)calloc(nints, sizeof(int)); + if (fs == NULL) { syslog(LOG_WARNING, "dnssd_clientstub more_bytes: malloc failed"); return 0; } + } + FD_SET(sd, fs); + ret = select((int)sd+1, fs, (fd_set*)NULL, (fd_set*)NULL, &tv); + if (fs != &readfds) free(fs); + return (ret > 0); + } + +// Wait for daemon to write to socket +static int wait_for_daemon(dnssd_sock_t sock, int timeout) + { +#ifndef WIN32 + // At this point the next operation (accept() or read()) on this socket may block for a few milliseconds waiting + // for the daemon to respond, but that's okay -- the daemon is a trusted service and we know if won't take more + // than a few milliseconds to respond. So we'll forego checking for readability of the socket. + (void) sock; + (void) timeout; +#else + // Windows on the other hand suffers from 3rd party software (primarily 3rd party firewall software) that + // interferes with proper functioning of the TCP protocol stack. Because of this and because we depend on TCP + // to communicate with the system service, we want to make sure that the next operation on this socket (accept() or + // read()) doesn't block indefinitely. + if (!gDaemonErr) + { + struct timeval tv; + fd_set set; + + FD_ZERO(&set); + FD_SET(sock, &set); + tv.tv_sec = timeout; + tv.tv_usec = 0; + if (!select((int)(sock + 1), &set, NULL, NULL, &tv)) + { + syslog(LOG_WARNING, "dnssd_clientstub wait_for_daemon timed out"); + gDaemonErr = kDNSServiceErr_Timeout; + } + } +#endif + return gDaemonErr; + } + +/* create_hdr + * + * allocate and initialize an ipc message header. Value of len should initially be the + * length of the data, and is set to the value of the data plus the header. data_start + * is set to point to the beginning of the data section. SeparateReturnSocket should be + * non-zero for calls that can't receive an immediate error return value on their primary + * socket, and therefore require a separate return path for the error code result. + * if zero, the path to a control socket is appended at the beginning of the message buffer. + * data_start is set past this string. + */ +static ipc_msg_hdr *create_hdr(uint32_t op, size_t *len, char **data_start, int SeparateReturnSocket, DNSServiceOp *ref) + { + char *msg = NULL; + ipc_msg_hdr *hdr; + int datalen; +#if !defined(USE_TCP_LOOPBACK) + char ctrl_path[64] = ""; // "/var/tmp/dnssd_result_socket.xxxxxxxxxx-xxx-xxxxxx" +#endif + + if (SeparateReturnSocket) + { +#if defined(USE_TCP_LOOPBACK) + *len += 2; // Allocate space for two-byte port number +#elif defined(USE_NAMED_ERROR_RETURN_SOCKET) + struct timeval tv; + if (gettimeofday(&tv, NULL) < 0) + { syslog(LOG_WARNING, "dnssd_clientstub create_hdr: gettimeofday failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); return NULL; } + sprintf(ctrl_path, "%s%d-%.3lx-%.6lu", CTL_PATH_PREFIX, (int)getpid(), + (unsigned long)(tv.tv_sec & 0xFFF), (unsigned long)(tv.tv_usec)); + *len += strlen(ctrl_path) + 1; +#else + *len += 1; // Allocate space for single zero byte (empty C string) +#endif + } + + datalen = (int) *len; + *len += sizeof(ipc_msg_hdr); + + // Write message to buffer + msg = static_cast(malloc(*len)); + if (!msg) { syslog(LOG_WARNING, "dnssd_clientstub create_hdr: malloc failed"); return NULL; } + + memset(msg, 0, *len); + hdr = (ipc_msg_hdr *)msg; + hdr->version = VERSION; + hdr->datalen = datalen; + hdr->ipc_flags = 0; + hdr->op = op; + hdr->client_context = ref->uid; + hdr->reg_index = 0; + *data_start = msg + sizeof(ipc_msg_hdr); +#if defined(USE_TCP_LOOPBACK) + // Put dummy data in for the port, since we don't know what it is yet. + // The data will get filled in before we send the message. This happens in deliver_request(). + if (SeparateReturnSocket) put_uint16(0, data_start); +#else + if (SeparateReturnSocket) put_string(ctrl_path, data_start); +#endif + return hdr; + } + +static void FreeDNSRecords(DNSServiceOp *sdRef) + { + DNSRecord *rec = sdRef->rec; + while (rec) + { + DNSRecord *next = rec->recnext; + free(rec); + rec = next; + } + } + +static void FreeDNSServiceOp(DNSServiceOp *x) + { + // We don't use our DNSServiceRefValid macro here because if we're cleaning up after a socket() call failed + // then sockfd could legitimately contain a failing value (e.g. dnssd_InvalidSocket) + if ((x->sockfd ^ x->validator) != ValidatorBits) + syslog(LOG_WARNING, "dnssd_clientstub attempt to dispose invalid DNSServiceRef %p %08X %08X", x, x->sockfd, x->validator); + else + { + x->next = NULL; + x->primary = NULL; + x->sockfd = dnssd_InvalidSocket; + x->validator = 0xDDDDDDDD; + x->op = request_op_none; + x->max_index = 0; + x->logcounter = 0; + x->moreptr = NULL; + x->ProcessReply = NULL; + x->AppCallback = NULL; + x->AppContext = NULL; +#if _DNS_SD_LIBDISPATCH + if (x->disp_source) dispatch_release(x->disp_source); + x->disp_source = NULL; + x->disp_queue = NULL; +#endif + // DNSRecords may have been added to subordinate sdRef e.g., DNSServiceRegister/DNSServiceAddRecord + // or on the main sdRef e.g., DNSServiceCreateConnection/DNSServiveRegisterRecord. DNSRecords may have + // been freed if the application called DNSRemoveRecord + FreeDNSRecords(x); + free(x); + } + } + +// Return a connected service ref (deallocate with DNSServiceRefDeallocate) +static DNSServiceErrorType ConnectToServer(DNSServiceRef *ref, DNSServiceFlags flags, uint32_t op, ProcessReplyFn ProcessReply, void *AppCallback, void *AppContext) + { + #if APPLE_OSX_mDNSResponder + int NumTries = DNSSD_CLIENT_MAXTRIES; + #else + int NumTries = 0; + #endif + + dnssd_sockaddr_t saddr; + DNSServiceOp *sdr; + + if (!ref) { syslog(LOG_WARNING, "dnssd_clientstub DNSService operation with NULL DNSServiceRef"); return kDNSServiceErr_BadParam; } + + if (flags & kDNSServiceFlagsShareConnection) + { + if (!*ref) + { + syslog(LOG_WARNING, "dnssd_clientstub kDNSServiceFlagsShareConnection used with NULL DNSServiceRef"); + return kDNSServiceErr_BadParam; + } + if (!DNSServiceRefValid(*ref) || (*ref)->op != connection_request || (*ref)->primary) + { + syslog(LOG_WARNING, "dnssd_clientstub kDNSServiceFlagsShareConnection used with invalid DNSServiceRef %p %08X %08X", + (*ref), (*ref)->sockfd, (*ref)->validator); + *ref = NULL; + return kDNSServiceErr_BadReference; + } + } + + #if defined(_WIN32) + if (!g_initWinsock) + { + WSADATA wsaData; + g_initWinsock = 1; + if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) { *ref = NULL; return kDNSServiceErr_ServiceNotRunning; } + } + // If the system service is disabled, we only want to try to connect once + if (IsSystemServiceDisabled()) NumTries = DNSSD_CLIENT_MAXTRIES; + #endif + + sdr = static_cast(malloc(sizeof(DNSServiceOp))); + if (!sdr) { syslog(LOG_WARNING, "dnssd_clientstub ConnectToServer: malloc failed"); *ref = NULL; return kDNSServiceErr_NoMemory; } + sdr->next = NULL; + sdr->primary = NULL; + sdr->sockfd = dnssd_InvalidSocket; + sdr->validator = sdr->sockfd ^ ValidatorBits; + sdr->op = op; + sdr->max_index = 0; + sdr->logcounter = 0; + sdr->moreptr = NULL; + sdr->uid.u32[0] = 0; + sdr->uid.u32[1] = 0; + sdr->ProcessReply = ProcessReply; + sdr->AppCallback = AppCallback; + sdr->AppContext = AppContext; + sdr->rec = NULL; +#if _DNS_SD_LIBDISPATCH + sdr->disp_source = NULL; + sdr->disp_queue = NULL; +#endif + + if (flags & kDNSServiceFlagsShareConnection) + { + DNSServiceOp **p = &(*ref)->next; // Append ourselves to end of primary's list + while (*p) p = &(*p)->next; + *p = sdr; + // Preincrement counter before we use it -- it helps with debugging if we know the all-zeroes ID should never appear + if (++(*ref)->uid.u32[0] == 0) ++(*ref)->uid.u32[1]; // In parent DNSServiceOp increment UID counter + sdr->primary = *ref; // Set our primary pointer + sdr->sockfd = (*ref)->sockfd; // Inherit primary's socket + sdr->validator = (*ref)->validator; + sdr->uid = (*ref)->uid; + //printf("ConnectToServer sharing socket %d\n", sdr->sockfd); + } + else + { + #ifdef SO_NOSIGPIPE + const unsigned long optval = 1; + #endif + *ref = NULL; + sdr->sockfd = socket(AF_DNSSD, SOCK_STREAM, 0); + sdr->validator = sdr->sockfd ^ ValidatorBits; + if (!dnssd_SocketValid(sdr->sockfd)) + { + syslog(LOG_WARNING, "dnssd_clientstub ConnectToServer: socket failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); + FreeDNSServiceOp(sdr); + return kDNSServiceErr_NoMemory; + } + #ifdef SO_NOSIGPIPE + // Some environments (e.g. OS X) support turning off SIGPIPE for a socket + if (setsockopt(sdr->sockfd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)) < 0) + syslog(LOG_WARNING, "dnssd_clientstub ConnectToServer: SO_NOSIGPIPE failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); + #endif + #if defined(USE_TCP_LOOPBACK) + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = inet_addr(MDNS_TCP_SERVERADDR); + saddr.sin_port = htons(MDNS_TCP_SERVERPORT); + #else + saddr.sun_family = AF_LOCAL; + strcpy(saddr.sun_path, MDNS_UDS_SERVERPATH); + #if !defined(__ppc__) && defined(SO_DEFUNCTOK) + { + int defunct = 1; + if (setsockopt(sdr->sockfd, SOL_SOCKET, SO_DEFUNCTOK, &defunct, sizeof(defunct)) < 0) + syslog(LOG_WARNING, "dnssd_clientstub ConnectToServer: SO_DEFUNCTOK failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); + } + #endif + #endif + + while (1) + { + int err = connect(sdr->sockfd, (struct sockaddr *) &saddr, sizeof(saddr)); + if (!err) break; // If we succeeded, return sdr + // If we failed, then it may be because the daemon is still launching. + // This can happen for processes that launch early in the boot process, while the + // daemon is still coming up. Rather than fail here, we'll wait a bit and try again. + // If, after four seconds, we still can't connect to the daemon, + // then we give up and return a failure code. + if (++NumTries < DNSSD_CLIENT_MAXTRIES) sleep(1); // Sleep a bit, then try again + else { dnssd_close(sdr->sockfd); FreeDNSServiceOp(sdr); return kDNSServiceErr_ServiceNotRunning; } + } + //printf("ConnectToServer opened socket %d\n", sdr->sockfd); + } + + *ref = sdr; + return kDNSServiceErr_NoError; + } + +#define deliver_request_bailout(MSG) \ + do { syslog(LOG_WARNING, "dnssd_clientstub deliver_request: %s failed %d (%s)", (MSG), dnssd_errno, dnssd_strerror(dnssd_errno)); goto cleanup; } while (0) + +static DNSServiceErrorType deliver_request(ipc_msg_hdr *hdr, DNSServiceOp *sdr) + { + uint32_t datalen = hdr->datalen; // We take a copy here because we're going to convert hdr->datalen to network byte order + #if defined(USE_TCP_LOOPBACK) || defined(USE_NAMED_ERROR_RETURN_SOCKET) + char *const data = (char *)hdr + sizeof(ipc_msg_hdr); + #endif + dnssd_sock_t listenfd = dnssd_InvalidSocket, errsd = dnssd_InvalidSocket; + DNSServiceErrorType err = kDNSServiceErr_Unknown; // Default for the "goto cleanup" cases + int MakeSeparateReturnSocket = 0; + + // Note: need to check hdr->op, not sdr->op. + // hdr->op contains the code for the specific operation we're currently doing, whereas sdr->op + // contains the original parent DNSServiceOp (e.g. for an add_record_request, hdr->op will be + // add_record_request but the parent sdr->op will be connection_request or reg_service_request) + if (sdr->primary || + hdr->op == reg_record_request || hdr->op == add_record_request || hdr->op == update_record_request || hdr->op == remove_record_request) + MakeSeparateReturnSocket = 1; + + if (!DNSServiceRefValid(sdr)) + { + syslog(LOG_WARNING, "dnssd_clientstub deliver_request: invalid DNSServiceRef %p %08X %08X", sdr, sdr->sockfd, sdr->validator); + return kDNSServiceErr_BadReference; + } + + if (!hdr) { syslog(LOG_WARNING, "dnssd_clientstub deliver_request: !hdr"); return kDNSServiceErr_Unknown; } + + if (MakeSeparateReturnSocket) + { + #if defined(USE_TCP_LOOPBACK) + { + union { uint16_t s; u_char b[2]; } port; + dnssd_sockaddr_t caddr; + dnssd_socklen_t len = (dnssd_socklen_t) sizeof(caddr); + listenfd = socket(AF_DNSSD, SOCK_STREAM, 0); + if (!dnssd_SocketValid(listenfd)) deliver_request_bailout("TCP socket"); + + caddr.sin_family = AF_INET; + caddr.sin_port = 0; + caddr.sin_addr.s_addr = inet_addr(MDNS_TCP_SERVERADDR); + if (bind(listenfd, (struct sockaddr*) &caddr, sizeof(caddr)) < 0) deliver_request_bailout("TCP bind"); + if (getsockname(listenfd, (struct sockaddr*) &caddr, &len) < 0) deliver_request_bailout("TCP getsockname"); + if (listen(listenfd, 1) < 0) deliver_request_bailout("TCP listen"); + port.s = caddr.sin_port; + data[0] = port.b[0]; // don't switch the byte order, as the + data[1] = port.b[1]; // daemon expects it in network byte order + } + #elif defined(USE_NAMED_ERROR_RETURN_SOCKET) + { + mode_t mask; + int bindresult; + dnssd_sockaddr_t caddr; + listenfd = socket(AF_DNSSD, SOCK_STREAM, 0); + if (!dnssd_SocketValid(listenfd)) deliver_request_bailout("USE_NAMED_ERROR_RETURN_SOCKET socket"); + + caddr.sun_family = AF_LOCAL; + // According to Stevens (section 3.2), there is no portable way to + // determine whether sa_len is defined on a particular platform. + #ifndef NOT_HAVE_SA_LEN + caddr.sun_len = sizeof(struct sockaddr_un); + #endif + strcpy(caddr.sun_path, data); + mask = umask(0); + bindresult = bind(listenfd, (struct sockaddr *)&caddr, sizeof(caddr)); + umask(mask); + if (bindresult < 0) deliver_request_bailout("USE_NAMED_ERROR_RETURN_SOCKET bind"); + if (listen(listenfd, 1) < 0) deliver_request_bailout("USE_NAMED_ERROR_RETURN_SOCKET listen"); + } + #else + { + dnssd_sock_t sp[2]; + if (socketpair(AF_DNSSD, SOCK_STREAM, 0, sp) < 0) deliver_request_bailout("socketpair"); + else + { + errsd = sp[0]; // We'll read our four-byte error code from sp[0] + listenfd = sp[1]; // We'll send sp[1] to the daemon + #if !defined(__ppc__) && defined(SO_DEFUNCTOK) + { + int defunct = 1; + if (setsockopt(errsd, SOL_SOCKET, SO_DEFUNCTOK, &defunct, sizeof(defunct)) < 0) + syslog(LOG_WARNING, "dnssd_clientstub ConnectToServer: SO_DEFUNCTOK failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); + } + #endif + } + } + #endif + } + +#if !defined(USE_TCP_LOOPBACK) && !defined(USE_NAMED_ERROR_RETURN_SOCKET) + // If we're going to make a separate error return socket, and pass it to the daemon + // using sendmsg, then we'll hold back one data byte to go with it. + // On some versions of Unix (including Leopard) sending a control message without + // any associated data does not work reliably -- e.g. one particular issue we ran + // into is that if the receiving program is in a kqueue loop waiting to be notified + // of the received message, it doesn't get woken up when the control message arrives. + if (MakeSeparateReturnSocket || sdr->op == send_bpf) datalen--; // Okay to use sdr->op when checking for op == send_bpf +#endif + + // At this point, our listening socket is set up and waiting, if necessary, for the daemon to connect back to + ConvertHeaderBytes(hdr); + //syslog(LOG_WARNING, "dnssd_clientstub deliver_request writing %lu bytes", (unsigned long)(datalen + sizeof(ipc_msg_hdr))); + //if (MakeSeparateReturnSocket) syslog(LOG_WARNING, "dnssd_clientstub deliver_request name is %s", data); +#if TEST_SENDING_ONE_BYTE_AT_A_TIME + unsigned int i; + for (i=0; isockfd, ((char *)hdr)+i, 1) < 0) + { syslog(LOG_WARNING, "write_all (byte %u) failed", i); goto cleanup; } + usleep(10000); + } +#else + if (write_all(sdr->sockfd, (char *)hdr, datalen + sizeof(ipc_msg_hdr)) < 0) + { + // write_all already prints an error message if there is an error writing to + // the socket except for DEFUNCT. Logging here is unnecessary and also wrong + // in the case of DEFUNCT sockets + syslog(LOG_INFO, "dnssd_clientstub deliver_request ERROR: write_all(%d, %lu bytes) failed", + sdr->sockfd, (unsigned long)(datalen + sizeof(ipc_msg_hdr))); + goto cleanup; + } +#endif + + if (!MakeSeparateReturnSocket) errsd = sdr->sockfd; + if (MakeSeparateReturnSocket || sdr->op == send_bpf) // Okay to use sdr->op when checking for op == send_bpf + { +#if defined(USE_TCP_LOOPBACK) || defined(USE_NAMED_ERROR_RETURN_SOCKET) + // At this point we may block in accept for a few milliseconds waiting for the daemon to connect back to us, + // but that's okay -- the daemon is a trusted service and we know if won't take more than a few milliseconds to respond. + dnssd_sockaddr_t daddr; + dnssd_socklen_t len = sizeof(daddr); + if ((err = wait_for_daemon(listenfd, DNSSD_CLIENT_TIMEOUT)) != kDNSServiceErr_NoError) goto cleanup; + errsd = accept(listenfd, (struct sockaddr *)&daddr, &len); + if (!dnssd_SocketValid(errsd)) deliver_request_bailout("accept"); +#else + +#if APPLE_OSX_mDNSResponder +// On Leopard, the stock definitions of the CMSG_* macros in /usr/include/sys/socket.h, +// while arguably correct in theory, nonetheless in practice produce code that doesn't work on 64-bit machines +// For details see Bonjour API broken for 64-bit apps (SCM_RIGHTS sendmsg fails) +#undef CMSG_DATA +#define CMSG_DATA(cmsg) ((unsigned char *)(cmsg) + (sizeof(struct cmsghdr))) +#undef CMSG_SPACE +#define CMSG_SPACE(l) ((sizeof(struct cmsghdr)) + (l)) +#undef CMSG_LEN +#define CMSG_LEN(l) ((sizeof(struct cmsghdr)) + (l)) +#endif + + struct iovec vec = { ((char *)hdr) + sizeof(ipc_msg_hdr) + datalen, 1 }; // Send the last byte along with the SCM_RIGHTS + struct msghdr msg; + struct cmsghdr *cmsg; + char cbuf[CMSG_SPACE(sizeof(dnssd_sock_t))]; + + if (sdr->op == send_bpf) // Okay to use sdr->op when checking for op == send_bpf + { + int i; + char p[12]; // Room for "/dev/bpf999" with terminating null + for (i=0; i<100; i++) + { + snprintf(p, sizeof(p), "/dev/bpf%d", i); + listenfd = open(p, O_RDWR, 0); + //if (dnssd_SocketValid(listenfd)) syslog(LOG_WARNING, "Sending fd %d for %s", listenfd, p); + if (!dnssd_SocketValid(listenfd) && dnssd_errno != EBUSY) + syslog(LOG_WARNING, "Error opening %s %d (%s)", p, dnssd_errno, dnssd_strerror(dnssd_errno)); + if (dnssd_SocketValid(listenfd) || dnssd_errno != EBUSY) break; + } + } + + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_control = cbuf; + msg.msg_controllen = CMSG_LEN(sizeof(dnssd_sock_t)); + msg.msg_flags = 0; + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(dnssd_sock_t)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *((dnssd_sock_t *)CMSG_DATA(cmsg)) = listenfd; + +#if TEST_KQUEUE_CONTROL_MESSAGE_BUG + sleep(1); +#endif + +#if DEBUG_64BIT_SCM_RIGHTS + syslog(LOG_WARNING, "dnssd_clientstub sendmsg read sd=%d write sd=%d %ld %ld %ld/%ld/%ld/%ld", + errsd, listenfd, sizeof(dnssd_sock_t), sizeof(void*), + sizeof(struct cmsghdr) + sizeof(dnssd_sock_t), + CMSG_LEN(sizeof(dnssd_sock_t)), (long)CMSG_SPACE(sizeof(dnssd_sock_t)), + (long)((char*)CMSG_DATA(cmsg) + 4 - cbuf)); +#endif // DEBUG_64BIT_SCM_RIGHTS + + if (sendmsg(sdr->sockfd, &msg, 0) < 0) + { + syslog(LOG_WARNING, "dnssd_clientstub deliver_request ERROR: sendmsg failed read sd=%d write sd=%d errno %d (%s)", + errsd, listenfd, dnssd_errno, dnssd_strerror(dnssd_errno)); + err = kDNSServiceErr_Incompatible; + goto cleanup; + } + +#if DEBUG_64BIT_SCM_RIGHTS + syslog(LOG_WARNING, "dnssd_clientstub sendmsg read sd=%d write sd=%d okay", errsd, listenfd); +#endif // DEBUG_64BIT_SCM_RIGHTS + +#endif + // Close our end of the socketpair *before* blocking in read_all to get the four-byte error code. + // Otherwise, if the daemon closes our socket (or crashes), we block in read_all() forever + // because the socket is not closed (we still have an open reference to it ourselves). + dnssd_close(listenfd); + listenfd = dnssd_InvalidSocket; // Make sure we don't close it a second time in the cleanup handling below + } + + // At this point we may block in read_all for a few milliseconds waiting for the daemon to send us the error code, + // but that's okay -- the daemon is a trusted service and we know if won't take more than a few milliseconds to respond. + if (sdr->op == send_bpf) // Okay to use sdr->op when checking for op == send_bpf + err = kDNSServiceErr_NoError; + else if ((err = wait_for_daemon(errsd, DNSSD_CLIENT_TIMEOUT)) == kDNSServiceErr_NoError) + { + if (read_all(errsd, (char*)&err, (int)sizeof(err)) < 0) + err = kDNSServiceErr_ServiceNotRunning; // On failure read_all will have written a message to syslog for us + else + err = ntohl(err); + } + + //syslog(LOG_WARNING, "dnssd_clientstub deliver_request: retrieved error code %d", err); + +cleanup: + if (MakeSeparateReturnSocket) + { + if (dnssd_SocketValid(listenfd)) dnssd_close(listenfd); + if (dnssd_SocketValid(errsd)) dnssd_close(errsd); +#if defined(USE_NAMED_ERROR_RETURN_SOCKET) + // syslog(LOG_WARNING, "dnssd_clientstub deliver_request: removing UDS: %s", data); + if (unlink(data) != 0) + syslog(LOG_WARNING, "dnssd_clientstub WARNING: unlink(\"%s\") failed errno %d (%s)", data, dnssd_errno, dnssd_strerror(dnssd_errno)); + // else syslog(LOG_WARNING, "dnssd_clientstub deliver_request: removed UDS: %s", data); +#endif + } + + free(hdr); + return err; + } + +int DNSSD_API DNSServiceRefSockFD(DNSServiceRef sdRef) + { + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRefSockFD called with NULL DNSServiceRef"); return dnssd_InvalidSocket; } + + if (!DNSServiceRefValid(sdRef)) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRefSockFD called with invalid DNSServiceRef %p %08X %08X", + sdRef, sdRef->sockfd, sdRef->validator); + return dnssd_InvalidSocket; + } + + if (sdRef->primary) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRefSockFD undefined for kDNSServiceFlagsShareConnection subordinate DNSServiceRef %p", sdRef); + return dnssd_InvalidSocket; + } + + return (int) sdRef->sockfd; + } + +#if _DNS_SD_LIBDISPATCH +static void CallbackWithError(DNSServiceRef sdRef, DNSServiceErrorType error) + { + DNSServiceOp *sdr = sdRef; + DNSServiceOp *sdrNext; + DNSRecord *rec; + DNSRecord *recnext; + int morebytes; + + while (sdr) + { + // We can't touch the sdr after the callback as it can be deallocated in the callback + sdrNext = sdr->next; + morebytes = 1; + sdr->moreptr = &morebytes; + switch (sdr->op) + { + case resolve_request: + if (sdr->AppCallback)((DNSServiceResolveReply) sdr->AppCallback)(sdr, 0, 0, error, NULL, 0, 0, 0, NULL, sdr->AppContext); + break; + case query_request: + if (sdr->AppCallback)((DNSServiceQueryRecordReply)sdr->AppCallback)(sdr, 0, 0, error, NULL, 0, 0, 0, NULL, 0, sdr->AppContext); + break; + case addrinfo_request: + if (sdr->AppCallback)((DNSServiceGetAddrInfoReply)sdr->AppCallback)(sdr, 0, 0, error, NULL, NULL, 0, sdr->AppContext); + break; + case browse_request: + if (sdr->AppCallback)((DNSServiceBrowseReply) sdr->AppCallback)(sdr, 0, 0, error, NULL, 0, NULL, sdr->AppContext); + break; + case reg_service_request: + if (sdr->AppCallback)((DNSServiceRegisterReply) sdr->AppCallback)(sdr, 0, error, NULL, 0, NULL, sdr->AppContext); + break; + case enumeration_request: + if (sdr->AppCallback)((DNSServiceDomainEnumReply) sdr->AppCallback)(sdr, 0, 0, error, NULL, sdr->AppContext); + break; + case connection_request: + // This means Register Record, walk the list of DNSRecords to do the callback + rec = sdr->rec; + while (rec) + { + recnext = rec->recnext; + if (rec->AppCallback) ((DNSServiceRegisterRecordReply)rec->AppCallback)(sdr, 0, 0, error, rec->AppContext); + // The Callback can call DNSServiceRefDeallocate which in turn frees sdr and all the records. + // Detect that and return early + if (!morebytes){syslog(LOG_WARNING, "dnssdclientstub:Record: CallbackwithError morebytes zero"); return;} + rec = recnext; + } + break; + case port_mapping_request: + if (sdr->AppCallback)((DNSServiceNATPortMappingReply)sdr->AppCallback)(sdr, 0, 0, error, 0, 0, 0, 0, 0, sdr->AppContext); + break; + default: + syslog(LOG_WARNING, "dnssd_clientstub CallbackWithError called with bad op %d", sdr->op); + } + // If DNSServiceRefDeallocate was called in the callback, morebytes will be zero. It means + // all other sdrefs have been freed. This happens for shared connections where the + // DNSServiceRefDeallocate on the first sdRef frees all other sdrefs. + if (!morebytes){syslog(LOG_WARNING, "dnssdclientstub:sdRef: CallbackwithError morebytes zero"); return;} + sdr = sdrNext; + } + } +#endif // _DNS_SD_LIBDISPATCH + +// Handle reply from server, calling application client callback. If there is no reply +// from the daemon on the socket contained in sdRef, the call will block. +DNSServiceErrorType DNSSD_API DNSServiceProcessResult(DNSServiceRef sdRef) + { + int morebytes = 0; + + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceProcessResult called with NULL DNSServiceRef"); return kDNSServiceErr_BadParam; } + + if (!DNSServiceRefValid(sdRef)) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceProcessResult called with invalid DNSServiceRef %p %08X %08X", sdRef, sdRef->sockfd, sdRef->validator); + return kDNSServiceErr_BadReference; + } + + if (sdRef->primary) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceProcessResult undefined for kDNSServiceFlagsShareConnection subordinate DNSServiceRef %p", sdRef); + return kDNSServiceErr_BadReference; + } + + if (!sdRef->ProcessReply) + { + static int num_logs = 0; + if (num_logs < 10) syslog(LOG_WARNING, "dnssd_clientstub DNSServiceProcessResult called with DNSServiceRef with no ProcessReply function"); + if (num_logs < 1000) num_logs++; else sleep(1); + return kDNSServiceErr_BadReference; + } + + do + { + CallbackHeader cbh; + char *data; + + // return NoError on EWOULDBLOCK. This will handle the case + // where a non-blocking socket is told there is data, but it was a false positive. + // On error, read_all will write a message to syslog for us, so don't need to duplicate that here + // Note: If we want to properly support using non-blocking sockets in the future + int result = read_all(sdRef->sockfd, (char *)&cbh.ipc_hdr, sizeof(cbh.ipc_hdr)); + if (result == read_all_fail) + { + // Set the ProcessReply to NULL before callback as the sdRef can get deallocated + // in the callback. + sdRef->ProcessReply = NULL; +#if _DNS_SD_LIBDISPATCH + // Call the callbacks with an error if using the dispatch API, as DNSServiceProcessResult + // is not called by the application and hence need to communicate the error. Cancel the + // source so that we don't get any more events + if (sdRef->disp_source) + { + dispatch_source_cancel(sdRef->disp_source); + dispatch_release(sdRef->disp_source); + sdRef->disp_source = NULL; + CallbackWithError(sdRef, kDNSServiceErr_ServiceNotRunning); + } +#endif + // Don't touch sdRef anymore as it might have been deallocated + return kDNSServiceErr_ServiceNotRunning; + } + else if (result == read_all_wouldblock) + { + if (morebytes && sdRef->logcounter < 100) + { + sdRef->logcounter++; + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceProcessResult error: select indicated data was waiting but read_all returned EWOULDBLOCK"); + } + return kDNSServiceErr_NoError; + } + + ConvertHeaderBytes(&cbh.ipc_hdr); + if (cbh.ipc_hdr.version != VERSION) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceProcessResult daemon version %d does not match client version %d", cbh.ipc_hdr.version, VERSION); + sdRef->ProcessReply = NULL; + return kDNSServiceErr_Incompatible; + } + + data = static_cast(malloc(cbh.ipc_hdr.datalen)); + if (!data) return kDNSServiceErr_NoMemory; + if (read_all(sdRef->sockfd, data, cbh.ipc_hdr.datalen) < 0) // On error, read_all will write a message to syslog for us + { + // Set the ProcessReply to NULL before callback as the sdRef can get deallocated + // in the callback. + sdRef->ProcessReply = NULL; +#if _DNS_SD_LIBDISPATCH + // Call the callbacks with an error if using the dispatch API, as DNSServiceProcessResult + // is not called by the application and hence need to communicate the error. Cancel the + // source so that we don't get any more events + if (sdRef->disp_source) + { + dispatch_source_cancel(sdRef->disp_source); + dispatch_release(sdRef->disp_source); + sdRef->disp_source = NULL; + CallbackWithError(sdRef, kDNSServiceErr_ServiceNotRunning); + } +#endif + // Don't touch sdRef anymore as it might have been deallocated + free(data); + return kDNSServiceErr_ServiceNotRunning; + } + else + { + const char *ptr = data; + cbh.cb_flags = get_flags (&ptr, data + cbh.ipc_hdr.datalen); + cbh.cb_interface = get_uint32 (&ptr, data + cbh.ipc_hdr.datalen); + cbh.cb_err = get_error_code(&ptr, data + cbh.ipc_hdr.datalen); + + // CAUTION: We have to handle the case where the client calls DNSServiceRefDeallocate from within the callback function. + // To do this we set moreptr to point to morebytes. If the client does call DNSServiceRefDeallocate(), + // then that routine will clear morebytes for us, and cause us to exit our loop. + morebytes = more_bytes(sdRef->sockfd); + if (morebytes) + { + cbh.cb_flags |= kDNSServiceFlagsMoreComing; + sdRef->moreptr = &morebytes; + } + if (ptr) sdRef->ProcessReply(sdRef, &cbh, ptr, data + cbh.ipc_hdr.datalen); + // Careful code here: + // If morebytes is non-zero, that means we set sdRef->moreptr above, and the operation was not + // cancelled out from under us, so now we need to clear sdRef->moreptr so we don't leave a stray + // dangling pointer pointing to a long-gone stack variable. + // If morebytes is zero, then one of two thing happened: + // (a) morebytes was 0 above, so we didn't set sdRef->moreptr, so we don't need to clear it + // (b) morebytes was 1 above, and we set sdRef->moreptr, but the operation was cancelled (with DNSServiceRefDeallocate()), + // so we MUST NOT try to dereference our stale sdRef pointer. + if (morebytes) sdRef->moreptr = NULL; + } + free(data); + } while (morebytes); + + return kDNSServiceErr_NoError; + } + +void DNSSD_API DNSServiceRefDeallocate(DNSServiceRef sdRef) + { + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRefDeallocate called with NULL DNSServiceRef"); return; } + + if (!DNSServiceRefValid(sdRef)) // Also verifies dnssd_SocketValid(sdRef->sockfd) for us too + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRefDeallocate called with invalid DNSServiceRef %p %08X %08X", sdRef, sdRef->sockfd, sdRef->validator); + return; + } + + // If we're in the middle of a DNSServiceProcessResult() invocation for this DNSServiceRef, clear its morebytes flag to break it out of its while loop + if (sdRef->moreptr) *(sdRef->moreptr) = 0; + + if (sdRef->primary) // If this is a subordinate DNSServiceOp, just send a 'stop' command + { + DNSServiceOp **p = &sdRef->primary->next; + while (*p && *p != sdRef) p = &(*p)->next; + if (*p) + { + char *ptr; + size_t len = 0; + ipc_msg_hdr *hdr = create_hdr(cancel_request, &len, &ptr, 0, sdRef); + if (hdr) + { + ConvertHeaderBytes(hdr); + write_all(sdRef->sockfd, (char *)hdr, len); + free(hdr); + } + *p = sdRef->next; + FreeDNSServiceOp(sdRef); + } + } + else // else, make sure to terminate all subordinates as well + { +#if _DNS_SD_LIBDISPATCH + // The cancel handler will close the fd if a dispatch source has been set + if (sdRef->disp_source) + { + // By setting the ProcessReply to NULL, we make sure that we never call + // the application callbacks ever, after returning from this function. We + // assume that DNSServiceRefDeallocate is called from the serial queue + // that was passed to DNSServiceSetDispatchQueue. Hence, dispatch_source_cancel + // should cancel all the blocks on the queue and hence there should be no more + // callbacks when we return from this function. Setting ProcessReply to NULL + // provides extra protection. + sdRef->ProcessReply = NULL; + dispatch_source_cancel(sdRef->disp_source); + dispatch_release(sdRef->disp_source); + sdRef->disp_source = NULL; + } + // if disp_queue is set, it means it used the DNSServiceSetDispatchQueue API. In that case, + // when the source was cancelled, the fd was closed in the handler. Currently the source + // is cancelled only when the mDNSResponder daemon dies + else if (!sdRef->disp_queue) dnssd_close(sdRef->sockfd); +#else + dnssd_close(sdRef->sockfd); +#endif + // Free DNSRecords added in DNSRegisterRecord if they have not + // been freed in DNSRemoveRecord + while (sdRef) + { + DNSServiceOp *p = sdRef; + sdRef = sdRef->next; + FreeDNSServiceOp(p); + } + } + } + +DNSServiceErrorType DNSSD_API DNSServiceGetProperty(const char *property, void *result, uint32_t *size) + { + char *ptr; + size_t len = strlen(property) + 1; + ipc_msg_hdr *hdr; + DNSServiceOp *tmp; + uint32_t actualsize; + + DNSServiceErrorType err = ConnectToServer(&tmp, 0, getproperty_request, NULL, NULL, NULL); + if (err) return err; + + hdr = create_hdr(getproperty_request, &len, &ptr, 0, tmp); + if (!hdr) { DNSServiceRefDeallocate(tmp); return kDNSServiceErr_NoMemory; } + + put_string(property, &ptr); + err = deliver_request(hdr, tmp); // Will free hdr for us + if (read_all(tmp->sockfd, (char*)&actualsize, (int)sizeof(actualsize)) < 0) + { DNSServiceRefDeallocate(tmp); return kDNSServiceErr_ServiceNotRunning; } + + actualsize = ntohl(actualsize); + if (read_all(tmp->sockfd, (char*)result, actualsize < *size ? actualsize : *size) < 0) + { DNSServiceRefDeallocate(tmp); return kDNSServiceErr_ServiceNotRunning; } + DNSServiceRefDeallocate(tmp); + + // Swap version result back to local process byte order + if (!strcmp(property, kDNSServiceProperty_DaemonVersion) && *size >= 4) + *(uint32_t*)result = ntohl(*(uint32_t*)result); + + *size = actualsize; + return kDNSServiceErr_NoError; + } + +static void handle_resolve_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *end) + { + char fullname[kDNSServiceMaxDomainName]; + char target[kDNSServiceMaxDomainName]; + uint16_t txtlen; + union { uint16_t s; u_char b[2]; } port; + unsigned char *txtrecord; + + get_string(&data, end, fullname, kDNSServiceMaxDomainName); + get_string(&data, end, target, kDNSServiceMaxDomainName); + if (!data || data + 2 > end) goto fail; + + port.b[0] = *data++; + port.b[1] = *data++; + txtlen = get_uint16(&data, end); + txtrecord = (unsigned char *)get_rdata(&data, end, txtlen); + + if (!data) goto fail; + ((DNSServiceResolveReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, fullname, target, port.s, txtlen, txtrecord, sdr->AppContext); + return; + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function +fail: + syslog(LOG_WARNING, "dnssd_clientstub handle_resolve_response: error reading result from daemon"); + } + +DNSServiceErrorType DNSSD_API DNSServiceResolve + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + const char *regtype, + const char *domain, + DNSServiceResolveReply callBack, + void *context + ) + { + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceErrorType err; + + if (!name || !regtype || !domain || !callBack) return kDNSServiceErr_BadParam; + + // Need a real InterfaceID for WakeOnResolve + if ((flags & kDNSServiceFlagsWakeOnResolve) != 0 && + ((interfaceIndex == kDNSServiceInterfaceIndexAny) || + (interfaceIndex == kDNSServiceInterfaceIndexLocalOnly) || + (interfaceIndex == kDNSServiceInterfaceIndexUnicast) || + (interfaceIndex == kDNSServiceInterfaceIndexP2P))) + { + return kDNSServiceErr_BadParam; + } + + err = ConnectToServer(sdRef, flags, resolve_request, handle_resolve_response, reinterpret_cast(callBack), context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + // Calculate total message length + len = sizeof(flags); + len += sizeof(interfaceIndex); + len += strlen(name) + 1; + len += strlen(regtype) + 1; + len += strlen(domain) + 1; + + hdr = create_hdr(resolve_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_string(name, &ptr); + put_string(regtype, &ptr); + put_string(domain, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; + } + +static void handle_query_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end) + { + uint32_t ttl; + char name[kDNSServiceMaxDomainName]; + uint16_t rrtype, rrclass, rdlen; + const char *rdata; + + get_string(&data, end, name, kDNSServiceMaxDomainName); + rrtype = get_uint16(&data, end); + rrclass = get_uint16(&data, end); + rdlen = get_uint16(&data, end); + rdata = get_rdata(&data, end, rdlen); + ttl = get_uint32(&data, end); + + if (!data) syslog(LOG_WARNING, "dnssd_clientstub handle_query_response: error reading result from daemon"); + else ((DNSServiceQueryRecordReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, name, rrtype, rrclass, rdlen, rdata, ttl, sdr->AppContext); + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function + } + +DNSServiceErrorType DNSSD_API DNSServiceQueryRecord + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + uint16_t rrtype, + uint16_t rrclass, + DNSServiceQueryRecordReply callBack, + void *context + ) + { + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceErrorType err = ConnectToServer(sdRef, flags, query_request, handle_query_response, reinterpret_cast(callBack), context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + if (!name) name = "\0"; + + // Calculate total message length + len = sizeof(flags); + len += sizeof(uint32_t); // interfaceIndex + len += strlen(name) + 1; + len += 2 * sizeof(uint16_t); // rrtype, rrclass + + hdr = create_hdr(query_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_string(name, &ptr); + put_uint16(rrtype, &ptr); + put_uint16(rrclass, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; + } + +static void handle_addrinfo_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end) + { + char hostname[kDNSServiceMaxDomainName]; + uint16_t rrtype, rdlen; + const char *rdata; + uint32_t ttl; + + get_string(&data, end, hostname, kDNSServiceMaxDomainName); + rrtype = get_uint16(&data, end); + get_uint16(&data, end); // rrclass + rdlen = get_uint16(&data, end); + rdata = get_rdata (&data, end, rdlen); + ttl = get_uint32(&data, end); + + // We only generate client callbacks for A and AAAA results (including NXDOMAIN results for + // those types, if the client has requested those with the kDNSServiceFlagsReturnIntermediates). + // Other result types, specifically CNAME referrals, are not communicated to the client, because + // the DNSServiceGetAddrInfoReply interface doesn't have any meaningful way to communiate CNAME referrals. + if (!data) syslog(LOG_WARNING, "dnssd_clientstub handle_addrinfo_response: error reading result from daemon"); + else if (rrtype == kDNSServiceType_A || rrtype == kDNSServiceType_AAAA) + { + struct sockaddr_in sa4; + struct sockaddr_in6 sa6; + const struct sockaddr *const sa = (rrtype == kDNSServiceType_A) ? (struct sockaddr*)&sa4 : (struct sockaddr*)&sa6; + if (rrtype == kDNSServiceType_A) + { + memset(&sa4, 0, sizeof(sa4)); + #ifndef NOT_HAVE_SA_LEN + sa4.sin_len = sizeof(struct sockaddr_in); + #endif + sa4.sin_family = AF_INET; + // sin_port = 0; + if (!cbh->cb_err) memcpy(&sa4.sin_addr, rdata, rdlen); + } + else + { + memset(&sa6, 0, sizeof(sa6)); + #ifndef NOT_HAVE_SA_LEN + sa6.sin6_len = sizeof(struct sockaddr_in6); + #endif + sa6.sin6_family = AF_INET6; + // sin6_port = 0; + // sin6_flowinfo = 0; + // sin6_scope_id = 0; + if (!cbh->cb_err) + { + memcpy(&sa6.sin6_addr, rdata, rdlen); + if (IN6_IS_ADDR_LINKLOCAL(&sa6.sin6_addr)) sa6.sin6_scope_id = cbh->cb_interface; + } + } + ((DNSServiceGetAddrInfoReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, hostname, sa, ttl, sdr->AppContext); + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function + } + } + +DNSServiceErrorType DNSSD_API DNSServiceGetAddrInfo + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + uint32_t protocol, + const char *hostname, + DNSServiceGetAddrInfoReply callBack, + void *context /* may be NULL */ + ) + { + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceErrorType err; + + if (!hostname) return kDNSServiceErr_BadParam; + + err = ConnectToServer(sdRef, flags, addrinfo_request, handle_addrinfo_response, reinterpret_cast(callBack), context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + // Calculate total message length + len = sizeof(flags); + len += sizeof(uint32_t); // interfaceIndex + len += sizeof(uint32_t); // protocol + len += strlen(hostname) + 1; + + hdr = create_hdr(addrinfo_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_uint32(protocol, &ptr); + put_string(hostname, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; + } + +static void handle_browse_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end) + { + char replyName[256], replyType[kDNSServiceMaxDomainName], replyDomain[kDNSServiceMaxDomainName]; + get_string(&data, end, replyName, 256); + get_string(&data, end, replyType, kDNSServiceMaxDomainName); + get_string(&data, end, replyDomain, kDNSServiceMaxDomainName); + if (!data) syslog(LOG_WARNING, "dnssd_clientstub handle_browse_response: error reading result from daemon"); + else ((DNSServiceBrowseReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, replyName, replyType, replyDomain, sdr->AppContext); + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function + } + +DNSServiceErrorType DNSSD_API DNSServiceBrowse + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *regtype, + const char *domain, + DNSServiceBrowseReply callBack, + void *context + ) + { + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceErrorType err = ConnectToServer(sdRef, flags, browse_request, handle_browse_response, reinterpret_cast(callBack), context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + if (!domain) domain = ""; + len = sizeof(flags); + len += sizeof(interfaceIndex); + len += strlen(regtype) + 1; + len += strlen(domain) + 1; + + hdr = create_hdr(browse_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_string(regtype, &ptr); + put_string(domain, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; + } + +DNSServiceErrorType DNSSD_API DNSServiceSetDefaultDomainForUser(DNSServiceFlags flags, const char *domain); +DNSServiceErrorType DNSSD_API DNSServiceSetDefaultDomainForUser(DNSServiceFlags flags, const char *domain) + { + DNSServiceOp *tmp; + char *ptr; + size_t len = sizeof(flags) + strlen(domain) + 1; + ipc_msg_hdr *hdr; + DNSServiceErrorType err = ConnectToServer(&tmp, 0, setdomain_request, NULL, NULL, NULL); + if (err) return err; + + hdr = create_hdr(setdomain_request, &len, &ptr, 0, tmp); + if (!hdr) { DNSServiceRefDeallocate(tmp); return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_string(domain, &ptr); + err = deliver_request(hdr, tmp); // Will free hdr for us + DNSServiceRefDeallocate(tmp); + return err; + } + +static void handle_regservice_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end) + { + char name[256], regtype[kDNSServiceMaxDomainName], domain[kDNSServiceMaxDomainName]; + get_string(&data, end, name, 256); + get_string(&data, end, regtype, kDNSServiceMaxDomainName); + get_string(&data, end, domain, kDNSServiceMaxDomainName); + if (!data) syslog(LOG_WARNING, "dnssd_clientstub handle_regservice_response: error reading result from daemon"); + else ((DNSServiceRegisterReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_err, name, regtype, domain, sdr->AppContext); + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function + } + +DNSServiceErrorType DNSSD_API DNSServiceRegister + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + const char *regtype, + const char *domain, + const char *host, + uint16_t PortInNetworkByteOrder, + uint16_t txtLen, + const void *txtRecord, + DNSServiceRegisterReply callBack, + void *context + ) + { + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceErrorType err; + union { uint16_t s; u_char b[2]; } port = { PortInNetworkByteOrder }; + + if (!name) name = ""; + if (!regtype) return kDNSServiceErr_BadParam; + if (!domain) domain = ""; + if (!host) host = ""; + if (!txtRecord) txtRecord = (void*)""; + + // No callback must have auto-rename + if (!callBack && (flags & kDNSServiceFlagsNoAutoRename)) return kDNSServiceErr_BadParam; + + err = ConnectToServer(sdRef, flags, reg_service_request, callBack ? handle_regservice_response : NULL, reinterpret_cast(callBack), context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + len = sizeof(DNSServiceFlags); + len += sizeof(uint32_t); // interfaceIndex + len += strlen(name) + strlen(regtype) + strlen(domain) + strlen(host) + 4; + len += 2 * sizeof(uint16_t); // port, txtLen + len += txtLen; + + hdr = create_hdr(reg_service_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + if (!callBack) hdr->ipc_flags |= IPC_FLAGS_NOREPLY; + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_string(name, &ptr); + put_string(regtype, &ptr); + put_string(domain, &ptr); + put_string(host, &ptr); + *ptr++ = port.b[0]; + *ptr++ = port.b[1]; + put_uint16(txtLen, &ptr); + put_rdata(txtLen, static_cast(txtRecord), &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; + } + +static void handle_enumeration_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end) + { + char domain[kDNSServiceMaxDomainName]; + get_string(&data, end, domain, kDNSServiceMaxDomainName); + if (!data) syslog(LOG_WARNING, "dnssd_clientstub handle_enumeration_response: error reading result from daemon"); + else ((DNSServiceDomainEnumReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, domain, sdr->AppContext); + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function + } + +DNSServiceErrorType DNSSD_API DNSServiceEnumerateDomains + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceDomainEnumReply callBack, + void *context + ) + { + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceErrorType err; + + int f1 = (flags & kDNSServiceFlagsBrowseDomains) != 0; + int f2 = (flags & kDNSServiceFlagsRegistrationDomains) != 0; + if (f1 + f2 != 1) return kDNSServiceErr_BadParam; + + err = ConnectToServer(sdRef, flags, enumeration_request, handle_enumeration_response, reinterpret_cast(callBack), context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + len = sizeof(DNSServiceFlags); + len += sizeof(uint32_t); + + hdr = create_hdr(enumeration_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; + } + +static void ConnectionResponse(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *const data, const char *const end) + { + DNSRecordRef rref = static_cast(cbh->ipc_hdr.client_context.context); + (void)data; // Unused + + //printf("ConnectionResponse got %d\n", cbh->ipc_hdr.op); + if (cbh->ipc_hdr.op != reg_record_reply_op) + { + // When using kDNSServiceFlagsShareConnection, need to search the list of associated DNSServiceOps + // to find the one this response is intended for, and then call through to its ProcessReply handler. + // We start with our first subordinate DNSServiceRef -- don't want to accidentally match the parent DNSServiceRef. + DNSServiceOp *op = sdr->next; + while (op && (op->uid.u32[0] != cbh->ipc_hdr.client_context.u32[0] || op->uid.u32[1] != cbh->ipc_hdr.client_context.u32[1])) + op = op->next; + // Note: We may sometimes not find a matching DNSServiceOp, in the case where the client has + // cancelled the subordinate DNSServiceOp, but there are still messages in the pipeline from the daemon + if (op && op->ProcessReply) op->ProcessReply(op, cbh, data, end); + // WARNING: Don't touch op or sdr after this -- client may have called DNSServiceRefDeallocate + return; + } + + if (sdr->op == connection_request) + rref->AppCallback(rref->sdr, rref, cbh->cb_flags, cbh->cb_err, rref->AppContext); + else + { + syslog(LOG_WARNING, "dnssd_clientstub ConnectionResponse: sdr->op != connection_request"); + rref->AppCallback(rref->sdr, rref, 0, kDNSServiceErr_Unknown, rref->AppContext); + } + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function + } + +DNSServiceErrorType DNSSD_API DNSServiceCreateConnection(DNSServiceRef *sdRef) + { + char *ptr; + size_t len = 0; + ipc_msg_hdr *hdr; + DNSServiceErrorType err = ConnectToServer(sdRef, 0, connection_request, ConnectionResponse, NULL, NULL); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + hdr = create_hdr(connection_request, &len, &ptr, 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; + } + +DNSServiceErrorType DNSSD_API DNSServiceRegisterRecord + ( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + DNSServiceRegisterRecordReply callBack, + void *context + ) + { + char *ptr; + size_t len; + ipc_msg_hdr *hdr = NULL; + DNSRecordRef rref = NULL; + DNSRecord **p; + int f1 = (flags & kDNSServiceFlagsShared) != 0; + int f2 = (flags & kDNSServiceFlagsUnique) != 0; + if (f1 + f2 != 1) return kDNSServiceErr_BadParam; + + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRegisterRecord called with NULL DNSServiceRef"); return kDNSServiceErr_BadParam; } + + if (!DNSServiceRefValid(sdRef)) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRegisterRecord called with invalid DNSServiceRef %p %08X %08X", sdRef, sdRef->sockfd, sdRef->validator); + return kDNSServiceErr_BadReference; + } + + if (sdRef->op != connection_request) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRegisterRecord called with non-DNSServiceCreateConnection DNSServiceRef %p %d", sdRef, sdRef->op); + return kDNSServiceErr_BadReference; + } + + *RecordRef = NULL; + + len = sizeof(DNSServiceFlags); + len += 2 * sizeof(uint32_t); // interfaceIndex, ttl + len += 3 * sizeof(uint16_t); // rrtype, rrclass, rdlen + len += strlen(fullname) + 1; + len += rdlen; + + hdr = create_hdr(reg_record_request, &len, &ptr, 1, sdRef); + if (!hdr) return kDNSServiceErr_NoMemory; + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_string(fullname, &ptr); + put_uint16(rrtype, &ptr); + put_uint16(rrclass, &ptr); + put_uint16(rdlen, &ptr); + put_rdata(rdlen, static_cast(rdata), &ptr); + put_uint32(ttl, &ptr); + + rref = static_cast(malloc(sizeof(DNSRecord))); + if (!rref) { free(hdr); return kDNSServiceErr_NoMemory; } + rref->AppContext = context; + rref->AppCallback = callBack; + rref->record_index = sdRef->max_index++; + rref->sdr = sdRef; + rref->recnext = NULL; + *RecordRef = rref; + hdr->client_context.context = rref; + hdr->reg_index = rref->record_index; + + p = &(sdRef)->rec; + while (*p) p = &(*p)->recnext; + *p = rref; + + return deliver_request(hdr, sdRef); // Will free hdr for us + } + +// sdRef returned by DNSServiceRegister() +DNSServiceErrorType DNSSD_API DNSServiceAddRecord + ( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint16_t rrtype, + uint16_t rdlen, + const void *rdata, + uint32_t ttl + ) + { + ipc_msg_hdr *hdr; + size_t len = 0; + char *ptr; + DNSRecordRef rref; + DNSRecord **p; + + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceAddRecord called with NULL DNSServiceRef"); return kDNSServiceErr_BadParam; } + if (!RecordRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceAddRecord called with NULL DNSRecordRef pointer"); return kDNSServiceErr_BadParam; } + if (sdRef->op != reg_service_request) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceAddRecord called with non-DNSServiceRegister DNSServiceRef %p %d", sdRef, sdRef->op); + return kDNSServiceErr_BadReference; + } + + if (!DNSServiceRefValid(sdRef)) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceAddRecord called with invalid DNSServiceRef %p %08X %08X", sdRef, sdRef->sockfd, sdRef->validator); + return kDNSServiceErr_BadReference; + } + + *RecordRef = NULL; + + len += 2 * sizeof(uint16_t); // rrtype, rdlen + len += rdlen; + len += sizeof(uint32_t); + len += sizeof(DNSServiceFlags); + + hdr = create_hdr(add_record_request, &len, &ptr, 1, sdRef); + if (!hdr) return kDNSServiceErr_NoMemory; + put_flags(flags, &ptr); + put_uint16(rrtype, &ptr); + put_uint16(rdlen, &ptr); + put_rdata(rdlen, static_cast(rdata), &ptr); + put_uint32(ttl, &ptr); + + rref = static_cast(malloc(sizeof(DNSRecord))); + if (!rref) { free(hdr); return kDNSServiceErr_NoMemory; } + rref->AppContext = NULL; + rref->AppCallback = NULL; + rref->record_index = sdRef->max_index++; + rref->sdr = sdRef; + rref->recnext = NULL; + *RecordRef = rref; + hdr->reg_index = rref->record_index; + + p = &(sdRef)->rec; + while (*p) p = &(*p)->recnext; + *p = rref; + + return deliver_request(hdr, sdRef); // Will free hdr for us + } + +// DNSRecordRef returned by DNSServiceRegisterRecord or DNSServiceAddRecord +DNSServiceErrorType DNSSD_API DNSServiceUpdateRecord + ( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags, + uint16_t rdlen, + const void *rdata, + uint32_t ttl + ) + { + ipc_msg_hdr *hdr; + size_t len = 0; + char *ptr; + + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceUpdateRecord called with NULL DNSServiceRef"); return kDNSServiceErr_BadParam; } + + if (!DNSServiceRefValid(sdRef)) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceUpdateRecord called with invalid DNSServiceRef %p %08X %08X", sdRef, sdRef->sockfd, sdRef->validator); + return kDNSServiceErr_BadReference; + } + + // Note: RecordRef is allowed to be NULL + + len += sizeof(uint16_t); + len += rdlen; + len += sizeof(uint32_t); + len += sizeof(DNSServiceFlags); + + hdr = create_hdr(update_record_request, &len, &ptr, 1, sdRef); + if (!hdr) return kDNSServiceErr_NoMemory; + hdr->reg_index = RecordRef ? RecordRef->record_index : TXT_RECORD_INDEX; + put_flags(flags, &ptr); + put_uint16(rdlen, &ptr); + put_rdata(rdlen, static_cast(rdata), &ptr); + put_uint32(ttl, &ptr); + return deliver_request(hdr, sdRef); // Will free hdr for us + } + +DNSServiceErrorType DNSSD_API DNSServiceRemoveRecord + ( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags + ) + { + ipc_msg_hdr *hdr; + size_t len = 0; + char *ptr; + DNSServiceErrorType err; + + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRemoveRecord called with NULL DNSServiceRef"); return kDNSServiceErr_BadParam; } + if (!RecordRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRemoveRecord called with NULL DNSRecordRef"); return kDNSServiceErr_BadParam; } + if (!sdRef->max_index) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRemoveRecord called with bad DNSServiceRef"); return kDNSServiceErr_BadReference; } + + if (!DNSServiceRefValid(sdRef)) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRemoveRecord called with invalid DNSServiceRef %p %08X %08X", sdRef, sdRef->sockfd, sdRef->validator); + return kDNSServiceErr_BadReference; + } + + len += sizeof(flags); + hdr = create_hdr(remove_record_request, &len, &ptr, 1, sdRef); + if (!hdr) return kDNSServiceErr_NoMemory; + hdr->reg_index = RecordRef->record_index; + put_flags(flags, &ptr); + err = deliver_request(hdr, sdRef); // Will free hdr for us + if (!err) + { + // This RecordRef could have been allocated in DNSServiceRegisterRecord or DNSServiceAddRecord. + // If so, delink from the list before freeing + DNSRecord **p = &sdRef->rec; + while (*p && *p != RecordRef) p = &(*p)->recnext; + if (*p) *p = RecordRef->recnext; + free(RecordRef); + } + return err; + } + +DNSServiceErrorType DNSSD_API DNSServiceReconfirmRecord + ( + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata + ) + { + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceOp *tmp; + + DNSServiceErrorType err = ConnectToServer(&tmp, flags, reconfirm_record_request, NULL, NULL, NULL); + if (err) return err; + + len = sizeof(DNSServiceFlags); + len += sizeof(uint32_t); + len += strlen(fullname) + 1; + len += 3 * sizeof(uint16_t); + len += rdlen; + hdr = create_hdr(reconfirm_record_request, &len, &ptr, 0, tmp); + if (!hdr) { DNSServiceRefDeallocate(tmp); return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_string(fullname, &ptr); + put_uint16(rrtype, &ptr); + put_uint16(rrclass, &ptr); + put_uint16(rdlen, &ptr); + put_rdata(rdlen, static_cast(rdata), &ptr); + + err = deliver_request(hdr, tmp); // Will free hdr for us + DNSServiceRefDeallocate(tmp); + return err; + } + +static void handle_port_mapping_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end) + { + union { uint32_t l; u_char b[4]; } addr; + uint8_t protocol; + union { uint16_t s; u_char b[2]; } internalPort; + union { uint16_t s; u_char b[2]; } externalPort; + uint32_t ttl; + + if (!data || data + 13 > end) goto fail; + + addr .b[0] = *data++; + addr .b[1] = *data++; + addr .b[2] = *data++; + addr .b[3] = *data++; + protocol = *data++; + internalPort.b[0] = *data++; + internalPort.b[1] = *data++; + externalPort.b[0] = *data++; + externalPort.b[1] = *data++; + ttl = get_uint32(&data, end); + if (!data) goto fail; + + ((DNSServiceNATPortMappingReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, addr.l, protocol, internalPort.s, externalPort.s, ttl, sdr->AppContext); + return; + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function + +fail: + syslog(LOG_WARNING, "dnssd_clientstub handle_port_mapping_response: error reading result from daemon"); + } + +DNSServiceErrorType DNSSD_API DNSServiceNATPortMappingCreate + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + uint32_t protocol, /* TCP and/or UDP */ + uint16_t internalPortInNetworkByteOrder, + uint16_t externalPortInNetworkByteOrder, + uint32_t ttl, /* time to live in seconds */ + DNSServiceNATPortMappingReply callBack, + void *context /* may be NULL */ + ) + { + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + union { uint16_t s; u_char b[2]; } internalPort = { internalPortInNetworkByteOrder }; + union { uint16_t s; u_char b[2]; } externalPort = { externalPortInNetworkByteOrder }; + + DNSServiceErrorType err = ConnectToServer(sdRef, flags, port_mapping_request, handle_port_mapping_response, reinterpret_cast(callBack), context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + len = sizeof(flags); + len += sizeof(interfaceIndex); + len += sizeof(protocol); + len += sizeof(internalPort); + len += sizeof(externalPort); + len += sizeof(ttl); + + hdr = create_hdr(port_mapping_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_uint32(protocol, &ptr); + *ptr++ = internalPort.b[0]; + *ptr++ = internalPort.b[1]; + *ptr++ = externalPort.b[0]; + *ptr++ = externalPort.b[1]; + put_uint32(ttl, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; + } + +#if _DNS_SD_LIBDISPATCH +DNSServiceErrorType DNSSD_API DNSServiceSetDispatchQueue + ( + DNSServiceRef service, + dispatch_queue_t queue + ) + { + int dnssd_fd = DNSServiceRefSockFD(service); + if (dnssd_fd == dnssd_InvalidSocket) return kDNSServiceErr_BadParam; + if (!queue) + { + syslog(LOG_WARNING, "dnssd_clientstub: DNSServiceSetDispatchQueue dispatch queue NULL"); + return kDNSServiceErr_BadParam; + } + if (service->disp_queue) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceSetDispatchQueue dispatch queue set already"); + return kDNSServiceErr_BadParam; + } + if (service->disp_source) + { + syslog(LOG_WARNING, "DNSServiceSetDispatchQueue dispatch source set already"); + return kDNSServiceErr_BadParam; + } + service->disp_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, dnssd_fd, 0, queue); + if (!service->disp_source) + { + syslog(LOG_WARNING, "DNSServiceSetDispatchQueue dispatch_source_create failed"); + return kDNSServiceErr_NoMemory; + } + service->disp_queue = queue; + dispatch_source_set_event_handler(service->disp_source, ^{DNSServiceProcessResult(service);}); + dispatch_source_set_cancel_handler(service->disp_source, ^{dnssd_close(dnssd_fd);}); + dispatch_resume(service->disp_source); + return kDNSServiceErr_NoError; + } +#endif // _DNS_SD_LIBDISPATCH +}} diff --git a/src/libs/zeroconf/embed/dnssd_ipc.c b/src/libs/zeroconf/embed/dnssd_ipc.c new file mode 100644 index 00000000000..ec3958395a6 --- /dev/null +++ b/src/libs/zeroconf/embed/dnssd_ipc.c @@ -0,0 +1,163 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003-2004, Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "dnssd_ipc.h" + +namespace ZeroConf { namespace embeddedLib { +#if defined(_WIN32) + +char *win32_strerror(int inErrorCode) + { + static char buffer[1024]; + DWORD n; + memset(buffer, 0, sizeof(buffer)); + n = FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + (DWORD) inErrorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buffer, + sizeof(buffer), + NULL); + if (n > 0) + { + // Remove any trailing CR's or LF's since some messages have them. + while ((n > 0) && isspace(((unsigned char *) buffer)[n - 1])) + buffer[--n] = '\0'; + } + return buffer; + } + +#endif + +void put_uint32(const uint32_t l, char **ptr) + { + (*ptr)[0] = (char)((l >> 24) & 0xFF); + (*ptr)[1] = (char)((l >> 16) & 0xFF); + (*ptr)[2] = (char)((l >> 8) & 0xFF); + (*ptr)[3] = (char)((l ) & 0xFF); + *ptr += sizeof(uint32_t); + } + +uint32_t get_uint32(const char **ptr, const char *end) + { + if (!*ptr || *ptr + sizeof(uint32_t) > end) + { + *ptr = NULL; + return(0); + } + else + { + uint8_t *p = (uint8_t*) *ptr; + *ptr += sizeof(uint32_t); + return((uint32_t) ((uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | p[3])); + } + } + +void put_uint16(uint16_t s, char **ptr) + { + (*ptr)[0] = (char)((s >> 8) & 0xFF); + (*ptr)[1] = (char)((s ) & 0xFF); + *ptr += sizeof(uint16_t); + } + +uint16_t get_uint16(const char **ptr, const char *end) + { + if (!*ptr || *ptr + sizeof(uint16_t) > end) + { + *ptr = NULL; + return(0); + } + else + { + uint8_t *p = (uint8_t*) *ptr; + *ptr += sizeof(uint16_t); + return((uint16_t) ((uint16_t)p[0] << 8 | p[1])); + } + } + +int put_string(const char *str, char **ptr) + { + if (!str) str = ""; + strcpy(*ptr, str); + *ptr += strlen(str) + 1; + return 0; + } + +int get_string(const char **ptr, const char *const end, char *buffer, int buflen) + { + if (!*ptr) + { + *buffer = 0; + return(-1); + } + else + { + char *lim = buffer + buflen; // Calculate limit + while (*ptr < end && buffer < lim) + { + char c = *buffer++ = *(*ptr)++; + if (c == 0) return(0); // Success + } + if (buffer == lim) buffer--; + *buffer = 0; // Failed, so terminate string, + *ptr = NULL; // clear pointer, + return(-1); // and return failure indication + } + } + +void put_rdata(const int rdlen, const unsigned char *rdata, char **ptr) + { + memcpy(*ptr, rdata, rdlen); + *ptr += rdlen; + } + +const char *get_rdata(const char **ptr, const char *end, int rdlen) + { + if (!*ptr || *ptr + rdlen > end) + { + *ptr = NULL; + return(0); + } + else + { + const char *rd = *ptr; + *ptr += rdlen; + return rd; + } + } + +void ConvertHeaderBytes(ipc_msg_hdr *hdr) + { + hdr->version = htonl(hdr->version); + hdr->datalen = htonl(hdr->datalen); + hdr->ipc_flags = htonl(hdr->ipc_flags); + hdr->op = htonl(hdr->op ); + hdr->reg_index = htonl(hdr->reg_index); + } + }} diff --git a/src/libs/zeroconf/embed/dnssd_ipc.h b/src/libs/zeroconf/embed/dnssd_ipc.h new file mode 100644 index 00000000000..f8e09fc77d7 --- /dev/null +++ b/src/libs/zeroconf/embed/dnssd_ipc.h @@ -0,0 +1,219 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003-2004, Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DNSSD_IPC_H +#define DNSSD_IPC_H + +#include "dns_sd_types.h" + +// +// Common cross platform services +// +#if defined(WIN32) +# include +# define dnssd_InvalidSocket INVALID_SOCKET +# define dnssd_SocketValid(s) ((s) != INVALID_SOCKET) +# define dnssd_EWOULDBLOCK WSAEWOULDBLOCK +# define dnssd_EINTR WSAEINTR +# define dnssd_ECONNRESET WSAECONNRESET +# define dnssd_sock_t SOCKET +# define dnssd_socklen_t int +# define dnssd_close(sock) closesocket(sock) +# define dnssd_errno WSAGetLastError() +# define dnssd_strerror(X) win32_strerror(X) +# define ssize_t int +# define getpid _getpid +# define unlink _unlink +extern char *win32_strerror(int inErrorCode); +# define USE_TCP_LOOPBACK +#else +# include +# include +# include +# include +# include +# include +# include +# include +# include +# define dnssd_InvalidSocket -1 +# define dnssd_SocketValid(s) ((s) >= 0) +# define dnssd_EWOULDBLOCK EWOULDBLOCK +# define dnssd_EINTR EINTR +# define dnssd_ECONNRESET ECONNRESET +# define dnssd_EPIPE EPIPE +# define dnssd_sock_t int +# define dnssd_socklen_t unsigned int +# define dnssd_close(sock) close(sock) +# define dnssd_errno errno +# define dnssd_strerror(X) strerror(X) +#endif + +#if defined(USE_TCP_LOOPBACK) +# define AF_DNSSD AF_INET +# define MDNS_TCP_SERVERADDR "127.0.0.1" +# define MDNS_TCP_SERVERPORT 5354 +# define LISTENQ 5 +# define dnssd_sockaddr_t struct sockaddr_in +#else +# define AF_DNSSD AF_LOCAL +# ifndef MDNS_UDS_SERVERPATH +# define MDNS_UDS_SERVERPATH "/var/run/mdnsd" +# endif +# define LISTENQ 100 + // longest legal control path length +# define MAX_CTLPATH 256 +# define dnssd_sockaddr_t struct sockaddr_un +#endif + +// Compatibility workaround +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif + +// General UDS constants +#define TXT_RECORD_INDEX ((uint32_t)(-1)) // record index for default text record + +// IPC data encoding constants and types +#define VERSION 1 +#define IPC_FLAGS_NOREPLY 1 // set flag if no asynchronous replies are to be sent to client + +// Structure packing macro. If we're not using GNUC, it's not fatal. Most compilers naturally pack the on-the-wire +// structures correctly anyway, so a plain "struct" is usually fine. In the event that structures are not packed +// correctly, our compile-time assertion checks will catch it and prevent inadvertent generation of non-working code. +#ifndef packedstruct + #if ((__GNUC__ > 2) || ((__GNUC__ == 2) && (__GNUC_MINOR__ >= 9))) + #define packedstruct struct __attribute__((__packed__)) + #define packedunion union __attribute__((__packed__)) + #else + #define packedstruct struct + #define packedunion union + #endif +#endif + +namespace ZeroConf { namespace embeddedLib { +typedef enum + { + request_op_none = 0, // No request yet received on this connection + connection_request = 1, // connected socket via DNSServiceConnect() + reg_record_request, // reg/remove record only valid for connected sockets + remove_record_request, + enumeration_request, + reg_service_request, + browse_request, + resolve_request, + query_request, + reconfirm_record_request, + add_record_request, + update_record_request, + setdomain_request, // Up to here is in Tiger and B4W 1.0.3 + getproperty_request, // New in B4W 1.0.4 + port_mapping_request, // New in Leopard and B4W 2.0 + addrinfo_request, + send_bpf, // New in SL + + cancel_request = 63 + } request_op_t; + +typedef enum + { + enumeration_reply_op = 64, + reg_service_reply_op, + browse_reply_op, + resolve_reply_op, + query_reply_op, + reg_record_reply_op, // Up to here is in Tiger and B4W 1.0.3 + getproperty_reply_op, // New in B4W 1.0.4 + port_mapping_reply_op, // New in Leopard and B4W 2.0 + addrinfo_reply_op + } reply_op_t; +}} +extern "C" { +#if defined(_WIN64) +# pragma pack(4) +#endif + +// Define context object big enough to hold a 64-bit pointer, +// to accomodate 64-bit clients communicating with 32-bit daemon. +// There's no reason for the daemon to ever be a 64-bit process, but its clients might be +typedef packedunion + { + void *context; + uint32_t u32[2]; + } client_context_t; + +typedef packedstruct + { + uint32_t version; + uint32_t datalen; + uint32_t ipc_flags; + uint32_t op; // request_op_t or reply_op_t + client_context_t client_context; // context passed from client, returned by server in corresponding reply + uint32_t reg_index; // identifier for a record registered via DNSServiceRegisterRecord() on a + // socket connected by DNSServiceCreateConnection(). Must be unique in the scope of the connection, such that and + // index/socket pair uniquely identifies a record. (Used to select records for removal by DNSServiceRemoveRecord()) + } ipc_msg_hdr; + +// routines to write to and extract data from message buffers. +// caller responsible for bounds checking. +// ptr is the address of the pointer to the start of the field. +// it is advanced to point to the next field, or the end of the message +} + +namespace ZeroConf { namespace embeddedLib { + +void put_uint32(const uint32_t l, char **ptr); +uint32_t get_uint32(const char **ptr, const char *end); + +void put_uint16(uint16_t s, char **ptr); +uint16_t get_uint16(const char **ptr, const char *end); + +#define put_flags put_uint32 +#define get_flags get_uint32 + +#define put_error_code put_uint32 +#define get_error_code get_uint32 + +int put_string(const char *str, char **ptr); +int get_string(const char **ptr, const char *const end, char *buffer, int buflen); + +void put_rdata(const int rdlen, const unsigned char *rdata, char **ptr); +const char *get_rdata(const char **ptr, const char *end, int rdlen); // return value is rdata pointed to by *ptr - + // rdata is not copied from buffer. + +void ConvertHeaderBytes(ipc_msg_hdr *hdr); + +struct CompileTimeAssertionChecks_dnssd_ipc + { + // Check that the compiler generated our on-the-wire packet format structure definitions + // properly packed, without adding padding bytes to align fields on 32-bit or 64-bit boundaries. + char assert0[(sizeof(client_context_t) == 8) ? 1 : -1]; + char assert1[(sizeof(ipc_msg_hdr) == 28) ? 1 : -1]; + }; + }} +#endif // DNSSD_IPC_H diff --git a/src/libs/zeroconf/embeddedLib.cpp b/src/libs/zeroconf/embeddedLib.cpp new file mode 100644 index 00000000000..25eb1be7c6d --- /dev/null +++ b/src/libs/zeroconf/embeddedLib.cpp @@ -0,0 +1,193 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifdef Q_OS_LINUX +#define EMBEDDED_LIB +#endif + +#ifdef Q_OS_WIN +# include +#endif +#include "servicebrowser_p.h" + +#include +#include + +#ifdef EMBEDDED_LIB +#include "embed/dnssd_ipc.c" +#include "embed/dnssd_clientlib.c" +#include "embed/dnssd_clientstub.c" +#ifdef Q_OS_WIN +#include "embed/DebugServices.c" +#endif + +namespace ZeroConf { +namespace Internal { +// represents a zero conf library exposing the dns-sd interface +class EmbeddedZConfLib : public ZConfLib{ +public: + QString daemonPath; + + EmbeddedZConfLib(const QString &daemonPath, ZConfLib *fallBack = 0) : ZConfLib(fallBack), daemonPath(daemonPath) + { } + + virtual ~EmbeddedZConfLib() { + } + + virtual QString name(){ + return QString::fromUtf8("EmbeddedZeroConfLib@%1").arg(size_t(this),0,16); + } + + // virtual bool tryStartDaemon(); + + virtual void refDeallocate(DNSServiceRef sdRef){ + embeddedLib::DNSServiceRefDeallocate(sdRef); + } + + virtual DNSServiceErrorType resolve(DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + const char *regtype, + const char *domain, + DNSServiceResolveReply callBack, + void *context) + { + return embeddedLib::DNSServiceResolve(sdRef, flags, interfaceIndex, name, regtype, domain, callBack, context); + } + + virtual DNSServiceErrorType queryRecord(DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + DNSServiceQueryRecordReply callBack, + void *context) + { + return embeddedLib::DNSServiceQueryRecord(sdRef, flags, interfaceIndex, fullname, + rrtype, rrclass, callBack, context); + } + + virtual DNSServiceErrorType getAddrInfo(DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceProtocol protocol, + const char *hostname, + DNSServiceGetAddrInfoReply callBack, + void *context) + { + return embeddedLib::DNSServiceGetAddrInfo(sdRef, flags, interfaceIndex, protocol, + hostname, callBack, context); + } + + virtual uint16_t txtRecordGetCount(uint16_t txtLen, + const void *txtRecord) + { + return embeddedLib::TXTRecordGetCount(txtLen, txtRecord); + } + + virtual DNSServiceErrorType txtRecordGetItemAtIndex(uint16_t txtLen, + const void *txtRecord, + uint16_t itemIndex, + uint16_t keyBufLen, + char *key, + uint8_t *valueLen, + const void **value) + { + return embeddedLib::TXTRecordGetItemAtIndex(txtLen, txtRecord, itemIndex, keyBufLen, + key, valueLen, value); + } + + virtual DNSServiceErrorType reconfirmRecord(DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata) + { + return embeddedLib::DNSServiceReconfirmRecord(flags, interfaceIndex, fullname, rrtype, + rrclass, rdlen, rdata); + } + + virtual DNSServiceErrorType browse(DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *regtype, + const char *domain, /* may be NULL */ + DNSServiceBrowseReply callBack, + void *context /* may be NULL */ + ) + { + return embeddedLib::DNSServiceBrowse(sdRef, flags, interfaceIndex, regtype, domain, callBack, context); + } + + virtual DNSServiceErrorType getProperty(const char *property, /* Requested property (i.e. kDNSServiceProperty_DaemonVersion) */ + void *result, /* Pointer to place to store result */ + uint32_t *size /* size of result location */ + ) + { + return embeddedLib::DNSServiceGetProperty(property, result, size); + } + + virtual DNSServiceErrorType processResult(DNSServiceRef sdRef){ + return embeddedLib::DNSServiceProcessResult(sdRef); + } + + virtual DNSServiceErrorType createConnection(DNSServiceRef *sdRef){ + return embeddedLib::DNSServiceCreateConnection(sdRef); + } + + virtual int refSockFD(DNSServiceRef sdRef){ + return embeddedLib::DNSServiceRefSockFD(sdRef); + } +}; + +ZConfLib *ZConfLib::createEmbeddedLib(const QString &daemonPath, ZConfLib *fallback){ + return new EmbeddedZConfLib(daemonPath, fallback); +} +} // namespace Internal +} // namespace ZeroConf + +#else // no embedded lib + +namespace ZeroConf { +namespace Internal { + +ZConfLib *ZConfLib::createEmbeddedLib(const QString &, ZConfLib * fallback){ + return fallback; +} + +} // namespace Internal +} // namespace ZeroConf +#endif diff --git a/src/libs/zeroconf/mdnsderived.cpp b/src/libs/zeroconf/mdnsderived.cpp new file mode 100644 index 00000000000..985f73d6c59 --- /dev/null +++ b/src/libs/zeroconf/mdnsderived.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2004, Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "mdnsderived.h" +#include "cstddef" +#include "cstring" +#ifdef _WIN32 +#define strncasecmp _strnicmp +#endif + +namespace ZeroConf { +namespace Internal { + +// DomainEndsInDot returns 1 if name ends with a dot, 0 otherwise +// (DNSServiceConstructFullName depends this returning 1 for true, rather than any non-zero value meaning true) +static int DomainEndsInDot(const char *dom) +{ + while (dom[0] && dom[1]) { + if (dom[0] == '\\') { // advance past escaped byte sequence + if ('0' <= dom[1] && dom[1] <= '9' && + '0' <= dom[2] && dom[2] <= '9' && + '0' <= dom[3] && dom[3] <= '9') + { + dom += 4; // If "\ddd" then skip four + } else { + dom += 2; // else if "\x" then skip two + } + } else { + dom++; // else goto next character + } + } + return (dom[0] == '.'); +} + +// Note: Need to make sure we don't write more than kDNSServiceMaxDomainName (1009) bytes to fullName +// In earlier builds this constant was defined to be 1005, so to avoid buffer overruns on clients +// compiled with that constant we'll actually limit the output to 1005 bytes. +DNSServiceErrorType myDNSServiceConstructFullName(char *const fullName, + const char *const service, // May be NULL + const char *const regtype, + const char *const domain) +{ + const size_t len = !regtype ? 0 : strlen(regtype) - DomainEndsInDot(regtype); + char *fn = fullName; + char *const lim = fullName + 1005; + const char *s = service; + const char *r = regtype; + const char *d = domain; + + // regtype must be at least "x._udp" or "x._tcp" + if (len < 6 || !domain || !domain[0]) return kDNSServiceErr_BadParam; + if (strncasecmp((regtype + len - 4), "_tcp", 4) && strncasecmp((regtype + len - 4), "_udp", 4)) return kDNSServiceErr_BadParam; + + if (service && *service) + { + while (*s) + { + unsigned char c = *s++; // Needs to be unsigned, or values like 0xFF will be interpreted as < 32 + if (c <= ' ') // Escape non-printable characters + { + if (fn + 4 >= lim) goto fail; + *fn++ = '\\'; + *fn++ = '0' + (c / 100); + *fn++ = '0' + (c / 10) % 10; + c = '0' + (c ) % 10; + } + else if (c == '.' || (c == '\\')) // Escape dot and backslash literals + { + if (fn + 2 >= lim) goto fail; + *fn++ = '\\'; + } + else + if (fn + 1 >= lim) goto fail; + *fn++ = (char)c; + } + *fn++ = '.'; + } + + while (*r) if (fn + 1 >= lim) goto fail; else *fn++ = *r++; + if (!DomainEndsInDot(regtype)) { if (fn + 1 >= lim) goto fail; else *fn++ = '.'; } + + while (*d) if (fn + 1 >= lim) goto fail; else *fn++ = *d++; + if (!DomainEndsInDot(domain)) { if (fn + 1 >= lim) goto fail; else *fn++ = '.'; } + + *fn = '\0'; + return kDNSServiceErr_NoError; + +fail: + *fn = '\0'; + return kDNSServiceErr_BadParam; +} + +} // namespace ZeroConf +} // namespace Internal diff --git a/src/libs/zeroconf/mdnsderived.h b/src/libs/zeroconf/mdnsderived.h new file mode 100644 index 00000000000..1068fa13d98 --- /dev/null +++ b/src/libs/zeroconf/mdnsderived.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2004, Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef MDNSDERIVED_H +#define MDNSDERIVED_H + +#include "dns_sd_types.h" + +namespace ZeroConf { +namespace Internal { + +DNSServiceErrorType myDNSServiceConstructFullName(char *const fullName, + const char *const service, + const char *const regtype, + const char *const domain); + +} +} +#endif // MDNSDERIVED_H diff --git a/src/libs/zeroconf/nativeLib.cpp b/src/libs/zeroconf/nativeLib.cpp new file mode 100644 index 00000000000..5e32f01636d --- /dev/null +++ b/src/libs/zeroconf/nativeLib.cpp @@ -0,0 +1,301 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "servicebrowser.h" +#include "servicebrowser_p.h" + +#include +#include +#include +#include + +#ifndef NO_NATIVE_LIB + +#ifdef Q_OS_MACX +#define ZCONF_STATIC_LINKING +#endif + +#ifdef ZCONF_STATIC_LINKING +extern "C" { +#include "dns_sd_funct.h" +} +#endif + +#ifdef Q_OS_UNIX +#include +#include +#include +#endif + +namespace ZeroConf { +namespace Internal { + +extern "C" { +typedef void (DNSSD_API *RefDeallocatePtr)(DNSServiceRef sdRef); +typedef DNSServiceErrorType (DNSSD_API *ResolvePtr)(DNSServiceRef *sdRef, DNSServiceFlags flags, + uint32_t interfaceIndex, const char *name, + const char *regtype, const char *domain, + DNSServiceResolveReply callBack, void *context); +typedef DNSServiceErrorType (DNSSD_API *QueryRecordPtr)(DNSServiceRef *sdRef, DNSServiceFlags flags, + uint32_t interfaceIndex, const char *fullname, + uint16_t rrtype, uint16_t rrclass, + DNSServiceQueryRecordReply callBack, void *context); +typedef DNSServiceErrorType (DNSSD_API *GetAddrInfoPtr)(DNSServiceRef *sdRef, DNSServiceFlags flags, + uint32_t interfaceIndex, DNSServiceProtocol protocol, + const char *hostname, DNSServiceGetAddrInfoReply callBack, + void *context); +typedef uint16_t (DNSSD_API *TxtRecordGetCountPtr)(uint16_t txtLen, const void *txtRecord); +typedef DNSServiceErrorType (DNSSD_API *TxtRecordGetItemAtIndexPtr)(uint16_t txtLen, const void *txtRecord, + uint16_t itemIndex, uint16_t keyBufLen, + char *key, uint8_t *valueLen, const void **value); +typedef DNSServiceErrorType (DNSSD_API *ReconfirmRecordPtr)(DNSServiceFlags flags, uint32_t interfaceIndex, + const char *fullname, uint16_t rrtype, + uint16_t rrclass, uint16_t rdlen, const void *rdata); +typedef DNSServiceErrorType (DNSSD_API *BrowsePtr)(DNSServiceRef *sdRef, DNSServiceFlags flags, + uint32_t interfaceIndex, const char *regtype, const char *domain, + DNSServiceBrowseReply callBack, void *context); +typedef DNSServiceErrorType (DNSSD_API *GetPropertyPtr)(const char *property, void *result, uint32_t *size); +typedef DNSServiceErrorType (DNSSD_API *ProcessResultPtr)(DNSServiceRef sdRef); +typedef DNSServiceErrorType (DNSSD_API *CreateConnectionPtr)(DNSServiceRef *sdRef); +typedef int (DNSSD_API *RefSockFDPtr)(DNSServiceRef sdRef); +} + +// represents a zero conf library exposing the dns-sd interface +class NativeZConfLib : public ZConfLib{ +private: + RefDeallocatePtr m_refDeallocate; + ResolvePtr m_resolve; + QueryRecordPtr m_queryRecord; + GetAddrInfoPtr m_getAddrInfo; + TxtRecordGetCountPtr m_txtRecordGetCount; + TxtRecordGetItemAtIndexPtr m_txtRecordGetItemAtIndex; + ReconfirmRecordPtr m_reconfirmRecord; + BrowsePtr m_browse; + GetPropertyPtr m_getProperty; + ProcessResultPtr m_processResult; + CreateConnectionPtr m_createConnection; + RefSockFDPtr m_refSockFD; + QLibrary nativeLib; +public: + + NativeZConfLib(QString libName = QLatin1String("dns_sd"), ZConfLib *fallBack = 0) : ZConfLib(fallBack), nativeLib(libName) + { +#ifndef ZCONF_STATIC_LINKING + // dynamic linking + if (!nativeLib.load()) { + qDebug() << "NativeZConfLib could not load native library"; + } + m_refDeallocate = reinterpret_cast(nativeLib.resolve("DNSServiceRefDeallocate")); + m_resolve = reinterpret_cast(nativeLib.resolve("DNSServiceResolve")); + m_queryRecord = reinterpret_cast(nativeLib.resolve("DNSServiceQueryRecord")); + m_getAddrInfo = reinterpret_cast(nativeLib.resolve("DNSServiceGetAddrInfo")); + m_txtRecordGetCount = reinterpret_cast(nativeLib.resolve("TXTRecordGetCount")); + m_txtRecordGetItemAtIndex = reinterpret_cast(nativeLib.resolve("TXTRecordGetItemAtIndex")) ; + m_reconfirmRecord = reinterpret_cast(nativeLib.resolve("DNSServiceReconfirmRecord")); + m_browse = reinterpret_cast(nativeLib.resolve("DNSServiceBrowse")); + m_getProperty = reinterpret_cast(nativeLib.resolve("DNSServiceGetProperty")); + m_processResult = reinterpret_cast(nativeLib.resolve("DNSServiceProcessResult")) ; + m_createConnection = reinterpret_cast(nativeLib.resolve("DNSServiceCreateConnection")); + m_refSockFD = reinterpret_cast(nativeLib.resolve("DNSServiceRefSockFD")); +#else + // static linking + m_refDeallocate = reinterpret_cast(&DNSServiceRefDeallocate); + m_resolve = reinterpret_cast(&DNSServiceResolve); + m_queryRecord = reinterpret_cast(DNSServiceQueryRecord); + m_getAddrInfo = reinterpret_cast(&DNSServiceGetAddrInfo); + m_txtRecordGetCount = reinterpret_cast(&TXTRecordGetCount); + m_txtRecordGetItemAtIndex = reinterpret_cast(&TXTRecordGetItemAtIndex) ; + m_reconfirmRecord = reinterpret_cast(&DNSServiceReconfirmRecord); + m_browse = reinterpret_cast(&DNSServiceBrowse); + m_getProperty = reinterpret_cast(&DNSServiceGetProperty); + m_processResult = reinterpret_cast(&DNSServiceProcessResult) ; + m_createConnection = reinterpret_cast(&DNSServiceCreateConnection); + m_refSockFD = reinterpret_cast(&DNSServiceRefSockFD); +#endif + if (m_refDeallocate == 0) qDebug() << QLatin1String("NativeZConfLib.m_refDeallocate == 0"); + if (m_resolve == 0) qDebug() << QLatin1String("NativeZConfLib.m_resolve == 0"); + if (m_queryRecord == 0) qDebug() << QLatin1String("NativeZConfLib.m_queryRecord == 0"); + if (m_getAddrInfo == 0) qDebug() << QLatin1String("NativeZConfLib.m_getAddrInfo == 0"); + if (m_txtRecordGetCount == 0) qDebug() << QLatin1String("NativeZConfLib.m_txtRecordGetCount == 0"); + if (m_txtRecordGetItemAtIndex == 0) qDebug() << QLatin1String("NativeZConfLib.m_txtRecordGetItemAtIndex == 0"); + if (m_reconfirmRecord == 0) qDebug() << QLatin1String("NativeZConfLib.m_reconfirmRecord == 0"); + if (m_browse == 0) qDebug() << QLatin1String("NativeZConfLib.m_browse == 0"); + if (m_getProperty == 0) qDebug() << QLatin1String("NativeZConfLib.m_getProperty == 0"); + if (m_processResult == 0) qDebug() << QLatin1String("NativeZConfLib.m_processResult == 0"); + if (m_createConnection == 0) qDebug() << QLatin1String("NativeZConfLib.m_createConnection == 0"); + if (m_refSockFD == 0) qDebug() << QLatin1String("NativeZConfLib.m_refSockFD == 0"); + } + + virtual ~NativeZConfLib() { + } + + virtual QString name(){ + return QString::fromUtf8("NativeZeroConfLib@%1").arg(size_t(this),0,16); + } + + // virtual bool tryStartDaemon(); + virtual void refDeallocate(DNSServiceRef sdRef) { + if (m_refDeallocate == 0) return; + m_refDeallocate(sdRef); + } + + virtual DNSServiceErrorType resolve(DNSServiceRef *sdRef, DNSServiceFlags flags, + uint32_t interfaceIndex, const char *name, + const char *regtype, const char *domain, + DNSServiceResolveReply callBack, void *context) + { + if (m_resolve == 0) return kDNSServiceErr_Unsupported; + return m_resolve(sdRef, flags, interfaceIndex, name, regtype, domain, callBack, context); + } + + virtual DNSServiceErrorType queryRecord(DNSServiceRef *sdRef, DNSServiceFlags flags, + uint32_t interfaceIndex, const char *fullname, + uint16_t rrtype, uint16_t rrclass, + DNSServiceQueryRecordReply callBack, void *context) + { + if (m_queryRecord == 0) return kDNSServiceErr_Unsupported; + return m_queryRecord(sdRef, flags, interfaceIndex, fullname, + rrtype, rrclass, callBack, context); + } + + virtual DNSServiceErrorType getAddrInfo(DNSServiceRef *sdRef, DNSServiceFlags flags, + uint32_t interfaceIndex, DNSServiceProtocol protocol, + const char *hostname, DNSServiceGetAddrInfoReply callBack, + void *context) + { + enum { longTTL = 100 }; + if (m_getAddrInfo == 0) { +#ifdef Q_OS_UNIX + // try to use getaddrinfo (for example on linux with avahi) + struct addrinfo req, *ans; int err; + memset(&req,0,sizeof(req)); + req.ai_flags = 0; + req.ai_family = AF_UNSPEC; + req.ai_socktype = SOCK_STREAM; + req.ai_protocol = 0; + if ((err = getaddrinfo(hostname, 0, &req, &ans)) != 0) { + qDebug() << "getaddrinfo for " << hostname << " failed with " << gai_strerror(err); + return kDNSServiceErr_Unsupported; // use another error here??? + } + for (struct addrinfo *ansAtt = ans; ansAtt != 0; ansAtt = ansAtt->ai_next){ + callBack(*sdRef, kDNSServiceFlagsAdd, interfaceIndex, kDNSServiceErr_NoError, + hostname, ansAtt->ai_addr, longTTL, context); + } + freeaddrinfo(ans); + return kDNSServiceErr_NoError; +#else + return kDNSServiceErr_Unsupported; +#endif + } + return m_getAddrInfo(sdRef, flags, interfaceIndex, protocol, + hostname, callBack, context); + } + + virtual uint16_t txtRecordGetCount(uint16_t txtLen, const void *txtRecord) + { + if (m_txtRecordGetCount == 0) return 0; + return m_txtRecordGetCount(txtLen, txtRecord); + } + + virtual DNSServiceErrorType txtRecordGetItemAtIndex(uint16_t txtLen, const void *txtRecord, + uint16_t itemIndex, uint16_t keyBufLen, + char *key, uint8_t *valueLen, const void **value) + { + if (m_txtRecordGetItemAtIndex == 0) return kDNSServiceErr_Unsupported; + return m_txtRecordGetItemAtIndex(txtLen, txtRecord, itemIndex, keyBufLen, + key, valueLen, value); + } + + virtual DNSServiceErrorType reconfirmRecord(DNSServiceFlags flags, uint32_t interfaceIndex, + const char *fullname, uint16_t rrtype, + uint16_t rrclass, uint16_t rdlen, const void *rdata) + { + if (m_reconfirmRecord == 0) return kDNSServiceErr_Unsupported; + return m_reconfirmRecord(flags, interfaceIndex, fullname, rrtype, + rrclass, rdlen, rdata); + } + + virtual DNSServiceErrorType browse(DNSServiceRef *sdRef, DNSServiceFlags flags, + uint32_t interfaceIndex, const char *regtype, + const char *domain, DNSServiceBrowseReply callBack, + void *context) + { + if (m_browse == 0) return kDNSServiceErr_Unsupported; + return m_browse(sdRef, flags, interfaceIndex, regtype, domain, callBack, context); + } + + virtual DNSServiceErrorType getProperty(const char *property, // Requested property (i.e. kDNSServiceProperty_DaemonVersion) + void *result, // Pointer to place to store result + uint32_t *size // size of result location + ) + { + if (m_getProperty == 0) + return kDNSServiceErr_Unsupported; + return m_getProperty(property, result, size); + } + + virtual DNSServiceErrorType processResult(DNSServiceRef sdRef) { + if (m_processResult == 0) return kDNSServiceErr_Unsupported; + return m_processResult(sdRef); + } + + virtual DNSServiceErrorType createConnection(DNSServiceRef *sdRef) { + if (m_createConnection == 0) return kDNSServiceErr_Unsupported; + return m_createConnection(sdRef); + } + + virtual int refSockFD(DNSServiceRef sdRef) { + if (m_refSockFD == 0) return kDNSServiceErr_Unsupported; + return m_refSockFD(sdRef); + } +}; + +ZConfLib *ZConfLib::createNativeLib(const QString &libName, ZConfLib *fallback) { + return new NativeZConfLib(libName, fallback); + return fallback; +} +} // namespace Internal +} // namespace ZeroConf + +#else // no native lib + +namespace ZeroConf { +namespace Internal { + +ZConfLib *ZConfLib::createNativeLib(const QString &/*extraPaths*/, ZConfLib * fallback) { + return fallback; +} + +} // namespace Internal +} // namespace ZeroConf +#endif + diff --git a/src/libs/zeroconf/servicebrowser.cpp b/src/libs/zeroconf/servicebrowser.cpp new file mode 100644 index 00000000000..87b851341f2 --- /dev/null +++ b/src/libs/zeroconf/servicebrowser.cpp @@ -0,0 +1,1521 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifdef Q_OS_WIN32 +# include +# ifndef SHUT_RDWR +# ifdef SD_BOTH +# define SHUT_RDWR SD_BOTH +# else +# define SHUT_RDWR 2 +# endif +# endif +#else +# include +#endif + +#include "mdnsderived.h" +#include "servicebrowser_p.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*! + \namespace ZeroConf + + \brief namespace for zeroconf (Bonjour/DNS-SD) functionality, currently mostly for browsing services. +*/ + +namespace { // anonymous namespace for free functions +// ----------------- free functions ----------------- +enum { DEBUG_SERVICEBROWSER = false }; + +using namespace ZeroConf; +using namespace ZeroConf::Internal; + +QString toFullNameC(const char * const service, const char * const regtype, const char * const domain) +{ + char fullName[kDNSServiceMaxDomainName]; + myDNSServiceConstructFullName(fullName, service, regtype, domain); + fullName[kDNSServiceMaxDomainName - 1] = 0; // just to be sure + return QString::fromUtf8(fullName); +} + +int fromFullNameC(const char * const fullName, QString &service, QString ®type, QString &domain) +{ + char fullNameDecoded[kDNSServiceMaxDomainName]; + int encodedI = 0; + int decodedI = 0; + int oldPos[4]; + int iPos = 0; + while (fullName[encodedI] != 0 && encodedI = '0' && c <= '9'){ + int val = (c - '0') * 100; + c = fullName[++encodedI]; + if (c < '0' || c > '9' || encodedI == kDNSServiceMaxDomainName) + return 2; + val += (c - '0') * 10; + c = fullName[++encodedI]; + if (c < '0' || c > '9') + return 3; + val += (c - '0'); + fullNameDecoded[decodedI++] = static_cast(static_cast(val)); + } else { + fullNameDecoded[decodedI++] = c; + } + } else if (c == '.') { + if (iPos < 4) { + oldPos[iPos++] = decodedI; + } + fullNameDecoded[decodedI++] = c; + } else { + fullNameDecoded[decodedI++] = c; + } + } + if (iPos != 4) return 5; + service = QString::fromUtf8(&fullNameDecoded[0], oldPos[0]); + regtype = QString::fromUtf8(&fullNameDecoded[oldPos[0] + 1], oldPos[3] - oldPos[0] - 1); + domain = QString::fromUtf8(&fullNameDecoded[oldPos[3] + 1], decodedI - oldPos[3] - 1); + return 0; +} + +// ----------------- C callbacks ----------------- + +extern "C" void cServiceResolveReply(DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *fullname, + const char *hosttarget, + uint16_t port, /* In network byte order */ + uint16_t txtLen, + const unsigned char *txtRecord, + void *context) +{ + if (DEBUG_SERVICEBROWSER) + qDebug() << "cServiceResolveReply(" << ((size_t)sdRef) << ", " << ((quint32)flags) << ", " << interfaceIndex + << ", " << ((int)errorCode) << ", " << fullname << ", " << hosttarget << ", " << port << ", " + << txtLen << ", '" << QString::fromUtf8((const char *)txtRecord, txtLen) << "', " << ((size_t)context); + ServiceGatherer *ctxGatherer = reinterpret_cast(context); + if (ctxGatherer){ + ctxGatherer->serviceResolveReply(sdRef, flags, interfaceIndex, errorCode, fullname, hosttarget, port, txtLen, txtRecord); + } +} + +extern "C" void cTxtRecordReply(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) +{ + if (DEBUG_SERVICEBROWSER) + qDebug() << "cTxtRecordReply(" << ((size_t)sdRef) << ", " << ((int)flags) << ", " << interfaceIndex + << ", " << ((int)errorCode) << ", " << fullname << ", " << rrtype << ", " << rrclass << ", " + << ", " << rdlen << QString::fromUtf8((const char *)rdata, rdlen) << "', " << ttl << ", " << ((size_t)context); + ServiceGatherer *ctxGatherer = reinterpret_cast(context); + if (ctxGatherer){ + ctxGatherer->txtRecordReply(sdRef, flags, interfaceIndex, errorCode, fullname, rrtype, rrclass, rdlen, rdata, ttl); + } +} + +extern "C" void cAddrReply(DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *hostname, + const struct sockaddr *address, + uint32_t ttl, + void *context) +{ + if (DEBUG_SERVICEBROWSER) + qDebug() << "cAddrReply(" << ((size_t)sdRef) << ", " << ((int)flags) << ", " << interfaceIndex + << ", " << ((int)errorCode) << ", " << hostname << ", " << QHostAddress(address).toString() << ", " << ttl << ", " + << ((size_t)context); + ServiceGatherer *ctxGatherer = reinterpret_cast(context); + if (ctxGatherer){ + ctxGatherer->addrReply(sdRef, flags, interfaceIndex, errorCode, hostname, address, ttl); + } +} + +/// callback for service browsing +extern "C" void cBrowseReply(DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *serviceName, + const char *regtype, + const char *replyDomain, + void *context) +{ + if (DEBUG_SERVICEBROWSER) + qDebug() << "cBrowseReply(" << ((size_t)sdRef) << ", " << flags << ", " << interfaceIndex + << ", " << ((int)errorCode) << ", " << serviceName << ", " << regtype << ", " << replyDomain << ", " + << ((size_t)context); + ServiceBrowserPrivate *sb = (ServiceBrowserPrivate *)(context); + if (sb == 0){ + qDebug() << "ServiceBrowser ignoring reply because context was null "; + return; + } + sb->browseReply(sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain); +} + +/// singleton for lib setup +class ZeroConfLib +{ +public: + ZeroConfLib(); + ZConfLib *defaultLib(); + void setDefaultLib(LibUsage usage, const QString &libName, const QString &daemonPath); + +private: + QMutex m_lock; + ZConfLib *m_defaultLib; +}; + +Q_GLOBAL_STATIC(ZeroConfLib, zeroConfLibInstance) + +ZeroConfLib::ZeroConfLib(): m_defaultLib(ZConfLib::createNativeLib(QLatin1String("dns_sd"), + ZConfLib::createEmbeddedLib(QString(), 0))) +{ + qRegisterMetaType("Service::ConstPtr"); +} + +ZConfLib *ZeroConfLib::defaultLib(){ + QMutexLocker l(&m_lock); + return m_defaultLib; +} + +void ZeroConfLib::setDefaultLib(LibUsage usage, const QString &libName, const QString &daemonPath){ // leaks... should be ok, switch to shared pointers??? + QMutexLocker l(&m_lock); + switch (usage){ + case (UseNativeOnly): + m_defaultLib = ZConfLib::createNativeLib(libName, 0); + break; + case (UseEmbeddedOnly): + m_defaultLib = ZConfLib::createEmbeddedLib(daemonPath, 0); + break; + case (UseNativeOrEmbedded): + m_defaultLib = ZConfLib::createNativeLib(libName, ZConfLib::createEmbeddedLib(daemonPath, 0)); + break; + default: + qDebug() << "invalid usage " << usage; + } +} + +} // end anonymous namespace + +namespace ZeroConf { + +// ----------------- Service impl ----------------- +/*! + \class ZeroConf::Service + + \brief class representing a zeroconf service + + Instances of this class are basically constant, but can be outdated. They are normally accessed through a Shared pointer. + This design avoids race conditions when used though multiple threads. + + \threadsafe + */ + +Service::Service(const Service&o): + QObject(0), m_name(o.m_name), m_type(o.m_type), m_domain(o.m_domain), m_fullName(o.m_fullName), m_port(o.m_port), m_txtRecord(o.m_txtRecord), + m_host((o.m_host != 0)?(new QHostInfo(*o.m_host)):o.m_host), m_interfaceNr(o.m_interfaceNr), m_outdated(o.m_outdated) +{ +} + +Service::Service(QObject *parent): + QObject(parent), m_name(), m_type(), m_domain(), m_fullName(), m_port(), m_txtRecord(), m_host(0), m_interfaceNr(0), m_outdated(false) +{ } + +Service::~Service() +{ + if (m_host) delete m_host; +} + +QDebug operator<<(QDebug dbg, const Service &service) +{ + dbg.maybeSpace() << "Service{ name:" << service.name() << ", " + << "type:" << service.type() << ", domain:" << service.domain() << ", " + << " fullName:" << service.fullName() << ", port:" << service.port() + << ", txtRecord:{"; + bool first=true; + const ServiceTxtRecord &txtRecord = service.txtRecord(); + foreach (const QString &k, txtRecord){ + if (first) + first = false; + else + dbg << ", "; + dbg << k << ":" << txtRecord.value(k); + } + dbg << "}, "; + if (const QHostInfo *host = service.host()){ + dbg << "host:{" << host->hostName() << ", addresses["; + first=true; + foreach (const QHostAddress &addr, host->addresses()){ + if (first) + first = false; + else + dbg << ", "; + dbg << addr.toString(); + } + dbg << "], },"; + } else { + dbg << " host:*null*,"; + } + dbg << " interfaceNr:" << service.interfaceNr() << ", outdated:" << service.outdated() << " }"; + return dbg.space(); +} +// inline methods +/*! + \fn bool Service::outdated() const + + Returns if the service data is outdated, its value might change even on + the (otherwise constant) objects returned by a ServiceBrowser. +*/ +/*! + \fn QString Service::name() const + + Returns the name of the service (non escaped). +*/ +/*! + \fn QString Service::type() const + + Returns the name of the service type (non escaped). +*/ +/*! + \fn QString Service::domain() const + + Returns the name of the domain (non escaped). +*/ +/*! + \fn QString Service::fullName() const + + Returns the full name (service.type.domain) with each component correctly escaped. +*/ +/*! + \fn QString Service::port() const + + Return the port of the service (as a string, not as number). +*/ +/*! + \fn const Service::ServiceTxtRecord &txtRecord() const + + Returns the extra information on this service. +*/ +/*! + \fn const Service::QHostInfo *host() const + + Returns the host through which this service is reachable. +*/ +/*! + \fn int Service::interfaceNr() const + + returns the interface on which the service is reachable, 1 based, 0 means to try all interfaces +*/ +/*! + \fn bool Service::invalidate() + + Marks this service as outdated. +*/ + +// ----------------- ServiceBrowser impl ----------------- +/*! + \class ZeroConf::ServiceBrowser + + \brief class that browses (searches) for a given zeronconf service + + The actual browsing starts only when startBrowsing() is called. If you want to receive all service + changes connect before starting browsing. + + The current list of services can be gathered with the services() method. + + \threadsafe + */ + +/// starts the browsing, return true if successfull +bool ServiceBrowser::startBrowsing(qint32 interfaceIndex) +{ + return d->startBrowsing(interfaceIndex); +} + +/// create a new brower for the given service type +ServiceBrowser::ServiceBrowser(const QString &serviceType, const QString &domain, bool requireAddresses, + QObject *parent): + QObject(parent), d(new ServiceBrowserPrivate(serviceType, domain, requireAddresses, MainConnectionPtr())) +{ + d->q = this; +} +/// create a new brower for the given service type +ServiceBrowser::ServiceBrowser(const QString &serviceType, const QString &domain, bool requireAddresses, + QObject *parent, MainConnectionPtr connection): + QObject(parent), d(new ServiceBrowserPrivate(serviceType, domain, requireAddresses, connection)) +{ + d->q = this; +} + +/// create a new ServiceBrowser (which is basically constant once created) +ServiceBrowser::ServiceBrowser(const QString &serviceType, MainConnectionPtr connection) : + QObject(), d(new ServiceBrowserPrivate(serviceType, QString::fromUtf8("local."), true, connection)) +{ + d->q = this; +} + +ServiceBrowser::~ServiceBrowser() +{ + delete d; +} + +/// returns the main connection used by this ServiceBrowser +MainConnectionPtr ServiceBrowser::mainConnection() +{ + return d->mainConnection; +} + +/// stops browsing, but does not delete all services found +void ServiceBrowser::stopBrowsing() +{ + d->stopBrowsing(); +} + +/// if the service is currently active +bool ServiceBrowser::isBrowsing() const +{ + return d->browsing; +} + +/// type of the service browsed (non escaped) +const QString& ServiceBrowser::serviceType() const +{ + return d->serviceType; +} + +/// domain that is browser (non escaped) +const QString& ServiceBrowser::domain() const +{ + return d->domain; +} + +/// if addresses should be resolved automatically for each service found +bool ServiceBrowser::adressesAutoResolved() const +{ + return d->autoResolveAddresses; +} + +/// if addresses are required to add the service to the list of available services +bool ServiceBrowser::adressesRequired() const +{ + return d->requireAddresses; +} + +/// list of current services (by copy on purpose) +QList ServiceBrowser::services() const +{ + QMutexLocker l(d->mainConnection->lock()); + return d->activeServices; +} + +/// forces a full update of a service (call this after a failure to connect to the service) +/// this is an expensive call, use only when needed +void ServiceBrowser::reconfirmService(Service::ConstPtr service) +{ + d->reconfirmService(service); +} + +// signals +/*! + \fn void ServiceBrowser::serviceChanged(Service::ConstPtr oldService, Service::ConstPtr newService, ServiceBrowser *browser) + + This signal is called when a service is added removed or changes. + Both oldService or newService might be null (covers both add and remove). + The services list might not be synchronized with respect to this signal. +*/ +/*! + \fn void ServiceBrowser::serviceAdded(Service::ConstPtr service, ServiceBrowser *browser) + + This signal is called when a service is added (convenience method) + the services list might not be synchronized with respect to this signal + + \sa serviceChanged() +*/ +/*! + \fn void ServiceBrowser::serviceRemoved(Service::ConstPtr service, ServiceBrowser *browser) + + This signal is called when a service is removed (convenience method) + the services list might not be synchronized with respect to this signal + + \sa serviceChanged() +*/ +/*! + \fn void ServiceBrowser::servicesUpdated(ServiceBrowser *browser) + + This signal is called when the list is updated. + It might collect several serviceChanged signals together, if you use the list returned by services(), + use this signal, not serviceChanged(), serviceAdded() or serviceRemoved() to know about changes to the list. +*/ + +// ----------------- library initialization impl ----------------- +/*! + Intializes the library used for the mdns queries. + This changes the default library used by the next MainConnection, it does not change the already instantiated + connections. + \a usage can decide which libraries are tried, + \a libName should be the name (or path) to the libdns library, + \a daemonPath is the path to the daemon executable which should be started by the embedded library if no daemon + is found. + + \threadsafe +*/ +void initLib(LibUsage usage, const QString &libName, const QString &daemonPath) +{ + zeroConfLibInstance()->setDefaultLib(usage, libName, daemonPath); +} + +namespace Internal { +// ----------------- ConnectionThread impl ----------------- + +void ConnectionThread::run() +{ + connection.handleEvents(); +} + +ConnectionThread::ConnectionThread(MainConnection &mc, QObject *parent): + QThread(parent), connection(mc) +{ } + +// ----------------- ServiceGatherer impl ----------------- + +ZConfLib *ServiceGatherer::lib() +{ + return serviceBrowser->mainConnection->lib; +} + +void ServiceGatherer::enactServiceChange() +{ + if (DEBUG_SERVICEBROWSER) + qDebug() << "ServiceGatherer::enactServiceChange() for service " << serviceName; + if (currentServiceCanBePublished()) { + Service::Ptr nService = Service::Ptr(currentService); + serviceBrowser->serviceChanged(publishedService, nService, serviceBrowser->q); + if (publishedService) { + serviceBrowser->nextActiveServices.removeOne(publishedService); + serviceBrowser->serviceRemoved(publishedService, serviceBrowser->q); + } + publishedService = nService; + if (nService) { + serviceBrowser->nextActiveServices.append(nService); + serviceBrowser->serviceAdded(nService, serviceBrowser->q); + currentService = new Service(*currentService); + } + } +} + +void ServiceGatherer::retireService() +{ + if (publishedService) { + Service::Ptr nService; + serviceBrowser->nextActiveServices.removeOne(publishedService); + serviceBrowser->serviceChanged(publishedService, nService, serviceBrowser->q); + serviceBrowser->serviceRemoved(publishedService, serviceBrowser->q); + publishedService = nService; + } +} + +void ServiceGatherer::stopResolve() +{ + if ((status & ResolveConnectionActive) == 0) return; + lib()->refDeallocate(resolveConnection); + status &= ~ResolveConnectionActive; + serviceBrowser->updateFlowStatusForCancel(); +} + +void ServiceGatherer::restartResolve() +{ + stopResolve(); + resolveConnection = serviceBrowser->mainRef(); + DNSServiceErrorType err = lib()->resolve( + &resolveConnection, + kDNSServiceFlagsShareConnection | kDNSServiceFlagsSuppressUnusable | kDNSServiceFlagsTimeout, + interfaceIndex, serviceName.toUtf8().constData(), + serviceType.toUtf8().constData(), domain.toUtf8().constData(), &cServiceResolveReply, this); + if (err != kDNSServiceErr_NoError) { + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " failed discovery of service " << serviceName + << " due to error " << err; + status = status | ResolveConnectionFailed; + } else { + status = ((status & ~ResolveConnectionFailed) | ResolveConnectionActive); + } +} + +void ServiceGatherer::stopTxt() +{ + if ((status & TxtConnectionActive) == 0) return; + lib()->refDeallocate(txtConnection); + status &= ~TxtConnectionActive; + serviceBrowser->updateFlowStatusForCancel(); +} + +void ServiceGatherer::restartTxt() +{ + stopTxt(); + txtConnection = serviceBrowser->mainRef(); + DNSServiceErrorType err = lib()->queryRecord(&txtConnection, + kDNSServiceFlagsShareConnection | kDNSServiceFlagsSuppressUnusable | kDNSServiceFlagsTimeout, + interfaceIndex, fullName.toUtf8().constData(), + kDNSServiceType_TXT, kDNSServiceClass_IN, &cTxtRecordReply, this); + + if (err != kDNSServiceErr_NoError) { + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " failed query of TXT record of service " << serviceName + << " due to error " << err; + status = status | TxtConnectionFailed; + } else { + status = ((status & ~TxtConnectionFailed) | TxtConnectionActive); + } +} + +void ServiceGatherer::stopHostResolution() +{ + if ((status & AddrConnectionActive) == 0) return; + lib()->refDeallocate(addrConnection); + status &= ~AddrConnectionActive; + serviceBrowser->updateFlowStatusForCancel(); +} + +void ServiceGatherer::restartHostResolution() +{ + stopHostResolution(); + if (DEBUG_SERVICEBROWSER) + qDebug() << "ServiceGatherer::restartHostResolution for host " << hostName << " service " << serviceName; + if (hostName.isEmpty()){ + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " cannot start host resolution without hostname for service " + << serviceName; + } + addrConnection = serviceBrowser->mainRef(); + DNSServiceErrorType err = lib()->getAddrInfo(&addrConnection, + kDNSServiceFlagsShareConnection | kDNSServiceFlagsSuppressUnusable | kDNSServiceFlagsTimeout, + interfaceIndex, 0 /* kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6 */, + hostName.toUtf8().constData(), &cAddrReply, this); + + if (err != kDNSServiceErr_NoError) { + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " failed starting resolution of host " + << hostName << " for service " << serviceName << " due to error " << err; + status = status | AddrConnectionFailed; + } else { + status = ((status & ~AddrConnectionFailed) | AddrConnectionActive); + } +} + +/// if the current service can be added +bool ServiceGatherer::currentServiceCanBePublished() +{ + return (currentService->host() && !currentService->host()->addresses().isEmpty()) || !serviceBrowser->requireAddresses; +} + +ServiceGatherer::ServiceGatherer(const QString &newServiceName, const QString &newType, const QString &newDomain, + const QString &fullName, uint32_t interfaceIndex, ServiceBrowserPrivate *serviceBrowser): + port(0), serviceName(newServiceName), serviceType(newType), domain(newDomain), fullName(fullName), serviceBrowser(serviceBrowser), + publishedService(0), currentService(new Service()), interfaceIndex(interfaceIndex), status(0) +{ + if (DEBUG_SERVICEBROWSER) + qDebug() << " creating ServiceGatherer(" << newServiceName << ", " << newType << ", " << newDomain << ", " + << fullName << ", " << interfaceIndex << ", " << ((size_t) serviceBrowser); + if (fullName.isEmpty()) + this->fullName = toFullNameC(serviceName.toUtf8().data(), serviceType.toUtf8().data(), domain.toUtf8().data()); + restartResolve(); + restartTxt(); +} + +ServiceGatherer::~ServiceGatherer() +{ + stopHostResolution(); + stopResolve(); + stopTxt(); + delete currentService; +} + +ServiceGatherer::Ptr ServiceGatherer::createGatherer(const QString &newServiceName, const QString &newType, + const QString &newDomain, const QString &fullName, + uint32_t interfaceIndex, ServiceBrowserPrivate *serviceBrowser) +{ + Ptr res(new ServiceGatherer(newServiceName, newType, newDomain, fullName, interfaceIndex, serviceBrowser)); + res->self = res.toWeakRef(); + return res; +} + +ServiceGatherer::Ptr ServiceGatherer::gatherer() { + return self.toStrongRef(); +} + +void ServiceGatherer::serviceResolveReply(DNSServiceRef /*sdRef*/, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *fullname, + const char *hosttarget, + uint16_t port, /* In network byte order */ + uint16_t txtLen, + const unsigned char *rawTxtRecord) +{ + if (errorCode != kDNSServiceErr_NoError){ + if (errorCode == kDNSServiceErr_Timeout){ + if ((status & ResolveConnectionSuccess) == 0){ + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " failed service resolution for service " + << serviceName << " as it did timeout"; + status |= ResolveConnectionFailed; + } + } else { + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " failed service resolution for service " + << serviceName << " with error " << errorCode; + status |= ResolveConnectionFailed; + } + if (status & ResolveConnectionActive) { + status &= ~ResolveConnectionActive; + lib()->refDeallocate(resolveConnection); + serviceBrowser->updateFlowStatusForCancel(); + } + return; + } + if (publishedService) publishedService->invalidate(); // delay this to enactServiceChange? + serviceBrowser->updateFlowStatusForFlags(flags); + + if (fullName != fullname){ + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " for service " + << serviceName << " ignoring resolve reply for " << fullname << " vs. " << fullName; + return; + } + + uint16_t nKeys = lib()->txtRecordGetCount(txtLen, rawTxtRecord); + for (uint16_t i = 0; i < nKeys; ++i){ + enum { maxTxtLen= 256 }; + char keyBuf[maxTxtLen]; + uint8_t valLen; + const char *valueCStr; + DNSServiceErrorType txtErr = lib()->txtRecordGetItemAtIndex(txtLen, rawTxtRecord, i, maxTxtLen, + keyBuf, &valLen, (const void **)&valueCStr); + if (txtErr != kDNSServiceErr_NoError){ + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " error " << txtErr + << " decoding txt record of service " << serviceName; + break; + } + keyBuf[maxTxtLen-1] = 0; // just to be sure + if (flags & kDNSServiceFlagsAdd) { + txtRecord[QString::fromUtf8(keyBuf)] = QString::fromUtf8(valueCStr, valLen); + } else { + txtRecord.remove(QString::fromUtf8(keyBuf)); // check value??? + } + } + this->interfaceIndex = interfaceIndex; + currentService->m_name = serviceName; + currentService->m_type = serviceType; + currentService->m_domain = domain; + currentService->m_fullName = fullName; + currentService->m_interfaceNr = interfaceIndex; + currentService->m_port = QString::number(qFromBigEndian(port)); + if (hostName != hosttarget) { + hostName = QString::fromUtf8(hosttarget); + if (!currentService->host()) + currentService->m_host = new QHostInfo(); + else + currentService->m_host->setAddresses(QList()); + currentService->m_host->setHostName(hostName); + if (serviceBrowser->autoResolveAddresses){ + restartHostResolution(); + } + } + if (currentServiceCanBePublished()) + serviceBrowser->pendingGathererAdd(gatherer()); +} + +void ServiceGatherer::txtRecordReply(DNSServiceRef /*sdRef*/, + DNSServiceFlags flags, + uint32_t /*interfaceIndex*/, + DNSServiceErrorType errorCode, + const char * /*fullname*/, + uint16_t rrtype, + uint16_t rrclass, + uint16_t txtLen, + const void *rawTxtRecord, + uint32_t /*ttl*/) +{ + if (errorCode != kDNSServiceErr_NoError){ + if (errorCode == kDNSServiceErr_Timeout){ + if ((status & TxtConnectionSuccess) == 0){ + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " failed txt gathering for service " + << serviceName << " as it did timeout"; + status |= TxtConnectionFailed; + } + } else { + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " failed txt gathering for service " + << serviceName << " with error " << errorCode; + status |= TxtConnectionFailed; + } + if (status & TxtConnectionActive) { + status &= ~TxtConnectionActive; + lib()->refDeallocate(txtConnection); + serviceBrowser->updateFlowStatusForCancel(); + } + return; + } + serviceBrowser->updateFlowStatusForFlags(flags); + if (rrtype != kDNSServiceType_TXT || rrclass != kDNSServiceClass_IN) { + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " for service " + << serviceName << " received an unexpected rrtype/class:" << rrtype << "/" << rrclass; + } + + uint16_t nKeys = lib()->txtRecordGetCount(txtLen, rawTxtRecord); + for (uint16_t i = 0; i < nKeys; ++i){ + char keyBuf[256]; + uint8_t valLen; + const char *valueCStr; + DNSServiceErrorType txtErr = lib()->txtRecordGetItemAtIndex(txtLen, rawTxtRecord, i, 256, keyBuf, &valLen, (const void **)&valueCStr); + if (txtErr != kDNSServiceErr_NoError){ + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " error " << txtErr + << " decoding txt record of service " << serviceName; + if ((flags & kDNSServiceFlagsAdd) == 0) + txtRecord.clear(); + break; + } + keyBuf[255] = 0; // just to be sure + if (flags & kDNSServiceFlagsAdd) { + txtRecord[QString::fromUtf8(keyBuf)] = QString::fromUtf8(valueCStr, valLen); + } else { + txtRecord.remove(QString::fromUtf8(keyBuf)); // check value??? + } + } + if ((flags & kDNSServiceFlagsAdd) != 0) { + status |= TxtConnectionSuccess; + } + if (txtRecord.count() != 0 && currentServiceCanBePublished()) + serviceBrowser->pendingGathererAdd(gatherer()); +} + +void ServiceGatherer::addrReply(DNSServiceRef /*sdRef*/, + DNSServiceFlags flags, + uint32_t /*interfaceIndex*/, + DNSServiceErrorType errorCode, + const char *hostname, + const struct sockaddr *address, + uint32_t /*ttl*/) // should we use this??? +{ + if (errorCode != kDNSServiceErr_NoError){ + if (errorCode == kDNSServiceErr_Timeout){ + if ((status & AddrConnectionSuccess) == 0){ + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " failed address resolve for service " + << serviceName << " as it did timeout"; + status |= AddrConnectionFailed; + } + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " failed addr resolve for service " + << serviceName << " with error " << errorCode; + status |= AddrConnectionFailed; + } + if (status & AddrConnectionActive){ + status &= ~AddrConnectionActive; + lib()->refDeallocate(addrConnection); + serviceBrowser->updateFlowStatusForCancel(); + } + return; + } + serviceBrowser->updateFlowStatusForFlags(flags); + if (!currentService->host()) + currentService->m_host=new QHostInfo(); + if (currentService->host()->hostName() != hostname) { + if ((flags & kDNSServiceFlagsAdd) == 1) + currentService->m_host->setHostName(QString::fromUtf8(hostname)); + if (currentService->host()->addresses().isEmpty()) { + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " for service " + << serviceName << " add with name " << hostname << " while old name " + << currentService->host()->hostName() << " has still adresses, removing them"; + currentService->m_host->setAddresses(QList()); + } + else{ + qDebug() << "ServiceBrowser " << serviceBrowser->serviceType << " for service " + << serviceName << " ignoring remove for " << hostname << " as current hostname is " + << currentService->host()->hostName(); + return; + } + } + QHostAddress newAddr(address); + QList addrNow = currentService->host()->addresses(); + if ((flags & kDNSServiceFlagsAdd) == 0) { + if (addrNow.removeOne(newAddr)) + currentService->m_host->setAddresses(addrNow); + } else { + if (!addrNow.contains(newAddr)){ + addrNow.append(newAddr); + currentService->m_host->setAddresses(addrNow); + } + } + serviceBrowser->pendingGathererAdd(gatherer()); +} + +void ServiceGatherer::maybeRemove() +{ + // could trigger an update, but for now we just ignore it (less chatty) +} + +void ServiceGatherer::stop() +{ + if (status & ResolveConnectionActive){ + status &= ~ResolveConnectionActive; + lib()->refDeallocate(resolveConnection); + serviceBrowser->updateFlowStatusForCancel(); + } + if (status & TxtConnectionActive){ + status &= ~TxtConnectionActive; + lib()->refDeallocate(txtConnection); + serviceBrowser->updateFlowStatusForCancel(); + } + if (status & AddrConnectionActive) { + status &= ~AddrConnectionActive; + lib()->refDeallocate(addrConnection); + serviceBrowser->updateFlowStatusForCancel(); + } +} + +void ServiceGatherer::reload(qint32 interfaceIndex) +{ + this->interfaceIndex = interfaceIndex; + stop(); + restartResolve(); + restartTxt(); + if (currentServiceCanBePublished()) // avoid??? + restartHostResolution(); +} + +void ServiceGatherer::remove() +{ + stop(); + if (serviceBrowser->gatherers.contains(fullName) && serviceBrowser->gatherers[fullName] == this) + serviceBrowser->gatherers.remove(fullName); +} +/// forces a full reload of the record +void ServiceGatherer::reconfirm() +{ + stop(); + /* DNSServiceErrorType err = DNSServiceReconfirmRecord(kDNSServiceFlagsShareConnection, + interfaceIndex, fullName.toUtf8().constData(), + kDNSServiceType_PTR, // kDNSServiceType_SRV could be another possibility + kDNSServiceClass_IN //, + // uint16_t rdlen, // not cached... what is the best solution??? + // const void *rdata + ); */ +} + +// ----------------- ServiceBrowserPrivate impl ----------------- + +DNSServiceRef ServiceBrowserPrivate::mainRef() +{ + return mainConnection->mainRef(); +} + +void ServiceBrowserPrivate::updateFlowStatusForCancel() +{ + mainConnection->updateFlowStatusForCancel(); +} + +void ServiceBrowserPrivate::updateFlowStatusForFlags(DNSServiceFlags flags) +{ + mainConnection->updateFlowStatusForFlags(flags); +} + +void ServiceBrowserPrivate::pendingGathererAdd(ServiceGatherer::Ptr gatherer) +{ + int ng = pendingGatherers.count(); + for (int i = 0; i < ng; ++i){ + const ServiceGatherer::Ptr &g=pendingGatherers.at(i); + if (g->fullName == gatherer->fullName){ + if (g != gatherer){ + gatherer->publishedService = g->publishedService; + pendingGatherers[i] = gatherer; + } + return; + } + } + pendingGatherers.append(gatherer); +} + +ServiceBrowserPrivate::ServiceBrowserPrivate(const QString &serviceType, const QString &domain, bool requireAddresses, MainConnectionPtr mconn): + q(0), serviceType(serviceType), domain(domain), mainConnection(mconn), serviceConnection(0), flags(0), interfaceIndex(0), + failed(false), browsing(false), autoResolveAddresses(requireAddresses), requireAddresses(requireAddresses) +{ +} + +ServiceBrowserPrivate::~ServiceBrowserPrivate() +{ + qDebug() << "destroying ServiceBrowserPrivate " << serviceType; + if (browsing){ + stopBrowsing(); + } + if (mainConnection){ + mainConnection->removeBrowser(this); + } +} + +void ServiceBrowserPrivate::insertGatherer(const QString &fullName) +{ + if (!gatherers.contains(fullName)){ + QString newServiceName, newType, newDomain; + if (fromFullNameC(fullName.toUtf8().data(), *&newServiceName, *&newType, *&newDomain)){ + qDebug() << "Error unescaping fullname " << fullName; + } else { + ServiceGatherer::Ptr serviceGatherer = ServiceGatherer::createGatherer(newServiceName, newType, newDomain, fullName, 0, this); + gatherers[fullName] = serviceGatherer; + } + } +} + +void ServiceBrowserPrivate::maybeUpdateLists() +{ + if (mainConnection->flowStatus != MainConnection::MoreComingRFS || pendingGatherers.count() > 50) { + QList::iterator i = knownServices.begin(), endi = knownServices.end(); + QMap::iterator j = gatherers.begin(); + while (i != endi && j != gatherers.end()) { + const QString vi = *i; + const QString vj = (*j)->fullName; + if (vi == vj){ + ++i; + ++j; + } else if (vi < vj) { + qDebug() << "ServiceBrowser " << serviceType << ", missing gatherer for " << vi; + insertGatherer(vi); + ++i; + } else { + (*j)->retireService(); + j = gatherers.erase(j); + } + } + while (i != endi) { + qDebug() << "ServiceBrowser " << serviceType << ", missing gatherer for " << *i; + insertGatherer(*i); + } + while (j != gatherers.end()) { + (*j)->retireService(); + j = gatherers.erase(j); + } + foreach (const ServiceGatherer::Ptr &g, pendingGatherers) + g->enactServiceChange(); + { + QMutexLocker l(mainConnection->lock()); + activeServices=nextActiveServices; + } + emit q->servicesUpdated(q); + } +} + +/// callback announcing +void ServiceBrowserPrivate::browseReply(DNSServiceRef /*sdRef*/, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *serviceName, + const char *regtype, + const char *replyDomain) +{ + if (DEBUG_SERVICEBROWSER) + qDebug() << "browseReply(" << ((int)flags) << ", " << interfaceIndex << ", " << ((int)errorCode) << ", " << serviceName << ", " + << regtype << ", " << replyDomain << ")"; + if (errorCode != kDNSServiceErr_NoError){ + qDebug() << "ServiceBrowser " << serviceType << " ignoring reply due to error " << errorCode; + return; + } + QString newServiceName = QString::fromUtf8(serviceName); + QString newType = serviceType; + QString newDomain = domain; + if (serviceType != regtype) // discard? should not happen... + newType = QString::fromUtf8(regtype); + if (domain != replyDomain) + domain = QString::fromUtf8(replyDomain); + QString fullName = toFullNameC(serviceName, regtype, replyDomain); + updateFlowStatusForFlags(flags); + if (flags & kDNSServiceFlagsAdd){ + ServiceGatherer::Ptr serviceGatherer; + if (!gatherers.contains(fullName)){ + serviceGatherer = ServiceGatherer::createGatherer(newServiceName, newType, newDomain, fullName, interfaceIndex, this); + gatherers[fullName] = serviceGatherer; + } else { + serviceGatherer = gatherers[fullName]; + serviceGatherer->reload(interfaceIndex); + } + QList::iterator pos = std::lower_bound(knownServices.begin(), knownServices.end(), fullName); + // could order later (more efficient, but then we have to handle eventual duplicates) + if (pos == knownServices.end() || *pos != fullName) + knownServices.insert(pos, fullName); + } else { + if (gatherers.contains(fullName)){ + gatherers[fullName]->maybeRemove(); + } + knownServices.removeOne(fullName); + } + maybeUpdateLists(); +} + +bool ServiceBrowserPrivate::startBrowsing(quint32 interfaceIndex) +{ + if (failed || browsing) return false; + if (mainConnection.isNull()) + mainConnection = MainConnectionPtr(new MainConnection()); + mainConnection->addBrowser(this); + serviceConnection = mainRef(); + DNSServiceErrorType err; + err = mainConnection->lib->browse(&serviceConnection, kDNSServiceFlagsShareConnection | kDNSServiceFlagsSuppressUnusable, interfaceIndex, + serviceType.toUtf8().constData(), + ((domain.isEmpty()) ? 0 : (domain.toUtf8().constData())), + &cBrowseReply, this); + if (err != kDNSServiceErr_NoError){ + qDebug() << "ServiceBrowser " << serviceType << " failed initializing serviceConnection"; + return false; + } + browsing = true; + if (DEBUG_SERVICEBROWSER) + qDebug() << "startBrowsing(" << interfaceIndex << ") for serviceType:" << serviceType << " domain:" << domain; + return true; +} + +void ServiceBrowserPrivate::stopBrowsing() +{ + QMutexLocker l(mainConnection->lock()); + if (browsing){ + if (serviceConnection) { + mainConnection->lib->refDeallocate(serviceConnection); + updateFlowStatusForCancel(); + serviceConnection=0; + } + } +} + +void ServiceBrowserPrivate::reconfirmService(Service::ConstPtr /*s*/) +{ + // to do +} + +/// called when a service is added removed or changes. oldService or newService might be null (covers both add and remove) +void ServiceBrowserPrivate::serviceChanged(const Service::ConstPtr &oldService, const Service::ConstPtr &newService, ServiceBrowser *browser) +{ + emit q->serviceChanged(oldService, newService, browser); +} + +/// called when a service is added (utility method) +void ServiceBrowserPrivate::serviceAdded(const Service::ConstPtr &service, ServiceBrowser *browser) +{ + emit q->serviceAdded(service, browser); +} + +/// called when a service is removed (utility method) +void ServiceBrowserPrivate::serviceRemoved(const Service::ConstPtr &service, ServiceBrowser *browser) +{ + emit q->serviceRemoved(service, browser); +} + +/// called when the list is updated (this might collect several serviceChanged signals together) +void ServiceBrowserPrivate::servicesUpdated(ServiceBrowser *browser) +{ + emit q->servicesUpdated(browser); +} + +/// called when there is an error +void ServiceBrowserPrivate::hadError(QStringList errorMsgs, bool completeFailure) +{ + if (completeFailure) + this->failed=true; + emit q->hadError(errorMsgs,completeFailure); +} + +// ----------------- MainConnection impl ----------------- + +void MainConnection::stop(bool wait) +{ + if (m_status < Stopping) + increaseStatusTo(Stopping); + if (m_mainRef) { + int sock=lib->refSockFD(m_mainRef); + if (sock>0) shutdown(sock,SHUT_RDWR); // quite brutal, but avoids any locking/long wait in read + } + if (!m_thread) + increaseStatusTo(Stopped); + else if (wait && QThread::currentThread() != m_thread) + m_thread->wait(); +} + +MainConnection::MainConnection(): + lib(zeroConfLibInstance()->defaultLib()),m_mainRef(0), m_timeOut(LONG_TIME), m_failed(false), m_status(Starting), m_nErrs(0), m_useSelect(false) +{ + if (lib == 0){ + qDebug() << "could not load a valid library for ZeroConf::MainConnection, failing"; + } else { + m_thread = new ConnectionThread(*this); + m_thread->start(); // delay startup?? + } +} + +MainConnection::~MainConnection() +{ + stop(); + delete m_thread; + // to do +} + +bool MainConnection::increaseStatusTo(int s) +{ + int sAtt = m_status; + while (sAtt < s){ + if (m_status.testAndSetRelaxed(sAtt, s)) + return true; + sAtt = m_status; + } + return false; +} + +QMutex *MainConnection::lock() +{ + return &m_lock; +} + +void MainConnection::waitStartup() +{ + int sAtt; + while (true){ + QMutexLocker l(lock()); + sAtt = m_status; + if (sAtt >= Running) + return; + } +} + +void MainConnection::addBrowser(ServiceBrowserPrivate *browser) +{ + waitStartup(); + QStringList errs; + bool didFail; + { + QMutexLocker l(lock()); + m_browsers.append(browser); + errs=m_errors; + didFail=m_failed; + } + if (didFail || !errs.isEmpty()) + browser->hadError(errs, didFail); +} + +void MainConnection::removeBrowser(ServiceBrowserPrivate *browser) +{ + QMutexLocker l(lock()); + m_browsers.removeOne(browser); +} + +void MainConnection::updateFlowStatusForCancel(){ + flowStatus = ForceUpdateRFS; +} +void MainConnection::updateFlowStatusForFlags(DNSServiceFlags flags) +{ + if (flags & kDNSServiceFlagsMoreComing) { + if (flowStatus == NormalRFS) + flowStatus = MoreComingRFS; + } else { + flowStatus = NormalRFS; + } +} +void MainConnection::maybeUpdateLists() +{ + foreach (ServiceBrowserPrivate *sb, m_browsers) { + sb->maybeUpdateLists(); + } +} + +QString MainConnection::tr(const char *s) +{ + return QCoreApplication::translate("ZeroConf", s, 0, QCoreApplication::CodecForTr); +} + +void MainConnection::gotoValidLib(){ + while (lib){ + if (lib->isOk()) break; + appendError(QStringList(tr("MainConnection giving up on non Ok lib %1") + .arg(lib->name())), false); + } + if (!lib) { + appendError(QStringList(tr("MainConnection has no valid library, aborting connection") + .arg(lib->name())), true); + increaseStatusTo(Stopping); + } +} + +void MainConnection::abortLib(){ + if (!lib){ + appendError(QStringList(tr("MainConnection has no valid library, aborting connection") + .arg(lib->name())), true); + increaseStatusTo(Stopping); + } else if (lib->fallbackLib){ + appendError(QStringList(tr("MainConnection giving up on lib %1, switching to lib %2") + .arg(lib->name()).arg(lib->fallbackLib->name())), false); + lib=lib->fallbackLib; + m_nErrs=0; + gotoValidLib(); + } else { + appendError(QStringList(tr("MainConnection giving up on lib %1, no fallback provided, aborting connection") + .arg(lib->name())), true); + increaseStatusTo(Stopping); + } +} + +void MainConnection::createConnection() +{ + gotoValidLib(); + while (m_status <= Running) { + uint32_t version; + uint32_t size=(uint32_t)sizeof(uint32_t); + DNSServiceErrorType err=lib->getProperty(kDNSServiceProperty_DaemonVersion, &version, &size); + if (err == kDNSServiceErr_NoError){ + DNSServiceErrorType error = lib->createConnection(&m_mainRef); + if (error != kDNSServiceErr_NoError){ + appendError(QStringList(tr("MainConnection using lib %1 failed the initialization of mainRef with error %2") + .arg(lib->name()).arg(error)),false); + ++m_nErrs; + if (m_nErrs > 10) + abortLib(); + } else { + increaseStatusTo(Running); + // multithreading issues if we support multiple cycles of createConnection/destroyConnection + for (int i = m_browsers.count(); i-- != 0; ){ + ServiceBrowserPrivate *bAtt=m_browsers[i]; + if (bAtt && !bAtt->browsing) + bAtt->startBrowsing(bAtt->interfaceIndex); + } + break; + } + } else if (err == kDNSServiceErr_ServiceNotRunning) { + appendError(QStringList(tr("MainConnection using lib %1 failed because no daemon is running") + .arg(lib->name())),false); + if (lib->tryStartDaemon()) { + appendError(QStringList(tr("MainConnection using lib %1 daemon starting seem successful, continuing") + .arg(lib->name())),false); + } else { + appendError(QStringList(tr("MainConnection using lib %1 failed because no daemon is running") + .arg(lib->name())),false); + abortLib(); + } + } else { + appendError(QStringList(tr("MainConnection using lib %1 failed getProperty call with error %2") + .arg(lib->name()).arg(err)),false); + abortLib(); + } + } +} + +bool MainConnection::handleEvent() +{ + int err = lib->processResult(m_mainRef); + if (err) { + qDebug() << "DNSServiceProcessResult returned " << err; + ++m_nErrs; + return false; + } else { + m_nErrs=0; + maybeUpdateLists(); + return true; + } +} + +void MainConnection::destroyConnection() +{ + // multithreading issues if we support multiple cycles of createConnection/destroyConnection + for (int i = m_browsers.count(); i-- != 0;){ + ServiceBrowserPrivate *bAtt=m_browsers[i]; + if (bAtt->browsing) + bAtt->stopBrowsing(); + } + if (m_mainRef != 0) + lib->refDeallocate(m_mainRef); + m_mainRef=0; +} + +void MainConnection::selectLoop() +{ +#if _DNS_SD_LIBDISPATCH + { + main_queue = dispatch_get_main_queue(); + if (_mainRef) DNSServiceSetDispatchQueue(_mainRef, main_queue); + dispatch_main(); + } +#else + { + int dns_sd_fd = (m_mainRef?lib->refSockFD(m_mainRef):-1); + int nfds = dns_sd_fd + 1; + fd_set readfds; + struct timeval tv; + int result; + while (m_status < Stopping) { + while ((!m_mainRef) && m_status < Stopping) { + if (++m_nErrs > 10) + increaseStatusTo(Stopping); + destroyConnection(); + createConnection(); + dns_sd_fd = (m_mainRef?lib->refSockFD(m_mainRef):-1); + nfds = dns_sd_fd + 1; + } + FD_ZERO(&readfds); + + FD_SET(dns_sd_fd , &readfds); + + tv.tv_sec = m_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)) + handleEvent(); + } else if (result == 0) { + // we are idle... could do something productive... :) + } else if (errno != EINTR) { + qDebug() << "select() returned " << result << " errno " << errno << strerror(errno); + ++m_nErrs; + if (++m_nErrs > 5) break; + } + } + } +#endif +} + +void MainConnection::handleEvents() +{ + if (!m_status.testAndSetAcquire(Starting, Started)){ + appendError(QStringList(tr("MainConnection::handleEvents called with m_status != Starting, aborting")),true); + increaseStatusTo(Stopped); + return; + } + m_nErrs = 0; + createConnection(); + increaseStatusTo(Running); + while (m_status < Stopping) { + if (m_nErrs > 10) + increaseStatusTo(Stopping); + if (m_useSelect) { + selectLoop(); +#if _DNS_SD_LIBDISPATCH + return; +#endif + } else { + handleEvent(); + } + } + destroyConnection(); + if (m_nErrs > 0){ + QString browsersNames=(m_browsers.isEmpty()?QString():m_browsers.at(0)->serviceType)+((m_browsers.count()>1)?QString::fromLatin1(",..."):QString()); + appendError(QStringList(tr("MainConnection for [%1] accumulated %2 consecutive errors, aborting").arg(browsersNames).arg(m_nErrs)),true); + } + increaseStatusTo(Stopped); +} + +DNSServiceRef MainConnection::mainRef() +{ + while (m_status < Running){ + QThread::yieldCurrentThread(); + } + return m_mainRef; +} + +QStringList MainConnection::errors() +{ + QMutexLocker l(lock()); + return m_errors; +} + +void MainConnection::clearErrors() +{ + QMutexLocker l(lock()); + m_errors.clear(); +} + +void MainConnection::appendError(const QStringList &s,bool failure) +{ + QList browsersAtt; + bool didFail; + { + QMutexLocker l(lock()); + m_errors.append(s); + browsersAtt=m_browsers; + m_failed= failure || m_failed; + didFail=m_failed; + } + foreach (ServiceBrowserPrivate *b,browsersAtt) + b->hadError(s,didFail); +} + +bool MainConnection::isOk() +{ + return !m_failed; +} + +// ----------------- ZConfLib impl ----------------- + +bool ZConfLib::tryStartDaemon() +{ + return false; +} + +QString ZConfLib::name(){ + return QString::fromUtf8("ZeroConfLib@%1").arg(size_t(this),0,16); +} + +ZConfLib::ZConfLib(ZConfLib * f) : fallbackLib(f), m_isOk(true) +{ } + +ZConfLib::~ZConfLib() +{ } + +bool ZConfLib::isOk() +{ + return m_isOk; +} + +} // namespace Internal +} // namespace ZeroConf diff --git a/src/libs/zeroconf/servicebrowser.h b/src/libs/zeroconf/servicebrowser.h new file mode 100644 index 00000000000..99dc4da71a8 --- /dev/null +++ b/src/libs/zeroconf/servicebrowser.h @@ -0,0 +1,141 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef SERVICEBROWSER_H +#define SERVICEBROWSER_H + +#include "zeroconf_global.h" + +#include +#include +#include +#include +class QHostInfo; + +namespace ZeroConf { + +namespace Internal { +class ServiceGatherer; +class MainConnection; +class ServiceBrowserPrivate; +} + +typedef QSharedPointer MainConnectionPtr; + +typedef QHash ServiceTxtRecord; + +class ZEROCONFSHARED_EXPORT Service : public QObject +{ + Q_OBJECT + friend class Internal::ServiceGatherer; + +public: + typedef QSharedPointer ConstPtr; + typedef QSharedPointer Ptr; + + Service(const Service &o); + Service(QObject *parent = 0); + ~Service(); + + bool outdated() const { return m_outdated; } + + QString name() const { return m_name; } + QString type() const { return m_type; } + QString domain() const { return m_domain; } + QString fullName() const { return m_fullName; } + QString port() const { return m_port; } + const ServiceTxtRecord &txtRecord() const { return m_txtRecord; } + const QHostInfo *host() const { return m_host; } + int interfaceNr() const { return m_interfaceNr; } + + bool invalidate() { bool res = m_outdated; m_outdated = true; return res; } +private: + QString m_name; + QString m_type; + QString m_domain; + QString m_fullName; + QString m_port; + ServiceTxtRecord m_txtRecord; + QHostInfo *m_host; + int m_interfaceNr; + bool m_outdated; +}; + +QDebug operator<<(QDebug dbg, const Service &service); + +class ZEROCONFSHARED_EXPORT ServiceBrowser : public QObject +{ + Q_OBJECT + friend class Internal::ServiceBrowserPrivate; +public: + ServiceBrowser(const QString &serviceType, const QString &domain = QLatin1String("local."), bool adressesRequired = true, QObject *parent = 0); + // this is here to avoid to instantiate a partially defined type (MainConnectionPtr) + ServiceBrowser(const QString &serviceType, const QString &domain, bool adressesRequired, QObject *parent, MainConnectionPtr mainConnection); + ServiceBrowser(const QString &serviceType, MainConnectionPtr mainConnection); + ~ServiceBrowser(); + + MainConnectionPtr mainConnection(); + + bool startBrowsing(qint32 interfaceIndex = 0); + void stopBrowsing(); + bool isBrowsing() const; + bool didFail() const; + + const QString& serviceType() const; + const QString& domain() const; + + bool adressesAutoResolved() const; + bool adressesRequired() const; + + QList services() const; + void reconfirmService(Service::ConstPtr service); +signals: + void serviceChanged(Service::ConstPtr oldService, Service::ConstPtr newService, ServiceBrowser *browser); + void serviceAdded(Service::ConstPtr service, ServiceBrowser *browser); + void serviceRemoved(Service::ConstPtr service, ServiceBrowser *browser); + void servicesUpdated(ServiceBrowser *browser); + void hadError(QStringList errorMsgs, bool completeFailure); +private: + Internal::ServiceBrowserPrivate *d; +}; + +enum LibUsage { + UseNativeOnly = 1, + UseEmbeddedOnly, + UseNativeOrEmbedded +}; + +void initLib(LibUsage usage, const QString &libName, const QString & daemonPaths); + +} + +#endif // SERVICEBROWSER_H diff --git a/src/libs/zeroconf/servicebrowser_p.h b/src/libs/zeroconf/servicebrowser_p.h new file mode 100644 index 00000000000..7d8a451db4e --- /dev/null +++ b/src/libs/zeroconf/servicebrowser_p.h @@ -0,0 +1,299 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef SERVICEBROWSER_P_H +#define SERVICEBROWSER_P_H + +#include "dns_sd_types.h" +#include "servicebrowser.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class QHostInfo; + +namespace ZeroConf { +namespace Internal { + +// represents a zero conf library exposing the dns-sd interface +class ZConfLib { +public: + ZConfLib *fallbackLib; + + ZConfLib(ZConfLib *fallBack); + virtual ~ZConfLib(); + + virtual QString name(); + + virtual bool tryStartDaemon(); + + virtual void refDeallocate(DNSServiceRef sdRef) = 0; + virtual DNSServiceErrorType resolve(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + const char *name, const char *regtype, const char *domain, + DNSServiceResolveReply callBack, void *context) = 0; + virtual DNSServiceErrorType queryRecord(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + const char *fullname, uint16_t rrtype, uint16_t rrclass, + DNSServiceQueryRecordReply callBack, void *context) = 0; + virtual DNSServiceErrorType getAddrInfo(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + DNSServiceProtocol protocol, const char *hostname, + DNSServiceGetAddrInfoReply callBack, void *context) = 0; + + // remove txt functions from lib and always embed? + virtual uint16_t DNSSD_API txtRecordGetCount(uint16_t txtLen, const void *txtRecord) = 0; + virtual DNSServiceErrorType DNSSD_API txtRecordGetItemAtIndex(uint16_t txtLen, const void *txtRecord, + uint16_t itemIndex, uint16_t keyBufLen, char *key, + uint8_t *valueLen, const void **value) = 0; + + virtual DNSServiceErrorType reconfirmRecord(DNSServiceFlags flags, uint32_t interfaceIndex, + const char *fullname, uint16_t rrtype, uint16_t rrclass, + uint16_t rdlen, const void *rdata) = 0; // opt + virtual DNSServiceErrorType browse(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + const char *regtype, const char *domain, DNSServiceBrowseReply callBack, + void *context) = 0; + virtual DNSServiceErrorType getProperty(const char *property, void *result, uint32_t *size) = 0; + virtual DNSServiceErrorType processResult(DNSServiceRef sdRef) = 0; + virtual DNSServiceErrorType createConnection(DNSServiceRef *sdRef) = 0; + virtual int refSockFD(DNSServiceRef sdRef) = 0; + bool isOk(); + + static ZConfLib *createEmbeddedLib(const QString &daemonPath, ZConfLib *fallback=0); + static ZConfLib *createNativeLib(const QString &libName, ZConfLib *fallback=0); + static ZConfLib *defaultLib(); +private: + bool m_isOk; +}; + +/// class that gathers all needed info on a service, all its methods (creation included) are supposed to be called by the listener/reaction thread +class ServiceGatherer { +public: + QHash txtRecord; + short port; + QString serviceName; + QString serviceType; + QString domain; + QString fullName; + QString hostName; + ServiceBrowserPrivate *serviceBrowser; + QHostInfo *host; + Service::Ptr publishedService; + Service *currentService; + + typedef QSharedPointer Ptr; + + enum Status{ + ResolveConnectionFailed = 1 << 0, + ResolveConnectionActive = 1 << 1, + ResolveConnectionSuccess = 1 << 2, + TxtConnectionFailed = 1 << 3, + TxtConnectionActive = 1 << 4, + TxtConnectionSuccess = 1 << 5, + AddrConnectionFailed = 1 << 6, + AddrConnectionActive = 1 << 7, + AddrConnectionSuccess = 1 << 8 + }; + + void enactServiceChange(); + void retireService(); + Ptr gatherer(); + + void stopResolve(); + void stopTxt(); + void stopHostResolution(); + void restartResolve(); + void restartTxt(); + void restartHostResolution(); + + bool currentServiceCanBePublished(); + + ~ServiceGatherer(); + static Ptr createGatherer(const QString &newService, const QString &newType, const QString &newDomain, + const QString &fullName, uint32_t interfaceIndex, ServiceBrowserPrivate *serviceBrowser); + + void serviceResolveReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, + uint16_t port /* In network byte order */, uint16_t txtLen, + const unsigned char *rawTxtRecord); + + void txtRecordReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, + uint16_t rrclass, uint16_t txtLen, const void *rawTxtRecord, uint32_t ttl); + + void addrReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + DNSServiceErrorType errorCode, const char *hostname, const struct sockaddr *address, + uint32_t ttl); + void maybeRemove(); + void stop(); + void reload(qint32 interfaceIndex=0); + void remove(); + void reconfirm(); + ZConfLib *lib(); +private: + ServiceGatherer(const QString &newService, const QString &newType, const QString &newDomain, + const QString &fullName, uint32_t interfaceIndex, ServiceBrowserPrivate *serviceBrowser); + + DNSServiceRef resolveConnection; + DNSServiceRef txtConnection; + DNSServiceRef addrConnection; + uint32_t interfaceIndex; + + quint32 status; + QWeakPointer self; +}; + +//Q_DECLARE_METATYPE(Service::ConstPtr) + +class ConnectionThread; + +class MainConnection{ +public: + enum RequestFlowStatus { + NormalRFS, + MoreComingRFS, + ForceUpdateRFS + }; + RequestFlowStatus flowStatus; + enum { + // Note: the select() implementation on Windows (Winsock2) fails with any timeout much larger than this + LONG_TIME = 100000000 + }; + /// WARNING order matters in this enum, and status should only be changed with increaseStatusTo + enum Status { + Starting, + Started, + Running, + Stopping, + Stopped + }; + ZConfLib *lib; + + MainConnection(); + ~MainConnection(); + QMutex *lock(); + void waitStartup(); + void stop(bool wait=true); + void addBrowser(ServiceBrowserPrivate *browser); + void removeBrowser(ServiceBrowserPrivate *browser); + void updateFlowStatusForCancel(); + void updateFlowStatusForFlags(DNSServiceFlags flags); + void maybeUpdateLists(); + QString tr(const char *s); + void gotoValidLib(); + void abortLib(); + void createConnection(); + void destroyConnection(); + bool handleEvent(); + bool increaseStatusTo(int s); + void selectLoop(); + void handleEvents(); + DNSServiceRef mainRef(); + + QStringList errors(); + void clearErrors(); + void appendError(const QStringList &msgs,bool fullFailure); + bool isOk(); +private: + mutable QMutex m_lock; + QList m_browsers; + DNSServiceRef m_mainRef; + volatile int m_timeOut; + bool m_failed; + ConnectionThread *m_thread; + QAtomicInt m_status; + int m_nErrs; + bool m_useSelect; + QStringList m_errors; +}; + +class ServiceBrowserPrivate { + friend class ServiceBrowser; +public: + ServiceBrowser *q; + QString serviceType; + QString domain; + MainConnectionPtr mainConnection; + DNSServiceRef serviceConnection; + DNSServiceFlags flags; + uint32_t interfaceIndex; + QList knownServices; + QMap gatherers; + QList activeServices; + QList nextActiveServices; + QList pendingGatherers; + bool failed; + bool browsing; + bool autoResolveAddresses; + bool requireAddresses; + + DNSServiceRef mainRef(); + void updateFlowStatusForCancel(); + void updateFlowStatusForFlags(DNSServiceFlags flags); + + void pendingGathererAdd(ServiceGatherer::Ptr gatherer); + + ServiceBrowserPrivate(const QString &serviceType, const QString &domain, bool requireAddresses, + MainConnectionPtr conn); + ~ServiceBrowserPrivate(); + + void insertGatherer(const QString &fullName); + void maybeUpdateLists(); + + bool startBrowsing(quint32 interfaceIndex); + void stopBrowsing(); + void reconfirmService(Service::ConstPtr s); + + void browseReply(DNSServiceRef sdRef, DNSServiceFlags flags, + uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, + const char *regtype, const char *replyDomain); + void serviceChanged(const Service::ConstPtr &oldService, const Service::ConstPtr &newService, ServiceBrowser *browser); + void serviceAdded(const Service::ConstPtr &service, ServiceBrowser *browser); + void serviceRemoved(const Service::ConstPtr &service, ServiceBrowser *browser); + void servicesUpdated(ServiceBrowser *browser); + void hadError(QStringList errorMsgs, bool completeFailure); +}; + +class ConnectionThread: public QThread{ + MainConnection &connection; + + void run(); +public: + ConnectionThread(MainConnection &mc, QObject *parent=0); +}; +} // namespace Internal +} // namespace Zeroconf + +#endif // SERVICEBROWSER_P_H diff --git a/src/libs/zeroconf/zeroconf.pri b/src/libs/zeroconf/zeroconf.pri new file mode 100644 index 00000000000..907e304a3b8 --- /dev/null +++ b/src/libs/zeroconf/zeroconf.pri @@ -0,0 +1,3 @@ +INCLUDEPATH += $$PWD + +LIBS *= -l$$qtLibraryName(zeroconf) diff --git a/src/libs/zeroconf/zeroconf.pro b/src/libs/zeroconf/zeroconf.pro new file mode 100644 index 00000000000..a592be4f296 --- /dev/null +++ b/src/libs/zeroconf/zeroconf.pro @@ -0,0 +1,25 @@ +QT -= gui +QT += network + +TARGET = zeroconf +TEMPLATE = lib + +DEFINES += ZEROCONF_LIBRARY + +SOURCES += servicebrowser.cpp \ + embeddedLib.cpp \ + nativeLib.cpp \ + mdnsderived.cpp + +HEADERS += servicebrowser.h\ + zeroconf_global.h \ + dns_sd_types.h \ + servicebrowser_p.h \ + mdnsderived.h + +include(../../qtcreatorlibrary.pri) + +win32{ + LIBS += -lws2_32 +} + diff --git a/src/libs/zeroconf/zeroconf_global.h b/src/libs/zeroconf/zeroconf_global.h new file mode 100644 index 00000000000..fc349cfea08 --- /dev/null +++ b/src/libs/zeroconf/zeroconf_global.h @@ -0,0 +1,44 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef ZEROCONF_GLOBAL_H +#define ZEROCONF_GLOBAL_H + +#include + +#if defined(ZEROCONF_LIBRARY) +# define ZEROCONFSHARED_EXPORT Q_DECL_EXPORT +#else +# define ZEROCONFSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // ZEROCONF_GLOBAL_H