blob: a65369d1081c686aa5c76be35f5e6b68e01eddda [file] [log] [blame]
/**
* Copyright 2003-2005 Arthur van Hoff, Rick Blair
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.jmdns;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* DNS record
*
* @version %I%, %G%
* @author Arthur van Hoff, Rick Blair, Werner Randelshofer, Pierre Frisch
*/
abstract class DNSRecord extends DNSEntry
{
private static Logger logger = Logger.getLogger(DNSRecord.class.toString());
int ttl;
private long created;
/**
* Create a DNSRecord with a name, type, clazz, and ttl.
*/
DNSRecord(String name, int type, int clazz, int ttl)
{
super(name, type, clazz);
this.ttl = ttl;
this.created = System.currentTimeMillis();
}
/**
* True if this record is the same as some other record.
*/
public boolean equals(Object other)
{
return (other instanceof DNSRecord) && sameAs((DNSRecord) other);
}
/**
* True if this record is the same as some other record.
*/
boolean sameAs(DNSRecord other)
{
return super.equals(other) && sameValue((DNSRecord) other);
}
/**
* True if this record has the same value as some other record.
*/
abstract boolean sameValue(DNSRecord other);
/**
* True if this record has the same type as some other record.
*/
boolean sameType(DNSRecord other)
{
return type == other.type;
}
/**
* Handles a query represented by this record.
*
* @return Returns true if a conflict with one of the services registered
* with JmDNS or with the hostname occured.
*/
abstract boolean handleQuery(JmDNS dns, long expirationTime);
/**
* Handles a responserepresented by this record.
*
* @return Returns true if a conflict with one of the services registered
* with JmDNS or with the hostname occured.
*/
abstract boolean handleResponse(JmDNS dns);
/**
* Adds this as an answer to the provided outgoing datagram.
*/
abstract DNSOutgoing addAnswer(JmDNS dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException;
/**
* True if this record is suppressed by the answers in a message.
*/
boolean suppressedBy(DNSIncoming msg)
{
try
{
for (int i = msg.numAnswers; i-- > 0;)
{
if (suppressedBy((DNSRecord) msg.answers.get(i)))
{
return true;
}
}
return false;
}
catch (ArrayIndexOutOfBoundsException e)
{
logger.log(Level.WARNING, "suppressedBy() message " + msg + " exception ", e);
// msg.print(true);
return false;
}
}
/**
* True if this record would be supressed by an answer.
* This is the case if this record would not have a
* significantly longer TTL.
*/
boolean suppressedBy(DNSRecord other)
{
if (sameAs(other) && (other.ttl > ttl / 2))
{
return true;
}
return false;
}
/**
* Get the expiration time of this record.
*/
long getExpirationTime(int percent)
{
return created + (percent * ttl * 10L);
}
/**
* Get the remaining TTL for this record.
*/
int getRemainingTTL(long now)
{
return (int) Math.max(0, (getExpirationTime(100) - now) / 1000);
}
/**
* Check if the record is expired.
*/
boolean isExpired(long now)
{
return getExpirationTime(100) <= now;
}
/**
* Check if the record is stale, ie it has outlived
* more than half of its TTL.
*/
boolean isStale(long now)
{
return getExpirationTime(50) <= now;
}
/**
* Reset the TTL of a record. This avoids having to
* update the entire record in the cache.
*/
void resetTTL(DNSRecord other)
{
created = other.created;
ttl = other.ttl;
}
/**
* Write this record into an outgoing message.
*/
abstract void write(DNSOutgoing out) throws IOException;
/**
* Address record.
*/
static class Address extends DNSRecord
{
private static Logger logger = Logger.getLogger(Address.class.toString());
InetAddress addr;
Address(String name, int type, int clazz, int ttl, InetAddress addr)
{
super(name, type, clazz, ttl);
this.addr = addr;
}
Address(String name, int type, int clazz, int ttl, byte[] rawAddress)
{
super(name, type, clazz, ttl);
try
{
this.addr = InetAddress.getByAddress(rawAddress);
}
catch (UnknownHostException exception)
{
logger.log(Level.WARNING, "Address() exception ", exception);
}
}
void write(DNSOutgoing out) throws IOException
{
if (addr != null)
{
byte[] buffer = addr.getAddress();
if (DNSConstants.TYPE_A == type)
{
// If we have a type A records we should answer with a IPv4 address
if (addr instanceof Inet4Address)
{
// All is good
}
else
{
// Get the last four bytes
byte[] tempbuffer = buffer;
buffer = new byte[4];
System.arraycopy(tempbuffer, 12, buffer, 0, 4);
}
}
else
{
// If we have a type AAAA records we should answer with a IPv6 address
if (addr instanceof Inet4Address)
{
byte[] tempbuffer = buffer;
buffer = new byte[16];
for (int i = 0; i < 16; i++)
{
if (i < 11)
{
buffer[i] = tempbuffer[i - 12];
}
else
{
buffer[i] = 0;
}
}
}
}
int length = buffer.length;
out.writeBytes(buffer, 0, length);
}
}
boolean same(DNSRecord other)
{
return ((sameName(other)) && ((sameValue(other))));
}
boolean sameName(DNSRecord other)
{
return name.equalsIgnoreCase(((Address) other).name);
}
boolean sameValue(DNSRecord other)
{
return addr.equals(((Address) other).getAddress());
}
InetAddress getAddress()
{
return addr;
}
/**
* Creates a byte array representation of this record.
* This is needed for tie-break tests according to
* draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2.
*/
private byte[] toByteArray()
{
try
{
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
dout.write(name.getBytes("UTF8"));
dout.writeShort(type);
dout.writeShort(clazz);
//dout.writeInt(len);
byte[] buffer = addr.getAddress();
for (int i = 0; i < buffer.length; i++)
{
dout.writeByte(buffer[i]);
}
dout.close();
return bout.toByteArray();
}
catch (IOException e)
{
throw new InternalError();
}
}
/**
* Does a lexicographic comparison of the byte array representation
* of this record and that record.
* This is needed for tie-break tests according to
* draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2.
*/
private int lexCompare(DNSRecord.Address that)
{
byte[] thisBytes = this.toByteArray();
byte[] thatBytes = that.toByteArray();
for (int i = 0, n = Math.min(thisBytes.length, thatBytes.length); i < n; i++)
{
if (thisBytes[i] > thatBytes[i])
{
return 1;
}
else
{
if (thisBytes[i] < thatBytes[i])
{
return -1;
}
}
}
return thisBytes.length - thatBytes.length;
}
/**
* Does the necessary actions, when this as a query.
*/
boolean handleQuery(JmDNS dns, long expirationTime)
{
DNSRecord.Address dnsAddress = dns.getLocalHost().getDNSAddressRecord(this);
if (dnsAddress != null)
{
if (dnsAddress.sameType(this) && dnsAddress.sameName(this) && (!dnsAddress.sameValue(this)))
{
logger.finer("handleQuery() Conflicting probe detected. dns state " + dns.getState() + " lex compare " + lexCompare(dnsAddress));
// Tie-breaker test
if (dns.getState().isProbing() && lexCompare(dnsAddress) >= 0)
{
// We lost the tie-break. We have to choose a different name.
dns.getLocalHost().incrementHostName();
dns.getCache().clear();
for (Iterator i = dns.services.values().iterator(); i.hasNext();)
{
ServiceInfo info = (ServiceInfo) i.next();
info.revertState();
}
}
dns.revertState();
return true;
}
}
return false;
}
/**
* Does the necessary actions, when this as a response.
*/
boolean handleResponse(JmDNS dns)
{
DNSRecord.Address dnsAddress = dns.getLocalHost().getDNSAddressRecord(this);
if (dnsAddress != null)
{
if (dnsAddress.sameType(this) && dnsAddress.sameName(this) && (!dnsAddress.sameValue(this)))
{
logger.finer("handleResponse() Denial detected");
if (dns.getState().isProbing())
{
dns.getLocalHost().incrementHostName();
dns.getCache().clear();
for (Iterator i = dns.services.values().iterator(); i.hasNext();)
{
ServiceInfo info = (ServiceInfo) i.next();
info.revertState();
}
}
dns.revertState();
return true;
}
}
return false;
}
DNSOutgoing addAnswer(JmDNS dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException
{
return out;
}
public String toString()
{
return toString(" address '" + (addr != null ? addr.getHostAddress() : "null") + "'");
}
}
/**
* Pointer record.
*/
static class Pointer extends DNSRecord
{
private static Logger logger = Logger.getLogger(Pointer.class.toString());
String alias;
Pointer(String name, int type, int clazz, int ttl, String alias)
{
super(name, type, clazz, ttl);
this.alias = alias;
}
void write(DNSOutgoing out) throws IOException
{
out.writeName(alias);
}
boolean sameValue(DNSRecord other)
{
return alias.equals(((Pointer) other).alias);
}
boolean handleQuery(JmDNS dns, long expirationTime)
{
// Nothing to do (?)
// I think there is no possibility for conflicts for this record type?
return false;
}
boolean handleResponse(JmDNS dns)
{
// Nothing to do (?)
// I think there is no possibility for conflicts for this record type?
return false;
}
String getAlias()
{
return alias;
}
DNSOutgoing addAnswer(JmDNS dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException
{
return out;
}
public String toString()
{
return toString(alias);
}
}
static class Text extends DNSRecord
{
private static Logger logger = Logger.getLogger(Text.class.toString());
byte text[];
Text(String name, int type, int clazz, int ttl, byte text[])
{
super(name, type, clazz, ttl);
this.text = text;
}
void write(DNSOutgoing out) throws IOException
{
out.writeBytes(text, 0, text.length);
}
boolean sameValue(DNSRecord other)
{
Text txt = (Text) other;
if (txt.text.length != text.length)
{
return false;
}
for (int i = text.length; i-- > 0;)
{
if (txt.text[i] != text[i])
{
return false;
}
}
return true;
}
boolean handleQuery(JmDNS dns, long expirationTime)
{
// Nothing to do (?)
// I think there is no possibility for conflicts for this record type?
return false;
}
boolean handleResponse(JmDNS dns)
{
// Nothing to do (?)
// Shouldn't we care if we get a conflict at this level?
/*
ServiceInfo info = (ServiceInfo) dns.services.get(name.toLowerCase());
if (info != null) {
if (! Arrays.equals(text,info.text)) {
info.revertState();
return true;
}
}*/
return false;
}
DNSOutgoing addAnswer(JmDNS dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException
{
return out;
}
public String toString()
{
return toString((text.length > 10) ? new String(text, 0, 7) + "..." : new String(text));
}
}
/**
* Service record.
*/
static class Service extends DNSRecord
{
private static Logger logger = Logger.getLogger(Service.class.toString());
int priority;
int weight;
int port;
String server;
Service(String name, int type, int clazz, int ttl, int priority, int weight, int port, String server)
{
super(name, type, clazz, ttl);
this.priority = priority;
this.weight = weight;
this.port = port;
this.server = server;
}
void write(DNSOutgoing out) throws IOException
{
out.writeShort(priority);
out.writeShort(weight);
out.writeShort(port);
out.writeName(server);
}
private byte[] toByteArray()
{
try
{
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
dout.write(name.getBytes("UTF8"));
dout.writeShort(type);
dout.writeShort(clazz);
//dout.writeInt(len);
dout.writeShort(priority);
dout.writeShort(weight);
dout.writeShort(port);
dout.write(server.getBytes("UTF8"));
dout.close();
return bout.toByteArray();
}
catch (IOException e)
{
throw new InternalError();
}
}
private int lexCompare(DNSRecord.Service that)
{
byte[] thisBytes = this.toByteArray();
byte[] thatBytes = that.toByteArray();
for (int i = 0, n = Math.min(thisBytes.length, thatBytes.length); i < n; i++)
{
if (thisBytes[i] > thatBytes[i])
{
return 1;
}
else
{
if (thisBytes[i] < thatBytes[i])
{
return -1;
}
}
}
return thisBytes.length - thatBytes.length;
}
boolean sameValue(DNSRecord other)
{
Service s = (Service) other;
return (priority == s.priority) && (weight == s.weight) && (port == s.port) && server.equals(s.server);
}
boolean handleQuery(JmDNS dns, long expirationTime)
{
ServiceInfo info = (ServiceInfo) dns.services.get(name.toLowerCase());
if (info != null
&& (port != info.port || !server.equalsIgnoreCase(dns.getLocalHost().getName())))
{
logger.finer("handleQuery() Conflicting probe detected");
// Tie breaker test
if (info.getState().isProbing() && lexCompare(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV,
DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE,
DNSConstants.DNS_TTL, info.priority,
info.weight, info.port, dns.getLocalHost().getName())) >= 0)
{
// We lost the tie break
String oldName = info.getQualifiedName().toLowerCase();
info.setName(dns.incrementName(info.getName()));
dns.services.remove(oldName);
dns.services.put(info.getQualifiedName().toLowerCase(), info);
logger.finer("handleQuery() Lost tie break: new unique name chosen:" + info.getName());
}
info.revertState();
return true;
}
return false;
}
boolean handleResponse(JmDNS dns)
{
ServiceInfo info = (ServiceInfo) dns.services.get(name.toLowerCase());
if (info != null
&& (port != info.port || !server.equalsIgnoreCase(dns.getLocalHost().getName())))
{
logger.finer("handleResponse() Denial detected");
if (info.getState().isProbing())
{
String oldName = info.getQualifiedName().toLowerCase();
info.setName(dns.incrementName(info.getName()));
dns.services.remove(oldName);
dns.services.put(info.getQualifiedName().toLowerCase(), info);
logger.finer("handleResponse() New unique name chose:" + info.getName());
}
info.revertState();
return true;
}
return false;
}
DNSOutgoing addAnswer(JmDNS dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException
{
ServiceInfo info = (ServiceInfo) dns.services.get(name.toLowerCase());
if (info != null)
{
if (this.port == info.port != server.equals(dns.getLocalHost().getName()))
{
return dns.addAnswer(in, addr, port, out,
new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV,
DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE,
DNSConstants.DNS_TTL, info.priority,
info.weight, info.port, dns.getLocalHost().getName()));
}
}
return out;
}
public String toString()
{
return toString(server + ":" + port);
}
}
public String toString(String other)
{
return toString("record", ttl + "/" + getRemainingTTL(System.currentTimeMillis()) + "," + other);
}
}