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