File Coverage

blib/lib/Mojo/TFTPd.pm
Criterion Covered Total %
statement 101 128 78.9
branch 38 60 63.3
condition 17 22 77.2
subroutine 16 21 76.1
pod 1 2 50.0
total 173 233 74.2


line stmt bran cond sub pod time code
1             package Mojo::TFTPd;
2              
3             =head1 NAME
4              
5             Mojo::TFTPd - Trivial File Transfer Protocol daemon
6              
7             =head1 VERSION
8              
9             0.04
10              
11             =head1 SYNOPSIS
12              
13             use Mojo::TFTPd;
14             my $tftpd = Mojo::TFTPd->new;
15              
16             $tftpd->on(error => sub {
17             warn "TFTPd: $_[1]\n";
18             });
19              
20             $tftpd->on(rrq => sub {
21             my($tftpd, $c) = @_;
22             open my $FH, '<', $c->file;
23             $c->filehandle($FH);
24             $c->filesize(-s $c->file);
25             });
26              
27             $tftpd->on(wrq => sub {
28             my($tftpd, $c) = @_;
29             open my $FH, '>', '/dev/null';
30             $c->filehandle($FH);
31             });
32              
33             $tftpd->on(finish => sub {
34             my($tftpd, $c, $error) = @_;
35             warn "Connection: $error\n" if $error;
36             });
37              
38             $tftpd->start;
39             $tftpd->ioloop->start unless $tftpd->ioloop->is_running;
40              
41             =head1 DESCRIPTION
42              
43             This module implements a server for the
44             L.
45              
46             From Wikipedia:
47              
48             Trivial File Transfer Protocol (TFTP) is a file transfer protocol notable
49             for its simplicity. It is generally used for automated transfer of
50             configuration or boot files between machines in a local environment.
51              
52             The connection ($c) which is referred to in this document is an instance of
53             L.
54              
55             =cut
56              
57 6     6   96867 use Mojo::Base 'Mojo::EventEmitter';
  6         42862  
  6         33  
58 6     6   251505 use Mojo::IOLoop;
  6         543773  
  6         29  
59 6     6   2419 use Mojo::TFTPd::Connection;
  6         14  
  6         50  
60 6     6   179 use constant OPCODE_RRQ => 1;
  6         7  
  6         296  
61 6     6   23 use constant OPCODE_WRQ => 2;
  6         6  
  6         260  
62 6     6   26 use constant OPCODE_DATA => 3;
  6         6  
  6         230  
63 6     6   23 use constant OPCODE_ACK => 4;
  6         6  
  6         214  
64 6     6   20 use constant OPCODE_ERROR => 5;
  6         7  
  6         236  
65 6     6   19 use constant OPCODE_OACK => 6;
  6         6  
  6         233  
66 6     6   21 use constant MIN_BLOCK_SIZE => 8;
  6         7  
  6         224  
67 6     6   21 use constant MAX_BLOCK_SIZE => 65464; # From RFC 2348
  6         9  
  6         302  
68 6 50   6   22 use constant DEBUG => $ENV{MOJO_TFTPD_DEBUG} ? 1 : 0;
  6         8  
  6         9700  
