Belgarion has asked for the wisdom of the Perl Monks concerning the following question:

Problem

A routine to generate an array of octets given an IPv6 netmask. For example, netmask /3 generates an array of one containing e0, while a netmask of /9 generates an array of two containing ff 80.

Attempt

The following code takes a numeric netmask value between 0 and 128 (including both) and returns an array of octets representing that netmask.

# # Return an array of bytes that correspond to an IPv6 netmask. # The MSB is returned as the first byte. # sub make_netmask { my $mask = shift; return if $mask < 0 or $mask > 128; my @bytes; while ($mask > 0) { my $off = ($mask >= 8) ? 0 : (8 - $mask); push @bytes, (0xff << $off) & 0xff; $mask -= 8; } return @bytes; } sub output { my $mask = shift; my @bytes = make_netmask($mask); print "$mask = ", join(' ', map { sprintf "%02x", $_ } @bytes), "\ +n"; } # Test some inputs output(0); output(3); output(8); output(9); output(127); output(128); __OUTPUT__ 0 = 3 = e0 8 = ff 9 = ff 80 127 = ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff fe 128 = ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

Question

The above code works (as can be seen by the OUTPUT section); however, I have a feeling that the make_netmask function could be improved. It just feels a little too subtle to me. I know what it's doing now (and if I comment it more, I'll know six months from now), but I'm wondering if there is a more elegant (or cleaner) way to generate this list of octets. I would appreciate both Perl specific and algorithm centric ideas.

Thank you.

Replies are listed 'Best First'.
Re: Generating IPv6 Masks
by Fletch (Bishop) on Apr 28, 2004 at 17:30 UTC

    Perhaps something modeled after Net::Netmask's imask using Math::BigInt?

    use Math::BigInt (); sub ipv6_mask { my $bits = shift; my $mask = Math::BigInt->new( 2 )->bpow( 128 ) - Math::BigInt->new( 2 )->bpow( 128 - $bits ); return $mask->as_hex; }

    Update: Ooop, you wanted an array of octets. return unpack( "a2"x16, $mask->as_hex ) will get you that.

Re: Generating IPv6 Masks
by hv (Prior) on Apr 28, 2004 at 17:43 UTC

    It doesn't seem dangerously subtle to me, but that's a very subjective thing; certainly there'd be nothing wrong with putting a comment explaining why this code does the right thing.

    As for elegance, I'd prefer to avoid a loop for this knowing that all but the last iteration of the loop will push a constant 0xff; I'd be inclined to write it more like:

    my @bytes = (0xff) x int($mask / 8); $off = $mask % 8; # to get a value with the top $off bits set, take lots of 1-bits and + shift in (8 - $off) zero bits push @bytes, (0xff << (8 - $off)) & 0xff if $off;

    Another point: it seems a bit dangerous to return an empty list for invalid arguments, and I notice that you don't include any invalid arguments in your test cases. Since the 'invalid' return is indistinguishable from the valid return for output(0) there is no way for the caller to test for errors.

    The simplest way to avoid that would be to die with a useful error message in that case: the caller can still trap errors with eval() if necessary, and it gives a clear distinction between valid and invalid usage.

    Finally, it might be unwise to use the variable name $off in the same line as several 0xff constants - they are similar enough that it takes a bit of extra effort to distinguish them when reading the code.

    Hugo

Re: Generating IPv6 Masks
by kvale (Monsignor) on Apr 28, 2004 at 18:00 UTC
    vec is useful for manipulating bit fields:
    sub make_netmask { my $mask = shift; return if $mask < 0 or $mask > 128; my $str = "\0" x 16; vec( $str, 127-$_, 1) = 0b1 foreach 0..$mask-1; return map{ vec $str, $_, 8 } reverse 0 .. 15; }

    -Mark

Re: Generating IPv6 Masks
by Roy Johnson (Monsignor) on Apr 28, 2004 at 19:18 UTC
    A pack/unpack solution:
    sub make_netmask { my $mask = shift; return if $mask < 0 or $mask > 128; return unpack('C*', pack('B*', '1' x $mask )); }
    Right-padding with zeroes can be accomplished by changing B* to B128.

    The PerlMonk tr/// Advocate