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

Brak komentarzy: