nexmon – Rev 1

Subversion Repositories:
Rev:
/***************************************************************************
 *                                                                         *
 *          ###########   ###########   ##########    ##########           *
 *         ############  ############  ############  ############          *
 *         ##            ##            ##   ##   ##  ##        ##          *
 *         ##            ##            ##   ##   ##  ##        ##          *
 *         ###########   ####  ######  ##   ##   ##  ##    ######          *
 *          ###########  ####  #       ##   ##   ##  ##    #    #          *
 *                   ##  ##    ######  ##   ##   ##  ##    #    #          *
 *                   ##  ##    #       ##   ##   ##  ##    #    #          *
 *         ############  ##### ######  ##   ##   ##  ##### ######          *
 *         ###########    ###########  ##   ##   ##   ##########           *
 *                                                                         *
 *            S E C U R E   M O B I L E   N E T W O R K I N G              *
 *                                                                         *
 * This file is part of NexMon.                                            *
 *                                                                         *
 * Copyright (c) 2016 NexMon Team                                          *
 *                                                                         *
 * NexMon is free software: you can redistribute it and/or modify          *
 * it under the terms of the GNU General Public License as published by    *
 * the Free Software Foundation, either version 3 of the License, or       *
 * (at your option) any later version.                                     *
 *                                                                         *
 * NexMon is distributed in the hope that it will be useful,               *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
 * GNU General Public License for more details.                            *
 *                                                                         *
 * You should have received a copy of the GNU General Public License       *
 * along with NexMon. If not, see <http://www.gnu.org/licenses/>.          *
 *                                                                         *
 **************************************************************************/

#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>
#include <argp-extern.h>
#include <string.h>
#include <byteswap.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/param.h> // for MIN macro

#include <sys/ioctl.h>
#include <arpa/inet.h>
#ifdef BUILD_ON_RPI
#include <types.h> //not sure why it was removed, but it is needed for typedefs like `uint`
#include <linux/if.h>
#else
#include <net/if.h>
#endif
#include <stdbool.h>
#define TYPEDEF_BOOL
#include <errno.h>

#include <wlcnt.h>

#include <nexioctls.h>

#include <typedefs.h>
#include <bcmwifi_channels.h>
#include <b64.h>

#if ANDROID
#include <sys/system_properties.h>
#endif

#define HEXDUMP_COLS 16

#define IPADDR(a,b,c,d) ((d) << 24 | (c) << 16 | (b) << 8 | (a))

struct nexio {
    struct ifreq *ifr;
    int sock_rx_ioctl;
    int sock_rx_frame;
    int sock_tx;
};

extern int nex_ioctl(struct nexio *nexio, int cmd, void *buf, int len, bool set);
extern struct nexio *nex_init_ioctl(const char *ifname);
extern struct nexio *nex_init_udp(unsigned int securitycookie, unsigned int txip);
extern struct nexio *nex_init_netlink(void);

char            *ifname = "wlan0";
unsigned char   set_monitor = 0;
unsigned char   set_monitor_value = 0;
unsigned char   get_monitor = 0;
unsigned char   set_promisc = 0;
unsigned char   set_promisc_value = 0;
unsigned char   get_promisc = 0;
unsigned char   set_scansuppress = 0;
unsigned char   set_scansuppress_value = 0;
unsigned char   get_scansuppress = 0;
unsigned char   set_securitycookie = 0;
unsigned int    set_securitycookie_value = 0;
unsigned char   get_securitycookie = 0;
unsigned int    use_udp_tunneling = 0; // contains the securtiy cookie
unsigned int    txip = IPADDR(192,168,222,255);
unsigned int    custom_cmd = 0;
signed char     custom_cmd_set = -1;
unsigned int    custom_cmd_buf_len = 4;
void            *custom_cmd_buf = NULL;
char            *custom_cmd_value = NULL;
unsigned char   get_chanspec = 0;
unsigned char   set_chanspec = 0;
char            *set_chanspec_value = NULL;
unsigned char   custom_cmd_value_int = false;
unsigned char   custom_cmd_value_base64 = false;
unsigned char   raw_output = false;
unsigned char   base64_output = false;
unsigned int    dump_objmem_addr = 0;
unsigned char   dump_objmem = false;
unsigned char   disassociate = false;
unsigned char   dump_wl_cnt = false;
unsigned char   revinfo = false;

