Uname: Linux web3.us.cloudlogin.co 5.10.226-xeon-hst #2 SMP Fri Sep 13 12:28:44 UTC 2024 x86_64
Software: Apache
PHP version: 8.1.31 [ PHP INFO ] PHP os: Linux
Server Ip: 162.210.96.117
Your Ip: 3.145.106.87
User: edustar (269686) | Group: tty (888)
Safe Mode: OFF
Disable Function:
NONE

name : IPv6.pm
#!/usr/bin/perl -w

#
# Fsdb::Support::IPv6.pm
# Copyright (C) 2021 by John Heidemann <johnh@ficus.cs.ucla.edu>
#
# This program is distributed under terms of the GNU general
# public license, version 2.  See the file COPYING
# in $dblib for details.
#

package Fsdb::Support::IPv6;

=head1 NAME

Fsdb::Support::IPv6 - ipv6-parsing helpers

=head1 SYNOPSIS

    use Fsdb::Support::IPv6;

=cut
#'


=head2 ipv6_zeroize

    $ipv6_zeroized = Fsdb::Support::IPv6::ipv6_zeroize('1:0002:3:4::');
    # result is 1:2:3:4:0:0:0:0

Normalize an IPv6 address so it has all hextets present,
with no leading zeros.

Also treats strings of hex digits without colons as IPv6 prefixes.

