]>
Commit | Line | Data |
---|---|---|
12471842 PL |
1 | /* |
2 | * This file is part of PowerDNS or dnsdist. | |
3 | * Copyright -- PowerDNS.COM B.V. and its contributors | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of version 2 of the GNU General Public License as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * In addition, for the avoidance of any doubt, permission is granted to | |
10 | * link this program with OpenSSL and to (re)distribute the binaries | |
11 | * produced as the result of such linking. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
21 | */ | |
39ec5d29 | 22 | #include "ixfr.hh" |
23 | #include "sstuff.hh" | |
24 | #include "dns_random.hh" | |
25 | #include "dnsrecords.hh" | |
189f9579 | 26 | #include "dnssecinfra.hh" |
60a1c204 | 27 | #include "tsigverifier.hh" |
7eafc52f | 28 | |
d67ae3b4 RG |
29 | vector<pair<vector<DNSRecord>, vector<DNSRecord> > > processIXFRRecords(const ComboAddress& master, const DNSName& zone, |
30 | const vector<DNSRecord>& records, const std::shared_ptr<SOARecordContent> masterSOA) | |
31 | { | |
32 | vector<pair<vector<DNSRecord>, vector<DNSRecord> > > ret; | |
33 | ||
34 | if (records.size() == 0 || masterSOA == nullptr) { | |
35 | return ret; | |
36 | } | |
37 | ||
38 | // we start at 1 to skip the first SOA record | |
39 | // we don't increase pos because the final SOA | |
40 | // of the previous sequence is also the first SOA | |
41 | // of this one | |
42 | for(unsigned int pos = 1; pos < records.size(); ) { | |
43 | vector<DNSRecord> remove, add; | |
44 | ||
45 | // cerr<<"Looking at record in position "<<pos<<" of type "<<QType(records[pos].d_type).getName()<<endl; | |
46 | ||
47 | if (records[pos].d_type != QType::SOA) { | |
48 | // this is an actual AXFR! | |
49 | return {{remove, records}}; | |
50 | } | |
51 | ||
52 | auto sr = getRR<SOARecordContent>(records[pos]); | |
53 | if (!sr) { | |
86f1af1c | 54 | throw std::runtime_error("Error getting the content of the first SOA record of this IXFR sequence for zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()+"'"); |
d67ae3b4 RG |
55 | } |
56 | ||
57 | // cerr<<"Serial is "<<sr->d_st.serial<<", final serial is "<<masterSOA->d_st.serial<<endl; | |
58 | ||
59 | // the serial of this SOA record is the serial of the | |
60 | // zone before the removals and updates of this sequence | |
61 | if (sr->d_st.serial == masterSOA->d_st.serial) { | |
62 | // if it's the final SOA, there is nothing for us to see | |
63 | break; | |
64 | } | |
65 | ||
66 | remove.push_back(records[pos]); // this adds the SOA | |
67 | ||
68 | // process removals | |
69 | for(pos++; pos < records.size() && records[pos].d_type != QType::SOA; ++pos) { | |
70 | remove.push_back(records[pos]); | |
71 | } | |
72 | ||
73 | if (pos >= records.size()) { | |
86f1af1c | 74 | throw std::runtime_error("No SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + master.toStringWithPort()); |
d67ae3b4 RG |
75 | } |
76 | ||
77 | sr = getRR<SOARecordContent>(records[pos]); | |
78 | if (!sr) { | |
86f1af1c | 79 | throw std::runtime_error("Invalid SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + master.toStringWithPort()); |
d67ae3b4 RG |
80 | } |
81 | ||
82 | // this is the serial of the zone after the removals | |
83 | // and updates, but that might not be the final serial | |
84 | // because there might be several sequences | |
85 | uint32_t newSerial = sr->d_st.serial; | |
86 | add.push_back(records[pos]); // this adds the new SOA | |
87 | ||
88 | // process additions | |
89 | for(pos++; pos < records.size() && records[pos].d_type != QType::SOA; ++pos) { | |
90 | add.push_back(records[pos]); | |
91 | } | |
92 | ||
93 | if (pos >= records.size()) { | |
86f1af1c | 94 | throw std::runtime_error("No SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + master.toStringWithPort()); |
d67ae3b4 RG |
95 | } |
96 | ||
97 | sr = getRR<SOARecordContent>(records[pos]); | |
98 | if (!sr) { | |
86f1af1c | 99 | throw std::runtime_error("Invalid SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + master.toStringWithPort()); |
d67ae3b4 RG |
100 | } |
101 | ||
102 | if (sr->d_st.serial != newSerial) { | |
86f1af1c | 103 | throw std::runtime_error("Invalid serial (" + std::to_string(sr->d_st.serial) + ", expecting " + std::to_string(newSerial) + ") in the SOA record finishing the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + master.toStringWithPort()); |
d67ae3b4 RG |
104 | } |
105 | ||
106 | if (newSerial == masterSOA->d_st.serial) { | |
107 | // this was the last sequence | |
108 | if (pos != (records.size() - 1)) { | |
86f1af1c | 109 | throw std::runtime_error("Trailing records after the last IXFR sequence of zone '" + zone.toLogString() + "' from " + master.toStringWithPort()); |
d67ae3b4 RG |
110 | } |
111 | } | |
112 | ||
113 | ret.push_back(make_pair(remove,add)); | |
114 | } | |
115 | ||
116 | return ret; | |
117 | } | |
118 | ||
3e7dcee6 | 119 | // Returns pairs of "remove & add" vectors. If you get an empty remove, it means you got an AXFR! |
120 | vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAddress& master, const DNSName& zone, const DNSRecord& oursr, | |
db8f9152 | 121 | const TSIGTriplet& tt, const ComboAddress* laddr, size_t maxReceivedBytes) |
39ec5d29 | 122 | { |
123 | vector<pair<vector<DNSRecord>, vector<DNSRecord> > > ret; | |
124 | vector<uint8_t> packet; | |
125 | DNSPacketWriter pw(packet, zone, QType::IXFR); | |
126 | pw.getHeader()->qr=0; | |
127 | pw.getHeader()->rd=0; | |
128 | pw.getHeader()->id=dns_random(0xffff); | |
189f9579 | 129 | pw.startRecord(zone, QType::SOA, 0, QClass::IN, DNSResourceRecord::AUTHORITY); |
39ec5d29 | 130 | oursr.d_content->toPacket(pw); |
189f9579 | 131 | |
39ec5d29 | 132 | pw.commit(); |
60a1c204 RG |
133 | TSIGRecordContent trc; |
134 | TSIGTCPVerifier tsigVerifier(tt, master, trc); | |
98c9ec39 | 135 | if(!tt.algo.empty()) { |
bd051ad6 PL |
136 | TSIGHashEnum the; |
137 | getTSIGHashEnum(tt.algo, the); | |
bd051ad6 PL |
138 | try { |
139 | trc.d_algoName = getTSIGAlgoName(the); | |
140 | } catch(PDNSException& pe) { | |
86f1af1c | 141 | throw std::runtime_error("TSIG algorithm '"+tt.algo.toLogString()+"' is unknown."); |
bd051ad6 | 142 | } |
189f9579 | 143 | trc.d_time = time((time_t*)NULL); |
144 | trc.d_fudge = 300; | |
145 | trc.d_origID=ntohs(pw.getHeader()->id); | |
146 | trc.d_eRcode=0; | |
ea3816cf | 147 | addTSIG(pw, trc, tt.name, tt.secret, "", false); |
189f9579 | 148 | } |
39ec5d29 | 149 | uint16_t len=htons(packet.size()); |
150 | string msg((const char*)&len, 2); | |
151 | msg.append((const char*)&packet[0], packet.size()); | |
152 | ||
153 | Socket s(master.sin4.sin_family, SOCK_STREAM); | |
154 | // cout<<"going to connect"<<endl; | |
3e7dcee6 | 155 | if(laddr) |
156 | s.bind(*laddr); | |
39ec5d29 | 157 | s.connect(master); |
158 | // cout<<"Connected"<<endl; | |
159 | s.writen(msg); | |
160 | ||
161 | // CURRENT MASTER SOA | |
162 | // REPEAT: | |
163 | // SOA WHERE THIS DELTA STARTS | |
164 | // RECORDS TO REMOVE | |
165 | // SOA WHERE THIS DELTA GOES | |
166 | // RECORDS TO ADD | |
167 | // CURRENT MASTER SOA | |
d67ae3b4 | 168 | std::shared_ptr<SOARecordContent> masterSOA = nullptr; |
39ec5d29 | 169 | vector<DNSRecord> records; |
db8f9152 | 170 | size_t receivedBytes = 0; |
60a1c204 | 171 | |
39ec5d29 | 172 | for(;;) { |
d67ae3b4 | 173 | if(s.read((char*)&len, sizeof(len)) != sizeof(len)) |
39ec5d29 | 174 | break; |
d67ae3b4 | 175 | |
39ec5d29 | 176 | len=ntohs(len); |
177 | // cout<<"Got chunk of "<<len<<" bytes"<<endl; | |
178 | if(!len) | |
179 | break; | |
db8f9152 RG |
180 | |
181 | if (maxReceivedBytes > 0 && (maxReceivedBytes - receivedBytes) < (size_t) len) | |
86f1af1c | 182 | throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toLogString()+"' from master "+master.toStringWithPort()); |
db8f9152 | 183 | |
39ec5d29 | 184 | char reply[len]; |
185 | readn2(s.getHandle(), reply, len); | |
db8f9152 | 186 | receivedBytes += len; |
d67ae3b4 | 187 | |
27c0050c | 188 | MOADNSParser mdp(false, string(reply, len)); |
189f9579 | 189 | if(mdp.d_header.rcode) |
86f1af1c | 190 | throw std::runtime_error("Got an error trying to IXFR zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode)); |
189f9579 | 191 | |
39ec5d29 | 192 | // cout<<"Got a response, rcode: "<<mdp.d_header.rcode<<", got "<<mdp.d_answers.size()<<" answers"<<endl; |
60a1c204 RG |
193 | |
194 | if(!tt.algo.empty()) { // TSIG verify message | |
195 | tsigVerifier.check(std::string(reply, len), mdp); | |
196 | } | |
197 | ||
39ec5d29 | 198 | for(auto& r: mdp.d_answers) { |
199 | // cout<<r.first.d_name<< " " <<r.first.d_content->getZoneRepresentation()<<endl; | |
d67ae3b4 RG |
200 | if(!masterSOA) { |
201 | // we have not seen the first SOA record yet | |
202 | if (r.first.d_type != QType::SOA) { | |
86f1af1c | 203 | throw std::runtime_error("The first record of the IXFR answer for zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()+"' is not a SOA ("+QType(r.first.d_type).getName()+")"); |
d67ae3b4 RG |
204 | } |
205 | ||
ba3c54cb | 206 | auto sr = getRR<SOARecordContent>(r.first); |
d67ae3b4 | 207 | if (!sr) { |
86f1af1c | 208 | throw std::runtime_error("Error getting the content of the first SOA record of the IXFR answer for zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()+"'"); |
d67ae3b4 RG |
209 | } |
210 | ||
211 | if(sr->d_st.serial == std::dynamic_pointer_cast<SOARecordContent>(oursr.d_content)->d_st.serial) { | |
212 | // we are up to date | |
213 | return ret; | |
214 | } | |
215 | masterSOA = sr; | |
39ec5d29 | 216 | } |
7eafc52f | 217 | |
d67ae3b4 RG |
218 | if(r.first.d_place != DNSResourceRecord::ANSWER) { |
219 | if(r.first.d_type == QType::TSIG) | |
220 | continue; | |
39ec5d29 | 221 | |
d67ae3b4 RG |
222 | if(r.first.d_type == QType::OPT) |
223 | continue; | |
224 | ||
86f1af1c | 225 | throw std::runtime_error("Unexpected record (" +QType(r.first.d_type).getName()+") in non-answer section ("+std::to_string(r.first.d_place)+")in IXFR response for zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()); |
d67ae3b4 RG |
226 | } |
227 | ||
228 | r.first.d_name.makeUsRelative(zone); | |
229 | records.push_back(r.first); | |
39ec5d29 | 230 | } |
39ec5d29 | 231 | } |
d67ae3b4 RG |
232 | |
233 | // cout<<"Got "<<records.size()<<" records"<<endl; | |
234 | ||
235 | return processIXFRRecords(master, zone, records, masterSOA); | |
39ec5d29 | 236 | } |