Message(DNSInput in) throws IOException { this(new Header(in)); boolean isUpdate = (header.getOpcode() == Opcode.UPDATE); boolean truncated = header.getFlag(Flags.TC); try { for (int i = 0; i < 4; i++) { int count = header.getCount(i); if (count > 0) sections[i] = new ArrayList(count); for (int j = 0; j < count; j++) { int pos = in.current(); Record rec = Record.fromWire(in, i, isUpdate); sections[i].add(rec); if (i == Section.ADDITIONAL) { if (rec.getType() == Type.TSIG) tsigstart = pos; if (rec.getType() == Type.SIG) { SIGRecord sig = (SIGRecord) rec; if (sig.getTypeCovered() == 0) sig0start = pos; } } } } } catch (WireParseException e) { if (!truncated) throw e; } size = in.current(); }
private Record parseRR(MyStringTokenizer st, boolean useLast, Record last, Name origin) throws IOException { Name name; int ttl; short type, dclass; if (!useLast) name = new Name(st.nextToken(), origin); else name = last.getName(); String s = st.nextToken(); try { ttl = TTL.parseTTL(s); s = st.nextToken(); } catch (NumberFormatException e) { if (!useLast || last == null) ttl = defaultTTL; else ttl = last.getTTL(); } if ((dclass = DClass.value(s)) > 0) s = st.nextToken(); else dclass = DClass.IN; if ((type = Type.value(s)) < 0) throw new IOException("Parse error"); return Record.fromString(name, type, dclass, ttl, st, origin); }
/** * Adds a record to the Zone * * @param r The record to be added * @see Record */ public void addRecord(Record r) { Name name = r.getName(); short type = r.getRRsetType(); RRset rrset = (RRset) findExactSet(name, type); if (rrset == null) addSet(name, type, rrset = new RRset()); rrset.addRR(r); }
/* Returns the number of records not successfully rendered. */ private int sectionToWire(DNSOutput out, int section, Compression c, int maxLength) { int n = sections[section].size(); int pos = out.current(); int rendered = 0; int skipped = 0; Record lastrec = null; for (int i = 0; i < n; i++) { Record rec = (Record) sections[section].get(i); if (section == Section.ADDITIONAL && rec instanceof OPTRecord) { skipped++; continue; } if (lastrec != null && !sameSet(rec, lastrec)) { pos = out.current(); rendered = i; } lastrec = rec; rec.toWire(out, section, c); if (out.current() > maxLength) { out.jump(pos); return n - rendered + skipped; } } return skipped; }
/** * Builds a new Record from its textual representation * * @param name The owner name of the record. * @param type The record's type. * @param dclass The record's class. * @param ttl The record's time to live. * @param st A tokenizer containing the textual representation of the rdata. * @param origin The default origin to be appended to relative domain names. * @return The new record * @throws IOException The text format was invalid. */ public static Record fromString( Name name, int type, int dclass, long ttl, Tokenizer st, Name origin) throws IOException { Record rec; if (!name.isAbsolute()) throw new RelativeNameException(name); Type.check(type); DClass.check(dclass); TTL.check(ttl); Tokenizer.Token t = st.get(); if (t.type == Tokenizer.IDENTIFIER && t.value.equals("\\#")) { int length = st.getUInt16(); byte[] data = st.getHex(); if (data == null) { data = new byte[0]; } if (length != data.length) throw st.exception("invalid unknown RR encoding: " + "length mismatch"); DNSInput in = new DNSInput(data); return newRecord(name, type, dclass, ttl, length, in); } st.unget(); rec = getEmptyRecord(name, type, dclass, ttl, true); rec.rdataFromString(st, origin); t = st.get(); if (t.type != Tokenizer.EOL && t.type != Tokenizer.EOF) { throw st.exception("unexpected tokens at end of record"); } return rec; }
/** * Determines if two Records are identical. This compares the name, type, class, and rdata (with * names canonicalized). The TTLs are not compared. * * @param arg The record to compare to * @return true if the records are equal, false otherwise. */ public boolean equals(Object arg) { if (arg == null || !(arg instanceof Record)) return false; Record r = (Record) arg; if (type != r.type || dclass != r.dclass || !name.equals(r.name)) return false; byte[] array1 = rdataToWireCanonical(); byte[] array2 = r.rdataToWireCanonical(); return Arrays.equals(array1, array2); }
/** * Determines if an RRset with the given name and type is already present in the given section. * * @see RRset * @see Section */ public boolean findRRset(Name name, int type, int section) { if (sections[section] == null) return false; for (int i = 0; i < sections[section].size(); i++) { Record r = (Record) sections[section].get(i); if (r.getType() == type && name.equals(r.getName())) return true; } return false; }
private final void maybeAddRecord(Record record) throws IOException { int rtype = record.getType(); Name name = record.getName(); if (rtype == Type.SOA && !name.equals(origin)) { throw new IOException("SOA owner " + name + " does not match zone origin " + origin); } if (name.subdomain(origin)) addRecord(record); }
/** * Removes a record from the Zone * * @param r The record to be removed * @see Record */ public void removeRecord(Record r) { Name name = r.getName(); short type = r.getRRsetType(); RRset rrset = (RRset) findExactSet(name, type); if (rrset != null) { rrset.deleteRR(r); if (rrset.size() == 0) removeSet(name, type, rrset); } }
/** * Removes a record from the Zone * * @param r The record to be removed * @see Record */ public void removeRecord(Record r) { Name name = r.getName(); int rtype = r.getRRsetType(); synchronized (this) { RRset rrset = findRRset(name, rtype); if (rrset == null) return; rrset.deleteRR(r); if (rrset.size() == 0) removeRRset(name, rtype); } }
private static final Record getEmptyRecord( Name name, int type, int dclass, long ttl, boolean hasData) { Record rec; if (hasData) rec = getTypedObject(type).getObject(); else rec = new EmptyRecord(); rec.name = name; rec.type = type; rec.dclass = dclass; rec.ttl = ttl; return rec; }
void toWire(DNSOutput out) { header.toWire(out); Compression c = new Compression(); for (int i = 0; i < 4; i++) { if (sections[i] == null) continue; for (int j = 0; j < sections[i].size(); j++) { Record rec = (Record) sections[i].get(j); rec.toWire(out, i, c); } } }
/** * Adds a Record to the Zone * * @param r The record to be added * @see Record */ public void addRecord(Record r) { Name name = r.getName(); int rtype = r.getRRsetType(); synchronized (this) { RRset rrset = findRRset(name, rtype); if (rrset == null) { rrset = new RRset(r); addRRset(name, rrset); } else { rrset.addRR(r); } } }
private static Record newRecord( Name name, int type, int dclass, long ttl, int length, DNSInput in) throws IOException { Record rec; int recstart; rec = getEmptyRecord(name, type, dclass, ttl, in != null); if (in != null) { if (in.remaining() < length) throw new WireParseException("truncated record"); in.setActive(length); rec.rrFromWire(in); if (in.remaining() > 0) throw new WireParseException("invalid record length"); in.clearActive(); } return rec; }
@Test public void itLogsServerErrors() throws Exception { header.setRcode(Rcode.REFUSED); Name name = Name.fromString("John Wayne."); Record question = Record.newRecord(name, 65530, 43210); Message query = Message.newQuery(question); Message response = mock(Message.class); when(response.getHeader()).thenReturn(header); when(response.getSectionArray(Section.ANSWER)).thenReturn(null); when(response.getQuestion()).thenReturn(question); when(nameServer.query( any(Message.class), any(InetAddress.class), any(DNSAccessRecord.Builder.class))) .thenThrow(new RuntimeException("Aw snap!")); FakeAbstractProtocol abstractProtocol = new FakeAbstractProtocol(client, query.toWire()); abstractProtocol.setNameServer(nameServer); abstractProtocol.run(); verify(accessLogger) .info( "144140678.000 qtype=DNS chi=192.168.23.45 ttms=345.123 xn=65535 fqdn=John\\032Wayne. type=TYPE65530 class=CLASS43210 rcode=SERVFAIL rtype=- rloc=\"-\" rdtl=- rerr=\"Server Error:RuntimeException:Aw snap!\" ttl=\"-\" ans=\"-\""); }
/** * Asynchronously sends a message to a single server, registering a listener to receive a callback * on success or exception. Multiple asynchronous lookups can be performed in parallel. Since the * callback may be invoked before the function returns, external synchronization is necessary. * * @param query The query to send * @param listener The object containing the callbacks. * @return An identifier, which is also a parameter in the callback */ public Object sendAsync(final Message query, final ResolverListener listener) { final Object id; synchronized (this) { id = new Integer(uniqueID++); } Record question = query.getQuestion(); String qname; if (question != null) qname = question.getName().toString(); else qname = "(none)"; String name = this.getClass() + ": " + qname; Thread thread = new ResolveThread(this, query, id, listener); thread.setName(name); thread.setDaemon(true); thread.start(); return id; }
/** * Constructs and returns the next record in the expansion. * * @throws IOException The name or rdata was invalid after substitutions were performed. */ public Record nextRecord() throws IOException { if (current > end) return null; String namestr = substitute(namePattern, current); Name name = Name.fromString(namestr, origin); String rdata = substitute(rdataPattern, current); current += step; return Record.fromString(name, type, dclass, ttl, rdata, origin); }
private final void maybeAddRecord(Record record, Cache cache, Object source) throws IOException { int type = record.getType(); Name name = record.getName(); if (type == Type.SOA) { if (!name.equals(origin)) throw new IOException("SOA owner " + name + " does not match zone origin " + origin); else { setOrigin(origin); dclass = record.getDClass(); } } if (origin == null && type != Type.SOA) throw new IOException("non-SOA record seen at " + name + " with no origin set"); if (name.subdomain(origin)) addRecord(record); else if (cache != null) cache.addRecord(record, Credibility.GLUE, source); }
/** * Adds a record to the Cache. * * @param r The record to be added * @param cred The credibility of the record * @param o The source of the record (this could be a Message, for example) * @see Record */ public void addRecord(Record r, byte cred, Object o) { Name name = r.getName(); short type = r.getRRsetType(); if (!Type.isRR(type)) return; boolean addrrset = false; Element element = (Element) findExactSet(name, type); if (element == null || cred > element.credibility) { RRset rrset = new RRset(); rrset.addRR(r); addRRset(rrset, cred); } else if (cred == element.credibility) { if (element instanceof PositiveElement) { PositiveElement pe = (PositiveElement) element; pe.rrset.addRR(r); } } }
/** * Converts the given section of the Message to a String. * * @see Section */ public String sectionToString(int i) { if (i > 3) return null; StringBuffer sb = new StringBuffer(); Record[] records = getSectionArray(i); for (int j = 0; j < records.length; j++) { Record rec = records[j]; if (i == Section.QUESTION) { sb.append(";;\t" + rec.name); sb.append(", type = " + Type.string(rec.type)); sb.append(", class = " + DClass.string(rec.dclass)); } else if (rec.getType() == Type.OPT) continue; else sb.append(rec); sb.append("\n"); } return sb.toString(); }
/** * Compares this Record to another Object. * * @param o The Object to be compared. * @return The value 0 if the argument is a record equivalent to this record; a value less than 0 * if the argument is less than this record in the canonical ordering, and a value greater * than 0 if the argument is greater than this record in the canonical ordering. The canonical * ordering is defined to compare by name, class, type, and rdata. * @throws ClassCastException if the argument is not a Record. */ public int compareTo(Object o) { Record arg = (Record) o; if (this == arg) return (0); int n = name.compareTo(arg.name); if (n != 0) return (n); n = dclass - arg.dclass; if (n != 0) return (n); n = type - arg.type; if (n != 0) return (n); byte[] rdata1 = rdataToWireCanonical(); byte[] rdata2 = arg.rdataToWireCanonical(); for (int i = 0; i < rdata1.length && i < rdata2.length; i++) { n = (rdata1[i] & 0xFF) - (rdata2[i] & 0xFF); if (n != 0) return (n); } return (rdata1.length - rdata2.length); }
/** * Constructs and returns all records in the expansion. * * @throws IOException The name or rdata of a record was invalid after substitutions were * performed. */ public Record[] expand() throws IOException { List list = new ArrayList(); for (long i = start; i < end; i += step) { String namestr = substitute(namePattern, current); Name name = Name.fromString(namestr, origin); String rdata = substitute(rdataPattern, current); list.add(Record.fromString(name, type, dclass, ttl, rdata, origin)); } return (Record[]) list.toArray(new Record[list.size()]); }
private static final Record getTypedObject(int type) { if (type < 0 || type > knownRecords.length) return unknownRecord.getObject(); if (knownRecords[type] != null) return knownRecords[type]; /* Construct the class name by putting the type before "Record". */ String s = Record.class.getPackage().getName() + "." + Type.string(type).replace('-', '_') + "Record"; try { Class c = Class.forName(s); Constructor m = c.getDeclaredConstructor(emptyClassArray); knownRecords[type] = (Record) m.newInstance(emptyObjectArray); } catch (ClassNotFoundException e) { /* This is normal; do nothing */ } catch (Exception e) { if (Options.check("verbose")) System.err.println(e); } if (knownRecords[type] == null) knownRecords[type] = unknownRecord.getObject(); return knownRecords[type]; }
/** * Creates an array containing fields of the SIG record and the RRsets to be signed/verified. * * @param sig The SIG record used to sign/verify the rrset. * @param rrset The data to be signed/verified. * @return The data to be cryptographically signed or verified. */ public static byte[] digestRRset(SIGRecord sig, RRset rrset) { DataByteOutputStream out = new DataByteOutputStream(); digestSIG(out, sig); int size = rrset.size(); byte[][] records = new byte[size][]; Iterator it = rrset.rrs(); Name name = rrset.getName(); Name wild = null; if (name.labels() > sig.getLabels()) wild = name.wild(name.labels() - sig.getLabels()); while (it.hasNext()) { Record rec = (Record) it.next(); if (wild != null) rec = rec.withName(wild); records[--size] = rec.toWireCanonical(); } Arrays.sort(records); for (int i = 0; i < records.length; i++) out.writeArray(records[i]); return out.toByteArray(); }
/* Returns the number of records not successfully rendered. */ private int sectionToWire(DNSOutput out, int section, Compression c, int maxLength) { int n = sections[section].size(); int pos = out.current(); int rendered = 0; Record lastrec = null; for (int i = 0; i < n; i++) { Record rec = (Record) sections[section].get(i); if (lastrec != null && !sameSet(rec, lastrec)) { pos = out.current(); rendered = i; } lastrec = rec; rec.toWire(out, section, c); if (out.current() > maxLength) { out.jump(pos); return n - rendered; } } return 0; }
@Test public void itLogsARecordQueries() throws Exception { header.setRcode(Rcode.NOERROR); Name name = Name.fromString("www.example.com."); Record question = Record.newRecord(name, Type.A, DClass.IN, 0L); Message query = Message.newQuery(question); query.getHeader().getRcode(); byte[] queryBytes = query.toWire(); whenNew(Message.class).withArguments(queryBytes).thenReturn(query); InetAddress resolvedAddress = Inet4Address.getByName("192.168.8.9"); Record answer = new ARecord(name, DClass.IN, 3600L, resolvedAddress); Record[] answers = new Record[] {answer}; Message response = mock(Message.class); when(response.getHeader()).thenReturn(header); when(response.getSectionArray(Section.ANSWER)).thenReturn(answers); when(response.getQuestion()).thenReturn(question); InetAddress client = Inet4Address.getByName("192.168.23.45"); when(nameServer.query( any(Message.class), any(InetAddress.class), any(DNSAccessRecord.Builder.class))) .thenReturn(response); FakeAbstractProtocol abstractProtocol = new FakeAbstractProtocol(client, queryBytes); abstractProtocol.setNameServer(nameServer); abstractProtocol.run(); verify(accessLogger) .info( "144140678.000 qtype=DNS chi=192.168.23.45 ttms=345.123 xn=65535 fqdn=www.example.com. type=A class=IN rcode=NOERROR rtype=- rloc=\"-\" rdtl=- rerr=\"-\" ttl=\"3600\" ans=\"192.168.8.9\""); }
/** * Creates a Zone by performing a zone transfer to the specified host. All records that do not * belong in the Zone are added to the specified Cache. * * @see Cache * @see Master */ public Zone(Name zone, short dclass, String remote, Cache cache) throws IOException { super(false); origin = zone; this.dclass = dclass; type = SECONDARY; Resolver res = new SimpleResolver(remote); Record rec = Record.newRecord(zone, Type.AXFR, dclass); Message query = Message.newQuery(rec); Message response = res.send(query); Record[] recs = response.getSectionArray(Section.ANSWER); for (int i = 0; i < recs.length; i++) { if (!recs[i].getName().subdomain(origin)) { if (Options.check("verbose")) System.err.println(recs[i].getName() + "is not in zone " + origin); continue; } addRecord(recs[i]); } if (cache != null) { recs = response.getSectionArray(Section.ADDITIONAL); for (int i = 0; i < recs.length; i++) cache.addRecord(recs[i], Credibility.GLUE, recs); } validate(); }
/** * Creates a new record identical to the current record, but with a different name. This is most * useful for replacing the name of a wildcard record. */ public Record withName(Name name) { if (!name.isAbsolute()) throw new RelativeNameException(name); Record rec = cloneRecord(); rec.name = name; return rec; }
private static boolean sameSet(Record r1, Record r2) { return (r1.getRRsetType() == r2.getRRsetType() && r1.getDClass() == r2.getDClass() && r1.getName().equals(r2.getName())); }
/** * Creates a new record identical to the current record, but with a different class and ttl. This * is most useful for dynamic update. */ Record withDClass(int dclass, long ttl) { Record rec = cloneRecord(); rec.dclass = dclass; rec.ttl = ttl; return rec; }