10 sierpnia 2011

Szablon XSL - słownie złotych

Przy okazji uruchamiania systemu generującego PDFy z fakturami, potrzebowałem czegoś, co zamieni kwoty przedstawione za pomocą liczb na odpowiadający im opis słowny. Tak powstał ten szablon XSL, gotowy do dołączenia w projektach.
Pierwowzorem był znaleziony w necie, pewien algorytm napisany w C#, który to w międzyczasie przerobiłem na Javascript (tego użyłem w innym projekcie związanym z PDF).
I tak oto powstał ten szbalon.
<xsl:template name="get-gr">
  <xsl:param name="amount"/>
  <xsl:variable name="a1" select="floor($amount)"/>
  <xsl:variable name="len" select="string-length($a1)"/>
  <xsl:variable name="a2" select="substring($a1,$len,1)"/>
  <xsl:choose>
    <xsl:when test="$a1 = 1"><xsl:value-of select="document('amount-in-words.xml')/amount-in-words/grosze[1]"/></xsl:when>
    <xsl:when test="$a2 > 1 and $a2 < 5"><xsl:value-of select="document('amount-in-words.xml')/amount-in-words/grosze[2]"/></xsl:when>
    <xsl:otherwise><xsl:value-of select="document('amount-in-words.xml')/amount-in-words/grosze[3]"/></xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="get-zl">
  <xsl:param name="amount"/>
  <xsl:variable name="a1" select="floor($amount)"/>
  <xsl:variable name="len" select="string-length($a1)"/>
  <xsl:variable name="a2" select="substring($a1,$len,1)"/>
  <xsl:choose>
    <xsl:when test="$a1 = 1"><xsl:value-of select="document('amount-in-words.xml')/amount-in-words/zlote[1]"/></xsl:when>
    <xsl:when test="$a2 > 1 and $a2 < 5"><xsl:value-of select="document('amount-in-words.xml')/amount-in-words/zlote[2]"/></xsl:when>
    <xsl:otherwise><xsl:value-of select="document('amount-in-words.xml')/amount-in-words/zlote[3]"/></xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="get-words">
  <xsl:param name="amount"/>
  <xsl:variable name="zl" select="floor($amount)"/>
  <xsl:variable name="len" select="string-length($zl)"/>
  <xsl:if test="$len > 6">
    <xsl:variable name="million2" select="substring($zl,$len -6,1)"/>
    <xsl:variable name="million">
        <xsl:if test="$len > 8"><xsl:value-of select="substring($zl,$len -8,1)"/></xsl:if>
        <xsl:if test="$len > 7"><xsl:value-of select="substring($zl,$len -7,1)"/></xsl:if>
        <xsl:value-of select="substring($zl,$len -6,1)"/>
    </xsl:variable>
    <xsl:if test="$million > 1">
      <xsl:call-template name="get-words">
        <xsl:with-param name="amount" select="$million"/>
      </xsl:call-template>
    </xsl:if>
    <xsl:choose>
      <xsl:when test="$million = 1">
        <xsl:value-of select="document('amount-in-words.xml')//millions[1]"/>
      </xsl:when>
      <xsl:when test="$million > 1 and $million < 5">
        <xsl:value-of select="document('amount-in-words.xml')//millions[2]"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="document('amount-in-words.xml')//millions[3]"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:if>

  <xsl:if test="$len > 3">
    <xsl:variable name="thousend2" select="substring($zl,$len -3,1)"/>
    <xsl:variable name="thousend">
      <xsl:if test="$len > 5"><xsl:value-of select="substring($zl,$len -5,1)"/></xsl:if>
      <xsl:if test="$len > 4"><xsl:value-of select="substring($zl,$len -4,1)"/></xsl:if>
      <xsl:value-of select="substring($zl,$len -3,1)"/>
    </xsl:variable>

    <xsl:if test="$thousend > 1">
      <xsl:call-template name="get-words">
        <xsl:with-param name="amount" select="$thousend"/>
      </xsl:call-template>
    </xsl:if>

    <xsl:choose>
      <xsl:when test="$thousend = 1">
        <xsl:value-of select="document('amount-in-words.xml')//thousends[1]"/>
      </xsl:when>
      <xsl:when test="$thousend2 > 1 and $thousend2 < 5">
        <xsl:value-of select="document('amount-in-words.xml')//thousends[2]"/>
      </xsl:when>
      <xsl:when test="$thousend = '000' and $len > 6">
        <xsl:text></xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="document('amount-in-words.xml')//thousends[3]"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:if>

  <xsl:if test="$len > 2">
    <xsl:if test="substring($zl,$len -2,1) != 0">
      <xsl:value-of select="document('amount-in-words.xml')//hundrets[substring($zl,$len -2,1) + 1]"/>
    </xsl:if>
  </xsl:if>

  <xsl:if test="$len > 0">
    <xsl:variable name="to99">
      <xsl:choose>
        <xsl:when test="$len = 1">
          <xsl:value-of select="substring($zl,$len,1)"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="substring($zl,$len -1,2)"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>

    <xsl:choose>
      <xsl:when test="$to99 < 20">
        <xsl:choose>
          <xsl:when test="substring($to99,1,1) = 0">
            <xsl:if test="substring($to99,2,1) != 0">
              <xsl:value-of select="document('amount-in-words.xml')//units[substring($to99,2,1) + 1]"/>
            </xsl:if>
          </xsl:when>
          <xsl:otherwise>
            <xsl:if test="$to99 != 0">
              <xsl:value-of select="document('amount-in-words.xml')//units[$to99 + 1]"/>
            </xsl:if>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="document('amount-in-words.xml')//tens[substring($to99,1,1)+1]"/>
        <xsl:if test="substring($to99,2,2) != 0">
          <xsl:value-of select="document('amount-in-words.xml')//units[substring($to99,2,2) + 1]"/>
        </xsl:if>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:if>

  <xsl:if test="$zl = 0">
    <xsl:value-of select="document('amount-in-words.xml')//units[1]"/>
  </xsl:if>
</xsl:template>

<xsl:template name="amount-in-words">
  <xsl:param name="amount"/>
  <xsl:call-template name="get-words"><xsl:with-param name="amount" select="$amount"/></xsl:call-template>
  <xsl:call-template name="get-zl"><xsl:with-param name="amount" select="$amount"/></xsl:call-template>
  <xsl:call-template name="get-words"><xsl:with-param name="amount" select="round(($amount - floor($amount))*100)"/></xsl:call-template>
  <xsl:call-template name="get-gr"><xsl:with-param name="amount" select="round(($amount - floor($amount))*100)"/></xsl:call-template>
