File Coverage

blib/lib/Mojo/TFTPd.pm
Criterion Covered Total %
statement 93 114 81.5
branch 45 60 75.0
condition 15 19 78.9
subroutine 19 22 86.3
pod 1 1 100.0
total 173 216 80.0


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