File Coverage

blib/lib/Net/BGP/Process.pm
Criterion Covered Total %
statement 147 164 89.6
branch 35 54 64.8
condition 10 12 83.3
subroutine 17 17 100.0
pod 0 5 0.0
total 209 252 82.9


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