1package POE::Component::Server::DNS; 2$POE::Component::Server::DNS::VERSION = '0.32'; 3#ABSTRACT: A non-blocking, concurrent DNS server POE component 4 5use strict; 6use warnings; 7use POE qw(Component::Client::DNS Wheel::ReadWrite Component::Client::DNS::Recursive Wheel::SocketFactory Filter::DNS::TCP); 8use Socket; 9use Net::DNS::RR; 10use IO::Socket::INET; 11 12sub spawn { 13 my $package = shift; 14 my %args = @_; 15 $args{lc $_} = delete $args{$_} for keys %args; 16 17 my $options = delete $args{options}; 18 19 my $self = bless \%args, $package; 20 21 $self->{_handlers} = [ ]; 22 23 unless ( $self->{no_clients} ) { 24 $self->{_localhost} = Net::DNS::RR->new('localhost. 0 A 127.0.0.1'); 25 } 26 27 $self->{session_id} = POE::Session->create( 28 object_states => [ 29 $self => { shutdown => '_shutdown', _sock_err_tcp => '_sock_err', }, 30 $self => [ qw(_start _dns_incoming _dns_err _dns_response _dns_recursive add_handler del_handler _handled_req _sock_up _sock_err log_event _sock_up_tcp) ], 31 ], 32 heap => $self, 33 options => ( $options && ref $options eq 'HASH' ? $options : { } ), 34 )->ID(); 35 36 return $self; 37} 38 39sub _start { 40 my ($kernel,$self,$session) = @_[KERNEL,OBJECT,SESSION]; 41 42 $self->{session_id} = $session->ID(); 43 44 if ( $self->{alias} ) { 45 $kernel->alias_set($self->{alias}); 46 } 47 else { 48 $kernel->alias_set("$self"); 49 $self->{alias} = "$self"; 50 } 51 52 unless ( $self->{no_clients} ) { 53 $self->{resolver_opts} = { } unless $self->{resolver_opts} and ref $self->{resolver_opts} eq 'HASH'; 54 delete $self->{resolver_opts}->{Alias}; 55 $self->{resolver} = POE::Component::Client::DNS->spawn( Alias => "resolver" . $self->session_id(), %{ $self->{resolver_opts} } ); 56# $self->{recursive}->hints( { event => '_hints' } ); 57 } 58 59 $self->{factory} = POE::Wheel::SocketFactory->new( 60 SocketProtocol => 'udp', 61 BindAddress => $self->{address} || INADDR_ANY, 62 BindPort => ( defined $self->{port} ? $self->{port} : 53 ), 63 SuccessEvent => '_sock_up', 64 FailureEvent => '_sock_err', 65 ); 66 67 $self->{factory_tcp} = POE::Wheel::SocketFactory->new( 68 SocketProtocol => 'tcp', 69 Reuse => 1, 70 BindAddress => $self->{address} || INADDR_ANY, 71 BindPort => ( defined $self->{port} ? $self->{port} : 53 ), 72 SuccessEvent => '_sock_up_tcp', 73 FailureEvent => '_sock_err_tcp', 74 ); 75 76 undef; 77} 78 79sub _sock_up { 80 my ($kernel,$self,$dns_socket) = @_[KERNEL,OBJECT,ARG0]; 81 $self->{_sockport} = ( sockaddr_in( getsockname($dns_socket) ) )[0]; 82 delete $self->{factory}; 83 $self->{dnsrw} = POE::Wheel::ReadWrite->new( 84 Handle => $dns_socket, 85 Driver => DNS::Driver::SendRecv->new(), 86 Filter => DNS::Filter::UDPDNS->new(), 87 InputEvent => '_dns_incoming', 88 ErrorEvent => '_dns_err', 89 ); 90 undef; 91} 92 93sub _sock_up_tcp { 94 my ($kernel,$self,$dns_socket, $address, $port) = @_[KERNEL,OBJECT,ARG0, ARG1, ARG2]; 95 $address = inet_ntoa($address); 96 97 POE::Session->create( 98 object_states => [ 99 $self => { _start => '_socket_success', _stop => '_socket_death' }, 100 $self => [ qw( _sock_err _socket_input _socket_death _handled_req _dns_incoming _dns_recursive _dns_response) ], 101 ], 102 args => [$dns_socket], 103 heap => { _tcp_sockport => "$address:$port", }, 104 ); 105 106 undef; 107} 108 109 110sub _socket_death { 111 my $heap = $_[HEAP]; 112 if ($heap->{socket_wheel}) { 113 delete $heap->{socket_wheel}; 114 } 115} 116 117sub _socket_success { 118 my ($heap,$kernel,$connected_socket) = @_[HEAP, KERNEL, ARG0]; 119 120 $heap->{socket_wheel} = POE::Wheel::ReadWrite->new( 121 Handle => $connected_socket, 122 Filter => POE::Filter::DNS::TCP->new(), 123 InputEvent => '_dns_incoming', 124 ErrorEvent => '_sock_err', 125 ); 126} 127 128sub _socket_input { 129 my ($heap, $buf) = @_[HEAP, ARG0]; 130 warn Dumper $buf; 131 delete $heap->{socket_wheel}; 132} 133 134sub _sock_err { 135 my ($operation, $errnum, $errstr, $wheel_id) = @_[ARG0..ARG3]; 136 # ErrorEvent may also indicate EOF on a FileHandle by returning operation "read" error 0. For sockets, this means the remote end has closed the connection. 137 return undef if ($operation eq "read" and $errnum == 0); 138 delete $_[OBJECT]->{factory}; 139 delete $_[OBJECT]->{"factory_tcp"}; 140 die "Wheel $wheel_id generated $operation error $errnum: $errstr\n"; 141 undef; 142} 143 144sub session_id { 145 return $_[0]->{session_id}; 146} 147 148sub resolver { 149 return $_[0]->{resolver}; 150} 151 152sub sockport { 153 return $_[0]->{_sockport}; 154} 155 156sub shutdown { 157 my $self = shift; 158 $poe_kernel->post( $self->session_id() => 'shutdown' ); 159} 160 161sub _shutdown { 162 my ($kernel,$self) = @_[KERNEL,OBJECT]; 163 $kernel->alarm_remove_all(); 164 $kernel->alias_remove( $_ ) for $kernel->alias_list( $_[SESSION] ); 165 delete $self->{dnsrw}; 166 delete $self->{'factory'}; 167 delete $self->{'factory_tcp'}; 168 unless ( $self->{no_clients} ) { 169 $self->{resolver}->shutdown(); 170 #$self->{recursive}->shutdown(); 171 } 172 $kernel->refcount_decrement( $_->{session}, __PACKAGE__ ) for @{ $self->{_handlers} }; 173 $kernel->refcount_decrement( $_, __PACKAGE__ ) for keys %{ $self->{_sessions} }; 174 delete $self->{_handlers}; 175 undef; 176} 177 178sub log_event { 179 my ($kernel,$self,$sender,$event) = @_[KERNEL,OBJECT,SENDER,ARG0]; 180 $sender = $sender->ID(); 181 182 if ( exists $self->{_sessions}->{ $sender } and !$event ) { 183 delete $self->{_sessions}->{ $sender }; 184 $kernel->refcount_decrement( $sender => __PACKAGE__ ); 185 return; 186 } 187 188 if ( exists $self->{_sessions}->{ $sender } ) { 189 $self->{_sessions}->{ $sender } = $event; 190 return; 191 } 192 193 $self->{_sessions}->{ $sender } = $event; 194 $kernel->refcount_increment( $sender => __PACKAGE__ ); 195 return; 196} 197 198sub add_handler { 199 my ($kernel,$self,$sender) = @_[KERNEL,OBJECT,SENDER]; 200 $sender = $sender->ID(); 201 202 # Get the arguments 203 my $args; 204 if (ref($_[ARG0]) eq 'HASH') { 205 $args = { %{ $_[ARG0] } }; 206 } 207 else { 208 warn "first parameter must be a ref hash, trying to adjust. " 209 ."(fix this to get rid of this message)"; 210 $args = { @_[ARG0 .. $#_ ] }; 211 } 212 213 $args->{ lc $_ } = delete $args->{$_} for keys %{ $args }; 214 215 unless ( $args->{label} ) { 216 warn "you must supply a label argument, to make it unique\n"; 217 return; 218 } 219 220 if ( grep { $_->{label} eq $args->{label} } @{ $self->{_handlers} } ) { 221 warn "you must supply a unique label argument, I already have that one\n"; 222 return; 223 } 224 225 unless ( $args->{event} ) { 226 warn "you must supply an event argument, otherwise where do I send the replies to\n"; 227 return; 228 } 229 230 unless ( $args->{match} ) { 231 warn "you must supply a match argument, otherwise what's the point\n"; 232 return; 233 } 234 235 my $regex; 236 eval { $regex = qr/$args->{match}/ }; 237 238 if ( $@ ) { 239 warn "The match argument you supplied was fubar, please try harder\n"; 240 return; 241 } 242 else { 243 $args->{match} = $regex; 244 } 245 246 $args->{session} = $sender unless $args->{session}; 247 if ( my $ref = $kernel->alias_resolve( $args->{session} ) ) { 248 $args->{session} = $ref->ID(); 249 } 250 else { 251 $args->{session} = $sender->ID(); 252 } 253 254 $kernel->refcount_increment( $args->{session}, __PACKAGE__ ); 255 256 push @{ $self->{_handlers} }, $args; 257 258 undef; 259} 260 261sub del_handler { 262 my ($kernel,$self,$label) = @_[KERNEL,OBJECT,ARG0]; 263 return unless $label; 264 265 my $i = 0; my $rec; 266 for ( @{ $self->{_handlers} } ) { 267 if ( $_->{label} eq $label ) { 268 splice( @{ $self->{_handlers} }, $i, 1 ); 269 $rec = $_; 270 last; 271 } 272 } 273 274 $kernel->refcount_decrement( $rec->{session}, __PACKAGE__ ); 275 undef; 276} 277 278sub _dns_incoming { 279 my($kernel,$self,$heap,$session,$dnsq) = @_[KERNEL,OBJECT,HEAP,SESSION,ARG0]; 280 281 # TCP remote address is handled differently than UDP, so fix that here. 282 if (defined($heap->{_tcp_sockport})) { 283 $dnsq->answerfrom($heap->{_tcp_sockport}); 284 } 285 286 my($q) = $dnsq->question(); 287 return unless $q; 288 289 foreach my $handler ( @{ $self->{_handlers} } ) { 290 next unless $q->qname =~ $handler->{match}; 291 my $callback = $session->callback( '_handled_req', $dnsq ); 292 $kernel->post( 293 $handler->{session}, 294 $handler->{event}, 295 $q->qname, 296 $q->qclass, 297 $q->qtype, 298 $callback, 299 $dnsq->answerfrom, $dnsq, $handler->{'label'} ); 300 return; 301 } 302 303 if ( $self->{no_clients} ) { 304 # Refuse unhandled requests, like an authoritative-only 305 # BIND server would. 306 $dnsq->header->rcode('REFUSED'); 307 $dnsq->header->qr(1); 308 $dnsq->header->aa(0); 309 $dnsq->header->ra(0); 310 $dnsq->header->ad(0); 311 my $str = $dnsq->string(); # Doesn't work without this, fucked if I know why. 312 $self->_dispatch_log( $dnsq ); 313 # $self->{dnsrw}->put( $dnsq ) if $self->{dnsrw}; 314 $self->{"dnsrw"}->put( $dnsq ) if (!(defined($heap) && defined($heap->{socket_wheel})) && $self->{"dnsrw"}); 315 $heap->{socket_wheel}->put($dnsq) if $heap->{socket_wheel}; 316 317 return; 318 } 319 320 if ( $q->qname =~ /^localhost\.*$/i ) { 321 $dnsq->push( answer => $self->{_localhost} ); 322 $self->_dispatch_log( $dnsq ); 323 $self->{"dnsrw"}->put( $dnsq ) if (!(defined($heap) && defined($heap->{socket_wheel})) && $self->{"dnsrw"}); 324 $heap->{socket_wheel}->put($dnsq) if $heap->{socket_wheel}; 325 return; 326 } 327 328 if ( $self->{forward_only} ) { 329 my %query = ( 330 class => $q->qclass, 331 type => $q->qtype, 332 host => $q->qname, 333 context => [ $dnsq->answerfrom, $dnsq->header->id ], 334 event => '_dns_response', 335 ); 336 337 my $response = $self->{resolver}->resolve( %query ); 338 $kernel->yield( '_dns_response', $response ) if $response; 339 340 } 341 else { 342# $self->{recursive}->query_dorecursion( { event => '_dns_recursive', data => [ $dnsq, $dnsq->answerfrom, $dnsq->header->id ], }, $q->qname, $q->qtype, $q->qclass ); 343 POE::Component::Client::DNS::Recursive->resolve( 344 event => '_dns_recursive', 345 context => [ $dnsq, $dnsq->answerfrom, $dnsq->header->id ], 346 host => $q->qname, 347 type => $q->qtype, 348 class => $q->qclass, 349 ); 350 } 351 352 undef; 353} 354 355sub _handled_req { 356 my ($kernel,$self,$passthru,$passback,$heap) = @_[KERNEL,OBJECT,ARG0,ARG1,HEAP]; 357 my $reply = $passthru->[0]; 358 my ($rcode, $ans, $auth, $add, $headermask) = @{ $passback }; 359 $reply->header->rcode($rcode); 360 $reply->push("answer", @$ans) if $ans; 361 $reply->push("authority", @$auth) if $auth; 362 $reply->push("additional", @$add) if $add; 363 if (!defined ($headermask)) { 364 $reply->header->ra($self->{no_clients} ? 0 : 1); 365 $reply->header->ad(0); 366 } 367 else { 368 $reply->header->aa(1) if $headermask->{'aa'}; 369 $reply->header->ra(1) if $headermask->{'ra'}; 370 $reply->header->ad(1) if $headermask->{'ad'}; 371 } 372 373 $reply->header->qr(1); 374 $self->_dispatch_log( $reply ); 375 376 $self->{"dnsrw"}->put( $reply ) if (!(defined($heap) && defined($heap->{socket_wheel})) && $self->{"dnsrw"}); 377 $heap->{socket_wheel}->put($reply) if $heap->{socket_wheel}; 378 undef; 379} 380 381sub _dns_err { 382 my($kernel,$self, $op, $errnum, $errstr) = @_[KERNEL,OBJECT, ARG0..ARG2]; 383 warn "DNS readwrite: $op generated error $errnum: $errstr\n"; 384 if (!($op eq "read" and ($errnum == 0 || $errnum == 104))) 385 { 386 warn "SHUTDOWN"; 387 $kernel->yield('shutdown'); 388 } 389 undef; 390} 391 392sub _dns_recursive { 393 my ($kernel,$heap,$self,$data) = @_[KERNEL,HEAP,OBJECT,ARG0]; 394 return if $data->{error}; 395 my ($dnsq,$answerfrom,$id) = @{ $data->{context} }; 396 397 my $socket = $heap->{socket_wheel}; 398 399 my $response = $data->{response}; 400 if ( $response ) { 401 $response->header->id( $id ); 402 $response->answerfrom( $answerfrom ); 403 $self->_dispatch_log( $response ); 404 $self->{"dnsrw"}->put( $response ) if (!(defined($socket)) && $self->{"dnsrw"}); 405 $socket->put($response) if $socket; 406 return; 407 } 408 $dnsq->header->rcode('NXDOMAIN'); 409 $self->_dispatch_log( $dnsq ); 410# $self->{dnsrw}->put( $dnsq ) if $self->{dnsrw}; 411 $self->{"dnsrw"}->put( $dnsq ) if (!(defined($socket)) && $self->{"dnsrw"}); 412 $socket->put($dnsq) if $socket; 413 414 undef; 415} 416 417sub _dns_response { 418 my ($kernel,$self,$heap,$reply) = @_[KERNEL,OBJECT,HEAP,ARG0]; 419 420 my ($answerfrom,$id) = @{ $reply->{context} }; 421 my $response = delete $reply->{response}; 422 $response->header->id( $id ); 423 $response->answerfrom( $answerfrom ); 424 $self->_dispatch_log( $response ); 425 $self->{"dnsrw"}->put( $response ) if (!(defined($heap) && defined($heap->{socket_wheel})) && $self->{"dnsrw"}); 426 $heap->{socket_wheel}->put($response) if $heap->{socket_wheel}; 427 undef; 428} 429 430sub _dispatch_log { 431 my $self = shift; 432 my $packet = shift || return; 433 my $af = $packet->answerfrom; 434 $poe_kernel->post( $_, $self->{_sessions}->{$_}, $af, $packet ) for keys %{ $self->{_sessions} }; 435 return 1; 436} 437 438package DNS::Driver::SendRecv; 439$DNS::Driver::SendRecv::VERSION = '0.32'; 440use strict; 441use POE::Driver; 442use Socket; 443 444sub new { 445 my $class = shift; 446 my $self = []; # the output queue 447 bless $self, $class; 448} 449 450sub get { 451 my $self = shift; 452 my $fh = shift; 453 454 my @ret; 455 while (1) { 456 my $from = recv($fh, my $buffer = '', 4096, 0 ); 457 last if !$from; 458 push @ret, [ $from, $buffer ]; 459 } 460 return if !@ret; 461 return \@ret; 462} 463 464sub put { 465 my $self = shift; 466 my $data = shift; 467 468 push @$self, @$data; 469 my $sum = 0; 470 $sum += length( $_->[1] ) for @$self; 471 return $sum; 472} 473 474sub flush { 475 my $self = shift; 476 my $fh = shift; 477 478 while ( @$self ) { 479 my $n = send($fh, $self->[0][1], 0, $self->[0][0]) 480 or return; 481 $n == length($self->[0][1]) 482 or die "Couldn't write complete message to socket: $!\n"; 483 shift @$self; 484 } 485} 486 487package DNS::Filter::UDPDNS; 488$DNS::Filter::UDPDNS::VERSION = '0.32'; 489use strict; 490use POE::Filter; 491use Socket; 492use Net::DNS::Packet; 493 494sub new { 495 my $class = shift; 496 bless {}, $class; 497} 498 499sub get { 500 my $self = shift; 501 my $data = shift; 502 503 my @ret; 504 for my $d ( @$data ) { 505 ref($d) eq "ARRAY" 506 or die "UDPDNS filter expected arrayrefs for input\n"; 507 my($port, $inet) = sockaddr_in($d->[0]); 508 my $inetstr = inet_ntoa($inet); 509 my($p, $err) = Net::DNS::Packet->new(\$d->[1]); 510 if ( !$p ) { 511 warn "Cannot create DNS question for packet received from " . 512 "$inetstr: $err\n"; 513 } else { 514 $p->answerfrom("$inetstr:$port"); 515 push @ret, $p; 516 } 517 } 518 return \@ret; 519} 520 521sub put { 522 my $self = shift; 523 my $data = shift; 524 525 my @ret; 526 for my $d ( @$data ) { 527 my($inetstr, $port) = split /:/, $d->answerfrom(); 528 $d->{buffer} = ''; #sigh 529 if ( !defined $port ) { 530 warn "answerfrom not set in DNS packet, no destination known\n"; 531 } else { 532 push @ret, 533 [ pack_sockaddr_in($port, inet_aton($inetstr)), $d->data ]; 534 } 535 } 536 return \@ret; 537} 538 5391; 540 541__END__ 542 543=pod 544 545=encoding UTF-8 546 547=head1 NAME 548 549POE::Component::Server::DNS - A non-blocking, concurrent DNS server POE component 550 551=head1 VERSION 552 553version 0.32 554 555=head1 SYNOPSIS 556 557 use strict; 558 use Net::DNS::RR; 559 use POE qw(Component::Server::DNS); 560 561 my $dns_server = POE::Component::Server::DNS->spawn( alias => 'dns_server' ); 562 563 POE::Session->create( 564 package_states => [ 'main' => [ qw(_start handler log) ], ], 565 ); 566 567 $poe_kernel->run(); 568 exit 0; 569 570 sub _start { 571 my ($kernel,$heap) = @_[KERNEL,HEAP]; 572 573 # Tell the component that we want log events to go to 'log' 574 $kernel->post( 'dns_server', 'log_event', 'log' ); 575 576 # register a handler for any foobar.com suffixed domains 577 $kernel->post( 'dns_server', 'add_handler', 578 { 579 event => 'handler', 580 label => 'foobar', 581 match => 'foobar\.com$', 582 } 583 ); 584 undef; 585 } 586 587 sub handler { 588 my ($qname,$qclass,$qtype,$callback) = @_[ARG0..ARG3]; 589 my ($rcode, @ans, @auth, @add); 590 591 if ($qtype eq "A") { 592 my ($ttl, $rdata) = (3600, "10.1.2.3"); 593 push @ans, Net::DNS::RR->new("$qname $ttl $qclass $qtype $rdata"); 594 $rcode = "NOERROR"; 595 } else { 596 $rcode = "NXDOMAIN"; 597 } 598 599 $callback->($rcode, \@ans, \@auth, \@add, { aa => 1 }); 600 undef; 601 } 602 603 sub log { 604 my ($ip_port,$net_dns_packet) = @_[ARG0..ARG1]; 605 $net_dns_packet->print(); 606 undef; 607 } 608 609=head1 DESCRIPTION 610 611POE::Component::Server::DNS is a L<POE> component that implements a DNS server. 612 613It uses L<POE::Component::Client::DNS> to handle resolving when configured as 'forward_only' and 614L<Net::DNS::Resolver::Recurse> wrapped by L<POE::Component::Generic> to perform recursion. 615 616One may add handlers to massage and manipulate responses to particular queries which is vaguely modelled 617after L<Net::DNS::Nameserver>. 618 619=head1 CONSTRUCTOR 620 621=over 622 623=item spawn 624 625Starts a POE::Component::Server::DNS component session and returns an object. Takes a number of optional arguments: 626 627 "alias", an alias to address the component by; 628 "port", which udp port to listen on. Default is 53, which requires 'root' privilege on UN*X type systems; 629 "address", which local IP address to listen on. Default is INADDR_ANY; 630 "resolver_opts", a set of options to pass to the POE::Component::Client::DNS constructor; 631 "forward_only", be a forwarding only DNS server. Default is 0, be recursive. 632 "no_clients", do not spawn client code (See following notes); 633 634"no_clients" disables the spawning of client code (PoCo::Client::DNS, Net::DNS::Resolver::Recursive), and doesn't attempt to forward or recurse inbound requests. Any request not handled by one of your handlers will be C<REFUSED>. Saves some resources when you intend your server to be authoritative only (as opposed to a general resolver for DNS client software to point at directly). Additionally, this argument changes the default "Recursion Available" flag in responses to off instead of on. 635 636=back 637 638=head1 METHODS 639 640These are methods that may be used with the object returned by spawn(). 641 642=over 643 644=item session_id 645 646Returns the L<POE::Session> ID of the component's session. 647 648=item resolver 649 650Returns a reference to the L<POE::Component::Client::DNS> object. 651 652=item shutdown 653 654Terminates the component and associated resolver. 655 656=item sockport 657 658Returns the port of the socket that the component is listening on. 659 660=back 661 662=head1 INPUT EVENTS 663 664These are states that the component will accept: 665 666=over 667 668=item add_handler 669 670Accepts a hashref as an argument with the following keys: 671 672 "event", the event the component will post to, mandatory; 673 "label", a unique name for this handler, mandatory; 674 "match", a regex expression ( without // ) to match against the host part of queries, mandatory; 675 "session", the session where this handler event should be sent to, defaults to SENDER; 676 677See OUTPUT EVENTS for details of what happens when a handler is triggered. 678 679=item del_handler 680 681Accepts a handler label to remove. 682 683=item log_event 684 685Tells the component that a session wishes to receive or stop receiving DNS log events. Specify the event you 686wish to receive log events as the first argument. If no event is specified you stop receiving log events. 687 688=item shutdown 689 690Terminates the component and associated resolver. 691 692=back 693 694=head1 HANDLER EVENTS 695 696These events are triggered by a DNS query matching a handler. The applicable event is fired in the requested session 697with the following paramters: 698 699 ARG0, query name 700 ARG1, query class 701 ARG2, query type 702 ARG3, a callback coderef 703 ARG4, the IP address and port of the requestor, 'IPaddr:port' 704 705Do your manipulating then use the callback to fire the response back to the component, returning a 706response code and references to the answer, authority, and additional sections of the response. For advanced 707usage there is an optional argument containing an hashref with the settings for the aa, ra, and ad header bits. 708The argument is of the form { ad => 1, aa => 0, ra => 1 }. 709 710 $callback->( $rcode, \@ans, \@auth, \@add, { aa => 1 } ); 711 712=head1 LOG EVENTS 713 714These events are triggered whenever a DNS response is sent to a client. 715 716 ARG0, the IP address and port of the requestor, 'IPaddr:port'; 717 ARG1, the Net::DNS::Packet object; 718 719See L<Net::DNS::Packet> for details. 720 721=head1 HISTORY 722 723The component's genesis was inspired by Jan-Pieter's 'Fun with POE' talk at YAPC::EU 2006, which lay much of the 724ground-work code such as the L<POE::Driver> and L<POE::Filter> used internally. BinGOs wrapped it all up in a 725component, added the tests ( borrowed shamelessly from L<POE::Component::Client::DNS>'s testsuite ) and documentation. 726 727Other suggestions as to the API were provided by Ben 'integral' Smith. 728 729Rocco Caputo brought L<POE::Component::Client::DNS> to the party. 730 731=head1 SEE ALSO 732 733L<POE::Component::Client::DNS> 734 735L<POE::Component::Generic> 736 737L<Net::DNS> 738 739L<Net::DNS::Packet> 740 741=head1 AUTHORS 742 743=over 4 744 745=item * 746 747Chris Williams <chris@bingosnet.co.uk> 748 749=item * 750 751Jan-Pieter Cornet <johnpc@xs4all.nl> 752 753=item * 754 755Brandon Black <blblack@gmail.com> 756 757=item * 758 759Richard Harman <richard@richardharman.com> 760 761=item * 762 763Stephan Jauernick <stephan@stejau.de> 764 765=back 766 767=head1 COPYRIGHT AND LICENSE 768 769This software is copyright (c) 2015 by Chris Williams, Jan-Pieter Cornet, Brandon Black, Richard Harman and Stephan Jauernick. 770 771This is free software; you can redistribute it and/or modify it under 772the same terms as the Perl 5 programming language system itself. 773 774=cut 775