File Coverage

blib/lib/Net/BGP/Process.pm
Criterion Covered Total %
statement 149 171 87.1
branch 37 60 61.6
condition 10 12 83.3
subroutine 17 17 100.0
pod 0 5 0.0
total 213 265 80.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Net::BGP::Process;
4              
5 3     3   3666 use strict;
  3         7  
  3         99  
6 3     3   15 use vars qw( $VERSION );
  3         6  
  3         146  
7              
8             ## Inheritance and Versioning ##
9              
10             $VERSION = '0.18';
11              
12             ## Module Imports ##
13              
14 3     3   16 use Carp;
  3         6  
  3         199  
15 3     3   1767 use IO::Select;
  3         5008  
  3         155  
16 3     3   675 use IO::Socket;
  3         22177  
  3         20  
17 3     3   2162 use Net::BGP::Peer qw( BGP_PORT TRUE FALSE );
  3         8  
  3         5191  
18              
19             ## Socket Constants ##
20              
21 2     2 0 12 sub LISTEN_QUEUE_SIZE { 5 }
22              
23             ## Public Methods ##
24              
25             sub new
26             {
27 3     3 0 1469 my $class = shift();
28 3         6 my ($arg, $value);
29              
30 3         20 my $this = {
31             _read_fh => IO::Select->new(),
32             _write_fh => IO::Select->new(),
33             _error_fh => IO::Select->new(),
34             _peer_list => {},
35             _peer_addr => {},
36             _trans_sock => {},
37             _trans_sock_fh => {},
38             _trans_sock_map=> {},
39             _listen_socket => undef,
40             _listen_port => BGP_PORT,
41             _listen_addr => INADDR_ANY,
42             };
43              
44 3         17 while ( defined($arg = shift()) ) {
45 2         5 $value = shift();
46 2 50       13 if ( $arg =~ /port/i ) {
    0          
47 2         8 $this->{_listen_port} = $value;
48             }
49             elsif ( $arg =~ /listenaddr/i ) {
50 0         0 $this->{_listen_addr} = inet_aton($value);
51             }
52             else {
53 0         0 croak "Unknown argument '$arg'";
54             }
55             }
56              
57 3         6 bless($this, $class);
58              
59 3         9 return ( $this );
60             }
61              
62             sub add_peer
63             {
64 4     4 0 17 my ($this, $peer) = @_;
65              
66 4 100       10 $this->{_peer_addr}->{$peer->this_id}->{$peer->peer_id} = $peer if $peer->is_listener;;
67 4         12 $this->{_peer_list}->{$peer} = $peer;
68             }
69              
70             sub remove_peer
71             {
72 4     4 0 27 my ($this, $peer) = @_;
73 4 50       16 if ( defined($this->{_peer_list}->{$peer}) ) {
74 4         37 $peer->stop();
75 4         14 foreach my $trans ($peer->transports)
76             {
77 4         13 $this->_update_select($trans);
78             };
79 4         20 delete $this->{_peer_addr}->{$peer->this_id}->{$peer->peer_id};
80 4         19 delete $this->{_peer_list}->{$peer};
81             }
82             }
83              
84             sub event_loop
85             {
86 2     2 0 232 my $this = shift();
87 2         7 my ($time, $last_time, $delta, $min, $min_timer);
88 2         0 my ($timer);
89              
90 2         19 my $sigorig = $SIG{'PIPE'};
91 2 50       10 unless (defined $SIG{'PIPE'}) {
92 2         22 $SIG{'PIPE'} = 'IGNORE';
93             }
94              
95             # Poll each peer and create listen socket if any is a listener
96 2         4 foreach my $peer ( values(%{$this->{_peer_list}}) ) {
  2         9  
97 3 100       10 if ( $peer->is_listener() ) {
98 2         11 $this->_init_listen_socket();
99 2         5 last;
100             }
101             }
102              
103 2         3 while ( scalar(keys(%{$this->{_peer_list}})) ) {
  40         490  
104              
105             # Process timeouts, events, etc.
106 40         67 $min_timer = 2147483647;
107 40         67 $time = time();
108              
109 40 100       158 if ( ! defined($last_time) ) {
110 2         4 $last_time = $time;
111             }
112              
113 40         70 $delta = $time - $last_time;
114 40         60 $last_time = $time;
115              
116 40         70 foreach my $peer ( values(%{$this->{_peer_list}}) ) {
  40         120  
117              
118 76         1727 foreach my $trans ($peer->transports) {
119 76         301 $trans->_handle_pending_events();
120             }
121              
122 76         209 $min = $peer->_update_timers($delta);
123 76 100       167 if ( $min < $min_timer ) {
124 51         71 $min_timer = $min;
125             }
126              
127 76         168 foreach my $trans ($peer->transports)
128             {
129 76         183 $this->_update_select($trans);
130             };
131             }
132              
133 40 100       849 last if scalar(keys(%{$this->{_peer_list}})) == 0;
  40         116  
134              
135 38         96 $! = 0;
136              
137 38         170 my @ready = IO::Select->select($this->{_read_fh}, $this->{_write_fh}, $this->{_error_fh}, $min_timer);
138              
139 38 100       10013933 if ( @ready ) {
140              
141             # dispatch ready to reads
142 28         42 foreach my $ready ( @{$ready[0]} ) {
  28         65  
143 34 100       98 if ( $ready == $this->{_listen_socket} ) {
144 2         13 $this->_handle_accept();
145             }
146             else {
147 32         72 my $trans = $this->{_trans_sock_map}->{$ready};
148 32         98 $trans->_handle_socket_read_ready();
149             }
150             }
151              
152             # dispatch ready to writes
153 28         45 foreach my $ready ( @{$ready[1]} ) {
  28         64  
154 4         10 my $trans = $this->{_trans_sock_map}->{$ready};
155 4         24 $trans->_handle_socket_write_ready();
156             }
157              
158             # dispatch exception conditions
159 28         37 foreach my $ready ( @{$ready[2]} ) {
  28         83  
160 0         0 my $trans = $this->{_trans_sock_map}->{$ready};
161 0         0 $trans->_handle_socket_error_condition();
162             }
163             } else {
164 10 50       223 if ($!{EBADF}) {
165             # One of the sockets is bad
166 0         0 foreach my $fh ( $this->{_error_fh}->handles ) {
167 0 0       0 if (!$fh->opened) {
168 0         0 my $trans = $this->{_trans_sock_map}->{$fh};
169             # We seem to have a transport with a dud socket
170             # Update the select statement - not sure if this
171             # is right though - Damian Ivereigh 29/09/2016
172 0 0       0 if ($trans) {
173 0         0 $this->_update_select($trans);
174             } else {
175 0         0 warn "Cannot find trans object\n";
176             }
177             }
178             }
179             }
180             }
181             }
182              
183 2         11 $this->_cleanup();
184              
185 2         49 delete $SIG{'PIPE'};
186 2 50       16 $SIG{'PIPE'} = $sigorig if defined $sigorig;
187             }
188              
189             ## Private Methods ##
190              
191             sub _add_trans_sock
192             {
193 4     4   10 my ($this, $trans, $sock) = @_;
194              
195 4         9 $this->{_trans_sock}->{$trans} = $sock;
196 4         26 $this->{_trans_sock_fh}->{$trans} = $sock->fileno();
197 4         38 $this->{_trans_sock_map}->{$sock} = $trans;
198             }
199              
200             sub _remove_trans_sock
201             {
202 4     4   10 my ($this, $trans) = @_;
203              
204 4         21 delete $this->{_trans_sock_map}->{$this->{_trans_sock}->{$trans}};
205 4         19 delete $this->{_trans_sock}->{$trans};
206 4         40 delete $this->{_trans_sock_fh}->{$trans};
207             }
208              
209             sub _init_listen_socket
210             {
211 2     2   5 my $this = shift();
212 2         4 my ($socket, $proto, $rv, $sock_addr);
213              
214 2         4 eval {
215 2         11 $socket = IO::Socket->new( Domain => AF_INET );
216 2 50       715 if ( ! defined($socket) ) {
217 0         0 die("IO::Socket construction failed");
218             }
219              
220 2         7 $rv = $socket->blocking(FALSE);
221 2 50       44 if ( ! defined($rv) ) {
222 0         0 die("set socket non-blocking failed");
223             }
224              
225 2         981 $proto = getprotobyname('tcp');
226 2         15 $rv = $socket->socket(PF_INET, SOCK_STREAM, $proto);
227 2 50       137 if ( ! defined($rv) ) {
228 0         0 die("socket() failed");
229             }
230              
231 2         7 $socket->sockopt(SO_REUSEADDR, TRUE);
232              
233             $sock_addr = sockaddr_in($this->{_listen_port},
234 2         58 $this->{_listen_addr});
235 2         27 $rv = $socket->bind($sock_addr);
236 2 50       72 if ( ! defined($rv) ) {
237 0         0 die("bind() failed");
238             }
239              
240 2         6 $rv = $socket->listen(LISTEN_QUEUE_SIZE);
241 2 50       47 if ( ! defined($rv) ) {
242 0         0 die("listen() failed");
243             }
244              
245 2         12 $this->{_read_fh}->add($socket);
246 2         95 $this->{_write_fh}->add($socket);
247 2         89 $this->{_error_fh}->add($socket);
248 2         57 $this->{_listen_socket} = $socket;
249             };
250 2 50       6 croak $@ if $@;
251             }
252              
253             sub _cleanup
254             {
255 2     2   5 my $this = shift();
256 2         4 my $socket;
257              
258 2 50       9 if ( defined($this->{_listen_socket}) ) {
259 2         6 $socket = $this->{_listen_socket};
260 2         20 $this->{_read_fh}->remove($socket);
261 2         81 $this->{_write_fh}->remove($socket);
262 2         63 $this->{_error_fh}->remove($socket);
263              
264 2         69 $socket->close();
265 2         80 $this->{_listen_socket} = undef;
266             }
267             }
268              
269             sub _handle_accept
270             {
271 2     2   6 my $this = shift;
272              
273 2         16 my ($socket, $peer_addr) = $this->{_listen_socket}->accept();
274 2         305 my ($port, $addr) = sockaddr_in($peer_addr);
275            
276 2         31 my $ip_addr = inet_ntoa($addr);
277 2         8 my $ip_local = inet_ntoa($socket->sockaddr);
278              
279 2         76 my $peer = $this->{_peer_addr}->{$ip_local}->{$ip_addr};
280 2 50       10 if ( ! defined($peer)) {
    50          
281 0         0 warn "Ignored incoming connection from unknown peer ($ip_addr => $ip_local)\n";
282 0         0 $socket->close();
283             }
284             elsif ( ! $peer->is_listener() ) {
285 0         0 warn "Ignored incoming connection for non-listning peer\n";
286 0         0 $socket->close();
287             }
288             else {
289 2         6 my $trans = $peer->transport;
290              
291             # Can't reuse the existing Net::BGP::Peer object unless it is a passive session
292 2 50       16 if (! $peer->is_passive() ) {
293              
294             # If there is a sibling, we need to kill it, assuming that there
295             # is a collision here.
296             #
297             # This can happen in a BGP misconfiguration where no OPEN gets
298             # sent by the other end (for instance, the other end is expecting
299             # a smaller bigger TTL, but still listens for BGP and sends SYNACK
300             # packets back - like Mikrotik)
301 0 0       0 if (defined $trans->{_sibling}) {
302 0         0 $trans->{_sibling}->_handle_collision_selfdestuct;
303             }
304              
305             # Now we can clone
306 0         0 $trans = $trans->_clone;
307             }
308              
309 2         8 $trans->_set_socket($socket);
310             }
311             }
312              
313             sub _update_select
314             {
315 80     80   137 my ($this, $trans) = @_;
316              
317 80         240 my $trans_socket = $trans->_get_socket();
318 80         285 my $this_socket = $this->{_trans_sock}->{$trans};
319              
320 80 100 100     597 if ( defined($trans_socket) && ! defined($this_socket) ) {
    100 100        
    100 66        
321 4         14 $this->_add_trans_sock($trans, $trans_socket);
322 4         14 $this->{_read_fh}->add($trans_socket);
323 4         148 $this->{_write_fh}->add($trans_socket);
324 4         125 $this->{_error_fh}->add($trans_socket);
325             }
326             elsif ( defined($this_socket) && ! defined($trans_socket) ) {
327 4         24 $this->{_read_fh}->remove($this->{_trans_sock_fh}->{$trans});
328 4         216 $this->{_write_fh}->remove($this->{_trans_sock_fh}->{$trans});
329 4         119 $this->{_error_fh}->remove($this->{_trans_sock_fh}->{$trans});
330 4         230 $this->_remove_trans_sock($trans);
331             }
332             elsif ( defined($this_socket) && defined($trans_socket) ) {
333 66 100 66     150 if ( $trans->_is_connected() && $this->{_write_fh}->exists($this_socket) ) {
334 4         83 $this->{_write_fh}->remove($this_socket);
335             }
336             }
337             }
338              
339             ## POD ##
340              
341             =pod
342              
343             =head1 NAME
344              
345             C - Class encapsulating BGP session multiplexing functionality
346              
347             =head1 SYNOPSIS
348              
349             use Net::BGP::Process;
350              
351             $bgp = Net::BGP::Process->new( Port => $port );
352              
353             $bgp->add_peer($peer);
354             $bgp->remove_peer($peer);
355             $bgp->event_loop();
356              
357             =head1 DESCRIPTION
358              
359             This module encapsulates the functionality necessary to multiplex multiple
360             BGP peering sessions. While individual L objects contain
361             the state of each peering session, it is the C object
362             which monitors each peer's transport-layer connection and timers and signals
363             the peer whenever messages are available for processing or timers expire.
364             A C object must be instantiated, even if a program only
365             intends to establish a session with a single peer.
366              
367             =head1 METHODS
368              
369             I - create a new C object
370              
371             $bgp = Net::BGP::Process->new( Port => $port, ListenAddr => '1.2.3.4' );
372              
373             This is the constructor for C objects. It returns a
374             reference to the newly created object. The following named parameters may
375             be passed to the constructor.
376              
377             =head2 Port
378              
379             This parameter sets the TCP port the BGP process listens on. It may be
380             omitted, in which case it defaults to the well-known BGP port TCP/179.
381             If the program cannot run with root priviliges, it is necessary to set
382             this parameter to a value greater than or equal to 1024. Note that some
383             BGP implementations may not allow the specification of an alternate port
384             and may be unable to establish a connection to the C.
385              
386             =head2 ListenAddr
387              
388             This parameter sets the IP address the BGP process listens on. Defaults
389             to INADDR_ANY.
390              
391             I - add a new peer to the BGP process
392              
393             $bgp->add_peer($peer);
394              
395             Each L object, which corresponds to a distinct peering
396             session, must be registered with the C object via this
397             method. It is typically called immediately after a new peer object is created
398             to add the peer to the BGP process. The method accepts a single parameter,
399             which is a reference to a L object.
400              
401             I - remove a peer from the BGP process
402              
403             $bgp->remove_peer($peer);
404              
405             This method should be called if a peer should no longer be managed by the
406             BGP process, for example, if the session is broken or closed and will not
407             be re-established. The method accepts a single parameter, which is a
408             reference to a L object which has previously been registered
409             with the process object with the add_peer() method.
410              
411             I - start the process event loop
412              
413             $bgp->event_loop();
414              
415             This method must called after all peers are instantiated and added to the
416             BGP process and any other necessary initialization has occured. Once it
417             is called, it takes over program control flow, and control will
418             only return to user code when one of the event callback functions is
419             invoked upon receipt of a BGP protocol message or a user
420             established timer expires (see L for details
421             on how to establish timers and callback functions). The method takes
422             no parameters. It will only return when there are no L
423             objects remaining under its management, which can only occur if they
424             are explicitly removed with the remove_peer() method (perhaps called
425             in one of the callback or timer functions).
426              
427             =head1 SEE ALSO
428              
429             =over
430              
431             =item L
432              
433             =item L
434              
435             =item L
436              
437             =item L
438              
439             =item L
440              
441             =item L
442              
443             =item L
444              
445             =back
446              
447             =head1 AUTHOR
448              
449             Stephen J. Scheck
450              
451             =cut
452              
453             ## End Package Net::BGP::Process ##
454              
455             1;