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   3222 use strict;
  3         7  
  3         97  
6 3     3   19 use vars qw( $VERSION );
  3         6  
  3         148  
7              
8             ## Inheritance and Versioning ##
9              
10             $VERSION = '0.17';
11              
12             ## Module Imports ##
13              
14 3     3   16 use Carp;
  3         7  
  3         196  
15 3     3   1401 use IO::Select;
  3         4812  
  3         147  
16 3     3   484 use IO::Socket;
  3         19156  
  3         17  
17 3     3   2068 use Net::BGP::Peer qw( BGP_PORT TRUE FALSE );
  3         10  
  3         5215  
18              
19             ## Socket Constants ##
20              
21 2     2 0 13 sub LISTEN_QUEUE_SIZE { 5 }
22              
23             ## Public Methods ##
24              
25             sub new
26             {
27 3     3 0 1304 my $class = shift();
28 3         7 my ($arg, $value);
29              
30 3         16 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         3 $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         5 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       17 $this->{_peer_addr}->{$peer->this_id}->{$peer->peer_id} = $peer if $peer->is_listener;;
67 4         18 $this->{_peer_list}->{$peer} = $peer;
68             }
69              
70             sub remove_peer
71             {
72 4     4 0 44 my ($this, $peer) = @_;
73 4 50       24 if ( defined($this->{_peer_list}->{$peer}) ) {
74 4         20 $peer->stop();
75 4         13 foreach my $trans ($peer->transports)
76             {
77 4         10 $this->_update_select($trans);
78             };
79 4         20 delete $this->{_peer_addr}->{$peer->this_id}->{$peer->peer_id};
80 4         21 delete $this->{_peer_list}->{$peer};
81             }
82             }
83              
84             sub event_loop
85             {
86 2     2 0 228 my $this = shift();
87 2         9 my ($time, $last_time, $delta, $min, $min_timer);
88 2         0 my ($timer);
89              
90 2         12 my $sigorig = $SIG{'PIPE'};
91 2 50       9 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         5 foreach my $peer ( values(%{$this->{_peer_list}}) ) {
  2         15  
97 3 100       10 if ( $peer->is_listener() ) {
98 2         15 $this->_init_listen_socket();
99 2         4 last;
100             }
101             }
102              
103 2         4 while ( scalar(keys(%{$this->{_peer_list}})) ) {
  40         444  
104              
105             # Process timeouts, events, etc.
106 40         78 $min_timer = 2147483647;
107 40         69 $time = time();
108              
109 40 100       103 if ( ! defined($last_time) ) {
110 2         3 $last_time = $time;
111             }
112              
113 40         73 $delta = $time - $last_time;
114 40         64 $last_time = $time;
115              
116 40         62 foreach my $peer ( values(%{$this->{_peer_list}}) ) {
  40         121  
117              
118 76         1177 foreach my $trans ($peer->transports) {
119 76         203 $trans->_handle_pending_events();
120             }
121              
122 76         245 $min = $peer->_update_timers($delta);
123 76 100       168 if ( $min < $min_timer ) {
124 51         80 $min_timer = $min;
125             }
126              
127 76         170 foreach my $trans ($peer->transports)
128             {
129 76         182 $this->_update_select($trans);
130             };
131             }
132              
133 40 100       766 last if scalar(keys(%{$this->{_peer_list}})) == 0;
  40         112  
134              
135 38         92 $! = 0;
136              
137 38         191 my @ready = IO::Select->select($this->{_read_fh}, $this->{_write_fh}, $this->{_error_fh}, $min_timer);
138              
139 38 100       10049765 if ( @ready ) {
140              
141             # dispatch ready to reads
142 28         45 foreach my $ready ( @{$ready[0]} ) {
  28         68  
143 34 100       106 if ( $ready == $this->{_listen_socket} ) {
144 2         7 $this->_handle_accept();
145             }
146             else {
147 32         76 my $trans = $this->{_trans_sock_map}->{$ready};
148 32         103 $trans->_handle_socket_read_ready();
149             }
150             }
151              
152             # dispatch ready to writes
153 28         53 foreach my $ready ( @{$ready[1]} ) {
  28         69  
154 4         11 my $trans = $this->{_trans_sock_map}->{$ready};
155 4         14 $trans->_handle_socket_write_ready();
156             }
157              
158             # dispatch exception conditions
159 28         60 foreach my $ready ( @{$ready[2]} ) {
  28         81  
160 0         0 my $trans = $this->{_trans_sock_map}->{$ready};
161 0         0 $trans->_handle_socket_error_condition();
162             }
163             } else {
164 10 50       210 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         21 $this->_cleanup();
184              
185 2         41 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   8 my ($this, $trans, $sock) = @_;
194              
195 4         28 $this->{_trans_sock}->{$trans} = $sock;
196 4         38 $this->{_trans_sock_fh}->{$trans} = $sock->fileno();
197 4         35 $this->{_trans_sock_map}->{$sock} = $trans;
198             }
199              
200             sub _remove_trans_sock
201             {
202 4     4   9 my ($this, $trans) = @_;
203              
204 4         21 delete $this->{_trans_sock_map}->{$this->{_trans_sock}->{$trans}};
205 4         39 delete $this->{_trans_sock}->{$trans};
206 4         57 delete $this->{_trans_sock_fh}->{$trans};
207             }
208              
209             sub _init_listen_socket
210             {
211 2     2   6 my $this = shift();
212 2         4 my ($socket, $proto, $rv, $sock_addr);
213              
214 2         3 eval {
215 2         11 $socket = IO::Socket->new( Domain => AF_INET );
216 2 50       668 if ( ! defined($socket) ) {
217 0         0 die("IO::Socket construction failed");
218             }
219              
220 2         7 $rv = $socket->blocking(FALSE);
221 2 50       51 if ( ! defined($rv) ) {
222 0         0 die("set socket non-blocking failed");
223             }
224              
225 2         955 $proto = getprotobyname('tcp');
226 2         16 $rv = $socket->socket(PF_INET, SOCK_STREAM, $proto);
227 2 50       122 if ( ! defined($rv) ) {
228 0         0 die("socket() failed");
229             }
230              
231 2         8 $socket->sockopt(SO_REUSEADDR, TRUE);
232              
233             $sock_addr = sockaddr_in($this->{_listen_port},
234 2         59 $this->{_listen_addr});
235 2         29 $rv = $socket->bind($sock_addr);
236 2 50       68 if ( ! defined($rv) ) {
237 0         0 die("bind() failed");
238             }
239              
240 2         6 $rv = $socket->listen(LISTEN_QUEUE_SIZE);
241 2 50       46 if ( ! defined($rv) ) {
242 0         0 die("listen() failed");
243             }
244              
245 2         9 $this->{_read_fh}->add($socket);
246 2         98 $this->{_write_fh}->add($socket);
247 2         66 $this->{_error_fh}->add($socket);
248 2         62 $this->{_listen_socket} = $socket;
249             };
250 2 50       7 croak $@ if $@;
251             }
252              
253             sub _cleanup
254             {
255 2     2   4 my $this = shift();
256 2         6 my $socket;
257              
258 2 50       7 if ( defined($this->{_listen_socket}) ) {
259 2         6 $socket = $this->{_listen_socket};
260 2         7 $this->{_read_fh}->remove($socket);
261 2         82 $this->{_write_fh}->remove($socket);
262 2         69 $this->{_error_fh}->remove($socket);
263              
264 2         70 $socket->close();
265 2         126 $this->{_listen_socket} = undef;
266             }
267             }
268              
269             sub _handle_accept
270             {
271 2     2   5 my $this = shift;
272              
273 2         23 my ($socket, $peer_addr) = $this->{_listen_socket}->accept();
274 2         311 my ($port, $addr) = sockaddr_in($peer_addr);
275            
276 2         31 my $ip_addr = inet_ntoa($addr);
277 2         11 my $ip_local = inet_ntoa($socket->sockaddr);
278              
279 2         104 my $peer = $this->{_peer_addr}->{$ip_local}->{$ip_addr};
280 2 50       12 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       6 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         10 $trans->_set_socket($socket);
310             }
311             }
312              
313             sub _update_select
314             {
315 80     80   156 my ($this, $trans) = @_;
316              
317 80         186 my $trans_socket = $trans->_get_socket();
318 80         215 my $this_socket = $this->{_trans_sock}->{$trans};
319              
320 80 100 100     635 if ( defined($trans_socket) && ! defined($this_socket) ) {
    100 100        
    100 66        
321 4         12 $this->_add_trans_sock($trans, $trans_socket);
322 4         14 $this->{_read_fh}->add($trans_socket);
323 4         157 $this->{_write_fh}->add($trans_socket);
324 4         126 $this->{_error_fh}->add($trans_socket);
325             }
326             elsif ( defined($this_socket) && ! defined($trans_socket) ) {
327 4         22 $this->{_read_fh}->remove($this->{_trans_sock_fh}->{$trans});
328 4         187 $this->{_write_fh}->remove($this->{_trans_sock_fh}->{$trans});
329 4         123 $this->{_error_fh}->remove($this->{_trans_sock_fh}->{$trans});
330 4         148 $this->_remove_trans_sock($trans);
331             }
332             elsif ( defined($this_socket) && defined($trans_socket) ) {
333 66 100 66     160 if ( $trans->_is_connected() && $this->{_write_fh}->exists($this_socket) ) {
334 4         86 $this->{_write_fh}->remove($this_socket);
335             }
336             }
337             }
338              
339             ## POD ##
340              
341             =pod
342              
343             =head1 NAME
344              
345             Net::BGP::Process - 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 B objects contain
361             the state of each peering session, it is the B 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 B 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 Net::BGP::Process object
370              
371             $bgp = Net::BGP::Process->new( Port => $port, ListenAddr => '1.2.3.4' );
372              
373             This is the constructor for Net::BGP::Process 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 B.
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 B object, which corresponds to a distinct peering
396             session, must be registered with the B 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 B 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 Net::BGP::Peer 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 Net::BGP::Peer
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             Net::BGP, Net::BGP::Peer, Net::BGP::Update, Net::BGP::Notification
430              
431             =head1 AUTHOR
432              
433             Stephen J. Scheck
434              
435             =cut
436              
437             ## End Package Net::BGP::Process ##
438              
439             1;