</xsl:template>
Powyższy fragment kody należy umieścić w dowolnym miejscu istniejącego szablonu. Dodatkowo w katalogu z szablonem, należy umieścić poniższy plik o nazwie amount-in-words.xml:
<?xml version="1.0" encoding="UTF-8"?>    
<amount-in-words>
<grosze>grosz </grosze>
<grosze>grosze </grosze>
<grosze>groszy </grosze>
<zlote>złoty </zlote>
<zlote>złote </zlote>
<zlote>złotych </zlote>
<units>zero </units>
<units>jeden </units>
<units>dwa </units>
<units>trzy </units>
<units>cztery </units>
<units>pięć </units>
<units>sześć </units>
<units>siedem </units>
<units>osiem </units>
<units>dziewięć </units>
<units>dziesięć </units>
<units>jedenaście </units>
<units>dwanaście </units>
<units>trzynaście </units>
<units>czternaście </units>
<units>piętnaście </units>
<units>szesnaście </units>
<units>siedemnaście </units>
<units>osiemnaście </units>
<units>dziewiętnaście </units>
<tens></tens>
<tens>dziesięć </tens>
<tens>dwadzieścia </tens>
<tens>trzydzieści </tens>
<tens>czterdzieści </tens>
<tens>pięćdziesiąt </tens>
<tens>sześćdziesiąt </tens>
<tens>siedemdziesiąt </tens>
<tens>osiemdziesiąt </tens>
<tens>dziewięćdziesiąt </tens>
<hundrets></hundrets>
<hundrets>sto </hundrets>
<hundrets>dwieście </hundrets>
<hundrets>trzysta </hundrets>
<hundrets>czterysta </hundrets>
<hundrets>pięćset </hundrets>
<hundrets>sześćset </hundrets>
<hundrets>siedemset </hundrets>
<hundrets>osiemset </hundrets>
<hundrets>dziewięćset </hundrets>
<thousends>tysiąc </thousends>
<thousends>tysiące </thousends>
<thousends>tysięcy </thousends>
<millions>milion </millions>
<millions>miliony </millions>
<millions>milionów </millions>
</amount-in-words>            
Wywołanie jest proste, polega na wstawieniu poniższego fragmentu w interesującym nas miejscu szblonu:
<xsl:call-template name="amount-in-words">
  <xsl:with-param name="amount" select="value"/>
</xsl:call-template>
Jak na razie, u mnie ma to status beta, jeśli ktoś znajdzie jakieś błędy, zaproponuje poprawki, zapraszam do dyskusji.

5 lipca 2011

cwRsync portable

Często używam rsync do robienia backupów albo do synchronizowania zawartości folderów na wielu serwerach. Ostatnio postanowiłem, że kupię sobie dysk przenośny o dużej pojemności i wrzucę tam wszystkie najważniejsze swoje rzeczy. Chciałem mieć możliwość robienia tego z dowolnego miejsca przez net, więc pomyślałem o rsync.

Wszystko chciałem uruchamiać pod Windows, na dowolnym kompie do którego się podepnę. Stworzyłem prostą strukturę katalogów w głównym folderze dysku:
\bin
\backup
Zawartość z katalogu bin z folderu instalacyjnego cwRsync wrzuciłem do katalogu bin na dysku przenośnym.

Dorzuciłem też plik cwrsync.cmd do głównego katalogu. Poniżej zawartość pliku:
@echo off
setlocal
set CWRSYNCHOME=%~d0\bin
set CYGWIN=nontsec
set HOME=%HOMEDRIVE%%HOMEPATH%
set PATH=%~d0\bin;%PATH%

cygpath -a backup > backup.d
set /p BACKUP= < backup.d
del backup.d

rsync -rtlv --compress user@host::moduł %BACKUP%/ < %~d0\plik_z_hasłem
Problem z rsync polegał na tym, że ścieżka docelowa musi być absolutną ścieżką w formacie cygwin, np.: /cygdrive/h/backup. Wszelkie próby z relatywną ścieżką zawodziły.
Wykorzystałem także możliwość prostej autoryzacji w rsync, jedynym sposobem przekazania hasła, było proste przekierowanie go z zewnętrznego pliku.

14 czerwca 2011

EVE Online i Skill Training Queue

Znowu się nudziłem i napisałem sobie mały projekt w PHP, który informuje mnie o kolejce skilli w EVE Online, bez potrzeby logowania się do gry.
Jeśli ktoś będzie zainteresowany źródłem, dorzucę do bloga.

Jeszcze pomyślę o "oklejeniu" tego jakimś javascriptem, aby dane wyglądały jeszcze bardziej "na bieżąco".

11 czerwca 2011

Ubiquiti cz.2

Dzisiaj rzutem na taśmę skaner do Ubiquiti. W odróżnieniu od Mikrotika, Ubiquiti odpowiada na adres z którego zostało wysłane zapytanie. Ale w tym skrypcie nasłuchujemy na dowolnym adresie, tak jak w skanerze dla Mikrotików. No i miały być szczegóły techniczne. Skaner rozsyła z portu 50122 na port 10001 paczkę 0x01000000 po broadcaście. Urządzenie wysyła odpowiedź na port 50122. Trochę dłubaniny było przy zmiennym formacie ramki ale dość łatwo było ominąć ten problem. Pierwszy rodzaj ramki:
@s = unpack("H6H2H6H12NH6H12H8(H2n/a*)*", $ramka);

$s[0]   sygnatura (0x010000)
$s[1]   nie pamiętam :)
$s[2]   ??
$s[3]   adres MAC
$s[4]   ??
$s[5]   ??
$s[6]   ??
$s[7]   ??
(
    $s[8]   typ pola
    X       długość łańcucha (automatycznie wchłaniane przez unpack)
    $s[9]   łańcuch
)
...
Drugi rodzaj ramki:
@s = unpack("H6H2H6H12NH6H12H14(H2n/a*)*", $ramka);