=cut
sub ipv6_zeroize {
    my ($s) = @_;
    if ($s !~ /:/) {
        # no :, so it must be a hex string
        # first, make it 32 bytes
        if (length($s) < 32) {
            $s = $s . "0" x (32 - length($s));
        };
        return undef if (length($s) > 32);
        # insert colons (that we will then take out below :-( )
        $s =~ s/(....)/$1:/g;
        $s =~ s/:$//;
    }
#    if ($s !~ /::/) {
#        return $s;
#    };
    my(@double_colon_parts) = split(/::/, $s);
    return undef if ($#double_colon_parts > 1);  # multiple :: !
    my(@hextets) = split(/:/, $s);
    if ($#hextets < 7) {
        my(@full) = ();
        my($found) = undef;
        foreach (@hextets) {
            if ($_ eq '') {
                if ($found) {
                    # ::1 will return the list('', '', '1')
                    push(@full, '0');
                } else {
                    push(@full, ('0') x (8 - $#hextets));
                    $found = 1;
                };
            } elsif ($_ ne '') {
                s/^0+//g;
                $_ = '0' if ($_ eq '');   # put back singleton zero
                push (@full, $_);
            };
        };
        # 1:: returns the list ('1')
        push (@full, ('0') x (7 - $#hextets)) if (!$found);
        return join(':', @full);
    } elsif ($#hextets == 7) {
        my(@full) = ();
        foreach (@hextets) {
            s/^0+//g;
            $_ = '0' if ($_ eq '');   # put back singleton zero
            push (@full, $_);
        };
        return join(':', @full);
    } else {
        return undef;
    };                 
};

=head2 ipv6_fullhex

    $ipv6_fullhex = Fsdb::Support::IPv6::ipv6_full('1:0002:3:4::');
    # result is 0001000200030004000000000000000

Rewrite an IPv6 address as a full, 128-bit, base-16 number.

=cut
sub ipv6_fullhex {
    my ($s) = ipv6_zeroize($_[0]);
    my($r) = '';
    my(@parts) = split(/:/, $s);
    return join('', map { length($_) >= 4 ? $_ : ('0' x (4 - length($_)) . $_) } @parts);
};


=head2 ipv6_normalize

    $ipv6_normal = Fsdb::Support::IPv6::ipv6_normalize('1:0002::7:0:00:8');
    # result is 1:2::7:0:0:8

Convert an IPv6 address to IETF normal form.
The input maybe has some fields 0 or leading zero,
or :: in a non-standard place.
Normalize it to remove leading zeros and replace the leftmost,
longest run of zeros with ::.

Also treats strings of hex digits without colons as IPv6 prefixes.

=cut
sub ipv6_normalize {
    my ($s) = @_;
    $s = ipv6_zeroize($s) if ($s =~ /::/ || $s !~ /:/);   # expand :: or hex-only
    # we must have all fields, but maybe have leading zeros and runs of zeros
    my(@hextets) = split(/:/, $s);
    return undef if ($#hextets != 7);
    my(@trimmed_hextets) = ();
    my(@zero_run);
    my($max_zero_run) = 0;
    foreach (@hextets) {
        s/^0//g;
        $_ = '0' if ($_ eq '');   # put back singleton zero
        push(@trimmed_hextets, $_);
        my($cur_zero_run) = ($_ ne '0' ? 0 : ($#zero_run == -1 ? 1 : $zero_run[$#zero_run] + 1));
        push(@zero_run, $cur_zero_run);
        $max_zero_run = $cur_zero_run if ($cur_zero_run > $max_zero_run);
    };
    # kill leftmost zero run, if any
    return join(':', @trimmed_hextets) if ($max_zero_run == 0);
    my (@zero_runned_hextets) = ();
    my $cur_zero_run = undef;
    my $found_zero_run = undef;
    foreach (0..$#trimmed_hextets) {
        if ($zero_run[$_]) {
            $cur_zero_run = $zero_run[$_];
        } else {
            if (defined($cur_zero_run)) {
                # end of a zero run
                if ($cur_zero_run == $max_zero_run && !$found_zero_run) {
                    # found the :: place!
                    $found_zero_run = 1;
                    push(@zero_runned_hextets, ($#zero_runned_hextets == -1 ? ':' : ''));
                } else {
                    push(@zero_runned_hextets, ('0') x $cur_zero_run);
                };
            };
            push(@zero_runned_hextets, $trimmed_hextets[$_]);
            $cur_zero_run = undef;
        };
    };
    # trailing zeros
    if (defined($cur_zero_run)) {
        if ($cur_zero_run == $max_zero_run && !$found_zero_run) {
            # found the :: place!
            $found_zero_run = 1;
            push(@zero_runned_hextets, ($#zero_runned_hextets == -1 ? '::' : ':'));  # trailing :
        } else {
            push(@zero_runned_hextets, ('0') x $cur_zero_run);
        };
    };
    return join (':', @zero_runned_hextets);
};

=head2 ip_fullhex_to_normal

    $ip_normal = Fsdb::Support::IPv6::ip_fullhex_to_normal('20010db8000300040005000600070008');
    # result is 2001:db8:3:4:5:6:7:8

    $ip_normal = Fsdb::Support::IPv6::ip_fullhex_to_normal('c0000201');
    # result is 192.0.2.1

Convert an IPv6 address to IETF normal form.
The input maybe has some fields 0 or leading zero,
or :: in a non-standard place.
Normalize it to remove leading zeros and replace the leftmost,
longest run of zeros with ::.

Also treats strings of hex digits without colons as IPv6 prefixes.

=cut
sub ip_fullhex_to_normal {
    my($s) = @_;
    if (length($s) <= 8) {
        my(@octets) = map { sprintf("%d", hex($_)) } unpack('(A2)*', $s);
        while ($#octets < 3) {
            push(@octets, '0');
        };
        return join('.', @octets);
    } else {
        return ipv6_normalize($s);
    };
}


=head2 _test_zero_fill

Internal testing.

=cut
sub _test_zero_fill {
    my($in, $out) = @_;
    my($trial) = ipv6_zeroize($in);
    $out //= "undef";
    $trial //= "undef";
    if ($trial eq $out) {
        print "zzf ok: $in -> $out\n";
    } else {
        print "zzf NO: $in -> $out but got $trial\n";
    };
}

=head2 _test_zero_remove

Internal testing.

=cut
sub _test_zero_remove {
    my($in, $out) = @_;
    return if (!defined($out));
    my($trial) = ipv6_normalize($in);
    $out //= "undef";
    $trial //= "undef";
    if ($trial eq $out) {
        print "zrr ok: $in -> $out\n";
    } else {
        print "zrr NO: $in -> $out but got $trial\n";
    };
}

=head2 _test_fullhex

Internal testing.

=cut
sub _test_fullhex {
    my($in, $out) = @_;
    my($trial) = ipv6_fullhex($in);
    $trial //= "undef";
    if ($trial eq $out) {
        print "fh ok: $in -> $out\n";
    } else {
        print "fh NO: $in -> $out but got $trial\n";
    };
}

=head2 _test_fullhex_to_normal

Internal testing.

=cut
sub _test_fullhex_to_normal {
    my($in, $out) = @_;
    my($trial) = ip_fullhex_to_normal($in);
    $trial //= "undef";
    if ($trial eq $out) {
        print "fhn ok: $in -> $out\n";
    } else {
        print "fhn NO: $in -> $out but got $trial\n";
    };
}

=head2 _test_both

Internal testing.

=cut
sub _test_both {
    my($in, $out) = @_;
    _test_zero_fill($in, $out);
    _test_zero_remove($out, $in);
};

=head2 _test_ipv6

Internal testing.

=cut
sub _test_ipv6 {
    _test_both("1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7:8");
    _test_zero_fill("1:2:3:4:5:6:7:0", "1:2:3:4:5:6:7:0");
    _test_both("1:2:3:4:5:6:7::", "1:2:3:4:5:6:7:0");
    _test_both("1:2:3:4:5:6::",   "1:2:3:4:5:6:0:0");
    _test_zero_fill("1:002:3:4:5:6::",   "1:2:3:4:5:6:0:0");
    _test_zero_fill("1:0000:3:4:5:6::",   "1:0:3:4:5:6:0:0");
    _test_both("1:2:3:4:5::8",    "1:2:3:4:5:0:0:8");
    _test_both("1:2:3:4::8",      "1:2:3:4:0:0:0:8");
    _test_both("1:2:3:4::7:8",    "1:2:3:4:0:0:7:8");
    _test_both("1::8",            "1:0:0:0:0:0:0:8");
    _test_both("::1",             "0:0:0:0:0:0:0:1");
    _test_both("1::",             "1:0:0:0:0:0:0:0");
    _test_both("::",              "0:0:0:0:0:0:0:0");
    _test_both("1:2::6:0:0:8",    "1:2:0:0:6:0:0:8");
    # _test_zero_remove("1:2::6:0:0:8",    "1:2:0:0:6:0:0:8");
    _test_zero_fill("1:002::6:0:0:8",    "1:2:0:0:6:0:0:8");
    _test_zero_fill("1:2:0:0:6::8",    "1:2:0:0:6:0:0:8");  # technically non-compliant
    _test_zero_fill("1:2::6::8",       undef);              # ambiguous, coudl be 1:2:0:0:6:0:0:8, or 1:2:0:0:0:6:0:8, or other configs
    _test_fullhex("1:2:3:4:5:6:7:8", "00010002000300040005000600070008");
    _test_fullhex("1:002::6:0:0:8",  "00010002000000000006000000000008");
    _test_fullhex("1:abcd::6:0:0:8", "0001abcd000000000006000000000008");
    # missing case 2021-12-10
    _test_zero_fill("1:02:003:0004:5:6:7:8", "1:2:3:4:5:6:7:8");
    _test_zero_fill("00010002000300040005000600070008", "1:2:3:4:5:6:7:8");
    _test_zero_fill("00010002000300040000000000070008", "1:2:3:4:0:0:7:8");
    _test_zero_fill("000100020003", "1:2:3:0:0:0:0:0");
    _test_fullhex_to_normal('20010db8000300040005000600070008', '2001:db8:3:4:5:6:7:8');
    _test_fullhex_to_normal('c0000201', '192.0.2.1');
};

# _test_ipv6;

1;

© 2025 GrazzMean