6 październik 2008 | Kategorie: BOINC, Perl

Przy robieniu swoich własnych statystyk BOINC, wykorzystywałem pliki ze statystykami generowanymi przez BOINC Manager, wymagało to jednak instalacji BOINC na danym komputerze. Po dyskusji na forum BOINC@Poland powstał skrypt (odbiegający trochę od pierwowzoru), który pobiera sam statystyki, bezpośrednio z schedulera danego projektu.

Konfiguracja skryptu znajduje się pliku xml, domyślnie config.xml w katalogu bieżącym. Gdy dany projekt wymaga alternatywnych parametrów konta, można je sprecyzować osobno dla każdego z projektów. Gdy poda się bezpośrednio klucz do danego projektu, skrypt pomija procedurę sprawdzania konta w danym projekcie. W tym przypadku pomijany jest adres e-mail i hasło do konta. W przypadku projektu World Community Grid w miejsce adresu e-mail wpisujemy nazwę użytkownika.

Pobierz config.xml
<config>
  <account_email>główny@adres.email</account_email>
  <account_passwd>głównehasło</account_passwd>
  <projects>
    <project>
      <account_email>alternatywny@adres.email</account_email>
      <account_passwd>alternatywnehasło</account_passwd>
      <project_url>http://www.ufluids.net/</project_url>
    </project>
    <project>
      <project_url>http://boinc.fzk.de/poem/</project_url>
    </project>
    <project>
      <project_url>http://milkyway.cs.rpi.edu/milkyway/</project_url>
      <!-- przykładowy klucz -->
      <account_key>a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0</account_key>
    </project>
  </projects>
</config>

W wyniku działania skryptu, otrzymujemy plik xml, domyślnie stats.xml w katalogu bieżącym. Gdy w danym momencie dany projekt jest niedostępny, w statystykach podawane są dane z ostatniego udanego ich pobrania. Podczas testów okazało się, że każde zapytanie do schedulera powoduje wygenerowanie nowego hostid w danym projekcie, a tym samym powstanie w bazach projektu “komputerów duchów” i zaśmiecanie bazy schedulera. Aby temu zapobiec, konieczne okazało się generowanie osobnego hostid dla każdego z projektów osobno. Następuje to przy pierwszym uruchomieniu skryptu. Dodatkowo generowany jest także host_cpid wykorzystywany przy generowaniu w/w hostid. Parametry te są przechowywane w pliku ze statystykami i nie należy ich usuwać.

Pobierz stats.xml
<stats>
  <host_cpid>01010101010101010101010101010101</host_cpid><!-- przykładowy -->
  <projects>
    <project>
      <project_name>uFluids</project_name>
      <project_url>http://www.ufluids.net/</project_url>
      <user_total_credit>128.281442</user_total_credit>
      <user_expavg_credit>3.607507</user_expavg_credit>
      <last_update>1224747609</last_update>
      <hostid>11</hostid><!-- przykładowy -->
    </project>
    <project>
      <project_name>Poem@Home</project_name>
      <project_url>http://boinc.fzk.de/poem/</project_url>
      <user_total_credit>573731.146552</user_total_credit>
      <user_expavg_credit>3152.617978</user_expavg_credit>
      <last_update>1224747610</last_update>
      <hostid>12</hostid><!-- przykładowy -->
    </project>
    <project>
      <project_name>Milkyway@home</project_name>
      <project_url>http://milkyway.cs.rpi.edu/milkyway/</project_url>
      <user_total_credit>2085998.570040</user_total_credit>
      <user_expavg_credit>21861.985225</user_expavg_credit>
      <last_update>1224747046</last_update>
      <hostid>13</hostid><!-- przykładowy -->
    </project>
  </projects>
</stats>

Skrypt można uruchomić z parametrami wskazującymi alternatywny plik konfiguracyjny i/lub alternatywny plik ze statystykami. Uruchomiony bez parametrów, przyjmuje wartości domyślne.

Pobierz get-stats.pl
#!/usr/bin/perl -w
 
