/* -*- Mode: Java; tab-width: 4 -*-
*
* Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
Change History (most recent first):
$Log: DNSSD.java,v $
Revision 1.11 2006/08/14 23:25:08 cheshire
Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0
Revision 1.10 2006/06/20 23:05:55 rpantos
<rdar://problem/3839132> Java needs to implement DNSServiceRegisterRecord equivalent
Revision 1.9 2005/10/26 01:52:24 cheshire
<rdar://problem/4316286> Race condition in Java code (doesn't work at all on Linux)
Revision 1.8 2005/07/11 01:55:21 cheshire
<rdar://problem/4175511> Race condition in Java API
Revision 1.7 2005/07/05 13:01:52 cheshire
<rdar://problem/4169791> If mDNSResponder daemon is stopped, Java API spins, burning CPU time
Revision 1.6 2005/07/05 00:02:25 cheshire
Add missing comma
Revision 1.5 2005/07/04 21:13:47 cheshire
Add missing error message strings
Revision 1.4 2004/12/11 03:00:59 rpantos
<rdar://problem/3907498> Java DNSRecord API should be cleaned up
Revision 1.3 2004/11/12 03:23:08 rpantos
rdar://problem/3809541 implement getIfIndexForName, getNameForIfIndex.
Revision 1.2 2004/05/20 17:43:18 cheshire
Fix invalid UTF-8 characters in file
Revision 1.1 2004/04/30 16:32:34 rpantos
First checked in.
This file declares and implements DNSSD, the central Java factory class
for doing DNS Service Discovery. It includes the mostly-abstract public
interface, as well as the Apple* implementation subclasses.
*/
/*
* ident "%Z%%M% %I% %E% SMI"
*/
/**
DNSSD provides access to DNS Service Discovery features of ZeroConf networking.<P>
It is a factory class that is used to invoke registration and discovery-related
operations. Most operations are non-blocking; clients are called back through an interface
with the result of an operation. Callbacks are made from a separate worker thread.<P>
For example, in this program<P>
<PRE><CODE>
class MyClient implements BrowseListener {
void lookForWebServers() {
myBrowser = DNSSD.browse("_http_.tcp", this);
}
public void serviceFound(DNSSDService browser, int flags, int ifIndex,
String serviceName, String regType, String domain) {}
...
}</CODE></PRE>
<CODE>MyClient.serviceFound()</CODE> would be called for every HTTP server discovered in the
default browse domain(s).
*/
abstract public class DNSSD
{
/** Flag indicates to a {@link BrowseListener} that another result is
queued. Applications should not update their UI to display browse
results if the MORE_COMING flag is set; they will be called at least once
more with the flag clear.
*/
/** If flag is set in a {@link DomainListener} callback, indicates that the result is the default domain. */
/** If flag is set, a name conflict will trigger an exception when registering non-shared records.<P>
A name must be explicitly specified when registering a service if this bit is set
(i.e. the default name may not not be used).
*/
/** If flag is set, allow multiple records with this name on the network (e.g. PTR records)
when registering individual records on a {@link DNSSDRegistration}.
*/
/** If flag is set, records with this name must be unique on the network (e.g. SRV records). */
/** Set flag when calling enumerateDomains() to restrict results to domains recommended for browsing. */
/** Set flag when calling enumerateDomains() to restrict results to domains recommended for registration. */
/** Maximum length, in bytes, of a domain name represented as an escaped C-String. */
/** Pass for ifIndex to specify all available interfaces. */
/** Pass for ifIndex to specify the localhost interface. */
/** Browse for instances of a service.<P>
Note: browsing consumes network bandwidth. Call {@link DNSSDService#stop} when you have finished browsing.<P>
@param flags
Currently ignored, reserved for future use.
<P>
@param ifIndex
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. Pass -1 to only browse for services provided on the local host.
<P>
@param regType
The registration type being browsed for followed by the protocol, separated by a
dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp".
<P>
@param 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).
<P>
@param listener
This object will get called when instances of the service are discovered (or disappear).
<P>
@return A {@link DNSSDService} that represents the active browse operation.
@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
@see RuntimePermission
*/
public static DNSSDService browse( int flags, int ifIndex, String regType, String domain, BrowseListener listener)
throws DNSSDException
/** Browse for instances of a service. Use default flags, ifIndex and domain.<P>
@param regType
The registration type being browsed for followed by the protocol, separated by a
dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp".
<P>
@param listener
This object will get called when instances of the service are discovered (or disappear).
<P>
@return A {@link DNSSDService} that represents the active browse operation.
@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
@see RuntimePermission
*/
throws DNSSDException
/** Resolve a service name discovered via browse() to a target host name, port number, and txt record.<P>
Note: Applications should NOT use resolve() solely for txt record monitoring - use
queryRecord() instead, as it is more efficient for this task.<P>
Note: When the desired results have been returned, the client MUST terminate the resolve by
calling {@link DNSSDService#stop}.<P>
Note: resolve() behaves correctly for typical services that have a single SRV record and
a single TXT record (the TXT record may be empty.) To resolve non-standard services with
multiple SRV or TXT records, use queryRecord().<P>
@param flags
Currently ignored, reserved for future use.
<P>
@param ifIndex
The interface on which to resolve the service. The client should
pass the interface on which the serviceName was discovered (i.e.
the ifIndex passed to the serviceFound() callback)
or 0 to resolve the named service on all available interfaces.
<P>
@param serviceName
The servicename to be resolved.
<P>
@param regType
The registration type being resolved followed by the protocol, separated by a
dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp".
<P>
@param domain
The domain on which the service is registered, i.e. the domain passed
to the serviceFound() callback.
<P>
@param listener
This object will get called when the service is resolved.
<P>
@return A {@link DNSSDService} that represents the active resolve operation.
@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
@see RuntimePermission
*/
throws DNSSDException
/** Register a service, to be discovered via browse() and resolve() calls.<P>
@param flags
Possible values are: NO_AUTO_RENAME.
<P>
@param ifIndex
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. Pass -1 to register a service only on the local
machine (service will not be visible to remote hosts).
<P>
@param serviceName
If non-null, specifies the service name to be registered.
Applications need not specify a name, in which case the
computer name is used (this name is communicated to the client via
the serviceRegistered() callback).
<P>
@param regType
The registration type being registered followed by the protocol, separated by a
dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp".
<P>
@param 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).
<P>
@param 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 {@link DNSSDRegistration#addRecord}.
<P>
@param port
The port 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.
<P>
@param txtRecord
The txt record rdata. May be null. Note that a non-null txtRecord
MUST be a properly formatted DNS TXT record, i.e. <length byte> <data>
<length byte> <data> ...
<P>
@param listener
This object will get called when the service is registered.
<P>
@return A {@link DNSSDRegistration} that controls the active registration.
@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
@see RuntimePermission
*/
public static DNSSDRegistration register( int flags, int ifIndex, String serviceName, String regType,
throws DNSSDException
{ return getInstance()._register( flags, ifIndex, serviceName, regType, domain, host, port, txtRecord, listener); }
/** Register a service, to be discovered via browse() and resolve() calls. Use default flags, ifIndex, domain, host and txtRecord.<P>
@param serviceName
If non-null, specifies the service name to be registered.
Applications need not specify a name, in which case the
computer name is used (this name is communicated to the client via
the serviceRegistered() callback).
<P>
@param regType
The registration type being registered followed by the protocol, separated by a
dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp".
<P>
@param port
The port 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.
<P>
@param listener
This object will get called when the service is registered.
<P>
@return A {@link DNSSDRegistration} that controls the active registration.
@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
@see RuntimePermission
*/
public static DNSSDRegistration register( String serviceName, String regType, int port, RegisterListener listener)
throws DNSSDException
/** Create a {@link DNSSDRecordRegistrar} allowing efficient registration of
multiple individual records.<P>
<P>
@return A {@link DNSSDRecordRegistrar} that can be used to register records.
@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
@see RuntimePermission
*/
throws DNSSDException
/** Query for an arbitrary DNS record.<P>
@param flags
Possible values are: MORE_COMING.
<P>
@param ifIndex
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. Passing -1 causes the name to be queried for only on the
local host.
<P>
@param serviceName
The full domain name of the resource record to be queried for.
<P>
@param rrtype
The numerical type of the resource record to be queried for (e.g. PTR, SRV, etc)
as defined in nameser.h.
<P>
@param rrclass
The class of the resource record, as defined in nameser.h
(usually 1 for the Internet class).
<P>
@param listener
This object will get called when the query completes.
<P>
@return A {@link DNSSDService} that controls the active query.
@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
@see RuntimePermission
*/
throws DNSSDException
/** Asynchronously enumerate domains available for browsing and registration.<P>
Currently, the only domain returned is "local.", but other domains will be returned in future.<P>
The enumeration MUST be cancelled by calling {@link DNSSDService#stop} when no more domains
are to be found.<P>
@param flags
Possible values are: BROWSE_DOMAINS, REGISTRATION_DOMAINS.
<P>
@param ifIndex
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.
<P>
@param listener
This object will get called when domains are found.
<P>
@return A {@link DNSSDService} that controls the active enumeration.
@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
@see RuntimePermission
*/
throws DNSSDException
/** Concatenate a three-part domain name (as provided to the listeners) into a
properly-escaped full domain name. Note that strings passed to listeners are
ALREADY ESCAPED where necessary.<P>
@param serviceName
The service name - any dots or slashes must NOT be escaped.
May be null (to construct a PTR record name, e.g. "_ftp._tcp.apple.com").
<P>
@param regType
The registration type followed by the protocol, separated by a dot (e.g. "_ftp._tcp").
<P>
@param domain
The domain name, e.g. "apple.com". Any literal dots or backslashes must be escaped.
<P>
@return The full domain name.
@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
@see RuntimePermission
*/
throws DNSSDException
/** 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.) <P>
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.<P>
@param flags
Currently unused, reserved for future use.
<P>
@param ifIndex
If non-zero, specifies the interface on which to reconfirm the record
(the index for a given interface is determined via the if_nametoindex()
family of calls.) Passing 0 causes the name to be reconfirmed on all
interfaces. Passing -1 causes the name to be reconfirmed only on the
local host.
<P>
@param fullName
The resource record's full domain name.
<P>
@param rrtype
The resource record's type (e.g. PTR, SRV, etc) as defined in nameser.h.
<P>
@param rrclass
The class of the resource record, as defined in nameser.h (usually 1).
<P>
@param rdata
The raw rdata of the resource record.
@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
@see RuntimePermission
*/
/** Return the canonical name of a particular interface index.<P>
@param ifIndex
A valid interface index. Must not be ALL_INTERFACES.
<P>
@return The name of the interface, which should match java.net.NetworkInterface.getName().
@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
@see RuntimePermission
*/
/** Return the index of a named interface.<P>
@param ifName
A valid interface name. An example is java.net.NetworkInterface.getName().
<P>
@return The interface index.
@throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>.
@see RuntimePermission
*/
/** Return the single instance of DNSSD. */
{
return fInstance;
}
abstract protected DNSSDService _makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener listener)
throws DNSSDException;
abstract protected DNSSDService _resolve( int flags, int ifIndex, String serviceName, String regType,
throws DNSSDException;
abstract protected DNSSDRegistration _register( int flags, int ifIndex, String serviceName, String regType,
throws DNSSDException;
throws DNSSDException;
abstract protected DNSSDService _queryRecord( int flags, int ifIndex, String serviceName, int rrtype,
throws DNSSDException;
throws DNSSDException;
throws DNSSDException;
static
{
try
{
}
catch( Exception e )
{
throw new InternalError( "cannot instantiate DNSSD" + e );
}
}
}
// Concrete implementation of DNSSDException
{
{
"UNKNOWN",
"NO_SUCH_NAME",
"NO_MEMORY",
"BAD_PARAM",
"BAD_REFERENCE",
"BAD_STATE",
"BAD_FLAGS",
"UNSUPPORTED",
"NOT_INITIALIZED",
"NO_CACHE",
"ALREADY_REGISTERED",
"NAME_CONFLICT",
"INVALID",
"FIREWALL",
"INCOMPATIBLE",
"BAD_INTERFACE_INDEX",
"REFUSED",
"NOSUCHRECORD",
"NOAUTH",
"NOSUCHKEY",
"NATTRAVERSAL",
"DOUBLENAT",
"BADTIME",
"BADSIG",
"BADKEY",
"TRANSIENT"
};
{
}
else
}
protected int fErrorCode;
}
// The concrete, default implementation.
{
static
{
throw new InternalError( "cannot instantiate DNSSD: " + new AppleDNSSDException( libInitResult).getMessage());
}
protected DNSSDService _makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener client)
throws DNSSDException
{
}
throws DNSSDException
{
}
throws DNSSDException
{
}
throws DNSSDException
{
return new AppleRecordRegistrar( listener);
}
throws DNSSDException
{
}
throws DNSSDException
{
}
throws DNSSDException
{
String[] responseHolder = new String[1]; // lame maneuver to get around Java's lack of reference parameters
if ( rc != 0)
throw new AppleDNSSDException( rc);
return responseHolder[0];
}
{
}
{
return GetNameForIfIndex( ifIndex);
}
{
return GetIfIndexForName( ifName);
}
protected native int ConstructName( String serviceName, String regType, String domain, String[] pOut);
}
{
/* Block until data arrives, or one second passes. Returns 1 if data present, 0 otherwise. */
protected native int BlockForData();
/* Call ProcessResults when data appears on socket descriptor. */
protected native int ProcessResults();
protected synchronized native void HaltOperation();
{
if ( rc != 0)
throw new AppleDNSSDException( rc);
}
public void run()
{
while ( true )
{
// Note: We want to allow our DNS-SD operation to be stopped from other threads, so we have to
// block waiting for data *outside* the synchronized section. Because we're doing this unsynchronized
// we have to write some careful code. Suppose our DNS-SD operation is stopped from some other thread,
// and then immediately afterwards that thread (or some third, unrelated thread) starts a new DNS-SD
// operation. The Unix kernel always allocates the lowest available file descriptor to a new socket,
// so the same file descriptor is highly likely to be reused for the new operation, and if our old
// stale ServiceThread accidentally consumes bytes off that new socket we'll get really messed up.
// To guard against that, before calling ProcessResults we check to ensure that our
// fNativeContext has not been deleted, which is a telltale sign that our operation was stopped.
// After calling ProcessResults we check again, because it's extremely common for callback
// functions to stop their own operation and start others. For example, a resolveListener callback
// may well stop the resolve and then start a QueryRecord call to monitor the TXT record.
//
// The remaining risk is that between our checking fNativeContext and calling ProcessResults(),
// some other thread could stop the operation and start a new one using same file descriptor, and
// we wouldn't know. To prevent this, the AppleService object's HaltOperation() routine is declared
// synchronized and we perform our checks synchronized on the AppleService object, which ensures
// that HaltOperation() can't execute while we're doing it. Because Java locks are re-entrant this
// locking DOESN'T prevent the callback routine from stopping its own operation, but DOES prevent
// any other thread from stopping it until after the callback has completed and returned to us here.
int result = BlockForData();
synchronized (this)
{
if (fNativeContext == 0) break; // Some other thread stopped our DNSSD operation; time to terminate this thread
result = ProcessResults();
if (fNativeContext == 0) break; // Event listener stopped its own DNSSD operation; terminate this thread
}
}
}
}
{
throws DNSSDException
{
super(client);
if ( !AppleDNSSD.hasAutoCallbacks)
}
// Sets fNativeContext. Returns non-zero on error.
}
{
throws DNSSDException
{
super(client);
if ( !AppleDNSSD.hasAutoCallbacks)
}
// Sets fNativeContext. Returns non-zero on error.
}
// An AppleDNSRecord is a simple wrapper around a dns_sd DNSRecord.
{
{
}
throws DNSSDException
{
}
public void remove()
throws DNSSDException
{
this.ThrowOnErr( this.Remove());
}
{
if ( rc != 0)
throw new AppleDNSSDException( rc);
}
protected native int Remove();
}
{
public AppleRegistration( int flags, int ifIndex, String serviceName, String regType, String domain,
throws DNSSDException
{
super(client);
this.ThrowOnErr( this.BeginRegister( ifIndex, flags, serviceName, regType, domain, host, port, txtRecord));
if ( !AppleDNSSD.hasAutoCallbacks)
}
throws DNSSDException
{
return newRecord;
}
throws DNSSDException
{
return new AppleDNSRecord( this); // A record with ref 0 is understood to be primary TXT record
}
// Sets fNativeContext. Returns non-zero on error.
// Sets fNativeContext. Returns non-zero on error.
protected native int AddRecord( int flags, int rrType, byte[] rData, int ttl, AppleDNSRecord destObj);
}
{
throws DNSSDException
{
super(listener);
this.ThrowOnErr( this.CreateConnection());
if ( !AppleDNSSD.hasAutoCallbacks)
}
throws DNSSDException
{
this.ThrowOnErr( this.RegisterRecord( flags, ifIndex, fullname, rrtype, rrclass, rdata, ttl, newRecord));
return newRecord;
}
// Sets fNativeContext. Returns non-zero on error.
protected native int CreateConnection();
// Sets fNativeContext. Returns non-zero on error.
}
{
throws DNSSDException
{
super(client);
if ( !AppleDNSSD.hasAutoCallbacks)
}
// Sets fNativeContext. Returns non-zero on error.
protected native int CreateQuery( int flags, int ifIndex, String serviceName, int rrtype, int rrclass);
}
{
throws DNSSDException
{
super(client);
if ( !AppleDNSSD.hasAutoCallbacks)
}
// Sets fNativeContext. Returns non-zero on error.
}