1 |
#!/usr/bin/perl |
2 |
|
3 |
use Event; |
4 |
use Time::HiRes qw(time); |
5 |
use Curses; |
6 |
use Socket; |
7 |
use GPS; |
8 |
|
9 |
my $GPS = eval { new GPS }; |
10 |
|
11 |
$DEV = "eth1"; |
12 |
$RAWDEV = "wifi0"; |
13 |
$PROC = "/proc/driver/aironet/$DEV"; |
14 |
|
15 |
$SIG{INT} = |
16 |
$SIG{TERM} = |
17 |
$SIG{HUP} = sub { exit }; |
18 |
|
19 |
sub config { |
20 |
open my $config, ">$PROC/Config" |
21 |
or die "$PROC/Config: $!"; |
22 |
print $config $_[0]; |
23 |
} |
24 |
|
25 |
config "Mode: yna\n"; |
26 |
system "ifconfig", $DEV, "up"; |
27 |
system "ifconfig", $RAWDEV, "up"; |
28 |
|
29 |
my $initscr; |
30 |
sub init_screen { |
31 |
initscr; |
32 |
$initscr = 1; |
33 |
start_color; |
34 |
cbreak; noecho; |
35 |
immedok 0; nonl; raw; intrflush 0; keypad 1; |
36 |
idlok 1; scrollok 0; leaveok 0; |
37 |
} |
38 |
|
39 |
sub end_win { |
40 |
endwin if $initscr--; |
41 |
} |
42 |
|
43 |
init_screen; |
44 |
|
45 |
my $refresh = Event->idle(nice => -5, parked => 1, min => 0.01, max => 1, repeat => 0, cb => sub { |
46 |
@menu = (); |
47 |
&$curmenu; |
48 |
|
49 |
$menu_idx += @menu if $menu_idx < 0; |
50 |
$menu_idx -= @menu if $menu_idx >= @menu; |
51 |
|
52 |
erase; |
53 |
|
54 |
my $data = $GPS ? $GPS->data : {}; |
55 |
|
56 |
move 0, 0; |
57 |
addstr $frame++ . ": " . localtime($data->{time}) . " $data->{lat} $data->{long} $data->{alt}m"; |
58 |
|
59 |
for my $idx (0 .. $#menu) { |
60 |
move 2 + $idx, 0; |
61 |
standout if $idx == $menu_idx; |
62 |
addstr $menu[$idx][0]; |
63 |
standend if $idx == $menu_idx; |
64 |
} |
65 |
|
66 |
move 3 + scalar@menu, 0; |
67 |
eval { |
68 |
$menu[$menu_idx][1]->(); |
69 |
}; |
70 |
|
71 |
refresh; |
72 |
}); |
73 |
|
74 |
Event->io(fd => \*STDIN, poll => 'r', cb => sub { |
75 |
my $ch = getch; |
76 |
if ($ch eq "\x1b" or $ch eq "q") { |
77 |
Event::unloop; |
78 |
} elsif ($ch eq KEY_DOWN or $ch eq "j") { |
79 |
$menu_idx++; |
80 |
} elsif ($ch eq KEY_UP or $ch eq "k") { |
81 |
$menu_idx--; |
82 |
} else { |
83 |
eval { |
84 |
$menu[$menu_idx][2]->($ch); |
85 |
}; |
86 |
} |
87 |
$refresh->start; |
88 |
}); |
89 |
|
90 |
END { |
91 |
end_win; |
92 |
config "Mode: adhoc\n"; |
93 |
} |
94 |
|
95 |
sub decode_tags { |
96 |
my $pkt = shift; |
97 |
my %tag; |
98 |
|
99 |
while ($pkt) { |
100 |
my ($id, $len) = unpack "C C", $pkt; |
101 |
my $data = substr $pkt, 2, $len; |
102 |
$pkt = substr $pkt, 2 + $len; |
103 |
|
104 |
$id = ({ |
105 |
0 => SSID, |
106 |
1 => rates, |
107 |
2 => FH, |
108 |
3 => DS, |
109 |
4 => CF, |
110 |
5 => TIM, |
111 |
6 => IBSS, |
112 |
16 => challenge, |
113 |
})->{$id}; |
114 |
$tag{$id} = $data; |
115 |
} |
116 |
|
117 |
%tag; |
118 |
} |
119 |
|
120 |
sub PF_PACKET (){ 17 } |
121 |
sub SOCK_RAW (){ 3 } |
122 |
sub ETH_P_ALL (){ 0x0003 } |
123 |
sub SIOCGIFINDEX (){ 0x000008933 } |
124 |
|
125 |
sub open_pcap_socket { |
126 |
socket my $cap, PF_PACKET, SOCK_RAW, (unpack "s", pack "n", ETH_P_ALL) |
127 |
or die "unable to open packet socket: $!"; |
128 |
|
129 |
my $ifidx = pack "a16 I", $RAWDEV; |
130 |
ioctl $cap, SIOCGIFINDEX, $ifidx |
131 |
or die "can't get index of interface $RAWDEV: $!"; |
132 |
$ifidx = unpack "x16 I", $ifidx; |
133 |
|
134 |
bind $cap, pack "S! S! I S! C C a8", PF_PACKET, (unpack "s", pack "n", ETH_P_ALL), $ifidx |
135 |
or die "unable to bind to interface: $!"; |
136 |
|
137 |
$cap; |
138 |
} |
139 |
|
140 |
sub e2h($) { |
141 |
join ":", map unpack("H*", $_), split //, $_[0]; |
142 |
} |
143 |
|
144 |
sub add_menu { |
145 |
my ($title, $display, $select) = @_; |
146 |
push @menu, [$title, $display, $select]; |
147 |
} |
148 |
|
149 |
$curmenu = \&menu_bss_list; |
150 |
|
151 |
sub bss2string { |
152 |
my $bss = shift; |
153 |
|
154 |
my $s = "AGE(" . int(time - $bss->{ts}) . ") " |
155 |
. "IP($bss->{ip}) " |
156 |
. "ARP($bss->{arp}) "; |
157 |
while (my ($k, $v) = each %{$bss->{ap}}) { |
158 |
$s .= "(" . e2h($k) . " $v->{mode} '$v->{essid}') "; |
159 |
} |
160 |
$s; |
161 |
} |
162 |
|
163 |
sub show_map { |
164 |
my ($map, $w, $h) = @_; |
165 |
|
166 |
my @data = (("-" x $w) x $h); |
167 |
|
168 |
my ($x1, $y1, $x2, $y2) = (1e6, 1e6, 1e-6, 1e-6); |
169 |
|
170 |
while (my ($k, $v) = each %$map) { |
171 |
my ($y, $x) = split /;/, $k; |
172 |
$x1 = $x if $x1 > $x; |
173 |
$x2 = $x if $x2 < $x; |
174 |
$y1 = $y if $y1 > $y; |
175 |
$y2 = $y if $y2 < $y; |
176 |
} |
177 |
|
178 |
$x2 = ($w - 1) / (($x2 - $x1) || 1e-6); |
179 |
$y2 = ($h - 1) / (($y2 - $y1) || 1e-6); |
180 |
|
181 |
while (my ($k, $v) = each %$map) { |
182 |
my ($y, $x) = split /;/, $k; |
183 |
|
184 |
$y = ($y - $y1) * $y2; |
185 |
$x = ($x - $x1) * $x2; |
186 |
|
187 |
substr $data[$y], $x, 1, "*"; |
188 |
} |
189 |
|
190 |
for (@data) { |
191 |
addstr $_ . "\n"; |
192 |
} |
193 |
} |
194 |
|
195 |
sub display_bss { |
196 |
my $bss = shift; |
197 |
|
198 |
addstr "TS: $bss->{ts}\n"; |
199 |
addstr "WEP/ARP/IP packets received: $bss->{wep}/$bss->{arp}/$bss->{ip}\n"; |
200 |
|
201 |
addstr "\naccess points\n"; |
202 |
|
203 |
while (my ($k, $v) = each %{$bss->{ap}}) { |
204 |
addstr " " . e2h($k) . "\n"; |
205 |
addstr " mode $v->{mode}; channel $v->{channel}; essid '$v->{essid}'; beacon frames $v->{beacon}\n"; |
206 |
} |
207 |
|
208 |
addstr "\nstations\n"; |
209 |
|
210 |
while (my ($k, $v) = each %{$bss->{station}}) { |
211 |
addstr " " . e2h($k); |
212 |
while (my ($k, $v) = each %$v) { |
213 |
addstr " " . (inet_ntoa $k) . "($v)"; |
214 |
} |
215 |
addstr "\n"; |
216 |
} |
217 |
|
218 |
if ($bss->{gps}) { |
219 |
addstr "\nmap\n"; |
220 |
show_map $bss->{gps}, 40, 20; |
221 |
} |
222 |
} |
223 |
|
224 |
sub menu_bss_list { |
225 |
my @menu; |
226 |
while (my ($k, $v) = each %$db) { |
227 |
add_menu e2h($k) . " " . bss2string($v), |
228 |
sub { display_bss $v }, |
229 |
sub { |
230 |
if ($_[0] eq KEY_LEFT or $_[0] eq "h") { |
231 |
Event::unloop; |
232 |
} elsif ($_[0] eq KEY_RIGHT or $_[0] eq "l") { |
233 |
#$curmenu = sub { display_bss $k }; |
234 |
} |
235 |
}; |
236 |
} |
237 |
} |
238 |
|
239 |
sub activity { |
240 |
if ($GPS) { |
241 |
my $data = $GPS->data; |
242 |
$db->{$_[0]}{gps}{"$data->{lat};$data->{long}"} = 1; |
243 |
} |
244 |
$db->{$_[0]}->{ts} = time; |
245 |
$refresh->start; |
246 |
} |
247 |
|
248 |
sub reg_ip { |
249 |
my ($bssid, $ip, $ether) = @_; |
250 |
$db->{$bssid}{station}{$ether}{$ip}++; |
251 |
activity $bssid; |
252 |
} |
253 |
|
254 |
{ |
255 |
my $cap = open_pcap_socket; |
256 |
my $pkt; |
257 |
|
258 |
Event->io(fd => $cap, poll => 'r', cb => sub { |
259 |
sysread $$cap, $pkt, 120; |
260 |
|
261 |
my ($fc1, $fc2, $sid, $a1, $a2, $a3, $sc, $pkt) |
262 |
= unpack "C C n a6 a6 a6 S a*", $pkt; |
263 |
|
264 |
$pkt = substr $pkt, 6 if $fc2 & 0x03 == 0x03; # skip A4 |
265 |
|
266 |
if ($fc1 == 0x80 or $fc1 == 0x50) { |
267 |
my ($ts1, $ts2, $bi, $cf, $pkt) |
268 |
= unpack "L L S S a*", $pkt; |
269 |
|
270 |
my %tag = decode_tags $pkt; |
271 |
|
272 |
my $ap = $db->{$a3}{ap}{$a2} ||= {}; |
273 |
$ap->{mode} = ($cf & 3) == 1 ? "AP" : "adhoc"; |
274 |
$ap->{essid} = $tag{SSID}; |
275 |
$ap->{channel} = ord $tag{DS}; |
276 |
$ap->{beacon}++; |
277 |
|
278 |
activity $a3; |
279 |
} elsif ($fc1 == 0x08) { |
280 |
my $bssid = ($a3, $a2, $a1)[$fc2 & 3]; |
281 |
|
282 |
if ($fc2 & 0x40) { |
283 |
$db->{$bssid}{wep}++; |
284 |
} else { |
285 |
my ($llc, $et, $pkt) = unpack "a6 n a*", $pkt; |
286 |
if ($llc eq "\xaa\xaa\x03\x00\x00\x00") { # SNAP/UI/ENCAP_ETHER |
287 |
if ($et == 0x0800) { |
288 |
my ($vhl, $tos, $len, $id, $off, $ttl, $prot, $sum, $src, $dst, $pkt) |
289 |
= unpack "C C n n n C C n a4 a4 a*", $pkt; |
290 |
if ($vhl & 0x40 == 0x40) { |
291 |
$db->{$bssid}{ip}++; |
292 |
reg_ip $bssid, $src, $a2; |
293 |
reg_ip $bssid, $dst, $a1; |
294 |
0 && |
295 |
printf "IP: %02x %02x %04x %04x: %s %s> %s %s %02x \n", |
296 |
$fc1, $fc2, $sid, $sc, |
297 |
(unpack "H*", $a2), (inet_ntoa $src), |
298 |
(unpack "H*", $a1), (inet_ntoa $dst), |
299 |
$prot; |
300 |
} |
301 |
} elsif ($et == 0x0806) { |
302 |
my ($hrd, $pro, $hln, $pln, $op, $src_hw, $src, $dst_hw, $dst) |
303 |
= unpack "n n C C n a6 a4 a6 a4", $pkt; |
304 |
if ($hrd == 1 and $hln == 6 and $pln == 4) { |
305 |
reg_ip $bssid, $src, $src_hw; |
306 |
reg_ip $bssid, $dst, $dst_hw if $op != 1; |
307 |
$db->{$bssid}{arp}++; |
308 |
0 && |
309 |
printf "ARP: %04x> %s %s : %s %s\n", |
310 |
$op, |
311 |
(unpack "H*", $src_hw), (inet_ntoa $src), |
312 |
(unpack "H*", $dst_hw), (inet_ntoa $dst); |
313 |
} |
314 |
} |
315 |
} else { |
316 |
0 && |
317 |
printf "?? %02x %02x %04x %04x: %s.%s \n", $fc1, $fc2, $sid, $sc, (unpack "H*", $llc), unpack "H*", $pkt; |
318 |
} |
319 |
} |
320 |
} elsif ($fc1 != 0x40 and $fc1 != 0x10 and $fc1 != 0x00 and $fc1 != 0xb0) { |
321 |
0 && |
322 |
printf "%02x %02x %04x %04x: %s \n", $fc1, $fc2, $sid, $sc, unpack "H*", $pkt; |
323 |
} |
324 |
}); |
325 |
} |
326 |
|
327 |
Event::loop; |
328 |
|
329 |
|