Skip Menu |

This queue is for tickets about the Net-DNS CPAN distribution.

Report information
The Basics
Id: 96069
Status: rejected
Priority: 0/
Queue: Net-DNS

People
Owner: Nobody in particular
Requestors: jfesler [...] gigo.com
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: 0.76
Fixed in: (no value)



Subject: Protocol specific (TCP or UDP) Nameserver objects (patch included)
Under high load, the CORE::select() call eats 50% of the total consumed CPU. The attached patch will I have been able to work around this by modifying Net::DNS::Nameserver to open up just a UDP socket or just a TCP socket. When opening just a single UDP socket, and calling loop_once with 0 timeout, I skip select() entirely and go straight to read. This very much doubled my load capacity for the Net::DNS::Nameserver based application. Calls with either TCP or with multiple LocalAddr values will still require IO::Select. I'm submitting this hoping you'll accept it upstream.
Subject: single-proto-patch.txt
*** /usr/local/lib/perl/5.14.2/Net/DNS/Nameserver.pm 2014-05-23 14:41:23.000000000 -0700 --- lib/Net/DNS/Nameserver.pm 2014-05-29 20:34:25.736867551 -0700 *************** *** 18,23 **** --- 18,24 ---- $nameserver = new Net::DNS::Nameserver( LocalAddr => ['::1' , '127.0.0.1' ], LocalPort => "5353", + Proto => "both", ReplyHandler => \&reply_handler, Verbose => 1, Truncate => 0 *************** *** 95,100 **** --- 96,102 ---- my $port = $self{LocalPort} || DEFAULT_PORT; $self{Truncate} = 1 unless defined( $self{Truncate} ); $self{IdleTimeout} = 120 unless defined( $self{IdleTimeout} ); + $self{Proto} = "both" unless (defined $self{Proto} ); my @sock_tcp; # All the TCP sockets we will listen to. my @sock_udp; # All the UDP sockets we will listen to. *************** *** 107,113 **** #-------------------------------------------------------------------------- # Create the TCP socket. #-------------------------------------------------------------------------- ! print "\nCreating TCP socket $addr#$port - " if $self{Verbose}; my $sock_tcp = inet_new( --- 109,115 ---- #-------------------------------------------------------------------------- # Create the TCP socket. #-------------------------------------------------------------------------- ! if ($self{Proto} =~ /^(both|tcp)$/) { print "\nCreating TCP socket $addr#$port - " if $self{Verbose}; my $sock_tcp = inet_new( *************** *** 124,135 **** } else { cluck "Couldn't create TCP socket: $!"; } #-------------------------------------------------------------------------- # Create the UDP Socket. #-------------------------------------------------------------------------- ! print "Creating UDP socket $addr#$port - " if $self{Verbose}; my $sock_udp = inet_new( LocalAddr => $addr, --- 126,139 ---- } else { cluck "Couldn't create TCP socket: $!"; } + } #-------------------------------------------------------------------------- # Create the UDP Socket. #-------------------------------------------------------------------------- ! if ($self{Proto} =~ /^(both|udp)$/) { ! print "Creating UDP socket $addr#$port - " if $self{Verbose}; my $sock_udp = inet_new( LocalAddr => $addr, *************** *** 143,149 **** } else { cluck "Couldn't create UDP socket: $!"; } ! } #-------------------------------------------------------------------------- --- 147,153 ---- } else { cluck "Couldn't create UDP socket: $!"; } ! } } #-------------------------------------------------------------------------- *************** *** 158,163 **** --- 162,175 ---- return undef unless $select->count; #-------------------------------------------------------------------------- + # Optimize for single-UDP-only case + #-------------------------------------------------------------------------- + if ($self{Proto} =~ /^(both|udp)$/) { + if (scalar @sock_udp == 1) { + $self{"SingleUDPSocket"}=$sock_udp[0]; + } + } + #-------------------------------------------------------------------------- # Return the object. #-------------------------------------------------------------------------- *************** *** 448,453 **** --- 460,473 ---- print ";loop_once called with timeout: " . ( defined($timeout) ? $timeout : "undefined" ) . "\n" if $self->{Verbose} && $self->{Verbose} > 4; + + # Optimize for single UDP socket case. + # Eliminates a large cost from CORE::select() + if ($self->{SingleUDPSocket}) { + $self->udp_connection($self->{SingleUDPSocket}); + return; + } + foreach my $sock ( keys %{$self->{_tcp}} ) { # There is TCP traffic to handle *************** *** 561,566 **** --- 581,594 ---- Verbose => 1 ); + my $ns = new Net::DNS::Nameserver( + LocalAddr => "10.1.2.3", + LocalPort => "5353", + ReplyHandler => \&reply_handler, + Verbose => 1, + Proto => 'udp' + ); + my $ns = new Net::DNS::Nameserver( *************** *** 572,577 **** --- 600,606 ---- ); + Returns a Net::DNS::Nameserver object, or undef if the object could not be created. *************** *** 579,584 **** --- 608,615 ---- LocalAddr IP address on which to listen. Defaults to INADDR_ANY. LocalPort Port on which to listen. Defaults to 53. + Proto Protocol to use; tcp, udp, Defaults to "both" + or both. ReplyHandler Reference to reply-handling subroutine Required. NotifyHandler Reference to reply-handling *************** *** 599,604 **** --- 630,637 ---- also list IPv6 addresses and the default is '0' (listen on all interfaces on IPv6 and IPv4); + The Proto setting, if specified, allows for the ability to bind + only a single protocol. See L</OPTIMIZATIONS>. The ReplyHandler subroutine is passed the query name, query class, query type and optionally an argument containing the peerhost, the *************** *** 732,737 **** --- 765,797 ---- $ns->main_loop; + =head1 OPTIMIZATIONS + + Normally, Net::DNS::Nameserver will bind to both UDP and TCP; + and can bind to multiple LocalAddr. IO::Select is automatically + used to respond to the appropriate sockets. Under high load, + CORE::select() can chew significant CPU. + + If you open just a single socket, for a single address, + for only UDP, then calls to loop_once(0) or main_loop() + will skip the select() call, and go straight to read(); + + Keep in mind, you may need to have a seperate process + that handles TCP; and/or seperate processes for IPv4 vs IPv6. + + my $ns = new Net::DNS::Nameserver( + LocalAddr => "0.0.0.0", + LocalPort => 53, + Proto => "udp", + ReplyHandler => \&reply_handler, + Verbose => 1 + ) || die "couldn't create nameserver object\n"; + + while(1) { + $ns->loop_once(0); + } + + =head1 BUGS Limitations in perl 5.8.6 makes it impossible to guarantee that
From: jfesler [...] gigo.com
Show quoted text
> The attached patch will
Ignore that line. Hit send too soon. That said, the patch and description of the patch are as intended.
From: jfesler [...] gigo.com
Any chance of this getting adopted? The changes are particularly useful for handling higher volume queries against a single socket; and to maximize queries per second against a single CPU core. These changes allow me to double my DNS serving capacity *per core*; and allow me to run multiple cores without the traditional pitfalls of multi-process using select() against the same socket. Tracking changes against this is a bit of a bummer. :-) Attached is an updated diff against 0.78
Subject: Nameserver.pm.PATCH-from-0.78.patch
*** Nameserver.pm.0.78 2014-08-16 14:49:33.140671435 -0700 --- Nameserver.pm 2014-08-16 14:51:51.875139456 -0700 *************** *** 18,23 **** --- 18,24 ---- $nameserver = new Net::DNS::Nameserver( LocalAddr => ['::1' , '127.0.0.1' ], LocalPort => "5353", + Proto => "both", ReplyHandler => \&reply_handler, Verbose => 1, Truncate => 0 *************** *** 95,100 **** --- 96,102 ---- my $port = $self{LocalPort} || DEFAULT_PORT; $self{Truncate} = 1 unless defined( $self{Truncate} ); $self{IdleTimeout} = 120 unless defined( $self{IdleTimeout} ); + $self{Proto} = "both" unless (defined $self{Proto} ); my @sock_tcp; # All the TCP sockets we will listen to. my @sock_udp; # All the UDP sockets we will listen to. *************** *** 108,113 **** --- 110,116 ---- # Create the TCP socket. #-------------------------------------------------------------------------- + if ($self{Proto} =~ /^(both|tcp)$/) { print "\nCreating TCP socket $addr#$port - " if $self{Verbose}; my $sock_tcp = inet_new( *************** *** 124,135 **** } else { cluck "Couldn't create TCP socket: $!"; } #-------------------------------------------------------------------------- # Create the UDP Socket. #-------------------------------------------------------------------------- ! print "Creating UDP socket $addr#$port - " if $self{Verbose}; my $sock_udp = inet_new( LocalAddr => $addr, --- 127,140 ---- } else { cluck "Couldn't create TCP socket: $!"; } + } #-------------------------------------------------------------------------- # Create the UDP Socket. #-------------------------------------------------------------------------- ! if ($self{Proto} =~ /^(both|udp)$/) { ! print "Creating UDP socket $addr#$port - " if $self{Verbose}; my $sock_udp = inet_new( LocalAddr => $addr, *************** *** 143,149 **** } else { cluck "Couldn't create UDP socket: $!"; } ! } #-------------------------------------------------------------------------- --- 148,154 ---- } else { cluck "Couldn't create UDP socket: $!"; } ! } } #-------------------------------------------------------------------------- *************** *** 158,163 **** --- 163,176 ---- return undef unless $select->count; #-------------------------------------------------------------------------- + # Optimize for single-UDP-only case + #-------------------------------------------------------------------------- + if ($self{Proto} =~ /^(both|udp)$/) { + if (scalar @sock_udp == 1) { + $self{"SingleUDPSocket"}=$sock_udp[0]; + } + } + #-------------------------------------------------------------------------- # Return the object. #-------------------------------------------------------------------------- *************** *** 448,453 **** --- 461,474 ---- print ";loop_once called with timeout: " . ( defined($timeout) ? $timeout : "undefined" ) . "\n" if $self->{Verbose} && $self->{Verbose} > 4; + + # Optimize for single UDP socket case. + # Eliminates a large cost from CORE::select() + if ($self->{SingleUDPSocket}) { + $self->udp_connection($self->{SingleUDPSocket}); + return; + } + foreach my $sock ( keys %{$self->{_tcp}} ) { # There is TCP traffic to handle *************** *** 561,566 **** --- 582,595 ---- Verbose => 1 ); + my $ns = new Net::DNS::Nameserver( + LocalAddr => "10.1.2.3", + LocalPort => "5353", + ReplyHandler => \&reply_handler, + Verbose => 1, + Proto => 'udp' + ); + my $ns = new Net::DNS::Nameserver( *************** *** 572,577 **** --- 601,607 ---- ); + Returns a Net::DNS::Nameserver object, or undef if the object could not be created. *************** *** 579,584 **** --- 609,616 ---- LocalAddr IP address on which to listen. Defaults to INADDR_ANY. LocalPort Port on which to listen. Defaults to 53. + Proto Protocol to use; tcp, udp, Defaults to "both" + or both. ReplyHandler Reference to reply-handling subroutine Required. NotifyHandler Reference to reply-handling *************** *** 599,604 **** --- 631,638 ---- also list IPv6 addresses and the default is '0' (listen on all interfaces on IPv6 and IPv4); + The Proto setting, if specified, allows for the ability to bind + only a single protocol. See L</OPTIMIZATIONS>. The ReplyHandler subroutine is passed the query name, query class, query type and optionally an argument containing the peerhost, the *************** *** 732,737 **** --- 766,798 ---- $ns->main_loop; + =head1 OPTIMIZATIONS + + Normally, Net::DNS::Nameserver will bind to both UDP and TCP; + and can bind to multiple LocalAddr. IO::Select is automatically + used to respond to the appropriate sockets. Under high load, + CORE::select() can chew significant CPU. + + If you open just a single socket, for a single address, + for only UDP, then calls to loop_once(0) or main_loop() + will skip the select() call, and go straight to read(); + + Keep in mind, you may need to have a seperate process + that handles TCP; and/or seperate processes for IPv4 vs IPv6. + + my $ns = new Net::DNS::Nameserver( + LocalAddr => "0.0.0.0", + LocalPort => 53, + Proto => "udp", + ReplyHandler => \&reply_handler, + Verbose => 1 + ) || die "couldn't create nameserver object\n"; + + while(1) { + $ns->loop_once(0); + } + + =head1 BUGS Limitations in perl 5.8.6 makes it impossible to guarantee that
From: rwfranks [...] acm.org
On Sat Aug 16 17:58:32 2014, jfesler wrote: Show quoted text
> Any chance of this getting adopted?
Vanishingly small. Net::DNS::Nameserver is a plaything intended for creating test responses. Arguably, it is not even the best tool for doing that. It is certainly *not* a production-grade nameserver implementation, never was, never will be. The preamble to your request indicates that your issue was about performance, or rather, lack of it. The easy way of solving that problem is to use BIND, Unbound, or any of the other dozen or so well supported nameserver implementations available. All of these are well over an order of magnitude faster than Net::DNS::Nameserver will ever be, even if modified in the way you suggest.
From: jfesler [...] gigo.com
TL:DR? Thanks for rejecting; I'll fork and move on. -- Net::DNS has been extremely handy for a poor-man's GSLB. Production quality or not, I'm getting enough performance now to run out of HTTP before I run out of DNS. And I'm getting the ability to act as a GSLB to distribute and route traffic both based on ISP and on health checks. Basically, a poor-man's Akamai GTM, to support a non-revenue-generating public resource. The "Easy" solutions you point out are not well suited for this. I actually have looked at the various mainstream DNS products. For static queries, they'll all work fine. For dynamic queries, the answers are less clear. bind9: no dynamic interface (other than inserts/deletes using DDNS). bind view ACLs too big when looking at the BGP routing table. bind10: not a viable product. unbound: is not an auth nsd: no dynamic interface (unless I missed it) djbdns: no dynamic interface. pdns: pipe interface does exist; I could hook into that. except it fails one of my critical use cases. *might* be possible as a LUA script. I've instead made Net::DNS do my bidding. And, after profiling, I was able to make changes that cut the CPU utilization by literally half, with little additional CPU overhead for the default use case. I'm a bit bummed; but I understand. I'll fork, and I'll move on. I'm even more bummed that I"ll have to fork and stagnate the entire Net::DNS instead of just Net::DNS::Resolver, but such is life. Thanks.
From: rwfranks [...] acm.org
On Sat Aug 16 20:37:58 2014, jfesler wrote: Show quoted text
> TL:DR? Thanks for rejecting; I'll fork and move on. >
[snip] Show quoted text
> I'm a bit bummed; but I understand. I'll fork, and I'll move on. I'm > even more bummed that I"ll have to fork and stagnate the entire > Net::DNS instead of just Net::DNS::Resolver, but such is life. >
Step back a little, and ask yourself whether it is reasonable to demand changes, for which you are the only user, and expect to offload the long-term maintenance commitment onto us. There is no such thing as a free lunch. The Perl licensing terms allow you to modify the code in any way you choose. Nothing prevents you from building it into your code, provided that you acknowledge the source and copyrights of the authors and contributors. There is nothing to prevent your modified nameserver component depending on later versions of Net::DNS in the same way it does already. If you are still not prepared to do the work, you might consider using the perlcc compiler. Both the compiler and Net::DNS needed changes to make this work, so please make sure you use the latest revisions.
Subject: Re: [rt.cpan.org #96069] Protocol specific (TCP or UDP) Nameserver objects (patch included)
Date: Sun, 17 Aug 2014 09:34:33 -0700
To: bug-Net-DNS [...] rt.cpan.org
From: Luis Muñoz <lem [...] itverx.com.ve>
While Net:: DNS is commonly blamed to be slow, it's certainly the most flexible DNS framework out there. The name server part of it might not be "production grade" as is now, but it can be used to implement interesting services. I've done it in the past and the requestor seems to have done the same. I know that what I was doing at my time worked very well, can't speak for others' implementations. Bottom line is, exotic tasks are easier to implement under Net::DNS then under any "real" name server. To complement the advice to the requestor, you might want to consider putting a caching name server in front of your dynamic application. Responses won't be authoritative though (but that could be fixed with rpz) Best regards -lem On August 17, 2014 3:55:42 AM PDT, Dick Franks via RT <bug-Net-DNS@rt.cpan.org> wrote: Show quoted text
> Queue: Net-DNS > Ticket <URL: https://rt.cpan.org/Ticket/Display.html?id=96069 > > >On Sat Aug 16 20:37:58 2014, jfesler wrote:
>> TL:DR? Thanks for rejecting; I'll fork and move on. >>
>[snip] > >
>> I'm a bit bummed; but I understand. I'll fork, and I'll move on.
>I'm
>> even more bummed that I"ll have to fork and stagnate the entire >> Net::DNS instead of just Net::DNS::Resolver, but such is life. >>
>Step back a little, and ask yourself whether it is reasonable to demand >changes, for which you are the only user, and expect to offload the >long-term maintenance commitment onto us. There is no such thing as a >free lunch. > >The Perl licensing terms allow you to modify the code in any way you >choose. Nothing prevents you from building it into your code, provided >that you acknowledge the source and copyrights of the authors and >contributors. > >There is nothing to prevent your modified nameserver component >depending on later versions of Net::DNS in the same way it does >already. > >If you are still not prepared to do the work, you might consider using >the perlcc compiler. Both the compiler and Net::DNS needed changes to >make this work, so please make sure you use the latest revisions.
-- Sent from my Android device with K-9 Mail. Please excuse my brevity.
From: rwfranks [...] acm.org
So there you have it. Three choices: 1) Make Net::DNS::Nameserver run an order of magnitude faster by compiling it (and supporting bits) using perlcc. 2) Limit the query load by putting a caching nameserver in front of it, and tune the overall scheme by suitable choice of TTLs. 3) Engineer something faster and/or better.