File Coverage

blib/lib/Device/Network/ConfigParser/Cisco/ASA.pm
Criterion Covered Total %
statement 29 65 44.6
branch 0 8 0.0
condition n/a
subroutine 11 17 64.7
pod 6 6 100.0
total 46 96 47.9


line stmt bran cond sub pod time code
1             package Device::Network::ConfigParser::Cisco::ASA;
2             # ABSTRACT: Parse Cisco ASA Configuration
3              
4 1     1   2596 use 5.006;
  1         4  
5 1     1   6 use strict;
  1         3  
  1         22  
6 1     1   6 use warnings;
  1         3  
  1         29  
7 1     1   306 use Modern::Perl;
  1         9041  
  1         8  
8 1     1   1270 use Parse::RecDescent;
  1         30464  
  1         8  
9 1     1   555 use Data::Dumper;
  1         6904  
  1         77  
10 1     1   441 use JSON;
  1         7561  
  1         7  
11              
12 1     1   166 use Exporter qw{import};
  1         3  
  1         866  
13              
14             our @EXPORT_OK = qw{get_parser get_output_drivers parse_config post_process};
15              
16              
17             sub get_parser {
18 1     1 1 615 return new Parse::RecDescent(q{
19            
20             startrule: config(s) { $item[1] }
21             config:
22             hostname { $item[1] } |
23             domain_name { $item[1] } |
24             name { $item[1] } |
25             route { $item[1] } |
26             acl { $item[1] } |
27             nat { $item[1] } |
28             object { $item[1] } |
29             object_group { $item[1] } |
30             not_config { $item[1] }
31              
32             hostname: 'hostname' m{\w+} { { type => $item{__RULE__}, config => $item{__PATTERN1__} } }
33             domain_name: 'domain-name' m{\S+} {
34             { type => $item{__RULE__}, config => $item{__PATTERN1__} }
35             }
36              
37             name: 'name' ipv4 name_alias {
38             { type => $item{__RULE__}, config => { ip => $item{ipv4}, alias => $item{name_alias} } }
39             }
40              
41             route: 'route' interface network netmask next_hop metric track(?) {
42             { type => $item{__RULE__}, config => {
43             interface => $item{interface},
44             destination => $item{network}."/".$item{netmask},
45             next_hop => $item{next_hop},
46             metric => $item{metric},
47             track_id => @{ $item{'track(?)'} } ? $item{'track(?)'}->[0] : '',
48             }
49             }
50             }
51             network: alias_or_ipv4 { $item[1] }
52             next_hop: alias_or_ipv4 { $item[1] }
53             metric: m{\d{1,3}} { $item{__PATTERN1__} }
54             track: 'track' m{\d{1,3}} { $item{__PATTERN1__} }
55              
56             acl: 'access-list' m{\N+} { { type => 'acl', slurp => $item{__PATTERN1__} } }
57              
58             nat: 'nat' int_interface ext_interface source_nat destination_nat(?) proxy_arp(?) route_lookup(?) nat_description(?) {
59             #use Data::Dumper;
60             #print STDERR Dumper \%item;
61             { type => $item{__RULE__}, config => {
62             int_interface => $item{int_interface},
63             ext_interface => $item{ext_interface},
64             source_nat => $item{source_nat},
65             destination_nat => @{ $item{'destination_nat(?)'} } ? $item{'destination_nat(?)'}->[0] : {},
66             description => @{ $item{'nat_description(?)'} } ? $item{'nat_description(?)'}->[0] : '',
67             proxy_arp => @{ $item{'proxy_arp(?)'} } ? 0 : 1,
68             route_lookup => scalar(@{ $item{'route_lookup(?)'} }),
69             }
70             }
71             }
72             source_nat: 'source' m{static|dynamic} real_src mapped_src {
73             { type => $item{__PATTERN1__}, real_src => $item{real_src}, mapped_src => $item{mapped_src}, }
74             }
75             destination_nat: 'destination' m{static|dynamic} mapped_dst real_dst {
76             { type => $item{__PATTERN1__}, mapped_dst => $item{mapped_dst}, real_dst => $item{real_dst}, }
77             }
78             int_interface: '(' m{\w+} ',' { $item{__PATTERN1__} }
79             ext_interface: m{\w+} ')' { $item{__PATTERN1__} }
80             real_src: 'any' { $item[1] } | object_name { $item[1] }
81             mapped_src: 'any' { $item[1] } | 'interface' { $item[1] } | object_name { $item[1] }
82             mapped_dst: 'any' { $item[1] } | object_name { $item[1] }
83             real_dst: 'any' { $item[1] } | object_name { $item[1] }
84             nat_description: 'description' m{[ -~]+} { $item{__PATTERN1__} }
85             proxy_arp: 'no-proxy-arp' { 1; }
86             route_lookup: 'route-lookup' { 1; }
87              
88             object: 'object' m{network|service} object_name (0..2) {
89             #use Data::Dumper;
90             #print Dumper \%item;
91             { type => 'object', config => {
92             name => $item{object_name},
93             obj_type => $item{__PATTERN1__},
94             obj_value => $item{'$item{__PATTERN1__}_obj_body(0..2)'}
95             }
96             }
97             }
98              
99             network_obj_body: 'host' ipv4 { { type => 'host', ip => $item{ipv4} } } |
100             'range' range { { type => 'range', range_start => $item{range}->[0], range_end => $item{range}->[1] } } |
101             'subnet' subnet { { type => 'subnet', network => $item{subnet}->[0], netmask => $item{subnet}->[1] } } |
102             'fqdn' fqdn { { type => 'fqdn', fqdn => $item{fqdn} } } |
103             description { { type => 'description', description => $item{description} } }
104              
105             service_obj_body: 'service' protocol description(?) { $item{protocol} } |
106              
107             protocol: m{\d{1,3}} { protocol => $item{__PATTERN1__} } |
108             tcp_udp { $item[1] } |
109             m{ah|eigrp|esp|gre|igmp|igrp|ip|ipinip|ipsec|nos|ospf|pcp|pim|pptp|sctp|snp} { { protocol => $item{__PATTERN1__} } }
110             description { type => 'description', description => $item{description} }
111            
112             tcp_udp: m{tcp|udp} 'destination' m{eq|gt|lt|neq|range} m{[\w-]+} {
113             { protocol => $item{__PATTERN1__}, destination => { op => $item{__PATTERN2__}, port => $item{__PATTERN3__} } }
114             }
115              
116              
117              
118              
119             object_group: 'object-group' m{network|service|protocol} object_name m{(tcp|tcp-udp|udp)?} (s?) {
120             #use Data::Dumper;
121             #print Dumper \%item;
122             {
123             type => $item{__RULE__},
124             name => $item{object_name},
125             obj_grp_type => $item{__PATTERN1__},
126             obj_grp_value => $item{'$item{__PATTERN1__}_obj_grp_body(s?)'},
127             }
128             }
129            
130             network_obj_grp_body: 'group-object' object_name { { type => 'group-object', group => $item{object_name} } } |
131             'network-object host' ipv4 { { type => 'host', host => $item{ipv4} } } |
132             'network-object' subnet { { type => 'subnet', network => $item{subnet}->[0], netmask => $item{subnet}->[1] } } |
133             description { { type => $item{__RULE__}, description => $item{description} } }
134              
135              
136             service_obj_grp_body: port_object { $item{port_object} } |
137             group_object { $item{group_object} } |
138             service_object { $item{service_object} } |
139             description { { description => $item{description} } }
140              
141             group_object: 'group-object' object_name { { type => 'group', object => $item{object_name} } }
142             port_object: 'port-object' m{eq|range} m{\N+} {
143             {
144             type => 'port',
145             op => $item{__PATTERN1__},
146             value => $item{__PATTERN2__},
147             }
148             }
149             service_object: 'service-object' m{\N+} { { type => 'service', slurp => $item{__PATTERN1__} } }
150            
151              
152             protocol_obj_grp_body: 'protocol' m{\N+} { { slurp => $item{__PATTERN1__} } }
153              
154            
155            
156            
157            
158            
159              
160              
161             # Utility definitions, used in many placed
162             ipv4: m{\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}} { $item{__PATTERN1__} }
163             netmask: m{\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}} { $item{__PATTERN1__} }
164             fqdn: m{[\-\w\.]+} { $item[1] }
165             range: ipv4 ipv4 { [$item[1], $item[2]] }
166             subnet: ipv4 netmask { [$item[1], $item[2]] }
167              
168             description: 'description' m{[ -~]+} { $item{__PATTERN1__} }
169              
170             name_alias: m{[!-~]+} { $item{__PATTERN1__} }
171             object_name: m{[!-~]+} { $item{__PATTERN1__} }
172              
173             interface: m{[!-~]+} { $item{__PATTERN1__} }
174              
175             alias_or_ipv4: name_alias { $item[1] } | ipv4 { $item[1] }
176              
177             not_config: m{\N+}i {
178             { type => $item[0], config => $item[1] }
179             }
180             });
181             }
182              
183              
184             sub parse_config {
185 8     8 1 242929 my ($parser, $config_contents) = @_;
186              
187             #$::RD_TRACE = 1;
188             #$::RD_HINT = 1;
189              
190 8         63 my $parse_tree = $parser->startrule($config_contents);
191              
192 8         43701 return $parse_tree;
193             }
194              
195              
196              
197             sub get_output_drivers {
198             return {
199 0     0 1 0 csv => \&csv_output_driver,
200             json => \&json_output_driver,
201             };
202             }
203              
204              
205             sub post_process {
206 8     8 1 33 my ($parsed_config) = @_;
207              
208 8         13 return $parsed_config;
209             }
210              
211              
212              
213             sub csv_output_driver {
214 0     0 1   my ($fh, $filename, $parsed_config) = @_;
215 0           my $csv_type_driver = {
216             interface => \&_csv_interface_driver,
217             static_route => \&_csv_static_route_driver,
218             not_config => \&_csv_not_config_driver,
219             };
220              
221 0           say "=" x 16 . "BEGIN FILE $filename" . "=" x 16;
222              
223             TYPE:
224 0           for my $type (keys %{ $parsed_config }) {
  0            
225 0           say "-" x 8 . "BEGIN TYPE $type" . "-" x 8;
226              
227             defined $csv_type_driver->{$type} ?
228 0 0         $csv_type_driver->{$type}->($fh, $parsed_config->{$type}) :
    0          
229             warn "No CSV output driver for $type\n" and next TYPE;
230              
231 0           say "-" x 8 . "END TYPE $type" . "-" x 8;
232             }
233              
234 0           say "-" x 8 . "END FILE $filename" . "-" x 8;
235             }
236              
237             sub _csv_interface_driver {
238 0     0     my ($fh, $interfaces_ref) = @_;
239              
240             # Print the CSV schema line
241 0           my @interface_properties = qw{name state vlan ipv4_address ipv4_mask auto_negotiation link_speed mtu comment};
242 0           say $fh join(',', @interface_properties);
243              
244             # Interface through the interfaces, extract and print their properties
245 0           for my $interface (@{ $interfaces_ref }) {
  0            
246 0           my @properties = @{ $interface }{ @interface_properties };
  0            
247              
248             # Replace any undef with an empty string
249 0 0         @properties = map { defined $_ ? $_ : '' } @properties;
  0            
250 0           say $fh join(',', @properties);
251             }
252             }
253              
254              
255             sub _csv_static_route_driver {
256 0     0     my ($fh, $static_routes_ref) = @_;
257              
258 0           my @static_route_properties = qw{destination nexthop nexthop_type status};
259 0           say $fh join(',', @static_route_properties);
260              
261 0           for my $route (@{ $static_routes_ref }) {
  0            
262 0           my @properties = @{ $route }{ @static_route_properties };
  0            
263              
264             # Replace any undef with an empty string
265 0 0         @properties = map { defined $_ ? $_ : '' } @properties;
  0            
266 0           say $fh join(',', @properties);
267             }
268             }
269              
270              
271             sub _csv_not_config_driver {
272 0     0     my ($fh, $not_config) = @_;
273              
274 0           for my $config_line (@{ $not_config }) {
  0            
275 0           print $fh "$config_line\n";
276             }
277             }
278              
279              
280              
281              
282              
283              
284             sub json_output_driver {
285 0     0 1   my ($fh, $filename, $parsed_config) = @_;
286              
287 0           print encode_json($parsed_config);
288             }
289              
290              
291             1; # End of Device::CheckPoint::ConfigParse
292              
293             __END__