]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/rpzloader.cc
1 #include "dnsparser.hh"
2 #include "dnsrecords.hh"
7 #include "rec-lua-conf.hh"
8 #include "rpzloader.hh"
9 #include "zoneparser-tng.hh"
10 #include "threadname.hh"
12 static Netmask
makeNetmaskFromRPZ ( const DNSName
& name
)
14 auto parts
= name
. getRawLabels ();
16 * why 2?, the minimally valid IPv6 address that can be encoded in an RPZ is
17 * $NETMASK.zz (::/$NETMASK)
20 if ( parts
. size () < 2 || parts
. size () > 9 )
21 throw PDNSException ( "Invalid IP address in RPZ: " + name
. toLogString ());
23 bool isV6
= ( stoi ( parts
[ 0 ]) > 32 );
26 for ( auto & part
: parts
) {
27 // Check if we have an IPv4 octet
32 if ( pdns_iequals ( part
, "zz" )) {
34 throw PDNSException ( "more than one 'zz' label found in RPZ name" + name
. toLogString ());
41 if ( isV6
&& parts
. size () < 9 && ! hadZZ
)
42 throw PDNSException ( "No 'zz' label found in an IPv6 RPZ name shorter than 9 elements: " + name
. toLogString ());
44 if ( parts
. size () == 5 && ! isV6
)
45 return Netmask ( parts
[ 4 ]+ "." + parts
[ 3 ]+ "." + parts
[ 2 ]+ "." + parts
[ 1 ]+ "/" + parts
[ 0 ]);
49 for ( uint8_t i
= parts
. size ()- 1 ; i
> 0 ; i
--) {
51 if ( parts
[ i
] == "" && i
== 1 && i
== parts
. size ()- 1 )
53 if ( parts
[ i
] == "" && i
!= parts
. size ()- 1 )
55 if ( parts
[ i
] != "" && i
!= 1 )
63 static void RPZRecordToPolicy ( const DNSRecord
& dr
, std :: shared_ptr
< DNSFilterEngine :: Zone
> zone
, bool addOrRemove
, boost :: optional
< DNSFilterEngine :: Policy
> defpol
, bool defpolOverrideLocal
, uint32_t maxTTL
)
65 static const DNSName
drop ( "rpz-drop." ), truncate ( "rpz-tcp-only." ), noaction ( "rpz-passthru." );
66 static const DNSName
rpzClientIP ( "rpz-client-ip" ), rpzIP ( "rpz-ip" ),
67 rpzNSDname ( "rpz-nsdname" ), rpzNSIP ( "rpz-nsip." );
68 static const std :: string
rpzPrefix ( "rpz-" );
70 DNSFilterEngine :: Policy pol
;
71 bool defpolApplied
= false ;
73 if ( dr
. d_class
!= QClass :: IN
) {
77 if ( dr
. d_type
== QType :: CNAME
) {
78 auto crc
= getRR
< CNAMERecordContent
>( dr
);
82 auto crcTarget
= crc
-> getTarget ();
87 else if ( crcTarget
. isRoot ()) {
88 // cerr<<"Wants NXDOMAIN for "<<dr.d_name<<": ";
89 pol
. d_kind
= DNSFilterEngine :: PolicyKind :: NXDOMAIN
;
90 } else if ( crcTarget
== g_wildcarddnsname
) {
91 // cerr<<"Wants NODATA for "<<dr.d_name<<": ";
92 pol
. d_kind
= DNSFilterEngine :: PolicyKind :: NODATA
;
94 else if ( crcTarget
== drop
) {
95 // cerr<<"Wants DROP for "<<dr.d_name<<": ";
96 pol
. d_kind
= DNSFilterEngine :: PolicyKind :: Drop
;
98 else if ( crcTarget
== truncate
) {
99 // cerr<<"Wants TRUNCATE for "<<dr.d_name<<": ";
100 pol
. d_kind
= DNSFilterEngine :: PolicyKind :: Truncate
;
102 else if ( crcTarget
== noaction
) {
103 // cerr<<"Wants NOACTION for "<<dr.d_name<<": ";
104 pol
. d_kind
= DNSFilterEngine :: PolicyKind :: NoAction
;
106 /* "The special RPZ encodings which are not to be taken as Local Data are
107 CNAMEs with targets that are:
108 + "." (NXDOMAIN action),
109 + "*." (NODATA action),
110 + a top level domain starting with "rpz-",
111 + a child of a top level domain starting with "rpz-".
113 else if (! crcTarget
. empty () && ! crcTarget
. isRoot () && crcTarget
. getRawLabel ( crcTarget
. countLabels () - 1 ). compare ( 0 , rpzPrefix
. length (), rpzPrefix
) == 0 ) {
114 /* this is very likely an higher format number or a configuration error,
115 let's just ignore it. */
116 g_log
<< Logger :: Info
<< "Discarding unsupported RPZ entry " << crcTarget
<< " for " << dr
. d_name
<< endl
;
120 pol
. d_kind
= DNSFilterEngine :: PolicyKind :: Custom
;
121 pol
. d_custom
. emplace_back ( dr
. d_content
);
122 // cerr<<"Wants custom "<<crcTarget<<" for "<<dr.d_name<<": ";
126 if ( defpol
&& defpolOverrideLocal
) {
128 defpolApplied
= true ;
131 pol
. d_kind
= DNSFilterEngine :: PolicyKind :: Custom
;
132 pol
. d_custom
. emplace_back ( dr
. d_content
);
133 // cerr<<"Wants custom "<<dr.d_content->getZoneRepresentation()<<" for "<<dr.d_name<<": ";
137 if (! defpolApplied
|| defpol
-> d_ttl
< 0 ) {
138 pol
. d_ttl
= static_cast < int32_t >( std :: min ( maxTTL
, dr
. d_ttl
));
140 pol
. d_ttl
= static_cast < int32_t >( std :: min ( maxTTL
, static_cast < uint32_t >( pol
. d_ttl
)));
143 // now to DO something with that
145 if ( dr
. d_name
. isPartOf ( rpzNSDname
)) {
146 DNSName filt
= dr
. d_name
. makeRelative ( rpzNSDname
);
148 zone
-> addNSTrigger ( filt
, std :: move ( pol
));
150 zone
-> rmNSTrigger ( filt
, std :: move ( pol
));
151 } else if ( dr
. d_name
. isPartOf ( rpzClientIP
)) {
152 DNSName filt
= dr
. d_name
. makeRelative ( rpzClientIP
);
153 auto nm
= makeNetmaskFromRPZ ( filt
);
155 zone
-> addClientTrigger ( nm
, std :: move ( pol
));
157 zone
-> rmClientTrigger ( nm
, std :: move ( pol
));
159 } else if ( dr
. d_name
. isPartOf ( rpzIP
)) {
160 // cerr<<"Should apply answer content IP policy: "<<dr.d_name<<endl;
161 DNSName filt
= dr
. d_name
. makeRelative ( rpzIP
);
162 auto nm
= makeNetmaskFromRPZ ( filt
);
164 zone
-> addResponseTrigger ( nm
, std :: move ( pol
));
166 zone
-> rmResponseTrigger ( nm
, std :: move ( pol
));
167 } else if ( dr
. d_name
. isPartOf ( rpzNSIP
)) {
168 DNSName filt
= dr
. d_name
. makeRelative ( rpzNSIP
);
169 auto nm
= makeNetmaskFromRPZ ( filt
);
171 zone
-> addNSIPTrigger ( nm
, std :: move ( pol
));
173 zone
-> rmNSIPTrigger ( nm
, std :: move ( pol
));
176 /* if we did override the existing policy with the default policy,
177 we might turn two A or AAAA into a CNAME, which would trigger
178 an exception. Let's just ignore it. */
179 zone
-> addQNameTrigger ( dr
. d_name
, std :: move ( pol
), defpolApplied
);
182 zone
-> rmQNameTrigger ( dr
. d_name
, std :: move ( pol
));
187 static shared_ptr
< SOARecordContent
> loadRPZFromServer ( const ComboAddress
& master
, const DNSName
& zoneName
, std :: shared_ptr
< DNSFilterEngine :: Zone
> zone
, boost :: optional
< DNSFilterEngine :: Policy
> defpol
, bool defpolOverrideLocal
, uint32_t maxTTL
, const TSIGTriplet
& tt
, size_t maxReceivedBytes
, const ComboAddress
& localAddress
, uint16_t axfrTimeout
)
189 g_log
<< Logger :: Warning
<< "Loading RPZ zone '" << zoneName
<< "' from " << master
. toStringWithPort ()<< endl
;
191 g_log
<< Logger :: Warning
<< "With TSIG key '" << tt
. name
<< "' of algorithm '" << tt
. algo
<< "'" << endl
;
193 ComboAddress
local ( localAddress
);
194 if ( local
== ComboAddress ())
195 local
= getQueryLocalAddress ( master
. sin4
. sin_family
, 0 );
197 AXFRRetriever
axfr ( master
, zoneName
, tt
, & local
, maxReceivedBytes
, axfrTimeout
);
198 unsigned int nrecords
= 0 ;
200 vector
< DNSRecord
> chunk
;
202 time_t axfrStart
= time ( nullptr );
203 time_t axfrNow
= time ( nullptr );
204 shared_ptr
< SOARecordContent
> sr
;
205 while ( axfr
. getChunk ( nop
, & chunk
, ( axfrStart
+ axfrTimeout
- axfrNow
))) {
206 for ( auto & dr
: chunk
) {
207 if ( dr
. d_type
== QType :: NS
|| dr
. d_type
== QType :: TSIG
) {
211 dr
. d_name
. makeUsRelative ( zoneName
);
212 if ( dr
. d_type
== QType :: SOA
) {
213 sr
= getRR
< SOARecordContent
>( dr
);
217 RPZRecordToPolicy ( dr
, zone
, true , defpol
, defpolOverrideLocal
, maxTTL
);
220 axfrNow
= time ( nullptr );
221 if ( axfrNow
< axfrStart
|| axfrNow
- axfrStart
> axfrTimeout
) {
222 throw PDNSException ( "Total AXFR time exceeded!" );
224 if ( last
!= time ( 0 )) {
225 g_log
<< Logger :: Info
<< "Loaded & indexed " << nrecords
<< " policy records so far for RPZ zone '" << zoneName
<< "'" << endl
;
229 g_log
<< Logger :: Info
<< "Done: " << nrecords
<< " policy records active, SOA: " << sr
-> getZoneRepresentation ()<< endl
;
233 // this function is silent - you do the logging
234 std :: shared_ptr
< SOARecordContent
> loadRPZFromFile ( const std :: string
& fname
, std :: shared_ptr
< DNSFilterEngine :: Zone
> zone
, boost :: optional
< DNSFilterEngine :: Policy
> defpol
, bool defpolOverrideLocal
, uint32_t maxTTL
)
236 shared_ptr
< SOARecordContent
> sr
= nullptr ;
237 ZoneParserTNG
zpt ( fname
);
238 DNSResourceRecord drr
;
240 while ( zpt
. get ( drr
)) {
242 if ( drr
. qtype
. getCode () == QType :: CNAME
&& drr
. content
. empty ())
245 if ( dr
. d_type
== QType :: SOA
) {
246 sr
= getRR
< SOARecordContent
>( dr
);
248 zone
-> setDomain ( domain
);
250 else if ( dr
. d_type
== QType :: NS
) {
254 dr
. d_name
= dr
. d_name
. makeRelative ( domain
);
255 RPZRecordToPolicy ( dr
, zone
, true , defpol
, defpolOverrideLocal
, maxTTL
);
258 catch ( const PDNSException
& pe
) {
259 throw PDNSException ( "Issue parsing '" + drr
. qname
. toLogString ()+ "' '" + drr
. content
+ "' at " + zpt
. getLineOfFile ()+ ": " + pe
. reason
);
266 static std :: unordered_map
< std :: string
, shared_ptr
< rpzStats
> > s_rpzStats
;
267 static std :: mutex s_rpzStatsMutex
;
269 shared_ptr
< rpzStats
> getRPZZoneStats ( const std :: string
& zone
)
271 std :: lock_guard
< std :: mutex
> l ( s_rpzStatsMutex
);
272 if ( s_rpzStats
. find ( zone
) == s_rpzStats
. end ()) {
273 s_rpzStats
[ zone
] = std :: make_shared
< rpzStats
>();
275 return s_rpzStats
[ zone
];
278 static void incRPZFailedTransfers ( const std :: string
& zone
)
280 auto stats
= getRPZZoneStats ( zone
);
281 if ( stats
!= nullptr )
282 stats
-> d_failedTransfers
++;
285 static void setRPZZoneNewState ( const std :: string
& zone
, uint32_t serial
, uint64_t numberOfRecords
, bool wasAXFR
)
287 auto stats
= getRPZZoneStats ( zone
);
288 if ( stats
== nullptr )
290 stats
-> d_successfulTransfers
++;
292 stats
-> d_fullTransfers
++;
294 stats
-> d_lastUpdate
= time ( nullptr );
295 stats
-> d_serial
= serial
;
296 stats
-> d_numberOfRecords
= numberOfRecords
;
299 static bool dumpZoneToDisk ( const DNSName
& zoneName
, const std :: shared_ptr
< DNSFilterEngine :: Zone
>& newZone
, const std :: string
& dumpZoneFileName
)
301 std :: string temp
= dumpZoneFileName
+ "XXXXXX" ;
302 int fd
= mkstemp (& temp
. at ( 0 ));
304 g_log
<< Logger :: Warning
<< "Unable to open a file to dump the content of the RPZ zone " << zoneName
. toLogString ()<< endl
;
308 auto fp
= std :: unique_ptr
< FILE , int (*)( FILE *)>( fdopen ( fd
, "w+" ), fclose
);
311 g_log
<< Logger :: Warning
<< "Unable to open a file pointer to dump the content of the RPZ zone " << zoneName
. toLogString ()<< endl
;
317 newZone
-> dump ( fp
. get ());
319 catch ( const std :: exception
& e
) {
320 g_log
<< Logger :: Warning
<< "Error while dumping the content of the RPZ zone " << zoneName
. toLogString ()<< ": " << e
. what ()<< endl
;
324 if ( fflush ( fp
. get ()) != 0 ) {
325 g_log
<< Logger :: Warning
<< "Error while flushing the content of the RPZ zone " << zoneName
. toLogString ()<< " to the dump file: " << strerror ( errno
)<< endl
;
329 if ( fsync ( fileno ( fp
. get ())) != 0 ) {
330 g_log
<< Logger :: Warning
<< "Error while syncing the content of the RPZ zone " << zoneName
. toLogString ()<< " to the dump file: " << strerror ( errno
)<< endl
;
334 if ( fclose ( fp
. release ()) != 0 ) {
335 g_log
<< Logger :: Warning
<< "Error while writing the content of the RPZ zone " << zoneName
. toLogString ()<< " to the dump file: " << strerror ( errno
)<< endl
;
339 if ( rename ( temp
. c_str (), dumpZoneFileName
. c_str ()) != 0 ) {
340 g_log
<< Logger :: Warning
<< "Error while moving the content of the RPZ zone " << zoneName
. toLogString ()<< " to the dump file: " << strerror ( errno
)<< endl
;
347 void RPZIXFRTracker ( const std :: vector
< ComboAddress
>& masters
, boost :: optional
< DNSFilterEngine :: Policy
> defpol
, bool defpolOverrideLocal
, uint32_t maxTTL
, size_t zoneIdx
, const TSIGTriplet
& tt
, size_t maxReceivedBytes
, const ComboAddress
& localAddress
, const uint16_t axfrTimeout
, std :: shared_ptr
< SOARecordContent
> sr
, std :: string dumpZoneFileName
, uint64_t configGeneration
)
349 setThreadName ( "pdns-r/RPZIXFR" );
350 bool isPreloaded
= sr
!= nullptr ;
351 auto luaconfsLocal
= g_luaconfs
. getLocal ();
352 /* we can _never_ modify this zone directly, we need to do a full copy then replace the existing zone */
353 std :: shared_ptr
< DNSFilterEngine :: Zone
> oldZone
= luaconfsLocal
-> dfe
. getZone ( zoneIdx
);
355 g_log
<< Logger :: Error
<< "Unable to retrieve RPZ zone with index " << zoneIdx
<< " from the configuration, exiting" << endl
;
358 uint32_t refresh
= oldZone
-> getRefresh ();
359 DNSName zoneName
= oldZone
-> getDomain ();
360 std :: string polName
= oldZone
-> getName () ? *( oldZone
-> getName ()) : zoneName
. toString ();
363 /* if we received an empty sr, the zone was not really preloaded */
365 /* full copy, as promised */
366 std :: shared_ptr
< DNSFilterEngine :: Zone
> newZone
= std :: make_shared
< DNSFilterEngine :: Zone
>(* oldZone
);
367 for ( const auto & master
: masters
) {
369 sr
= loadRPZFromServer ( master
, zoneName
, newZone
, defpol
, defpolOverrideLocal
, maxTTL
, tt
, maxReceivedBytes
, localAddress
, axfrTimeout
);
371 refresh
= sr
-> d_st
. refresh
;
373 newZone
-> setSerial ( sr
-> d_st
. serial
);
374 setRPZZoneNewState ( polName
, sr
-> d_st
. serial
, newZone
-> size (), true );
376 g_luaconfs
. modify ([ zoneIdx
, & newZone
]( LuaConfigItems
& lci
) {
377 lci
. dfe
. setZone ( zoneIdx
, newZone
);
380 if (! dumpZoneFileName
. empty ()) {
381 dumpZoneToDisk ( zoneName
, newZone
, dumpZoneFileName
);
384 /* no need to try another master */
387 catch ( const std :: exception
& e
) {
388 g_log
<< Logger :: Warning
<< "Unable to load RPZ zone '" << zoneName
<< "' from '" << master
<< "': '" << e
. what ()<< "'. (Will try again in " <<( refresh
> 0 ? refresh
: 10 )<< " seconds...)" << endl
;
389 incRPZFailedTransfers ( polName
);
391 catch ( const PDNSException
& e
) {
392 g_log
<< Logger :: Warning
<< "Unable to load RPZ zone '" << zoneName
<< "' from '" << master
<< "': '" << e
. reason
<< "'. (Will try again in " <<( refresh
> 0 ? refresh
: 10 )<< " seconds...)" << endl
;
393 incRPZFailedTransfers ( polName
);
406 bool skipRefreshDelay
= isPreloaded
;
412 if ( skipRefreshDelay
) {
413 skipRefreshDelay
= false ;
419 if ( luaconfsLocal
-> generation
!= configGeneration
) {
420 /* the configuration has been reloaded, meaning that a new thread
421 has been started to handle that zone and we are now obsolete.
423 g_log
<< Logger :: Info
<< "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName
<< endl
;
427 vector
< pair
< vector
< DNSRecord
>, vector
< DNSRecord
> > > deltas
;
428 for ( const auto & master
: masters
) {
429 g_log
<< Logger :: Info
<< "Getting IXFR deltas for " << zoneName
<< " from " << master
. toStringWithPort ()<< ", our serial: " << getRR
< SOARecordContent
>( dr
)-> d_st
. serial
<< endl
;
431 ComboAddress
local ( localAddress
);
432 if ( local
== ComboAddress ()) {
433 local
= getQueryLocalAddress ( master
. sin4
. sin_family
, 0 );
437 deltas
= getIXFRDeltas ( master
, zoneName
, dr
, tt
, & local
, maxReceivedBytes
);
439 /* no need to try another master */
441 } catch ( const std :: runtime_error
& e
){
442 g_log
<< Logger :: Warning
<< e
. what ()<< endl
;
443 incRPZFailedTransfers ( polName
);
452 g_log
<< Logger :: Info
<< "Processing " << deltas
. size ()<< " delta" << addS ( deltas
)<< " for RPZ " << zoneName
<< endl
;
454 oldZone
= luaconfsLocal
-> dfe
. getZone ( zoneIdx
);
455 /* we need to make a _full copy_ of the zone we are going to work on */
456 std :: shared_ptr
< DNSFilterEngine :: Zone
> newZone
= std :: make_shared
< DNSFilterEngine :: Zone
>(* oldZone
);
458 int totremove
= 0 , totadd
= 0 ;
459 bool fullUpdate
= false ;
460 for ( const auto & delta
: deltas
) {
461 const auto & remove
= delta
. first
;
462 const auto & add
= delta
. second
;
464 g_log
<< Logger :: Warning
<< "IXFR update is a whole new zone" << endl
;
468 for ( const auto & rr
: remove
) { // should always contain the SOA
469 if ( rr
. d_type
== QType :: NS
)
471 if ( rr
. d_type
== QType :: SOA
) {
472 auto oldsr
= getRR
< SOARecordContent
>( rr
);
473 if ( oldsr
&& oldsr
-> d_st
. serial
== sr
-> d_st
. serial
) {
474 // cout<<"Got good removal of SOA serial "<<oldsr->d_st.serial<<endl;
477 g_log
<< Logger :: Error
<< "GOT WRONG SOA SERIAL REMOVAL, SHOULD TRIGGER WHOLE RELOAD" << endl
;
481 g_log
<<( g_logRPZChanges
? Logger :: Info
: Logger :: Debug
)<< "Had removal of " << rr
. d_name
<< " from RPZ zone " << zoneName
<< endl
;
482 RPZRecordToPolicy ( rr
, newZone
, false , defpol
, defpolOverrideLocal
, maxTTL
);
486 for ( const auto & rr
: add
) { // should always contain the new SOA
487 if ( rr
. d_type
== QType :: NS
)
489 if ( rr
. d_type
== QType :: SOA
) {
490 auto newsr
= getRR
< SOARecordContent
>( rr
);
491 // g_log<<Logger::Info<<"New SOA serial for "<<zoneName<<": "<<newsr->d_st.serial<<endl;
498 g_log
<<( g_logRPZChanges
? Logger :: Info
: Logger :: Debug
)<< "Had addition of " << rr
. d_name
<< " to RPZ zone " << zoneName
<< endl
;
499 RPZRecordToPolicy ( rr
, newZone
, true , defpol
, defpolOverrideLocal
, maxTTL
);
503 g_log
<< Logger :: Info
<< "Had " << totremove
<< " RPZ removal" << addS ( totremove
)<< ", " << totadd
<< " addition" << addS ( totadd
)<< " for " << zoneName
<< " New serial: " << sr
-> d_st
. serial
<< endl
;
504 newZone
-> setSerial ( sr
-> d_st
. serial
);
505 setRPZZoneNewState ( polName
, sr
-> d_st
. serial
, newZone
-> size (), fullUpdate
);
507 /* we need to replace the existing zone with the new one,
508 but we don't want to touch anything else, especially other zones,
509 since they might have been updated by another RPZ IXFR tracker thread.
511 g_luaconfs
. modify ([ zoneIdx
, & newZone
]( LuaConfigItems
& lci
) {
512 lci
. dfe
. setZone ( zoneIdx
, newZone
);
515 if (! dumpZoneFileName
. empty ()) {
516 dumpZoneToDisk ( zoneName
, newZone
, dumpZoneFileName
);