Basics 102: To Skin a Cat (Or To Xen a MAC)
Programming HowTo's - General How To's
Written by ikenticus   
Tuesday, 06 May 2008 23:34

As the title suggests, there is always more than one way. For a popular scripting language like perl, the number of variations can lead to both better and worse code. Others, like bash and expect, have certain limitations that make it a bit more cumbersome for some undertakings but, with a bit of logic, there are usually workarounds (never let it be said that "until" is a necessary conditional function when there exists "while").

Taking the MAC Generators for Xen that Allen and Chris wrote, I wanted to expand it to be more generic and illustrate some of the strengths and differences between the scripts. We also explore some different methods on how to extract valid hexadecimal pairs. As far as I can tell, randomizers are less likely to repeat a sequence when you generate them consecutively instead of looping them repeatedly, which causes the randomizer to reseed on each pass (if the seed is duplicated, so is the output).


In python, there is the ability to create loops within lists that gives us the advantage of a more compacted generate mechanism, whereas the other languages will require sorting through an array. The triple-for loop can be written as one line, but I separated it into 3 for display purposes.

python:

#!/usr/bin/python
# Generate unique MAC addresses

from random import randint
from re import findall
from string import upper
from sys import stdin, exit

max = 6 # max number of hex pairs
sep = ':' # mac field separator
xen = '00:16:3e' # change this if not XEN

# ask for user input without newline
print '\nSpecify prefix [%s]: ' % xen,
pre = stdin.readline()[:-1]
if pre is '':
pre = xen

# only extract hex pairs from input, discard singles and all else
pairs = findall('[0-9A-Fa-f]{2}', pre)
if len(pairs) > max:
print 'MAC Addresses cannot be longer than %d hex pairs' % max
exit(1)
pre = sep.join(['%02x' % int(p,16) for p in pairs])
if len(pairs) == 0:
print 'Generating full MAC without prefix'
else:
print 'Using prefix ' + pre
pre += ':'

# loop until user enter a valid numerical response
num = []
while len(num) == 0:
print '\nHow many MACs do you want to generate? ',
num = findall('^\d+, stdin.readline())
print
num = int(num[0])

# generate random list using the most compacted method possible
for mac in [ sep.join(['%02X' % randint(0,255)
for p in range(max - len(pairs))])
for r in range(int(num)) ]:
print upper(pre) + mac

In bash, the "#%/" variable manipulation lacked the complexity of other languages. I had almost given up and resorted to sed:

sed -re 's/[0-9A-Fa-f]{2}/& /g;s/(^| )[0-9A-Fa-f]??[^0-9A-Fa-f]+/ /g;' )

But then figured out a workaround with a while/case hack.

bash:

#!/bin/bash
# Generate unique MAC addresses

max=6 # max number of hex pairs
sep=: # mac field separator
xen=00:16:3e # change this if not XEN

# ask for user input without newline
echo
read -p "Specify prefix [$xen]: " pre
pre=${pre:=$xen}

