-
Notifications
You must be signed in to change notification settings - Fork 46
/
wifi_scan.c
897 lines (727 loc) · 31.3 KB
/
wifi_scan.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
/*
* wifi-scan library implementation
*
* Copyright 2016-2018 (C) Bartosz Meglicki <[email protected]>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
*/
/*
* Library Overview
*
* This library uses netlink nl80211 user-space interface to retrieve wireless device information from kernel-space.
* For netlink communication libmnl is used (minimalistic user-space netlink library).
*
* First concept you need to understand is that netlink uses sockets to communicate between user-space and kernel-space.
*
* There are 2 netlink communication channels (sockets/buffers)
* - for notifications (about triggers, ready scan results)
* - for commands (commanding triggers, retrieving scan results, station information)
*
* wifi_scan_init initializes 2 channels, gets nl80211 id using generic netlink (genetlink), gets id of
* multicast group scan and subscribes to this group notifcations using notifications channel.
*
* wifi_scan_station gets the last (not necessarilly fresh) scan results that are available from the device,
* checks which station we are associated with and retrieves information about this station (using commands channel)
*
* wifi_scan_all reads up any pending notifications, commands a trigger if necessary, waits for the device to gather
* results and finally reads scan results with get_scan function (those are fresh results)
*
* wifi_scan_close frees up resources of two channels and any other resoureces that library uses.
*
* prepare_nl_messsage/send_nl_message/receive_nl_message are helper functions to simplify common tasks when issuing commands
*
* validate function simplifies common tasks (validates each attribute against table specifying what is valid)
*
*/
#include "wifi_scan.h"
#include <libmnl/libmnl.h> //netlink libmnl
#include <linux/nl80211.h> //nl80211 netlink
#include <linux/genetlink.h> //generic netlink
#include <net/if.h>
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <fcntl.h> //fntnl (set descriptor options)
#include <errno.h> //errno
// everything needed for sending/receiving with netlink
struct netlink_channel
{
struct mnl_socket *nl; //netlink socket
char *buf; //buffer for messages (in and out)
uint16_t nl80211_id; //generic netlink nl80211 id
uint32_t ifindex; //the wireless interface number (e.g. interface number for wlan0)
uint32_t sequence; //the sequence number of netlink message
void *context; //additional data to be stored/used when processing concrete message
};
// internal library data passed around by user
struct wifi_scan
{
struct netlink_channel notification_channel;
struct netlink_channel command_channel;
};
// DECLARATIONS AND TOP-DOWN LIBRARY OVERVIEW
// INITIALIZATION
// data needed from CTRL_CMD_GETFAMILY for nl80211, nl80211 id is stored in the channel rather then here
struct context_CTRL_CMD_GETFAMILY
{
uint32_t id_NL80211_MULTICAST_GROUP_SCAN; //the id of group scan which we need to subscribe to
};
// public interface - initialize the library for wireless interface (e.g. wlan0)
struct wifi_scan *wifi_scan_init(const char *interface);
// allocate memory, set initial values, etc.
static void init_netlink_channel(struct netlink_channel *channel, const char *interface);
// create netlink sockets for generic netlink
static void init_netlink_socket(struct netlink_channel *channel);
// execute command to get nl80211 family and process the results
static int get_family_and_scan_ids(struct netlink_channel *channel);
// this processes kernel reply for get family request, stores family id
static int handle_CTRL_CMD_GETFAMILY(const struct nlmsghdr *nlh, void *data);
// parses multicast groups to get scan multicast group id
static void parse_CTRL_ATTR_MCAST_GROUPS(struct nlattr *nested, struct netlink_channel *channel);
// subscribes channel to multicast group scan using scan group id
static void subscribe_NL80211_MULTICAST_GROUP_SCAN(struct netlink_channel *channel, uint32_t scan_group_id);
// CLEANUP
// public interface - cleans up after library
void wifi_scan_close(struct wifi_scan *wifi);
// cleans up after single channel
static void close_netlink_channel(struct netlink_channel *channel);
// SCANNING
// public interface - trigger scan if necessary, retrieve information about all known BSSes
int wifi_scan_all(struct wifi_scan *wifi, struct bss_info *bss_infos, int bss_infos_length);
// SCANNING - notification related
// the data needed from notifications
struct context_NL80211_MULTICAST_GROUP_SCAN
{
int new_scan_results; //are new scan results waiting for us?
int scan_triggered; //was scan was already triggered by somebody else?
};
// read but do not block
static void read_past_notifications(struct netlink_channel *notifications);
// go non-blocking
static void set_channel_non_blocking(struct netlink_channel *channel);
// go back blocking
static void set_channel_blocking(struct netlink_channel *channel);
// this handles notifications
static int handle_NL80211_MULTICAST_GROUP_SCAN(const struct nlmsghdr *nlh, void *data);
// triggers scan if no results are waiting yet and if it was not already triggered
static int trigger_scan_if_necessary(struct netlink_channel *commands, struct context_NL80211_MULTICAST_GROUP_SCAN *scanning);
// triggers the scan
static int trigger_scan(struct netlink_channel *channel);
// wait for the notification that scan finished
static void wait_for_new_scan_results(struct netlink_channel *notifications);
// SCANNING - scan related
// the data needed from new scan results
struct context_NL80211_CMD_NEW_SCAN_RESULTS
{
struct bss_info *bss_infos;
int bss_infos_length;
int scanned;
};
// get scan results cached by the driver
static int get_scan(struct netlink_channel *channel);
// process the new scan results
static int handle_NL80211_CMD_NEW_SCAN_RESULTS(const struct nlmsghdr *nlh, void *data);
// get the information about bss (nested attribute)
static void parse_NL80211_ATTR_BSS(struct nlattr *nested, struct netlink_channel *channel);
// get the information from IE (non-netlink binary data here!)
static void parse_NL80211_BSS_INFORMATION_ELEMENTS(struct nlattr *attr, char SSID_OUT[33]);
// get BSSID (mac address)
static void parse_NL80211_BSS_BSSID(struct nlattr *attr, uint8_t bssid_out[BSSID_LENGTH]);
// STATION
// data needed from command new station
struct context_NL80211_CMD_NEW_STATION
{
struct station_info *station;
};
// public interface - get information about station we are associated with
int wifi_scan_station(struct wifi_scan *wifi,struct station_info *station);
// get information about station with BSSID
static int get_station(struct netlink_channel *channel, uint8_t bssid[BSSID_LENGTH]);
// process command new station
static int handle_NL80211_CMD_NEW_STATION(const struct nlmsghdr *nlh, void *data);
// process station info (nested attribute)
static void parse_NL80211_ATTR_STA_INFO(struct nlattr *nested, struct netlink_channel *channel);
// NETLINK HELPERS
// NETLINK HELPERS - message construction/sending/receiving
// create the message with specified parameters for the channel
// fill the message with additional attributes as needed with:
// mnl_attr_put_[|u8|u16|u32|u64|str|strz] and mnl_attr_nest_[start|end]
static struct nlmsghdr *prepare_nl_message(uint32_t type, uint16_t flags, uint8_t genl_cmd, struct netlink_channel *channel);
// send the above message
static void send_nl_message(struct nlmsghdr *nlh, struct netlink_channel *channel);
// receive the results and process them using callback function
static int receive_nl_message(struct netlink_channel *channel, mnl_cb_t callback);
// NETLINK HELPERS - validation
// formal requirements for attribute
struct attribute_validation
{
int attr; // attribute constant from nl80211.h
enum mnl_attr_data_type type; // MNL_TYPE_[UNSPEC|U8|U16|U32|U64|STRING|FLAG|MSECS|NESTED|NESTED_COMPAT|NUL_STRING|BINARY]
size_t len; // length in bytes, can be ommitted for attibutes of known size (e.g. U16), can be 0 if unspeciffied
};
// all information needed to validate attributes
struct validation_data
{
struct nlattr **attribute_table; //validated attributes are returned here
int attribute_length; //at most that many, distinct constants from nl80211.h go here
const struct attribute_validation *validation; //vavildate against that table
int validation_length;
};
// data of type struct validation_data*, validate attr against data, this is called for each attribute
static int validate(const struct nlattr *attr, void *data);
// GENNERAL PURPOSE
// if anything goes wrong...
static void die(const char *s);
// as above but scream errno
static void die_errno(const char *s);
// #####################################################################
// IMPLEMENTATION
// validate only what we are going to use, note that
// this lists all the attributes used by the library
const struct attribute_validation NL80211_VALIDATION[]={
{CTRL_ATTR_FAMILY_ID, MNL_TYPE_U16},
{CTRL_ATTR_MCAST_GROUPS, MNL_TYPE_NESTED} };
const struct attribute_validation NL80211_MCAST_GROUPS_VALIDATION[]={
{CTRL_ATTR_MCAST_GRP_ID, MNL_TYPE_U32},
{CTRL_ATTR_MCAST_GRP_NAME, MNL_TYPE_STRING} };
const struct attribute_validation NL80211_BSS_VALIDATION[]={
{NL80211_BSS_BSSID, MNL_TYPE_BINARY, 6},
{NL80211_BSS_FREQUENCY, MNL_TYPE_U32},
{NL80211_BSS_INFORMATION_ELEMENTS, MNL_TYPE_BINARY},
{NL80211_BSS_STATUS, MNL_TYPE_U32},
{NL80211_BSS_SIGNAL_MBM, MNL_TYPE_U32},
{NL80211_BSS_SEEN_MS_AGO, MNL_TYPE_U32} };
const struct attribute_validation NL80211_NEW_SCAN_RESULTS_VALIDATION[]={
{NL80211_ATTR_IFINDEX, MNL_TYPE_U32},
{NL80211_ATTR_SCAN_SSIDS, MNL_TYPE_NESTED},
{NL80211_ATTR_BSS, MNL_TYPE_NESTED} };
const struct attribute_validation NL80211_CMD_NEW_STATION_VALIDATION[]={
{NL80211_ATTR_STA_INFO, MNL_TYPE_NESTED},
};
const struct attribute_validation NL80211_STA_INFO_VALIDATION[]={
{NL80211_STA_INFO_SIGNAL, MNL_TYPE_U8},
{NL80211_STA_INFO_SIGNAL_AVG, MNL_TYPE_U8},
{NL80211_STA_INFO_RX_PACKETS, MNL_TYPE_U32},
{NL80211_STA_INFO_TX_PACKETS, MNL_TYPE_U32}
};
const int NL80211_VALIDATION_LENGTH=sizeof(NL80211_VALIDATION)/sizeof(struct attribute_validation);
const int NL80211_MCAST_GROUPS_VALIDATION_LENGTH=sizeof(NL80211_MCAST_GROUPS_VALIDATION)/sizeof(struct attribute_validation);
const int NL80211_BSS_VALIDATION_LENGTH=sizeof(NL80211_BSS_VALIDATION)/sizeof(struct attribute_validation);
const int NL80211_NEW_SCAN_RESULTS_VALIDATION_LENGTH=sizeof(NL80211_NEW_SCAN_RESULTS_VALIDATION)/sizeof(struct attribute_validation);
const int NL80211_CMD_NEW_STATION_VALIDATION_LENGTH=sizeof(NL80211_CMD_NEW_STATION_VALIDATION)/sizeof(struct attribute_validation);
const int NL80211_STA_INFO_VALIDATION_LENGTH=sizeof(NL80211_STA_INFO_VALIDATION)/sizeof(struct attribute_validation);
// INITIALIZATION
// public interface - pass wireless interface like wlan0
struct wifi_scan *wifi_scan_init(const char *interface)
{
struct wifi_scan *wifi=malloc(sizeof(struct wifi_scan));
if(wifi==NULL)
die("Insufficient memory - malloc(sizeof(struct wifi_data)");
init_netlink_channel(&wifi->notification_channel, interface);
struct context_CTRL_CMD_GETFAMILY family_context ={0};
wifi->notification_channel.context=&family_context;
if(get_family_and_scan_ids(&wifi->notification_channel) == -1)
die_errno("GetFamilyAndScanId failed");
if(family_context.id_NL80211_MULTICAST_GROUP_SCAN == 0)
die("No scan multicast group in generic netlink nl80211\n");
init_netlink_channel(&wifi->command_channel, interface);
wifi->command_channel.nl80211_id = wifi->notification_channel.nl80211_id;
subscribe_NL80211_MULTICAST_GROUP_SCAN(&wifi->notification_channel, family_context.id_NL80211_MULTICAST_GROUP_SCAN);
return wifi;
}
// prerequisities:
// - proper interface, e.g. wlan0, wlan1
static void init_netlink_channel(struct netlink_channel *channel, const char *interface)
{
channel->sequence=1;
channel->buf=(char*) malloc(MNL_SOCKET_BUFFER_SIZE);
if(channel->buf == NULL)
die("Insufficent memory for netlink socket buffer");
channel->ifindex=if_nametoindex(interface);
if(channel->ifindex==0)
die_errno("Incorrect network interface");
channel->context=NULL;
init_netlink_socket(channel);
}
static void init_netlink_socket(struct netlink_channel *channel)
{
channel->nl = mnl_socket_open(NETLINK_GENERIC);
if (channel->nl == NULL)
die_errno("mnl_socket_open");
if (mnl_socket_bind(channel->nl, 0, MNL_SOCKET_AUTOPID) < 0)
die_errno("mnl_socket_bind");
}
// prerequisities:
// - channel initialized with init_netlink_channel
// - channel context of type context_CTRL_CMD_GETFAMILY
static int get_family_and_scan_ids(struct netlink_channel *channel)
{
struct nlmsghdr *nlh=prepare_nl_message(GENL_ID_CTRL, NLM_F_REQUEST | NLM_F_ACK, CTRL_CMD_GETFAMILY, channel);
mnl_attr_put_u16(nlh, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL);
mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, NL80211_GENL_NAME);
send_nl_message(nlh, channel);
return receive_nl_message(channel, handle_CTRL_CMD_GETFAMILY);
}
// prerequisities:
// - netlink_channel passed as data
// - data->context of type struct context_CTRL_CMD_GETFAMILY
static int handle_CTRL_CMD_GETFAMILY(const struct nlmsghdr *nlh, void *data)
{
struct nlattr *tb[CTRL_ATTR_MAX+1] = {};
struct genlmsghdr *genl = (struct genlmsghdr *)mnl_nlmsg_get_payload(nlh);
struct netlink_channel *channel = (struct netlink_channel*)data;
struct validation_data vd={tb, CTRL_ATTR_MAX, NL80211_VALIDATION, NL80211_VALIDATION_LENGTH};
mnl_attr_parse(nlh, sizeof(*genl), validate, &vd);
if (!tb[CTRL_ATTR_FAMILY_ID])
die("No family id attribute");
channel->nl80211_id=mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
if (tb[CTRL_ATTR_MCAST_GROUPS])
parse_CTRL_ATTR_MCAST_GROUPS(tb[CTRL_ATTR_MCAST_GROUPS], channel);
return MNL_CB_OK;
}
// prerequisities:
// - data->context of type struct context_CTRL_CMD_GETFAMILY
static void parse_CTRL_ATTR_MCAST_GROUPS(struct nlattr *nested, struct netlink_channel *channel)
{
struct nlattr *pos;
mnl_attr_for_each_nested(pos, nested)
{
struct nlattr *tb[CTRL_ATTR_MCAST_GRP_MAX+1] = {};
struct validation_data vd={tb, CTRL_ATTR_MCAST_GRP_MAX, NL80211_MCAST_GROUPS_VALIDATION, NL80211_MCAST_GROUPS_VALIDATION_LENGTH};
mnl_attr_parse_nested(pos, validate, &vd);
if ( tb[CTRL_ATTR_MCAST_GRP_NAME])
{
const char *name=mnl_attr_get_str(tb[CTRL_ATTR_MCAST_GRP_NAME]);
if( strcmp(name, "scan") == 0 )
{
if (tb[CTRL_ATTR_MCAST_GRP_ID])
{
struct context_CTRL_CMD_GETFAMILY *context=channel->context;
context->id_NL80211_MULTICAST_GROUP_SCAN= mnl_attr_get_u32(tb[CTRL_ATTR_MCAST_GRP_ID]);
}
else
die("Missing id attribute for scan multicast group");
}
}
}
}
// prerequisities:
// - channel initialized with init_netlink_channel
static void subscribe_NL80211_MULTICAST_GROUP_SCAN(struct netlink_channel *channel, uint32_t scan_group_id)
{
if (mnl_socket_setsockopt(channel->nl, NETLINK_ADD_MEMBERSHIP, &scan_group_id, sizeof(int)) < 0)
die_errno("mnl_socket_set_sockopt");
}
// CLEANUP
// prerequisities:
// - wifi initialized with wifi_scan_init
void wifi_scan_close(struct wifi_scan *wifi)
{
close_netlink_channel(&wifi->notification_channel);
close_netlink_channel(&wifi->command_channel);
free(wifi);
}
// prerequisities:
// - channel initalized with init_netlink-channel
static void close_netlink_channel(struct netlink_channel *channel)
{
free(channel->buf);
mnl_socket_close(channel->nl);
}
// SCANNING
// handle also trigger abort
// public interface
//
// prerequisities:
// - wifi initialized with wifi_scan_init
// - bss_info table of sized bss_info_length passed
int wifi_scan_all(struct wifi_scan *wifi, struct bss_info *bss_infos, int bss_infos_length)
{
struct netlink_channel *notifications=&wifi->notification_channel;
struct context_NL80211_MULTICAST_GROUP_SCAN scanning={0,0};
notifications->context=&scanning;
struct netlink_channel *commands=&wifi->command_channel;
struct context_NL80211_CMD_NEW_SCAN_RESULTS scan_results = {bss_infos, bss_infos_length, 0};
commands->context=&scan_results;
//somebody else might have triggered scanning or even the results can be already waiting
read_past_notifications(notifications);
//if no results yet or scan not triggered then trigger it.
//the device can be busy - we have to take it into account
if( trigger_scan_if_necessary(commands, &scanning) == -1)
return -1; //most likely with errno set to EBUSY
//now just wait for trigger/new_scan_results
wait_for_new_scan_results(notifications);
//finally read the scan
get_scan(commands);
return scan_results.scanned;
}
// SCANNING - notification related
// prerequisities
// - subscribed to scan group with subscribe_NL80211_MULTICAST_GROUP_SCAN
// - context_NL80211_MULTICAST_GROUP_SCAN set for notifications
static void read_past_notifications(struct netlink_channel *notifications)
{
set_channel_non_blocking(notifications);
int ret, run_ret;
while( (ret = mnl_socket_recvfrom(notifications->nl, notifications->buf, MNL_SOCKET_BUFFER_SIZE) ) >= 0)
{
//the line below fills context about past scans/triggers
run_ret = mnl_cb_run(notifications->buf, ret, 0, 0, handle_NL80211_MULTICAST_GROUP_SCAN, notifications);
if(run_ret <= 0)
die_errno("ReadPastNotificationsNonBlocking mnl_cb_run failed");
}
if(ret == -1)
if( !(errno == EINPROGRESS || errno == EWOULDBLOCK) )
die_errno("ReadPastNotificationsNonBlocking mnl_socket_recv failed");
//no more notifications waiting
set_channel_blocking(notifications);
}
// prerequisities
// - channel initialized with init_netlink_channel
static void set_channel_non_blocking(struct netlink_channel *channel)
{
int fd = mnl_socket_get_fd(channel->nl);
int flags = fcntl(fd, F_GETFL, 0);
if(flags == -1)
die_errno("SetChannelNonBlocking F_GETFL");
if( fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
die_errno("SetChannelNonBlocking F_SETFL");
}
// prerequisities
// - channel initialized with init_netlink_channel
static void set_channel_blocking(struct netlink_channel *channel)
{
int fd = mnl_socket_get_fd(channel->nl);
int flags = fcntl(fd, F_GETFL, 0);
if(flags == -1)
die_errno("SetChannelNonBlocking F_GETFL");
if( fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) == -1)
die_errno("SetChannelNonBlocking F_SETFL");
}
// prerequisities:
// - subscribed to scan group with subscribe_NL80211_MULTICAST_GROUP_SCAN
// - netlink_channel passed as data
// - data->context of type struct context_NL80211_MULTICAST_GROUP_SCAN
static int handle_NL80211_MULTICAST_GROUP_SCAN(const struct nlmsghdr *nlh, void *data)
{
struct netlink_channel *channel=data;
struct context_NL80211_MULTICAST_GROUP_SCAN *context = channel->context;
struct genlmsghdr *genl = (struct genlmsghdr *)mnl_nlmsg_get_payload(nlh);
// printf("Got message type %d seq %d pid %d genl cmd %d \n", nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd);
if(genl->cmd == NL80211_CMD_TRIGGER_SCAN)
{
context->scan_triggered=1;
// printf("TRIGGER type %u seq %u pid %u genl cmd %u\n", nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd);
return MNL_CB_OK; //do nothing for now
}
else if(genl->cmd == NL80211_CMD_NEW_SCAN_RESULTS)
{
// printf("NEW SCAN RESULTS type %u seq %u pid %u genl cmd %u\n", nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd);
if(nlh->nlmsg_pid==0 && nlh->nlmsg_seq==0)
context->new_scan_results = 1;
return MNL_CB_OK; //do nothing for now
}
else
{
fprintf(stderr, "Ignoring generic netlink command type %u seq %u pid %u genl cmd %u\n",nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd);
return MNL_CB_OK;
}
}
// prerequisities:
// - commands initialized with init_netlink_channel
// - scanning updated with read_past_notifications
static int trigger_scan_if_necessary(struct netlink_channel *commands, struct context_NL80211_MULTICAST_GROUP_SCAN *scanning)
{
if(!scanning->new_scan_results && !scanning->scan_triggered)
if(trigger_scan(commands) == -1)
return -1; //most likely errno set to EBUSY which means hardware is doing something else, try again later
return 0;
}
// prerequisities:
// - channel initialized with init_netlink_channel
static int trigger_scan(struct netlink_channel *channel)
{
struct nlmsghdr *nlh=prepare_nl_message(channel->nl80211_id, NLM_F_REQUEST | NLM_F_ACK, NL80211_CMD_TRIGGER_SCAN, channel);
mnl_attr_put_u32(nlh, NL80211_ATTR_IFINDEX, channel->ifindex);
send_nl_message(nlh, channel);
return receive_nl_message(channel, handle_NL80211_CMD_NEW_SCAN_RESULTS);
}
// prerequisities
// - channel initalized with init_netlink_channel
// - subscribed to scan group with subscribe_NL80211_MULTICAST_GROUP_SCAN
// - context_NL80211_MULTICAST_GROUP_SCAN set for notifications
static void wait_for_new_scan_results(struct netlink_channel *notifications)
{
struct context_NL80211_MULTICAST_GROUP_SCAN *scanning=notifications->context;
int ret;
while(!scanning->new_scan_results)
{
if ( (ret = mnl_socket_recvfrom(notifications->nl, notifications->buf, MNL_SOCKET_BUFFER_SIZE)) <=0 )
die_errno("Waiting for new scan results failed - mnl_socket_recvfrom");
if ( (ret=mnl_cb_run(notifications->buf, ret, 0, 0, handle_NL80211_MULTICAST_GROUP_SCAN, notifications)) <=0 )
die_errno("Processing notificatoins failed - mnl_cb_run");
}
}
// SCANNING - scan related
// prerequisities:
// - channel initalized with init_netlink_channel
// - channel context of type context_NL80211_CMD_NEW_SCAN_RESULTS
static int get_scan(struct netlink_channel *channel)
{
struct nlmsghdr *nlh=prepare_nl_message(channel->nl80211_id, NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK, NL80211_CMD_GET_SCAN, channel);
mnl_attr_put_u32(nlh, NL80211_ATTR_IFINDEX, channel->ifindex);
send_nl_message(nlh, channel);
return receive_nl_message(channel, handle_NL80211_CMD_NEW_SCAN_RESULTS);
}
// prerequisities:
// - netlink_channel passed as data
// - data->context of type context_NL80211_CMD_NEW_SCAN_RESULTS
static int handle_NL80211_CMD_NEW_SCAN_RESULTS(const struct nlmsghdr *nlh, void *data)
{
struct netlink_channel *channel=data;
struct nlattr *tb[NL80211_ATTR_MAX+1] = {};
struct validation_data vd={tb, NL80211_ATTR_MAX, NL80211_NEW_SCAN_RESULTS_VALIDATION, NL80211_NEW_SCAN_RESULTS_VALIDATION_LENGTH};
struct genlmsghdr *genl = (struct genlmsghdr *)mnl_nlmsg_get_payload(nlh);
// printf("NSR type %u seq %u pid %u genl cmd %u\n", nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd);
if(genl->cmd != NL80211_CMD_NEW_SCAN_RESULTS)
{
fprintf(stderr, "Ignoring generic netlink command %u seq %u pid %u genl cmd %u\n", nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd);
return MNL_CB_OK;
}
//seq 0 - notification from kernel, then pid should also 0, if it is result of our scan we have sequence and our pid
// int new_scan_results= nlh->nlmsg_seq != 0 && nlh->nlmsg_pid != 0;
mnl_attr_parse(nlh, sizeof(*genl), validate, &vd);
if(tb[NL80211_ATTR_IFINDEX])
{
// uint32_t ifindex=mnl_attr_get_u32(tb[NL80211_ATTR_IFINDEX]);
// printf("ifindex %u\n", ifindex);
}
if (!tb[NL80211_ATTR_BSS])
return MNL_CB_OK;
parse_NL80211_ATTR_BSS(tb[NL80211_ATTR_BSS], channel);
return MNL_CB_OK;
}
// prerequisities:
// - channel context of type context_NL80211_CMD_NEW_SCAN_RESULTS
static void parse_NL80211_ATTR_BSS(struct nlattr *nested, struct netlink_channel *channel)
{
struct nlattr *tb[NL80211_BSS_MAX+1] = {};
struct validation_data vd={tb, NL80211_BSS_MAX, NL80211_BSS_VALIDATION, NL80211_BSS_VALIDATION_LENGTH};
struct context_NL80211_CMD_NEW_SCAN_RESULTS *scan_results = channel->context;
struct bss_info *bss = scan_results->bss_infos + scan_results->scanned;
mnl_attr_parse_nested(nested, validate, &vd);
enum nl80211_bss_status status=BSS_NONE;
if(tb[NL80211_BSS_STATUS])
status=mnl_attr_get_u32(tb[NL80211_BSS_STATUS]);
//if we have found associated station store first as last and associated as first
if(status==NL80211_BSS_STATUS_ASSOCIATED || status==NL80211_BSS_STATUS_ASSOCIATED || status==NL80211_BSS_STATUS_IBSS_JOINED)
{
if(scan_results->scanned>0 && scan_results->scanned < scan_results->bss_infos_length)
memcpy(bss, scan_results->bss_infos, sizeof(struct bss_info));
bss=scan_results->bss_infos;
}
//check bounds, make exception if we have found associated station and replace previous data
if(scan_results->bss_infos_length == 0 || ( scan_results->scanned >= scan_results->bss_infos_length && bss != scan_results->bss_infos ) )
{
++scan_results->scanned;
return;
}
if ( tb[NL80211_BSS_BSSID])
parse_NL80211_BSS_BSSID(tb[NL80211_BSS_BSSID], bss->bssid);
if ( tb[NL80211_BSS_FREQUENCY])
bss->frequency = mnl_attr_get_u32(tb[NL80211_BSS_FREQUENCY]);
if ( tb[NL80211_BSS_INFORMATION_ELEMENTS])
parse_NL80211_BSS_INFORMATION_ELEMENTS(tb[NL80211_BSS_INFORMATION_ELEMENTS], bss->ssid);
if ( tb[NL80211_BSS_SIGNAL_MBM])
bss->signal_mbm=mnl_attr_get_u32(tb[NL80211_BSS_SIGNAL_MBM]);
if ( tb[NL80211_BSS_SEEN_MS_AGO])
bss->seen_ms_ago = mnl_attr_get_u32(tb[NL80211_BSS_SEEN_MS_AGO]);
bss->status=status;
++scan_results->scanned;
}
//This is guesswork! Read up on that!!! I don't think it's netlink in this attribute, some lower beacon layer
static void parse_NL80211_BSS_INFORMATION_ELEMENTS(struct nlattr *attr, char SSID_OUT[SSID_MAX_LENGTH_WITH_NULL])
{
const char *payload=mnl_attr_get_payload(attr);
int len=mnl_attr_get_payload_len(attr);
if(len==0 || payload[0]!=0 || payload[1] >= SSID_MAX_LENGTH_WITH_NULL || payload[1] > len-2)
{
fprintf(stderr, "SSID len 0 or payload not starting from 0 or payload length > 32 or payload length > length-2!\n");
SSID_OUT[0]='\0';
return;
}
int ssid_len=payload[1];
strncpy(SSID_OUT, payload+2, ssid_len);
SSID_OUT[ssid_len]='\0';
}
static void parse_NL80211_BSS_BSSID(struct nlattr *attr, uint8_t bssid_out[BSSID_LENGTH])
{
const char *payload=mnl_attr_get_payload(attr);
int len=mnl_attr_get_payload_len(attr);
if(len != BSSID_LENGTH)
{
fprintf(stderr, "BSSID length != %d, ignoring", BSSID_LENGTH);
memset(bssid_out, 0, BSSID_LENGTH);
return;
}
memcpy(bssid_out, payload, BSSID_LENGTH);
}
// STATION
// public interface
//
// prerequisities:
// - wifi initialized with wifi_scan_init
int wifi_scan_station(struct wifi_scan *wifi,struct station_info *station)
{
struct netlink_channel *commands=&wifi->command_channel;
struct bss_info bss;
struct context_NL80211_CMD_NEW_SCAN_RESULTS scan_results = {&bss, 1, 0};
commands->context=&scan_results;
get_scan(commands);
if(scan_results.scanned==0)
return 0;
struct context_NL80211_CMD_NEW_STATION station_results = {station};
commands->context=&station_results;
get_station(commands, bss.bssid);
memcpy(station->bssid, bss.bssid, BSSID_LENGTH);
memcpy(station->ssid, bss.ssid, SSID_MAX_LENGTH_WITH_NULL);
station->status=bss.status;
return 1;
}
// prerequisites:
// - channel initalized with init_netlink_channel
// - context_NL80211_CMD_NEW_STATION set for channel
static int get_station(struct netlink_channel *channel, uint8_t bssid[BSSID_LENGTH])
{
struct nlmsghdr *nlh=prepare_nl_message(channel->nl80211_id, NLM_F_REQUEST | NLM_F_ACK, NL80211_CMD_GET_STATION, channel);
mnl_attr_put_u32(nlh, NL80211_ATTR_IFINDEX, channel->ifindex);
mnl_attr_put(nlh, NL80211_ATTR_MAC, BSSID_LENGTH, bssid);
send_nl_message(nlh, channel);
return receive_nl_message(channel, handle_NL80211_CMD_NEW_STATION);
}
// prerequisities:
// - netlink_channel passed as data
// - data->context of type context_NL80211_CMD_NEW_STATION
static int handle_NL80211_CMD_NEW_STATION(const struct nlmsghdr *nlh, void *data)
{
struct netlink_channel *channel=data;
struct nlattr *tb[NL80211_ATTR_MAX+1] = {};
struct validation_data vd={tb, NL80211_ATTR_MAX, NL80211_CMD_NEW_STATION_VALIDATION, NL80211_CMD_NEW_STATION_VALIDATION_LENGTH};
struct genlmsghdr *genl = (struct genlmsghdr *)mnl_nlmsg_get_payload(nlh);
if(genl->cmd != NL80211_CMD_NEW_STATION)
{
fprintf(stderr, "Ignoring generic netlink command %u seq %u pid %u genl cmd %u\n", nlh->nlmsg_type, nlh->nlmsg_seq, nlh->nlmsg_pid, genl->cmd);
return MNL_CB_OK;
}
mnl_attr_parse(nlh, sizeof(*genl), validate, &vd);
if(!tb[NL80211_ATTR_STA_INFO]) //or error, no statoin
return MNL_CB_OK;
parse_NL80211_ATTR_STA_INFO(tb[NL80211_ATTR_STA_INFO], channel);
return MNL_CB_OK;
}
// prerequisities:
// - channel context of type context_NL80211_CMD_NEW_STATION
static void parse_NL80211_ATTR_STA_INFO(struct nlattr *nested, struct netlink_channel *channel)
{
struct nlattr *tb[NL80211_STA_INFO_MAX+1] = {};
struct validation_data vd={tb, NL80211_STA_INFO_MAX, NL80211_STA_INFO_VALIDATION, NL80211_STA_INFO_VALIDATION_LENGTH};
struct context_NL80211_CMD_NEW_STATION *station_results = channel->context;
struct station_info *station= station_results->station;
mnl_attr_parse_nested(nested, validate, &vd);
if ( tb[NL80211_STA_INFO_SIGNAL])
station->signal_dbm=(int8_t)mnl_attr_get_u8(tb[NL80211_STA_INFO_SIGNAL]);
if ( tb[NL80211_STA_INFO_SIGNAL_AVG])
station->signal_avg_dbm=(int8_t)mnl_attr_get_u8(tb[NL80211_STA_INFO_SIGNAL_AVG]);
if (tb[NL80211_STA_INFO_RX_PACKETS])
station->rx_packets=mnl_attr_get_u32(tb[NL80211_STA_INFO_RX_PACKETS]);
if (tb[NL80211_STA_INFO_TX_PACKETS])
station->tx_packets=mnl_attr_get_u32(tb[NL80211_STA_INFO_TX_PACKETS]);
}
// NETLINK HELPERS
// NETLINK HELPERS - message construction/sending/receiving
// prerequisities:
// - channel initialized with init_netlink_channel
static struct nlmsghdr *prepare_nl_message(uint32_t type, uint16_t flags, uint8_t genl_cmd, struct netlink_channel *channel)
{
struct nlmsghdr *nlh;
struct genlmsghdr *genl;
nlh = mnl_nlmsg_put_header(channel->buf);
nlh->nlmsg_type = type;
nlh->nlmsg_flags = flags;
nlh->nlmsg_seq = channel->sequence;
genl = (struct genlmsghdr*)mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
genl->cmd = genl_cmd;
genl->version = 1;
return nlh;
}
// prerequisities:
// - prepare_nl_message called first
// - mnl_attr_put_xxx used if additional attributes needed
static void send_nl_message(struct nlmsghdr *nlh, struct netlink_channel *channel)
{
if (mnl_socket_sendto(channel->nl, nlh, nlh->nlmsg_len) < 0)
die_errno("mnl_socket_sendto");
}
// prerequisities:
// - send_nl_message called first
// - prerequisities for callback matched
static int receive_nl_message(struct netlink_channel *channel, mnl_cb_t callback)
{
int ret;
unsigned int portid = mnl_socket_get_portid(channel->nl);
ret = mnl_socket_recvfrom(channel->nl, channel->buf, MNL_SOCKET_BUFFER_SIZE);
while (ret > 0)
{
ret = mnl_cb_run(channel->buf, ret, channel->sequence, portid, callback, channel);
if (ret <= 0)
break;
ret = mnl_socket_recvfrom(channel->nl, channel->buf, MNL_SOCKET_BUFFER_SIZE);
}
++channel->sequence;
return ret;
}
// NETLINK HELPERS - validation
// prerequisities:
// - data of type validation_data
static int validate(const struct nlattr *attr, void *data)
{
struct validation_data *vd=data;
const struct nlattr **tb = (const struct nlattr**) vd->attribute_table;
int type = mnl_attr_get_type(attr) ,i;
// printf("%d\n", type);
if (mnl_attr_type_valid(attr, vd->attribute_length) < 0)
return MNL_CB_OK;
for(i=0; i < vd->validation_length;++i)
if(type == vd->validation[i].attr)
{
int len=vd->validation[i].len;
if(len==0 && mnl_attr_validate(attr, vd->validation[i].type) < 0)
{
perror("mnl_attr_validate error");
return MNL_CB_ERROR;
}
if(len != 0 && mnl_attr_validate2(attr, vd->validation[i].type, len) < 0)
{
perror("mnl_attr_validate error");
return MNL_CB_ERROR;
}
}
tb[type] = attr;
return MNL_CB_OK;
}
// GENNERAL PURPOSE
static void die(const char *s)
{
fprintf(stderr, "%s", s);
fprintf(stderr, "\n");
exit(1);
}
static void die_errno(const char *s)
{
perror(s);
exit(1);
}