Pokazywanie postów oznaczonych etykietą mikrotik. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą mikrotik. Pokaż wszystkie posty

11 czerwca 2011

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";
    }
}

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.

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...