$s[0]   sygnatura (0x010000)
$s[1]   nie pamiętam :)
$s[2]   ??
$s[3]   adres MAC
$s[4]   ??
$s[5]   ??
$s[6]   ??
$s[7]   ?? (tutaj długość jest większa o 3 bajty)
(
    $s[8]   typ pola
    X       długość łańcucha (automatycznie wchłaniane przez unpack)
    $s[9]   łańcuch
)
...
Zidentyfikowane typy pól:
0x03 - wersja softu
0x0a - uptime
0x0b - nadana nazwa dla urządzenia
0x0c - model urządzenia
0x0d - SSID do którego jest podpięte urządzenie gdy pracuje w trybie klienta
No i skrypt:
#!/usr/bin/perl

###############################################################################
#
# scan-ubiquiti.pl v1.2.0
#
# scan utility for ubiquiti
#
# 2011-06-11
# przepisano engine na klasyczne sockety
#
# mindc.net
# 2011-03-20
###############################################################################

use strict;
use warnings;
use Socket;
use Time::HiRes qw(gettimeofday);

print "scan-ubiquiti.pl v1.2.0
    hw                 ip               board            sw   uptime name                     ssid
--------------------------------------------------------------------------------------------------------
";

my %dev;
open my $ph, "-|","ip a";
while ( <$ph> ) {
    $dev{$2} = $1 if m@\s(\S+)/\d+\s.*(eth\d+)@;
}
close $ph;

my $data = {};

foreach my $saddr ( values %dev ) {
    my $socket;
    socket($socket,PF_INET,SOCK_DGRAM,17);
    setsockopt($socket, SOL_SOCKET, SO_BROADCAST, 1 );
    my $sin = sockaddr_in(50122,inet_aton($saddr));
    bind($socket,$sin);
    my $sout = sockaddr_in(10001,INADDR_BROADCAST);
    send($socket,pack("H8","01000000"),0,$sout);
    send($socket,pack("H8","01000000"),0,$sout);
    send($socket,pack("H8","01000000"),0,$sout);
    close $socket;
}

my $socket;
socket($socket,PF_INET,SOCK_DGRAM,17) or die "socket error - $!";
my $sin = sockaddr_in(50122,INADDR_ANY);
bind($socket,$sin);

my $rin = "";
vec($rin,fileno($socket),1) = 1;

my $end = gettimeofday() + 3;
my $timeout = $end - gettimeofday();

my $i = 0;
while ( select(my $rout = $rin,undef,undef,$timeout) ) {
    $timeout = $end - gettimeofday();
    my $rtime = "";
    my $his = recv($socket,$rtime,1500,0) || die "recv: $!";
    my ($port,$hisaddr) = sockaddr_in($his);
    next if length($rtime) < 8; #skip self
    my $o = ubiquiti_header(inet_ntoa($hisaddr),$rtime);

    if ( not exists($data->{$o->{mac}})) {
        my $sw = parse_sw($o->{sw});
        printf("%2d. %17s  %-16s %-16s %-4s %6s %-24s %s\n",
            ++$i,
            $o->{mac},
            $o->{ip},
            models($o->{board}),
            $sw->{sw_version},
            sec2human($o->{uptime}),
            $o->{name},
            $o->{ssid}
        );
    }
    $data->{$o->{mac}}++;
}
close $socket;
print "\n" if keys %$data;
exit;

sub ubiquiti_header {
    my $ip = shift;
    my $frame = shift;

    my $template = "H6 H2 H6 H12 N H6 H12 H8 (H2n/a*)*";
    my @header = unpack($template,$frame);
    my $signature = shift @header;
    return undef if $signature ne '010000';
    my $t = shift @header;
    shift @header;
    my $mac = shift @header;
    shift @header;
    shift @header;
    shift @header;

    shift @header;

    if ( $header[0] ne '01' ) {
        $template = "H6 H2 H6 H12 N H6 H12 H14 (H2n/a*)*";
        @header = unpack($template, $frame);
        $signature = shift @header;
        return undef if $signature ne '010000';
        $t = shift @header;
        shift @header;
        $mac = shift @header;
        shift @header;
        shift @header;
        shift @header;
        shift @header;
    }

    my $out = {};
    @{$out}{qw(ip mac sw board key uptime ssid)} = ($ip,'','','','',0,'');

    for ( my $i = 0; $i < @header ; $i += 2 ) {
        my $type = $header[$i];
        my $value = $header[$i+1];

        $out->{sw} = $value if $type eq '03';
        $out->{board} = $value if $type eq '0c';
        $out->{name} = $value if $type eq '0b';
        $out->{uptime} = unpack("N",$value) if $type eq '0a';
        $out->{ssid} = $value if $type eq '0d';

    }

    $out->{mac} = uc join(":",unpack("(H2)6",pack("H*",$mac)));
    $out->{frame} = $frame;
    $out->{structure} = \@header;
    return $out;
}

sub sec2human {
    my $sec = shift;

    return "" if $sec <= 0;

    my $w = int($sec / 60 / 60 / 24 / 7);
    my $d = int($sec / 60 / 60 / 24 % 7);
    my $h = int($sec / 60 / 60 % 24);
    my $m = int($sec / 60 % 60);
    my $s = int($sec % 60);
    if ( $w ) {
        return sprintf("%dw%dd",$w,$d);
    } elsif ( $d ) {
        return sprintf("%dd%dh",$d,$h);
    } elsif ( $h ) {
        return sprintf("%dh%dm",$h,$m);
    } elsif ( $m ) {
        return sprintf("%dm%ds",$m,$s);
    } else {
        return "${s}s";
    }
}

sub parse_sw {
    my $sw = shift;
    return {} unless $sw;
    my @s = split /\./,$sw;
    $s[2] = $s[2] || '';
    $s[3] = $s[3] || '';
    $s[2] =~ s/v//g;
    my $out = {
    board => $s[0] || '',
    chipset => uc $s[1] || '',
    build => $s[4] || '',
    date => $s[5] || '',
    unkn => $s[6] || ''
    };

    $out->{sw_version} = $s[2].".".$s[3] if $s[2] && $s[3];

    return $out;
}

sub models {
    my $m = shift;
    return 'NanoBridgeM' if $m eq 'NB5';
    return 'WISPStation5' if $m eq 'MS5';
    return 'RocketM' if $m eq 'R5N';
    return 'AirGridM5' if $m eq 'AG5';
    return $m;
};

MNDP cz.4

