]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ixfr.cc
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
24 #include "dns_random.hh"
25 #include "dnsrecords.hh"
26 #include "dnssecinfra.hh"
27 #include "tsigverifier.hh"
29 vector
< pair
< vector
< DNSRecord
>, vector
< DNSRecord
> > > processIXFRRecords ( const ComboAddress
& primary
, const DNSName
& zone
,
30 const vector
< DNSRecord
>& records
, const std :: shared_ptr
< const SOARecordContent
>& primarySOA
)
32 vector
< pair
< vector
< DNSRecord
>, vector
< DNSRecord
> > > ret
;
34 if ( records
. size () == 0 || primarySOA
== nullptr ) {
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
42 for ( unsigned int pos
= 1 ; pos
< records
. size (); ) {
43 vector
< DNSRecord
> remove
, add
;
45 // cerr<<"Looking at record in position "<<pos<<" of type "<<QType(records[pos].d_type).getName()<<endl;
47 if ( records
[ pos
]. d_type
!= QType :: SOA
) {
48 // this is an actual AXFR!
49 return {{ remove
, records
}};
52 auto sr
= getRR
< SOARecordContent
>( records
[ pos
]);
54 throw std :: runtime_error ( "Error getting the content of the first SOA record of this IXFR sequence for zone '" + zone
. toLogString ()+ "' from primary '" + primary
. toStringWithPort ()+ "'" );
57 // cerr<<"Serial is "<<sr->d_st.serial<<", final serial is "<<primarySOA->d_st.serial<<endl;
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
== primarySOA
-> d_st
. serial
) {
62 if ( records
. size () == 2 ) {
63 // if the entire update is two SOAs records with the same
64 // serial, this is actually an empty AXFR!
65 return {{ remove
, records
}};
68 // if it's the final SOA, there is nothing for us to see
72 remove
. push_back ( records
[ pos
]); // this adds the SOA
75 for ( pos
++; pos
< records
. size () && records
[ pos
]. d_type
!= QType :: SOA
; ++ pos
) {
76 remove
. push_back ( records
[ pos
]);
79 if ( pos
>= records
. size ()) {
80 throw std :: runtime_error ( "No SOA record to finish the removals part of the IXFR sequence of zone '" + zone
. toLogString () + "' from " + primary
. toStringWithPort ());
83 sr
= getRR
< SOARecordContent
>( records
[ pos
]);
85 throw std :: runtime_error ( "Invalid SOA record to finish the removals part of the IXFR sequence of zone '" + zone
. toLogString () + "' from " + primary
. toStringWithPort ());
88 // this is the serial of the zone after the removals
89 // and updates, but that might not be the final serial
90 // because there might be several sequences
91 uint32_t newSerial
= sr
-> d_st
. serial
;
92 add
. push_back ( records
[ pos
]); // this adds the new SOA
95 for ( pos
++; pos
< records
. size () && records
[ pos
]. d_type
!= QType :: SOA
; ++ pos
) {
96 add
. push_back ( records
[ pos
]);
99 if ( pos
>= records
. size ()) {
100 throw std :: runtime_error ( "No SOA record to finish the additions part of the IXFR sequence of zone '" + zone
. toLogString () + "' from " + primary
. toStringWithPort ());
103 sr
= getRR
< SOARecordContent
>( records
[ pos
]);
105 throw std :: runtime_error ( "Invalid SOA record to finish the additions part of the IXFR sequence of zone '" + zone
. toLogString () + "' from " + primary
. toStringWithPort ());
108 if ( sr
-> d_st
. serial
!= newSerial
) {
109 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 " + primary
. toStringWithPort ());
112 if ( newSerial
== primarySOA
-> d_st
. serial
) {
113 // this was the last sequence
114 if ( pos
!= ( records
. size () - 1 )) {
115 throw std :: runtime_error ( "Trailing records after the last IXFR sequence of zone '" + zone
. toLogString () + "' from " + primary
. toStringWithPort ());
119 ret
. emplace_back ( remove
, add
);
125 // Returns pairs of "remove & add" vectors. If you get an empty remove, it means you got an AXFR!
126 // NOLINTNEXTLINE(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
127 vector
< pair
< vector
< DNSRecord
>, vector
< DNSRecord
>>> getIXFRDeltas ( const ComboAddress
& primary
, const DNSName
& zone
, const DNSRecord
& oursr
,
128 uint16_t xfrTimeout
, bool totalTimeout
,
129 const TSIGTriplet
& tt
, const ComboAddress
* laddr
, size_t maxReceivedBytes
)
131 // Auth documents xfrTimeout to be a max idle time (sets totalTimeout=false)
132 // Rec documents it to be a total XFR time (sets totalTimeout=true)
134 vector
< pair
< vector
< DNSRecord
>, vector
< DNSRecord
> > > ret
;
135 vector
< uint8_t > packet
;
136 DNSPacketWriter
pw ( packet
, zone
, QType :: IXFR
);
137 pw
. getHeader ()-> qr
= 0 ;
138 pw
. getHeader ()-> rd
= 0 ;
139 pw
. getHeader ()-> id
= dns_random_uint16 ();
140 pw
. startRecord ( zone
, QType :: SOA
, 0 , QClass :: IN
, DNSResourceRecord :: AUTHORITY
);
141 oursr
. getContent ()-> toPacket ( pw
);
144 TSIGRecordContent trc
;
145 TSIGTCPVerifier
tsigVerifier ( tt
, primary
, trc
);
146 if (! tt
. algo
. empty ()) {
148 getTSIGHashEnum ( tt
. algo
, the
);
150 trc
. d_algoName
= getTSIGAlgoName ( the
);
151 } catch ( PDNSException
& pe
) {
152 throw std :: runtime_error ( "TSIG algorithm '" + tt
. algo
. toLogString ()+ "' is unknown." );
154 trc
. d_time
= time (( time_t *) nullptr );
156 trc
. d_origID
= ntohs ( pw
. getHeader ()-> id
);
158 addTSIG ( pw
, trc
, tt
. name
, tt
. secret
, "" , false );
160 uint16_t len
= htons ( packet
. size ());
161 string
msg (( const char *)& len
, 2 );
162 msg
. append (( const char *)& packet
[ 0 ], packet
. size ());
164 Socket
s ( primary
. sin4
. sin_family
, SOCK_STREAM
);
165 if ( laddr
!= nullptr ) {
170 const time_t xfrStart
= time ( nullptr );
172 // Helper function: if we have a total timeout, check it and set elapsed to the total time taken sofar,
173 // otherwise set elapsed to 0, making the total time limit ineffective
174 const auto timeoutChecker
= [=] () -> time_t {
177 elapsed
= time ( nullptr ) - xfrStart
;
178 if ( elapsed
>= xfrTimeout
) {
179 throw std :: runtime_error ( "Reached the maximum elapsed time in an IXFR delta for zone '" + zone
. toLogString () + "' from primary " + primary
. toStringWithPort ());
185 s
. connect ( primary
, xfrTimeout
);
187 time_t elapsed
= timeoutChecker ();
188 // coverity[store_truncates_time_t]
189 s
. writenWithTimeout ( msg
. data (), msg
. size (), xfrTimeout
- elapsed
);
191 // CURRENT PRIMARY SOA
193 // SOA WHERE THIS DELTA STARTS
195 // SOA WHERE THIS DELTA GOES
197 // CURRENT PRIMARY SOA
198 std :: shared_ptr
< const SOARecordContent
> primarySOA
= nullptr ;
199 vector
< DNSRecord
> records
;
200 size_t receivedBytes
= 0 ;
203 enum transferStyle
{ Unknown
, AXFR
, IXFR
} style
= Unknown
;
204 const unsigned int expectedSOAForAXFR
= 2 ;
205 const unsigned int expectedSOAForIXFR
= 3 ;
206 unsigned int primarySOACount
= 0 ;
210 // IXFR or AXFR style end reached? We don't want to process trailing data after the closing SOA
211 if ( style
== AXFR
&& primarySOACount
== expectedSOAForAXFR
) {
215 if ( style
== IXFR
&& primarySOACount
== expectedSOAForIXFR
) {
220 elapsed
= timeoutChecker ();
222 const struct timeval remainingTime
= { . tv_sec
= xfrTimeout
- elapsed
, . tv_usec
= 0 };
223 const struct timeval idleTime
= remainingTime
;
224 readn2WithTimeout ( s
. getHandle (), & len
, sizeof ( len
), idleTime
, remainingTime
, false );
226 catch ( const runtime_error
& ex
) {
236 // Currently no more break statements after this
238 if ( maxReceivedBytes
> 0 && ( maxReceivedBytes
- receivedBytes
) < ( size_t ) len
) {
239 throw std :: runtime_error ( "Reached the maximum number of received bytes in an IXFR delta for zone '" + zone
. toLogString ()+ "' from primary " + primary
. toStringWithPort ());
244 elapsed
= timeoutChecker ();
245 const struct timeval remainingTime
= { . tv_sec
= xfrTimeout
- elapsed
, . tv_usec
= 0 };
246 const struct timeval idleTime
= remainingTime
;
247 readn2WithTimeout ( s
. getHandle (), reply
. data (), len
, idleTime
, remainingTime
, false );
248 receivedBytes
+= len
;
250 MOADNSParser
mdp ( false , reply
);
251 if ( mdp
. d_header
. rcode
) {
252 throw std :: runtime_error ( "Got an error trying to IXFR zone '" + zone
. toLogString ()+ "' from primary '" + primary
. toStringWithPort ()+ "': " + RCode :: to_s ( mdp
. d_header
. rcode
));
255 if (! tt
. algo
. empty ()) { // TSIG verify message
256 tsigVerifier
. check ( reply
, mdp
);
259 for ( auto & r
: mdp
. d_answers
) {
261 // we have not seen the first SOA record yet
262 if ( r
. first
. d_type
!= QType :: SOA
) {
263 throw std :: runtime_error ( "The first record of the IXFR answer for zone '" + zone
. toLogString ()+ "' from primary '" + primary
. toStringWithPort ()+ "' is not a SOA (" + QType ( r
. first
. d_type
). toString ()+ ")" );
266 auto sr
= getRR
< SOARecordContent
>( r
. first
);
268 throw std :: runtime_error ( "Error getting the content of the first SOA record of the IXFR answer for zone '" + zone
. toLogString ()+ "' from primary '" + primary
. toStringWithPort ()+ "'" );
271 if ( sr
-> d_st
. serial
== getRR
< SOARecordContent
>( oursr
)-> d_st
. serial
) {
275 primarySOA
= std :: move ( sr
);
277 } else if ( r
. first
. d_type
== QType :: SOA
) {
278 auto sr
= getRR
< SOARecordContent
>( r
. first
);
280 throw std :: runtime_error ( "Error getting the content of SOA record of IXFR answer for zone '" + zone
. toLogString ()+ "' from primary '" + primary
. toStringWithPort ()+ "'" );
283 // we hit a marker SOA record
284 if ( primarySOA
-> d_st
. serial
== sr
-> d_st
. serial
) {
288 // When we see the 2nd record, we can decide what the style is
289 if ( records
. size () == 1 && style
== Unknown
) {
290 if ( r
. first
. d_type
!= QType :: SOA
) {
291 // Non-empty AXFR style has a non-SOA record following the first SOA
294 else if ( primarySOACount
== expectedSOAForAXFR
) {
295 // Empty zone AXFR style: start SOA is immediately followed by end marker SOA
299 // IXFR has a 2nd SOA (with different serial) following the first
304 if ( r
. first
. d_place
!= DNSResourceRecord :: ANSWER
) {
305 if ( r
. first
. d_type
== QType :: TSIG
)
308 if ( r
. first
. d_type
== QType :: OPT
)
311 throw std :: runtime_error ( "Unexpected record (" + QType ( r
. first
. d_type
). toString ()+ ") in non-answer section (" + std :: to_string ( r
. first
. d_place
)+ ") in IXFR response for zone '" + zone
. toLogString ()+ "' from primary '" + primary
. toStringWithPort ());
314 r
. first
. d_name
. makeUsRelative ( zone
);
315 records
. push_back ( r
. first
);
321 if ( primarySOACount
!= expectedSOAForIXFR
) {
322 throw std :: runtime_error ( "Incomplete IXFR transfer (primarySOACount=" + std :: to_string ( primarySOACount
) + ") for '" + zone
. toLogString () + "' from primary '" + primary
. toStringWithPort () + " state=" + state
);
326 if ( primarySOACount
!= expectedSOAForAXFR
){
327 throw std :: runtime_error ( "Incomplete AXFR style transfer (primarySOACount=" + std :: to_string ( primarySOACount
) + ") for '" + zone
. toLogString () + "' from primary '" + primary
. toStringWithPort () + " state=" + state
);
331 throw std :: runtime_error ( "Incomplete XFR (primarySOACount=" + std :: to_string ( primarySOACount
) + ") for '" + zone
. toLogString () + "' from primary '" + primary
. toStringWithPort () + " state=" + state
);
335 return processIXFRRecords ( primary
, zone
, records
, primarySOA
);