10 # Python2/3 compatibility hacks
11 if sys
.version_info
[0] == 2:
12 from Queue
import Queue
15 from queue
import Queue
16 range = range # allow re-export of the builtin name
18 from recursortests
import RecursorTest
20 protobufQueue
= Queue()
21 protobufServerPort
= 4243
23 def ProtobufConnectionHandler(queue
, conn
):
29 (datalen
,) = struct
.unpack("!H", data
)
30 data
= conn
.recv(datalen
)
34 queue
.put(data
, True, timeout
=2.0)
38 def ProtobufListener(port
):
40 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
41 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEPORT
, 1)
43 sock
.bind(("127.0.0.1", port
))
44 except socket
.error
as e
:
45 print("Error binding in the protobuf listener: %s" % str(e
))
51 (conn
, _
) = sock
.accept()
52 thread
= threading
.Thread(name
='Connection Handler',
53 target
=ProtobufConnectionHandler
,
54 args
=[protobufQueue
, conn
])
55 thread
.setDaemon(True)
58 except socket
.error
as e
:
59 print('Error in protobuf socket: %s' % str(e
))
64 protobufListener
= threading
.Thread(name
='Protobuf Listener', target
=ProtobufListener
, args
=[protobufServerPort
])
65 protobufListener
.setDaemon(True)
66 protobufListener
.start()
68 class TestRecursorProtobuf(RecursorTest
):
70 global protobufServerPort
71 _lua_config_file
= """
72 protobufServer("127.0.0.1:%d")
73 """ % (protobufServerPort
)
76 def getFirstProtobufMessage(self
, retries
=1, waitTime
=1):
80 while protobufQueue
.empty
:
87 self
.assertFalse(protobufQueue
.empty())
88 data
= protobufQueue
.get(False)
90 msg
= dnsmessage_pb2
.PBDNSMessage()
91 msg
.ParseFromString(data
)
94 def checkNoRemainingMessage(self
):
96 self
.assertTrue(protobufQueue
.empty())
98 def checkProtobufBase(self
, msg
, protocol
, query
, initiator
, normalQueryResponse
=True, expectedECS
=None):
100 self
.assertTrue(msg
.HasField('timeSec'))
101 self
.assertTrue(msg
.HasField('socketFamily'))
102 self
.assertEquals(msg
.socketFamily
, dnsmessage_pb2
.PBDNSMessage
.INET
)
103 self
.assertTrue(msg
.HasField('from'))
104 fromvalue
= getattr(msg
, 'from')
105 self
.assertEquals(socket
.inet_ntop(socket
.AF_INET
, fromvalue
), initiator
)
106 self
.assertTrue(msg
.HasField('socketProtocol'))
107 self
.assertEquals(msg
.socketProtocol
, protocol
)
108 self
.assertTrue(msg
.HasField('messageId'))
109 self
.assertTrue(msg
.HasField('serverIdentity'))
110 self
.assertTrue(msg
.HasField('id'))
111 self
.assertEquals(msg
.id, query
.id)
112 self
.assertTrue(msg
.HasField('inBytes'))
113 if normalQueryResponse
:
114 # compare inBytes with length of query/response
115 self
.assertEquals(msg
.inBytes
, len(query
.to_wire()))
116 if expectedECS
is not None:
117 self
.assertTrue(msg
.HasField('originalRequestorSubnet'))
119 self
.assertEquals(len(msg
.originalRequestorSubnet
), 4)
120 self
.assertEquals(socket
.inet_ntop(socket
.AF_INET
, msg
.originalRequestorSubnet
), '127.0.0.1')
122 def checkOutgoingProtobufBase(self
, msg
, protocol
, query
, initiator
):
124 self
.assertTrue(msg
.HasField('timeSec'))
125 self
.assertTrue(msg
.HasField('socketFamily'))
126 self
.assertEquals(msg
.socketFamily
, dnsmessage_pb2
.PBDNSMessage
.INET
)
127 self
.assertTrue(msg
.HasField('socketProtocol'))
128 self
.assertEquals(msg
.socketProtocol
, protocol
)
129 self
.assertTrue(msg
.HasField('messageId'))
130 self
.assertTrue(msg
.HasField('serverIdentity'))
131 self
.assertTrue(msg
.HasField('id'))
132 self
.assertNotEquals(msg
.id, query
.id)
133 self
.assertTrue(msg
.HasField('inBytes'))
134 # compare inBytes with length of query/response
135 self
.assertEquals(msg
.inBytes
, len(query
.to_wire()))
137 def checkProtobufQuery(self
, msg
, protocol
, query
, qclass
, qtype
, qname
, initiator
='127.0.0.1'):
138 self
.assertEquals(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSQueryType
)
139 self
.checkProtobufBase(msg
, protocol
, query
, initiator
)
140 # dnsdist doesn't fill the responder field for responses
141 # because it doesn't keep the information around.
142 self
.assertTrue(msg
.HasField('to'))
143 self
.assertEquals(socket
.inet_ntop(socket
.AF_INET
, msg
.to
), '127.0.0.1')
144 self
.assertTrue(msg
.HasField('question'))
145 self
.assertTrue(msg
.question
.HasField('qClass'))
146 self
.assertEquals(msg
.question
.qClass
, qclass
)
147 self
.assertTrue(msg
.question
.HasField('qType'))
148 self
.assertEquals(msg
.question
.qClass
, qtype
)
149 self
.assertTrue(msg
.question
.HasField('qName'))
150 self
.assertEquals(msg
.question
.qName
, qname
)
152 def checkProtobufResponse(self
, msg
, protocol
, response
, initiator
='127.0.0.1'):
153 self
.assertEquals(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSResponseType
)
154 self
.checkProtobufBase(msg
, protocol
, response
, initiator
)
155 self
.assertTrue(msg
.HasField('response'))
156 self
.assertTrue(msg
.response
.HasField('queryTimeSec'))
158 def checkProtobufResponseRecord(self
, record
, rclass
, rtype
, rname
, rttl
):
159 self
.assertTrue(record
.HasField('class'))
160 self
.assertEquals(getattr(record
, 'class'), rclass
)
161 self
.assertTrue(record
.HasField('type'))
162 self
.assertEquals(record
.type, rtype
)
163 self
.assertTrue(record
.HasField('name'))
164 self
.assertEquals(record
.name
, rname
)
165 self
.assertTrue(record
.HasField('ttl'))
166 self
.assertEquals(record
.ttl
, rttl
)
167 self
.assertTrue(record
.HasField('rdata'))
169 def checkProtobufPolicy(self
, msg
, policyType
, reason
):
170 self
.assertEquals(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSResponseType
)
171 self
.assertTrue(msg
.response
.HasField('appliedPolicyType'))
172 self
.assertTrue(msg
.response
.HasField('appliedPolicy'))
173 self
.assertEquals(msg
.response
.appliedPolicy
, reason
)
174 self
.assertEquals(msg
.response
.appliedPolicyType
, policyType
)
176 def checkProtobufTags(self
, msg
, tags
):
177 self
.assertEquals(len(msg
.response
.tags
), len(tags
))
178 for tag
in msg
.response
.tags
:
179 self
.assertTrue(tag
in tags
)
181 def checkProtobufOutgoingQuery(self
, msg
, protocol
, query
, qclass
, qtype
, qname
, initiator
='127.0.0.1'):
182 self
.assertEquals(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSOutgoingQueryType
)
183 self
.checkOutgoingProtobufBase(msg
, protocol
, query
, initiator
)
184 self
.assertTrue(msg
.HasField('to'))
185 self
.assertTrue(msg
.HasField('question'))
186 self
.assertTrue(msg
.question
.HasField('qClass'))
187 self
.assertEquals(msg
.question
.qClass
, qclass
)
188 self
.assertTrue(msg
.question
.HasField('qType'))
189 self
.assertEquals(msg
.question
.qClass
, qtype
)
190 self
.assertTrue(msg
.question
.HasField('qName'))
191 self
.assertEquals(msg
.question
.qName
, qname
)
193 def checkProtobufIncomingResponse(self
, msg
, protocol
, response
, initiator
='127.0.0.1'):
194 self
.assertEquals(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSIncomingResponseType
)
195 self
.checkOutgoingProtobufBase(msg
, protocol
, response
, initiator
)
196 self
.assertTrue(msg
.HasField('response'))
197 self
.assertTrue(msg
.response
.HasField('queryTimeSec'))
202 global protobufListener
203 global protobufServerPort
204 global ProtobufListener
205 if protobufListener
is None or not protobufListener
.isAlive():
206 protobufListener
= threading
.Thread(name
='Protobuf Listener', target
=ProtobufListener
, args
=[protobufServerPort
])
207 protobufListener
.setDaemon(True)
208 protobufListener
.start()
212 cls
.startResponders()
214 confdir
= os
.path
.join('configs', cls
._confdir
)
215 cls
.createConfigDir(confdir
)
217 cls
.generateRecursorConfig(confdir
)
218 cls
.startRecursor(confdir
, cls
._recursorPort
)
221 # Make sure the queue is empty, in case
222 # a previous test failed
224 while not protobufQueue
.empty():
225 protobufQueue
.get(False)
228 def generateRecursorConfig(cls
, confdir
):
229 authzonepath
= os
.path
.join(confdir
, 'example.zone')
230 with
open(authzonepath
, 'w') as authzone
:
231 authzone
.write("""$ORIGIN example.
233 a 3600 IN A 192.0.2.42
234 tagged 3600 IN A 192.0.2.84
235 query-selected 3600 IN A 192.0.2.84
236 answer-selected 3600 IN A 192.0.2.84
237 """.format(soa
=cls
._SOA
))
238 super(TestRecursorProtobuf
, cls
).generateRecursorConfig(confdir
)
241 def tearDownClass(cls
):
242 cls
.tearDownRecursor()
244 class ProtobufDefaultTest(TestRecursorProtobuf
):
246 This test makes sure that we correctly export queries and response over protobuf.
249 _confdir
= 'ProtobufDefault'
250 _config_template
= """
251 auth-zones=example=configs/%s/example.zone""" % _confdir
255 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
256 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
257 query
.flags |
= dns
.flags
.CD
258 res
= self
.sendUDPQuery(query
)
259 self
.assertRRsetInAnswer(res
, expected
)
261 # check the protobuf messages corresponding to the UDP query and answer
262 msg
= self
.getFirstProtobufMessage()
263 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
265 msg
= self
.getFirstProtobufMessage()
266 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
267 self
.assertEquals(len(msg
.response
.rrs
), 1)
268 rr
= msg
.response
.rrs
[0]
269 # we have max-cache-ttl set to 15
270 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
271 self
.assertEquals(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
272 self
.checkNoRemainingMessage()
274 class OutgoingProtobufDefaultTest(TestRecursorProtobuf
):
276 This test makes sure that we correctly export outgoing queries over protobuf.
277 It must be improved and setup env so we can check for incoming responses, but makes sure for now
278 that the recursor at least connects to the protobuf server.
281 _confdir
= 'OutgoingProtobufDefault'
282 _config_template
= """
283 auth-zones=example=configs/%s/example.zone""" % _confdir
284 _lua_config_file
= """
285 outgoingProtobufServer("127.0.0.1:%d")
286 """ % (protobufServerPort
)
289 name
= 'www.example.org.'
290 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
291 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
292 query
.flags |
= dns
.flags
.RD
293 res
= self
.sendUDPQuery(query
)
295 # check the protobuf messages corresponding to the UDP query and answer
296 msg
= self
.getFirstProtobufMessage()
297 self
.checkProtobufOutgoingQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
298 # # then the response
299 # msg = self.getFirstProtobufMessage()
300 # self.checkProtobufIncomingResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
301 self
.checkNoRemainingMessage()
303 class ProtobufMasksTest(TestRecursorProtobuf
):
305 This test makes sure that we correctly export queries and response over protobuf, respecting the configured initiator masking.
308 _confdir
= 'ProtobufMasks'
309 _config_template
= """
310 auth-zones=example=configs/%s/example.zone""" % _confdir
311 global protobufServerPort
313 _protobufMaskV6
= 128
314 _lua_config_file
= """
315 protobufServer("127.0.0.1:%d")
316 setProtobufMasks(%d, %d)
317 """ % (protobufServerPort
, _protobufMaskV4
, _protobufMaskV6
)
321 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
322 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
323 query
.flags |
= dns
.flags
.CD
324 res
= self
.sendUDPQuery(query
)
325 self
.assertRRsetInAnswer(res
, expected
)
327 # check the protobuf messages corresponding to the UDP query and answer
328 # but first let the protobuf messages the time to get there
329 msg
= self
.getFirstProtobufMessage()
330 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, '112.0.0.0')
332 msg
= self
.getFirstProtobufMessage()
333 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '112.0.0.0')
334 self
.assertEquals(len(msg
.response
.rrs
), 1)
335 rr
= msg
.response
.rrs
[0]
336 # we have max-cache-ttl set to 15
337 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
338 self
.assertEquals(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
339 self
.checkNoRemainingMessage()
341 class ProtobufQueriesOnlyTest(TestRecursorProtobuf
):
343 This test makes sure that we correctly export queries but not responses over protobuf.
346 _confdir
= 'ProtobufQueriesOnly'
347 _config_template
= """
348 auth-zones=example=configs/%s/example.zone""" % _confdir
349 global protobufServerPort
350 _lua_config_file
= """
351 protobufServer("127.0.0.1:%d", { logQueries=true, logResponses=false } )
352 """ % (protobufServerPort
)
356 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
357 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
358 query
.flags |
= dns
.flags
.CD
359 res
= self
.sendUDPQuery(query
)
360 self
.assertRRsetInAnswer(res
, expected
)
362 # check the protobuf message corresponding to the UDP query
363 msg
= self
.getFirstProtobufMessage()
364 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
366 self
.checkNoRemainingMessage()
368 class ProtobufResponsesOnlyTest(TestRecursorProtobuf
):
370 This test makes sure that we correctly export responses but not queries over protobuf.
373 _confdir
= 'ProtobufResponsesOnly'
374 _config_template
= """
375 auth-zones=example=configs/%s/example.zone""" % _confdir
376 global protobufServerPort
377 _lua_config_file
= """
378 protobufServer("127.0.0.1:%d", { logQueries=false, logResponses=true } )
379 """ % (protobufServerPort
)
383 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
384 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
385 query
.flags |
= dns
.flags
.CD
386 res
= self
.sendUDPQuery(query
)
387 self
.assertRRsetInAnswer(res
, expected
)
389 # check the protobuf message corresponding to the UDP response
390 msg
= self
.getFirstProtobufMessage()
391 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
392 self
.assertEquals(len(msg
.response
.rrs
), 1)
393 rr
= msg
.response
.rrs
[0]
394 # we have max-cache-ttl set to 15
395 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
396 self
.assertEquals(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
397 # nothing else in the queue
398 self
.checkNoRemainingMessage()
400 class ProtobufTaggedOnlyTest(TestRecursorProtobuf
):
402 This test makes sure that we correctly export queries and responses but only if they have been tagged.
405 _confdir
= 'ProtobufTaggedOnly'
406 _config_template
= """
407 auth-zones=example=configs/%s/example.zone""" % _confdir
408 global protobufServerPort
409 _lua_config_file
= """
410 protobufServer("127.0.0.1:%d", { logQueries=true, logResponses=true, taggedOnly=true } )
411 """ % (protobufServerPort
)
412 _tags
= ['tag1', 'tag2']
413 _tag_from_gettag
= 'tag-from-gettag'
414 _lua_dns_script_file
= """
415 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
416 if qname:equal('tagged.example.') then
421 function preresolve(dq)
422 if dq.qname:equal('tagged.example.') then
423 dq:addPolicyTag('%s')
424 dq:addPolicyTag('%s')
428 """ % (_tag_from_gettag
, _tags
[0], _tags
[1])
432 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
433 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
434 query
.flags |
= dns
.flags
.CD
435 res
= self
.sendUDPQuery(query
)
436 self
.assertRRsetInAnswer(res
, expected
)
438 # check the protobuf message corresponding to the UDP response
439 # the first query and answer are not tagged, so there is nothing in the queue
441 self
.checkNoRemainingMessage()
443 def testTagged(self
):
444 name
= 'tagged.example.'
445 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.84')
446 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
447 query
.flags |
= dns
.flags
.CD
448 res
= self
.sendUDPQuery(query
)
449 self
.assertRRsetInAnswer(res
, expected
)
451 # check the protobuf messages corresponding to the UDP query and answer
452 msg
= self
.getFirstProtobufMessage()
453 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
454 self
.checkProtobufTags(msg
, [self
._tag
_from
_gettag
])
456 msg
= self
.getFirstProtobufMessage()
457 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
458 self
.assertEquals(len(msg
.response
.rrs
), 1)
459 rr
= msg
.response
.rrs
[0]
460 # we have max-cache-ttl set to 15
461 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
462 self
.assertEquals(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.84')
463 tags
= [self
._tag
_from
_gettag
] + self
._tags
464 self
.checkProtobufTags(msg
, tags
)
465 self
.checkNoRemainingMessage()
467 class ProtobufSelectedFromLuaTest(TestRecursorProtobuf
):
469 This test makes sure that we correctly export queries and responses but only if they have been selected from Lua.
472 _confdir
= 'ProtobufSelectedFromLua'
473 _config_template
= """
474 auth-zones=example=configs/%s/example.zone""" % _confdir
475 global protobufServerPort
476 _lua_config_file
= """
477 protobufServer("127.0.0.1:%d", { logQueries=false, logResponses=false } )
478 """ % (protobufServerPort
)
479 _lua_dns_script_file
= """
480 local ffi = require("ffi")
483 typedef struct pdns_ffi_param pdns_ffi_param_t;
485 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
486 void pdns_ffi_param_set_log_query(pdns_ffi_param_t* ref, bool logQuery);
489 function gettag_ffi(obj)
490 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
491 if qname == 'query-selected.example' then
492 ffi.C.pdns_ffi_param_set_log_query(obj, true)
497 function preresolve(dq)
498 if dq.qname:equal('answer-selected.example.') then
499 dq.logResponse = true
507 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
508 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
509 query
.flags |
= dns
.flags
.CD
510 res
= self
.sendUDPQuery(query
)
511 self
.assertRRsetInAnswer(res
, expected
)
513 # check the protobuf message corresponding to the UDP response
514 # the first query and answer are not selected, so there is nothing in the queue
515 self
.checkNoRemainingMessage()
517 def testQuerySelected(self
):
518 name
= 'query-selected.example.'
519 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.84')
520 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
521 query
.flags |
= dns
.flags
.CD
522 res
= self
.sendUDPQuery(query
)
523 self
.assertRRsetInAnswer(res
, expected
)
525 # check the protobuf messages corresponding to the UDP query
526 msg
= self
.getFirstProtobufMessage()
527 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
528 # there should be no response
529 self
.checkNoRemainingMessage()
531 def testResponseSelected(self
):
532 name
= 'answer-selected.example.'
533 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.84')
534 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
535 query
.flags |
= dns
.flags
.CD
536 res
= self
.sendUDPQuery(query
)
537 self
.assertRRsetInAnswer(res
, expected
)
539 # check the protobuf messages corresponding to the UDP response
540 msg
= self
.getFirstProtobufMessage()
541 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
542 self
.assertEquals(len(msg
.response
.rrs
), 1)
543 rr
= msg
.response
.rrs
[0]
544 # we have max-cache-ttl set to 15
545 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
546 self
.assertEquals(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.84')
547 self
.checkNoRemainingMessage()