Zaczął mnie irytować ten tcpdump. Ogarnąłem się trochę i przepisałem część kodu w zwykłych socketach.
#!/usr/bin/perl

###############################################################################
#
# scan-mikrotik.pl v2.8.0
#
# scan utility for mikrotik
#
# 2011-06-11
# przepisano engine na klasyczne sockety
#
# 2011-03-27
# dodano uptime
#
# mindc.net
# 2011-03-20
###############################################################################

use strict;
use warnings;
use Socket;
use Time::HiRes qw(gettimeofday);

print "scan-mikrotik.pl v2.8.0
    hw                 ip               board      sw      uptime identity
--------------------------------------------------------------------------------
";

my %dev;
open my $ph, "-|","ip a";
while ( <$ph> ) {
    $dev{$2} = $1 if m@\s(\S+)/\d+\s.*(eth\d+)@;
}
close $ph;

my $data = {};

foreach my $saddr ( values %dev ) {
    my $socket;
    socket($socket,PF_INET,SOCK_DGRAM,17);
    setsockopt($socket, SOL_SOCKET, SO_BROADCAST, 1 );
    my $sin = sockaddr_in(5678,inet_aton($saddr));
    bind($socket,$sin);
    my $sout = sockaddr_in(5678,INADDR_BROADCAST);
    send($socket,pack("H8",0),0,$sout);
    send($socket,pack("H8",0),0,$sout);
    send($socket,pack("H8",0),0,$sout);
    close $socket;
}

my $socket;
socket($socket,PF_INET,SOCK_DGRAM,17) or die "socket error - $!";
my $sin = sockaddr_in(5678,INADDR_ANY);
bind($socket,$sin);

my $rin = "";
vec($rin,fileno($socket),1) = 1;

my $end = gettimeofday() + 3;
my $timeout = $end - gettimeofday();

my $i = 0;
while ( select(my $rout = $rin,undef,undef,$timeout) ) {
    $timeout = $end - gettimeofday();
    my $rtime = "";
    my $his = recv($socket,$rtime,1500,0) || die "recv: $!";
    my ($port,$hisaddr) = sockaddr_in($his);
    next if length($rtime) < 5; #skip self
    my $o = mikrotik_header(inet_ntoa($hisaddr),$rtime);
    if ( not exists($data->{$o->{mac}})) {
        printf("%2d. %17s  %-16s %-10s %-7s %6s %s\n",
            ++$i,
            $o->{mac},
            $o->{ip},
            $o->{board},
            $o->{sw},
            sec2human($o->{uptime}),
            $o->{identity}
        );
    }
    $data->{$o->{mac}}++;
}
close $socket;
print "\n" if keys %$data;
exit;

sub mikrotik_header {
    my $ip = shift;
    my $buffer = shift;

    my @header = unpack("H2H2S(H4n/a*)*", $buffer);

    shift @header; #?
    shift @header; #?
    shift @header; #age?

    my $out = {};
    @{$out}{qw(ip mac identity vendor sw board key uptime)} = ($ip,'','','','','','',0);

    for ( my $i = 0; $i < @header ; $i += 2 ) {
        my $type = $header[$i];
        my $value = $header[$i+1];

        $out->{mac} = uc join(":",unpack("(H2)6",$value)) if $type eq '0001';
        $out->{identity} = $value if $type eq '0005';
        $out->{vendor} = $value if $type eq '0008';
        $out->{sw} = $value if $type eq '0007';
        $out->{board} = $value if $type eq '000c';
        $out->{key} = $value if $type eq '000b';
        $out->{uptime} = unpack("L",$value) if $type eq '000a';
#       $out->{unknown2} = $value if $type eq '000d';
    }
    return $out;
}

sub sec2human {
    my $sec = shift;

    return "" if $sec <= 0;

    my $w = int($sec / 60 / 60 / 24 / 7);
    my $d = int($sec / 60 / 60 / 24 % 7);
    my $h = int($sec / 60 / 60 % 24);
    my $m = int($sec / 60 % 60);
    my $s = int($sec % 60);
    if ( $w ) {
        return sprintf("%dw%dd",$w,$d);
    } elsif ( $d ) {
        return sprintf("%dd%dh",$d,$h);
    } elsif ( $h ) {
        return sprintf("%dh%dm",$h,$m);
    } elsif ( $m ) {
        return sprintf("%dm%ds",$m,$s);
    } else {
        return "${s}s";
    }
}

20 maja 2011

APE (Ajax Push Engine) i SSL

Po długich poszukiwaniach rozwiązania tego problemu... utknąłem... W jednym miejscu piszą, że to działa tylko ze starą wersją stunnel (u mnie to nie działało) gdzie indziej robią sztuczki z przekierowaniami na Apachu (siedzę od jakiegoś czasu na lighttpd i nie wnikałem)... Po wnikliwej analizie co się dzieje w momencie negocjacji przez stunnel doszedłem, że chodzi o coś z timeoutami... Jedna z opcji wydawała się obiecująca... Poniżej działający bezproblemowo z APE, stunnel.conf
output = /var/log/stunnel.log
pid = /var/run/stunnel.pid
foreground = no

[ape]
TIMEOUTclose = 0
cert = <ścieżka do certyfikatu>
accept = <host na którym nasłuchujemy>:4443
connect = <ape serwer>:6969
Pogrubiłem opcję, która wszystko "naprawiła". Należy pamiętać także o zmianach w pliku config.js
APE.Config.baseUrl = '<serwer z którego pobieramy biblioteki>'; //APE JSF
APE.Config.domain = '<domena jak w pliku ape.conf>';
APE.Config.server = '<ape serwer>:4443'; //APE server URL
APE.Config.secure = true;
I możemy się cieszyć APE z SSLem. Należy tylko pamiętać o "wildcardowych" certyfikatach...

5 maja 2011

Google Maps API v2 + zumi.pl

