From: Ruben d'Arco Date: Tue, 4 Dec 2012 06:21:27 +0000 (+0100) Subject: Add rfc2136 implementation with replaceRRSEt and dirty cache clean X-Git-Tag: rec-3.6.0-rc1~556^2~3^2~95 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6a323f63147a92a31fdea56e83f078f475ebcaf4;p=thirdparty%2Fpdns.git Add rfc2136 implementation with replaceRRSEt and dirty cache clean This completes the RFC2136 implementation and passes all the dyndns tests using the replaceRRSet method. The runtest now understands a 'once' option because a SOA-update can only be performed once. dnsbackend.hh has a listSubZone() - this is not a pretty method, but it's required to fix delegation flags without reading the complete zone. replaceRRSet is also updated to understand 'ANY' as a Qtype. This performs the query without the where type-cause. It also allows for not adding any items which makes it usable to delete RRSET's. --- diff --git a/pdns/backends/gsql/gsqlbackend.cc b/pdns/backends/gsql/gsqlbackend.cc index 8105809691..adf05cc881 100644 --- a/pdns/backends/gsql/gsqlbackend.cc +++ b/pdns/backends/gsql/gsqlbackend.cc @@ -770,6 +770,25 @@ bool GSQLBackend::list(const string &target, int domain_id ) return true; } +bool GSQLBackend::listSubZone(const string &zone, int domain_id) { + string wildzone = "%." + zone; + string listSubZone = "select content,ttl,prio,type,domain_id,name from records where (name='%s' OR name like '%s') and domain_id=%d"; + if (d_dnssecQueries) + listSubZone = "select content,ttl,prio,type,domain_id,name,auth from records where (name='%s' OR name like '%s') and domain_id=%d"; + string output = (boost::format(listSubZone) % sqlEscape(zone) % sqlEscape(wildzone) % domain_id).str(); + try { + d_db->doQuery(output.c_str()); + } + catch(SSqlException &e) { + throw AhuException("GSQLBackend listSubZone query: "+e.txtReason()); + } + d_qname=""; + d_count=0; + return true; +} + + + bool GSQLBackend::superMasterBackend(const string &ip, const string &domain, const vector&nsset, string *account, DNSBackend **ddb) { string format; @@ -884,10 +903,20 @@ bool GSQLBackend::get(DNSResourceRecord &r) bool GSQLBackend::replaceRRSet(uint32_t domain_id, const string& qname, const QType& qt, const vector& rrset) { - string deleteQuery = (boost::format(d_DeleteRRSet) % domain_id % sqlEscape(qname) % sqlEscape(qt.getName())).str(); + string deleteQuery; + string deleteRRSet; + if (qt != QType::ANY) { + deleteRRSet = "delete from records where domain_id = %d and name='%s' and type='%s'"; + deleteQuery = (boost::format(deleteRRSet) % domain_id % sqlEscape(qname) % sqlEscape(qt.getName())).str(); + } else { + deleteRRSet = "delete from records where domain_id = %d and name='%s'"; + deleteQuery = (boost::format(deleteRRSet) % domain_id % sqlEscape(qname)).str(); + } d_db->doCommand(deleteQuery); - BOOST_FOREACH(const DNSResourceRecord& rr, rrset) { - feedRecord(rr); + if (rrset.size() > 0) { + BOOST_FOREACH(const DNSResourceRecord& rr, rrset) { + feedRecord(rr); + } } return true; diff --git a/pdns/backends/gsql/gsqlbackend.hh b/pdns/backends/gsql/gsqlbackend.hh index ad35a341bd..fc3c689b31 100644 --- a/pdns/backends/gsql/gsqlbackend.hh +++ b/pdns/backends/gsql/gsqlbackend.hh @@ -54,6 +54,7 @@ public: virtual bool calculateSOASerial(const string& domain, const SOAData& sd, time_t& serial); bool replaceRRSet(uint32_t domain_id, const string& qname, const QType& qt, const vector& rrset); + bool listSubZone(const string &zone, int domain_id); int addDomainKey(const string& name, const KeyData& key); bool getDomainKeys(const string& name, unsigned int kind, std::vector& keys); bool getDomainMetadata(const string& name, const std::string& kind, std::vector& meta); diff --git a/pdns/dnsbackend.hh b/pdns/dnsbackend.hh index 2dae53cba0..9b8d9093fd 100644 --- a/pdns/dnsbackend.hh +++ b/pdns/dnsbackend.hh @@ -101,6 +101,11 @@ public: return false; } + virtual bool listSubZone(const string &zone, int domain_id) + { + return false; + } + // the DNSSEC related (getDomainMetadata has broader uses too) virtual bool getDomainMetadata(const string& name, const std::string& kind, std::vector& meta) { return false; } virtual bool setDomainMetadata(const string& name, const std::string& kind, const std::vector& meta) {return false;} diff --git a/pdns/qtype.cc b/pdns/qtype.cc index 52ee10176c..fe9402b314 100644 --- a/pdns/qtype.cc +++ b/pdns/qtype.cc @@ -114,6 +114,11 @@ bool QType::operator==(const uint16_t comp) const return(comp==code); } +bool QType::operator!=(const uint16_t comp) const +{ + return(comp!=code); +} + QType &QType::operator=(const string &s) { code=chartocode(s.c_str()); diff --git a/pdns/qtype.hh b/pdns/qtype.hh index 0a4a256b31..a0c1d25b73 100644 --- a/pdns/qtype.hh +++ b/pdns/qtype.hh @@ -72,6 +72,7 @@ public: bool operator!=(const QType &) const; //!< not equal operator bool operator==(const QType &) const; //!< equality operator bool operator==(const uint16_t) const; //!< equality operator + bool operator!=(const uint16_t) const; //!< equality operator const string getName() const; //!< Get a string representation of this type uint16_t getCode() const; //!< Get the integer representation of this type diff --git a/pdns/rfc2136handler.cc b/pdns/rfc2136handler.cc index b7e39f3e5d..715843fba1 100644 --- a/pdns/rfc2136handler.cc +++ b/pdns/rfc2136handler.cc @@ -19,11 +19,11 @@ int PacketHandler::checkUpdatePrerequisites(const DNSRecord *rr, DomainInfo *di) if ( (rr->d_class == QClass::NONE || rr->d_class == QClass::ANY) && rr->d_clen != 0) return RCode::FormErr; - string rLabel = stripDot(rr->d_label); + string rrLabel = stripDot(rr->d_label); bool foundRecord=false; DNSResourceRecord rec; - di->backend->lookup(QType(QType::ANY), rLabel); + di->backend->lookup(QType(QType::ANY), rrLabel); while(di->backend->get(rec)) { if (!rec.qtype.getCode()) continue; @@ -77,76 +77,86 @@ int PacketHandler::checkUpdatePrescan(const DNSRecord *rr) { return RCode::NoError; } + // Implements section 3.4.2 of RFC2136 uint16_t PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord *rr, DomainInfo *di, bool narrow, bool haveNSEC3, const NSEC3PARAMRecordContent *ns3pr, bool *updatedSerial) { - /*DNSResourceRecord rec; - uint16_t updatedRecords = 0, deletedRecords = 0, insertedRecords = 0; + uint16_t addedRecords = 0, updatedRecords = 0; + DNSResourceRecord rec; + vector rrset, recordsToDelete; + set delnonterm, insnonterm; // used to (at the end) fix ENT records. + + string rrLabel = stripDot(rr->d_label); + QType rrType = QType(rr->d_type); - string rLabel = stripDot(rr->d_label); + if (rr->d_class == QClass::IN) { // 3.4.2.2 QClass::IN means insert or update + DLOG(L<d_class == QClass::IN) { // 3.4.2.2, Add/update records. - DLOG(L< delnonterm; - vector > recordsToUpdate; - di->backend->lookup(QType(QType::ANY), rLabel); + bool foundRecord = false; + di->backend->lookup(rrType, rrLabel); while (di->backend->get(rec)) { - if (!rec.qtype.getCode()) - delnonterm.insert(rec.qname); // we're inserting a record which is a ENT, so we must delete that ENT - if (rr->d_type == QType::SOA && rec.qtype == QType::SOA) { + rrset.push_back(rec); foundRecord = true; - DNSResourceRecord newRec = rec; - newRec.setContent(rr->d_content->getZoneRepresentation()); - newRec.ttl = rr->d_ttl; + } + + + if (foundRecord) { + + // SOA updates require the serial to be updated. + if (rrType == QType::SOA) { SOAData sdOld, sdUpdate; - fillSOAData(rec.content, sdOld); - fillSOAData(newRec.content, sdUpdate); + DNSResourceRecord *oldRec = &rrset.front(); + fillSOAData(oldRec->content, sdOld); + oldRec->setContent(rr->d_content->getZoneRepresentation()); + fillSOAData(oldRec->content, sdUpdate); if (rfc1982LessThan(sdOld.serial, sdUpdate.serial)) { - recordsToUpdate.push_back(make_pair(rec, newRec)); + updatedRecords++; + di->backend->replaceRRSet(di->id, oldRec->qname, oldRec->qtype, rrset); *updatedSerial = true; } else L<d_type == QType::CNAME && rec.qtype == QType::CNAME) { // If the update record is a cname, we update that cname. - foundRecord = true; - DNSResourceRecord newRec = rec; - newRec.ttl = rr->d_ttl; - newRec.setContent(rr->d_content->getZoneRepresentation()); - recordsToUpdate.push_back(make_pair(rec, newRec)); - } else if (rec.qtype == rr->d_type) { - string content = rr->d_content->getZoneRepresentation(); - if (rec.getZoneRepresentation() == content) { - foundRecord=true; - DNSResourceRecord newRec = rec; - newRec.ttl = rr->d_ttl; // If content matches, we can only update the TTL. - recordsToUpdate.push_back(make_pair(rec, newRec)); + + // It's not possible to have multiple CNAME's with the same NAME. So we always update. + } else if (rrType == QType::CNAME) { + for (vector::iterator i = rrset.begin(); i != rrset.end(); i++) { + i->ttl = rr->d_ttl; + i->setContent(rr->d_content->getZoneRepresentation()); + updatedRecords++; } + di->backend->replaceRRSet(di->id, rrLabel, rrType, rrset); + + // In any other case, we must check if the TYPE and RDATA match to provide an update (which effectily means a update of TTL) + } else { + foundRecord = false; + for (vector::iterator i = rrset.begin(); i != rrset.end(); i++) { + string content = rr->d_content->getZoneRepresentation(); + if (rrType == i->qtype.getCode() && i->getZoneRepresentation() == content) { + foundRecord = true; + i->ttl = rr->d_ttl; + updatedRecords++; + } + } + if (foundRecord) + di->backend->replaceRRSet(di->id, rrLabel, rrType, rrset); } } - // Update the records - for(vector >::const_iterator i=recordsToUpdate.begin(); i!=recordsToUpdate.end(); ++i){ - di->backend->updateRecord(i->first, i->second); - L<first.qname<<"|"<first.qtype.getName()<id; - L<backend->feedRecord(newRec); - insertedRecords++; - } - - // The next section will fix order and Auth fields and insert ENT's - if (insertedRecords > 0) { - string shorter(rLabel); + addedRecords++; + + + // because we added a record, we need to fix DNSSEC data. + string shorter(rrLabel); bool auth=true; set insnonterm; - if (shorter != di->zone && rr->d_type != QType::DS) { + if (shorter != di->zone && rrType != QType::DS) { do { if (shorter == di->zone) break; @@ -154,47 +164,48 @@ uint16_t PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord * bool foundShorter = false; di->backend->lookup(QType(QType::ANY), shorter); while (di->backend->get(rec)) { - if (rec.qname != rLabel) + if (rec.qname != rrLabel) foundShorter = true; if (rec.qtype == QType::NS) auth=false; } - if (!foundShorter && shorter != rLabel && shorter != di->zone) + if (!foundShorter && shorter != rrLabel && shorter != di->zone) insnonterm.insert(shorter); } while(chopOff(shorter)); } - if(haveNSEC3) { string hashed; if(!narrow) - hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr->d_iterations, ns3pr->d_salt, rLabel))); + hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr->d_iterations, ns3pr->d_salt, rrLabel))); - di->backend->updateDNSSECOrderAndAuthAbsolute(di->id, rLabel, hashed, auth); - if(!auth || rr->d_type == QType::DS) + di->backend->updateDNSSECOrderAndAuthAbsolute(di->id, rrLabel, hashed, auth); + if(!auth || rrType == QType::DS) { - di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rLabel, "NS"); - di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rLabel, "A"); - di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rLabel, "AAAA"); + di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rrLabel, "NS"); + di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rrLabel, "A"); + di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rrLabel, "AAAA"); } } else // NSEC { - di->backend->updateDNSSECOrderAndAuth(di->id, di->zone, rLabel, auth); - if(!auth || rr->d_type == QType::DS) + di->backend->updateDNSSECOrderAndAuth(di->id, di->zone, rrLabel, auth); + if(!auth || rrType == QType::DS) { - di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rLabel, "A"); - di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rLabel, "AAAA"); + di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rrLabel, "A"); + di->backend->nullifyDNSSECOrderNameAndAuth(di->id, rrLabel, "AAAA"); } } + + // If we insert an NS, all the records below it become non auth - so, we're inserting a delegate. - // Auth can only be false when the rLabel is not the zone - if (auth == false && rr->d_type == QType::NS) { - DLOG(L< qnames; - di->backend->listSubZone(rLabel, di->id); + di->backend->listSubZone(rrLabel, di->id); while(di->backend->get(rec)) { if (rec.qtype.getCode() && rec.qtype.getCode() != QType::DS) // Skip ENT and DS records. qnames.push_back(rec.qname); @@ -215,68 +226,40 @@ uint16_t PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord * di->backend->nullifyDNSSECOrderNameAndAuth(di->id, *qname, "A"); } } - - //Insert and delete ENT's - if (insnonterm.size() > 0 || delnonterm.size() > 0) { - DLOG(L<backend->updateEmptyNonTerminals(di->id, di->zone, insnonterm, delnonterm, false); - for (set::const_iterator i=insnonterm.begin(); i!=insnonterm.end(); i++) { - string hashed; - if(haveNSEC3) - { - string hashed; - if(!narrow) - hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr->d_iterations, ns3pr->d_salt, *i))); - di->backend->updateDNSSECOrderAndAuthAbsolute(di->id, *i, hashed, false); - } - } - } } } // rr->d_class == QClass::IN - - - // The following section deals with the removal of records. When the class is ANY, all records of - // that name (and/or type) are deleted. When the type is NONE, the RDATA must match as well. - // There are special cases for SOA and NS records to ensure the zone will remain operational. - //Section 3.4.2.3: Delete RRs based on name and (if provided) type, but never delete NS or SOA at the zone apex. - vector recordsToDelete; - if (rr->d_class == QClass::ANY) { - DLOG(L<zone && (rr->d_type == QType::SOA || rr->d_type == QType::NS) ) ) { - di->backend->lookup(QType(QType::ANY), rLabel); - while (di->backend->get(rec)) { - if (rec.qtype.getCode() && (rr->d_type == QType::ANY || rr->d_type == rec.qtype.getCode())) + + // Delete records - section 3.4.2.3 and 3.4.2.4 with the exception of the 'always leave 1 NS rule' as that's handled by + // the code that calls this performUpdate(). + if ((rr->d_class == QClass::ANY || rr->d_class == QClass::NONE) && rrType != QType::SOA) { // never delete a SOA. + DLOG(L<d_class<<"; rrType: "<backend->lookup(rrType, rrLabel); + while(di->backend->get(rec)) { + if (rr->d_class == QClass::ANY) { // 3.4.2.3 + if (rec.qname == di->zone && (rec.qtype == QType::NS || rec.qtype == QType::SOA)) // Never delete all SOA and NS's + rrset.push_back(rec); + else recordsToDelete.push_back(rec); } - } - } + if (rr->d_class == QClass::NONE) { // 3.4.2.4 + if (rrType == rec.qtype && rec.getZoneRepresentation() == rr->d_content->getZoneRepresentation()) + recordsToDelete.push_back(rec); + else + rrset.push_back(rec); + } - // Section 3.4.2.4, Delete a specific record that matches name, type and rdata - // There are special conditions for SOA (never delete them). There is also a special condition for NS records, - // but that's filtered out by not calling this method in those cases - There's a check to make sure we don't delete the - // last NS. - if (rr->d_class == QClass::NONE && rr->d_type != QType::SOA) { // never remove SOA. - DLOG(L<backend->lookup(QType(QType::ANY), rLabel); - while(di->backend->get(rec)) { - if (rec.qtype.getCode() && rec.qtype == rr->d_type && rec.getZoneRepresentation() == rr->d_content->getZoneRepresentation()) - recordsToDelete.push_back(rec); } - } + di->backend->replaceRRSet(di->id, rrLabel, rrType, rrset); - if (recordsToDelete.size()) { - // Perform removes on the backend and fix auth/ordername - for(vector::const_iterator recToDelete=recordsToDelete.begin(); recToDelete!=recordsToDelete.end(); ++recToDelete){ - L<qname<<"|"<qtype.getName()<backend->removeRecord(*recToDelete); - deletedRecords++; - if (recToDelete->qtype.getCode() == QType::NS && recToDelete->qname != di->zone) { + if (recordsToDelete.size()) { + // If we remove an NS which is not at apex of the zone, we need to make everthing below it auth=true as those now are not delegated anymore. + if (rrType == QType::NS && rrLabel != di->zone) { vector changeAuth; - di->backend->listSubZone(recToDelete->qname, di->id); + di->backend->listSubZone(rrLabel, di->id); while (di->backend->get(rec)) { - if (rec.qtype.getCode()) // skip ENT records + if (rec.qtype.getCode()) // skip ENT records, they are always false. changeAuth.push_back(rec.qname); } for (vector::const_iterator changeRec=changeAuth.begin(); changeRec!=changeAuth.end(); ++changeRec) { @@ -291,70 +274,68 @@ uint16_t PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord * di->backend->updateDNSSECOrderAndAuth(di->id, di->zone, *changeRec, true); } } - } - // Fix ENT records. - // We must check if we have a record below the current level and if we removed the 'last' record - // on that level. If so, we must insert an ENT record. - // We take extra care here to not 'include' the record that we just deleted. Some backends will still return it. - set insnonterm, delnonterm; - bool foundDeeper = false, foundOther = false; - di->backend->listSubZone(rLabel, di->id); - while (di->backend->get(rec)) { - if (rec.qname == rLabel && !count(recordsToDelete.begin(), recordsToDelete.end(), rec)) - foundOther = true; - if (rec.qname != rLabel) - foundDeeper = true; - } + // Fix ENT records. + // We must check if we have a record below the current level and if we removed the 'last' record + // on that level. If so, we must insert an ENT record. + // We take extra care here to not 'include' the record that we just deleted. Some backends will still return it. + bool foundDeeper = false, foundOther = false; + di->backend->listSubZone(rrLabel, di->id); + while (di->backend->get(rec)) { + if (rec.qname == rrLabel && !count(recordsToDelete.begin(), recordsToDelete.end(), rec)) + foundOther = true; + if (rec.qname != rrLabel) + foundDeeper = true; + } - if (foundDeeper && !foundOther) { - insnonterm.insert(rLabel); - } else if (!foundOther) { - // If we didn't have to insert an ENT, we might have deleted a record at very deep level - // and we must then clean up the ENT's above the deleted record. - string shorter(rLabel); - do { - bool foundRealRR=false; - if (shorter == di->zone) - break; - // The reason for a listSubZone here is because might go up the tree and find the root ENT of another branch - // consider these non ENT-records: - // a.b.c.d.e.test.com - // a.b.d.e.test.com - // if we delete a.b.c.d.e.test.com, we go up to d.e.test.com and then find a.b.d.e.test.com - // At that point we can stop deleting ENT's because the tree is in tact again. - di->backend->listSubZone(shorter, di->id); - while (di->backend->get(rec)) { - if (rec.qtype.getCode()) - foundRealRR=true; - } - if (!foundRealRR) - delnonterm.insert(shorter); - else - break; // we found a real record - tree is ok again. - }while(chopOff(shorter)); + if (foundDeeper && !foundOther) { + insnonterm.insert(rrLabel); + } else if (!foundOther) { + // If we didn't have to insert an ENT, we might have deleted a record at very deep level + // and we must then clean up the ENT's above the deleted record. + string shorter(rrLabel); + do { + bool foundRealRR=false; + if (shorter == di->zone) + break; + // The reason for a listSubZone here is because might go up the tree and find the root ENT of another branch + // consider these non ENT-records: + // a.b.c.d.e.test.com + // a.b.d.e.test.com + // if we delete a.b.c.d.e.test.com, we go up to d.e.test.com and then find a.b.d.e.test.com + // At that point we can stop deleting ENT's because the tree is in tact again. + di->backend->listSubZone(shorter, di->id); + while (di->backend->get(rec)) { + if (rec.qtype.getCode()) + foundRealRR=true; + } + if (!foundRealRR) + delnonterm.insert(shorter); + else + break; // we found a real record - tree is ok again. + }while(chopOff(shorter)); + } } + } - if (insnonterm.size() > 0 || delnonterm.size() > 0) { - DLOG(L<backend->updateEmptyNonTerminals(di->id, di->zone, insnonterm, delnonterm, false); - for (set::const_iterator i=insnonterm.begin(); i!=insnonterm.end(); i++) { + + //Insert and delete ENT's + if (insnonterm.size() > 0 || delnonterm.size() > 0) { + DLOG(L<backend->updateEmptyNonTerminals(di->id, di->zone, insnonterm, delnonterm, false); + for (set::const_iterator i=insnonterm.begin(); i!=insnonterm.end(); i++) { + string hashed; + if(haveNSEC3) + { string hashed; - if(haveNSEC3) - { - string hashed; - if(!narrow) - hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr->d_iterations, ns3pr->d_salt, *i))); - di->backend->updateDNSSECOrderAndAuthAbsolute(di->id, *i, hashed, true); - } + if(!narrow) + hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr->d_iterations, ns3pr->d_salt, *i))); + di->backend->updateDNSSECOrderAndAuthAbsolute(di->id, *i, hashed, false); } } } - L< > beforeAfterSet; - if (!haveNSEC3) { + /*if (!haveNSEC3) { for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i != mdp.d_answers.end(); ++i) { const DNSRecord *rr = &i->first; if (rr->d_place == DNSRecord::Nameserver) { string before, after; -// di.backend->getBeforeAndAfterNames(di.id, di.zone, stripDot(rr->d_label), before, after, (rr->d_class != QClass::IN)); + di.backend->getBeforeAndAfterNames(di.id, di.zone, stripDot(rr->d_label), before, after, (rr->d_class != QClass::IN)); beforeAfterSet.push_back(make_pair(before, after)); } } - } + }*/ // 3.4.2 - Perform the updates. // There's a special condition where deleting the last NS record at zone apex is never deleted (3.4.2.4) @@ -590,8 +571,11 @@ int PacketHandler::processUpdate(DNSPacket *p) { } } - // Purge the records! + string zone(di.zone); + zone.append("$"); + PC.purge(zone); // For NSEC3, nuke the complete zone. +/* if (changedRecords > 0) { if (haveNSEC3) { string zone(di.zone); @@ -602,7 +586,7 @@ int PacketHandler::processUpdate(DNSPacket *p) { //PC.purgeRange(i->first, i->second, di.zone); } } - +*/ // Section 3.6 - Update the SOA serial - outside of performUpdate because we do a SOA update for the complete update message if (changedRecords > 0 && !updatedSerial) increaseSerial(msgPrefix, di); diff --git a/regression-tests/1dyndns-update-add-invalid-record/sendupdate.pl b/regression-tests/1dyndns-update-add-invalid-record/sendupdate.pl new file mode 100755 index 0000000000..cb0acd3213 --- /dev/null +++ b/regression-tests/1dyndns-update-add-invalid-record/sendupdate.pl @@ -0,0 +1,20 @@ +#!/usr/bin/perl + +use strict; +use Net::DNS; +use Net::DNS::Update; + +my $update = Net::DNS::Update->new('test.dyndns'); +$update->push(update => rr_add('host-invalid.test.dyndns.')); + +my $res = Net::DNS::Resolver->new; +$res->nameservers($ARGV[0]); +$res->port($ARGV[1]); + +my $reply = $res->send($update); + +if ($reply) { + print "RCODE: ", $reply->header->rcode, "\n"; +} else { + print "ERROR: ", $res->errorstring, "\n"; +} diff --git a/regression-tests/2dyndns-update-replace-soa/once b/regression-tests/2dyndns-update-replace-soa/once new file mode 100644 index 0000000000..e69de29bb2 diff --git a/regression-tests/runtests b/regression-tests/runtests index 9b78dd1e71..df663859b0 100755 --- a/regression-tests/runtests +++ b/regression-tests/runtests @@ -56,7 +56,11 @@ do then echo $testname >> skipped_tests skipped=$[$skipped+1] - else + else + if [ ! -e $a/once ] + then + $a/command > /dev/null + fi $a/command > $a/real_result expected=$a/expected_result for extracontext in $extracontexts