docs: public void startSearch(int timeout, boolean wait) throws KNXException
  	{
  		if (timeout < 0)
  			throw new KNXIllegalArgumentException("timeout has to be >= 0");
  		final Enumeration eni;
  		try {
  			eni = NetworkInterface.getNetworkInterfaces();
  		}
  		catch (final SocketException e) {
  			logger.error("failed to get network interfaces", e);
  			throw new KNXException("network interface error: " + e.getMessage());
  		}
  		if (eni == null) {
  			logger.error("no network interfaces found");
  			throw new KNXException("no network interfaces found");
  		}
  		final List rcv = new ArrayList();
  		boolean lo = false;
  		while (eni.hasMoreElements()) {
  			final NetworkInterface ni = (NetworkInterface) eni.nextElement();
  			for (final Enumeration ea = ni.getInetAddresses(); ea.hasMoreElements();) {
  				final InetAddress a = (InetAddress) ea.nextElement();
  				if (!isNatAware && a.getAddress().length != 4)
  					logger.info("skipped " + a + ", not an IPv4 address");
  				else
  					try {
  						if (!(lo && a.isLoopbackAddress()))
  							rcv.add(search(new InetSocketAddress(a, port), ni, timeout));
  						if (a.isLoopbackAddress())
  							lo = true;
  						else
  							break;
  					}
  					catch (final KNXException e) {}
  			}
  		}
  		if (rcv.size() == 0)
  			throw new KNXException("search couldn't be started on any network interface");
  		if (wait)
  			for (final Iterator i = rcv.iterator(); i.hasNext();)
  				join((Thread) i.next());
  	}
  
  	/**
  	 * Stops every search currently running within this Discoverer.
  	 * <p>
  	 * Already gathered search responses from a search will not be removed.
  	 */
  	public final void stopSearch()
  	{
  		synchronized (receiver) {
  			for (final Iterator i = receiver.iterator(); i.hasNext();)
  				((Receiver) i.next()).quit();
  			receiver.clear();
  		}
  	}
  
  	/**
  	 * Returns <code>true</code> if a search is currently running.
  	 * <p>
  	 * 
  	 * @return a <code>boolean</code> showing the search state
  	 */
  	public final boolean isSearching()
  	{
  		return receiver.size() != 0;
  	}
  
  	/**
  	 * Returns all collected search responses received by searches so far.
  	 * <p>
  	 * As long as searches are running, new responses might be added to the list of
  	 * responses.
  	 * 
  	 * @return array of {@link SearchResponse}s
  	 * @see #stopSearch()
  	 */
  	public final SearchResponse[] getSearchResponses()
  	{
  		return (SearchResponse[]) responses.toArray(new SearchResponse[responses.size()]);
  	}
  
  	/**
  	 * Removes all search responses collected so far.
  	 * <p>
  	 */
  	public final void clearSearchResponses()
  	{
  		responses.clear();
  	}
  
  	/**
  	 * Sends a description request to <code>server</code> and waits at most
  	 * <code>timeout</code> seconds for the answer message to arrive.
  	 * <p>
  	 * 
  	 * @param server the InetSocketAddress of the server the description is requested from
  	 * @param timeout time window in seconds to wait for answer message, 0 &lt; timeout
  	 *        &lt; ({@link Integer#MAX_VALUE} / 1000)
  	 * @return the description response message
  	 * @throws KNXException on network I/O error
  	 * @throws KNXTimeoutException if the timeout was reached before the description
  	 *         response arrived
  	 * @throws KNXInvalidResponseException if a received message from <code>server</code>
  	 *         does not match the expected response
  	 */
  	public DescriptionResponse getDescription(InetSocketAddress server, int timeout)
  		throws KNXException
  	{
  		if (timeout <= 0 || timeout >= Integer.MAX_VALUE / 1000)
  			throw new KNXIllegalArgumentException("timeout out of range");
  		DatagramSocket s = null;
  		try {
  			s = new DatagramSocket(port, host);
  			final byte[] buf = PacketHelper.toPacket(new DescriptionRequest(
  				isNatAware ? null : (InetSocketAddress) s.getLocalSocketAddress()));
  			s.send(new DatagramPacket(buf, buf.length, server));
  			final long end = System.currentTimeMillis() + timeout * 1000L;
  			DatagramPacket p = null;
  			while ((p = receive(s, end)) != null) {
  				if (p.getSocketAddress().equals(server)) {
  					final KNXnetIPHeader h =
  						new KNXnetIPHeader(p.getData(), p.getOffset());
  					if (h.getServiceType() == KNXnetIPHeader.DESCRIPTION_RES)
  						return new DescriptionResponse(p.getData(), p.getOffset()
  							+ h.getStructLength());
  				}
  			}
  		}
  		catch (final IOException e) {
  			final String msg = "network failure on getting description";
  			logger.error(msg, e);
  			throw new KNXException(msg);
  		}
  		catch (final KNXFormatException e) {
  			logger.error("invalid description response", e);
  			throw new KNXInvalidResponseException(e.getMessage());
  		}
  		finally {
  			if (s != null)
  				s.close();
  		}
  		final String msg = "timeout, no description response received";
  		logger.warn(msg);
  		throw new KNXTimeoutException(msg);
  	}
  
  	/**
  	 * Starts a search sending a search request message.
  	 * <p>
  	 * 
  	 * @param a local SocketAddress to send search request from
  	 * @param ni {@link NetworkInterface} used to send outgoing multicast, or
  	 *        <code>null</code> to use the default multicast interface
  	 * @param timeout timeout in seconds, timeout >= 0, 0 for an infinite time window
  	 * @return the receiver thread for the search started
  	 * @throws KNXException
  	 */
  	private Receiver search(InetSocketAddress a, NetworkInterface ni, int timeout)
  		throws KNXException
  	{
  		logger.info("search on " + a);
  		MulticastSocket s = null;
  		try {
  			s = new MulticastSocket(a);
  			if (ni != null)
  				s.setNetworkInterface(ni);
  			final byte[] buf = PacketHelper.toPacket(new SearchRequest(isNatAware ? null
  				: (InetSocketAddress) s.getLocalSocketAddress()));
  			s.send(new DatagramPacket(buf, buf.length, multicast, SEARCH_PORT));
  			final Receiver r = new Receiver(s, timeout);
  			receiver.add(r);
  			return r;
  		}
  		catch (final IOException e) {
  			if (s != null)
  				s.close();
              e.printStackTrace();
  			logger.warn("I/O failure sending search request on " + a, e);
  			throw new KNXException("search request failed, " + e.getMessage());
  		}
  	}
  
  	// timeEnd = 0 for infinite timeout
  	private DatagramPacket receive(DatagramSocket s, long timeEnd) throws IOException
  	{
  		final long timeout = timeEnd == 0 ? 0 : timeEnd - System.currentTimeMillis();
  		if (timeout > 0 || timeEnd == 0) {
  			final byte[] buf = new byte[bufferSize];
  			final DatagramPacket p = new DatagramPacket(buf, bufferSize);
  			try {
  				s.setSoTimeout((int) timeout);
  				s.receive(p);
  				return p;
  			}
  			catch (final InterruptedIOException ignore) {}
  		}
  		return null;
  	}
  
  	private void join(Thread t)
  	{
  		while (t.isAlive())
  			try {
  				t.join();
  			}
  			catch (final InterruptedException ignore) {}
  	}
  
  	private void checkHost() throws KNXException
  	{
  		if (isNatAware || host.getAddress().length == 4)
  			return;
  		final KNXException e =
  			new KNXException(host.getHostAddress() + " is not an IPv4 address");
  		logger.error("NAT not used, only IPv4 address support", e);
  		throw e;
  	}
  
  	private final class Receiver extends Thread
  	{
  		private volatile boolean quit;
  		private final MulticastSocket s;
  		private final int timeout;
  
  		/**
  		 * Creates a new Receiver.
  		 * <p>
  		 * 
  		 * @param socket socket to receive from
  		 * @param timeout live time of this Receiver, timeout >= 0, 0 is infinite timeout
  		 */
  		Receiver(MulticastSocket socket, int timeout)
  		{
  			super("Discoverer receiver " + socket.getLocalAddress().getHostAddress());
  			s = socket;
  			this.timeout = timeout;
  			setDaemon(true);
  			start();
  		}
  
  		void quit()
  		{
  			quit = true;
  			s.close();
  		}
  
  		/* (non-Javadoc)
  		 * @see java.lang.Thread#run()
  		 */
  		public void run()
  		{
  			final long end = System.currentTimeMillis() + timeout * 1000;
  			DatagramPacket p;
  			try {
  				while (!quit && (p = receive(s, timeout == 0 ? 0 : end)) != null)
  					checkForResponse(p);
  			}
  			catch (final IOException e) {
  				if (!quit)
  					logger.error("while waiting for response", e);
  			}
  			finally {
  				s.close();
  				receiver.remove(this);
  			}
  		}
  
  		private void checkForResponse(final DatagramPacket p)
  		{
  			try {
  				final KNXnetIPHeader h = new KNXnetIPHeader(p.getData(), p.getOffset());
  				if (h.getServiceType() == KNXnetIPHeader.SEARCH_RES)
  					// sync with receiver queue: check if our search was stopped
  					synchronized (receiver) {
  						if (receiver.contains(this))
  							responses.add(new SearchResponse(p.getData(), p.getOffset()
  								+ h.getStructLength()));
  					}
  			}
  			catch (final KNXFormatException ignore) {}
  		}
  	}
  }