use strict;
use Digest::MD5 qw(md5_hex);
use XML::Simple;
use Time::HiRes qw(gettimeofday);
use Data::Dumper;
use LWP::UserAgent;
use Getopt::Long qw(:config bundling pass_through);
use Carp;
$|++;
 
# defaults
my $config_file = './config.xml',
my $stats_file = './stats.xml',
my $debug = 0;
 
my $ua = LWP::UserAgent->new;
$ua->timeout(20);
 
my $host_cpid = '';
my $stats = {};
my $stats_tmp = {};
my $config = {};
 
GetOptions(
    "c|config=s" => \$config_file,
    "o|output=s" => \$stats_file,
    "d|debug" => \$debug,
    "h|help|usage" => \&usage,
);
 
usage() if $ARGV[0];
 
# wymagany plik konfiguracyjny
if ( -r $config_file ) {
    $config = XMLin(
        $config_file,
        KeyAttr => '',
        GroupTags => { projects => 'project' },
        ForceArray => [ 'project' ],
        );
    print Dumper $config if $debug;
}
else {
    croak "$config_file $!\n";
}
 
# odczytanie wcześniej zpaisanych danych
if ( -r $stats_file ) {
    $stats = XMLin(
        $stats_file,
        KeyAttr => '',
        GroupTags => { projects => 'project' },
        ForceArray => [ 'project' ],
        );
    print Dumper $stats if $debug;
 
    if ( $host_cpid = $stats->{host_cpid} ) {
        print "using `$host_cpid' as host_cpid\n";
    } else {
        $host_cpid = md5_hex(gettimeofday());
        print "new host_cpid generated: `$host_cpid'\n";
    }
 
    foreach my $project ( @{$stats->{projects}} ) {
        if ( $project->{project_url} ne '' ) {
            $stats_tmp->{$project->{project_url}} = {
                total_credit => $project->{user_total_credit},
                expavg_credit => $project->{user_expavg_credit},
                project_url => $project->{project_url},
                project_name => $project->{project_name},
                last_update => $project->{last_update},
                hostid => $project->{hostid},
            };
        }
    }
}
else {
    carp "$stats_file $!\n";
}
 
print Dumper $stats_tmp if $debug;
 
# główna pętla
MAIN: foreach my $project ( @{$config->{projects}} ) {
    my $prj_url = $project->{project_url};
    my $prj_email = $project->{account_email} || $config->{account_email};
    my $prj_passwd = $project->{account_passwd} || $config->{account_passwd};
    my $hostid = $stats_tmp->{$prj_url}{hostid} || '';
    my $prj_key;
    my $sch_url;
 
    if ( $prj_email && $prj_passwd ) {
        print "getting stats from `$prj_url' for `@{[ $project->{account_key} || $prj_email ]}' using `$hostid' as hostid\n";
        unless ( $prj_key = ($project->{account_key} || lookup_account($prj_url,$prj_email,$prj_passwd)) ) {
            carp "account key not found\n";
            next MAIN;
        }
    }
    else {
        carp "project email or password not found, skipping...";
        next MAIN;
    }
 
    unless ( $sch_url = get_scheduler_url($prj_url) ) {
        carp "scheduler url not found, skipping...";
        next MAIN;
    }
 
    if ( $sch_url && $prj_key ) {
        if ( my $st = get_stats($sch_url,$prj_key,$hostid,$host_cpid) ) {
            $stats_tmp->{$prj_url}{project_name} = $st->{project_name};
            $stats_tmp->{$prj_url}{total_credit} = $st->{user_total_credit};
            $stats_tmp->{$prj_url}{expavg_credit} = $st->{user_expavg_credit};
            $stats_tmp->{$prj_url}{hostid} = $st->{hostid} if defined $st->{hostid};
            $stats_tmp->{$prj_url}{project_url} = $prj_url;
            $stats_tmp->{$prj_url}{last_update} = time;
        }
        else {
            carp "cannot get statistics, skipping...";
            next MAIN;
        }
    }
}
 
print Dumper $stats_tmp if $debug;
 