# only extract hex pairs from input, discard singles and all else
declare -a pairs=( )
while [[ ${#pre} -gt 0 ]]; do
case $pre in
[0-9A-Fa-f][0-9A-Fa-f]*)
pairs[${#pairs[*]}]=${pre::2}
pre=${pre#??} ;;
*) pre=${pre#?} ;;
esac
done
len=${#pairs[*]}
pairs="${pairs[*]}"
if [[ $len -gt $max ]]; then
printf ' MAC Addresses cannot be longer than %d hex pairs\n' $max
exit 1
fi
pre=${pairs// /$sep}
pre=${pre#$sep}
if [[ $len -eq 0 ]]; then
echo -e " Generating full MAC without prefix"
unset pre
else
echo -e " Using prefix $pre"
fi

# loop until user enter a valid numerical response
set --
declare -i num="${1:-0}"
until [[ $num -gt 0 ]]; do
echo
read -p "How many MACs do you want to generate? " num
done
echo

# generate the list using the most compacted method possible
for ((r=0; r<$num; r++)); do
echo -n ${pre%$sep} | tr [:lower:] [:upper:]
for ((p=$[max-len]; p>0; p--)); do
[[ $p -ne $max ]] && echo -n $sep
printf "%02X" $((RANDOM % 256))
done
echo
done

perl:

#!/usr/bin/perl
# Generate unique MAC addresses

my $max = 6; # max number of hex pairs
my $sep = ':'; # mac field separator
my $xen = '00:16:3e'; # change this if not XEN

# ask for user input without newline
printf "\nSpecify prefix [%s]: ", $xen;
my $pre = ;
chomp $pre;
$pre = $xen if (!$pre);

# only extract hex pairs from input, discard singles and all else
my @pairs = ();
while ($pre =~ m/([0-9A-Fa-f]{2})/) {
push(@pairs,
);
$pre =~ s/^.*
//;
}
if (scalar(@pairs) > $max) {
printf " MAC Addresses cannot be longer than %d hex pairs", $max;
exit 1;
}
$pre = join(":", @pairs);
if (scalar(@pairs) == 0) {
print " Generating full MAC without prefix\n";
} else {
print " Using prefix $pre\n";
}

# loop until user enter a valid numerical response
my $num = 0;
while ($num <= 0) {
print "\nHow many MACs do you want to generate? ";
$num = ;
}
print "\n";

# generate random list using the most compacted method possible
for (my $r=0; $r<$num; $r++) {
print uc($pre);
for (my $p=($max-scalar(@pairs)); $p>0; $p--) {
print $sep if ($p != $max);
printf "%02X", rand(255);
}
print "\n";
}


expect:

#!/usr/bin/expect
# Generate unique MAC addresses

exp_version -exit 5.0
exp_internal 0
log_user 0
set timeout 15

set max 6; # max number of hex pairs
set sep :; # mac field separator
set xen 00:16:3e; # change this if not XEN

# ask for user input without newline
send_user [ format "\nSpecify prefix \[%s\]: " $xen ]
expect -re "(.*)\n" { set pre $expect_out(1,string) }
if { $pre == "" } then { set pre $xen }

# only extract hex pairs from input, discard singles and all else
set pairs [ regexp -all -inline -- {[0-9A-Fa-f][0-9A-Fa-f]} $pre ]
set len 0
foreach p $pairs { incr len }
if { $len > $max } {
send_user " MAC Addresses cannot be longer than $max hex pairs\n"
exit 1
}
set pre [ join $pairs $sep ]
if { $len == 0 } {
send_user " Generating full MAC without prefix\n";
} else {
send_user " Using prefix $pre\n";
}

# loop until user enter a valid numerical response
send_user "\nHow many MACs do you want to generate? "
expect -re "(\\d+)\n" { set num $expect_out(1,string) }
send_user "\n"

# generate random list using the most compacted method possible
for {set r 0} {$r<$num} {incr r} {
send_user [ string toupper $pre ]
for {set p [expr $max-$len]} {$p>0} {incr p -1} {
if { $p != $max } { send_user $sep }
send_user [ format "%02X" [expr int(rand()*255)] ]
}
send_user "\n"
}

php:

#!/usr/bin/php
<?php
// Generate unique MAC addresses

$max = 6; # max number of hex pairs
$sep = ':'; # mac field separator
$xen = '00:16:3e'; # change this if not XEN

// ask for user input without newline
printf ("\nSpecify prefix [%s]", $xen);
$pre = readline(": ");
if (!$pre) { $pre = $xen; }

// only extract hex pairs from input, discard singles and all else
preg_match_all('/[0-9A-Fa-f]{2}/', $pre, $pairs);
if (count($pairs[0]) > $max) {
printf (" MAC Addresses cannot be longer than %d hex pairs\n", $max);
exit(1);
}
$pre = join($sep, $pairs[0]);
if (count($pairs[0]) == 0) {
print " Generating full MAC without prefix\n";
} else {
print " Using prefix $pre\n";
}

// loop until user enter a valid numerical response
$num = 0;
while ($num == 0) {
$num = intval(readline("\nHow many MACs do you want to generate? "));
}
print "\n";

// generate random list using the most compacted method possible
for ($r=0; $r<$num; $r++) {
print strtoupper($pre);
for ($p=($max-count($pairs[0])); $p>0; $p--) {
if ($p != $max) { print $sep; }
printf ("%02X", rand(0,255));
}
print "\n";
}

?>

Spent about two hours between last night and this morning and learned myself a little bit of ruby or, at least, enough to convert this script to ruby. I cannot guarantee that it is the most efficient, but it produces the same exact output as the rest:

ruby:

#!/usr/bin/ruby
# Generate unique MAC addresses

max = 6 # max number of hex pairs
sep = ':' # mac field separator
xen = '00:16:3e' # change this if not XEN

# ask for user input without newline
printf("\nSpecify prefix [%s]: ", xen)
pre = readline.chomp
if pre.empty?
pre = xen
end

# only extract hex pairs from input, discard singles and all else
pairs = pre.scan(/[0-9A-Fa-f]{2}/)
if pairs.length > max
printf(" MAC Addresses cannot be longer than %d hex pairs\n", max)
Process.exit 1
end
pre = pairs.join(sep)
if pairs.length == 0
puts ' Generating full MAC without prefix'
else
puts ' Using prefix ' + pre
end

# loop until user enter a valid numerical response
num = nil
while num == nil
printf("\nHow many MACs do you want to generate? ")
num = Integer(readline.chomp.scan(/^\d+$/)[0])
end
puts

# generate random list using the most compacted method possible
p = Array(1..(max-pairs.length))
Array(1..num).each {
if pairs.length > 0
printf("%s:", pre.upcase)
end
p.collect! { |r| sprintf('%02X', Integer(rand*255)) }
puts p.join(sep)
}


Add this page to your favorite Social Bookmarking websites
Reddit! Del.icio.us! Mixx! Free and Open Source Software News Google! Live! Facebook! StumbleUpon! Yahoo! Free Joomla PHP extensions, software, information and tutorials.
Comments
Search RSS
Only registered users can write comments!

3.22 Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved."

Last Updated on Monday, 12 May 2008 20:10