const char *argp_program_version = VERSION;
const char *argp_program_bug_address = "<mschulz@seemoo.tu-darmstadt.de>";

static char doc[] = "nexutil -- a program to control a nexmon firmware for broadcom chips.";

static struct argp_option options[] = {
    {"interface-name", 'I', "CHAR", 0, "Set interface name (default: wlan0)"},
    {"monitor", 'm', "INT", OPTION_ARG_OPTIONAL, "Set/Get monitor mode"},
    {"promisc", 'p', "INT", OPTION_ARG_OPTIONAL, "Set/Get promiscuous mode"},
    {"scansuppress", 'c', "INT", OPTION_ARG_OPTIONAL, "Set/Get scan suppress setting to avoid scanning"},
    {"disassociate", 'd', 0, 0, "Disassociate from access point"},
    {"get-custom-cmd", 'g', "INT", 0, "Get custom command, e.g. 107 for WLC_GET_VAR"},
    {"set-custom-cmd", 's', "INT", 0, "Set custom command, e.g. 108 for WLC_SET_VAR"},
    {"custom-cmd-buf-len", 'l', "INT", 0, "Custom command buffer length (default: 4)"},
    {"custom-cmd-value", 'v', "CHAR/INT", 0, "Initialization value for the buffer used by custom command"},
    {"custom-cmd-value-int", 'i', 0, 0, "Define that custom-cmd-value should be interpreted as integer"},
    {"custom-cmd-value-base64", 'b', 0, 0, "Define that custom-cmd-value should be interpreted as base64 string"},
    {"base64-output", 'R', 0, 0, "Write base64 encoded strings to stdout instead of hex dumping"},
    {"raw-output", 'r', 0, 0, "Write raw output to stdout instead of hex dumping"},
    {"dump-wl_cnt", 'w', 0, 0, "Dump WL counters"},
    {"dump-objmem", 'o', "INT", 0, "Dumps objmem at addr INT"},
    {"chanspec", 'k', "CHAR/INT", OPTION_ARG_OPTIONAL, "Set chanspec either as integer (e.g., 0x1001, set -i) or as string (e.g., 64/80)."},
    {"security-cookie", 'x', "INT", OPTION_ARG_OPTIONAL, "Set/Get security cookie"},
    {"use-udp-tunneling", 'X', "INT", 0, "Use UDP tunneling with security cookie INT"},
    {"broadcast-ip", 'B', "CHAR", 0, "Broadcast IP to use for UDP tunneling (default: 192.168.222.255)"},
    {"revinfo", 'V', 0, 0, "Dump revision information of the Wi-Fi chip"},
    { 0 }
};