Dzisiaj wracam do tematu map z zumi.pl. Ten mały projekt powstał jeszcze za czasów, gdy w Google Maps, zdjęcia satelitarne były mało szczegółowe. Wpadłem więc na pomysł, aby połączyć Google Maps API z kafelkami map z zumi.pl. Pierwszy był skrypt w perlu który... ściągał wszystkie kafelki z zumi.pl, dla każdego przybliżenia. Ale po ściągnięciu ich wszystkich stwierdziłem, że wykorzystanie tego jest bezcelowe. Drugim podejściem było połączenie właśnie z API Google, które to już trochę poznałem i wykorzystywałem do własnych celów. Problemem z połączeniem tych dwóch map polegał na tym, że odwzorowanie map, a raczej "pocięcie" całej mapy na kafelki znacznie różniło się w obu tych przypadkach. Kluczem do sukcesu było poznanie algorytmu kafelkowania map w zumi.pl. Myślałem, że wyjaśnię co z czego wynika, ale nie pamiętam już za wiele. Po prostu trzeba zajrzeć w źródła :) Stronka ta, to okrojona wersja czegoś co używam na co dzień, więc mogą tam być jeszcze jakieś niepotrzebne "śmieci". Kluczowym elementem jest funkcja getTileUrl, która na podstawie podanej szerokości i długości geograficznej oraz przybliżenia, wylicza nam, który kafelek ma być pobrany z serwera zumi.pl. Reszta to funkcje wyświetlające to wszystko.

20 kwietnia 2011

Apache FOP

Znowu wróciłem do FOP'a. Pomyślałem sobie, że jak znajdę sposób na obliczenie szerokości kolumn w tabeli na podstawie znajdujących się tam danych i ustawię tą szerokość na sztywno, to będzie można spokojnie obejść problem table-layout="fixed". W PHP mamy do dyspozycji funkcję imagettfbbox, dzięki której można wyliczyć szerokość tekstu przy zastosowaniu określonej czcionki. Najprościej można to zrobić tak:
function get_text_width($font,$font_size,$text) {
    $box = imagettfbbox($font_size,0,$font,$text);
    return $box[4];
}
Gdzie: $font - ścieżka do użytej czcionki $font_size - rozmiar czcionki dla której ma być wyliczona szerokość tekstu $text - tekst dla którego wyliczymy szerokość. Przed wygenerowanie właściwej tabeli w XSL-FO, wyliczam maksymalne szerokości dla danych z każdej kolumny i na tej podstawie generuję tagi <fo:table-column column-width="??"/>.

16 kwietnia 2011

MNDP cz.3

poniżej zamieszcza skrypt, skanujący za pomocą MNDP otoczenie sieciowe
#!/usr/bin/perl -w

###############################################################################
#
# scan-mikrotik.pl v2.6.0
#
# scan utility for mikrotik
#
# 2011-03-27
# dodano uptime
#
# 2011-03-20
# mindc.net
###############################################################################

use strict;
use IO::Socket;
use Data::Dumper;
use Time::HiRes qw( usleep );

$|=1;

my %links;

print "scan-mikrotik.pl v2.6.0
link  hw                 ip               board      sw      uptime identity
--------------------------------------------------------------------------------
wait for 3 sec...\r";

open my $ph, "-|","ip a";
while ( <$ph> ) {
    $links{$2} = $1 if m@\s(\S+)/\d+\s.*(eth\d+)@;
}
close $ph;

foreach my $link ( keys %links ) {
    unless ( fork ) {
        my $socket = IO::Socket::INET->new(
            Proto => 'UDP',
            PeerPort => 5678,
            PeerAddr => inet_ntoa(INADDR_BROADCAST),
            LocalAddr => $links{$link},
            Broadcast => 1,
            Reuse => 1
        ) or die "cannot bind socket $!\n";
        my $data = {};
        eval {
            my $out = {};
            local $SIG{ALRM} = sub {
                foreach ( sort keys %$data ) {
                    my $o = $data->{$_};
                    printf("%s  %17s  %-16s %-10s %-7s %6s %s\n",
                        $link,
                        $o->{mac},
                        $o->{ip},
                        $o->{board},
                        $o->{sw},
                        sec2human($o->{uptime}),
                        $o->{identity}
                    );
                }
                print "\n" if keys %$data;
                exit;
            };
            alarm 3;
            open (STDIN,"tcpdump -i $link -lnqxt -s 1024 src port 5678 and proto UDP 2>/dev/null |");
            usleep(100000);
            $socket->send(pack("H8",0));
            usleep(100000);
            $socket->send(pack("H8",0));
            usleep(100000);
            $socket->send(pack("H8",0));
            close $socket;
            my $buffer = '';
            my $i = 0;
            while (<>) {
                s/IP \S+.5678 > \S+ UDP, length \d+.*//g;
                s/0x\d\d\d\d://g;
                s/\s+//g;
                $buffer .= $_;
                if ( $_ =~ /^$/ && $buffer ne '' ) {
                    $i++;
                    my $out = mikrotik_header($buffer);
                    $data->{$out->{mac}} = $out;
                    $buffer = '';
                }
            }
            alarm 0;
        };
        exit;
    }
}

1 while ( wait() != -1);
system "killall tcpdump";

exit;

sub mikrotik_header {
    my $buffer = shift;
                         #ip       #udp  #mikrotik
    my @header = unpack("H4nH8H8NN nnnH4 H2H2S(H4n/a*)*", pack("H*",$buffer));

    #ip header
    shift @header; #ip1
    shift @header; #ip_lenght
    shift @header; #ip2
    shift @header; #ip3
    my $ip = shift @header; #ip_src
    shift @header; #ip_dst
    #udp header
    shift @header; #udp_src_port
    shift @header; #udp_dst_port
    shift @header; #udp_lenght
    shift @header; #udp_checksum

    shift @header; #?
    shift @header; #?
    shift @header; #age?

    my $out = {};

    $out->{mac} = '';
    $out->{identity} = '';
    $out->{vendor} = '';
    $out->{sw} = '';
    $out->{board} = '';
    $out->{key} = '';
    $out->{uptime} = 0;

    $out->{ip} = join(".",unpack("C4",pack("N",$ip)));

    for ( my $i = 0; $i < @header ; $i += 2 ) {
        my $type = $header[$i];
        my $value = $header[$i+1];

        $out->{mac} = uc join(":",unpack("(H2)6",$value)) if $type eq '0001';
        $out->{identity} = $value if $type eq '0005';
        $out->{vendor} = $value if $type eq '0008';
        $out->{sw} = $value if $type eq '0007';
        $out->{board} = $value if $type eq '000c';
        $out->{key} = $value if $type eq '000b';
        $out->{uptime} = unpack("L",$value) if $type eq '000a';
#       $out->{unknown2} = $value if $type eq '000d';
    }
    return $out;
}