# wyjście
my $out = "<stats>\n";
$out .= "  <host_cpid>$host_cpid</host_cpid>\n";
$out .= "  <projects>\n";
 
foreach ( sort { uc $stats_tmp->{$a}{project_name} cmp uc $stats_tmp->{$b}{project_name} } keys %$stats_tmp ) {
    if ( defined $stats_tmp->{$_}{project_url} ) {
        $out .= "    <project>\n";
        $out .= "      <project_name>@{[$stats_tmp->{$_}{project_name}]}</project_name>\n";
        $out .= "      <project_url>@{[$stats_tmp->{$_}{project_url}]}</project_url>\n";
        $out .= "      <user_total_credit>@{[$stats_tmp->{$_}{total_credit}]}</user_total_credit>\n";
        $out .= "      <user_expavg_credit>@{[$stats_tmp->{$_}{expavg_credit}]}</user_expavg_credit>\n";
        $out .= "      <last_update>@{[$stats_tmp->{$_}{last_update}]}</last_update>\n";
        $out .= "      <hostid>@{[$stats_tmp->{$_}{hostid}]}</hostid>\n";
        $out .= "    </project>\n";
    }
}
 
$out .= "  </projects>\n";
$out .= "</stats>\n";
 
open my $fh,'>',$stats_file;
print $fh $out;
close $fh;
 
exit;
 