static error_t
parse_opt(int key, char *arg, struct argp_state *state)
{
    switch (key) {
        case 'I':
            ifname = arg;
            break;

        case 'm':
            if (arg) {
                set_monitor = true;
                set_monitor_value = strtol(arg, NULL, 0);
            } else {
                get_monitor = true;
            }
            break;

        case 'p':
            if (arg) {
                set_promisc = true;
                set_promisc_value = strtol(arg, NULL, 0);
            } else {
                get_promisc = true;
            }
            break;

        case 'c':
            if (arg) {
                set_scansuppress = true;
                set_scansuppress_value = strtol(arg, NULL, 0);
            } else {
                get_scansuppress = true;
            }
            break;

        case 'd':
            disassociate = true;
            break;

        case 'g':
            custom_cmd_set = false;
            custom_cmd = strtol(arg, NULL, 0);
            break;

        case 's':
            custom_cmd_set = true;
            custom_cmd = strtol(arg, NULL, 0);
            break;
        
        case 'l':
            custom_cmd_buf_len = strtol(arg, NULL, 0);
            break;

        case 'k':
            if (arg) {
                set_chanspec = true;
                set_chanspec_value = arg;
            } else {
                get_chanspec = true;
            }
            break;

        case 'v':
            custom_cmd_value = arg;
            break;

        case 'i':
            if (!custom_cmd_value_base64)
                custom_cmd_value_int = true;
            else
                printf("ERR: you can only either use base64 or integer encoding.");
            break;

        case 'b':
            if (!custom_cmd_value_int)
                custom_cmd_value_base64 = true;
            else
                printf("ERR: you can only either use base64 or integer encoding.");
            break;

        case 'r':
            if (!base64_output)
                raw_output = true;
            else
                printf("ERR: you can only either use base64 or raw output.");
            break;

        case 'R':
            if (!raw_output)
                base64_output = true;
            else
                printf("ERR: you can only either use base64 or raw output.");
            break;

        case 'o':
            dump_objmem_addr = strtol(arg, NULL, 0);
            dump_objmem = true;
            break;

        case 'w':
            dump_wl_cnt = true;
            break;

        case 'x':
            if (arg) {
                set_securitycookie = true;
                set_securitycookie_value = strtol(arg, NULL, 0);
            } else {
                get_securitycookie = true;
            }
            break;

        case 'X':
            if (set_securitycookie == false) {
                use_udp_tunneling = strtol(arg, NULL, 0);
                if (use_udp_tunneling == 0)
                    printf("ERR: You need to use a security cookie different from 0.");
            } else {
                printf("ERR: You cannot use -x in combination with -X.");
            }
            break;

        case 'B':
            if (arg) {
                txip = inet_addr(arg);
            }
            break;

        case 'V':
            revinfo = true;
            break;
        
        default:
            return ARGP_ERR_UNKNOWN;
    }

    return 0;
}

static struct argp argp = { options, parse_opt, 0, doc };

/* source: http://grapsus.net/blog/post/Hexadecimal-dump-in-C */
void
hexdump(void *mem, unsigned int len)
{
    unsigned int i, j;
    
    for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {
        if (i % HEXDUMP_COLS == 0) {
                printf("0x%06x: ", i);
        }

        if(i < len) {
            printf("%02x ", 0xFF & ((char*)mem)[i]);
        } else {
            printf("   ");
        }

        if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
            for(j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
                if(j >= len) {
                    putchar(' ');
                } else if(isprint(((char*)mem)[j])) {
                    putchar(0xFF & ((char*)mem)[j]);        
                } else {
                        putchar('.');
                }
            }
            putchar('\n');
        }
    }
}

/* Produce a human-readable string for boardrev */
char *
bcm_brev_str(uint32 brev, char *buf)
{
    if (brev < 0x100)
        snprintf(buf, 8, "%d.%d", (brev & 0xf0) >> 4, brev & 0xf);
    else
        snprintf(buf, 8, "%c%03x", ((brev & 0xf000) == 0x1000) ? 'P' : 'A', brev & 0xfff);

    return (buf);
}

