blob: 606ac1d362ee13b118799be18ae135c923340450 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 Markus Alexander Kuppe.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Markus Alexander Kuppe (ecf-dev_eclipse.org <at> lemmster <dot> de) - initial API and implementation
******************************************************************************/
package org.eclipse.ecf.provider.dnssd;
import java.io.EOFException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedSet;
import org.eclipse.core.runtime.Assert;
import org.eclipse.ecf.core.ContainerConnectException;
import org.eclipse.ecf.core.events.ContainerConnectedEvent;
import org.eclipse.ecf.core.events.ContainerConnectingEvent;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.identity.IDCreateException;
import org.eclipse.ecf.core.identity.IDFactory;
import org.eclipse.ecf.core.identity.Namespace;
import org.eclipse.ecf.core.security.IConnectContext;
import org.eclipse.ecf.core.util.Trace;
import org.eclipse.ecf.discovery.DiscoveryContainerConfig;
import org.eclipse.ecf.discovery.IServiceInfo;
import org.eclipse.ecf.discovery.identity.IServiceTypeID;
import org.xbill.DNS.DClass;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.Message;
import org.xbill.DNS.NSRecord;
import org.xbill.DNS.Name;
import org.xbill.DNS.Rcode;
import org.xbill.DNS.Record;
import org.xbill.DNS.SOARecord;
import org.xbill.DNS.SRVRecord;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;
import org.xbill.DNS.Update;
public class DnsSdDiscoveryAdvertiser extends DnsSdDiscoveryContainerAdapter {
private static final String _DNS_UPDATE = "_dns-update._udp."; //$NON-NLS-1$
private static final boolean ADD = true;
private static final boolean REMOVE = false;
public DnsSdDiscoveryAdvertiser() {
super(DnsSdNamespace.NAME, new DiscoveryContainerConfig(IDFactory
.getDefault().createStringID(
DnsSdDiscoveryAdvertiser.class.getName())));
}
/* (non-Javadoc)
* @see org.eclipse.ecf.provider.dnssd.DnsSdDiscoveryLocator#registerService(org.eclipse.ecf.discovery.IServiceInfo)
*/
public void registerService(final IServiceInfo serviceInfo) {
Trace.trace(Activator.PLUGIN_ID, DnsSdDebugOptions.METHODS_TRACING, this.getClass(), "registerService(IServiceInfo serviceInfo)", "Registering service"); //$NON-NLS-1$ //$NON-NLS-2$
sendToServer(serviceInfo, ADD);
}
/* (non-Javadoc)
* @see org.eclipse.ecf.provider.dnssd.DnsSdDiscoveryLocator#unregisterService(org.eclipse.ecf.discovery.IServiceInfo)
*/
public void unregisterService(final IServiceInfo serviceInfo) {
Trace.trace(Activator.PLUGIN_ID, DnsSdDebugOptions.METHODS_TRACING, this.getClass(), "unregisterService(IServiceInfo serviceInfo)", "Unregistering service"); //$NON-NLS-1$ //$NON-NLS-2$
sendToServer(serviceInfo, REMOVE);
}
/* (non-Javadoc)
* @see org.eclipse.ecf.discovery.AbstractDiscoveryContainerAdapter#unregisterAllServices()
*/
public void unregisterAllServices() {
Trace.trace(Activator.PLUGIN_ID, DnsSdDebugOptions.METHODS_TRACING, this.getClass(), "unregisterAllServices()", "Unregistering all services"); //$NON-NLS-1$ //$NON-NLS-2$
throw new UnsupportedOperationException("Not yet implemented, see http://bugs.eclipse.org/321959"); //$NON-NLS-1$
}
/* (non-Javadoc)
* @see org.eclipse.ecf.discovery.AbstractDiscoveryContainerAdapter#purgeCache()
*/
public IServiceInfo[] purgeCache() {
// purge cache means renew resolver?
throw new UnsupportedOperationException("Not yet implemented, see http://bugs.eclipse.org/"); //$NON-NLS-1$
}
/* (non-Javadoc)
* @see org.eclipse.ecf.provider.dnssd.DnsSdDiscoveryLocator#connect(org.eclipse.ecf.core.identity.ID, org.eclipse.ecf.core.security.IConnectContext)
*/
public void connect(final ID aTargetID, final IConnectContext connectContext)
throws ContainerConnectException {
Trace.trace(Activator.PLUGIN_ID, DnsSdDebugOptions.METHODS_TRACING, this.getClass(), "connect(ID aTargetID, IConnectContext connectContext)", "connecting container"); //$NON-NLS-1$ //$NON-NLS-2$
// connect can only be called once
if (targetID != null || getConfig() == null) {
throw new ContainerConnectException(Messages.DnsSdDiscoveryAdvertiser_Container_Already_Connected);
}
//TODO convert non DnsSdServiceTypeIDs into DSTIDs
if(aTargetID == null) {
targetID = new DnsSdServiceTypeID();
} else {
final Namespace ns = getConnectNamespace();
try {
targetID = (DnsSdServiceTypeID) ns.createInstance(new Object[]{aTargetID});
} catch(IDCreateException e) {
throw new ContainerConnectException(e);
}
}
// instantiate a default resolver
if(resolver == null) {
try {
resolver = new SimpleResolver();
resolver.setTCP(true);
} catch (UnknownHostException e) {
throw new ContainerConnectException(e);
}
}
// done setting up this provider, send event
fireContainerEvent(new ContainerConnectingEvent(this.getID(), targetID,
connectContext));
fireContainerEvent(new ContainerConnectedEvent(this.getID(), targetID));
}
protected void sendToServer(final IServiceInfo serviceInfo, final boolean mode) {
Assert.isNotNull(serviceInfo);
Assert.isLegal(serviceInfo.getServiceID() instanceof DnsSdServiceID);
final DnsSdServiceID serviceID = (DnsSdServiceID) serviceInfo.getServiceID();
try {
final Record srvRecord = serviceID.toSRVRecord(); // TYPE.SRV
final Record[] txtRecords = serviceID.toTXTRecords(srvRecord); // TYPE.TXT
final Name name = serviceID.getDnsName();
final String[] registrationDomains = getRegistrationDomains(serviceID.getServiceTypeID());
for (int i = 0; i < registrationDomains.length; i++) {
final Name zone = new Name(registrationDomains[i]);
final Name fqdn = new Name(name.toString() + "." + zone.toString()); //$NON-NLS-1$
final Update update = new Update(zone);
//TYPE.SRV
if(mode == ADD) {
//TODO add absent/present condition checks
update.replace(srvRecord.withName(fqdn));
} else {
update.delete(srvRecord.withName(fqdn));
}
//TYPE.TXT
for (int j = 0; j < txtRecords.length; j++) {
if(mode == ADD) {
update.add(txtRecords[j].withName(fqdn));
} else {
update.delete(txtRecords[j].withName(fqdn));
}
}
// set up a the resolver for the given domain (a scope might use different domains)
final Collection dnsServers = getUpdateDomain(zone);
if(dnsServers.size() == 0) {
throw new DnsSdDiscoveryException(Messages.DnsSdDiscoveryAdvertiser_No_DynDns_Servers_Found);
}
for (final Iterator iterator = dnsServers.iterator(); iterator.hasNext();) {
final SRVRecord dnsServer = (SRVRecord) iterator.next();
// try to send msg and fail gracefully if more dns servers are available
final Name target = dnsServer.getTarget();
final Message response;
final InetAddress byName;
try {
byName = InetAddress.getByName(target.toString());
((SimpleResolver) resolver).setAddress(byName);
((SimpleResolver) resolver).setPort(dnsServer.getPort());
response = resolver.send(update);
} catch (UnknownHostException uhe) {
if(iterator.hasNext()) {
continue;
} else {
throw new DnsSdDiscoveryException(uhe);
}
} catch (EOFException eof) {
if(iterator.hasNext()) {
continue;
} else {
throw new DnsSdDiscoveryException(eof);
}
}
// catch some errors and fall back to the next dnsServer
if (response.getRcode() != Rcode.NOERROR) {
if(iterator.hasNext()) {
continue;
} else {
throw DnsSdDiscoveryException.getException(response.getRcode());
}
}
}
}
} catch (Exception e) {
throw new DnsSdDiscoveryException(e);
}
}
protected Collection getUpdateDomain(final Name zone) throws TextParseException {
Trace.trace(Activator.PLUGIN_ID, DnsSdDebugOptions.METHODS_TRACING, this.getClass(), "getUpdateDomain(Name zone)", "Getting update domain"); //$NON-NLS-1$ //$NON-NLS-2$
// query for special "_dns-update" SRV records which mark the server to use for dyndns
final Lookup query = new Lookup(_DNS_UPDATE + zone, Type.SRV);
// use the SRV record with the lowest priority/weight first
final SortedSet srvRecords = getSRVRecord(query, new SRVRecordComparator());
// if no dedicated "_dns-update" server is configured, fall back to regular authoritative server
if(srvRecords.size() == 0) {
Trace.trace(Activator.PLUGIN_ID, DnsSdDebugOptions.METHODS_TRACING, this.getClass(), "getUpdateDomain(Name zone)", "Found no _dns-update SRV records in zone"); //$NON-NLS-1$ //$NON-NLS-2$
return getAuthoritativeNameServer(zone);
}
return srvRecords;
}
protected Collection getAuthoritativeNameServer(final Name zone) throws TextParseException {
Trace.trace(Activator.PLUGIN_ID, DnsSdDebugOptions.METHODS_TRACING, this.getClass(), "getAuthoritativeNameServer(Name zone)", "Trying to find authoritative name server"); //$NON-NLS-1$ //$NON-NLS-2$
final Set result = new HashSet();
final Name name = new Name(_DNS_UPDATE + zone);
//query for NS records
Lookup query = new Lookup(zone, Type.NS);
query.setResolver(resolver);
Record[] queryResult = query.run();
//TODO file bug upstream that queryResult may never be null
int length = queryResult == null ? 0 : queryResult.length;
for (int j = 0; j < length; j++) {
final Record record = queryResult[j];
if(record instanceof NSRecord) {
final NSRecord nsRecord = (NSRecord) record;
final Name target = nsRecord.getTarget();
result.add(new SRVRecord(name, DClass.IN, nsRecord.getTTL(), 0, 0, SimpleResolver.DEFAULT_PORT, target));
}
}
//query for primary ns in SOA record (may overwrite/be equal to one of the ns records)
query = new Lookup(zone, Type.SOA);
query.setResolver(resolver);
queryResult = query.run();
//TODO file bug upstream that queryResult may never be null
length = queryResult == null ? 0 : queryResult.length;
for (int j = 0; j < length; j++) {
final Record record = queryResult[j];
if(record instanceof SOARecord) {
final SOARecord soaRecord = (SOARecord) record;
result.add(new SRVRecord(name, DClass.IN, soaRecord.getTTL(), 0, 0, SimpleResolver.DEFAULT_PORT, soaRecord.getHost()));
}
}
return result;
}
protected String[] getRegistrationDomains(final IServiceTypeID aServiceTypeId) {
Trace.trace(Activator.PLUGIN_ID, DnsSdDebugOptions.METHODS_TRACING, this.getClass(), "getRegistrationDomains(IServiceTypeID aServiceTypeId)", "Getting registration domains"); //$NON-NLS-1$ //$NON-NLS-2$
final String[] rrs = new String[] {BnRDnsSdServiceTypeID.REG_DOMAINS, BnRDnsSdServiceTypeID.DEFAULT_REG_DOMAIN};
final Collection registrationDomains = getBrowsingOrRegistrationDomains(aServiceTypeId, rrs);
final String[] scopes = aServiceTypeId.getScopes();
for (int i = 0; i < scopes.length; i++) {
scopes[i] = scopes[i].concat("."); //$NON-NLS-1$
}
return registrationDomains.size() == 0 ? scopes : (String[]) registrationDomains.toArray(new String[registrationDomains.size()]);
}
/* (non-Javadoc)
* @see org.eclipse.ecf.discovery.AbstractDiscoveryContainerAdapter#getContainerName()
*/
public String getContainerName() {
return Activator.DISCOVERY_CONTAINER_NAME_VALUE + Activator.ADVERTISER;
}
}