sub get_stats {
    my ($url,$key,$hostid,$host_cpid) = @_;
    $url =~ s!https://secure!http://www!; # obejście przy World Community Grid
 
    # wysyłamy zapytanie do schedulera
    # w większości projektów nie trzeba podawać wersji klienta
    # ale POEM wymagał tego
 
    my $req = HTTP::Request->new(POST => $url );
    $req->content_type('application/x-www-form-urlencoded');
    $req->content("<scheduler_request>
            <authenticator>$key</authenticator>
            <core_client_major_version>6</core_client_major_version>
            <core_client_minor_version>2</core_client_minor_version>
            <core_client_release>18</core_client_release>
            <hostid>$hostid</hostid>
            <host_info>
                <host_cpid>$host_cpid</host_cpid>
                <domain_name>perl script for personal stats</domain_name>
                <os_name>Ghost Host</os_name>
                <os_version>[safe to delete]</os_version>
                <p_vendor>Ghost Host</p_vendor>
                <p_model>[safe to delete]</p_model>
            </host_info>
            </scheduler_request>
            ");
 
    my $res = $ua->request($req);
 
    if ( $res->is_success ) {
        my $tmp = $res->content;
        $tmp =~ s!< <!<!g; # obejście przy uFluids
        $tmp =~ s!</scheduler_reply>.*!!s; # obejście przy World Community Grid
        my $cnt;
        eval {
            $cnt = XMLin($tmp);
        };
 
        if ( $@ ) {
            carp $@;
            return 0;
        }
 
        if ( exists($cnt->{project_is_down}) ) {
            carp "$cnt->{message}{content}\n";
            return 0;
        }
        else {
            return $cnt;
        }
 
    }
    else {
        carp $res->status_line;
        return 0;
    }
}
 
sub lookup_account {
    my ($url,$email,$passwd) = @_;
    $url =~ s!/$!!;
 
    # pobieramy uid
    my $res = $ua->get(
        "$url/lookup_account.php?email_addr=$email&passwd_hash=@{[md5_hex($passwd.$email)]}"
        );
 
    if ( $res->is_success ) {
        my $tmp = $res->content;
        my $cnt;
        eval {
            $cnt = XMLin($tmp);
        };
 
        if ( $@ ) {
            carp $@;
            return 0;
        }
 
        # sprawdzamy czy nie było jakiegoś błędu przy identyfikacji
        if ( exists($cnt->{error_msg})) {
            carp $cnt->{error_msg};
            return 0;
        }
        else {
            return $cnt->{authenticator};
        }
 
    }
    else {
        carp $res->status_line;
        return 0;
    }
}
 
sub get_scheduler_url {
    my $url = shift;
    my $scheduler_url;
    my $res = $ua->get($url);
 
    if ( $res->is_success ) {
        # szukanie na głównej stronie projektu adresu schedulera
        ($scheduler_url) = $res->content =~ m'<scheduler>\s*(.*?)\s*</scheduler>';
        return $scheduler_url;
    }
    else {
        carp $res->status_line;
        return 0
    }
}
 
sub usage {
    print "$0 [-c|--config=FILE] [-o|--output=FILE] [-d] [-h|--help|--usage]\n\n";
    exit(1);
}
3 październik 2008 | Kategorie: Inne

Dawno temu, gdy posiadałem kartę TV na układzie BT848, do oglądania telewizji korzystałem z programu DScaler. Niestety, wbudowane listy kanałów, nie pokrywały całego pasma dostepnego w polskiej kablówce. To jest lista wszystkich możliwych kanałów (Air i CATV) jakie mogą oficjalnie występować na terenie Polski.

[Poland Air and Cable Frequencies]
      49750000      ; K01
      59250000      ; K02
      77250000      ; K03
      85250000      ; K04
      93250000      ; K05
      111250000      ; S01
      119250000      ; S02
      127250000      ; S03
      135250000      ; S04
      143250000      ; S05
      151250000      ; S06
      159250000      ; S07
      167250000      ; S08
      175250000      ; K06
      183250000      ; K07
      191250000      ; K08
      199250000      ; K09
      207250000      ; K10
      215250000      ; K11
      223250000      ; K12
      231250000      ; S09
      239250000      ; S10
      247250000      ; S11
      255250000      ; S12
      263250000      ; S13
      271250000      ; S14
      279250000      ; S15
      287250000      ; S16
      295250000      ; S17
      303250000      ; S18
      311250000      ; S19
      319250000      ; S20
      327250000      ; S21
      335250000      ; S22
      343250000      ; S23
      351250000      ; S24
      359250000      ; S25
      367250000      ; S26
      375250000      ; S27
      383250000      ; S28
      391250000      ; S29
      399250000      ; S30
      407250000      ; S31
      415250000      ; S32
      423250000      ; S33
      431250000      ; S34
      439250000      ; S35
      447250000      ; S36
      455250000      ; S37
      463250000      ; S38
      471250000      ; K21
      479250000      ; K22
      487250000      ; K23
      495250000      ; K24
      503250000      ; K25
      511250000      ; K26
      519250000      ; K27
      527250000      ; K28
      535250000      ; K29
      543250000      ; K30
      551250000      ; K31
      559250000      ; K32
      567250000      ; K33
      575250000      ; K34
      583250000      ; K35
      591250000      ; K36
      599250000      ; K37
      607250000      ; K38
      615250000      ; K39
      623250000      ; K40
      631250000      ; K41
      639250000      ; K42
      647250000      ; K43
      655250000      ; K44
      663250000      ; K45
      671250000      ; K46
      679250000      ; K47
      687250000      ; K48
      695250000      ; K49
      703250000      ; K50
      711250000      ; K51
      719250000      ; K52
      727250000      ; K53
      735250000      ; K54
      743250000      ; K55
      751250000      ; K56
      759250000      ; K57
      767250000      ; K58
      775250000      ; K59
      783250000      ; K60
      791250000      ; K61
      799250000      ; K62
      807250000      ; K63
      815250000      ; K64
      823250000      ; K65
      831250000      ; K66
      839250000      ; K67
      847250000      ; K68
      855250000      ; K69
2 październik 2008 | Kategorie: Administracja, Bash

Przy konfiguracji tej strony, pojawiły się problemy z tłumaczeniem szablonu ‘Blocks2′. Po odkryciu plików .po i przegrzebaniu internetu, powstał ten mini-skrypt. Na serwerze ustawione jest kodowanie iso8859-2 i wszystkie aplikacje pracują w tym kodowaniu (vi). Natomiast strona używa kodowania utf8, więc po edycji pliku pl_PL.po, należało zmienić kodowanie. Skrypt uruchamiamy podając jako parametr nazwę pliku np.: ./build-mo.sh pl_PL.po. Po uruchomieniu, w bieżącym katalogu zostanie utworzony plik o nazwie pl_PL.mo, który jest już właściwym plikiem, wykorzystywanym przez WordPress przy tłumaczeniu zawartości.

Pobierz build-mo.sh
#!/bin/bash
 
function namename() {
  local name=${1##*/}
  local name0="${name%.*}"
  echo "${name0:-$name}"
}
 
iconv -f iso8859-2 -t utf-8 $1 -o $1.tmp
msgfmt -vc $1.tmp -o "$(namename $1).mo"
rm $1.tmp
1 październik 2008 | Kategorie: Administracja, Perl, WiFi

Skrypt ten wykrywa urządzenia z zainstalowanym systemem MikroTik, nawet gdy urządzenie nie ma ustawionego adresu IP. Po uruchomieniu, na konsoli otrzymujemy:

root@XXXXX:~# ./tools/scan-mikrotik.pl
MAC Address        IP Address       Identity                         Version
00:00:5E:80:XX:XX  192.168.XXX.XXX  MT-XXXX                          2.9.27
00:02:6F:22:XX:XX  192.168.XXX.XXX  MT-XXXX                          2.8.21
00:0F:CB:B0:XX:XX  192.168.XXX.XXX  MT-XXXX                          2.9.27
00:02:6F:37:XX:XX  192.168.XXX.XXX  MT-XXXX                          2.9.6
00:30:05:01:XX:XX  192.168.XXX.XXX  MT-XXXX                          2.9.27
00:0F:CB:B0:XX:XX  192.168.XXX.XXX  MT-XXXX                          2.9.6
00:40:CA:15:XX:XX  192.168.XXX.XXX  MT-XXXX                          2.9.6
root@XXXXX:~#
#!/usr/bin/perl -w
 
###############################################################################
#
# scan_mikrotik v.1.0.0
#
# scan utility for mikrotik
#
# panther@mindc.net
# 2008-04-14
###############################################################################
 
use strict;
use IO::Socket;
use Data::Dumper;
use Time::HiRes qw( usleep );
 
use constant MT_PORT => 5678;
use constant FORMAT => "%-17s  %-15s  %-32s %-16s\n";
 
my %LADDR;
my $mikrotik = { };
 
foreach( `/sbin/ip a`) {
    $LADDR{$2} = $1 if m/inet\s+(\S+)\/.*\s(eth\d+)(\s|:)/;
}
 
printf FORMAT,'MAC Address','IP Address','Identity','Version';
 
if (fork) {
    my $socket = IO::Socket::INET->new(
                    Proto => 'UDP',
                    LocalPort => MT_PORT,
                    LocalAddr => inet_ntoa(INADDR_ANY),
                    Reuse => 1
                ) or die "cannot bind socket $!\n";
 
    local $SIG{ALRM} = sub { close $socket;end() };
 
    eval {
        alarm 3;
        while ( $socket->recv(my $data,1024) ) {
            #my @datas = unpack("L1 H8 H12 H8 Z* H6 Z* H6 Z* H22 Z8",$data); #segmentation fault
            my @datas = unpack("L1 H8 H12 H8 Z* H6 Z*",$data);
            next if $datas[1] ne '00010006';
            my ($port, $ipaddr) = sockaddr_in($socket->peername);
            $mikrotik->{$datas[2]}{'mac'} = uc join(':',unpack('H2' x 6,pack("H12",$datas[2])));
            $mikrotik->{$datas[2]}{'ipaddr'} = join('.',unpack('C4',$ipaddr));
            $mikrotik->{$datas[2]}{'identity'} = $datas[4];
            $mikrotik->{$datas[2]}{'version'} = $datas[6];
        }
        alarm 0;
    };
    close $socket;
    end();
 
} else {
    foreach my $dev ( keys %LADDR ) {
        unless (fork) {
            my $socket = IO::Socket::INET->new(
                            Proto => 'UDP',
                            PeerPort => MT_PORT,
                            PeerAddr => inet_ntoa(INADDR_BROADCAST),
                            LocalAddr => $LADDR{$dev},
                            LocalPort => MT_PORT,
                            Broadcast => 1,
                            Reuse => 1
                        ) or die "cannot bind socket $!\n";
#           for (1..3) {
#               usleep(300000);
                $socket->send(pack("H8",0));
#           }
            close $socket;
            exit;
        }
    }
}
 
1 while ( wait() != -1);
 
exit;
 
sub end {
    foreach ( keys %$mikrotik ) {
        printf FORMAT,
                $mikrotik->{$_}{'mac'},
                $mikrotik->{$_}{'ipaddr'},
                $mikrotik->{$_}{'identity'},
                $mikrotik->{$_}{'version'};
    }
    exit;
}
29 wrzesień 2008 | Kategorie: Administracja, Perl, WiFi

Do oprogramowania dla Access Pointów Orinoco AP2000/AP2500, dołączany jest mały programik o nazwie ScanTool (Windows), który pozwala na podstawowe operacje na AP: ustawienie statycznego adresu IP, aktualizację oprogramowania. Pozwala także na wyszukanie “zaginionego” AP, gdy nie znamy jego adresu IP. Potrzebowałem narzędzia, które “znajdzie” mi Orinoco wprost z konsoli, bez dostępu do X+Wine a tym bardziej do Windows. Skrypt jedynie wykrywa Orinoco w lokalnej sieci. Nie pozwala na modyfikacje czegokolwiek. Po uruchomieniu skryptu, na konsoli otrzymujemy:

root@XXXXX:~# ./tools/scan-tool.pl
Dev   MAC Address        IP Address      s/d  Uptime        System Description
--------------------------------------------------------------------------------
eth2:
      00:20:A6:4A:XX:XX  192.168.XXX.XXX  s   013:00:12:22  AP-2000 v2.4.11(821)
      00:20:A6:4A:XX:XX  192.168.XXX.XXX  s   025:19:06:36  AP-2000 v2.4.11(821)

--------------------------------------------------------------------------------
eth0:
      00:02:2D:71:XX:XX  10.XXX.XXX.XXX   s   014:07:07:49  AP-2000 v2.4.11(821)
      00:02:2D:48:XX:XX  10.XXX.XXX.XXX   s   005:22:34:29  AP-2000 v2.4.11(821)

root@XXXXX:~#

Skrypt nie zawsze działa poprawnie, gdy na danym interfejsie mamy ustawiony więcej niż jeden adres IP z tej samej podsieci. Po uruchomieniu ./scan-tool.pl -v otrzymamy więcej informacji na konsoli.

Pobierz scan-tool.pl
#!/usr/bin/perl -w
 
###############################################################################
#
# scan_tool v.1.0.0
#
# scan utility for orinoco ap
#
# panther@mindc.net
# 2008-04-11
###############################################################################
 
use strict;
use IO::Socket;
use Data::Dumper;
use Time::HiRes qw( usleep );
 
use constant ST_BROADCAST => 'ab010000';
use constant ST_STATIC => 'ab03070f';
use constant ST_DYNAMIC => 'ab03040b';
use constant ST_BROADCAST_RESPONSE => 'ac0207ff';
use constant ST_RESPONSE => 'ac0407fe';
use constant ST_ERROR => 'acff0001';
use constant ST_PORT => 2719;
use constant FORMAT => "%-4s  %-17s  %-15s%3s  %-12s  %sn";
 
my $LPORT = 1000 + int rand 2000;
my %LADDR;
my $orinoco = { };
 
my $param = $ARGV[0] || '';
my $verbose = 0;$verbose = 1 if $param =~ m/^(-v|--verbose)$/;
my $debug = 0;$debug = 1 if $param =~ m/^(-d|--debug)$/;
 
foreach( reverse `/sbin/ip a`) {
    $LADDR{$2} = $1 if m/inets+(S+)/.*s(ethd+)(?:s|:)/;
}
 
printf FORMAT,'Dev','MAC Address','IP Address','s/d','Uptime','System Description';
 
foreach my $dev ( keys %LADDR ) {
    unless (fork) {
        if (fork) {
            my $socket = IO::Socket::INET->new( Proto => 'UDP',
                                                PeerPort => ST_PORT,
                                                LocalAddr => $LADDR{$dev},
                                                LocalPort => $LPORT,
                                                Reuse => 1) or die "cannot bind socket $!n";
            local $SIG{ALRM} = sub { close $socket;end($dev) };
            eval {
                alarm 1;
                while ( $socket->recv(my $data, 612) ) {
                    my ( $signature,$echo,$mac,undef, $ipaddr, $name, $uptime, $sysname, $tftpipaddr, $tftpfilename, $ipsubmask, $ipgw, $ipaddrtype) = unpack("H8 Z32 H12 H4 H8 Z32 N1 Z256 H8 Z256 H8 H8 L1",$data);
                    next if $signature ne ST_BROADCAST_RESPONSE;
                    $mac = uc join(':',unpack('H2' x 6,pack("H12",$mac)));
                    $orinoco->{$mac}{'signature'} = $signature;
                    $orinoco->{$mac}{'echo'} = $echo;
                    $orinoco->{$mac}{'ip'} = join('.',unpack("C4",pack("H8",$ipaddr)));
                    $orinoco->{$mac}{'name'} = $name;
                    $orinoco->{$mac}{'uptime'} = $uptime/100;
                    $orinoco->{$mac}{'desc'} = $sysname;
                    $orinoco->{$mac}{'tftp-ip'} = join('.',unpack("C4",pack("H8",$tftpipaddr)));
                    $orinoco->{$mac}{'tftp-filename'} = $tftpfilename;
                    $orinoco->{$mac}{'mask'} = join('.',unpack("C4",pack("H8",$ipsubmask)));;
                    $orinoco->{$mac}{'gateway'} = join('.',unpack("C4",pack("H8",$ipgw)));;
                    $orinoco->{$mac}{'dhcp'} = $ipaddrtype - 1;
                }
                alarm 0;
            };
            close $socket;
            end($dev);
        }
        else {
            my $socket = IO::Socket::INET->new( Proto => 'UDP',
                                                PeerPort => ST_PORT,
                                                PeerAddr => inet_ntoa(INADDR_BROADCAST),
                                                LocalAddr => $LADDR{$dev},
                                                LocalPort => $LPORT,
                                                Broadcast => 1,
                                                Reuse => 1) or die "cannot bind socket $!n";
            for (1..3) {
                usleep(200000);
                $socket->send(pack("H*",ST_BROADCAST . '00' x 608));
            }
            close $socket;
            exit;
        }
    }
}
 
1 while ( wait() != -1);
 
exit;
 
sub end {
    my $dev = shift;
    if ( my @aps = keys %$orinoco ) {
        print '-' x 80,"n";
        print "$dev:n";
        foreach ( @aps ) {
            my $d = $orinoco->{$_}{'dhcp'} ? ' d ' : ' s ';
            my $desc = $orinoco->{$_}{'desc'};
            $desc =~ s/(.*)s+SN.*/$1/ unless $verbose;
            printf FORMAT,'',$_,$orinoco->{$_}{'ip'},$d,uptime($orinoco->{$_}{'uptime'}),$desc;
            printf FORMAT,'','',$orinoco->{$_}{'mask'},'','',$orinoco->{$_}{'name'} if $verbose;
            printf FORMAT,'','',$orinoco->{$_}{'gateway'},'','','' if $verbose;
            print "n" if $verbose;
        }
        print Data::Dumper->Dump([$orinoco],[$dev]) if $param =~ m/^(--debug|-d)$/;
        print "n" unless $verbose;
    }
#    else {
#        printf FORMAT,'','-','-',' - ','-','-';
#    }
    exit;
}
 
sub uptime {
    my $time = shift;
    return sprintf "%03d:%02d:%02d:%02d",
        int($time/60/60/24),
        $time/60/60%24,
        $time/60%60,
        $time%60;
}
WordPress Loves AJAX