69              
70             our $VERSION = '0.04';
71              
72             =head1 EVENTS
73              
74             =head2 error
75              
76             $self->on(error => sub {
77             my($self, $str) = @_;
78             });
79              
80             This event is emitted when something goes wrong: Fail to L to socket,
81             read from socket or other internal errors.
82              
83             =head2 finish
84              
85             $self->on(finish => sub {
86             my($self, $c, $error) = @_;
87             });
88              
89             This event is emitted when the client finish, either successfully or due to an
90             error. C<$error> will be an empty string on success.
91              
92             =head2 rrq
93              
94             $self->on(rrq => sub {
95             my($self, $c) = @_;
96             });
97              
98             This event is emitted when a new read request arrives from a client. The
99             callback should set L or the connection
100             will be dropped.
101             L can also be a L reference.
102              
103             =head2 wrq
104              
105             $self->on(wrq => sub {
106             my($self, $c) = @_;
107             });
108              
109             This event is emitted when a new write request arrives from a client. The
110             callback should set L or the connection
111             will be dropped.
112             L can also be a L reference.
113              
114             =head1 ATTRIBUTES
115              
116             =head2 connection_class
117              
118             $str = $self->connection_class;
119             $self = $self->connection_class($str);
120              
121             Used to set a custom connection class. Defaults to L.
122              
123             =cut
124              
125             has connection_class => 'Mojo::TFTPd::Connection';
126              
127             =head2 ioloop
128              
129             Holds an instance of L.
130              
131             =cut
132              
133             has ioloop => sub { Mojo::IOLoop->singleton };
134              
135             =head2 listen
136              
137             $str = $self->server;
138             $self->server("127.0.0.1:69");
139             $self->server("tftp://*:69"); # any interface
140              
141             The bind address for this server.
142              
143             =cut
144              
145             has listen => 'tftp://*:69';
146              
147             =head2 max_connections
148              
149             How many concurrent connections this server can handle. Default to 1000.
150              
151             =cut
152              
153             has max_connections => 1000;
154              
155             =head2 retries
156              
157             How many times the server should try to send ACK or DATA to the client before
158             dropping the L.
159              
160             =cut
161              
162             has retries => 1;
163              
164             =head2 inactive_timeout
165              
166             How long a L can stay idle before
167             being dropped. Default is 15 seconds.
168              
169             =cut
170              
171             has inactive_timeout => 15;
172              
173             =head2 retransmit
174              
175             How many times the server should try to retransmit the last packet on timeout before
176             dropping the L. Default is 0 (disable retransmits)
177              
178             =cut
179              
180             has retransmit => 0;
181              
182             =head2 retransmit_timeout
183              
184             How long a L can stay idle before last packet
185             being retransmitted. Default is 2 seconds.
186              
187             =cut
188              
189             has retransmit_timeout => 2;
190              
191              
192             =head1 METHODS
193              
194             =head2 start
195              
196             Starts listening to the address and port set in L. The L
197             event will be emitted if the server fail to start.
198              
199             =cut
200              
201             sub start {
202 0     0 1 0 my $self = shift;
203 0         0 my $reactor = $self->ioloop->reactor;
204 0         0 my $socket;
205              
206 0 0       0 $self->{connections} and return $self;
207 0         0 $self->{connections} = {};
208              
209             # split $self->listen into host and port
210 0         0 my ($host, $port) = $self->_parse_listen;
211              
212 0         0 warn "[Mojo::TFTPd] Listen to $host:$port\n" if DEBUG;
213              
214 0         0 $socket = IO::Socket::INET->new(
215             LocalAddr => $host,
216             LocalPort => $port,
217             Proto => 'udp',
218             );
219              
220 0 0       0 if(!$socket) {
221 0         0 delete $self->{connections};
222 0         0 return $self->emit(error => "Can't create listen socket: $!");
223             };
224              
225 0         0 Scalar::Util::weaken($self);
226              
227 0         0 $socket->blocking(0);
228 0     0   0 $reactor->io($socket, sub { $self->_incoming });
  0         0  
229 0         0 $reactor->watch($socket, 1, 0); # watch read events
230 0         0 $self->{socket} = $socket;
231              
232 0         0 return $self;
233             }
234              
235             sub _incoming {
236 131131     131131   2118054 my $self = shift;
237 131131         144124 my $socket = $self->{socket};
238 131131         215244 my $read = $socket->recv(my $datagram, MAX_BLOCK_SIZE + 4); # Add 4 Bytes of Opcode + Block#
239 131131         313935 my($opcode, $connection);
240 131131         120257 my $keep = 0;
241              
242 131131 100       220944 if(!defined $read) {
243 1         16 return $self->emit(error => "Read: $!");
244             }
245              
246 131130         298958 $opcode = unpack 'n', substr $datagram, 0, 2, '';
247              
248             # new connection
249 131130 100       341955 if($opcode eq OPCODE_RRQ) {
    100          
250 18         43 return $self->_new_request(rrq => $datagram);
251             }
252             elsif($opcode eq OPCODE_WRQ) {
253 7         17 return $self->_new_request(wrq => $datagram);
254             }
255              
256             # existing connection
257 131105         251336 $connection = $self->{connections}{$socket->peername};
258              
259 131105 100       376627 if(!$connection) {
260 1         3 return $self->emit(error => "@{[$socket->peerhost]} has no connection");
  1         3  
261             }
262              
263             # Stop retransmit/inactive timer
264 131104         2565386 $self->ioloop->remove($connection->{timer});
265 131104         5052130 delete $connection->{timer};
266              
267              
268 131104 100       223130 if($opcode == OPCODE_ACK) {
    50          
    0          
269 65558         142195 $keep = $connection->receive_ack($datagram);
270             }
271             elsif($opcode == OPCODE_DATA) {
272 65546         125479 $keep = $connection->receive_data($datagram);
273             }
274             elsif($opcode == OPCODE_ERROR) {
275 0         0 $connection->receive_error($datagram);
276             }
277             else {
278 0         0 $connection->error('Unknown opcode');
279             }
280              
281 131104 100       217853 if ($keep) {
282             # restart retransmit/inactive timer
283             $connection->{timer} = $self->ioloop->recurring($connection->timeout => sub {
284 0 0   0   0 $connection->send_retransmit or $self->_delete_connection($connection);
285 131092         2000577 });
286 131092         10244839 return;
287             }
288              
289             # if something goes wrong or finish with connection
290 12         26 $self->_delete_connection($connection);
291             }
292              
293             sub _new_request {
294 25     25   40 my($self, $type, $datagram) = @_;
295 25         81 my($file, $mode, @rfc) = split "\0", $datagram;
296 25         36 my $socket = $self->{socket};
297 25         21 my $connection;
298 25         25 my $keep = 0;
299              
300 25         19 warn "[Mojo::TFTPd] <<< @{[$socket->peerhost]} $type $file $mode @rfc\n" if DEBUG;
301              
302 25 100       99 if(!$self->has_subscribers($type)) {
303 4         39 $self->emit(error => "Cannot handle $type requests");
304 4         49 return;
305             }
306 21 100       603 if($self->max_connections <= keys %{ $self->{connections} }) {
  21         162  
307 2         10 $self->emit(error => "Max connections ($self->{max_connections}) reached");
308 2         26 return;
309             }
310              
311 19         47 my %rfc = @rfc;
312 19 100       343 $connection = $self->connection_class->new(
313             type => $type,
314             file => $file,
315             mode => $mode,
316             peerhost => $socket->peerhost,
317             peername => $socket->peername,
318             retries => $self->retries,
319             timeout => $self->retransmit ? $self->retransmit_timeout : $self->inactive_timeout,
320             retransmit => $self->retransmit,
321             rfc => \%rfc,
322             socket => $socket,
323             );
324              
325 19 100       1797 if ($rfc{blksize}) {
326 5 50       21 $rfc{blksize} = MIN_BLOCK_SIZE if $rfc{blksize} < MIN_BLOCK_SIZE;
327 5 50       17 $rfc{blksize} = MAX_BLOCK_SIZE if $rfc{blksize} > MAX_BLOCK_SIZE;
328 5         96 $connection->blocksize($rfc{blksize});
329             }
330 19 50 66     83 if ($rfc{timeout} and $rfc{timeout} >= 0 and $rfc{timeout} <= 255) {
      66        
331 1         20 $connection->timeout($rfc{timeout});
332             }
333 19 100 100     70 if ($type eq 'wrq' and $rfc{tsize}) {
334 1         17 $connection->filesize($rfc{tsize});
335             }
336              
337 19         76 $self->emit($type => $connection);
338              
339 19 100 66     2075 if (!$connection->filehandle) {
    100 66        
    50          
340 3   50     100 $connection->send_error(file_not_found => $connection->error || 'No filehandle');
341 3         16 $keep = 1;
342             }
343             elsif ((%rfc and $connection->send_oack)
344             or $type eq 'rrq' ? $connection->send_data : $connection->send_ack) {
345 16         22 $keep = 1;
346             }
347              
348 19 50       42 if ($keep) {
349 19         293 $self->{connections}{$connection->peername} = $connection;
350             # start retransmit/inactive timer
351             $connection->{timer} = $self->ioloop->recurring($connection->timeout => sub {
352 0 0   0   0 $connection->send_retransmit or $self->_delete_connection($connection);
353 19         345 });
354             }
355             else {
356 0         0 $self->emit(finish => $connection, $connection->error);
357             }
358             }
359              
360             sub _parse_listen {
361 7     7   5892 my $self = shift;
362              
363 7         135 my ($scheme, $host, $port) = $self->listen =~ m!
364             (?: ([^:/]+) :// )? # part before ://
365             ([^:]*) # everyting until a :
366             (?: : (\d+) )? # any digits after the :
367             !xms;
368              
369             # if scheme is set but no port, use scheme
370 7 100 100     551 $port = getservbyname($scheme, '') if $scheme && !defined $port;
371              
372             # use port 69 as fallback
373 7   100     26 $port //= 69;
374              
375             # if host == '*', replace it with '0.0.0.0'
376 7 100       14 $host = '0.0.0.0' if $host eq '*';
377              
378 7         24 return ($host, $port);
379             }
380              
381             sub _delete_connection {
382 12     12   15 my($self, $connection) = @_;
383 12 50       24 $self->ioloop->remove($connection->{timer}) if $connection->{timer};
384 12         183 delete $self->{connections}{$connection->peername};
385 12         236 $self->emit(finish => $connection, $connection->error);
386             }
387              
388             sub DEMOLISH {
389 0     0 0   my $self = shift;
390 0 0         my $reactor = eval { $self->ioloop->reactor } or return; # may be undef during global destruction
  0            
391              
392 0 0         $reactor->remove($self->{socket}) if $self->{socket};
393             }
394              
395             =head1 AUTHOR
396              
397             Svetoslav Naydenov - C
398              
399             Jan Henning Thorsen - C
400              
401             =cut
402              
403             1;