int
main(int argc, char **argv)
{
    struct nexio *nexio;
    int ret;
    int buf = 0;

    argp_parse(&argp, argc, argv, 0, 0, 0);

    if (use_udp_tunneling != 0)
        nexio = nex_init_udp(use_udp_tunneling, txip);
    else
#ifdef USE_NETLINK
        nexio = nex_init_netlink();
#else
        nexio = nex_init_ioctl(ifname);
#endif

    if (set_monitor) {
        buf = set_monitor_value;
        ret = nex_ioctl(nexio, WLC_SET_MONITOR, &buf, 4, true);
    }

    if (set_promisc) {
        buf = set_promisc_value;
        ret = nex_ioctl(nexio, WLC_SET_PROMISC, &buf, 4, true);
    }

    if (set_scansuppress) {
        buf = set_scansuppress_value;
        if (set_scansuppress_value)
            ret = nex_ioctl(nexio, WLC_SET_SCANSUPPRESS, &buf, 4, true);
        else
            ret = nex_ioctl(nexio, WLC_SET_SCANSUPPRESS, &buf, 1, true);
    }

    if (set_securitycookie) {
        buf = set_securitycookie_value;
        ret = nex_ioctl(nexio, NEX_SET_SECURITYCOOKIE, &buf, 4, true);
    }

    if (get_monitor) {
        ret = nex_ioctl(nexio, WLC_GET_MONITOR, &buf, 4, false);
        printf("monitor: %d\n", buf);
    }

    if (get_promisc) {
        ret = nex_ioctl(nexio, WLC_GET_PROMISC, &buf, 4, false);
        printf("promisc: %d\n", buf);
    }

    if (get_scansuppress) {
        ret = nex_ioctl(nexio, WLC_GET_SCANSUPPRESS, &buf, 4, false);
        printf("scansuppress: %d\n", buf);
    }

    if (get_securitycookie) {
        ret = nex_ioctl(nexio, NEX_GET_SECURITYCOOKIE, &buf, 4, false);
        printf("securitycookie: %d\n", buf);
    }

    if (get_chanspec) {
        char charbuf[9] = "chanspec";
        uint16 chanspec = 0;
        ret = nex_ioctl(nexio, WLC_GET_VAR, charbuf, 9, false);
        chanspec = *(uint16 *) charbuf;
        printf("chanspec: 0x%04x, %s\n", chanspec, wf_chspec_ntoa(chanspec, charbuf));
    }

    if (set_chanspec) {
        char charbuf[13] = "chanspec";
        uint32 *chanspec = (uint32 *) &charbuf[9];

        if (custom_cmd_value_int)
            *chanspec = strtoul(set_chanspec_value, NULL, 0);
        else
            *chanspec = wf_chspec_aton(set_chanspec_value);

        if (*chanspec == 0)
            printf("invalid chanspec\n");
        else
            ret = nex_ioctl(nexio, WLC_SET_VAR, charbuf, 13, true);
    }

    if (custom_cmd_set != -1) {
        custom_cmd_buf = malloc(custom_cmd_buf_len);
        if (!custom_cmd_buf)
            return -1;

        memset(custom_cmd_buf, 0, custom_cmd_buf_len);

        if (custom_cmd_set == 1 && raw_output) { // set command using raw input
            //freopen(NULL, "rb", stdin);
            fread(custom_cmd_buf, 1, custom_cmd_buf_len, stdin);
        } else {
            if (custom_cmd_value) {
                if (custom_cmd_value_int) {
                    *(uint32 *) custom_cmd_buf = strtoul(custom_cmd_value, NULL, 0);
                } else if (custom_cmd_value_base64) {
                    size_t decoded_len = 0;
                    unsigned char *decoded = b64_decode_ex(custom_cmd_value, strlen(custom_cmd_value), &decoded_len);
                    memcpy(custom_cmd_buf, decoded, MIN(decoded_len, custom_cmd_buf_len));
                } else {
                    strncpy(custom_cmd_buf, custom_cmd_value, custom_cmd_buf_len);
                }
            } else {
                if (custom_cmd_value_int) {
                    *(uint32 *) custom_cmd_buf = strtoul(custom_cmd_value, NULL, 0);
                }
            }
        }

        /* NOTICE: Using SDIO to communicate to the firmware, the maximum CDC message length 
         * is limited to CDC_MAX_MSG_SIZE = ETHER_MAX_LEN = 1518, however only 1502 bytes 
         * arrive in the ioctl function, the rest might be used for the ioctl header.
         */
        if (custom_cmd_buf_len > 1502 && custom_cmd_set)
            fprintf(stderr, "WARN: Using SDIO, the ioctl payload length is limited to 1502 bytes.\n");
        ret = nex_ioctl(nexio, custom_cmd, custom_cmd_buf, custom_cmd_buf_len, custom_cmd_set);

        if (custom_cmd_set == false) {
            if (raw_output) {
                fwrite(custom_cmd_buf, sizeof(char), custom_cmd_buf_len, stdout);
                fflush(stdout);
            } else if (base64_output) {
                char *encoded = b64_encode(custom_cmd_buf, custom_cmd_buf_len);
                fwrite(encoded, sizeof(char), strlen(encoded), stdout);
                fflush(stdout);
            } else {
                hexdump(custom_cmd_buf, custom_cmd_buf_len);
            }
        }
    }

    if (dump_objmem) {
        custom_cmd_buf = malloc(custom_cmd_buf_len);
        if (!custom_cmd_buf)
            return -1;

        memset(custom_cmd_buf, 0, custom_cmd_buf_len);

        unsigned int *custom_cmd_buf_pos = (unsigned int *) custom_cmd_buf;

        int i = 0;
        for (i = 0; i < custom_cmd_buf_len / 0x2000; i++) {
            *custom_cmd_buf_pos = dump_objmem_addr + i * 0x2000 / 4;
            printf("%8p %08x\n", custom_cmd_buf_pos, *custom_cmd_buf_pos);
            ret = nex_ioctl(nexio, 406, custom_cmd_buf_pos, 0x2000, false);    
            custom_cmd_buf_pos += 0x2000 / 4;
        }
        if (custom_cmd_buf_len % 0x2000 != 0) {
            *(unsigned int *) custom_cmd_buf_pos = dump_objmem_addr + i * 0x2000 / 4;
            ret = nex_ioctl(nexio, 406, custom_cmd_buf_pos, custom_cmd_buf_len % 0x2000, false);
        }

        if (raw_output) {
            fwrite(custom_cmd_buf, sizeof(char), custom_cmd_buf_len, stdout);
            fflush(stdout);
        } else {
            hexdump(custom_cmd_buf, custom_cmd_buf_len);
        }
    }

    if (disassociate) {
        buf = 1;
        ret = nex_ioctl(nexio, WLC_DISASSOC, &buf, 4, true);
    }

    if (dump_wl_cnt) {
        wl_cnt_t cnt;
        wl_cnt_t *_cnt = &cnt;
        memset(_cnt, 0, sizeof(cnt));
        ret = nex_ioctl(nexio, NEX_GET_WL_CNT, _cnt, sizeof(cnt), false);
        unsigned int i;
        printf("version: %d\n", _cnt->version);
        printf("length: %d\n", _cnt->length);
        for (i = 1; i < sizeof(cnt)/4; i++) {
            printf("%s: %d (%s)\n", wl_cnt_varname[i], ((uint32 *) _cnt)[i], wl_cnt_description[i]);
        }
        //hexdump(_cnt, sizeof(_cnt)); 
    }

    if (revinfo) {
        typedef struct wlc_rev_info {
            uint        vendorid;   /* PCI vendor id */
            uint        deviceid;   /* device id of chip */
            uint        radiorev;   /* radio revision */
            uint        chiprev;    /* chip revision */
            uint        corerev;    /* core revision */
            uint        boardid;    /* board identifier (usu. PCI sub-device id) */
            uint        boardvendor;    /* board vendor (usu. PCI sub-vendor id) */
            uint        boardrev;   /* board revision */
            uint        driverrev;  /* driver version */
            uint        ucoderev;   /* microcode version */
            uint        bus;        /* bus type */
            uint        chipnum;    /* chip number */
            uint        phytype;    /* phy type */
            uint        phyrev;     /* phy revision */
            uint        anarev;     /* anacore rev */
            uint        chippkg;    /* chip package info */
            uint        nvramrev;   /* nvram revision number */
        } wlc_rev_info_t;

        char b[8];
        char str[17][32] = { 0 };
        wlc_rev_info_t revinfo;
        
        memset(&revinfo, 0, sizeof(revinfo));

        ret = nex_ioctl(nexio, WLC_GET_REVINFO, &revinfo, sizeof(revinfo), false);

#ifdef ANDROID
        char model_string[PROP_VALUE_MAX + 1];
        __system_property_get("ro.product.model", model_string);
#else
        char model_string[] = "unknown";
#endif
        char fw_ver[256] = "ver\0";
        nex_ioctl(nexio, WLC_GET_VAR, fw_ver, sizeof(fw_ver), false);
        char *fw_ver2 = strstr(fw_ver, "version") + 8;
        fw_ver2[strlen(fw_ver2) - 1] = 0;

        snprintf(str[0], sizeof(str[0]), "0x%x", revinfo.vendorid);
        snprintf(str[1], sizeof(str[0]), "0x%x", revinfo.deviceid);
        snprintf(str[2], sizeof(str[0]), "0x%x", revinfo.radiorev);
        snprintf(str[3], sizeof(str[0]), "0x%x", revinfo.chipnum);
        snprintf(str[4], sizeof(str[0]), "0x%x", revinfo.chiprev);
        snprintf(str[5], sizeof(str[0]), "0x%x", revinfo.chippkg);
        snprintf(str[6], sizeof(str[0]), "0x%x", revinfo.corerev);
        snprintf(str[7], sizeof(str[0]), "0x%x", revinfo.boardid);
        snprintf(str[8], sizeof(str[0]), "0x%x", revinfo.boardvendor);
        snprintf(str[9], sizeof(str[0]), "%s", bcm_brev_str(revinfo.boardrev, b));
        snprintf(str[10], sizeof(str[0]), "0x%x", revinfo.driverrev);
        snprintf(str[11], sizeof(str[0]), "0x%x", revinfo.ucoderev);
        snprintf(str[12], sizeof(str[0]), "0x%x", revinfo.bus);
        snprintf(str[13], sizeof(str[0]), "0x%x", revinfo.phytype);
        snprintf(str[14], sizeof(str[0]), "0x%x", revinfo.phyrev);
        snprintf(str[15], sizeof(str[0]), "0x%x", revinfo.anarev);
        snprintf(str[16], sizeof(str[0]), "0x%x", revinfo.nvramrev);

#ifdef ANDROID
        printf("platform %s\n", model_string);
#endif
        printf("firmware %s\n", fw_ver2);
        printf("vendorid %s\n", str[0]);
        printf("deviceid %s\n", str[1]);
        printf("radiorev %s\n", str[2]);
        printf("chipnum %s\n", str[3]);
        printf("chiprev %s\n", str[4]);
        printf("chippackage %s\n", str[5]);
        printf("corerev %s\n", str[6]);
        printf("boardid %s\n", str[7]);
        printf("boardvendor %s\n", str[8]);
        printf("boardrev %s\n", str[9]);
        printf("driverrev %s\n", str[10]);
        printf("ucoderev %s\n", str[11]);
        printf("bus %s\n", str[12]);
        printf("phytype %s\n", str[13]);
        printf("phyrev %s\n", str[14]);
        printf("anarev %s\n", str[15]);
        printf("nvramrev %s\n", str[16]);

        printf("\n");
        printf("platform             | firmware                         | vendorid | deviceid | radiorev   | chipnum | chiprev | chippackage | corerev | boardid | boardvendor | boardrev | driverrev | ucoderev  | bus | phytype | phyrev | anarev | nvramrev\n");
        printf("-------------------- | -------------------------------- | -------- | -------- | ---------- | ------- | ------- | ----------- | ------- | ------- | ----------- | -------- | --------- | --------- | --- | ------- | ------ | ------ | --------\n");
        printf("%-20s | %-32s | %8s | %8s | %10s | %7s | %7s | %11s | %7s | %7s | %11s | %8s | %9s | %9s | %3s | %7s | %6s | %6s | %8s\n", 
            model_string, fw_ver2, str[0], str[1], str[2], str[3], str[4], str[5], str[6], str[7], str[8], str[9], str[10], str[11], str[12], str[13], str[14], str[15], str[16]);
    }

    return 0;
}