blob: 4145f2c5a7195e57956daeea927a1d730b242570 [file] [log] [blame]
/*
* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/*
* Send Management Frame
*
* Userspace helper which sends management frames via nl80211. This
* can be used to inject frames used for regulatory testing, for example
* spectrum management frames.
*/
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/if_ether.h>
#include <net/if.h>
#include <pcap.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include "ieee80211_header.h"
/*
* Minimal ieee80211 radiotap header. The bitmap must remain zero
* for this header to be valid.
*/
struct radiotap_header {
__u16 version;
__u16 header_length;
__u32 bitmap;
} __attribute__((packed));
struct radiotap_packet {
struct radiotap_header radiotap_header;
struct ieee80211_mgmt ieee80211_mgmt_frame;
/* Depending on frame type, some IEs may fit above; the rest go below. */
unsigned char overflow[1024];
} __attribute__((packed));
struct radiotap_packet_buf {
struct radiotap_packet packet;
size_t len; /* Length of populated data */
};
#define PACKET_TIMEOUT_MS 1000
#define SSID_LENGTH 32
static const char * type_beacon = "beacon";
static const char * type_channel_switch = "channel_switch";
static const char * type_probe_response = "probe_response";
static const char * usage =
"Usage:\n"
" send_management_frame -i interface -t channel_switch\n"
" [-a dest-addr] [-b num-bss] [-c channel]\n"
" [-d delay] [-n pkt-count] [-f footer-file]\n"
"\n"
" send_management_frame -i interface -t <beacon|probe_response>\n"
" [-a dest-addr] [-b num-bss] [-c channel]\n"
" [-d delay] [-n pkt-count] [-s ssid-prefix]\n"
" [-f footer-file]\n"
"\n"
"Common options:\n"
" interface: interface to inject frames.\n"
" dest-addr: destination address (DA) for the frame.\n"
" default to broadcast.\n"
" num-bss: number of synthetic bss for sending frames.\n"
" default to 0 (use interface MAC).\n"
" channel: channel to inject frames, default to 1.\n"
" delay: milliseconds delay in between frames,\n"
" default to 0 (no delay).\n"
" pkt-count: total number of frames to send, 0 means infinite\n"
" default to 1.\n"
" footer-file: non-empty file containing data to append to frames.\n"
"\n"
"beacon, probe_response options:\n"
" ssid-prefix: prefix for the SSIDs, default to FakeSSID\n";
enum message_type {
/* No external meaning, so sort alphabetically */
BEACON,
CHANNEL_SWITCH,
PROBE_RESPONSE,
};
uint8_t message_type_to_80211_frame_subtype[] = {
/* Ordered to match |enum message_type| */
WLAN_FC_STYPE_BEACON,
WLAN_FC_STYPE_ACTION,
WLAN_FC_STYPE_PROBE_RESP,
};
int get_interface_info(char *interface,
int *interface_index,
unsigned char *mac_address) {
struct ifreq ifr;
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sock < 0) {
return -1;
}
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
if (ioctl(sock, SIOCGIFINDEX, &ifr) != 0) {
fprintf(stderr, "Can't get interface index for %s.\n", interface);
return -1;
}
*interface_index = ifr.ifr_ifindex;
if (ioctl(sock, SIOCGIFHWADDR, &ifr) != 0) {
fprintf(stderr, "Can't get hardware address for %s.\n", interface);
return -1;
}
memcpy(mac_address, &ifr.ifr_hwaddr.sa_data, ETH_ALEN);
printf("Found interface %s at index %d, "
"address %02x:%02x:%02x:%02x:%02x:%02x.\n",
interface, *interface_index,
mac_address[0], mac_address[1], mac_address[2],
mac_address[3], mac_address[4], mac_address[5]);
return 0;
}
/*
* SSID prefix should be under 24 characters.
*/
int create_ssid(char *ssid_prefix, int bss_number, char **ssid_out) {
*ssid_out = calloc(1, SSID_LENGTH + 1);
if (!ssid_prefix)
sprintf(*ssid_out, "FakeSSID%08x", bss_number);
else {
if (strlen(ssid_prefix) > 24) {
fprintf(stderr, "SSID prefix too long, must be less than 24 "
"characters.\n");
return -EINVAL;
}
sprintf(*ssid_out, "%s%08X", ssid_prefix, bss_number);
}
return 0;
}
struct radiotap_packet_buf* packet_buf_alloc(uint16_t ieee80211_frame_subtype,
const unsigned char* bssid,
const unsigned char* source,
const unsigned char* destination) {
struct radiotap_packet_buf *packet_buf;
struct ieee80211_mgmt *mgm_packet_ptr;
packet_buf = calloc(1, sizeof(struct radiotap_packet_buf));
packet_buf->len = 0;
struct radiotap_packet* packet = &packet_buf->packet;
packet->radiotap_header.version = 0;
packet->radiotap_header.header_length = sizeof(packet->radiotap_header);
packet->radiotap_header.bitmap = 0;
packet_buf->len += sizeof(packet->radiotap_header);
mgm_packet_ptr = &packet->ieee80211_mgmt_frame;
mgm_packet_ptr->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
ieee80211_frame_subtype);
memcpy(&mgm_packet_ptr->da, destination, sizeof(mgm_packet_ptr->da));
memcpy(&mgm_packet_ptr->sa, source, sizeof(mgm_packet_ptr->sa));
memcpy(&mgm_packet_ptr->bssid, bssid, sizeof(mgm_packet_ptr->bssid));
packet_buf->len += (unsigned char *) &(mgm_packet_ptr->u) -
(unsigned char *) mgm_packet_ptr;
return packet_buf;
}
bool packet_buf_can_accept_bytes(size_t length,
struct radiotap_packet_buf *packet_buf) {
if (length > sizeof(packet_buf->packet) - packet_buf->len)
return false;
unsigned char *dest = (unsigned char *) packet_buf + packet_buf->len;
struct ieee80211_mgmt *mgm_packet_ptr =
&packet_buf->packet.ieee80211_mgmt_frame;
switch (WLAN_FC_GET_STYPE(mgm_packet_ptr->frame_control)) {
case WLAN_FC_STYPE_BEACON:
return dest >= mgm_packet_ptr->u.beacon.variable;
case WLAN_FC_STYPE_ACTION:
if (mgm_packet_ptr->u.action.category == WLAN_ACTION_SPECTRUM_MGMT)
return dest > &mgm_packet_ptr->u.action.u.chan_switch.switch_count;
else
assert(false);
break;
case WLAN_FC_STYPE_PROBE_RESP:
return dest >= mgm_packet_ptr->u.probe_resp.variable;
break;
default:
assert(false);
break;
}
}
int packet_buf_add_info_element(unsigned char element_id,
unsigned char element_data_length,
const unsigned char *element_data,
struct radiotap_packet_buf *packet_buf) {
size_t info_element_len =
sizeof(element_id) + sizeof(element_data_length) + element_data_length;
if (!packet_buf_can_accept_bytes(info_element_len, packet_buf))
return -1;
unsigned char *dest = (unsigned char *) packet_buf + packet_buf->len;
*dest++ = element_id;
*dest++ = element_data_length;
memcpy(dest, element_data, element_data_length);
packet_buf->len += info_element_len;
return 0;
}
int packet_buf_add_raw_data(size_t data_length,
const unsigned char *data,
struct radiotap_packet_buf *packet_buf) {
if (!packet_buf_can_accept_bytes(data_length, packet_buf))
return -1;
unsigned char *dest = (unsigned char *) packet_buf + packet_buf->len;
memcpy(dest, data, data_length);
packet_buf->len += data_length;
return 0;
}
void packet_buf_add_fixed_params(int frame_num,
struct radiotap_packet_buf *packet_buf) {
struct ieee80211_mgmt *mgm_packet_ptr =
&packet_buf->packet.ieee80211_mgmt_frame;
uint16_t stype = WLAN_FC_GET_STYPE(mgm_packet_ptr->frame_control);
if (stype == WLAN_FC_STYPE_PROBE_RESP) {
mgm_packet_ptr->u.probe_resp.timestamp[0] = frame_num;
mgm_packet_ptr->u.probe_resp.timestamp[1] = frame_num >> 8;
mgm_packet_ptr->u.probe_resp.timestamp[2] = frame_num >> 16;
mgm_packet_ptr->u.probe_resp.timestamp[3] = frame_num >> 24;
mgm_packet_ptr->u.probe_resp.beacon_int = 0x64; // 0.1024 seconds. A lie.
mgm_packet_ptr->u.probe_resp.capab_info = 0x1; // We are an AP.
packet_buf->len += sizeof(mgm_packet_ptr->u.probe_resp);
} else if (stype == WLAN_FC_STYPE_BEACON) {
mgm_packet_ptr->u.beacon.timestamp[0] = frame_num;
mgm_packet_ptr->u.beacon.timestamp[1] = frame_num >> 8;
mgm_packet_ptr->u.beacon.timestamp[2] = frame_num >> 16;
mgm_packet_ptr->u.beacon.timestamp[3] = frame_num >> 24;
mgm_packet_ptr->u.beacon.beacon_int = 0x64; // 0.1024 seconds. A lie.
mgm_packet_ptr->u.beacon.capab_info = 0x1; // We are an AP.
packet_buf->len += sizeof(mgm_packet_ptr->u.beacon);
}
}
int packet_buf_add_bss_info(char *ssid_prefix,
int bss_number,
uint8_t channel,
struct radiotap_packet_buf *packet_buf) {
char *ssid;
if (create_ssid(ssid_prefix, bss_number, &ssid) != 0) {
goto err_exit;
}
unsigned char supported_rates[] = { 0x82, 0x84, 0x8b, 0x96,
0x0c, 0x12, 0x18, 0x24 };
if (packet_buf_add_info_element(WLAN_EID_SSID, strlen(ssid),
(unsigned char *) ssid, packet_buf) != 0 ||
packet_buf_add_info_element(WLAN_EID_SUPP_RATES, sizeof(supported_rates),
supported_rates, packet_buf) != 0 ||
packet_buf_add_info_element(WLAN_EID_DS_PARAMS, sizeof(channel),
&channel, packet_buf) != 0 ) {
goto err_exit;
}
free(ssid);
return 0;
err_exit:
free(ssid);
return -1;
}
int fill_chanswitch_message_frame(uint8_t channel,
struct radiotap_packet_buf *packet_buf) {
struct radiotap_packet *packet = &packet_buf->packet;
struct ieee80211_mgmt *chanswitch_packet_ptr = &packet->ieee80211_mgmt_frame;
chanswitch_packet_ptr->u.action.category = WLAN_ACTION_SPECTRUM_MGMT;
packet_buf->len += sizeof(chanswitch_packet_ptr->u.action.category);
chanswitch_packet_ptr->u.action.u.chan_switch.action_code =
WLAN_ACTION_SPCT_CHL_SWITCH;
chanswitch_packet_ptr->u.action.u.chan_switch.element_id =
WLAN_EID_CHANNEL_SWITCH;
chanswitch_packet_ptr->u.action.u.chan_switch.length =
WLAN_EID_LENGTH_CHANNEL_SWITCH;
chanswitch_packet_ptr->u.action.u.chan_switch.switch_mode =
WLAN_CHANNEL_SWITCH_MODE_XMIT_FORBIDDEN;
chanswitch_packet_ptr->u.action.u.chan_switch.new_chan = channel;
chanswitch_packet_ptr->u.action.u.chan_switch.switch_count = 5;
packet_buf->len += sizeof(chanswitch_packet_ptr->u.action.u.chan_switch);
return 0;
}
int fill_beacon_proberesp_message_frame(char *ssid_prefix,
uint8_t channel,
int bss_number,
int frame_num,
struct radiotap_packet_buf *packet_buf)
{
packet_buf_add_fixed_params(frame_num, packet_buf);
if (packet_buf_add_bss_info(ssid_prefix, bss_number, channel, packet_buf) !=
0) {
fprintf(stderr, "BSS info didn't fit in output buffer!?\n");
return -EINVAL;
}
return 0;
}
struct radiotap_packet_buf *get_message_frame(
enum message_type message_type,
const unsigned char *interface_address,
const unsigned char *destination_address,
char *ssid_prefix,
uint8_t channel,
int bss_count,
int frame_num,
unsigned char *footer_data,
size_t footer_len) {
int bss_number = 0;
unsigned char bss_address[ETH_ALEN];
memcpy(bss_address, interface_address, sizeof(bss_address));
if (bss_count) {
bss_number = frame_num % bss_count;
bss_address[0] = 0x2; // Make this an administratively scoped address.
bss_address[5] += bss_number; // Make the BSSIDs unique.
bss_address[4] += bss_number / 256;
}
struct radiotap_packet_buf *packet_buf =
packet_buf_alloc(message_type_to_80211_frame_subtype[message_type],
bss_address, interface_address, destination_address);
/* Generate frame based on the message type */
int ret;
switch (message_type) {
case BEACON:
case PROBE_RESPONSE:
ret = fill_beacon_proberesp_message_frame(ssid_prefix, channel,
bss_number, frame_num,
packet_buf);
break;
case CHANNEL_SWITCH:
ret = fill_chanswitch_message_frame(channel, packet_buf);
break;
}
if (ret != 0) {
fprintf(stderr, "Message frame generation failed with %d.\n", ret);
ret = -EINVAL;
goto err_exit;
}
if (footer_data) {
ret = packet_buf_add_raw_data(footer_len, footer_data, packet_buf);
if (ret != 0) {
fprintf(stderr, "Footer append failed with %d.\n", ret);
goto err_exit;
}
}
return packet_buf;
err_exit:
free(packet_buf);
return NULL;
}
static int g_do_exit = 0;
void set_do_exit(int signum) {
g_do_exit = 1;
}
int get_footer_bytes(char *footer_file, unsigned char **footer) {
struct stat st;
if (stat(footer_file, &st) != 0) {
fprintf(stderr, "Error getting footer file size.\n");
goto cleanup_footer;
}
if (!st.st_size) {
fprintf(stderr, "Footer file must be non-empty.\n");
goto cleanup_footer;
}
*footer = (unsigned char *) malloc(st.st_size);
int fd = open(footer_file, O_RDONLY);
if (fd == -1) {
fprintf(stderr, "Error opening footer file.\n");
goto cleanup_mem;
}
if (read(fd, *footer, st.st_size) != st.st_size) {
fprintf(stderr, "Error reading footer file.\n");
goto cleanup_fd;
}
close(fd);
return st.st_size;
cleanup_fd:
close(fd);
cleanup_mem:
free(*footer);
cleanup_footer:
*footer = NULL;
return -1;
}
int main(int argc, char **argv) {
char *interface = NULL;
int interface_index;
unsigned char mac_address[ETH_ALEN];
char *message_name = NULL;
unsigned char *frame = NULL;
const int promiscuous = 1;
pcap_t *pcap = NULL;
char errbuf[PCAP_ERRBUF_SIZE];
int inject_return;
char buf[2048];
int exit_value = 1;
int pkt_count = 1;
unsigned char channel = 1;
int ms_delay = 0;
int num_bss = 0;
char *ssid_prefix = NULL;
char *destination_address_string = NULL;
char *footer_file = NULL;
unsigned char *footer_data = NULL;
int c;
while ((c = getopt (argc, argv, "hb:c:d:i:n:t:s:a:f:")) != -1) {
switch (c) {
case 'b':
num_bss = atoi(optarg);
break;
case 'c':
channel = (unsigned char) atoi(optarg);
break;
case 'd':
ms_delay = atoi(optarg);
break;
case 'i':
interface = optarg;
break;
case 'n':
pkt_count = atoi(optarg);
break;
case 't':
message_name = optarg;
break;
case 's':
ssid_prefix = optarg;
break;
case 'a':
destination_address_string = optarg;
break;
case 'f':
footer_file = optarg;
break;
case 'h':
default:
fprintf(stderr, "%s", usage);
return 1;
}
}
/* Validate arguments; ordering follows usage message */
if (interface == NULL || message_name == NULL) {
fprintf(stderr, "%s", usage);
goto cleanup;
}
if (ms_delay < 0) {
fprintf(stderr, "Invalid value for delay %d, must be >= 0.\n",
ms_delay);
goto cleanup;
}
enum message_type message_type;
if (strcmp(message_name, type_beacon) == 0) {
message_type = BEACON;
} else if (strcmp(message_name, type_channel_switch) == 0) {
message_type = CHANNEL_SWITCH;
} else if (strcmp(message_name, type_probe_response) == 0) {
message_type = PROBE_RESPONSE;
} else {
fprintf(stderr, "Invalid message type [%s].\n", message_name);
goto cleanup;
}
if (ssid_prefix && message_type == CHANNEL_SWITCH) {
fprintf(stderr, "-s is not valid for message type [%s].\n", message_name);
goto cleanup;
}
unsigned char custom_dest[ETH_ALEN];
const unsigned char broadcast_address[] = { 0xff, 0xff, 0xff,
0xff, 0xff, 0xff };
const unsigned char *destination_address_bytes;
if (destination_address_string) {
if (sscanf(destination_address_string, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&custom_dest[0], &custom_dest[1], &custom_dest[2],
&custom_dest[3], &custom_dest[4], &custom_dest[5]) !=
ETH_ALEN) {
fprintf(stderr, "Invalid destination address [%s].\n",
destination_address_string);
goto cleanup;
}
destination_address_bytes = custom_dest;
} else {
destination_address_bytes = broadcast_address;
}
size_t footer_len = 0;
if (footer_file) {
footer_len = get_footer_bytes(footer_file, &footer_data);
}
/* Get interface information */
if (get_interface_info(interface, &interface_index, mac_address) != 0) {
fprintf(stderr, "Can't get information on AP interface %s.\n", interface);
goto cleanup;
}
pcap = pcap_open_live(interface,
sizeof(buf),
promiscuous,
PACKET_TIMEOUT_MS,
errbuf);
if (pcap == NULL) {
fprintf(stderr, "Could not open capture handle.\n");
goto cleanup;
}
if (pcap_datalink(pcap) != DLT_IEEE802_11_RADIO) {
fprintf(stderr, "Interface %s does not use RadioTap.\n", interface);
goto cleanup;
}
struct sigaction exit_action;
exit_action.sa_handler = set_do_exit;
exit_action.sa_flags = 0;
sigfillset(&exit_action.sa_mask);
sigaction(SIGTERM, &exit_action, NULL);
sigaction(SIGINT, &exit_action, NULL);
/*
* Generate and inject number of frames specified. Continuous sending
* if number of frame specified is 0.
*/
int i = 0;
while ( (i < pkt_count) || (pkt_count == 0) ) {
struct radiotap_packet_buf *packet_buf =
get_message_frame(message_type, mac_address, destination_address_bytes,
ssid_prefix, channel, num_bss, i, footer_data,
footer_len);
if (!packet_buf) {
fprintf(stderr, "Can't generate a frame of type %s.\n", message_name);
goto cleanup;
}
unsigned char *frame = (unsigned char *) &packet_buf->packet;
size_t frame_length = packet_buf->len;
size_t j = 0;
if (i == 0) {
printf("Frame (length %zu): ", frame_length);
for (j = 0; j < frame_length; j++) {
printf("%02x ", frame[j]);
}
printf("\n");
}
if (ms_delay > 0)
usleep(ms_delay * 1000);
inject_return = pcap_inject(pcap, frame, frame_length);
if (i == 1) printf("Inject returned %d.\n", inject_return);
i++;
free(frame);
frame=NULL;
if (g_do_exit)
break;
}
exit_value = 0;
printf("Transmitted %d frames.\n", i);
cleanup:
if (pcap) {
pcap_close(pcap);
}
free(footer_data);
free(frame);
return exit_value;
}