sub sec2human {
    my $sec = shift;

    return "" if $sec <= 0;

    my $w = int($sec / 60 / 60 / 24 / 7);
    my $d = int($sec / 60 / 60 / 24 % 7);
    my $h = int($sec / 60 / 60 % 24);
    my $m = int($sec / 60 % 60);
    my $s = int($sec % 60);
    if ( $w ) {
        return sprintf("%dw%dd",$w,$d);
    } elsif ( $d ) {
        return sprintf("%dd%dh",$d,$h);
    } elsif ( $h ) {
        return sprintf("%dh%dm",$h,$m);
    } elsif ( $m ) {
        return sprintf("%dm%ds",$m,$s);
    } else {
        return "${s}s";
    }
}
Działanie jest proste. Skrypt wysyła na każdym z interfejsów zapytanie 0x00000000 i zaczyna nasłuchiwać na odpowiedź w formacie MNDP. Warunkiem działania skrytpu jest istnienie co najmniej jednego adresu IP na danym interfejsie. Niezbyt eleganckie rozwiązanie z killall tcpdump, ale nie mogłem sobie z tym poradzić, gdy nie zamykam STDIN. A może by tak select.

13 kwietnia 2011

Apache FOP vs. RenderX XEP

No to krótkie porównanie... Apache FOP - totalny Open Source, można wykorzystać gdzie się chce i jak się chce. Ogromną zaletą FOP'a jest generowanie plików o minimalnym rozmiarze, prawdopodobnie dołącza do generowanego pliku wyłącznie fonty użyte w dokumencie. Ale niestety, nie można ustawić dla tabeli atrybutu table-layout="auto", co przy generowaniu dynamicznych tabel, jest nie do przyjęcia. RenderX XEP - zamknięte źródła, do użytku za free dostępna jedynie licencja personal. Na dole każdej strony w generowanym dokumencie, dołączana jest stopka RenderX z logiem. Do tego dołącza cały zastaw fontów do generowanego dokumentu co wiąże się z dość dużym nakładem na wielkość pliku. Ale... poprawnie interpretuje atrybut table-layout="auto". Tak więc, zdecydowałem się używać XEP'a ze względu na te tabele...

7 kwietnia 2011

FOP i fonty

Aby użyć dodatkowych fontów w Apache FOP, w pliku konfiguracyjnym w sekcji <fonts>, należy umieścić odpowiednie wpisy. Poniżej zamieszczam prosty skrypt, który generuje nam te wpisy. Używam czcionki DejaVu ponieważ jest "otwarta" i zawiera znaki Unicode.
#!/bin/bash

FOP="/home/tools/fop/"
INCLUD="${FOP}build/fop.jar:${FOP}lib/avalon-framework.jar:${FOP}lib/commons-logging.jar:${FOP}lib/commons-io.jar:${FOP}lib/xmlgraphics-commons.jar"

for font in `find /home/tools/fonts -name "*.ttf"`;do
    NAME=${font%%.*}
    java -cp "${INCLUD}" org.apache.fop.fonts.apps.TTFReader "${font}" "${NAME}.xml"
done

for FONT in `find /home/tools/fonts -name "*.ttf" | sort`;do
    NAME=${FONT%%.*}
    FILE=`basename ${FONT}`

    NAME2=${FILE%%.*}

    echo "<font metrics-url=\"${NAME}.xml\" kerning=\"yes\" embed-url=\"${FONT}\">"

    STYLE="normal"
    WEIGHT="normal"

    if echo ${NAME2} | grep -qi oblique;then
        NAME2=`echo ${NAME2} | sed -re 's/\-?oblique//i'`
        STYLE="oblique"
    fi

    if echo ${NAME2} | grep -qi italic;then
        NAME2=`echo ${NAME2} | sed -re 's/\-?italic//i'`
        STYLE="italic"
    fi


    if echo ${NAME2} | grep -qi bold;then
        NAME2=`echo ${NAME2} | sed -re 's/\-?bold//i'`
        WEIGHT="bold"
    fi

    echo "      <font-triplet name=\"${NAME2}\" style=\"${STYLE}\" weight=\"${WEIGHT}\"/>"
    echo "</font>"

done
Zawartość katalogu /home/tools/fonts przed uruchomieniem skryptu:
DejaVuSans-Bold.ttf
DejaVuSans-BoldOblique.ttf
DejaVuSans-ExtraLight.ttf
DejaVuSans-Oblique.ttf
DejaVuSans.ttf
DejaVuSansCondensed-Bold.ttf
DejaVuSansCondensed-BoldOblique.ttf
DejaVuSansCondensed-Oblique.ttf
DejaVuSansCondensed.ttf
DejaVuSansMono-Bold.ttf
DejaVuSansMono-BoldOblique.ttf
DejaVuSansMono-Oblique.ttf
DejaVuSansMono.ttf
DejaVuSerif-Bold.ttf
DejaVuSerif-BoldItalic.ttf
DejaVuSerif-Italic.ttf
DejaVuSerif.ttf
DejaVuSerifCondensed-Bold.ttf
DejaVuSerifCondensed-BoldItalic.ttf
DejaVuSerifCondensed-Italic.ttf
DejaVuSerifCondensed.ttf
Poniżej rezultat uruchomienia skryptu:
<font metrics-url="/home/tools/fonts/DejaVuSans-Bold.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSans-Bold.ttf">
        <font-triplet name="DejaVuSans" style="normal" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSans-BoldOblique.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSans-BoldOblique.ttf">
        <font-triplet name="DejaVuSans" style="oblique" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSans-ExtraLight.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSans-ExtraLight.ttf">
        <font-triplet name="DejaVuSans-ExtraLight" style="normal" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSans-Oblique.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSans-Oblique.ttf">
        <font-triplet name="DejaVuSans" style="oblique" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSans.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSans.ttf">
        <font-triplet name="DejaVuSans" style="normal" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansCondensed-Bold.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansCondensed-Bold.ttf">
        <font-triplet name="DejaVuSansCondensed" style="normal" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansCondensed-BoldOblique.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansCondensed-BoldOblique.ttf">
        <font-triplet name="DejaVuSansCondensed" style="oblique" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansCondensed-Oblique.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansCondensed-Oblique.ttf">
        <font-triplet name="DejaVuSansCondensed" style="oblique" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansCondensed.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansCondensed.ttf">
        <font-triplet name="DejaVuSansCondensed" style="normal" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansMono-Bold.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansMono-Bold.ttf">
        <font-triplet name="DejaVuSansMono" style="normal" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansMono-BoldOblique.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansMono-BoldOblique.ttf">
        <font-triplet name="DejaVuSansMono" style="oblique" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansMono-Oblique.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansMono-Oblique.ttf">
        <font-triplet name="DejaVuSansMono" style="oblique" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansMono.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansMono.ttf">
        <font-triplet name="DejaVuSansMono" style="normal" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerif-Bold.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerif-Bold.ttf">
        <font-triplet name="DejaVuSerif" style="normal" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerif-BoldItalic.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerif-BoldItalic.ttf">
        <font-triplet name="DejaVuSerif" style="italic" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerif-Italic.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerif-Italic.ttf">
        <font-triplet name="DejaVuSerif" style="italic" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerif.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerif.ttf">
        <font-triplet name="DejaVuSerif" style="normal" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerifCondensed-Bold.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerifCondensed-Bold.ttf">
        <font-triplet name="DejaVuSerifCondensed" style="normal" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerifCondensed-BoldItalic.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerifCondensed-BoldItalic.ttf">
        <font-triplet name="DejaVuSerifCondensed" style="italic" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerifCondensed-Italic.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerifCondensed-Italic.ttf">
        <font-triplet name="DejaVuSerifCondensed" style="italic" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerifCondensed.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerifCondensed.ttf">
        <font-triplet name="DejaVuSerifCondensed" style="normal" weight="normal"/>
