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