001 /**
002 * Copyright 2003-2005 Arthur van Hoff, Rick Blair
003 *
004 * Licensed to the Apache Software Foundation (ASF) under one or more
005 * contributor license agreements. See the NOTICE file distributed with
006 * this work for additional information regarding copyright ownership.
007 * The ASF licenses this file to You under the Apache License, Version 2.0
008 * (the "License"); you may not use this file except in compliance with
009 * the License. You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019 package org.apache.activemq.jmdns;
020
021 import java.io.IOException;
022 import java.net.DatagramPacket;
023 import java.net.InetAddress;
024 import java.net.MulticastSocket;
025 import java.util.*;
026 import java.util.logging.Level;
027 import java.util.logging.Logger;
028
029 // REMIND: multiple IP addresses
030
031 /**
032 * mDNS implementation in Java.
033 *
034 * @version %I%, %G%
035 */
036 public class JmDNS {
037 private static Logger logger = Logger.getLogger(JmDNS.class.toString());
038 /**
039 * The version of JmDNS.
040 */
041 public static String VERSION = "2.0";
042
043 /**
044 * This is the multicast group, we are listening to for multicast DNS messages.
045 */
046 private InetAddress group;
047 /**
048 * This is our multicast socket.
049 */
050 private MulticastSocket socket;
051
052 /**
053 * Used to fix live lock problem on unregester.
054 */
055
056 protected boolean closed = false;
057
058 /**
059 * Holds instances of JmDNS.DNSListener.
060 * Must by a synchronized collection, because it is updated from
061 * concurrent threads.
062 */
063 private List listeners;
064 /**
065 * Holds instances of ServiceListener's.
066 * Keys are Strings holding a fully qualified service type.
067 * Values are LinkedList's of ServiceListener's.
068 */
069 private Map serviceListeners;
070 /**
071 * Holds instances of ServiceTypeListener's.
072 */
073 private List typeListeners;
074
075
076 /**
077 * Cache for DNSEntry's.
078 */
079 private DNSCache cache;
080
081 /**
082 * This hashtable holds the services that have been registered.
083 * Keys are instances of String which hold an all lower-case version of the
084 * fully qualified service name.
085 * Values are instances of ServiceInfo.
086 */
087 Map services;
088
089 /**
090 * This hashtable holds the service types that have been registered or
091 * that have been received in an incoming datagram.
092 * Keys are instances of String which hold an all lower-case version of the
093 * fully qualified service type.
094 * Values hold the fully qualified service type.
095 */
096 Map serviceTypes;
097 /**
098 * This is the shutdown hook, we registered with the java runtime.
099 */
100 private Thread shutdown;
101
102 /**
103 * Handle on the local host
104 */
105 HostInfo localHost;
106
107 private Thread incomingListener = null;
108
109 /**
110 * Throttle count.
111 * This is used to count the overall number of probes sent by JmDNS.
112 * When the last throttle increment happened .
113 */
114 private int throttle;
115 /**
116 * Last throttle increment.
117 */
118 private long lastThrottleIncrement;
119
120 /**
121 * The timer is used to dispatch all outgoing messages of JmDNS.
122 * It is also used to dispatch maintenance tasks for the DNS cache.
123 */
124 private Timer timer;
125
126 /**
127 * The source for random values.
128 * This is used to introduce random delays in responses. This reduces the
129 * potential for collisions on the network.
130 */
131 private final static Random random = new Random();
132
133 /**
134 * This lock is used to coordinate processing of incoming and outgoing
135 * messages. This is needed, because the Rendezvous Conformance Test
136 * does not forgive race conditions.
137 */
138 private Object ioLock = new Object();
139
140 /**
141 * If an incoming package which needs an answer is truncated, we store it
142 * here. We add more incoming DNSRecords to it, until the JmDNS.Responder
143 * timer picks it up.
144 * Remind: This does not work well with multiple planned answers for packages
145 * that came in from different clients.
146 */
147 private DNSIncoming plannedAnswer;
148
149 // State machine
150 /**
151 * The state of JmDNS.
152 * <p/>
153 * For proper handling of concurrency, this variable must be
154 * changed only using methods advanceState(), revertState() and cancel().
155 */
156 private DNSState state = DNSState.PROBING_1;
157
158 /**
159 * Timer task associated to the host name.
160 * This is used to prevent from having multiple tasks associated to the host
161 * name at the same time.
162 */
163 TimerTask task;
164
165 /**
166 * This hashtable is used to maintain a list of service types being collected
167 * by this JmDNS instance.
168 * The key of the hashtable is a service type name, the value is an instance
169 * of JmDNS.ServiceCollector.
170 *
171 * @see #list
172 */
173 private HashMap serviceCollectors = new HashMap();
174
175 /**
176 * Create an instance of JmDNS.
177 */
178 public JmDNS() throws IOException {
179 logger.finer("JmDNS instance created");
180 try {
181 InetAddress addr = InetAddress.getLocalHost();
182 init(addr.isLoopbackAddress() ? null : addr, addr.getHostName()); // [PJYF Oct 14 2004] Why do we disallow the loopback address?
183 } catch (IOException e) {
184 init(null, "computer");
185 }
186 }
187
188 /**
189 * Create an instance of JmDNS and bind it to a
190 * specific network interface given its IP-address.
191 */
192 public JmDNS(InetAddress addr) throws IOException {
193 try {
194 init(addr, addr.getHostName());
195 } catch (IOException e) {
196 init(null, "computer");
197 }
198 }
199
200 /**
201 * Initialize everything.
202 *
203 * @param address The interface to which JmDNS binds to.
204 * @param name The host name of the interface.
205 */
206 private void init(InetAddress address, String name) throws IOException {
207 // A host name with "." is illegal. so strip off everything and append .local.
208 int idx = name.indexOf(".");
209 if (idx > 0) {
210 name = name.substring(0, idx);
211 }
212 name += ".local.";
213 // localHost to IP address binding
214 localHost = new HostInfo(address, name);
215
216 cache = new DNSCache(100);
217
218 listeners = Collections.synchronizedList(new ArrayList());
219 serviceListeners = new HashMap();
220 typeListeners = new ArrayList();
221
222 services = new Hashtable(20);
223 serviceTypes = new Hashtable(20);
224
225 timer = new Timer("JmDNS.Timer");
226 new RecordReaper().start();
227 shutdown = new Thread(new Shutdown(), "JmDNS.Shutdown");
228 Runtime.getRuntime().addShutdownHook(shutdown);
229
230 incomingListener = new Thread(new SocketListener(), "JmDNS.SocketListener");
231
232 // Bind to multicast socket
233 openMulticastSocket(localHost);
234 start(services.values());
235 }
236
237 private void start(Collection serviceInfos) {
238 state = DNSState.PROBING_1;
239 incomingListener.start();
240 new Prober().start();
241 for (Iterator iterator = serviceInfos.iterator(); iterator.hasNext(); ) {
242 try {
243 registerService(new ServiceInfo((ServiceInfo) iterator.next()));
244 } catch (Exception exception) {
245 logger.log(Level.WARNING, "start() Registration exception ", exception);
246 }
247 }
248 }
249
250 private void openMulticastSocket(HostInfo hostInfo) throws IOException {
251 if (group == null) {
252 group = InetAddress.getByName(DNSConstants.MDNS_GROUP);
253 }
254 if (socket != null) {
255 this.closeMulticastSocket();
256 }
257 socket = new MulticastSocket(DNSConstants.MDNS_PORT);
258 if ((hostInfo != null) && (localHost.getInterface() != null)) {
259 socket.setNetworkInterface(hostInfo.getInterface());
260 }
261 socket.setTimeToLive(255);
262 socket.joinGroup(group);
263 }
264
265 private void closeMulticastSocket() {
266 logger.finer("closeMulticastSocket()");
267 if (socket != null) {
268 // close socket
269 try {
270 socket.leaveGroup(group);
271 socket.close();
272 if (incomingListener != null) {
273 incomingListener.join();
274 }
275 } catch (Exception exception) {
276 logger.log(Level.WARNING, "closeMulticastSocket() Close socket exception ", exception);
277 }
278 socket = null;
279 }
280 }
281
282 // State machine
283
284 /**
285 * Sets the state and notifies all objects that wait on JmDNS.
286 */
287 synchronized void advanceState() {
288 state = state.advance();
289 notifyAll();
290 }
291
292 /**
293 * Sets the state and notifies all objects that wait on JmDNS.
294 */
295 synchronized void revertState() {
296 state = state.revert();
297 notifyAll();
298 }
299
300 /**
301 * Sets the state and notifies all objects that wait on JmDNS.
302 */
303 synchronized void cancel() {
304 state = DNSState.CANCELED;
305 notifyAll();
306 }
307
308 /**
309 * Returns the current state of this info.
310 */
311 DNSState getState() {
312 return state;
313 }
314
315
316 /**
317 * Return the DNSCache associated with the cache variable
318 */
319 DNSCache getCache() {
320 return cache;
321 }
322
323 /**
324 * Return the HostName associated with this JmDNS instance.
325 * Note: May not be the same as what started. The host name is subject to
326 * negotiation.
327 */
328 public String getHostName() {
329 return localHost.getName();
330 }
331
332 public HostInfo getLocalHost() {
333 return localHost;
334 }
335
336 /**
337 * Return the address of the interface to which this instance of JmDNS is
338 * bound.
339 */
340 public InetAddress getInterface() throws IOException {
341 return socket.getInterface();
342 }
343
344 /**
345 * Get service information. If the information is not cached, the method
346 * will block until updated information is received.
347 * <p/>
348 * Usage note: Do not call this method from the AWT event dispatcher thread.
349 * You will make the user interface unresponsive.
350 *
351 * @param type fully qualified service type, such as <code>_http._tcp.local.</code> .
352 * @param name unqualified service name, such as <code>foobar</code> .
353 * @return null if the service information cannot be obtained
354 */
355 public ServiceInfo getServiceInfo(String type, String name) {
356 return getServiceInfo(type, name, 3 * 1000);
357 }
358
359 /**
360 * Get service information. If the information is not cached, the method
361 * will block for the given timeout until updated information is received.
362 * <p/>
363 * Usage note: If you call this method from the AWT event dispatcher thread,
364 * use a small timeout, or you will make the user interface unresponsive.
365 *
366 * @param type full qualified service type, such as <code>_http._tcp.local.</code> .
367 * @param name unqualified service name, such as <code>foobar</code> .
368 * @param timeout timeout in milliseconds
369 * @return null if the service information cannot be obtained
370 */
371 public ServiceInfo getServiceInfo(String type, String name, int timeout) {
372 ServiceInfo info = new ServiceInfo(type, name);
373 new ServiceInfoResolver(info).start();
374
375 try {
376 long end = System.currentTimeMillis() + timeout;
377 long delay;
378 synchronized (info) {
379 while (!info.hasData() && (delay = end - System.currentTimeMillis()) > 0) {
380 info.wait(delay);
381 }
382 }
383 } catch (InterruptedException e) {
384 // empty
385 }
386
387 return (info.hasData()) ? info : null;
388 }
389
390 /**
391 * Request service information. The information about the service is
392 * requested and the ServiceListener.resolveService method is called as soon
393 * as it is available.
394 * <p/>
395 * Usage note: Do not call this method from the AWT event dispatcher thread.
396 * You will make the user interface unresponsive.
397 *
398 * @param type full qualified service type, such as <code>_http._tcp.local.</code> .
399 * @param name unqualified service name, such as <code>foobar</code> .
400 */
401 public void requestServiceInfo(String type, String name) {
402 requestServiceInfo(type, name, 3 * 1000);
403 }
404
405 /**
406 * Request service information. The information about the service is requested
407 * and the ServiceListener.resolveService method is called as soon as it is available.
408 *
409 * @param type full qualified service type, such as <code>_http._tcp.local.</code> .
410 * @param name unqualified service name, such as <code>foobar</code> .
411 * @param timeout timeout in milliseconds
412 */
413 public void requestServiceInfo(String type, String name, int timeout) {
414 registerServiceType(type);
415 ServiceInfo info = new ServiceInfo(type, name);
416 new ServiceInfoResolver(info).start();
417
418 try {
419 long end = System.currentTimeMillis() + timeout;
420 long delay;
421 synchronized (info) {
422 while (!info.hasData() && (delay = end - System.currentTimeMillis()) > 0) {
423 info.wait(delay);
424 }
425 }
426 } catch (InterruptedException e) {
427 // empty
428 }
429 }
430
431 void handleServiceResolved(ServiceInfo info) {
432 List list = (List) serviceListeners.get(info.type.toLowerCase());
433 if (list != null) {
434 ServiceEvent event = new ServiceEvent(this, info.type, info.getName(), info);
435 // Iterate on a copy in case listeners will modify it
436 final ArrayList listCopy = new ArrayList(list);
437 for (Iterator iterator = listCopy.iterator(); iterator.hasNext(); ) {
438 ((ServiceListener) iterator.next()).serviceResolved(event);
439 }
440 }
441 }
442
443 /**
444 * Listen for service types.
445 *
446 * @param listener listener for service types
447 */
448 public void addServiceTypeListener(ServiceTypeListener listener) throws IOException {
449 synchronized (this) {
450 typeListeners.remove(listener);
451 typeListeners.add(listener);
452 }
453
454 // report cached service types
455 for (Iterator iterator = serviceTypes.values().iterator(); iterator.hasNext(); ) {
456 listener.serviceTypeAdded(new ServiceEvent(this, (String) iterator.next(), null, null));
457 }
458
459 new TypeResolver().start();
460 }
461
462 /**
463 * Remove listener for service types.
464 *
465 * @param listener listener for service types
466 */
467 public void removeServiceTypeListener(ServiceTypeListener listener) {
468 synchronized (this) {
469 typeListeners.remove(listener);
470 }
471 }
472
473 /**
474 * Listen for services of a given type. The type has to be a fully qualified
475 * type name such as <code>_http._tcp.local.</code>.
476 *
477 * @param type full qualified service type, such as <code>_http._tcp.local.</code>.
478 * @param listener listener for service updates
479 */
480 public void addServiceListener(String type, ServiceListener listener) {
481 String lotype = type.toLowerCase();
482 removeServiceListener(lotype, listener);
483 List list = null;
484 synchronized (this) {
485 list = (List) serviceListeners.get(lotype);
486 if (list == null) {
487 list = Collections.synchronizedList(new LinkedList());
488 serviceListeners.put(lotype, list);
489 }
490 list.add(listener);
491 }
492
493 // report cached service types
494 for (Iterator i = cache.iterator(); i.hasNext(); ) {
495 for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n = n.next()) {
496 DNSRecord rec = (DNSRecord) n.getValue();
497 if (rec.type == DNSConstants.TYPE_SRV) {
498 if (rec.name.endsWith(type)) {
499 listener.serviceAdded(new ServiceEvent(this, type, toUnqualifiedName(type, rec.name), null));
500 }
501 }
502 }
503 }
504 new ServiceResolver(type).start();
505 }
506
507 /**
508 * Remove listener for services of a given type.
509 *
510 * @param listener listener for service updates
511 */
512 public void removeServiceListener(String type, ServiceListener listener) {
513 type = type.toLowerCase();
514 List list = (List) serviceListeners.get(type);
515 if (list != null) {
516 synchronized (this) {
517 list.remove(listener);
518 if (list.size() == 0) {
519 serviceListeners.remove(type);
520 }
521 }
522 }
523 }
524
525 /**
526 * Register a service. The service is registered for access by other jmdns clients.
527 * The name of the service may be changed to make it unique.
528 */
529 public void registerService(ServiceInfo info) throws IOException {
530 registerServiceType(info.type);
531
532 // bind the service to this address
533 info.server = localHost.getName();
534 info.addr = localHost.getAddress();
535
536 synchronized (this) {
537 makeServiceNameUnique(info);
538 services.put(info.getQualifiedName().toLowerCase(), info);
539 }
540
541 new /*Service*/Prober().start();
542 try {
543 synchronized (info) {
544 while (info.getState().compareTo(DNSState.ANNOUNCED) < 0) {
545 info.wait();
546 }
547 }
548 } catch (InterruptedException e) {
549 //empty
550 }
551 logger.fine("registerService() JmDNS registered service as " + info);
552 }
553
554 /**
555 * Unregister a service. The service should have been registered.
556 */
557 public void unregisterService(ServiceInfo info) {
558 synchronized (this) {
559 services.remove(info.getQualifiedName().toLowerCase());
560 }
561 info.cancel();
562
563 // Note: We use this lock object to synchronize on it.
564 // Synchronizing on another object (e.g. the ServiceInfo) does
565 // not make sense, because the sole purpose of the lock is to
566 // wait until the canceler has finished. If we synchronized on
567 // the ServiceInfo or on the Canceler, we would block all
568 // accesses to synchronized methods on that object. This is not
569 // what we want!
570 Object lock = new Object();
571 try {
572 new Canceler(info, lock).start();
573
574 // Remind: We get a deadlock here, if the Canceler does not run!
575 try {
576 synchronized (lock) {
577 //don'r wait forever
578 lock.wait(5000);
579 }
580 } catch (InterruptedException e) {
581 // empty
582 }
583 } catch (Throwable e) {
584 logger.info("Failed to properly unregister ");
585 }
586 }
587
588 /**
589 * Unregister all services.
590 */
591 public void unregisterAllServices() {
592 logger.finer("unregisterAllServices()");
593 if (services.size() == 0) {
594 return;
595 }
596
597 Collection list;
598 synchronized (this) {
599 list = new LinkedList(services.values());
600 services.clear();
601 }
602 for (Iterator iterator = list.iterator(); iterator.hasNext(); ) {
603 ((ServiceInfo) iterator.next()).cancel();
604 }
605
606 try {
607 Object lock = new Object();
608 new Canceler(list, lock).start();
609 // Remind: We get a livelock here, if the Canceler does not run!
610 try {
611 synchronized (lock) {
612 if (!closed) {
613 lock.wait(5000);
614 }
615 }
616 } catch (InterruptedException e) {
617 // empty
618 }
619 } catch (Throwable e) {
620 logger.info("Failed to unregister");
621 }
622
623
624 }
625
626 /**
627 * Register a service type. If this service type was not already known,
628 * all service listeners will be notified of the new service type. Service types
629 * are automatically registered as they are discovered.
630 */
631 public void registerServiceType(String type) {
632 String name = type.toLowerCase();
633 if (serviceTypes.get(name) == null) {
634 if ((type.indexOf("._mdns._udp.") < 0) && !type.endsWith(".in-addr.arpa.")) {
635 Collection list;
636 synchronized (this) {
637 serviceTypes.put(name, type);
638 list = new LinkedList(typeListeners);
639 }
640 for (Iterator iterator = list.iterator(); iterator.hasNext(); ) {
641 ((ServiceTypeListener) iterator.next()).serviceTypeAdded(new ServiceEvent(this, type, null, null));
642 }
643 }
644 }
645 }
646
647 /**
648 * Generate a possibly unique name for a host using the information we
649 * have in the cache.
650 *
651 * @return returns true, if the name of the host had to be changed.
652 */
653 private boolean makeHostNameUnique(DNSRecord.Address host) {
654 String originalName = host.getName();
655 long now = System.currentTimeMillis();
656
657 boolean collision;
658 do {
659 collision = false;
660
661 // Check for collision in cache
662 for (DNSCache.CacheNode j = cache.find(host.getName().toLowerCase()); j != null; j = j.next()) {
663 DNSRecord a = (DNSRecord) j.getValue();
664 if (false) {
665 host.name = incrementName(host.getName());
666 collision = true;
667 break;
668 }
669 }
670 }
671 while (collision);
672
673 if (originalName.equals(host.getName())) {
674 return false;
675 } else {
676 return true;
677 }
678 }
679
680 /**
681 * Generate a possibly unique name for a service using the information we
682 * have in the cache.
683 *
684 * @return returns true, if the name of the service info had to be changed.
685 */
686 private boolean makeServiceNameUnique(ServiceInfo info) {
687 String originalQualifiedName = info.getQualifiedName();
688 long now = System.currentTimeMillis();
689
690 boolean collision;
691 do {
692 collision = false;
693
694 // Check for collision in cache
695 for (DNSCache.CacheNode j = cache.find(info.getQualifiedName().toLowerCase()); j != null; j = j.next()) {
696 DNSRecord a = (DNSRecord) j.getValue();
697 if ((a.type == DNSConstants.TYPE_SRV) && !a.isExpired(now)) {
698 DNSRecord.Service s = (DNSRecord.Service) a;
699 if (s.port != info.port || !s.server.equals(localHost.getName())) {
700 logger.finer("makeServiceNameUnique() JmDNS.makeServiceNameUnique srv collision:" + a + " s.server=" + s.server + " " + localHost.getName() + " equals:" + (s.server.equals(localHost.getName())));
701 info.setName(incrementName(info.getName()));
702 collision = true;
703 break;
704 }
705 }
706 }
707
708 // Check for collision with other service infos published by JmDNS
709 Object selfService = services.get(info.getQualifiedName().toLowerCase());
710 if (selfService != null && selfService != info) {
711 info.setName(incrementName(info.getName()));
712 collision = true;
713 }
714 }
715 while (collision);
716
717 return !(originalQualifiedName.equals(info.getQualifiedName()));
718 }
719
720 String incrementName(String name) {
721 try {
722 int l = name.lastIndexOf('(');
723 int r = name.lastIndexOf(')');
724 if ((l >= 0) && (l < r)) {
725 name = name.substring(0, l) + "(" + (Integer.parseInt(name.substring(l + 1, r)) + 1) + ")";
726 } else {
727 name += " (2)";
728 }
729 } catch (NumberFormatException e) {
730 name += " (2)";
731 }
732 return name;
733 }
734
735 /**
736 * Add a listener for a question. The listener will receive updates
737 * of answers to the question as they arrive, or from the cache if they
738 * are already available.
739 */
740 void addListener(DNSListener listener, DNSQuestion question) {
741 long now = System.currentTimeMillis();
742
743 // add the new listener
744 synchronized (this) {
745 listeners.add(listener);
746 }
747
748 // report existing matched records
749 if (question != null) {
750 for (DNSCache.CacheNode i = cache.find(question.name); i != null; i = i.next()) {
751 DNSRecord c = (DNSRecord) i.getValue();
752 if (question.answeredBy(c) && !c.isExpired(now)) {
753 listener.updateRecord(this, now, c);
754 }
755 }
756 }
757 }
758
759 /**
760 * Remove a listener from all outstanding questions. The listener will no longer
761 * receive any updates.
762 */
763 void removeListener(DNSListener listener) {
764 synchronized (this) {
765 listeners.remove(listener);
766 }
767 }
768
769
770 // Remind: Method updateRecord should receive a better name.
771
772 /**
773 * Notify all listeners that a record was updated.
774 */
775 void updateRecord(long now, DNSRecord rec) {
776 // We do not want to block the entire DNS while we are updating the record for each listener (service info)
777 List listenerList = null;
778 synchronized (this) {
779 listenerList = new ArrayList(listeners);
780 }
781 for (Iterator iterator = listenerList.iterator(); iterator.hasNext(); ) {
782 DNSListener listener = (DNSListener) iterator.next();
783 listener.updateRecord(this, now, rec);
784 }
785 if (rec.type == DNSConstants.TYPE_PTR || rec.type == DNSConstants.TYPE_SRV) {
786 List serviceListenerList = null;
787 synchronized (this) {
788 serviceListenerList = (List) serviceListeners.get(rec.name.toLowerCase());
789 // Iterate on a copy in case listeners will modify it
790 if (serviceListenerList != null) {
791 serviceListenerList = new ArrayList(serviceListenerList);
792 }
793 }
794 if (serviceListenerList != null) {
795 boolean expired = rec.isExpired(now);
796 String type = rec.getName();
797 String name = ((DNSRecord.Pointer) rec).getAlias();
798 // DNSRecord old = (DNSRecord)services.get(name.toLowerCase());
799 if (!expired) {
800 // new record
801 ServiceEvent event = new ServiceEvent(this, type, toUnqualifiedName(type, name), null);
802 for (Iterator iterator = serviceListenerList.iterator(); iterator.hasNext(); ) {
803 ((ServiceListener) iterator.next()).serviceAdded(event);
804 }
805 } else {
806 // expire record
807 ServiceEvent event = new ServiceEvent(this, type, toUnqualifiedName(type, name), null);
808 for (Iterator iterator = serviceListenerList.iterator(); iterator.hasNext(); ) {
809 ((ServiceListener) iterator.next()).serviceRemoved(event);
810 }
811 }
812 }
813 }
814 }
815
816 /**
817 * Handle an incoming response. Cache answers, and pass them on to
818 * the appropriate questions.
819 */
820 private void handleResponse(DNSIncoming msg) throws IOException {
821 long now = System.currentTimeMillis();
822
823 boolean hostConflictDetected = false;
824 boolean serviceConflictDetected = false;
825
826 for (Iterator i = msg.answers.iterator(); i.hasNext(); ) {
827 boolean isInformative = false;
828 DNSRecord rec = (DNSRecord) i.next();
829 boolean expired = rec.isExpired(now);
830
831 // update the cache
832 DNSRecord c = (DNSRecord) cache.get(rec);
833 if (c != null) {
834 if (expired) {
835 isInformative = true;
836 cache.remove(c);
837 } else {
838 c.resetTTL(rec);
839 rec = c;
840 }
841 } else {
842 if (!expired) {
843 isInformative = true;
844 cache.add(rec);
845 }
846 }
847 switch (rec.type) {
848 case DNSConstants.TYPE_PTR:
849 // handle _mdns._udp records
850 if (rec.getName().indexOf("._mdns._udp.") >= 0) {
851 if (!expired && rec.name.startsWith("_services._mdns._udp.")) {
852 isInformative = true;
853 registerServiceType(((DNSRecord.Pointer) rec).alias);
854 }
855 continue;
856 }
857 registerServiceType(rec.name);
858 break;
859 }
860
861 if ((rec.getType() == DNSConstants.TYPE_A) || (rec.getType() == DNSConstants.TYPE_AAAA)) {
862 hostConflictDetected |= rec.handleResponse(this);
863 } else {
864 serviceConflictDetected |= rec.handleResponse(this);
865 }
866
867 // notify the listeners
868 if (isInformative) {
869 updateRecord(now, rec);
870 }
871 }
872
873 if (hostConflictDetected || serviceConflictDetected) {
874 new Prober().start();
875 }
876 }
877
878 /**
879 * Handle an incoming query. See if we can answer any part of it
880 * given our service infos.
881 */
882 private void handleQuery(DNSIncoming in, InetAddress addr, int port) throws IOException {
883 // Track known answers
884 boolean hostConflictDetected = false;
885 boolean serviceConflictDetected = false;
886 long expirationTime = System.currentTimeMillis() + DNSConstants.KNOWN_ANSWER_TTL;
887 for (Iterator i = in.answers.iterator(); i.hasNext(); ) {
888 DNSRecord answer = (DNSRecord) i.next();
889 if ((answer.getType() == DNSConstants.TYPE_A) || (answer.getType() == DNSConstants.TYPE_AAAA)) {
890 hostConflictDetected |= answer.handleQuery(this, expirationTime);
891 } else {
892 serviceConflictDetected |= answer.handleQuery(this, expirationTime);
893 }
894 }
895
896 if (plannedAnswer != null) {
897 plannedAnswer.append(in);
898 } else {
899 if (in.isTruncated()) {
900 plannedAnswer = in;
901 }
902
903 new Responder(in, addr, port).start();
904 }
905
906 if (hostConflictDetected || serviceConflictDetected) {
907 new Prober().start();
908 }
909 }
910
911 /**
912 * Add an answer to a question. Deal with the case when the
913 * outgoing packet overflows
914 */
915 DNSOutgoing addAnswer(DNSIncoming in, InetAddress addr, int port, DNSOutgoing out, DNSRecord rec) throws IOException {
916 if (out == null) {
917 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
918 }
919 try {
920 out.addAnswer(in, rec);
921 } catch (IOException e) {
922 out.flags |= DNSConstants.FLAGS_TC;
923 out.id = in.id;
924 out.finish();
925 send(out);
926
927 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
928 out.addAnswer(in, rec);
929 }
930 return out;
931 }
932
933
934 /**
935 * Send an outgoing multicast DNS message.
936 */
937 private void send(DNSOutgoing out) throws IOException {
938 out.finish();
939 if (!out.isEmpty()) {
940 DatagramPacket packet = new DatagramPacket(out.data, out.off, group, DNSConstants.MDNS_PORT);
941
942 try {
943 DNSIncoming msg = new DNSIncoming(packet);
944 logger.finest("send() JmDNS out:" + msg.print(true));
945 } catch (IOException e) {
946 logger.throwing(getClass().toString(), "send(DNSOutgoing) - JmDNS can not parse what it sends!!!", e);
947 }
948 socket.send(packet);
949 }
950 }
951
952 /**
953 * Listen for multicast packets.
954 */
955 class SocketListener implements Runnable {
956 public void run() {
957 try {
958 byte buf[] = new byte[DNSConstants.MAX_MSG_ABSOLUTE];
959 DatagramPacket packet = new DatagramPacket(buf, buf.length);
960 while (state != DNSState.CANCELED) {
961 packet.setLength(buf.length);
962 socket.receive(packet);
963 if (state == DNSState.CANCELED) {
964 break;
965 }
966 try {
967 if (localHost.shouldIgnorePacket(packet)) {
968 continue;
969 }
970
971 DNSIncoming msg = new DNSIncoming(packet);
972 logger.finest("SocketListener.run() JmDNS in:" + msg.print(true));
973
974 synchronized (ioLock) {
975 if (msg.isQuery()) {
976 if (packet.getPort() != DNSConstants.MDNS_PORT) {
977 handleQuery(msg, packet.getAddress(), packet.getPort());
978 }
979 handleQuery(msg, group, DNSConstants.MDNS_PORT);
980 } else {
981 handleResponse(msg);
982 }
983 }
984 } catch (IOException e) {
985 logger.log(Level.WARNING, "run() exception ", e);
986 }
987 }
988 } catch (IOException e) {
989 if (state != DNSState.CANCELED) {
990 logger.log(Level.WARNING, "run() exception ", e);
991 recover();
992 }
993 }
994 }
995 }
996
997
998 /**
999 * Periodicaly removes expired entries from the cache.
1000 */
1001 private class RecordReaper extends TimerTask {
1002 public void start() {
1003 timer.schedule(this, DNSConstants.RECORD_REAPER_INTERVAL, DNSConstants.RECORD_REAPER_INTERVAL);
1004 }
1005
1006 public void run() {
1007 synchronized (JmDNS.this) {
1008 if (state == DNSState.CANCELED) {
1009 return;
1010 }
1011 logger.finest("run() JmDNS reaping cache");
1012
1013 // Remove expired answers from the cache
1014 // -------------------------------------
1015 // To prevent race conditions, we defensively copy all cache
1016 // entries into a list.
1017 List list = new ArrayList();
1018 synchronized (cache) {
1019 for (Iterator i = cache.iterator(); i.hasNext(); ) {
1020 for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n = n.next()) {
1021 list.add(n.getValue());
1022 }
1023 }
1024 }
1025 // Now, we remove them.
1026 long now = System.currentTimeMillis();
1027 for (Iterator i = list.iterator(); i.hasNext(); ) {
1028 DNSRecord c = (DNSRecord) i.next();
1029 if (c.isExpired(now)) {
1030 updateRecord(now, c);
1031 cache.remove(c);
1032 }
1033 }
1034 }
1035 }
1036 }
1037
1038
1039 /**
1040 * The Prober sends three consecutive probes for all service infos
1041 * that needs probing as well as for the host name.
1042 * The state of each service info of the host name is advanced, when a probe has
1043 * been sent for it.
1044 * When the prober has run three times, it launches an Announcer.
1045 * <p/>
1046 * If a conflict during probes occurs, the affected service infos (and affected
1047 * host name) are taken away from the prober. This eventually causes the prober
1048 * tho cancel itself.
1049 */
1050 private class Prober extends TimerTask {
1051 /**
1052 * The state of the prober.
1053 */
1054 DNSState taskState = DNSState.PROBING_1;
1055
1056 public Prober() {
1057 // Associate the host name to this, if it needs probing
1058 if (state == DNSState.PROBING_1) {
1059 task = this;
1060 }
1061 // Associate services to this, if they need probing
1062 synchronized (JmDNS.this) {
1063 for (Iterator iterator = services.values().iterator(); iterator.hasNext(); ) {
1064 ServiceInfo info = (ServiceInfo) iterator.next();
1065 if (info.getState() == DNSState.PROBING_1) {
1066 info.task = this;
1067 }
1068 }
1069 }
1070 }
1071
1072
1073 public void start() {
1074 long now = System.currentTimeMillis();
1075 if (now - lastThrottleIncrement < DNSConstants.PROBE_THROTTLE_COUNT_INTERVAL) {
1076 throttle++;
1077 } else {
1078 throttle = 1;
1079 }
1080 lastThrottleIncrement = now;
1081
1082 if (state == DNSState.ANNOUNCED && throttle < DNSConstants.PROBE_THROTTLE_COUNT) {
1083 timer.schedule(this, random.nextInt(1 + DNSConstants.PROBE_WAIT_INTERVAL), DNSConstants.PROBE_WAIT_INTERVAL);
1084 } else {
1085 timer.schedule(this, DNSConstants.PROBE_CONFLICT_INTERVAL, DNSConstants.PROBE_CONFLICT_INTERVAL);
1086 }
1087 }
1088
1089 public boolean cancel() {
1090 // Remove association from host name to this
1091 if (task == this) {
1092 task = null;
1093 }
1094
1095 // Remove associations from services to this
1096 synchronized (JmDNS.this) {
1097 for (Iterator i = services.values().iterator(); i.hasNext(); ) {
1098 ServiceInfo info = (ServiceInfo) i.next();
1099 if (info.task == this) {
1100 info.task = null;
1101 }
1102 }
1103 }
1104
1105 return super.cancel();
1106 }
1107
1108 public void run() {
1109 synchronized (ioLock) {
1110 DNSOutgoing out = null;
1111 try {
1112 // send probes for JmDNS itself
1113 if (state == taskState && task == this) {
1114 if (out == null) {
1115 out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
1116 }
1117 out.addQuestion(new DNSQuestion(localHost.getName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
1118 DNSRecord answer = localHost.getDNS4AddressRecord();
1119 if (answer != null) {
1120 out.addAuthorativeAnswer(answer);
1121 }
1122 answer = localHost.getDNS6AddressRecord();
1123 if (answer != null) {
1124 out.addAuthorativeAnswer(answer);
1125 }
1126 advanceState();
1127 }
1128 // send probes for services
1129 // Defensively copy the services into a local list,
1130 // to prevent race conditions with methods registerService
1131 // and unregisterService.
1132 List list;
1133 synchronized (JmDNS.this) {
1134 list = new LinkedList(services.values());
1135 }
1136 for (Iterator i = list.iterator(); i.hasNext(); ) {
1137 ServiceInfo info = (ServiceInfo) i.next();
1138
1139 synchronized (info) {
1140 if (info.getState() == taskState && info.task == this) {
1141 info.advanceState();
1142 logger.fine("run() JmDNS probing " + info.getQualifiedName() + " state " + info.getState());
1143 if (out == null) {
1144 out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
1145 out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
1146 }
1147 out.addAuthorativeAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()));
1148 }
1149 }
1150 }
1151 if (out != null) {
1152 logger.finer("run() JmDNS probing #" + taskState);
1153 send(out);
1154 } else {
1155 // If we have nothing to send, another timer taskState ahead
1156 // of us has done the job for us. We can cancel.
1157 cancel();
1158 return;
1159 }
1160 } catch (Throwable e) {
1161 logger.log(Level.WARNING, "run() exception ", e);
1162 recover();
1163 }
1164
1165 taskState = taskState.advance();
1166 if (!taskState.isProbing()) {
1167 cancel();
1168
1169 new Announcer().start();
1170 }
1171 }
1172 }
1173
1174 }
1175
1176 /**
1177 * The Announcer sends an accumulated query of all announces, and advances
1178 * the state of all serviceInfos, for which it has sent an announce.
1179 * The Announcer also sends announcements and advances the state of JmDNS itself.
1180 * <p/>
1181 * When the announcer has run two times, it finishes.
1182 */
1183 private class Announcer extends TimerTask {
1184 /**
1185 * The state of the announcer.
1186 */
1187 DNSState taskState = DNSState.ANNOUNCING_1;
1188
1189 public Announcer() {
1190 // Associate host to this, if it needs announcing
1191 if (state == DNSState.ANNOUNCING_1) {
1192 task = this;
1193 }
1194 // Associate services to this, if they need announcing
1195 synchronized (JmDNS.this) {
1196 for (Iterator s = services.values().iterator(); s.hasNext(); ) {
1197 ServiceInfo info = (ServiceInfo) s.next();
1198 if (info.getState() == DNSState.ANNOUNCING_1) {
1199 info.task = this;
1200 }
1201 }
1202 }
1203 }
1204
1205 public void start() {
1206 timer.schedule(this, DNSConstants.ANNOUNCE_WAIT_INTERVAL, DNSConstants.ANNOUNCE_WAIT_INTERVAL);
1207 }
1208
1209 public boolean cancel() {
1210 // Remove association from host to this
1211 if (task == this) {
1212 task = null;
1213 }
1214
1215 // Remove associations from services to this
1216 synchronized (JmDNS.this) {
1217 for (Iterator i = services.values().iterator(); i.hasNext(); ) {
1218 ServiceInfo info = (ServiceInfo) i.next();
1219 if (info.task == this) {
1220 info.task = null;
1221 }
1222 }
1223 }
1224
1225 return super.cancel();
1226 }
1227
1228 public void run() {
1229 DNSOutgoing out = null;
1230 try {
1231 // send probes for JmDNS itself
1232 if (state == taskState) {
1233 if (out == null) {
1234 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
1235 }
1236 DNSRecord answer = localHost.getDNS4AddressRecord();
1237 if (answer != null) {
1238 out.addAnswer(answer, 0);
1239 }
1240 answer = localHost.getDNS6AddressRecord();
1241 if (answer != null) {
1242 out.addAnswer(answer, 0);
1243 }
1244 advanceState();
1245 }
1246 // send announces for services
1247 // Defensively copy the services into a local list,
1248 // to prevent race conditions with methods registerService
1249 // and unregisterService.
1250 List list;
1251 synchronized (JmDNS.this) {
1252 list = new ArrayList(services.values());
1253 }
1254 for (Iterator i = list.iterator(); i.hasNext(); ) {
1255 ServiceInfo info = (ServiceInfo) i.next();
1256 synchronized (info) {
1257 if (info.getState() == taskState && info.task == this) {
1258 info.advanceState();
1259 logger.finer("run() JmDNS announcing " + info.getQualifiedName() + " state " + info.getState());
1260 if (out == null) {
1261 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
1262 }
1263 out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), 0);
1264 out.addAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()), 0);
1265 out.addAnswer(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.text), 0);
1266 }
1267 }
1268 }
1269 if (out != null) {
1270 logger.finer("run() JmDNS announcing #" + taskState);
1271 send(out);
1272 } else {
1273 // If we have nothing to send, another timer taskState ahead
1274 // of us has done the job for us. We can cancel.
1275 cancel();
1276 }
1277 } catch (Throwable e) {
1278 logger.log(Level.WARNING, "run() exception ", e);
1279 recover();
1280 }
1281
1282 taskState = taskState.advance();
1283 if (!taskState.isAnnouncing()) {
1284 cancel();
1285
1286 new Renewer().start();
1287 }
1288 }
1289 }
1290
1291 /**
1292 * The Renewer is there to send renewal announcment when the record expire for ours infos.
1293 */
1294 private class Renewer extends TimerTask {
1295 /**
1296 * The state of the announcer.
1297 */
1298 DNSState taskState = DNSState.ANNOUNCED;
1299
1300 public Renewer() {
1301 // Associate host to this, if it needs renewal
1302 if (state == DNSState.ANNOUNCED) {
1303 task = this;
1304 }
1305 // Associate services to this, if they need renewal
1306 synchronized (JmDNS.this) {
1307 for (Iterator s = services.values().iterator(); s.hasNext(); ) {
1308 ServiceInfo info = (ServiceInfo) s.next();
1309 if (info.getState() == DNSState.ANNOUNCED) {
1310 info.task = this;
1311 }
1312 }
1313 }
1314 }
1315
1316 public void start() {
1317 timer.schedule(this, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL);
1318 }
1319
1320 public boolean cancel() {
1321 // Remove association from host to this
1322 if (task == this) {
1323 task = null;
1324 }
1325
1326 // Remove associations from services to this
1327 synchronized (JmDNS.this) {
1328 for (Iterator i = services.values().iterator(); i.hasNext(); ) {
1329 ServiceInfo info = (ServiceInfo) i.next();
1330 if (info.task == this) {
1331 info.task = null;
1332 }
1333 }
1334 }
1335
1336 return super.cancel();
1337 }
1338
1339 public void run() {
1340 DNSOutgoing out = null;
1341 try {
1342 // send probes for JmDNS itself
1343 if (state == taskState) {
1344 if (out == null) {
1345 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
1346 }
1347 DNSRecord answer = localHost.getDNS4AddressRecord();
1348 if (answer != null) {
1349 out.addAnswer(answer, 0);
1350 }
1351 answer = localHost.getDNS6AddressRecord();
1352 if (answer != null) {
1353 out.addAnswer(answer, 0);
1354 }
1355 advanceState();
1356 }
1357 // send announces for services
1358 // Defensively copy the services into a local list,
1359 // to prevent race conditions with methods registerService
1360 // and unregisterService.
1361 List list;
1362 synchronized (JmDNS.this) {
1363 list = new ArrayList(services.values());
1364 }
1365 for (Iterator i = list.iterator(); i.hasNext(); ) {
1366 ServiceInfo info = (ServiceInfo) i.next();
1367 synchronized (info) {
1368 if (info.getState() == taskState && info.task == this) {
1369 info.advanceState();
1370 logger.finer("run() JmDNS announced " + info.getQualifiedName() + " state " + info.getState());
1371 if (out == null) {
1372 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
1373 }
1374 out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), 0);
1375 out.addAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()), 0);
1376 out.addAnswer(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.text), 0);
1377 }
1378 }
1379 }
1380 if (out != null) {
1381 logger.finer("run() JmDNS announced");
1382 send(out);
1383 } else {
1384 // If we have nothing to send, another timer taskState ahead
1385 // of us has done the job for us. We can cancel.
1386 cancel();
1387 }
1388 } catch (Throwable e) {
1389 logger.log(Level.WARNING, "run() exception ", e);
1390 recover();
1391 }
1392
1393 taskState = taskState.advance();
1394 if (!taskState.isAnnounced()) {
1395 cancel();
1396
1397 }
1398 }
1399 }
1400
1401 /**
1402 * The Responder sends a single answer for the specified service infos
1403 * and for the host name.
1404 */
1405 private class Responder extends TimerTask {
1406 private DNSIncoming in;
1407 private InetAddress addr;
1408 private int port;
1409
1410 public Responder(DNSIncoming in, InetAddress addr, int port) {
1411 this.in = in;
1412 this.addr = addr;
1413 this.port = port;
1414 }
1415
1416 public void start() {
1417 // According to draft-cheshire-dnsext-multicastdns.txt
1418 // chapter "8 Responding":
1419 // We respond immediately if we know for sure, that we are
1420 // the only one who can respond to the query.
1421 // In all other cases, we respond within 20-120 ms.
1422 //
1423 // According to draft-cheshire-dnsext-multicastdns.txt
1424 // chapter "7.2 Multi-Packet Known Answer Suppression":
1425 // We respond after 20-120 ms if the query is truncated.
1426
1427 boolean iAmTheOnlyOne = true;
1428 for (Iterator i = in.questions.iterator(); i.hasNext(); ) {
1429 DNSEntry entry = (DNSEntry) i.next();
1430 if (entry instanceof DNSQuestion) {
1431 DNSQuestion q = (DNSQuestion) entry;
1432 logger.finest("start() question=" + q);
1433 iAmTheOnlyOne &= (q.type == DNSConstants.TYPE_SRV
1434 || q.type == DNSConstants.TYPE_TXT
1435 || q.type == DNSConstants.TYPE_A
1436 || q.type == DNSConstants.TYPE_AAAA
1437 || localHost.getName().equalsIgnoreCase(q.name)
1438 || services.containsKey(q.name.toLowerCase()));
1439 if (!iAmTheOnlyOne) {
1440 break;
1441 }
1442 }
1443 }
1444 int delay = (iAmTheOnlyOne && !in.isTruncated()) ? 0 : DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + random.nextInt(DNSConstants.RESPONSE_MAX_WAIT_INTERVAL - DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + 1) - in.elapseSinceArrival();
1445 if (delay < 0) {
1446 delay = 0;
1447 }
1448 logger.finest("start() Responder chosen delay=" + delay);
1449 timer.schedule(this, delay);
1450 }
1451
1452 public void run() {
1453 synchronized (ioLock) {
1454 if (plannedAnswer == in) {
1455 plannedAnswer = null;
1456 }
1457
1458 // We use these sets to prevent duplicate records
1459 // FIXME - This should be moved into DNSOutgoing
1460 HashSet questions = new HashSet();
1461 HashSet answers = new HashSet();
1462
1463
1464 if (state == DNSState.ANNOUNCED) {
1465 try {
1466 long now = System.currentTimeMillis();
1467 long expirationTime = now + 1; //=now+DNSConstants.KNOWN_ANSWER_TTL;
1468 boolean isUnicast = (port != DNSConstants.MDNS_PORT);
1469
1470
1471 // Answer questions
1472 for (Iterator iterator = in.questions.iterator(); iterator.hasNext(); ) {
1473 DNSEntry entry = (DNSEntry) iterator.next();
1474 if (entry instanceof DNSQuestion) {
1475 DNSQuestion q = (DNSQuestion) entry;
1476
1477 // for unicast responses the question must be included
1478 if (isUnicast) {
1479 //out.addQuestion(q);
1480 questions.add(q);
1481 }
1482
1483 int type = q.type;
1484 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]
1485 if (localHost.getName().equalsIgnoreCase(q.getName())) {
1486 // type = DNSConstants.TYPE_A;
1487 DNSRecord answer = localHost.getDNS4AddressRecord();
1488 if (answer != null) {
1489 answers.add(answer);
1490 }
1491 answer = localHost.getDNS6AddressRecord();
1492 if (answer != null) {
1493 answers.add(answer);
1494 }
1495 type = DNSConstants.TYPE_IGNORE;
1496 } else {
1497 if (serviceTypes.containsKey(q.getName().toLowerCase())) {
1498 type = DNSConstants.TYPE_PTR;
1499 }
1500 }
1501 }
1502
1503 switch (type) {
1504 case DNSConstants.TYPE_A: {
1505 // Answer a query for a domain name
1506 //out = addAnswer( in, addr, port, out, host );
1507 DNSRecord answer = localHost.getDNS4AddressRecord();
1508 if (answer != null) {
1509 answers.add(answer);
1510 }
1511 break;
1512 }
1513 case DNSConstants.TYPE_AAAA: {
1514 // Answer a query for a domain name
1515 DNSRecord answer = localHost.getDNS6AddressRecord();
1516 if (answer != null) {
1517 answers.add(answer);
1518 }
1519 break;
1520 }
1521 case DNSConstants.TYPE_PTR: {
1522 // Answer a query for services of a given type
1523
1524 // find matching services
1525 for (Iterator serviceIterator = services.values().iterator(); serviceIterator.hasNext(); ) {
1526 ServiceInfo info = (ServiceInfo) serviceIterator.next();
1527 if (info.getState() == DNSState.ANNOUNCED) {
1528 if (q.name.equalsIgnoreCase(info.type)) {
1529 DNSRecord answer = localHost.getDNS4AddressRecord();
1530 if (answer != null) {
1531 answers.add(answer);
1532 }
1533 answer = localHost.getDNS6AddressRecord();
1534 if (answer != null) {
1535 answers.add(answer);
1536 }
1537 answers.add(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()));
1538 answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()));
1539 answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.text));
1540 }
1541 }
1542 }
1543 if (q.name.equalsIgnoreCase("_services._mdns._udp.local.")) {
1544 for (Iterator serviceTypeIterator = serviceTypes.values().iterator(); serviceTypeIterator.hasNext(); ) {
1545 answers.add(new DNSRecord.Pointer("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) serviceTypeIterator.next()));
1546 }
1547 }
1548 break;
1549 }
1550 case DNSConstants.TYPE_SRV:
1551 case DNSConstants.TYPE_ANY:
1552 case DNSConstants.TYPE_TXT: {
1553 ServiceInfo info = (ServiceInfo) services.get(q.name.toLowerCase());
1554 if (info != null && info.getState() == DNSState.ANNOUNCED) {
1555 DNSRecord answer = localHost.getDNS4AddressRecord();
1556 if (answer != null) {
1557 answers.add(answer);
1558 }
1559 answer = localHost.getDNS6AddressRecord();
1560 if (answer != null) {
1561 answers.add(answer);
1562 }
1563 answers.add(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()));
1564 answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.priority, info.weight, info.port, localHost.getName()));
1565 answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.text));
1566 }
1567 break;
1568 }
1569 default: {
1570 //System.out.println("JmDNSResponder.unhandled query:"+q);
1571 break;
1572 }
1573 }
1574 }
1575 }
1576
1577
1578 // remove known answers, if the ttl is at least half of
1579 // the correct value. (See Draft Cheshire chapter 7.1.).
1580 for (Iterator i = in.answers.iterator(); i.hasNext(); ) {
1581 DNSRecord knownAnswer = (DNSRecord) i.next();
1582 if (knownAnswer.ttl > DNSConstants.DNS_TTL / 2 && answers.remove(knownAnswer)) {
1583 logger.log(Level.FINER, "JmDNS Responder Known Answer Removed");
1584 }
1585 }
1586
1587
1588 // responde if we have answers
1589 if (answers.size() != 0) {
1590 logger.finer("run() JmDNS responding");
1591 DNSOutgoing out = null;
1592 if (isUnicast) {
1593 out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false);
1594 }
1595
1596 for (Iterator i = questions.iterator(); i.hasNext(); ) {
1597 out.addQuestion((DNSQuestion) i.next());
1598 }
1599 for (Iterator i = answers.iterator(); i.hasNext(); ) {
1600 out = addAnswer(in, addr, port, out, (DNSRecord) i.next());
1601 }
1602 send(out);
1603 }
1604 cancel();
1605 } catch (Throwable e) {
1606 logger.log(Level.WARNING, "run() exception ", e);
1607 close();
1608 }
1609 }
1610 }
1611 }
1612 }
1613
1614 /**
1615 * Helper class to resolve service types.
1616 * <p/>
1617 * The TypeResolver queries three times consecutively for service types, and then
1618 * removes itself from the timer.
1619 * <p/>
1620 * The TypeResolver will run only if JmDNS is in state ANNOUNCED.
1621 */
1622 private class TypeResolver extends TimerTask {
1623 public void start() {
1624 timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL);
1625 }
1626
1627 /**
1628 * Counts the number of queries that were sent.
1629 */
1630 int count = 0;
1631
1632 public void run() {
1633 try {
1634 if (state == DNSState.ANNOUNCED) {
1635 if (++count < 3) {
1636 logger.finer("run() JmDNS querying type");
1637 DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
1638 out.addQuestion(new DNSQuestion("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN));
1639 for (Iterator iterator = serviceTypes.values().iterator(); iterator.hasNext(); ) {
1640 out.addAnswer(new DNSRecord.Pointer("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) iterator.next()), 0);
1641 }
1642 send(out);
1643 } else {
1644 // After three queries, we can quit.
1645 cancel();
1646 }
1647 ;
1648 } else {
1649 if (state == DNSState.CANCELED) {
1650 cancel();
1651 }
1652 }
1653 } catch (Throwable e) {
1654 logger.log(Level.WARNING, "run() exception ", e);
1655 recover();
1656 }
1657 }
1658 }
1659
1660 /**
1661 * The ServiceResolver queries three times consecutively for services of
1662 * a given type, and then removes itself from the timer.
1663 * <p/>
1664 * The ServiceResolver will run only if JmDNS is in state ANNOUNCED.
1665 * REMIND: Prevent having multiple service resolvers for the same type in the
1666 * timer queue.
1667 */
1668 private class ServiceResolver extends TimerTask {
1669 /**
1670 * Counts the number of queries being sent.
1671 */
1672 int count = 0;
1673 private String type;
1674
1675 public ServiceResolver(String type) {
1676 this.type = type;
1677 }
1678
1679 public void start() {
1680 timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL);
1681 }
1682
1683 public void run() {
1684 try {
1685 if (state == DNSState.ANNOUNCED) {
1686 if (count++ < 3) {
1687 logger.finer("run() JmDNS querying service");
1688 long now = System.currentTimeMillis();
1689 DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
1690 out.addQuestion(new DNSQuestion(type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN));
1691 for (Iterator s = services.values().iterator(); s.hasNext(); ) {
1692 final ServiceInfo info = (ServiceInfo) s.next();
1693 try {
1694 out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), now);
1695 } catch (IOException ee) {
1696 break;
1697 }
1698 }
1699 send(out);
1700 } else {
1701 // After three queries, we can quit.
1702 cancel();
1703 }
1704 ;
1705 } else {
1706 if (state == DNSState.CANCELED) {
1707 cancel();
1708 }
1709 }
1710 } catch (Throwable e) {
1711 logger.log(Level.WARNING, "run() exception ", e);
1712 recover();
1713 }
1714 }
1715 }
1716
1717 /**
1718 * The ServiceInfoResolver queries up to three times consecutively for
1719 * a service info, and then removes itself from the timer.
1720 * <p/>
1721 * The ServiceInfoResolver will run only if JmDNS is in state ANNOUNCED.
1722 * REMIND: Prevent having multiple service resolvers for the same info in the
1723 * timer queue.
1724 */
1725 private class ServiceInfoResolver extends TimerTask {
1726 /**
1727 * Counts the number of queries being sent.
1728 */
1729 int count = 0;
1730 private ServiceInfo info;
1731
1732 public ServiceInfoResolver(ServiceInfo info) {
1733 this.info = info;
1734 info.dns = JmDNS.this;
1735 addListener(info, new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
1736 }
1737
1738 public void start() {
1739 timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL);
1740 }
1741
1742 public void run() {
1743 try {
1744 if (state == DNSState.ANNOUNCED) {
1745 if (count++ < 3 && !info.hasData()) {
1746 long now = System.currentTimeMillis();
1747 DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY);
1748 out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN));
1749 out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN));
1750 if (info.server != null) {
1751 out.addQuestion(new DNSQuestion(info.server, DNSConstants.TYPE_A, DNSConstants.CLASS_IN));
1752 }
1753 out.addAnswer((DNSRecord) cache.get(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN), now);
1754 out.addAnswer((DNSRecord) cache.get(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN), now);
1755 if (info.server != null) {
1756 out.addAnswer((DNSRecord) cache.get(info.server, DNSConstants.TYPE_A, DNSConstants.CLASS_IN), now);
1757 }
1758 send(out);
1759 } else {
1760 // After three queries, we can quit.
1761 cancel();
1762 removeListener(info);
1763 }
1764 ;
1765 } else {
1766 if (state == DNSState.CANCELED) {
1767 cancel();
1768 removeListener(info);
1769 }
1770 }
1771 } catch (Throwable e) {
1772 logger.log(Level.WARNING, "run() exception ", e);
1773 recover();
1774 }
1775 }
1776 }
1777
1778 /**
1779 * The Canceler sends two announces with TTL=0 for the specified services.
1780 */
1781 private class Canceler extends TimerTask {
1782 /**
1783 * Counts the number of announces being sent.
1784 */
1785 int count = 0;
1786 /**
1787 * The services that need cancelling.
1788 * Note: We have to use a local variable here, because the services
1789 * that are canceled, are removed immediately from variable JmDNS.services.
1790 */
1791 private ServiceInfo[] infos;
1792 /**
1793 * We call notifyAll() on the lock object, when we have canceled the
1794 * service infos.
1795 * This is used by method JmDNS.unregisterService() and
1796 * JmDNS.unregisterAllServices, to ensure that the JmDNS
1797 * socket stays open until the Canceler has canceled all services.
1798 * <p/>
1799 * Note: We need this lock, because ServiceInfos do the transition from
1800 * state ANNOUNCED to state CANCELED before we get here. We could get
1801 * rid of this lock, if we added a state named CANCELLING to DNSState.
1802 */
1803 private Object lock;
1804 int ttl = 0;
1805
1806 public Canceler(ServiceInfo info, Object lock) {
1807 this.infos = new ServiceInfo[]{info};
1808 this.lock = lock;
1809 addListener(info, new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN));
1810 }
1811
1812 public Canceler(ServiceInfo[] infos, Object lock) {
1813 this.infos = infos;
1814 this.lock = lock;
1815 }
1816
1817 public Canceler(Collection infos, Object lock) {
1818 this.infos = (ServiceInfo[]) infos.toArray(new ServiceInfo[infos.size()]);
1819 this.lock = lock;
1820 }
1821
1822 public void start() {
1823 timer.schedule(this, 0, DNSConstants.ANNOUNCE_WAIT_INTERVAL);
1824 }
1825
1826 public void run() {
1827 try {
1828 if (++count < 3) {
1829 logger.finer("run() JmDNS canceling service");
1830 // announce the service
1831 //long now = System.currentTimeMillis();
1832 DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA);
1833 for (int i = 0; i < infos.length; i++) {
1834 ServiceInfo info = infos[i];
1835 out.addAnswer(new DNSRecord.Pointer(info.type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, ttl, info.getQualifiedName()), 0);
1836 out.addAnswer(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, ttl, info.priority, info.weight, info.port, localHost.getName()), 0);
1837 out.addAnswer(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN, ttl, info.text), 0);
1838 DNSRecord answer = localHost.getDNS4AddressRecord();
1839 if (answer != null) {
1840 out.addAnswer(answer, 0);
1841 }
1842 answer = localHost.getDNS6AddressRecord();
1843 if (answer != null) {
1844 out.addAnswer(answer, 0);
1845 }
1846 }
1847 send(out);
1848 } else {
1849 // After three successful announcements, we are finished.
1850 synchronized (lock) {
1851 closed = true;
1852 lock.notifyAll();
1853 }
1854 cancel();
1855 }
1856 } catch (Throwable e) {
1857 logger.log(Level.WARNING, "run() exception ", e);
1858 recover();
1859 }
1860 }
1861 }
1862
1863 // REMIND: Why is this not an anonymous inner class?
1864
1865 /**
1866 * Shutdown operations.
1867 */
1868 private class Shutdown implements Runnable {
1869 public void run() {
1870 shutdown = null;
1871 close();
1872 }
1873 }
1874
1875 /**
1876 * Recover jmdns when there is an error.
1877 */
1878 protected void recover() {
1879 logger.finer("recover()");
1880 // We have an IO error so lets try to recover if anything happens lets close it.
1881 // This should cover the case of the IP address changing under our feet
1882 if (DNSState.CANCELED != state) {
1883 synchronized (this) { // Synchronize only if we are not already in process to prevent dead locks
1884 //
1885 logger.finer("recover() Cleanning up");
1886 // Stop JmDNS
1887 state = DNSState.CANCELED; // This protects against recursive calls
1888
1889 // We need to keep a copy for reregistration
1890 Collection oldServiceInfos = new ArrayList(services.values());
1891
1892 // Cancel all services
1893 unregisterAllServices();
1894 disposeServiceCollectors();
1895 //
1896 // close multicast socket
1897 closeMulticastSocket();
1898 //
1899 cache.clear();
1900 logger.finer("recover() All is clean");
1901 //
1902 // All is clear now start the services
1903 //
1904 try {
1905 openMulticastSocket(localHost);
1906 start(oldServiceInfos);
1907 } catch (Exception exception) {
1908 logger.log(Level.WARNING, "recover() Start services exception ", exception);
1909 }
1910 logger.log(Level.WARNING, "recover() We are back!");
1911 }
1912 }
1913 }
1914
1915 /**
1916 * Close down jmdns. Release all resources and unregister all services.
1917 */
1918 public void close() {
1919 if (state != DNSState.CANCELED) {
1920 synchronized (this) { // Synchronize only if we are not already in process to prevent dead locks
1921 // Stop JmDNS
1922 state = DNSState.CANCELED; // This protects against recursive calls
1923
1924 unregisterAllServices();
1925 disposeServiceCollectors();
1926
1927 // close socket
1928 closeMulticastSocket();
1929
1930 // Stop the timer
1931 timer.cancel();
1932
1933 // remove the shutdown hook
1934 if (shutdown != null) {
1935 Runtime.getRuntime().removeShutdownHook(shutdown);
1936 }
1937
1938 }
1939 }
1940 }
1941
1942 /**
1943 * List cache entries, for debugging only.
1944 */
1945 void print() {
1946 System.out.println("---- cache ----");
1947 cache.print();
1948 System.out.println();
1949 }
1950
1951 /**
1952 * List Services and serviceTypes.
1953 * Debugging Only
1954 */
1955
1956 public void printServices() {
1957 System.err.println(toString());
1958 }
1959
1960 public String toString() {
1961 StringBuffer aLog = new StringBuffer();
1962 aLog.append("\t---- Services -----");
1963 if (services != null) {
1964 for (Iterator k = services.keySet().iterator(); k.hasNext(); ) {
1965 Object key = k.next();
1966 aLog.append("\n\t\tService: " + key + ": " + services.get(key));
1967 }
1968 }
1969 aLog.append("\n");
1970 aLog.append("\t---- Types ----");
1971 if (serviceTypes != null) {
1972 for (Iterator k = serviceTypes.keySet().iterator(); k.hasNext(); ) {
1973 Object key = k.next();
1974 aLog.append("\n\t\tType: " + key + ": " + serviceTypes.get(key));
1975 }
1976 }
1977 aLog.append("\n");
1978 aLog.append(cache.toString());
1979 aLog.append("\n");
1980 aLog.append("\t---- Service Collectors ----");
1981 if (serviceCollectors != null) {
1982 synchronized (serviceCollectors) {
1983 for (Iterator k = serviceCollectors.keySet().iterator(); k.hasNext(); ) {
1984 Object key = k.next();
1985 aLog.append("\n\t\tService Collector: " + key + ": " + serviceCollectors.get(key));
1986 }
1987 serviceCollectors.clear();
1988 }
1989 }
1990 return aLog.toString();
1991 }
1992
1993 /**
1994 * Returns a list of service infos of the specified type.
1995 *
1996 * @param type Service type name, such as <code>_http._tcp.local.</code>.
1997 * @return An array of service instance names.
1998 */
1999 public ServiceInfo[] list(String type) {
2000 // Implementation note: The first time a list for a given type is
2001 // requested, a ServiceCollector is created which collects service
2002 // infos. This greatly speeds up the performance of subsequent calls
2003 // to this method. The caveats are, that 1) the first call to this method
2004 // for a given type is slow, and 2) we spawn a ServiceCollector
2005 // instance for each service type which increases network traffic a
2006 // little.
2007
2008 ServiceCollector collector;
2009
2010 boolean newCollectorCreated;
2011 synchronized (serviceCollectors) {
2012 collector = (ServiceCollector) serviceCollectors.get(type);
2013 if (collector == null) {
2014 collector = new ServiceCollector(type);
2015 serviceCollectors.put(type, collector);
2016 addServiceListener(type, collector);
2017 newCollectorCreated = true;
2018 } else {
2019 newCollectorCreated = false;
2020 }
2021 }
2022
2023 // After creating a new ServiceCollector, we collect service infos for
2024 // 200 milliseconds. This should be enough time, to get some service
2025 // infos from the network.
2026 if (newCollectorCreated) {
2027 try {
2028 Thread.sleep(200);
2029 } catch (InterruptedException e) {
2030 }
2031 }
2032
2033 return collector.list();
2034 }
2035
2036 /**
2037 * This method disposes all ServiceCollector instances which have been
2038 * created by calls to method <code>list(type)</code>.
2039 *
2040 * @see #list
2041 */
2042 private void disposeServiceCollectors() {
2043 logger.finer("disposeServiceCollectors()");
2044 synchronized (serviceCollectors) {
2045 for (Iterator i = serviceCollectors.values().iterator(); i.hasNext(); ) {
2046 ServiceCollector collector = (ServiceCollector) i.next();
2047 removeServiceListener(collector.type, collector);
2048 }
2049 serviceCollectors.clear();
2050 }
2051 }
2052
2053 /**
2054 * Instances of ServiceCollector are used internally to speed up the
2055 * performance of method <code>list(type)</code>.
2056 *
2057 * @see #list
2058 */
2059 private static class ServiceCollector implements ServiceListener {
2060 private static Logger logger = Logger.getLogger(ServiceCollector.class.toString());
2061 /**
2062 * A set of collected service instance names.
2063 */
2064 private Map infos = Collections.synchronizedMap(new HashMap());
2065
2066 public String type;
2067
2068 public ServiceCollector(String type) {
2069 this.type = type;
2070 }
2071
2072 /**
2073 * A service has been added.
2074 */
2075 public void serviceAdded(ServiceEvent event) {
2076 synchronized (infos) {
2077 event.getDNS().requestServiceInfo(event.getType(), event.getName(), 0);
2078 }
2079 }
2080
2081 /**
2082 * A service has been removed.
2083 */
2084 public void serviceRemoved(ServiceEvent event) {
2085 synchronized (infos) {
2086 infos.remove(event.getName());
2087 }
2088 }
2089
2090 /**
2091 * A service hase been resolved. Its details are now available in the
2092 * ServiceInfo record.
2093 */
2094 public void serviceResolved(ServiceEvent event) {
2095 synchronized (infos) {
2096 infos.put(event.getName(), event.getInfo());
2097 }
2098 }
2099
2100 /**
2101 * Returns an array of all service infos which have been collected by this
2102 * ServiceCollector.
2103 */
2104 public ServiceInfo[] list() {
2105 synchronized (infos) {
2106 return (ServiceInfo[]) infos.values().toArray(new ServiceInfo[infos.size()]);
2107 }
2108 }
2109
2110 public String toString() {
2111 StringBuffer aLog = new StringBuffer();
2112 synchronized (infos) {
2113 for (Iterator k = infos.keySet().iterator(); k.hasNext(); ) {
2114 Object key = k.next();
2115 aLog.append("\n\t\tService: " + key + ": " + infos.get(key));
2116 }
2117 }
2118 return aLog.toString();
2119 }
2120 }
2121
2122 ;
2123
2124 private static String toUnqualifiedName(String type, String qualifiedName) {
2125 if (qualifiedName.endsWith(type)) {
2126 return qualifiedName.substring(0, qualifiedName.length() - type.length() - 1);
2127 } else {
2128 return qualifiedName;
2129 }
2130 }
2131 }
2132