</font>

6 kwietnia 2011

Apache FOP na Apache Tomcat cz.4

Nie odpuściłem tego servleta... Po pewnych walkach (przypominam, że to moje pierwsze programowanie w Javie), udało się skompilować fop.war. Dokonane zmiany wyglądają tak:
--- FopServlet.java.org 2010-07-12 21:34:45.000000000 +0200
+++ FopServlet.java     2011-04-06 19:00:01.639060332 +0200
@@ -44,6 +44,10 @@ import org.apache.fop.apps.Fop;
 import org.apache.fop.apps.FopFactory;
 import org.apache.fop.apps.MimeConstants;

+import org.xml.sax.SAXException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 /**
  * Example servlet to generate a PDF from a servlet.
  * 
@@ -77,6 +81,8 @@ public class FopServlet extends HttpServ /** Name of the parameter used for the XSLT file */ protected static final String XSLT_REQUEST_PARAM = "xslt"; + private final Log log = LogFactory.getLog(FopServlet.class); + /** The TransformerFactory used to create Transformer instances */ protected TransformerFactory transFactory = null; /** The FopFactory used to create Fop instances */ @@ -94,6 +100,13 @@ public class FopServlet extends HttpServ //Configure FopFactory as desired this.fopFactory = FopFactory.newInstance(); this.fopFactory.setURIResolver(this.uriResolver); + try { + fopFactory.setUserConfig(new File("userconf.xml")); + } catch (IOException e) { + log.error("No such file or directory", e); + } catch (SAXException e) { + throw new ServletException(e); + } configureFopFactory(); }
Plik konfiguracyjny userconf.xml wrzucamy po prostu do katalogu z Tomcat'em. Jeśli brak jest pliku, generowany jest log, jeśli są błędy w strukturze xml, zgłaszany jest wyjątek.

Apache FOP na Apache Tomcat cz.3

Ta ścieżka została, odrzucona. Dlaczego? Nie wiem jak podpiąć plik konfiguracyjny. Edycja a później kompilacja nowego servleta nie wchodzi w grę. A potrzebuję własnego pliku konfiguracyjnego aby wrzucić fonty z polskimi znakami. Bez tego ani rusz. Walki skończyły się na tym, że zrobiłem najprostszego wrappera w PHP, generuję plik .fo, zapisuję w tempie, wywołuję bezpośrednio skrypt fop ze ścieżką do zmodyfikowanego pliku konfiguracyjnego i wypluwam do przeglądarki zawartość wygenerowanego pliku PDF. Wrapper wygląda następująco:
<?php
$fo_tmp = tempnam("/tmp", "fop_fo_");
$pdf_tmp = tempnam("/tmp", "fop_pdf_");

$fo = '<?xml version="1.0" encoding="utf-8"?>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
  <fo:layout-master-set>
    <fo:simple-page-master master-name="first" 
                           page-height="29.7cm" 
                           page-width="21cm"
                           margin-top="1cm"
                           margin-bottom="2cm"
                           margin-left="1cm"
                           margin-right="1cm">
      <fo:region-body margin-top=".5cm"/>
      <fo:region-before extent="0cm"/>
      <fo:region-after extent="0cm"/>
    </fo:simple-page-master>
  </fo:layout-master-set>
  <fo:page-sequence master-reference="first">
    <fo:flow flow-name="xsl-region-body">
      <fo:block">Hello World!</fo:block>
    </fo:flow>
  </fo:page-sequence>
</fo:root>';

file_put_contents($fo_tmp,$fo);
system("/home/tools/fop/fop -c /home/tools/fop/userconf.xml $fo_tmp $pdf_tmp");

$lastmodified = gmdate('D, d M Y H:i:s \G\M\T',time());
header("Content-Type: application/pdf");
header("Last-Modified: $lastmodified");
$pdf = @file_get_contents($pdf_tmp);
header("Content-Lenght: ".strlen($pdf));
header("Content-Length: ".strlen($pdf));
echo $pdf;
@unlink($fo_tmp);
@unlink($pdf_tmp);
exit;
?>
Należy pamiętać o odpowiedniej modyfikacji ścieżek. Po delikatnych modyfikacjach, możemy dostosować ten wrapper do konwersji plików xml+xslt.

31 marca 2011

Apache FOP na Apache Tomcat cz.2

Jak człowiek sam nie pokombinuje, to sobie może szukać w necie i oczywistych rzeczy nie zobaczy... Wystarczyło ściągnąć źródła FOPa, przekompilować wszystko ant, przekopiować fop.war do katalogu webapps/ i już. W Slackware, wystarczyło ściagnąć i odpalić Tomcat'a, wcześniej kopiując skompilowany pod Debianem fop.war do katalogu webapps/ i... gotowe.

