File Coverage

blib/lib/Net/Magallanes.pm
Criterion Covered Total %
statement 20 184 10.8
branch 0 82 0.0
condition 0 21 0.0
subroutine 7 14 50.0
pod 5 7 71.4
total 32 308 10.3


line stmt bran cond sub pod time code
1             package Net::Magallanes;
2              
3 1     1   90345 use strict;
  1         2  
  1         28  
4 1     1   18 use 5.008_005;
  1         2  
5             our $VERSION = '0.04';
6              
7 1     1   751 use LWP::UserAgent;
  1         46740  
  1         33  
8 1     1   1801 use JSON;
  1         9965  
  1         6  
9 1     1   740 use MIME::Base64;
  1         891  
  1         83  
10 1     1   655 use Net::DNS;
  1         155007  
  1         151  
11 1     1   9 use Carp;
  1         2  
  1         1488  
12              
13             sub new {
14 0     0 1   my $this = shift;
15 0           my %params = @_;
16              
17 0           my ($API_KEY, $IN_FILES);
18 0           my $API_BASE = 'https://atlas.ripe.net/api/v2';
19              
20 0   0       my $class = ref($this) || $this;
21              
22 0 0         $API_KEY = $params{'KEY'} if $params{'KEY'};
23 0 0         $IN_FILES = $params{'INFILES'} if $params{'INFILES'};
24              
25             # armar estructura con defaults sensibles
26 0           my $self = {};
27 0           bless $self, $class;
28              
29             # Si no hay KEY igual le damos, pero no podremos crear cosas, solo
30             # consultar.
31 0           $self->{'KEY'} = $API_KEY;
32 0           $self->{'ua'} = LWP::UserAgent->new(timeout => 10);
33 0           $self->{'ua'}->default_header('Content-Type' => 'application/json');
34 0           $self->{'ua'}->default_header('Accept' => 'application/json');
35 0           $self->{'URL'} = $API_BASE;
36              
37 0           $self->{'_CACHE_MSM'} = {};
38              
39 0 0         if ($IN_FILES) {
40 0           my @files = split ',', $IN_FILES;
41 0           my $data;
42 0           foreach my $file (@files) {
43 0 0         open my $fh, '<', $file
44             or croak "Couldn't open file $file: $!";
45 0           local $/ = undef;
46 0           $data = <$fh>;
47 0           close $fh;
48 0           my $result = decode_json $data;
49 0           my $mi = $result->[0]->{msm_id};
50 0           $self->{'_CACHE_MSM'}->{$mi} = $result;
51             }
52             }
53              
54             # Qué puede venir:
55             # timeouts de https request
56             # versión de API
57             # defaults comunes a todo:
58             # - one_off (default true)
59              
60 0           return $self;
61             }
62              
63             sub results {
64 0     0 0   my $self = shift;
65 0           my $msm_id = shift;
66              
67 0           my $result;
68              
69 0 0 0       croak("You must provide the measurement identificator msm_id (only digits)")
70             unless defined $msm_id and $msm_id =~ /^\d+$/;
71              
72             return $self->{'_CACHE_MSM'}->{$msm_id}
73 0 0         if defined $self->{'_CACHE_MSM'}->{$msm_id};
74              
75 0           my $res = $self->{'ua'}->get( $self->{'URL'} .
76             "/measurements/$msm_id/results/" .
77             '?format=json'
78             );
79              
80 0           $self->{'_JSON'} = $res->decoded_content;
81              
82 0 0         if ($res->is_success) {
83 0           $result = decode_json $res->decoded_content;
84             }
85             else {
86 0           $result = 'ERROR: ' . $res->status_line;
87             }
88              
89 0           $self->{'_CACHE_MSM'}->{$msm_id} = $result;
90              
91 0           return $result;
92             }
93              
94             sub json {
95 0     0 0   my $self = shift;
96              
97 0           return $self->{'_JSON'};
98             }
99              
100             sub answers {
101 0     0 1   my $self = shift;
102 0           my $msm_id = shift;
103 0           my $args = shift;
104              
105 0           my %args;
106 0 0         %args = %{$args} if defined $args;
  0            
107              
108 0 0         my $type = (defined($args{'TYPE'}) ? $args{'TYPE'} : 'A');
109 0 0         my $istime = (defined($args{'TIMESTAMP'}) ? $args{'TIMESTAMP'} : 0);
110              
111 0           my $result = results($self, $msm_id);
112              
113 0           my @sal;
114 0           foreach my $resdo (@{$result}) {
  0            
115 0           my $timestamp = $resdo->{'timestamp'};
116 0 0         if ($resdo->{'type'} eq 'dns') {
117 0           my $res_set = $resdo->{'resultset'};
118 0 0         if ($#{$res_set} < 0) {
  0            
119 0           push @{$res_set}, $resdo;
  0            
120             }
121 0           foreach my $dns (@$res_set) {
122 0           my $abuf = $dns->{'result'}->{'abuf'};
123 0 0         next unless $abuf;
124 0           my $dec_buff = decode_base64 $abuf;
125 0 0 0       if(defined $abuf && defined $dec_buff) {
126 0           my ($dns_pack)= new Net::DNS::Packet(\$dec_buff);
127 0           my @ans = $dns_pack->answer;
128 0           foreach my $ans (@ans) {
129 0 0         next unless $ans->type eq $type;
130 0           my $res_ip;
131 0 0         if ($type eq 'A') {
    0          
132 0           $res_ip = $ans->address;
133             }
134             elsif ($type eq 'AAAA') {
135 0           $res_ip = $ans->address_short;
136             }
137             else {
138 0           $res_ip = $ans->string;
139             }
140 0 0         if ($res_ip) {
141 0 0         if ($istime) {
142 0           my @val = ($timestamp, $res_ip);
143 0           push @sal, \@val;
144             }
145             else {
146 0           push @sal, $res_ip;
147             }
148             }
149             }
150             }
151             }
152             }
153             }
154 0           return @sal;
155             }
156              
157             sub nsids {
158 0     0 1   my $self = shift;
159 0           my $msm_id = shift;
160              
161 0           my $result = results($self, $msm_id);
162              
163 0           my @sal;
164 0           foreach my $resdo (@{$result}) {
  0            
165 0 0         if ($resdo->{'type'} eq 'dns') {
166 0           my $res_set = $resdo->{'resultset'};
167 0 0         if ($#{$res_set} < 0) {
  0            
168 0           push @{$res_set}, $resdo;
  0            
169             }
170 0           foreach my $dns (@$res_set) {
171 0           my $abuf = $dns->{'result'}->{'abuf'};
172 0 0         next unless $abuf;
173 0           my $dec_buff = decode_base64 $abuf;
174 0 0 0       if(defined $abuf && defined $dec_buff) {
175 0           my ($dns_pack)= new Net::DNS::Packet(\$dec_buff);
176 0           my @edns = $dns_pack->edns;
177 0           foreach my $edn (@edns) {
178 0           my $res_ip = $edn->option(3);
179 0 0         push @sal, ($res_ip ? $res_ip : 'NULL');
180             }
181             }
182             }
183             }
184             }
185 0           return @sal;
186             }
187              
188             sub rcodes {
189 0     0 1   my $self = shift;
190 0           my $msm_id = shift;
191 0           my $args = shift;
192              
193 0           my %args;
194 0 0         %args = %{$args} if defined $args;
  0            
195              
196 0 0         my $istime = (defined($args{'TIMESTAMP'}) ? $args{'TIMESTAMP'} : 0);
197 0 0         my $isprbi = (defined($args{'PROBEID'}) ? $args{'PROBEID'} : 0);
198              
199 0           my $result = results($self, $msm_id);
200              
201 0           my @sal;
202 0           foreach my $resdo (@{$result}) {
  0            
203 0           my $timestamp = $resdo->{'timestamp'};
204 0           my $probe_id = $resdo->{'prb_id'};
205 0 0         if ($resdo->{'type'} eq 'dns') {
206 0           my $res_set = $resdo->{'resultset'};
207 0 0         if ($#{$res_set} < 0) {
  0            
208 0           push @{$res_set}, $resdo;
  0            
209             }
210 0           foreach my $dns (@$res_set) {
211 0           my $abuf = $dns->{'result'}->{'abuf'};
212 0 0         next unless $abuf;
213 0           my $dec_buff = decode_base64 $abuf;
214 0 0 0       if(defined $abuf && defined $dec_buff) {
215 0           my ($dns_pack)= new Net::DNS::Packet(\$dec_buff);
216 0           my $header = $dns_pack->header;
217 0 0 0       if ($istime or $isprbi) {
218 0           my @val;
219 0 0         push @val, $timestamp if $istime;
220 0 0         push @val, $probe_id if $isprbi;
221 0           push @val, $header->rcode;
222 0           push @sal, \@val;
223             }
224             else {
225 0           push @sal, $header->rcode;
226             }
227             }
228             }
229             }
230             }
231 0           return @sal;
232             }
233              
234             sub dns {
235 0     0 1   my $self = shift;
236 0           my %params = @_;
237              
238             croak("You must provide at least the query name")
239 0 0         unless defined $params{'name'};
240             croak('You must provide an API key (KEY constructor param) to create measurements')
241 0 0 0       unless defined $self->{'KEY'} and $self->{'KEY'};
242              
243 0 0         my $qtype = defined($params{'type'}) ? $params{'type'} : 'AAAA';
244 0 0         my $nprb = defined($params{'num_prb'}) ? $params{'num_prb'} : 5;
245              
246 0           my %DEFS = (
247             description => 'DNS measurement to ',
248             type => 'dns',
249             query_class => 'IN',
250             timeout => 5000,
251             retry => 0,
252             af => 4,
253             use_macros => 'false',
254             use_probe_resolver => 'true',
255             resolve_on_probe => 'false',
256             set_nsid_bit => 'true',
257             protocol => 'UDP',
258             udp_payload_size => 1232,
259             skip_dns_check => 'false',
260             include_qbuf => 'false',
261             include_abuf => 'true',
262             prepend_probe_id => 'false',
263             set_rd_bit => 'false',
264             set_do_bit => 'true',
265             set_cd_bit => 'false',
266             # start_time
267             # stop_time
268             # interval
269             # target
270             );
271              
272 0           my %PROBES = (
273             type => 'area',
274             value => 'WW',
275             # tags_include => 'system-ipv4-works,system-can-resolve-a',
276             tags_include => 'system-ipv4-works',
277             );
278              
279 0           $PROBES{'requested'} = $nprb;
280              
281 0           $DEFS{'query_argument'} = $params{'name'};
282 0           $DEFS{'query_type'} = $qtype;
283 0           $DEFS{'description'} .= $params{'name'};
284              
285 0           my %ATLASCALL;
286 0           push @{$ATLASCALL{'definitions'}}, \%DEFS;
  0            
287 0           push @{$ATLASCALL{'probes'}}, \%PROBES;
  0            
288              
289 0           $ATLASCALL{'is_oneoff'} = 'true';
290              
291 0           my $json = encode_json \%ATLASCALL;
292              
293             my $res = $self->{'ua'}->post( $self->{'URL'} .
294             '/measurements/',
295 0           'Authorization' => 'Key ' . $self->{'KEY'},
296             Content => $json
297             );
298              
299 0 0         if ($res->is_success) {
300 0           my $msmout = $res->decoded_content;
301 0 0         my $msm = $1 if $res->decoded_content =~ /{"measurements":\[(\d+)\]}/;
302              
303 0 0         croak 'Bad measurement id, please check: ' . $res->decoded_content unless $msm;
304              
305 0           return $msm;
306             }
307             else {
308 0           croak 'Could not create a measurement: ' . $res->status_line;
309             }
310             }
311              
312             1;
313              
314             __END__