//Copyright 2003-2005 Arthur van Hoff, Rick Blair //Licensed under Apache License version 2.0 //Original license LGPL package javax.jmdns.impl.tasks; import java.net.InetAddress; import java.util.HashSet; import java.util.Iterator; import java.util.TimerTask; //import java.util.logging.Level; //import java.util.logging.Logger; import javax.jmdns.impl.DNSConstants; import javax.jmdns.impl.DNSEntry; import javax.jmdns.impl.DNSIncoming; import javax.jmdns.impl.DNSOutgoing; import javax.jmdns.impl.DNSQuestion; import javax.jmdns.impl.DNSRecord; import javax.jmdns.impl.DNSState; import javax.jmdns.impl.JmDNSImpl; import javax.jmdns.impl.ServiceInfoImpl; /** * The Responder sends a single answer for the specified service infos * and for the host name. */ public class Responder extends TimerTask { // static Logger logger = Logger.getLogger(Responder.class.getName()); /** * */ private final JmDNSImpl jmDNSImpl; private DNSIncoming in; private InetAddress addr; private int port; public Responder(JmDNSImpl jmDNSImpl, DNSIncoming in, InetAddress addr, int port) { this.jmDNSImpl = jmDNSImpl; this.in = in; this.addr = addr; this.port = port; } public void start() { // According to draft-cheshire-dnsext-multicastdns.txt // chapter "8 Responding": // We respond immediately if we know for sure, that we are // the only one who can respond to the query. // In all other cases, we respond within 20-120 ms. // // According to draft-cheshire-dnsext-multicastdns.txt // chapter "7.2 Multi-Packet Known Answer Suppression": // We respond after 20-120 ms if the query is truncated. boolean iAmTheOnlyOne = true; for (Iterator i = in.getQuestions().iterator(); i.hasNext();) { DNSEntry entry = (DNSEntry) i.next(); if (entry instanceof DNSQuestion) { DNSQuestion q = (DNSQuestion) entry; // logger.finest("start() question=" + q); iAmTheOnlyOne &= (q.getType() == DNSConstants.TYPE_SRV || q.getType() == DNSConstants.TYPE_TXT || q.getType() == DNSConstants.TYPE_A || q.getType() == DNSConstants.TYPE_AAAA || this.jmDNSImpl.getLocalHost().getName().equalsIgnoreCase(q.getName()) || this.jmDNSImpl.getServices().containsKey(q.getName().toLowerCase())); if (!iAmTheOnlyOne) { break; } } } int delay = (iAmTheOnlyOne && !in.isTruncated()) ? 0 : DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + JmDNSImpl.getRandom().nextInt(DNSConstants.RESPONSE_MAX_WAIT_INTERVAL - DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + 1) - in.elapseSinceArrival(); if (delay < 0) { delay = 0; } // logger.finest("start() Responder chosen delay=" + delay); this.jmDNSImpl.schedule(this, delay); } public void run() { synchronized (this.jmDNSImpl.getIoLock()) { if (this.jmDNSImpl.getPlannedAnswer() == in) { this.jmDNSImpl.setPlannedAnswer(null); } // We use these sets to prevent duplicate records // FIXME - This should be moved into DNSOutgoing HashSet questions = new HashSet(); HashSet answers = new HashSet(); if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED) { try { boolean isUnicast = (port != DNSConstants.MDNS_PORT); // Answer questions for (Iterator iterator = in.getQuestions().iterator(); iterator.hasNext();) { DNSEntry entry = (DNSEntry) iterator.next(); if (entry instanceof DNSQuestion) { DNSQuestion q = (DNSQuestion) entry; // for unicast responses the question must be included if (isUnicast) { //out.addQuestion(q); questions.add(q); } int type = q.getType(); if (type == DNSConstants.TYPE_ANY || type == DNSConstants.TYPE_SRV) { // I ama not sure of why there is a special case here [PJYF Oct 15 2004] if (this.jmDNSImpl.getLocalHost().getName().equalsIgnoreCase(q.getName())) { // type = DNSConstants.TYPE_A; DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord(); if (answer != null) { answers.add(answer); } answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord(); if (answer != null) { answers.add(answer); } type = DNSConstants.TYPE_IGNORE; } else { if (this.jmDNSImpl.getServiceTypes().containsKey(q.getName().toLowerCase())) { type = DNSConstants.TYPE_PTR; } } } switch (type) { case DNSConstants.TYPE_A: { // Answer a query for a domain name //out = addAnswer( in, addr, port, out, host ); DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord(); if (answer != null) { answers.add(answer); } break; } case DNSConstants.TYPE_AAAA: { // Answer a query for a domain name DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord(); if (answer != null) { answers.add(answer); } break; } case DNSConstants.TYPE_PTR: { // Answer a query for services of a given type // find matching services for (Iterator serviceIterator = this.jmDNSImpl.getServices().values().iterator(); serviceIterator.hasNext();) { ServiceInfoImpl info = (ServiceInfoImpl) serviceIterator.next(); if (info.getState() == DNSState.ANNOUNCED) { if (q.getName().equalsIgnoreCase(info.getType())) { DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord(); if (answer != null) { answers.add(answer); } answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord(); if (answer != null) { answers.add(answer); } answers.add(new DNSRecord.Pointer(info.getType(), DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName())); answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.getPriority(), info.getWeight(), info.getPort(), this.jmDNSImpl.getLocalHost().getName())); answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.getText())); } } } if (q.getName().equalsIgnoreCase("_services" + DNSConstants.DNS_META_QUERY + "local.")) { for (Iterator serviceTypeIterator = this.jmDNSImpl.getServiceTypes().values().iterator(); serviceTypeIterator.hasNext();) { answers.add(new DNSRecord.Pointer("_services" + DNSConstants.DNS_META_QUERY + "local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) serviceTypeIterator.next())); } } break; } case DNSConstants.TYPE_SRV: case DNSConstants.TYPE_ANY: case DNSConstants.TYPE_TXT: { ServiceInfoImpl info = (ServiceInfoImpl) this.jmDNSImpl.getServices().get(q.getName().toLowerCase()); if (info != null && info.getState() == DNSState.ANNOUNCED) { DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord(); if (answer != null) { answers.add(answer); } answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord(); if (answer != null) { answers.add(answer); } answers.add(new DNSRecord.Pointer(info.getType(), DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName())); answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.getPriority(), info.getWeight(), info.getPort(), this.jmDNSImpl.getLocalHost().getName())); answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.getText())); } break; } default : { //System.out.println("JmDNSResponder.unhandled query:"+q); break; } } } } // remove known answers, if the ttl is at least half of // the correct value. (See Draft Cheshire chapter 7.1.). for (Iterator i = in.getAnswers().iterator(); i.hasNext();) { DNSRecord knownAnswer = (DNSRecord) i.next(); if (knownAnswer.getTtl() > DNSConstants.DNS_TTL / 2 && answers.remove(knownAnswer)) { // logger.log(Level.FINER, "JmDNS Responder Known Answer Removed"); } } // responde if we have answers if (answers.size() != 0) { // logger.finer("run() JmDNS responding"); DNSOutgoing out = null; if (isUnicast) { out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false); } for (Iterator i = questions.iterator(); i.hasNext();) { out.addQuestion((DNSQuestion) i.next()); } for (Iterator i = answers.iterator(); i.hasNext();) { out = this.jmDNSImpl.addAnswer(in, addr, port, out, (DNSRecord) i.next()); } this.jmDNSImpl.send(out); } this.cancel(); } catch (Throwable e) { // logger.log(Level.WARNING, "run() exception ", e); this.jmDNSImpl.close(); } } } } }