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
);
556 static void generateOptRR(const std::string
& optRData
, string
& res
)
558 const uint8_t name
= 0;
565 dh
.d_type
= htons(QType::OPT
);
566 dh
.d_class
= htons(1280);
567 memcpy(&dh
.d_ttl
, &edns0
, sizeof edns0
);
568 dh
.d_clen
= htons((uint16_t) optRData
.length());
569 res
.assign((const char *) &name
, sizeof name
);
570 res
.append((const char *) &dh
, sizeof dh
);
571 res
.append(optRData
.c_str(), optRData
.length());
574 static void addECSOption(char* packet
, const size_t& packetSize
, uint16_t* len
, const ComboAddress
& remote
, int stamp
)
577 struct dnsheader
* dh
= (struct dnsheader
*) packet
;
581 eso
.source
= Netmask(remote
);
583 ComboAddress
stamped(remote
);
584 *((char*)&stamped
.sin4
.sin_addr
.s_addr
)=stamp
;
585 eso
.source
= Netmask(stamped
);
587 string optRData
=makeEDNSSubnetOptsString(eso
);
589 generateEDNSOption(EDNSOptionCode::ECS
, optRData
, record
);
590 generateOptRR(record
, EDNSRR
);
593 uint16_t arcount
= ntohs(dh
->arcount
);
594 /* does it fit in the existing buffer? */
595 if (packetSize
- *len
> EDNSRR
.size()) {
597 dh
->arcount
= htons(arcount
);
598 memcpy(packet
+ *len
, EDNSRR
.c_str(), EDNSRR
.size());
599 *len
+= EDNSRR
.size();
603 static bool g_rdSelector
;
604 static uint16_t g_pcapDnsPort
;
606 static bool sendPacketFromPR(PcapPacketReader
& pr
, const ComboAddress
& remote
, int stamp
)
608 dnsheader
* dh
=(dnsheader
*)pr
.d_payload
;
610 if((ntohs(pr
.d_udp
->uh_dport
)!=g_pcapDnsPort
&& ntohs(pr
.d_udp
->uh_sport
)!=g_pcapDnsPort
) || dh
->rd
!= g_rdSelector
|| (unsigned int)pr
.d_len
<= sizeof(dnsheader
))
615 // yes, we send out ALWAYS. Even if we don't do anything with it later,
616 if(!dh
->qr
) { // this is to stress out the reference server with all the pain
618 qd
.d_assignedID
= s_idmanager
.getID();
620 dh
->id
=htons(qd
.d_assignedID
);
621 // dh->rd=1; // useful to replay traffic to auths to a recursor
622 uint16_t dlen
= pr
.d_len
;
624 if (stamp
>= 0) addECSOption((char*)pr
.d_payload
, 1500, &dlen
, pr
.getSource(), stamp
);
626 s_socket
->sendTo((const char*)pr
.d_payload
, dlen
, remote
);
630 MOADNSParser
mdp(false, (const char*)pr
.d_payload
, pr
.d_len
);
631 QuestionIdentifier qi
=QuestionIdentifier::create(pr
.getSource(), pr
.getDest(), mdp
);
633 if(!mdp
.d_header
.qr
) {
637 cout
<<"Saw an exact duplicate question in PCAP "<<qi
<< endl
;
639 s_idmanager
.releaseID(qd
.d_assignedID
); // release = puts at back of pool
642 // new question - ID assigned above already
644 gettimeofday(&qd
.d_resentTime
,0);
649 qids_t::const_iterator iter
=qids
.find(qi
);
650 if(iter
!= qids
.end()) {
651 QuestionData eqd
=*iter
;
652 eqd
.d_origAnswers
=mdp
.d_answers
;
653 eqd
.d_origRcode
=mdp
.d_header
.rcode
;
656 s_norecursionavailable
++;
657 eqd
.d_norecursionavailable
=true;
659 qids
.replace(iter
, eqd
);
661 if(eqd
.d_newRcode
!=-1) {
662 measureResultAndClean(iter
);
670 cout
<<"Unmatched original answer "<<qi
<<endl
;
674 catch(MOADNSException
&e
)
677 cerr
<<"Error parsing packet: "<<e
.what()<<endl
;
678 s_idmanager
.releaseID(qd
.d_assignedID
); // not added to qids for cleanup
681 catch(std::exception
&e
)
684 cerr
<<"Error parsing packet: "<<e
.what()<<endl
;
686 s_idmanager
.releaseID(qd
.d_assignedID
); // not added to qids for cleanup
693 void usage(po::options_description
&desc
) {
694 cerr
<< "Usage: dnsreplay [OPTIONS] FILENAME [IP-ADDRESS] [PORT]"<<endl
;
695 cerr
<< desc
<< "\n";
698 int main(int argc
, char** argv
)
701 po::options_description
desc("Allowed options");
703 ("help,h", "produce help message")
704 ("version", "show version number")
705 ("packet-limit", po::value
<uint32_t>()->default_value(0), "stop after this many packets")
706 ("pcap-dns-port", po::value
<uint16_t>()->default_value(53), "look at packets from or to this port in the PCAP (defaults to 53)")
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>();
753 g_pcapDnsPort
= g_vm
["pcap-dns-port"].as
<uint16_t>();
755 g_quiet
= g_vm
["quiet"].as
<bool>();
757 signal(SIGINT
, pleaseQuitHandler
);
758 float speedup
=g_vm
["speedup"].as
<float>();
759 g_timeoutMsec
=g_vm
["timeout-msec"].as
<uint32_t>();
761 PcapPacketReader
pr(g_vm
["pcap-source"].as
<string
>());
762 s_socket
= new Socket(AF_INET
, SOCK_DGRAM
);
764 s_socket
->setNonBlocking();
766 if(g_vm
.count("source-ip") && !g_vm
["source-ip"].as
<string
>().empty())
767 s_socket
->bind(ComboAddress(g_vm
["source-ip"].as
<string
>(), g_vm
["source-port"].as
<uint16_t>()));
769 setSocketReceiveBuffer(s_socket
->getHandle(), 2000000);
770 setSocketSendBuffer(s_socket
->getHandle(), 2000000);
772 ComboAddress
remote(g_vm
["target-ip"].as
<string
>(),
773 g_vm
["target-port"].as
<uint16_t>());
776 if(g_vm
.count("ecs-stamp") && g_vm
.count("ecs-mask"))
777 stamp
=g_vm
["ecs-mask"].as
<uint16_t>();
779 cerr
<<"Replaying packets to: '"<<g_vm
["target-ip"].as
<string
>()<<"', port "<<g_vm
["target-port"].as
<uint16_t>()<<endl
;
782 struct timeval mental_time
;
783 mental_time
.tv_sec
=0; mental_time
.tv_usec
=0;
785 if(!pr
.getUDPPacket()) // we do this here so we error out more cleanly on no packets
787 unsigned int count
=0;
791 cerr
<<"Interrupted from terminal"<<endl
;
797 struct timeval packet_ts
;
798 packet_ts
.tv_sec
= 0;
799 packet_ts
.tv_usec
= 0;
801 while(packet_ts
< mental_time
) {
802 if(!first
&& !pr
.getUDPPacket()) // otherwise we miss the first packet
806 packet_ts
.tv_sec
= pr
.d_pheader
.ts
.tv_sec
;
807 packet_ts
.tv_usec
= pr
.d_pheader
.ts
.tv_usec
;
809 if(sendPacketFromPR(pr
, remote
, stamp
))
812 if(packetLimit
&& count
>= packetLimit
)
815 mental_time
=packet_ts
;
816 struct timeval then
, now
;
817 gettimeofday(&then
,0);
819 receiveFromReference();
821 gettimeofday(&now
, 0);
823 mental_time
= mental_time
+ speedup
* (now
-then
);
827 receiveFromReference();
831 catch(std::exception
& e
)
833 cerr
<<"Fatal: "<<e
.what()<<endl
;