View Javadoc

1   // Copyright 2003-2005 Arthur van Hoff, Rick Blair
2   // Licensed under Apache License version 2.0
3   // Original license LGPL
4   
5   package javax.jmdns.impl.tasks;
6   
7   import java.util.HashSet;
8   import java.util.Set;
9   import java.util.Timer;
10  import java.util.logging.Level;
11  import java.util.logging.Logger;
12  
13  import javax.jmdns.impl.DNSIncoming;
14  import javax.jmdns.impl.DNSOutgoing;
15  import javax.jmdns.impl.DNSQuestion;
16  import javax.jmdns.impl.DNSRecord;
17  import javax.jmdns.impl.JmDNSImpl;
18  import javax.jmdns.impl.constants.DNSConstants;
19  
20  /**
21   * The Responder sends a single answer for the specified service infos and for the host name.
22   */
23  public class Responder extends DNSTask {
24      static Logger             logger = Logger.getLogger(Responder.class.getName());
25  
26      /**
27       *
28       */
29      private final DNSIncoming _in;
30  
31      /**
32       *
33       */
34      private final boolean     _unicast;
35  
36      public Responder(JmDNSImpl jmDNSImpl, DNSIncoming in, int port) {
37          super(jmDNSImpl);
38          this._in = in;
39          this._unicast = (port != DNSConstants.MDNS_PORT);
40      }
41  
42      /*
43       * (non-Javadoc)
44       * @see javax.jmdns.impl.tasks.DNSTask#getName()
45       */
46      @Override
47      public String getName() {
48          return "Responder(" + (this.getDns() != null ? this.getDns().getName() : "") + ")";
49      }
50  
51      /*
52       * (non-Javadoc)
53       * @see java.lang.Object#toString()
54       */
55      @Override
56      public String toString() {
57          return super.toString() + " incomming: " + _in;
58      }
59  
60      /*
61       * (non-Javadoc)
62       * @see javax.jmdns.impl.tasks.DNSTask#start(java.util.Timer)
63       */
64      @Override
65      public void start(Timer timer) {
66          // According to draft-cheshire-dnsext-multicastdns.txt chapter "7 Responding":
67          // We respond immediately if we know for sure, that we are the only one who can respond to the query.
68          // In all other cases, we respond within 20-120 ms.
69          //
70          // According to draft-cheshire-dnsext-multicastdns.txt chapter "6.2 Multi-Packet Known Answer Suppression":
71          // We respond after 20-120 ms if the query is truncated.
72  
73          boolean iAmTheOnlyOne = true;
74          for (DNSQuestion question : _in.getQuestions()) {
75              if (logger.isLoggable(Level.FINEST)) {
76                  logger.finest(this.getName() + "start() question=" + question);
77              }
78              iAmTheOnlyOne = question.iAmTheOnlyOne(this.getDns());
79              if (!iAmTheOnlyOne) {
80                  break;
81              }
82          }
83          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();
84          if (delay < 0) {
85              delay = 0;
86          }
87          if (logger.isLoggable(Level.FINEST)) {
88              logger.finest(this.getName() + "start() Responder chosen delay=" + delay);
89          }
90          if (!this.getDns().isCanceling() && !this.getDns().isCanceled()) {
91              timer.schedule(this, delay);
92          }
93      }
94  
95      @Override
96      public void run() {
97          this.getDns().respondToQuery(_in);
98  
99          // We use these sets to prevent duplicate records
100         Set<DNSQuestion> questions = new HashSet<DNSQuestion>();
101         Set<DNSRecord> answers = new HashSet<DNSRecord>();
102 
103         if (this.getDns().isAnnounced()) {
104             try {
105                 // Answer questions
106                 for (DNSQuestion question : _in.getQuestions()) {
107                     if (logger.isLoggable(Level.FINER)) {
108                         logger.finer(this.getName() + "run() JmDNS responding to: " + question);
109                     }
110                     // for unicast responses the question must be included
111                     if (_unicast) {
112                         // out.addQuestion(q);
113                         questions.add(question);
114                     }
115 
116                     question.addAnswers(this.getDns(), answers);
117                 }
118 
119                 // remove known answers, if the ttl is at least half of the correct value. (See Draft Cheshire chapter 7.1.).
120                 long now = System.currentTimeMillis();
121                 for (DNSRecord knownAnswer : _in.getAnswers()) {
122                     if (knownAnswer.isStale(now)) {
123                         answers.remove(knownAnswer);
124                         if (logger.isLoggable(Level.FINER)) {
125                             logger.finer(this.getName() + "JmDNS Responder Known Answer Removed");
126                         }
127                     }
128                 }
129 
130                 // respond if we have answers
131                 if (!answers.isEmpty()) {
132                     if (logger.isLoggable(Level.FINER)) {
133                         logger.finer(this.getName() + "run() JmDNS responding");
134                     }
135                     DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, !_unicast, _in.getSenderUDPPayload());
136                     out.setId(_in.getId());
137                     for (DNSQuestion question : questions) {
138                         if (question != null) {
139                             out = this.addQuestion(out, question);
140                         }
141                     }
142                     for (DNSRecord answer : answers) {
143                         if (answer != null) {
144                             out = this.addAnswer(out, _in, answer);
145 
146                         }
147                     }
148                     if (!out.isEmpty()) this.getDns().send(out);
149                 }
150                 // this.cancel();
151             } catch (Throwable e) {
152                 logger.log(Level.WARNING, this.getName() + "run() exception ", e);
153                 this.getDns().close();
154             }
155         }
156     }
157 }