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.
23 Replay all recursion-desired DNS questions to a specified IP address.
25 Track all outgoing questions, remap id to one of ours.
26 Also track all recorded answers, and map them to that same id, the 'expectation'.
28 When we see a question, parse it, give it a QuestionIdentifier, and and an id from the free-id list.
30 When we see an answer in the tcpdump, parse it, make QI, and add it to the original QI
33 When we see an answer from the socket, use the id to match it up to the original QI
36 There is one central object, which has (when complete)
42 What to do with timeouts. We keep around at most 65536 outstanding answers.
52 } while(time_of_last_packet_sent < mental_clock)
53 mental_clock=time_of_last_packet_sent;
55 wait for a response packet for 0.1 seconds
56 note how much time has passed
57 mental_clock+=time_passed;
70 #include <boost/program_options.hpp>
71 #include "dnsrecords.hh"
72 #include "ednssubnet.hh"
73 #include "ednsoptions.hh"
74 // this is needed because boost multi_index also uses 'L', as do we (which is sad enough)
80 #include <boost/format.hpp>
81 #include <boost/utility.hpp>
82 #include <boost/multi_index_container.hpp>
83 #include <boost/multi_index/ordered_index.hpp>
84 #include <boost/multi_index/key_extractors.hpp>
86 #include "namespaces.hh"
87 using namespace ::boost::multi_index
;
88 #include "namespaces.hh"
94 namespace po
= boost::program_options
;
96 po::variables_map g_vm
;
98 const struct timeval
operator*(float fact
, const struct timeval
& rhs
)
100 // cout<<"In: "<<rhs.tv_sec<<" + "<<rhs.tv_usec<<"\n";
102 if( (2000000000 / fact
) < rhs
.tv_usec
) {
103 double d
=1.0 * rhs
.tv_usec
* fact
;
104 ret
.tv_sec
=fact
* rhs
.tv_sec
;
105 ret
.tv_sec
+=(int) (d
/1000000);
109 ret
.tv_usec
=(unsigned int)(1000000*d
);
112 cout
<<"out complex: "<<ret
.tv_sec
<<" + "<<ret
.tv_usec
<<"\n";
117 ret
.tv_sec
=rhs
.tv_sec
* fact
;
118 ret
.tv_usec
=rhs
.tv_usec
* fact
;
121 // cout<<"out simple: "<<ret.tv_sec<<" + "<<ret.tv_usec<<"\n";
126 void pleaseQuitHandler(int)
131 class DNSIDManager
: public boost::noncopyable
136 for(unsigned int i
=0; i
< 65536; ++i
)
137 d_available
.push_back(i
);
143 if(!d_available
.empty()) {
144 ret
=d_available
.front();
148 throw runtime_error("out of ids!"); // XXX FIXME
153 uint16_t ret
=peakID();
154 d_available
.pop_front();
158 void releaseID(uint16_t id
)
160 d_available
.push_back(id
);
164 deque
<uint16_t> d_available
;
169 void setSocketBuffer(int fd
, int optname
, uint32_t size
)
172 socklen_t len
=sizeof(psize
);
174 if(!getsockopt(fd
, SOL_SOCKET
, optname
, (char*)&psize
, &len
) && psize
> size
) {
175 cerr
<<"Not decreasing socket buffer size from "<<psize
<<" to "<<size
<<endl
;
179 if (setsockopt(fd
, SOL_SOCKET
, optname
, (char*)&size
, sizeof(size
)) < 0 )
180 cerr
<<"Warning: unable to raise socket buffer size to "<<size
<<": "<<strerror(errno
)<<endl
;
183 static void setSocketReceiveBuffer(int fd
, uint32_t size
)
185 setSocketBuffer(fd
, SO_RCVBUF
, size
);
188 static void setSocketSendBuffer(int fd
, uint32_t size
)
190 setSocketBuffer(fd
, SO_SNDBUF
, size
);
194 struct AssignedIDTag
{};
195 struct QuestionTag
{};
199 QuestionData() : d_assignedID(-1), d_origRcode(-1), d_newRcode(-1), d_norecursionavailable(false), d_origlate(false), d_newlate(false)
202 QuestionIdentifier d_qi
;
204 MOADNSParser::answers_t d_origAnswers
, d_newAnswers
;
205 int d_origRcode
, d_newRcode
;
206 struct timeval d_resentTime
;
207 bool d_norecursionavailable
;
208 bool d_origlate
, d_newlate
;
211 typedef multi_index_container
<
214 ordered_unique
<tag
<QuestionTag
>, BOOST_MULTI_INDEX_MEMBER(QuestionData
, QuestionIdentifier
, d_qi
) > ,
215 ordered_unique
<tag
<AssignedIDTag
>, BOOST_MULTI_INDEX_MEMBER(QuestionData
, int, d_assignedID
) >
222 unsigned int s_questions
, s_origanswers
, s_weanswers
, s_wetimedout
, s_perfect
, s_mostly
, s_origtimedout
;
223 unsigned int s_wenever
, s_orignever
;
224 unsigned int s_webetter
, s_origbetter
, s_norecursionavailable
;
225 unsigned int s_weunmatched
, s_origunmatched
;
226 unsigned int s_wednserrors
, s_origdnserrors
, s_duplicates
;
230 void WeOrigSlowQueriesDelta(int& weOutstanding
, int& origOutstanding
, int& weSlow
, int& origSlow
)
233 gettimeofday(&now
, 0);
235 weOutstanding
=origOutstanding
=weSlow
=origSlow
=0;
237 for(qids_t::iterator i
=qids
.begin(); i
!=qids
.end(); ++i
) {
238 double dt
=DiffTime(i
->d_resentTime
, now
);
240 if(i
->d_newRcode
== -1)
242 if(i
->d_origRcode
== -1)
246 if(i
->d_newRcode
== -1) {
256 if(i
->d_origRcode
== -1) {
270 void compactAnswerSet(MOADNSParser::answers_t orig
, set
<DNSRecord
>& compacted
)
272 for(MOADNSParser::answers_t::const_iterator i
=orig
.begin(); i
!= orig
.end(); ++i
)
273 if(i
->first
.d_place
==DNSResourceRecord::ANSWER
)
274 compacted
.insert(i
->first
);
277 bool isRcodeOk(int rcode
)
279 return rcode
==0 || rcode
==3;
282 set
<pair
<DNSName
,uint16_t> > s_origbetterset
;
284 bool isRootReferral(const MOADNSParser::answers_t
& answers
)
290 for(MOADNSParser::answers_t::const_iterator iter
= answers
.begin(); iter
!= answers
.end(); ++iter
) {
291 // cerr<<(int)iter->first.d_place<<", "<<iter->first.d_name<<" "<<iter->first.d_type<<", # "<<answers.size()<<endl;
292 if(iter
->first
.d_place
!=2)
294 if(!iter
->first
.d_name
.isRoot() || iter
->first
.d_type
!=QType::NS
)
300 vector
<uint32_t> flightTimes
;
301 void accountFlightTime(qids_t::const_iterator iter
)
303 if(flightTimes
.empty())
304 flightTimes
.resize(2050);
307 gettimeofday(&now
, 0);
308 unsigned int mdiff
= 1000*DiffTime(iter
->d_resentTime
, now
);
309 if(mdiff
> flightTimes
.size()-2)
310 mdiff
= flightTimes
.size()-1;
312 flightTimes
[mdiff
]++;
315 uint64_t countLessThan(unsigned int msec
)
318 for(unsigned int i
= 0 ; i
< msec
&& i
< flightTimes
.size() ; ++i
) {
319 ret
+= flightTimes
[i
];
324 void emitFlightTimes()
326 uint64_t totals
= countLessThan(flightTimes
.size());
327 unsigned int limits
[]={1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 200, 500, 1000, (unsigned int) flightTimes
.size()};
329 cout
.setf(std::ios::fixed
);
331 for(unsigned int i
=0 ; i
< sizeof(limits
)/sizeof(limits
[0]); ++i
) {
332 if(limits
[i
]!=flightTimes
.size())
333 cout
<<"Within "<<limits
[i
]<<" msec: ";
335 cout
<<"Beyond "<<limits
[i
]-2<<" msec: ";
336 uint64_t here
= countLessThan(limits
[i
]);
337 cout
<<100.0*here
/totals
<<"% ("<<100.0*(here
-sofar
)/totals
<<"%)"<<endl
;
343 void measureResultAndClean(qids_t::const_iterator iter
)
345 const QuestionData
& qd
=*iter
;
346 accountFlightTime(iter
);
348 set
<DNSRecord
> canonicOrig
, canonicNew
;
349 compactAnswerSet(qd
.d_origAnswers
, canonicOrig
);
350 compactAnswerSet(qd
.d_newAnswers
, canonicNew
);
353 cout
<<qd
.d_qi
<<", orig rcode: "<<qd
.d_origRcode
<<", ours: "<<qd
.d_newRcode
;
354 cout
<<", "<<canonicOrig
.size()<< " vs " << canonicNew
.size()<<", perfect: ";
357 if(canonicOrig
==canonicNew
) {
366 if(qd
.d_norecursionavailable
)
368 cout
<<"\t* original nameserver did not provide recursion for this question *"<<endl
;
369 if(qd
.d_origRcode
== qd
.d_newRcode
) {
371 cout
<<"\t* mostly correct *"<<endl
;
375 if(!isRcodeOk(qd
.d_origRcode
) && isRcodeOk(qd
.d_newRcode
)) {
377 cout
<<"\t* we better *"<<endl
;
380 if(isRcodeOk(qd
.d_origRcode
) && !isRcodeOk(qd
.d_newRcode
) && !isRootReferral(qd
.d_origAnswers
)) {
382 cout
<<"\t* orig better *"<<endl
;
385 if(s_origbetterset
.insert(make_pair(qd
.d_qi
.d_qname
, qd
.d_qi
.d_qtype
)).second
) {
386 cout
<<"orig better: " << qd
.d_qi
.d_qname
<<" "<< qd
.d_qi
.d_qtype
<<endl
;
391 cout
<<"orig: rcode="<<qd
.d_origRcode
<<"\n";
392 for(set
<DNSRecord
>::const_iterator i
=canonicOrig
.begin(); i
!=canonicOrig
.end(); ++i
)
393 cout
<<"\t"<<i
->d_name
<<"\t"<<DNSRecordContent::NumberToType(i
->d_type
)<<"\t'" << (i
->d_content
? i
->d_content
->getZoneRepresentation() : "") <<"'\n";
394 cout
<<"new: rcode="<<qd
.d_newRcode
<<"\n";
395 for(set
<DNSRecord
>::const_iterator i
=canonicNew
.begin(); i
!=canonicNew
.end(); ++i
)
396 cout
<<"\t"<<i
->d_name
<<"\t"<<DNSRecordContent::NumberToType(i
->d_type
)<<"\t'" << (i
->d_content
? i
->d_content
->getZoneRepresentation() : "") <<"'\n";
403 int releaseID
=qd
.d_assignedID
;
404 qids
.erase(iter
); // qd invalid now
405 s_idmanager
.releaseID(releaseID
);
411 void receiveFromReference()
416 int res
=waitForData(s_socket
->getHandle(), g_timeoutMsec
/1000, 1000*(g_timeoutMsec
%1000));
418 if(res
< 0 || res
==0)
421 while(s_socket
->recvFromAsync(packet
, remote
)) {
424 MOADNSParser
mdp(false, packet
.c_str(), packet
.length());
425 if(!mdp
.d_header
.qr
) {
426 cout
<<"Received a question from our reference nameserver!"<<endl
;
430 typedef qids_t::index
<AssignedIDTag
>::type qids_by_id_index_t
;
431 qids_by_id_index_t
& idindex
=qids
.get
<AssignedIDTag
>();
432 qids_by_id_index_t::const_iterator found
=idindex
.find(ntohs(mdp
.d_header
.id
));
433 if(found
== idindex
.end()) {
435 cout
<<"Received an answer ("<<mdp
.d_qname
<<") from reference nameserver with id "<<mdp
.d_header
.id
<<" which we can't match to a question!"<<endl
;
440 QuestionData qd
=*found
; // we have to make a copy because we reinsert below
441 qd
.d_newAnswers
=mdp
.d_answers
;
442 qd
.d_newRcode
=mdp
.d_header
.rcode
;
443 idindex
.replace(found
, qd
);
444 if(qd
.d_origRcode
!=-1) {
445 qids_t::const_iterator iter
= qids
.project
<0>(found
);
446 measureResultAndClean(iter
);
449 catch(MOADNSException
&e
)
453 catch(std::out_of_range
&e
)
457 catch(std::exception
& e
)
463 catch(std::exception
& e
)
465 cerr
<<"Receiver function died: "<<e
.what()<<endl
;
470 cerr
<<"Receiver function died with unknown exception"<<endl
;
477 gettimeofday(&now
, 0);
479 for(qids_t::iterator i
=qids
.begin(); i
!=qids
.end(); ) {
480 if(DiffTime(i
->d_resentTime
, now
) < 10)
483 s_idmanager
.releaseID(i
->d_assignedID
);
484 if(i
->d_newRcode
==-1) {
487 if(i
->d_origRcode
==-1) {
496 void printStats(uint64_t origWaitingFor
=0, uint64_t weWaitingFor
=0)
498 format
headerfmt ("%|9t|Questions - Pend. - Drop = Answers = (On time + Late) = (Err + Ok)\n");
499 format
datafmt("%s%|9t|%d %|21t|%d %|29t|%d %|36t|%d %|47t|%d %|57t|%d %|66t|%d %|72t|%d\n");
502 cerr
<<(datafmt
% "Orig" % s_questions
% origWaitingFor
% s_orignever
% s_origanswers
% 0 % s_origtimedout
% 0 % 0);
503 cerr
<<(datafmt
% "Refer." % s_questions
% weWaitingFor
% s_wenever
% s_weanswers
% 0 % s_wetimedout
% 0 % 0);
505 cerr
<<weWaitingFor
<<" queries that could still come in on time, "<<qids
.size()<<" outstanding"<<endl
;
507 cerr
<<"we late: "<<s_wetimedout
<<", orig late: "<< s_origtimedout
<<", "<<s_questions
<<" questions sent, "<<s_origanswers
508 <<" original answers, "<<s_perfect
<<" perfect, "<<s_mostly
<<" mostly correct"<<", "<<s_webetter
<<" we better, "<<s_origbetter
<<" orig better ("<<s_origbetterset
.size()<<" diff)"<<endl
;
509 cerr
<<"we never: "<<s_wenever
<<", orig never: "<<s_orignever
<<endl
;
510 cerr
<<"original questions from IP addresses for which recursion was not available: "<<s_norecursionavailable
<<endl
;
511 cerr
<<"Unmatched from us: "<<s_weunmatched
<<", unmatched from original: "<<s_origunmatched
<< " ( - decoding err: "<<s_origunmatched
-s_origdnserrors
<<")"<<endl
;
512 cerr
<<"DNS decoding errors from us: "<<s_wednserrors
<<", from original: "<<s_origdnserrors
<<", exact duplicates from client: "<<s_duplicates
<<endl
<<endl
;
521 gettimeofday(&now
, 0);
523 if(DiffTime(last
, now
) < 0.3)
526 int weWaitingFor
, origWaitingFor
, weSlow
, origSlow
;
527 WeOrigSlowQueriesDelta(weWaitingFor
, origWaitingFor
, weSlow
, origSlow
);
530 if( weWaitingFor
> 1000) {
531 cerr
<<"Too many questions ("<<weWaitingFor
<<") outstanding, throttling"<<endl
;
535 else if(weWaitingFor
< 750) {
536 cerr
<<"Unthrottling ("<<weWaitingFor
<<")"<<endl
;
540 if(DiffTime(last
, now
) < 2)
546 Questions - Pend. - Drop = Answers = (On time + Late) = (Err + Ok)
547 Orig 9 21 29 36 47 57 66 72
552 printStats(origWaitingFor
, weWaitingFor
);
559 static void generateOptRR(const std::string
& optRData
, string
& res
)
561 const uint8_t name
= 0;
568 dh
.d_type
= htons(QType::OPT
);
569 dh
.d_class
= htons(1280);
570 memcpy(&dh
.d_ttl
, &edns0
, sizeof edns0
);
571 dh
.d_clen
= htons((uint16_t) optRData
.length());
572 res
.assign((const char *) &name
, sizeof name
);
573 res
.append((const char *) &dh
, sizeof dh
);
574 res
.append(optRData
.c_str(), optRData
.length());
577 static void addECSOption(char* packet
, const size_t& packetSize
, uint16_t* len
, const ComboAddress
& remote
, int stamp
)
580 struct dnsheader
* dh
= (struct dnsheader
*) packet
;
584 eso
.source
= Netmask(remote
);
586 ComboAddress
stamped(remote
);
587 *((char*)&stamped
.sin4
.sin_addr
.s_addr
)=stamp
;
588 eso
.source
= Netmask(stamped
);
590 string optRData
=makeEDNSSubnetOptsString(eso
);
592 generateEDNSOption(EDNSOptionCode::ECS
, optRData
, record
);
593 generateOptRR(record
, EDNSRR
);
596 uint16_t arcount
= ntohs(dh
->arcount
);
597 /* does it fit in the existing buffer? */
598 if (packetSize
- *len
> EDNSRR
.size()) {
600 dh
->arcount
= htons(arcount
);
601 memcpy(packet
+ *len
, EDNSRR
.c_str(), EDNSRR
.size());
602 *len
+= EDNSRR
.size();
607 bool sendPacketFromPR(PcapPacketReader
& pr
, const ComboAddress
& remote
, int stamp
)
609 dnsheader
* dh
=(dnsheader
*)pr
.d_payload
;
611 if((ntohs(pr
.d_udp
->uh_dport
)!=53 && ntohs(pr
.d_udp
->uh_sport
)!=53) || dh
->rd
!= g_rdSelector
|| (unsigned int)pr
.d_len
<= sizeof(dnsheader
))
616 // yes, we send out ALWAYS. Even if we don't do anything with it later,
617 if(!dh
->qr
) { // this is to stress out the reference server with all the pain
619 qd
.d_assignedID
= s_idmanager
.getID();
621 dh
->id
=htons(qd
.d_assignedID
);
622 // dh->rd=1; // useful to replay traffic to auths to a recursor
623 uint16_t dlen
= pr
.d_len
;
625 if (stamp
>= 0) addECSOption((char*)pr
.d_payload
, 1500, &dlen
, pr
.getSource(), stamp
);
627 s_socket
->sendTo((const char*)pr
.d_payload
, dlen
, remote
);
631 MOADNSParser
mdp(false, (const char*)pr
.d_payload
, pr
.d_len
);
632 QuestionIdentifier qi
=QuestionIdentifier::create(pr
.getSource(), pr
.getDest(), mdp
);
634 if(!mdp
.d_header
.qr
) {
638 cout
<<"Saw an exact duplicate question in PCAP "<<qi
<< endl
;
640 s_idmanager
.releaseID(qd
.d_assignedID
); // release = puts at back of pool
643 // new question - ID assigned above already
645 gettimeofday(&qd
.d_resentTime
,0);
650 qids_t::const_iterator iter
=qids
.find(qi
);
651 if(iter
!= qids
.end()) {
652 QuestionData qd
=*iter
;
653 qd
.d_origAnswers
=mdp
.d_answers
;
654 qd
.d_origRcode
=mdp
.d_header
.rcode
;
657 s_norecursionavailable
++;
658 qd
.d_norecursionavailable
=true;
660 qids
.replace(iter
, qd
);
662 if(qd
.d_newRcode
!=-1) {
663 measureResultAndClean(iter
);
671 cout
<<"Unmatched original answer "<<qi
<<endl
;
675 catch(MOADNSException
&e
)
678 cerr
<<"Error parsing packet: "<<e
.what()<<endl
;
679 s_idmanager
.releaseID(qd
.d_assignedID
); // not added to qids for cleanup
682 catch(std::exception
&e
)
685 cerr
<<"Error parsing packet: "<<e
.what()<<endl
;
687 s_idmanager
.releaseID(qd
.d_assignedID
); // not added to qids for cleanup
694 void usage(po::options_description
&desc
) {
695 cerr
<< "Usage: dnsreplay [OPTIONS] FILENAME [IP-ADDRESS] [PORT]"<<endl
;
696 cerr
<< desc
<< "\n";
699 int main(int argc
, char** argv
)
702 po::options_description
desc("Allowed options");
704 ("help,h", "produce help message")
705 ("version", "show version number")
706 ("packet-limit", po::value
<uint32_t>()->default_value(0), "stop after this many packets")
707 ("quiet", po::value
<bool>()->default_value(true), "don't be too noisy")
708 ("recursive", po::value
<bool>()->default_value(true), "look at recursion desired packets, or not (defaults true)")
709 ("speedup", po::value
<float>()->default_value(1), "replay at this speedup")
710 ("timeout-msec", po::value
<uint32_t>()->default_value(500), "wait at least this many milliseconds for a reply")
711 ("ecs-stamp", "Add original IP address to ECS in replay")
712 ("ecs-mask", po::value
<uint16_t>(), "Replace first octet of src IP address with this value in ECS")
713 ("source-ip", po::value
<string
>()->default_value(""), "IP to send the replayed packet from")
714 ("source-port", po::value
<uint16_t>()->default_value(0), "Port to send the replayed packet from");
716 po::options_description alloptions
;
717 po::options_description
hidden("hidden options");
719 ("pcap-source", po::value
<string
>(), "PCAP source file")
720 ("target-ip", po::value
<string
>()->default_value("127.0.0.1"), "target-ip")
721 ("target-port", po::value
<uint16_t>()->default_value(5300), "target port");
723 alloptions
.add(desc
).add(hidden
);
724 po::positional_options_description p
;
725 p
.add("pcap-source", 1);
726 p
.add("target-ip", 1);
727 p
.add("target-port", 1);
729 po::store(po::command_line_parser(argc
, argv
).options(alloptions
).positional(p
).run(), g_vm
);
734 if (g_vm
.count("help")) {
739 if (g_vm
.count("version")) {
740 cerr
<<"dnsreplay "<<VERSION
<<endl
;
744 if(!g_vm
.count("pcap-source")) {
745 cerr
<<"Fatal, need to specify at least a PCAP source file"<<endl
;
750 uint32_t packetLimit
= g_vm
["packet-limit"].as
<uint32_t>();
752 g_rdSelector
= g_vm
["recursive"].as
<bool>();
754 g_quiet
= g_vm
["quiet"].as
<bool>();
756 signal(SIGINT
, pleaseQuitHandler
);
757 float speedup
=g_vm
["speedup"].as
<float>();
758 g_timeoutMsec
=g_vm
["timeout-msec"].as
<uint32_t>();
760 PcapPacketReader
pr(g_vm
["pcap-source"].as
<string
>());
761 s_socket
= new Socket(AF_INET
, SOCK_DGRAM
);
763 s_socket
->setNonBlocking();
765 if(g_vm
.count("source-ip") && !g_vm
["source-ip"].as
<string
>().empty())
766 s_socket
->bind(ComboAddress(g_vm
["source-ip"].as
<string
>(), g_vm
["source-port"].as
<uint16_t>()));
768 setSocketReceiveBuffer(s_socket
->getHandle(), 2000000);
769 setSocketSendBuffer(s_socket
->getHandle(), 2000000);
771 ComboAddress
remote(g_vm
["target-ip"].as
<string
>(),
772 g_vm
["target-port"].as
<uint16_t>());
775 if(g_vm
.count("ecs-stamp") && g_vm
.count("ecs-mask"))
776 stamp
=g_vm
["ecs-mask"].as
<uint16_t>();
778 cerr
<<"Replaying packets to: '"<<g_vm
["target-ip"].as
<string
>()<<"', port "<<g_vm
["target-port"].as
<uint16_t>()<<endl
;
781 struct timeval mental_time
;
782 mental_time
.tv_sec
=0; mental_time
.tv_usec
=0;
784 if(!pr
.getUDPPacket()) // we do this here so we error out more cleanly on no packets
786 unsigned int count
=0;
790 cerr
<<"Interrupted from terminal"<<endl
;
796 struct timeval packet_ts
;
797 packet_ts
.tv_sec
= 0;
798 packet_ts
.tv_usec
= 0;
800 while(packet_ts
< mental_time
) {
801 if(!first
&& !pr
.getUDPPacket()) // otherwise we miss the first packet
805 packet_ts
.tv_sec
= pr
.d_pheader
.ts
.tv_sec
;
806 packet_ts
.tv_usec
= pr
.d_pheader
.ts
.tv_usec
;
808 if(sendPacketFromPR(pr
, remote
, stamp
))
811 if(packetLimit
&& count
>= packetLimit
)
814 mental_time
=packet_ts
;
815 struct timeval then
, now
;
816 gettimeofday(&then
,0);
818 receiveFromReference();
820 gettimeofday(&now
, 0);
822 mental_time
= mental_time
+ speedup
* (now
-then
);
826 receiveFromReference();
830 catch(std::exception
& e
)
832 cerr
<<"Fatal: "<<e
.what()<<endl
;