Apache FOP na Apache Tomcat cz.1

Zachciało mi się to odpalać... ech... jak na razie pod górę... a chciałem tylko generować sobie skomplikowane PDFy w przeglądarce... Niby w manualach wygląda to na dziecinnie proste, ale schody są od początku. Pierwsze próby na Debianie, niby gotowe paczki i tak dalej a jednak nie wychodzi... To co się będzie działo, jak będę próbował to odpalać na Slacku... Mam nadzieję, że się uda, bo jak zobaczyłem FOPa w akcji, to szczena mi opadła... A jak się nie uda, to się zrobi jakiegoś wrappera w Perlu i już...

29 marca 2011

Ubiquiti cz.1

Ponieważ ostatnio mieliłem temat skanowania mikrotików, przyszło mi do głowy aby zrobić podobny skaner do urządzeń Ubiquiti. Dostępny jest oficjalny skaner, wieloplatformowy, napisany w Javie, działający w trybie graficznym, a... ja pracuję w konsoli i trzeba było sobie z tym jakoś poradzić. Z tym wykrywaniem ubiquiti jest malutki problem, zmienny format ramki. Ponieważ mam możliwość przeskanowania kilku rodzajów tych urządzeń, nie było więc problemu z rozszyfrowaniem ich struktury. Szczegóły techniczne zostawię na część drugą...

Orinoco

Jak już jestem w temacie skanowania urządzeń, pierwszym skryptem do skanowania jaki napisałem, był skaner access pointów Orinoco. Oczywiście istniała aplikacja do ich skanowania - ScanTool. Problem z tą aplikacją polegał na tym, że uruchamiała się tylko pod... Windows 98. Dopiero po dość długim czasie, zapewne po hotfix'ach wydanych przez Microsoft do swoich systemów, zaczął działać poprawnie pod Windows 2000 i XP. Ale tym razem nie będę się tu rozpisywał na temat samego skryptu, ponieważ sprzęt który wykorzystywaliśmy, stał się ciężko osiągalny i do tego został wyparty przez rozwiązania oparte na urządzeniach MikroTik. Jednakże gdy pojawią się zainteresowani, udostępnię kod.

26 marca 2011

MNDP cz.2

Aplikacja Winbox w momencie rozpoczęcia wykrywania urządzeń, wysyła na adresie rozgłoszeniowym trzy ramki UDP o wartości 0x00000000, na porcie 5678. Urządzenia odpowiadają ramką w formacie MNDP, bardzo podobną do CDP. Ponieważ skrypt do wykrywania mikrotików napisałem w perlu, strukturę ramki przedstawię jako argument funkcji unpack, a wygląda ona następująco:
@s = unpack "H2H2H4(H4n/a*)*", $ramka;

             MNDP           CDP
$s[0]        ??             wersja ramki CDP
$s[1]        ??             time-to-live
$s[2]        age?           suma kontrolna
(
    $s[3]    typ            typ
    X
    $s[4]    wartość        wartość
)
...
W tabeli zawarto porównanie elementów struktury ramki MNDP z ramką CDP. Trzy pierwsze elementy struktury są ciężkie do zidentyfikowania, na podstawie wartości które tam się pojawiają. Element oznaczony X to długość następującego po nim łańcucha. Element ten jest automatycznie "wchłaniany" przez unpack, dlatego nie występuje jako osobny element w zwracanej tablicy. Element $s[3], jak sama nazwa wskazuje, zawiera typ przesyłanej wartości. Rozpoznane typy:
    0x0001    MAC interfejsu z którego urządzenie wysłało odpowiedź
    0x0005    identyfikator urządzenia (domyślnie MikroTik)
    0x0007    wersja RouterOS/SwOS
    0x0008    platforma
    0x000a    uptime w sekundach (unsigned long)
    0x000b    software-id - jeśli dotyczy
    0x000c    model RouterBOARD'a - jeśli dotyczy
    0x000d    niezidentyfikowany - zawsze pusty łańcuch
Więcej typów nie zaobserwowałem, być może jest ich więcej (sprawdzałem ramki dla różnych wersji oprogramowania RouterOS oraz SwOS). Grup, składających się z elementów $s[3] i $s[4] jest kilka. Ilość i ich rodzaj, uzależniona jest od wersji RouterOS/SwOS. Na razie to tyle, skrypt w kolejnej części.

24 marca 2011

MNDP cz.1

MNDP (MikroTik Neighbor Discovery Protocol) to protokół wykorzystywany przez Mikrotik'a do odnajdywania urządzeń w sieci. Jeśli ktoś interesował się produktami firmy Mikrotik, wie na pewno co to jest Winbox. Ale... Winbox działa tylko pod Windows. Potrzebowałem narzędzia, które wykryje mi urządzenia w linuksowej konsoli. Kiedyś na innym swoim blogu, umieściłem źródła pierwszej wersji takiej aplikacji. Miała ona poważne błędy, potrafiła wywołać Segmentation fault w pewnych specyficznych warunkach, do tego nie wykrywała urządzeń o adresie IP nie należącym do podsieci postawionej na maszynie, na której uruchamiano aplikację. Co ciekawe, wykrywała urządzenia bez ustawionego adresu IP. Głównym problemem do rozwiązania, była interpretacja ramki MNDP. Po wielu próbach, gdy udało się to rozgryźć, okazało się że to niemal to samo co CDP (Cisco Discovery Protocol) Więc jak wygląda struktura ramki MNDP w praktyce? O tym w następnym odcinku...

23 marca 2011

RRDTool i BOINC

Przyczyną powstania tego małego projektu, było posiadanie własnych statystyk BOINC. Zaczęło się od wyciągania danych z plików xml bezpośrednio z menadżera BOINC. Ale... wymagało to zainstalowania klienta BOINC i dodania do niego wszystkich monitorowanych projektów. Projekt ewoluował... i teraz dane są wyciągane bezpośrednio z schedulerów projektów. Zawsze można pobierać dane z istniejących baz danych, ale dane te są aktualizowane jedynie kilka razy na dobę albo i rzadziej. W tym przypadku, ograniczeniem na częstotliwość pobrań danych, są ustawienia samego schedulera projektu.

Wszystko dokładnie jest opisane na osobnej stronie http://boinc.mindc.net, którą napisałem już jakiś czas temu, więc nie będę tu się rozpisywał...