perl logo Perl logo (Thanks to Olaf Alders)

The weekly challenge 363 - Task 2: Subnet Sheriff

  1 #!/usr/bin/env perl
  2 # https://theweeklychallenge.org/blog/perl-weekly-challenge-363/#TASK2
  3 #
  4 # Task 2: Subnet Sheriff
  5 # ======================
  6 #
  7 # You are given an IPv4 address and an IPv4 network (in CIDR format).
  8 #
  9 # Write a script to determine whether both are valid and the address falls
 10 # within the network. For more information see the Wikipedia article.
 11 #
 12 ## Example 1
 13 ##
 14 ## Input: $ip_addr = "192.168.1.45"
 15 ##        $domain  = "192.168.1.0/24"
 16 ## Output: true
 17 #
 18 #
 19 ## Example 2
 20 ##
 21 ## Input: $ip_addr = "10.0.0.256"
 22 ##        $domain  = "10.0.0.0/24"
 23 ## Output: false
 24 #
 25 #
 26 ## Example 3
 27 ##
 28 ## Input: $ip_addr = "172.16.8.9"
 29 ##        $domain  = "172.16.8.9/32"
 30 ## Output: true
 31 #
 32 #
 33 ## Example 4
 34 ##
 35 ## Input: $ip_addr = "172.16.4.5"
 36 ##        $domain  = "172.16.0.0/14"
 37 ## Output: true
 38 #
 39 #
 40 ## Example 5
 41 ##
 42 ## Input: $ip_addr = "192.0.2.0"
 43 ##        $domain  = "192.0.2.0/25"
 44 ## Output: true
 45 #
 46 ############################################################
 47 ##
 48 ## discussion
 49 ##
 50 ############################################################
 51 #
 52 # At first, we make sure the ip_addr and domain are valid:
 53 # - they have the right amount of digits and "."
 54 # - the domain has a "/" and one or two more digits
 55 # - each number in the ip_addr or the IP part of the domain is
 56 #   in the range 0..255
 57 # - the netmask is in the range 1..32
 58 # Now, we just need to check whether ip_addr is in the range
 59 # given by domain. For this, we turn both into binary form
 60 # and check whether the first mask many digits are the same
 61 # for both.
 62 
 63 use v5.36;
 64 
 65 subnet_sheriff("192.168.1.45", "192.168.1.0/24");
 66 subnet_sheriff("10.0.0.256", "10.0.0.0/24");
 67 subnet_sheriff("172.16.8.9", "172.16.8.9/32");
 68 subnet_sheriff("172.16.4.5", "172.16.0.0/14");
 69 subnet_sheriff("192.0.2.0", "192.0.2.0/25");
 70 
 71 sub subnet_sheriff($ip_addr, $domain) {
 72     say "Input: $ip_addr, $domain";
 73     return say "Output: false" unless $ip_addr =~ m/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/;
 74     return say "Output: false" unless $domain =~ m/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}/;
 75     return say "Output: false" unless ip_ok($ip_addr);
 76     return say "Output: false" unless domain_ok($domain);
 77     my @ip_binary = to_binary($ip_addr);
 78     my ($ip_part, $mask) = split /\//, $domain;
 79     my @domain_binary = to_binary($ip_part);
 80     foreach my $digit (0..$mask-1) {
 81         return say "Output: false" unless $ip_binary[$digit] == $domain_binary[$digit];
 82     }
 83     say "Output: true";
 84 }
 85 
 86 sub ip_ok($ip) {
 87     my ($i,$j,$k,$l) = split /\./, $ip;
 88     return 0 if $i > 255;
 89     return 0 if $j > 255;
 90     return 0 if $k > 255;
 91     return 0 if $l > 255;
 92     return 1;
 93 }
 94 
 95 sub domain_ok($domain) {
 96     my ($ip, $mask) = split /\//, $domain;
 97     return 0 if $mask > 32;
 98     return 0 if $mask < 1;
 99     return ip_ok($ip);
100 }
101 
102 sub to_binary($ip) {
103     my @binary = ();
104     my ($i,$j,$k,$l) = split /\./, $ip;
105     foreach my $part (($i, $j, $k, $l)) {
106         foreach my $num ((128,64,32,16,8,4,2,1) ) {
107             if($part >= $num) {
108                 push @binary, 1;
109                 $part -= $num;
110             } else {
111                 push @binary, 0;
112             }
113         }
114     }
115     return @binary;
116 }