/* * Wireless Tools * * Jean II - HPLB 97->99 - HPL 99->09 * * Common subroutines to all the wireless tools... * * This file is released under the GPL license. * Copyright (c) 1997-2009 Jean Tourrilhes */ /***************************** INCLUDES *****************************/ #include "iwlib-private.h" /* Private header */ /************************ CONSTANTS & MACROS ************************/ /* * Constants fof WE-9->15 */ #define IW15_MAX_FREQUENCIES 16 #define IW15_MAX_BITRATES 8 #define IW15_MAX_TXPOWER 8 #define IW15_MAX_ENCODING_SIZES 8 #define IW15_MAX_SPY 8 #define IW15_MAX_AP 8 /****************************** TYPES ******************************/ /* * Struct iw_range up to WE-15 */ struct iw15_range { __u32 throughput; __u32 min_nwid; __u32 max_nwid; __u16 num_channels; __u8 num_frequency; struct iw_freq freq[IW15_MAX_FREQUENCIES]; __s32 sensitivity; struct iw_quality max_qual; __u8 num_bitrates; __s32 bitrate[IW15_MAX_BITRATES]; __s32 min_rts; __s32 max_rts; __s32 min_frag; __s32 max_frag; __s32 min_pmp; __s32 max_pmp; __s32 min_pmt; __s32 max_pmt; __u16 pmp_flags; __u16 pmt_flags; __u16 pm_capa; __u16 encoding_size[IW15_MAX_ENCODING_SIZES]; __u8 num_encoding_sizes; __u8 max_encoding_tokens; __u16 txpower_capa; __u8 num_txpower; __s32 txpower[IW15_MAX_TXPOWER]; __u8 we_version_compiled; __u8 we_version_source; __u16 retry_capa; __u16 retry_flags; __u16 r_time_flags; __s32 min_retry; __s32 max_retry; __s32 min_r_time; __s32 max_r_time; struct iw_quality avg_qual; }; /* * Union for all the versions of iwrange. * Fortunately, I mostly only add fields at the end, and big-bang * reorganisations are few. */ union iw_range_raw { struct iw15_range range15; /* WE 9->15 */ struct iw_range range; /* WE 16->current */ }; /* * Offsets in iw_range struct */ #define iwr15_off(f) ( ((char *) &(((struct iw15_range *) NULL)->f)) - \ (char *) NULL) #define iwr_off(f) ( ((char *) &(((struct iw_range *) NULL)->f)) - \ (char *) NULL) /* * Union to perform unaligned access when working around alignement issues */ union iw_align_u16 { __u16 value; unsigned char byte[2]; }; /**************************** VARIABLES ****************************/ /* Modes as human readable strings */ const char * const iw_operation_mode[] = { "Auto", "Ad-Hoc", "Managed", "Master", "Repeater", "Secondary", "Monitor", "Unknown/bug" }; /* Modulations as human readable strings */ const struct iw_modul_descr iw_modul_list[] = { /* Start with aggregate types, so that they display first */ { IW_MODUL_11AG, "11ag", "IEEE 802.11a + 802.11g (2.4 & 5 GHz, up to 54 Mb/s)" }, { IW_MODUL_11AB, "11ab", "IEEE 802.11a + 802.11b (2.4 & 5 GHz, up to 54 Mb/s)" }, { IW_MODUL_11G, "11g", "IEEE 802.11g (2.4 GHz, up to 54 Mb/s)" }, { IW_MODUL_11A, "11a", "IEEE 802.11a (5 GHz, up to 54 Mb/s)" }, { IW_MODUL_11B, "11b", "IEEE 802.11b (2.4 GHz, up to 11 Mb/s)" }, /* Proprietary aggregates */ { IW_MODUL_TURBO | IW_MODUL_11A, "turboa", "Atheros turbo mode at 5 GHz (up to 108 Mb/s)" }, { IW_MODUL_TURBO | IW_MODUL_11G, "turbog", "Atheros turbo mode at 2.4 GHz (up to 108 Mb/s)" }, { IW_MODUL_PBCC | IW_MODUL_11B, "11+", "TI 802.11+ (2.4 GHz, up to 22 Mb/s)" }, /* Individual modulations */ { IW_MODUL_OFDM_G, "OFDMg", "802.11g higher rates, OFDM at 2.4 GHz (up to 54 Mb/s)" }, { IW_MODUL_OFDM_A, "OFDMa", "802.11a, OFDM at 5 GHz (up to 54 Mb/s)" }, { IW_MODUL_CCK, "CCK", "802.11b higher rates (2.4 GHz, up to 11 Mb/s)" }, { IW_MODUL_DS, "DS", "802.11 Direct Sequence (2.4 GHz, up to 2 Mb/s)" }, { IW_MODUL_FH, "FH", "802.11 Frequency Hopping (2,4 GHz, up to 2 Mb/s)" }, /* Proprietary modulations */ { IW_MODUL_TURBO, "turbo", "Atheros turbo mode, channel bonding (up to 108 Mb/s)" }, { IW_MODUL_PBCC, "PBCC", "TI 802.11+ higher rates (2.4 GHz, up to 22 Mb/s)" }, { IW_MODUL_CUSTOM, "custom", "Driver specific modulation (check driver documentation)" }, }; /* Disable runtime version warning in iw_get_range_info() */ int iw_ignore_version = 0; /************************ SOCKET SUBROUTINES *************************/ /*------------------------------------------------------------------*/ /* * Open a socket. * Depending on the protocol present, open the right socket. The socket * will allow us to talk to the driver. */ int iw_sockets_open(void) { static const int families[] = { AF_INET, AF_IPX, AF_AX25, AF_APPLETALK }; unsigned int i; int sock; /* * Now pick any (exisiting) useful socket family for generic queries * Note : don't open all the socket, only returns when one matches, * all protocols might not be valid. * Workaround by Jim Kaba * Note : in 99% of the case, we will just open the inet_sock. * The remaining 1% case are not fully correct... */ /* Try all families we support */ for(i = 0; i < sizeof(families)/sizeof(int); ++i) { /* Try to open the socket, if success returns it */ sock = socket(families[i], SOCK_DGRAM, 0); if(sock >= 0) return sock; } return -1; } /*------------------------------------------------------------------*/ /* * Extract the interface name out of /proc/net/wireless or /proc/net/dev. */ static inline char * iw_get_ifname(char * name, /* Where to store the name */ int nsize, /* Size of name buffer */ char * buf) /* Current position in buffer */ { char * end; /* Skip leading spaces */ while(isspace(*buf)) buf++; #ifndef IW_RESTRIC_ENUM /* Get name up to the last ':'. Aliases may contain ':' in them, * but the last one should be the separator */ end = strrchr(buf, ':'); #else /* Get name up to ": " * Note : we compare to ": " to make sure to process aliased interfaces * properly. Doesn't work on /proc/net/dev, because it doesn't guarantee * a ' ' after the ':'*/ end = strstr(buf, ": "); #endif /* Not found ??? To big ??? */ if((end == NULL) || (((end - buf) + 1) > nsize)) return(NULL); /* Copy */ memcpy(name, buf, (end - buf)); name[end - buf] = '\0'; /* Return value currently unused, just make sure it's non-NULL */ return(end); } /*------------------------------------------------------------------*/ /* * Enumerate devices and call specified routine * The new way just use /proc/net/wireless, so get all wireless interfaces, * whether configured or not. This is the default if available. * The old way use SIOCGIFCONF, so get only configured interfaces (wireless * or not). */ void iw_enum_devices(int skfd, iw_enum_handler fn, char * args[], int count) { char buff[1024]; FILE * fh; struct ifconf ifc; struct ifreq *ifr; int i; #ifndef IW_RESTRIC_ENUM /* Check if /proc/net/dev is available */ fh = fopen(PROC_NET_DEV, "r"); #else /* Check if /proc/net/wireless is available */ fh = fopen(PROC_NET_WIRELESS, "r"); #endif if(fh != NULL) { /* Success : use data from /proc/net/wireless */ /* Eat 2 lines of header */ fgets(buff, sizeof(buff), fh); fgets(buff, sizeof(buff), fh); /* Read each device line */ while(fgets(buff, sizeof(buff), fh)) { char name[IFNAMSIZ + 1]; char *s; /* Skip empty or almost empty lines. It seems that in some * cases fgets return a line with only a newline. */ if((buff[0] == '\0') || (buff[1] == '\0')) continue; /* Extract interface name */ s = iw_get_ifname(name, sizeof(name), buff); if(!s) { /* Failed to parse, complain and continue */ #ifndef IW_RESTRIC_ENUM fprintf(stderr, "Cannot parse " PROC_NET_DEV "\n"); #else fprintf(stderr, "Cannot parse " PROC_NET_WIRELESS "\n"); #endif } else /* Got it, print info about this interface */ (*fn)(skfd, name, args, count); } fclose(fh); } else { /* Get list of configured devices using "traditional" way */ ifc.ifc_len = sizeof(buff); ifc.ifc_buf = buff; if(ioctl(skfd, SIOCGIFCONF, &ifc) < 0) { fprintf(stderr, "SIOCGIFCONF: %s\n", strerror(errno)); return; } ifr = ifc.ifc_req; /* Print them */ for(i = ifc.ifc_len / sizeof(struct ifreq); --i >= 0; ifr++) (*fn)(skfd, ifr->ifr_name, args, count); } } /*********************** WIRELESS SUBROUTINES ************************/ /*------------------------------------------------------------------*/ /* * Extract WE version number from /proc/net/wireless * In most cases, you really want to get version information from * the range info (range->we_version_compiled), see below... * * If we have WE-16 and later, the WE version is available at the * end of the header line of the file. * For version prior to that, we can only detect the change from * v11 to v12, so we do an approximate job. Fortunately, v12 to v15 * are highly binary compatible (on the struct level). */ int iw_get_kernel_we_version(void) { char buff[1024]; FILE * fh; char * p; int v; /* Check if /proc/net/wireless is available */ fh = fopen(PROC_NET_WIRELESS, "r"); if(fh == NULL) { fprintf(stderr, "Cannot read " PROC_NET_WIRELESS "\n"); return(-1); } /* Read the first line of buffer */ fgets(buff, sizeof(buff), fh); if(strstr(buff, "| WE") == NULL) { /* Prior to WE16, so explicit version not present */ /* Black magic */ if(strstr(buff, "| Missed") == NULL) v = 11; else v = 15; fclose(fh); return(v); } /* Read the second line of buffer */ fgets(buff, sizeof(buff), fh); /* Get to the last separator, to get the version */ p = strrchr(buff, '|'); if((p == NULL) || (sscanf(p + 1, "%d", &v) != 1)) { fprintf(stderr, "Cannot parse " PROC_NET_WIRELESS "\n"); fclose(fh); return(-1); } fclose(fh); return(v); } /*------------------------------------------------------------------*/ /* * Print the WE versions of the interface. */ static int print_iface_version_info(int skfd, char * ifname, char * args[], /* Command line args */ int count) /* Args count */ { struct iwreq wrq; char buffer[sizeof(iwrange) * 2]; /* Large enough */ struct iw_range * range; /* Avoid "Unused parameter" warning */ args = args; count = count; /* If no wireless name : no wireless extensions. * This enable us to treat the SIOCGIWRANGE failure below properly. */ if(iw_get_ext(skfd, ifname, SIOCGIWNAME, &wrq) < 0) return(-1); /* Cleanup */ memset(buffer, 0, sizeof(buffer)); wrq.u.data.pointer = (caddr_t) buffer; wrq.u.data.length = sizeof(buffer); wrq.u.data.flags = 0; if(iw_get_ext(skfd, ifname, SIOCGIWRANGE, &wrq) < 0) { /* Interface support WE (see above), but not IWRANGE */ fprintf(stderr, "%-8.16s Driver has no Wireless Extension version information.\n\n", ifname); return(0); } /* Copy stuff at the right place, ignore extra */ range = (struct iw_range *) buffer; /* For new versions, we can check the version directly, for old versions * we use magic. 300 bytes is a also magic number, don't touch... */ if(wrq.u.data.length >= 300) { /* Version is always at the same offset, so it's ok */ printf("%-8.16s Recommend Wireless Extension v%d or later,\n", ifname, range->we_version_source); printf(" Currently compiled with Wireless Extension v%d.\n\n", range->we_version_compiled); } else { fprintf(stderr, "%-8.16s Wireless Extension version too old.\n\n", ifname); } return(0); } /*------------------------------------------------------------------*/ /* * Print the WE versions of the tools. */ int iw_print_version_info(const char * toolname) { int skfd; /* generic raw socket desc. */ int we_kernel_version; /* Create a channel to the NET kernel. */ if((skfd = iw_sockets_open()) < 0) { perror("socket"); return -1; } /* Information about the tools themselves */ if(toolname != NULL) printf("%-8.16s Wireless-Tools version %d\n", toolname, WT_VERSION); printf(" Compatible with Wireless Extension v11 to v%d.\n\n", WE_MAX_VERSION); /* Get version from kernel */ we_kernel_version = iw_get_kernel_we_version(); /* Only version >= 16 can be verified, other are guessed */ if(we_kernel_version > 15) printf("Kernel Currently compiled with Wireless Extension v%d.\n\n", we_kernel_version); /* Version for each device */ iw_enum_devices(skfd, &print_iface_version_info, NULL, 0); iw_sockets_close(skfd); return 0; } /*------------------------------------------------------------------*/ /* * Get the range information out of the driver */ int iw_get_range_info(int skfd, const char * ifname, iwrange * range) { struct iwreq wrq; char buffer[sizeof(iwrange) * 2]; /* Large enough */ union iw_range_raw * range_raw; /* Cleanup */ bzero(buffer, sizeof(buffer)); wrq.u.data.pointer = (caddr_t) buffer; wrq.u.data.length = sizeof(buffer); wrq.u.data.flags = 0; if(iw_get_ext(skfd, ifname, SIOCGIWRANGE, &wrq) < 0) return(-1); /* Point to the buffer */ range_raw = (union iw_range_raw *) buffer; /* For new versions, we can check the version directly, for old versions * we use magic. 300 bytes is a also magic number, don't touch... */ if(wrq.u.data.length < 300) { /* That's v10 or earlier. Ouch ! Let's make a guess...*/ range_raw->range.we_version_compiled = 9; } /* Check how it needs to be processed */ if(range_raw->range.we_version_compiled > 15) { /* This is our native format, that's easy... */ /* Copy stuff at the right place, ignore extra */ memcpy((char *) range, buffer, sizeof(iwrange)); } else { /* Zero unknown fields */ bzero((char *) range, sizeof(struct iw_range)); /* Initial part unmoved */ memcpy((char *) range, buffer, iwr15_off(num_channels)); /* Frequencies pushed futher down towards the end */ memcpy((char *) range + iwr_off(num_channels), buffer + iwr15_off(num_channels), iwr15_off(sensitivity) - iwr15_off(num_channels)); /* This one moved up */ memcpy((char *) range + iwr_off(sensitivity), buffer + iwr15_off(sensitivity), iwr15_off(num_bitrates) - iwr15_off(sensitivity)); /* This one goes after avg_qual */ memcpy((char *) range + iwr_off(num_bitrates), buffer + iwr15_off(num_bitrates), iwr15_off(min_rts) - iwr15_off(num_bitrates)); /* Number of bitrates has changed, put it after */ memcpy((char *) range + iwr_off(min_rts), buffer + iwr15_off(min_rts), iwr15_off(txpower_capa) - iwr15_off(min_rts)); /* Added encoding_login_index, put it after */ memcpy((char *) range + iwr_off(txpower_capa), buffer + iwr15_off(txpower_capa), iwr15_off(txpower) - iwr15_off(txpower_capa)); /* Hum... That's an unexpected glitch. Bummer. */ memcpy((char *) range + iwr_off(txpower), buffer + iwr15_off(txpower), iwr15_off(avg_qual) - iwr15_off(txpower)); /* Avg qual moved up next to max_qual */ memcpy((char *) range + iwr_off(avg_qual), buffer + iwr15_off(avg_qual), sizeof(struct iw_quality)); } /* We are now checking much less than we used to do, because we can * accomodate more WE version. But, there are still cases where things * will break... */ if(!iw_ignore_version) { /* We don't like very old version (unfortunately kernel 2.2.X) */ if(range->we_version_compiled <= 10) { fprintf(stderr, "Warning: Driver for device %s has been compiled with an ancient version\n", ifname); fprintf(stderr, "of Wireless Extension, while this program support version 11 and later.\n"); fprintf(stderr, "Some things may be broken...\n\n"); } /* We don't like future versions of WE, because we can't cope with * the unknown */ if(range->we_version_compiled > WE_MAX_VERSION) { fprintf(stderr, "Warning: Driver for device %s has been compiled with version %d\n", ifname, range->we_version_compiled); fprintf(stderr, "of Wireless Extension, while this program supports up to version %d.\n", WE_MAX_VERSION); fprintf(stderr, "Some things may be broken...\n\n"); } /* Driver version verification */ if((range->we_version_compiled > 10) && (range->we_version_compiled < range->we_version_source)) { fprintf(stderr, "Warning: Driver for device %s recommend version %d of Wireless Extension,\n", ifname, range->we_version_source); fprintf(stderr, "but has been compiled with version %d, therefore some driver features\n", range->we_version_compiled); fprintf(stderr, "may not be available...\n\n"); } /* Note : we are only trying to catch compile difference, not source. * If the driver source has not been updated to the latest, it doesn't * matter because the new fields are set to zero */ } /* Don't complain twice. * In theory, the test apply to each individual driver, but usually * all drivers are compiled from the same kernel. */ iw_ignore_version = 1; return(0); } /*------------------------------------------------------------------*/ /* * Get information about what private ioctls are supported by the driver * * Note : there is one danger using this function. If it return 0, you * still need to free() the buffer. Beware. */ int iw_get_priv_info(int skfd, const char * ifname, iwprivargs ** ppriv) { struct iwreq wrq; iwprivargs * priv = NULL; /* Not allocated yet */ int maxpriv = 16; /* Minimum for compatibility WE<13 */ iwprivargs * newpriv; /* Some driver may return a very large number of ioctls. Some * others a very small number. We now use a dynamic allocation * of the array to satisfy everybody. Of course, as we don't know * in advance the size of the array, we try various increasing * sizes. Jean II */ do { /* (Re)allocate the buffer */ newpriv = realloc(priv, maxpriv * sizeof(priv[0])); if(newpriv == NULL) { fprintf(stderr, "%s: Allocation failed\n", __FUNCTION__); break; } priv = newpriv; /* Ask the driver if it's large enough */ wrq.u.data.pointer = (caddr_t) priv; wrq.u.data.length = maxpriv; wrq.u.data.flags = 0; if(iw_get_ext(skfd, ifname, SIOCGIWPRIV, &wrq) >= 0) { /* Success. Pass the buffer by pointer */ *ppriv = priv; /* Return the number of ioctls */ return(wrq.u.data.length); } /* Only E2BIG means the buffer was too small, abort on other errors */ if(errno != E2BIG) { /* Most likely "not supported". Don't barf. */ break; } /* Failed. We probably need a bigger buffer. Check if the kernel * gave us any hints. */ if(wrq.u.data.length > maxpriv) maxpriv = wrq.u.data.length; else maxpriv *= 2; } while(maxpriv < 1000); /* Cleanup */ if(priv) free(priv); *ppriv = NULL; return(-1); } /*------------------------------------------------------------------*/ /* * Get essential wireless config from the device driver * We will call all the classical wireless ioctl on the driver through * the socket to know what is supported and to get the settings... * Note : compare to the version in iwconfig, we extract only * what's *really* needed to configure a device... */ int iw_get_basic_config(int skfd, const char * ifname, wireless_config * info) { struct iwreq wrq; memset((char *) info, 0, sizeof(struct wireless_config)); /* Get wireless name */ if(iw_get_ext(skfd, ifname, SIOCGIWNAME, &wrq) < 0) /* If no wireless name : no wireless extensions */ return(-1); else { strncpy(info->name, wrq.u.name, IFNAMSIZ); info->name[IFNAMSIZ] = '\0'; } /* Get network ID */ if(iw_get_ext(skfd, ifname, SIOCGIWNWID, &wrq) >= 0) { info->has_nwid = 1; memcpy(&(info->nwid), &(wrq.u.nwid), sizeof(iwparam)); } /* Get frequency / channel */ if(iw_get_ext(skfd, ifname, SIOCGIWFREQ, &wrq) >= 0) { info->has_freq = 1; info->freq = iw_freq2float(&(wrq.u.freq)); info->freq_flags = wrq.u.freq.flags; } /* Get encryption information */ wrq.u.data.pointer = (caddr_t) info->key; wrq.u.data.length = IW_ENCODING_TOKEN_MAX; wrq.u.data.flags = 0; if(iw_get_ext(skfd, ifname, SIOCGIWENCODE, &wrq) >= 0) { info->has_key = 1; info->key_size = wrq.u.data.length; info->key_flags = wrq.u.data.flags; } /* Get ESSID */ wrq.u.essid.pointer = (caddr_t) info->essid; wrq.u.essid.length = IW_ESSID_MAX_SIZE + 2; wrq.u.essid.flags = 0; if(iw_get_ext(skfd, ifname, SIOCGIWESSID, &wrq) >= 0) { info->has_essid = 1; info->essid_on = wrq.u.data.flags; info->essid_len = wrq.u.essid.length; } /* Get operation mode */ if(iw_get_ext(skfd, ifname, SIOCGIWMODE, &wrq) >= 0) { info->has_mode = 1; /* Note : event->u.mode is unsigned, no need to check <= 0 */ if(wrq.u.mode < IW_NUM_OPER_MODE) info->mode = wrq.u.mode; else info->mode = IW_NUM_OPER_MODE; /* Unknown/bug */ } return(0); } /*------------------------------------------------------------------*/ /* * Set essential wireless config in the device driver * We will call all the classical wireless ioctl on the driver through * the socket to know what is supported and to set the settings... * We support only the restricted set as above... */ int iw_set_basic_config(int skfd, const char * ifname, wireless_config * info) { struct iwreq wrq; int ret = 0; /* Get wireless name (check if interface is valid) */ if(iw_get_ext(skfd, ifname, SIOCGIWNAME, &wrq) < 0) /* If no wireless name : no wireless extensions */ return(-2); /* Set the current mode of operation * Mode need to be first : some settings apply only in a specific mode * (such as frequency). */ if(info->has_mode) { strncpy(wrq.ifr_name, ifname, IFNAMSIZ); wrq.u.mode = info->mode; if(iw_get_ext(skfd, ifname, SIOCSIWMODE, &wrq) < 0) { fprintf(stderr, "SIOCSIWMODE: %s\n", strerror(errno)); ret = -1; } } /* Set frequency / channel */ if(info->has_freq) { iw_float2freq(info->freq, &(wrq.u.freq)); if(iw_set_ext(skfd, ifname, SIOCSIWFREQ, &wrq) < 0) { fprintf(stderr, "SIOCSIWFREQ: %s\n", strerror(errno)); ret = -1; } } /* Set encryption information */ if(info->has_key) { int flags = info->key_flags; /* Check if there is a key index */ if((flags & IW_ENCODE_INDEX) > 0) { /* Set the index */ wrq.u.data.pointer = (caddr_t) NULL; wrq.u.data.flags = (flags & (IW_ENCODE_INDEX)) | IW_ENCODE_NOKEY; wrq.u.data.length = 0; if(iw_set_ext(skfd, ifname, SIOCSIWENCODE, &wrq) < 0) { fprintf(stderr, "SIOCSIWENCODE(%d): %s\n", errno, strerror(errno)); ret = -1; } } /* Mask out index to minimise probability of reject when setting key */ flags = flags & (~IW_ENCODE_INDEX); /* Set the key itself (set current key in this case) */ wrq.u.data.pointer = (caddr_t) info->key; wrq.u.data.length = info->key_size; wrq.u.data.flags = flags; /* Compatibility with WE<13 */ if(flags & IW_ENCODE_NOKEY) wrq.u.data.pointer = NULL; if(iw_set_ext(skfd, ifname, SIOCSIWENCODE, &wrq) < 0) { fprintf(stderr, "SIOCSIWENCODE(%d): %s\n", errno, strerror(errno)); ret = -1; } } /* Set Network ID, if available (this is for non-802.11 cards) */ if(info->has_nwid) { memcpy(&(wrq.u.nwid), &(info->nwid), sizeof(iwparam)); wrq.u.nwid.fixed = 1; /* Hum... When in Rome... */ if(iw_set_ext(skfd, ifname, SIOCSIWNWID, &wrq) < 0) { fprintf(stderr, "SIOCSIWNWID: %s\n", strerror(errno)); ret = -1; } } /* Set ESSID (extended network), if available. * ESSID need to be last : most device re-perform the scanning/discovery * when this is set, and things like encryption keys are better be * defined if we want to discover the right set of APs/nodes. */ if(info->has_essid) { int we_kernel_version; we_kernel_version = iw_get_kernel_we_version(); wrq.u.essid.pointer = (caddr_t) info->essid; wrq.u.essid.length = strlen(info->essid); wrq.u.data.flags = info->essid_on; if(we_kernel_version < 21) wrq.u.essid.length++; if(iw_set_ext(skfd, ifname, SIOCSIWESSID, &wrq) < 0) { fprintf(stderr, "SIOCSIWESSID: %s\n", strerror(errno)); ret = -1; } } return(ret); } /*********************** PROTOCOL SUBROUTINES ***********************/ /* * Fun stuff with protocol identifiers (SIOCGIWNAME). * We assume that drivers are returning sensible values in there, * which is not always the case :-( */ /*------------------------------------------------------------------*/ /* * Compare protocol identifiers. * We don't want to know if the two protocols are the exactly same, * but if they interoperate at some level, and also if they accept the * same type of config (ESSID vs NWID, freq...). * This is supposed to work around the alphabet soup. * Return 1 if protocols are compatible, 0 otherwise */ int iw_protocol_compare(const char * protocol1, const char * protocol2) { const char * dot11 = "IEEE 802.11"; const char * dot11_ds = "Dbg"; const char * dot11_5g = "a"; /* If the strings are the same -> easy */ if(!strncmp(protocol1, protocol2, IFNAMSIZ)) return(1); /* Are we dealing with one of the 802.11 variant ? */ if( (!strncmp(protocol1, dot11, strlen(dot11))) && (!strncmp(protocol2, dot11, strlen(dot11))) ) { const char * sub1 = protocol1 + strlen(dot11); const char * sub2 = protocol2 + strlen(dot11); unsigned int i; int isds1 = 0; int isds2 = 0; int is5g1 = 0; int is5g2 = 0; /* Check if we find the magic letters telling it's DS compatible */ for(i = 0; i < strlen(dot11_ds); i++) { if(strchr(sub1, dot11_ds[i]) != NULL) isds1 = 1; if(strchr(sub2, dot11_ds[i]) != NULL) isds2 = 1; } if(isds1 && isds2) return(1); /* Check if we find the magic letters telling it's 5GHz compatible */ for(i = 0; i < strlen(dot11_5g); i++) { if(strchr(sub1, dot11_5g[i]) != NULL) is5g1 = 1; if(strchr(sub2, dot11_5g[i]) != NULL) is5g2 = 1; } if(is5g1 && is5g2) return(1); } /* Not compatible */ return(0); } /************************ ESSID SUBROUTINES ************************/ /* * The ESSID identify 802.11 networks, and is an array if 32 bytes. * Most people use it as an ASCII string, and are happy with it. * However, any byte is valid, including the NUL character. Characters * beyond the ASCII range are interpreted according to the locale and * the OS, which is somethign we don't control (network of other * people). * Routines in here try to deal with that in asafe way. */ /*------------------------------------------------------------------*/ /* * Escape non-ASCII characters from ESSID. * This allow us to display those weirds characters to the user. * * Source is 32 bytes max. * Destination buffer needs to be at least 129 bytes, will be NUL * terminated. */ void iw_essid_escape(char * dest, const char * src, const int slen) { const unsigned char * s = (const unsigned char *) src; const unsigned char * e = s + slen; char * d = dest; /* Look every character of the string */ while(s < e) { int isescape; /* Escape the escape to avoid ambiguity. * We do a fast path test for performance reason. Compiler will * optimise all that ;-) */ if(*s == '\\') { /* Check if we would confuse it with an escape sequence */ if((e-s) > 4 && (s[1] == 'x') && (isxdigit(s[2])) && (isxdigit(s[3]))) { isescape = 1; } else isescape = 0; } else isescape = 0; /* Is it a non-ASCII character ??? */ if(isescape || !isascii(*s) || iscntrl(*s)) { /* Escape */ sprintf(d, "\\x%02X", *s); d += 4; } else { /* Plain ASCII, just copy */ *d = *s; d++; } s++; } /* NUL terminate destination */ *d = '\0'; } /* ---------------------------------------------------------------- */ /* * Un-Escape non-ASCII characters from ESSID * This allow the user to specify weird characters in ESSID. * * The source is a NUL terminated string. * Destination buffer is at least the size of source (ESSID will shrink) * Destination may contains NUL, therefore we return the length. * This function still works is src and dest are the same ;-) */ int iw_essid_unescape(char * dest, const char * src) { const char * s = src; char * d = dest; char * p; int len; /* Look-up the next '\' sequence, stop when no more */ while((p = strchr(s, '\\')) != NULL) { /* Copy block of unescaped chars before the '\' */ len = p - s; memcpy(d, s, len); d += len; s += len; /* Identical to 's = p' */ /* Check if it is really an escape sequence. We do also check for NUL */ if((s[1] == 'x') && (isxdigit(s[2])) && (isxdigit(s[3]))) { unsigned int temp; /* Valid Escape sequence, un-escape it */ sscanf(s + 2, "%2X", &temp); *d = temp; d++; s+=4; } else { /* Not valid, don't un-escape it */ *d = *s; d++; s++; } } /* Copy remaining of the string */ len = strlen(s); memcpy(d, s, len + 1); /* Return length */ return((d - dest) + len); } /********************** FREQUENCY SUBROUTINES ***********************/ /* * Note : the two functions below are the cause of troubles on * various embeeded platforms, as they are the reason we require * libm (math library). * In this case, please use enable BUILD_NOLIBM in the makefile * * FIXME : check negative mantissa and exponent */ /*------------------------------------------------------------------*/ /* * Convert a floating point the our internal representation of * frequencies. * The kernel doesn't want to hear about floating point, so we use * this custom format instead. */ void iw_float2freq(double in, iwfreq * out) { #ifdef WE_NOLIBM /* Version without libm : slower */ out->e = 0; while(in > 1e9) { in /= 10; out->e++; } out->m = (long) in; #else /* WE_NOLIBM */ /* Version with libm : faster */ out->e = (short) (floor(log10(in))); if(out->e > 8) { out->m = ((long) (floor(in / pow(10,out->e - 6)))) * 100; out->e -= 8; } else { out->m = (long) in; out->e = 0; } #endif /* WE_NOLIBM */ } /*------------------------------------------------------------------*/ /* * Convert our internal representation of frequencies to a floating point. */ double iw_freq2float(const iwfreq * in) { #ifdef WE_NOLIBM /* Version without libm : slower */ int i; double res = (double) in->m; for(i = 0; i < in->e; i++) res *= 10; return(res); #else /* WE_NOLIBM */ /* Version with libm : faster */ return ((double) in->m) * pow(10,in->e); #endif /* WE_NOLIBM */ } /*------------------------------------------------------------------*/ /* * Output a frequency with proper scaling */ void iw_print_freq_value(char * buffer, int buflen, double freq) { if(freq < KILO) snprintf(buffer, buflen, "%g", freq); else { char scale; int divisor; if(freq >= GIGA) { scale = 'G'; divisor = GIGA; } else { if(freq >= MEGA) { scale = 'M'; divisor = MEGA; } else { scale = 'k'; divisor = KILO; } } snprintf(buffer, buflen, "%g %cHz", freq / divisor, scale); } } /*------------------------------------------------------------------*/ /* * Output a frequency with proper scaling */ void iw_print_freq(char * buffer, int buflen, double freq, int channel, int freq_flags) { char sep = ((freq_flags & IW_FREQ_FIXED) ? '=' : ':'); char vbuf[16]; /* Print the frequency/channel value */ iw_print_freq_value(vbuf, sizeof(vbuf), freq); /* Check if channel only */ if(freq < KILO) snprintf(buffer, buflen, "Channel%c%s", sep, vbuf); else { /* Frequency. Check if we have a channel as well */ if(channel >= 0) snprintf(buffer, buflen, "Frequency%c%s (Channel %d)", sep, vbuf, channel); else snprintf(buffer, buflen, "Frequency%c%s", sep, vbuf); } } /*------------------------------------------------------------------*/ /* * Convert a frequency to a channel (negative -> error) */ int iw_freq_to_channel(double freq, const struct iw_range * range) { double ref_freq; int k; /* Check if it's a frequency or not already a channel */ if(freq < KILO) return(-1); /* We compare the frequencies as double to ignore differences * in encoding. Slower, but safer... */ for(k = 0; k < range->num_frequency; k++) { ref_freq = iw_freq2float(&(range->freq[k])); if(freq == ref_freq) return(range->freq[k].i); } /* Not found */ return(-2); } /*------------------------------------------------------------------*/ /* * Convert a channel to a frequency (negative -> error) * Return the channel on success */ int iw_channel_to_freq(int channel, double * pfreq, const struct iw_range * range) { int has_freq = 0; int k; /* Check if the driver support only channels or if it has frequencies */ for(k = 0; k < range->num_frequency; k++) { if((range->freq[k].e != 0) || (range->freq[k].m > (int) KILO)) has_freq = 1; } if(!has_freq) return(-1); /* Find the correct frequency in the list */ for(k = 0; k < range->num_frequency; k++) { if(range->freq[k].i == channel) { *pfreq = iw_freq2float(&(range->freq[k])); return(channel); } } /* Not found */ return(-2); } /*********************** BITRATE SUBROUTINES ***********************/ /*------------------------------------------------------------------*/ /* * Output a bitrate with proper scaling */ void iw_print_bitrate(char * buffer, int buflen, int bitrate) { double rate = bitrate; char scale; int divisor; if(rate >= GIGA) { scale = 'G'; divisor = GIGA; } else { if(rate >= MEGA) { scale = 'M'; divisor = MEGA; } else { scale = 'k'; divisor = KILO; } } snprintf(buffer, buflen, "%g %cb/s", rate / divisor, scale); } /************************ POWER SUBROUTINES *************************/ /*------------------------------------------------------------------*/ /* * Convert a value in dBm to a value in milliWatt. */ int iw_dbm2mwatt(int in) { #ifdef WE_NOLIBM /* Version without libm : slower */ int ip = in / 10; int fp = in % 10; int k; double res = 1.0; /* Split integral and floating part to avoid accumulating rounding errors */ for(k = 0; k < ip; k++) res *= 10; for(k = 0; k < fp; k++) res *= LOG10_MAGIC; return((int) res); #else /* WE_NOLIBM */ /* Version with libm : faster */ return((int) (floor(pow(10.0, (((double) in) / 10.0))))); #endif /* WE_NOLIBM */ } /*------------------------------------------------------------------*/ /* * Convert a value in milliWatt to a value in dBm. */ int iw_mwatt2dbm(int in) { #ifdef WE_NOLIBM /* Version without libm : slower */ double fin = (double) in; int res = 0; /* Split integral and floating part to avoid accumulating rounding errors */ while(fin > 10.0) { res += 10; fin /= 10.0; } while(fin > 1.000001) /* Eliminate rounding errors, take ceil */ { res += 1; fin /= LOG10_MAGIC; } return(res); #else /* WE_NOLIBM */ /* Version with libm : faster */ return((int) (ceil(10.0 * log10((double) in)))); #endif /* WE_NOLIBM */ } /*------------------------------------------------------------------*/ /* * Output a txpower with proper conversion */ void iw_print_txpower(char * buffer, int buflen, struct iw_param * txpower) { int dbm; /* Check if disabled */ if(txpower->disabled) { snprintf(buffer, buflen, "off"); } else { /* Check for relative values */ if(txpower->flags & IW_TXPOW_RELATIVE) { snprintf(buffer, buflen, "%d", txpower->value); } else { /* Convert everything to dBm */ if(txpower->flags & IW_TXPOW_MWATT) dbm = iw_mwatt2dbm(txpower->value); else dbm = txpower->value; /* Display */ snprintf(buffer, buflen, "%d dBm", dbm); } } } /********************** STATISTICS SUBROUTINES **********************/ /*------------------------------------------------------------------*/ /* * Read /proc/net/wireless to get the latest statistics * Note : strtok not thread safe, not used in WE-12 and later. */ int iw_get_stats(int skfd, const char * ifname, iwstats * stats, const iwrange * range, int has_range) { /* Fortunately, we can always detect this condition properly */ if((has_range) && (range->we_version_compiled > 11)) { struct iwreq wrq; wrq.u.data.pointer = (caddr_t) stats; wrq.u.data.length = sizeof(struct iw_statistics); wrq.u.data.flags = 1; /* Clear updated flag */ strncpy(wrq.ifr_name, ifname, IFNAMSIZ); if(iw_get_ext(skfd, ifname, SIOCGIWSTATS, &wrq) < 0) return(-1); /* Format has not changed since WE-12, no conversion */ return(0); } else { FILE * f = fopen(PROC_NET_WIRELESS, "r"); char buf[256]; char * bp; int t; if(f==NULL) return -1; /* Loop on all devices */ while(fgets(buf,255,f)) { bp=buf; while(*bp&&isspace(*bp)) bp++; /* Is it the good device ? */ if(strncmp(bp,ifname,strlen(ifname))==0 && bp[strlen(ifname)]==':') { /* Skip ethX: */ bp=strchr(bp,':'); bp++; /* -- status -- */ bp = strtok(bp, " "); sscanf(bp, "%X", &t); stats->status = (unsigned short) t; /* -- link quality -- */ bp = strtok(NULL, " "); if(strchr(bp,'.') != NULL) stats->qual.updated |= 1; sscanf(bp, "%d", &t); stats->qual.qual = (unsigned char) t; /* -- signal level -- */ bp = strtok(NULL, " "); if(strchr(bp,'.') != NULL) stats->qual.updated |= 2; sscanf(bp, "%d", &t); stats->qual.level = (unsigned char) t; /* -- noise level -- */ bp = strtok(NULL, " "); if(strchr(bp,'.') != NULL) stats->qual.updated += 4; sscanf(bp, "%d", &t); stats->qual.noise = (unsigned char) t; /* -- discarded packets -- */ bp = strtok(NULL, " "); sscanf(bp, "%d", &stats->discard.nwid); bp = strtok(NULL, " "); sscanf(bp, "%d", &stats->discard.code); bp = strtok(NULL, " "); sscanf(bp, "%d", &stats->discard.misc); fclose(f); /* No conversion needed */ return 0; } } fclose(f); return -1; } } /*------------------------------------------------------------------*/ /* * Output the link statistics, taking care of formating */ void iw_print_stats(char * buffer, int buflen, const iwqual * qual, const iwrange * range, int has_range) { int len; /* People are very often confused by the 8 bit arithmetic happening * here. * All the values here are encoded in a 8 bit integer. 8 bit integers * are either unsigned [0 ; 255], signed [-128 ; +127] or * negative [-255 ; 0]. * Further, on 8 bits, 0x100 == 256 == 0. * * Relative/percent values are always encoded unsigned, between 0 and 255. * Absolute/dBm values are always encoded between -192 and 63. * (Note that up to version 28 of Wireless Tools, dBm used to be * encoded always negative, between -256 and -1). * * How do we separate relative from absolute values ? * The old way is to use the range to do that. As of WE-19, we have * an explicit IW_QUAL_DBM flag in updated... * The range allow to specify the real min/max of the value. As the * range struct only specify one bound of the value, we assume that * the other bound is 0 (zero). * For relative values, range is [0 ; range->max]. * For absolute values, range is [range->max ; 63]. * * Let's take two example : * 1) value is 75%. qual->value = 75 ; range->max_qual.value = 100 * 2) value is -54dBm. noise floor of the radio is -104dBm. * qual->value = -54 = 202 ; range->max_qual.value = -104 = 152 * * Jean II */ /* Just do it... * The old way to detect dBm require both the range and a non-null * level (which confuse the test). The new way can deal with level of 0 * because it does an explicit test on the flag. */ if(has_range && ((qual->level != 0) || (qual->updated & (IW_QUAL_DBM | IW_QUAL_RCPI)))) { /* Deal with quality : always a relative value */ if(!(qual->updated & IW_QUAL_QUAL_INVALID)) { len = snprintf(buffer, buflen, "Quality%c%d/%d ", qual->updated & IW_QUAL_QUAL_UPDATED ? '=' : ':', qual->qual, range->max_qual.qual); buffer += len; buflen -= len; } /* Check if the statistics are in RCPI (IEEE 802.11k) */ if(qual->updated & IW_QUAL_RCPI) { /* Deal with signal level in RCPI */ /* RCPI = int{(Power in dBm +110)*2} for 0dbm > Power > -110dBm */ if(!(qual->updated & IW_QUAL_LEVEL_INVALID)) { double rcpilevel = (qual->level / 2.0) - 110.0; len = snprintf(buffer, buflen, "Signal level%c%g dBm ", qual->updated & IW_QUAL_LEVEL_UPDATED ? '=' : ':', rcpilevel); buffer += len; buflen -= len; } /* Deal with noise level in dBm (absolute power measurement) */ if(!(qual->updated & IW_QUAL_NOISE_INVALID)) { double rcpinoise = (qual->noise / 2.0) - 110.0; len = snprintf(buffer, buflen, "Noise level%c%g dBm", qual->updated & IW_QUAL_NOISE_UPDATED ? '=' : ':', rcpinoise); } } else { /* Check if the statistics are in dBm */ if((qual->updated & IW_QUAL_DBM) || (qual->level > range->max_qual.level)) { /* Deal with signal level in dBm (absolute power measurement) */ if(!(qual->updated & IW_QUAL_LEVEL_INVALID)) { int dblevel = qual->level; /* Implement a range for dBm [-192; 63] */ if(qual->level >= 64) dblevel -= 0x100; len = snprintf(buffer, buflen, "Signal level%c%d dBm ", qual->updated & IW_QUAL_LEVEL_UPDATED ? '=' : ':', dblevel); buffer += len; buflen -= len; } /* Deal with noise level in dBm (absolute power measurement) */ if(!(qual->updated & IW_QUAL_NOISE_INVALID)) { int dbnoise = qual->noise; /* Implement a range for dBm [-192; 63] */ if(qual->noise >= 64) dbnoise -= 0x100; len = snprintf(buffer, buflen, "Noise level%c%d dBm", qual->updated & IW_QUAL_NOISE_UPDATED ? '=' : ':', dbnoise); } } else { /* Deal with signal level as relative value (0 -> max) */ if(!(qual->updated & IW_QUAL_LEVEL_INVALID)) { len = snprintf(buffer, buflen, "Signal level%c%d/%d ", qual->updated & IW_QUAL_LEVEL_UPDATED ? '=' : ':', qual->level, range->max_qual.level); buffer += len; buflen -= len; } /* Deal with noise level as relative value (0 -> max) */ if(!(qual->updated & IW_QUAL_NOISE_INVALID)) { len = snprintf(buffer, buflen, "Noise level%c%d/%d", qual->updated & IW_QUAL_NOISE_UPDATED ? '=' : ':', qual->noise, range->max_qual.noise); } } } } else { /* We can't read the range, so we don't know... */ snprintf(buffer, buflen, "Quality:%d Signal level:%d Noise level:%d", qual->qual, qual->level, qual->noise); } } /*********************** ENCODING SUBROUTINES ***********************/ /*------------------------------------------------------------------*/ /* * Output the encoding key, with a nice formating */ void iw_print_key(char * buffer, int buflen, const unsigned char * key, /* Must be unsigned */ int key_size, int key_flags) { int i; /* Check buffer size -> 1 bytes => 2 digits + 1/2 separator */ if((key_size * 3) > buflen) { snprintf(buffer, buflen, ""); return; } /* Is the key present ??? */ if(key_flags & IW_ENCODE_NOKEY) { /* Nope : print on or dummy */ if(key_size <= 0) strcpy(buffer, "on"); /* Size checked */ else { strcpy(buffer, "**"); /* Size checked */ buffer +=2; for(i = 1; i < key_size; i++) { if((i & 0x1) == 0) strcpy(buffer++, "-"); /* Size checked */ strcpy(buffer, "**"); /* Size checked */ buffer +=2; } } } else { /* Yes : print the key */ sprintf(buffer, "%.2X", key[0]); /* Size checked */ buffer +=2; for(i = 1; i < key_size; i++) { if((i & 0x1) == 0) strcpy(buffer++, "-"); /* Size checked */ sprintf(buffer, "%.2X", key[i]); /* Size checked */ buffer +=2; } } } /*------------------------------------------------------------------*/ /* * Convert a passphrase into a key * ### NOT IMPLEMENTED ### * Return size of the key, or 0 (no key) or -1 (error) */ static int iw_pass_key(const char * input, unsigned char * key) { input = input; key = key; fprintf(stderr, "Error: Passphrase not implemented\n"); return(-1); } /*------------------------------------------------------------------*/ /* * Parse a key from the command line. * Return size of the key, or 0 (no key) or -1 (error) * If the key is too long, it's simply truncated... */ int iw_in_key(const char * input, unsigned char * key) { int keylen = 0; /* Check the type of key */ if(!strncmp(input, "s:", 2)) { /* First case : as an ASCII string (Lucent/Agere cards) */ keylen = strlen(input + 2); /* skip "s:" */ if(keylen > IW_ENCODING_TOKEN_MAX) keylen = IW_ENCODING_TOKEN_MAX; memcpy(key, input + 2, keylen); } else if(!strncmp(input, "p:", 2)) { /* Second case : as a passphrase (PrismII cards) */ return(iw_pass_key(input + 2, key)); /* skip "p:" */ } else { const char * p; int dlen; /* Digits sequence length */ unsigned char out[IW_ENCODING_TOKEN_MAX]; /* Third case : as hexadecimal digits */ p = input; dlen = -1; /* Loop until we run out of chars in input or overflow the output */ while(*p != '\0') { int temph; int templ; int count; /* No more chars in this sequence */ if(dlen <= 0) { /* Skip separator */ if(dlen == 0) p++; /* Calculate num of char to next separator */ dlen = strcspn(p, "-:;.,"); } /* Get each char separatly (and not by two) so that we don't * get confused by 'enc' (=> '0E'+'0C') and similar */ count = sscanf(p, "%1X%1X", &temph, &templ); if(count < 1) return(-1); /* Error -> non-hex char */ /* Fixup odd strings such as '123' is '01'+'23' and not '12'+'03'*/ if(dlen % 2) count = 1; /* Put back two chars as one byte and output */ if(count == 2) templ |= temph << 4; else templ = temph; out[keylen++] = (unsigned char) (templ & 0xFF); /* Check overflow in output */ if(keylen >= IW_ENCODING_TOKEN_MAX) break; /* Move on to next chars */ p += count; dlen -= count; } /* We use a temporary output buffer 'out' so that if there is * an error, we don't overwrite the original key buffer. * Because of the way iwconfig loop on multiple key/enc arguments * until it finds an error in here, this is necessary to avoid * silently corrupting the encryption key... */ memcpy(key, out, keylen); } #ifdef DEBUG { char buf[IW_ENCODING_TOKEN_MAX * 3]; iw_print_key(buf, sizeof(buf), key, keylen, 0); printf("Got key : %d [%s]\n", keylen, buf); } #endif return(keylen); } /*------------------------------------------------------------------*/ /* * Parse a key from the command line. * Return size of the key, or 0 (no key) or -1 (error) */ int iw_in_key_full(int skfd, const char * ifname, const char * input, unsigned char * key, __u16 * flags) { int keylen = 0; char * p; if(!strncmp(input, "l:", 2)) { struct iw_range range; /* Extra case : as a login (user:passwd - Cisco LEAP) */ keylen = strlen(input + 2) + 1; /* skip "l:", add '\0' */ /* Most user/password is 8 char, so 18 char total, < 32 */ if(keylen > IW_ENCODING_TOKEN_MAX) keylen = IW_ENCODING_TOKEN_MAX; memcpy(key, input + 2, keylen); /* Separate the two strings */ p = strchr((char *) key, ':'); if(p == NULL) { fprintf(stderr, "Error: Invalid login format\n"); return(-1); } *p = '\0'; /* Extract range info */ if(iw_get_range_info(skfd, ifname, &range) < 0) /* Hum... Maybe we should return an error ??? */ memset(&range, 0, sizeof(range)); if(range.we_version_compiled > 15) { printf("flags = %X, index = %X\n", *flags, range.encoding_login_index); if((*flags & IW_ENCODE_INDEX) == 0) { /* Extract range info */ if(iw_get_range_info(skfd, ifname, &range) < 0) memset(&range, 0, sizeof(range)); printf("flags = %X, index = %X\n", *flags, range.encoding_login_index); /* Set the index the driver expects */ *flags |= range.encoding_login_index & IW_ENCODE_INDEX; } printf("flags = %X, index = %X\n", *flags, range.encoding_login_index); } } else /* Simpler routine above */ keylen = iw_in_key(input, key); return(keylen); } /******************* POWER MANAGEMENT SUBROUTINES *******************/ /*------------------------------------------------------------------*/ /* * Output a power management value with all attributes... */ void iw_print_pm_value(char * buffer, int buflen, int value, int flags, int we_version) { /* Check size */ if(buflen < 25) { snprintf(buffer, buflen, ""); return; } buflen -= 25; /* Modifiers */ if(flags & IW_POWER_MIN) { strcpy(buffer, " min"); /* Size checked */ buffer += 4; } if(flags & IW_POWER_MAX) { strcpy(buffer, " max"); /* Size checked */ buffer += 4; } /* Type */ if(flags & IW_POWER_TIMEOUT) { strcpy(buffer, " timeout:"); /* Size checked */ buffer += 9; } else { if(flags & IW_POWER_SAVING) { strcpy(buffer, " saving:"); /* Size checked */ buffer += 8; } else { strcpy(buffer, " period:"); /* Size checked */ buffer += 8; } } /* Display value without units */ if(flags & IW_POWER_RELATIVE) { if(we_version < 21) value /= MEGA; snprintf(buffer, buflen, "%d", value); } else { /* Display value with units */ if(value >= (int) MEGA) snprintf(buffer, buflen, "%gs", ((double) value) / MEGA); else if(value >= (int) KILO) snprintf(buffer, buflen, "%gms", ((double) value) / KILO); else snprintf(buffer, buflen, "%dus", value); } } /*------------------------------------------------------------------*/ /* * Output a power management mode */ void iw_print_pm_mode(char * buffer, int buflen, int flags) { /* Check size */ if(buflen < 28) { snprintf(buffer, buflen, ""); return; } /* Print the proper mode... */ switch(flags & IW_POWER_MODE) { case IW_POWER_UNICAST_R: strcpy(buffer, "mode:Unicast only received"); /* Size checked */ break; case IW_POWER_MULTICAST_R: strcpy(buffer, "mode:Multicast only received"); /* Size checked */ break; case IW_POWER_ALL_R: strcpy(buffer, "mode:All packets received"); /* Size checked */ break; case IW_POWER_FORCE_S: strcpy(buffer, "mode:Force sending"); /* Size checked */ break; case IW_POWER_REPEATER: strcpy(buffer, "mode:Repeat multicasts"); /* Size checked */ break; default: strcpy(buffer, ""); /* Size checked */ break; } } /***************** RETRY LIMIT/LIFETIME SUBROUTINES *****************/ /*------------------------------------------------------------------*/ /* * Output a retry value with all attributes... */ void iw_print_retry_value(char * buffer, int buflen, int value, int flags, int we_version) { /* Check buffer size */ if(buflen < 20) { snprintf(buffer, buflen, ""); return; } buflen -= 20; /* Modifiers */ if(flags & IW_RETRY_MIN) { strcpy(buffer, " min"); /* Size checked */ buffer += 4; } if(flags & IW_RETRY_MAX) { strcpy(buffer, " max"); /* Size checked */ buffer += 4; } if(flags & IW_RETRY_SHORT) { strcpy(buffer, " short"); /* Size checked */ buffer += 6; } if(flags & IW_RETRY_LONG) { strcpy(buffer, " long"); /* Size checked */ buffer += 6; } /* Type lifetime of limit */ if(flags & IW_RETRY_LIFETIME) { strcpy(buffer, " lifetime:"); /* Size checked */ buffer += 10; /* Display value without units */ if(flags & IW_RETRY_RELATIVE) { if(we_version < 21) value /= MEGA; snprintf(buffer, buflen, "%d", value); } else { /* Display value with units */ if(value >= (int) MEGA) snprintf(buffer, buflen, "%gs", ((double) value) / MEGA); else if(value >= (int) KILO) snprintf(buffer, buflen, "%gms", ((double) value) / KILO); else snprintf(buffer, buflen, "%dus", value); } } else snprintf(buffer, buflen, " limit:%d", value); } /************************* TIME SUBROUTINES *************************/ /*------------------------------------------------------------------*/ /* * Print timestamps * Inspired from irdadump... */ void iw_print_timeval(char * buffer, int buflen, const struct timeval * timev, const struct timezone * tz) { int s; s = (timev->tv_sec - tz->tz_minuteswest * 60) % 86400; snprintf(buffer, buflen, "%02d:%02d:%02d.%06u", s / 3600, (s % 3600) / 60, s % 60, (u_int32_t) timev->tv_usec); } /*********************** ADDRESS SUBROUTINES ************************/ /* * This section is mostly a cut & past from net-tools-1.2.0 * (Well... This has evolved over the years) * manage address display and input... */ /*------------------------------------------------------------------*/ /* * Check if interface support the right MAC address type... */ int iw_check_mac_addr_type(int skfd, const char * ifname) { struct ifreq ifr; /* Get the type of hardware address */ strncpy(ifr.ifr_name, ifname, IFNAMSIZ); if((ioctl(skfd, SIOCGIFHWADDR, &ifr) < 0) || ((ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) && (ifr.ifr_hwaddr.sa_family != ARPHRD_IEEE80211))) { /* Deep trouble... */ fprintf(stderr, "Interface %s doesn't support MAC addresses\n", ifname); return(-1); } #ifdef DEBUG { char buf[20]; printf("Hardware : %d - %s\n", ifr.ifr_hwaddr.sa_family, iw_saether_ntop(&ifr.ifr_hwaddr, buf)); } #endif return(0); } /*------------------------------------------------------------------*/ /* * Check if interface support the right interface address type... */ int iw_check_if_addr_type(int skfd, const char * ifname) { struct ifreq ifr; /* Get the type of interface address */ strncpy(ifr.ifr_name, ifname, IFNAMSIZ); if((ioctl(skfd, SIOCGIFADDR, &ifr) < 0) || (ifr.ifr_addr.sa_family != AF_INET)) { /* Deep trouble... */ fprintf(stderr, "Interface %s doesn't support IP addresses\n", ifname); return(-1); } #ifdef DEBUG printf("Interface : %d - 0x%lX\n", ifr.ifr_addr.sa_family, *((unsigned long *) ifr.ifr_addr.sa_data)); #endif return(0); } /*------------------------------------------------------------------*/ /* * Display an arbitrary length MAC address in readable format. */ char * iw_mac_ntop(const unsigned char * mac, int maclen, char * buf, int buflen) { int i; /* Overflow check (don't forget '\0') */ if(buflen < (maclen * 3 - 1 + 1)) return(NULL); /* First byte */ sprintf(buf, "%02X", mac[0]); /* Other bytes */ for(i = 1; i < maclen; i++) sprintf(buf + (i * 3) - 1, ":%02X", mac[i]); return(buf); } /*------------------------------------------------------------------*/ /* * Display an Ethernet address in readable format. */ void iw_ether_ntop(const struct ether_addr * eth, char * buf) { sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", eth->ether_addr_octet[0], eth->ether_addr_octet[1], eth->ether_addr_octet[2], eth->ether_addr_octet[3], eth->ether_addr_octet[4], eth->ether_addr_octet[5]); } /*------------------------------------------------------------------*/ /* * Display an Wireless Access Point Socket Address in readable format. * Note : 0x44 is an accident of history, that's what the Orinoco/PrismII * chipset report, and the driver doesn't filter it. */ char * iw_sawap_ntop(const struct sockaddr * sap, char * buf) { const struct ether_addr ether_zero = {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}; const struct ether_addr ether_bcast = {{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }}; const struct ether_addr ether_hack = {{ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44 }}; const struct ether_addr * ether_wap = (const struct ether_addr *) sap->sa_data; if(!iw_ether_cmp(ether_wap, ðer_zero)) sprintf(buf, "Not-Associated"); else if(!iw_ether_cmp(ether_wap, ðer_bcast)) sprintf(buf, "Invalid"); else if(!iw_ether_cmp(ether_wap, ðer_hack)) sprintf(buf, "None"); else iw_ether_ntop(ether_wap, buf); return(buf); } /*------------------------------------------------------------------*/ /* * Input an arbitrary length MAC address and convert to binary. * Return address size. */ int iw_mac_aton(const char * orig, unsigned char * mac, int macmax) { const char * p = orig; int maclen = 0; /* Loop on all bytes of the string */ while(*p != '\0') { int temph; int templ; int count; /* Extract one byte as two chars */ count = sscanf(p, "%1X%1X", &temph, &templ); if(count != 2) break; /* Error -> non-hex chars */ /* Output two chars as one byte */ templ |= temph << 4; mac[maclen++] = (unsigned char) (templ & 0xFF); /* Check end of string */ p += 2; if(*p == '\0') { #ifdef DEBUG char buf[20]; iw_ether_ntop((const struct ether_addr *) mac, buf); fprintf(stderr, "iw_mac_aton(%s): %s\n", orig, buf); #endif return(maclen); /* Normal exit */ } /* Check overflow */ if(maclen >= macmax) { #ifdef DEBUG fprintf(stderr, "iw_mac_aton(%s): trailing junk!\n", orig); #endif errno = E2BIG; return(0); /* Error -> overflow */ } /* Check separator */ if(*p != ':') break; p++; } /* Error... */ #ifdef DEBUG fprintf(stderr, "iw_mac_aton(%s): invalid ether address!\n", orig); #endif errno = EINVAL; return(0); } /*------------------------------------------------------------------*/ /* * Input an Ethernet address and convert to binary. */ int iw_ether_aton(const char *orig, struct ether_addr *eth) { int maclen; maclen = iw_mac_aton(orig, (unsigned char *) eth, ETH_ALEN); if((maclen > 0) && (maclen < ETH_ALEN)) { errno = EINVAL; maclen = 0; } return(maclen); } /*------------------------------------------------------------------*/ /* * Input an Internet address and convert to binary. */ int iw_in_inet(char *name, struct sockaddr *sap) { struct hostent *hp; struct netent *np; struct sockaddr_in *sain = (struct sockaddr_in *) sap; /* Grmpf. -FvK */ sain->sin_family = AF_INET; sain->sin_port = 0; /* Default is special, meaning 0.0.0.0. */ if (!strcmp(name, "default")) { sain->sin_addr.s_addr = INADDR_ANY; return(1); } /* Try the NETWORKS database to see if this is a known network. */ if ((np = getnetbyname(name)) != (struct netent *)NULL) { sain->sin_addr.s_addr = htonl(np->n_net); strcpy(name, np->n_name); return(1); } /* Always use the resolver (DNS name + IP addresses) */ if ((hp = gethostbyname(name)) == (struct hostent *)NULL) { errno = h_errno; return(-1); } memcpy((char *) &sain->sin_addr, (char *) hp->h_addr_list[0], hp->h_length); strcpy(name, hp->h_name); return(0); } /*------------------------------------------------------------------*/ /* * Input an address and convert to binary. */ int iw_in_addr(int skfd, const char * ifname, char * bufp, struct sockaddr *sap) { /* Check if it is a hardware or IP address */ if(strchr(bufp, ':') == NULL) { struct sockaddr if_address; struct arpreq arp_query; /* Check if we have valid interface address type */ if(iw_check_if_addr_type(skfd, ifname) < 0) { fprintf(stderr, "%-8.16s Interface doesn't support IP addresses\n", ifname); return(-1); } /* Read interface address */ if(iw_in_inet(bufp, &if_address) < 0) { fprintf(stderr, "Invalid interface address %s\n", bufp); return(-1); } /* Translate IP addresses to MAC addresses */ memcpy((char *) &(arp_query.arp_pa), (char *) &if_address, sizeof(struct sockaddr)); arp_query.arp_ha.sa_family = 0; arp_query.arp_flags = 0; /* The following restrict the search to the interface only */ /* For old kernels which complain, just comment it... */ strncpy(arp_query.arp_dev, ifname, IFNAMSIZ); if((ioctl(skfd, SIOCGARP, &arp_query) < 0) || !(arp_query.arp_flags & ATF_COM)) { fprintf(stderr, "Arp failed for %s on %s... (%d)\nTry to ping the address before setting it.\n", bufp, ifname, errno); return(-1); } /* Store new MAC address */ memcpy((char *) sap, (char *) &(arp_query.arp_ha), sizeof(struct sockaddr)); #ifdef DEBUG { char buf[20]; printf("IP Address %s => Hw Address = %s\n", bufp, iw_saether_ntop(sap, buf)); } #endif } else /* If it's an hardware address */ { /* Check if we have valid mac address type */ if(iw_check_mac_addr_type(skfd, ifname) < 0) { fprintf(stderr, "%-8.16s Interface doesn't support MAC addresses\n", ifname); return(-1); } /* Get the hardware address */ if(iw_saether_aton(bufp, sap) == 0) { fprintf(stderr, "Invalid hardware address %s\n", bufp); return(-1); } } #ifdef DEBUG { char buf[20]; printf("Hw Address = %s\n", iw_saether_ntop(sap, buf)); } #endif return(0); } /************************* MISC SUBROUTINES **************************/ /* Size (in bytes) of various events */ static const int priv_type_size[] = { 0, /* IW_PRIV_TYPE_NONE */ 1, /* IW_PRIV_TYPE_BYTE */ 1, /* IW_PRIV_TYPE_CHAR */ 0, /* Not defined */ sizeof(__u32), /* IW_PRIV_TYPE_INT */ sizeof(struct iw_freq), /* IW_PRIV_TYPE_FLOAT */ sizeof(struct sockaddr), /* IW_PRIV_TYPE_ADDR */ 0, /* Not defined */ }; /*------------------------------------------------------------------*/ /* * Max size in bytes of an private argument. */ int iw_get_priv_size(int args) { int num = args & IW_PRIV_SIZE_MASK; int type = (args & IW_PRIV_TYPE_MASK) >> 12; return(num * priv_type_size[type]); } /************************ EVENT SUBROUTINES ************************/ /* * The Wireless Extension API 14 and greater define Wireless Events, * that are used for various events and scanning. * Those functions help the decoding of events, so are needed only in * this case. */ /* -------------------------- CONSTANTS -------------------------- */ /* Type of headers we know about (basically union iwreq_data) */ #define IW_HEADER_TYPE_NULL 0 /* Not available */ #define IW_HEADER_TYPE_CHAR 2 /* char [IFNAMSIZ] */ #define IW_HEADER_TYPE_UINT 4 /* __u32 */ #define IW_HEADER_TYPE_FREQ 5 /* struct iw_freq */ #define IW_HEADER_TYPE_ADDR 6 /* struct sockaddr */ #define IW_HEADER_TYPE_POINT 8 /* struct iw_point */ #define IW_HEADER_TYPE_PARAM 9 /* struct iw_param */ #define IW_HEADER_TYPE_QUAL 10 /* struct iw_quality */ /* Handling flags */ /* Most are not implemented. I just use them as a reminder of some * cool features we might need one day ;-) */ #define IW_DESCR_FLAG_NONE 0x0000 /* Obvious */ /* Wrapper level flags */ #define IW_DESCR_FLAG_DUMP 0x0001 /* Not part of the dump command */ #define IW_DESCR_FLAG_EVENT 0x0002 /* Generate an event on SET */ #define IW_DESCR_FLAG_RESTRICT 0x0004 /* GET : request is ROOT only */ /* SET : Omit payload from generated iwevent */ #define IW_DESCR_FLAG_NOMAX 0x0008 /* GET : no limit on request size */ /* Driver level flags */ #define IW_DESCR_FLAG_WAIT 0x0100 /* Wait for driver event */ /* ---------------------------- TYPES ---------------------------- */ /* * Describe how a standard IOCTL looks like. */ struct iw_ioctl_description { __u8 header_type; /* NULL, iw_point or other */ __u8 token_type; /* Future */ __u16 token_size; /* Granularity of payload */ __u16 min_tokens; /* Min acceptable token number */ __u16 max_tokens; /* Max acceptable token number */ __u32 flags; /* Special handling of the request */ }; /* -------------------------- VARIABLES -------------------------- */ /* * Meta-data about all the standard Wireless Extension request we * know about. */ static const struct iw_ioctl_description standard_ioctl_descr[] = { [SIOCSIWCOMMIT - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_NULL, }, [SIOCGIWNAME - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_CHAR, .flags = IW_DESCR_FLAG_DUMP, }, [SIOCSIWNWID - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, .flags = IW_DESCR_FLAG_EVENT, }, [SIOCGIWNWID - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, .flags = IW_DESCR_FLAG_DUMP, }, [SIOCSIWFREQ - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_FREQ, .flags = IW_DESCR_FLAG_EVENT, }, [SIOCGIWFREQ - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_FREQ, .flags = IW_DESCR_FLAG_DUMP, }, [SIOCSIWMODE - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_UINT, .flags = IW_DESCR_FLAG_EVENT, }, [SIOCGIWMODE - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_UINT, .flags = IW_DESCR_FLAG_DUMP, }, [SIOCSIWSENS - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCGIWSENS - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCSIWRANGE - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_NULL, }, [SIOCGIWRANGE - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = sizeof(struct iw_range), .flags = IW_DESCR_FLAG_DUMP, }, [SIOCSIWPRIV - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_NULL, }, [SIOCGIWPRIV - SIOCIWFIRST] = { /* (handled directly by us) */ .header_type = IW_HEADER_TYPE_NULL, }, [SIOCSIWSTATS - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_NULL, }, [SIOCGIWSTATS - SIOCIWFIRST] = { /* (handled directly by us) */ .header_type = IW_HEADER_TYPE_NULL, .flags = IW_DESCR_FLAG_DUMP, }, [SIOCSIWSPY - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = sizeof(struct sockaddr), .max_tokens = IW_MAX_SPY, }, [SIOCGIWSPY - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = sizeof(struct sockaddr) + sizeof(struct iw_quality), .max_tokens = IW_MAX_SPY, }, [SIOCSIWTHRSPY - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = sizeof(struct iw_thrspy), .min_tokens = 1, .max_tokens = 1, }, [SIOCGIWTHRSPY - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = sizeof(struct iw_thrspy), .min_tokens = 1, .max_tokens = 1, }, [SIOCSIWAP - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_ADDR, }, [SIOCGIWAP - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_ADDR, .flags = IW_DESCR_FLAG_DUMP, }, [SIOCSIWMLME - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .min_tokens = sizeof(struct iw_mlme), .max_tokens = sizeof(struct iw_mlme), }, [SIOCGIWAPLIST - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = sizeof(struct sockaddr) + sizeof(struct iw_quality), .max_tokens = IW_MAX_AP, .flags = IW_DESCR_FLAG_NOMAX, }, [SIOCSIWSCAN - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .min_tokens = 0, .max_tokens = sizeof(struct iw_scan_req), }, [SIOCGIWSCAN - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = IW_SCAN_MAX_DATA, .flags = IW_DESCR_FLAG_NOMAX, }, [SIOCSIWESSID - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = IW_ESSID_MAX_SIZE + 1, .flags = IW_DESCR_FLAG_EVENT, }, [SIOCGIWESSID - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = IW_ESSID_MAX_SIZE + 1, .flags = IW_DESCR_FLAG_DUMP, }, [SIOCSIWNICKN - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = IW_ESSID_MAX_SIZE + 1, }, [SIOCGIWNICKN - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = IW_ESSID_MAX_SIZE + 1, }, [SIOCSIWRATE - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCGIWRATE - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCSIWRTS - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCGIWRTS - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCSIWFRAG - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCGIWFRAG - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCSIWTXPOW - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCGIWTXPOW - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCSIWRETRY - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCGIWRETRY - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCSIWENCODE - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, /* Hack : this never returns any payload in event. * Fix the 64->32 bit hack... */ .token_size = 0, .max_tokens = IW_ENCODING_TOKEN_MAX, .flags = IW_DESCR_FLAG_EVENT | IW_DESCR_FLAG_RESTRICT, }, [SIOCGIWENCODE - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, /* Hack : this never returns any payload in event. * Fix the 64->32 bit hack... */ .token_size = 0, .max_tokens = IW_ENCODING_TOKEN_MAX, .flags = IW_DESCR_FLAG_DUMP | IW_DESCR_FLAG_RESTRICT, }, [SIOCSIWPOWER - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCGIWPOWER - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCSIWMODUL - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCGIWMODUL - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCSIWGENIE - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = IW_GENERIC_IE_MAX, }, [SIOCGIWGENIE - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = IW_GENERIC_IE_MAX, }, [SIOCSIWAUTH - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCGIWAUTH - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_PARAM, }, [SIOCSIWENCODEEXT - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .min_tokens = sizeof(struct iw_encode_ext), .max_tokens = sizeof(struct iw_encode_ext) + IW_ENCODING_TOKEN_MAX, }, [SIOCGIWENCODEEXT - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .min_tokens = sizeof(struct iw_encode_ext), .max_tokens = sizeof(struct iw_encode_ext) + IW_ENCODING_TOKEN_MAX, }, [SIOCSIWPMKSA - SIOCIWFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .min_tokens = sizeof(struct iw_pmksa), .max_tokens = sizeof(struct iw_pmksa), }, }; static const unsigned int standard_ioctl_num = (sizeof(standard_ioctl_descr) / sizeof(struct iw_ioctl_description)); /* * Meta-data about all the additional standard Wireless Extension events * we know about. */ static const struct iw_ioctl_description standard_event_descr[] = { [IWEVTXDROP - IWEVFIRST] = { .header_type = IW_HEADER_TYPE_ADDR, }, [IWEVQUAL - IWEVFIRST] = { .header_type = IW_HEADER_TYPE_QUAL, }, [IWEVCUSTOM - IWEVFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = IW_CUSTOM_MAX, }, [IWEVREGISTERED - IWEVFIRST] = { .header_type = IW_HEADER_TYPE_ADDR, }, [IWEVEXPIRED - IWEVFIRST] = { .header_type = IW_HEADER_TYPE_ADDR, }, [IWEVGENIE - IWEVFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = IW_GENERIC_IE_MAX, }, [IWEVMICHAELMICFAILURE - IWEVFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = sizeof(struct iw_michaelmicfailure), }, [IWEVASSOCREQIE - IWEVFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = IW_GENERIC_IE_MAX, }, [IWEVASSOCRESPIE - IWEVFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = IW_GENERIC_IE_MAX, }, [IWEVPMKIDCAND - IWEVFIRST] = { .header_type = IW_HEADER_TYPE_POINT, .token_size = 1, .max_tokens = sizeof(struct iw_pmkid_cand), }, }; static const unsigned int standard_event_num = (sizeof(standard_event_descr) / sizeof(struct iw_ioctl_description)); /* Size (in bytes) of various events */ static const int event_type_size[] = { IW_EV_LCP_PK_LEN, /* IW_HEADER_TYPE_NULL */ 0, IW_EV_CHAR_PK_LEN, /* IW_HEADER_TYPE_CHAR */ 0, IW_EV_UINT_PK_LEN, /* IW_HEADER_TYPE_UINT */ IW_EV_FREQ_PK_LEN, /* IW_HEADER_TYPE_FREQ */ IW_EV_ADDR_PK_LEN, /* IW_HEADER_TYPE_ADDR */ 0, IW_EV_POINT_PK_LEN, /* Without variable payload */ IW_EV_PARAM_PK_LEN, /* IW_HEADER_TYPE_PARAM */ IW_EV_QUAL_PK_LEN, /* IW_HEADER_TYPE_QUAL */ }; /*------------------------------------------------------------------*/ /* * Initialise the struct stream_descr so that we can extract * individual events from the event stream. */ void iw_init_event_stream(struct stream_descr * stream, /* Stream of events */ char * data, int len) { /* Cleanup */ memset((char *) stream, '\0', sizeof(struct stream_descr)); /* Set things up */ stream->current = data; stream->end = data + len; } /*------------------------------------------------------------------*/ /* * Extract the next event from the event stream. */ int iw_extract_event_stream(struct stream_descr * stream, /* Stream of events */ struct iw_event * iwe, /* Extracted event */ int we_version) { const struct iw_ioctl_description * descr = NULL; int event_type = 0; unsigned int event_len = 1; /* Invalid */ char * pointer; /* Don't "optimise" the following variable, it will crash */ unsigned cmd_index; /* *MUST* be unsigned */ /* Check for end of stream */ if((stream->current + IW_EV_LCP_PK_LEN) > stream->end) return(0); #ifdef DEBUG printf("DBG - stream->current = %p, stream->value = %p, stream->end = %p\n", stream->current, stream->value, stream->end); #endif /* Extract the event header (to get the event id). * Note : the event may be unaligned, therefore copy... */ memcpy((char *) iwe, stream->current, IW_EV_LCP_PK_LEN); #ifdef DEBUG printf("DBG - iwe->cmd = 0x%X, iwe->len = %d\n", iwe->cmd, iwe->len); #endif /* Check invalid events */ if(iwe->len <= IW_EV_LCP_PK_LEN) return(-1); /* Get the type and length of that event */ if(iwe->cmd <= SIOCIWLAST) { cmd_index = iwe->cmd - SIOCIWFIRST; if(cmd_index < standard_ioctl_num) descr = &(standard_ioctl_descr[cmd_index]); } else { cmd_index = iwe->cmd - IWEVFIRST; if(cmd_index < standard_event_num) descr = &(standard_event_descr[cmd_index]); } if(descr != NULL) event_type = descr->header_type; /* Unknown events -> event_type=0 => IW_EV_LCP_PK_LEN */ event_len = event_type_size[event_type]; /* Fixup for earlier version of WE */ if((we_version <= 18) && (event_type == IW_HEADER_TYPE_POINT)) event_len += IW_EV_POINT_OFF; /* Check if we know about this event */ if(event_len <= IW_EV_LCP_PK_LEN) { /* Skip to next event */ stream->current += iwe->len; return(2); } event_len -= IW_EV_LCP_PK_LEN; /* Set pointer on data */ if(stream->value != NULL) pointer = stream->value; /* Next value in event */ else pointer = stream->current + IW_EV_LCP_PK_LEN; /* First value in event */ #ifdef DEBUG printf("DBG - event_type = %d, event_len = %d, pointer = %p\n", event_type, event_len, pointer); #endif /* Copy the rest of the event (at least, fixed part) */ if((pointer + event_len) > stream->end) { /* Go to next event */ stream->current += iwe->len; return(-2); } /* Fixup for WE-19 and later : pointer no longer in the stream */ /* Beware of alignement. Dest has local alignement, not packed */ if((we_version > 18) && (event_type == IW_HEADER_TYPE_POINT)) memcpy((char *) iwe + IW_EV_LCP_LEN + IW_EV_POINT_OFF, pointer, event_len); else memcpy((char *) iwe + IW_EV_LCP_LEN, pointer, event_len); /* Skip event in the stream */ pointer += event_len; /* Special processing for iw_point events */ if(event_type == IW_HEADER_TYPE_POINT) { /* Check the length of the payload */ unsigned int extra_len = iwe->len - (event_len + IW_EV_LCP_PK_LEN); if(extra_len > 0) { /* Set pointer on variable part (warning : non aligned) */ iwe->u.data.pointer = pointer; /* Check that we have a descriptor for the command */ if(descr == NULL) /* Can't check payload -> unsafe... */ iwe->u.data.pointer = NULL; /* Discard paylod */ else { /* Those checks are actually pretty hard to trigger, * because of the checks done in the kernel... */ unsigned int token_len = iwe->u.data.length * descr->token_size; /* Ugly fixup for alignement issues. * If the kernel is 64 bits and userspace 32 bits, * we have an extra 4+4 bytes. * Fixing that in the kernel would break 64 bits userspace. */ if((token_len != extra_len) && (extra_len >= 4)) { union iw_align_u16 alt_dlen; unsigned int alt_token_len; /* Usespace seems to not always like unaligned access, * so be careful and make sure to align value. * I hope gcc won't play any of its aliasing tricks... */ alt_dlen.byte[0] = *(pointer); alt_dlen.byte[1] = *(pointer + 1); alt_token_len = alt_dlen.value * descr->token_size; #ifdef DEBUG printf("DBG - alt_token_len = %d\n", alt_token_len); #endif /* Verify that data is consistent if assuming 64 bit * alignement... */ if((alt_token_len + 8) == extra_len) { /* Ok, let's redo everything */ pointer -= event_len; pointer += 4; /* Dest has local alignement, not packed */ memcpy((char *) iwe + IW_EV_LCP_LEN + IW_EV_POINT_OFF, pointer, event_len); pointer += event_len + 4; token_len = alt_token_len; /* We may have no payload */ if(alt_token_len) iwe->u.data.pointer = pointer; else iwe->u.data.pointer = NULL; } } /* Discard bogus events which advertise more tokens than * what they carry... */ if(token_len > extra_len) iwe->u.data.pointer = NULL; /* Discard paylod */ /* Check that the advertised token size is not going to * produce buffer overflow to our caller... */ if((iwe->u.data.length > descr->max_tokens) && !(descr->flags & IW_DESCR_FLAG_NOMAX)) iwe->u.data.pointer = NULL; /* Discard paylod */ /* Same for underflows... */ if(iwe->u.data.length < descr->min_tokens) iwe->u.data.pointer = NULL; /* Discard paylod */ #ifdef DEBUG printf("DBG - extra_len = %d, token_len = %d, token = %d, max = %d, min = %d\n", extra_len, token_len, iwe->u.data.length, descr->max_tokens, descr->min_tokens); #endif } } else /* No data */ iwe->u.data.pointer = NULL; /* Go to next event */ stream->current += iwe->len; } else { /* Ugly fixup for alignement issues. * If the kernel is 64 bits and userspace 32 bits, * we have an extra 4 bytes. * Fixing that in the kernel would break 64 bits userspace. */ if((stream->value == NULL) && ((((iwe->len - IW_EV_LCP_PK_LEN) % event_len) == 4) || ((iwe->len == 12) && ((event_type == IW_HEADER_TYPE_UINT) || (event_type == IW_HEADER_TYPE_QUAL))) )) { #ifdef DEBUG printf("DBG - alt iwe->len = %d\n", iwe->len - 4); #endif pointer -= event_len; pointer += 4; /* Beware of alignement. Dest has local alignement, not packed */ memcpy((char *) iwe + IW_EV_LCP_LEN, pointer, event_len); pointer += event_len; } /* Is there more value in the event ? */ if((pointer + event_len) <= (stream->current + iwe->len)) /* Go to next value */ stream->value = pointer; else { /* Go to next event */ stream->value = NULL; stream->current += iwe->len; } } return(1); } /*********************** SCANNING SUBROUTINES ***********************/ /* * The Wireless Extension API 14 and greater define Wireless Scanning. * The normal API is complex, this is an easy API that return * a subset of the scanning results. This should be enough for most * applications that want to use Scanning. * If you want to have use the full/normal API, check iwlist.c... * * Precaution when using scanning : * The scanning operation disable normal network traffic, and therefore * you should not abuse of scan. * The scan need to check the presence of network on other frequencies. * While you are checking those other frequencies, you can *NOT* be on * your normal frequency to listen to normal traffic in the cell. * You need typically in the order of one second to actively probe all * 802.11b channels (do the maths). Some cards may do that in background, * to reply to scan commands faster, but they still have to do it. * Leaving the cell for such an extended period of time is pretty bad. * Any kind of streaming/low latency traffic will be impacted, and the * user will perceive it (easily checked with telnet). People trying to * send traffic to you will retry packets and waste bandwidth. Some * applications may be sensitive to those packet losses in weird ways, * and tracing those weird behavior back to scanning may take time. * If you are in ad-hoc mode, if two nodes scan approx at the same * time, they won't see each other, which may create associations issues. * For those reasons, the scanning activity should be limited to * what's really needed, and continuous scanning is a bad idea. * Jean II */ /*------------------------------------------------------------------*/ /* * Process/store one element from the scanning results in wireless_scan */ static inline struct wireless_scan * iw_process_scanning_token(struct iw_event * event, struct wireless_scan * wscan) { struct wireless_scan * oldwscan; /* Now, let's decode the event */ switch(event->cmd) { case SIOCGIWAP: /* New cell description. Allocate new cell descriptor, zero it. */ oldwscan = wscan; wscan = (struct wireless_scan *) malloc(sizeof(struct wireless_scan)); if(wscan == NULL) return(wscan); /* Link at the end of the list */ if(oldwscan != NULL) oldwscan->next = wscan; /* Reset it */ bzero(wscan, sizeof(struct wireless_scan)); /* Save cell identifier */ wscan->has_ap_addr = 1; memcpy(&(wscan->ap_addr), &(event->u.ap_addr), sizeof (sockaddr)); break; case SIOCGIWNWID: wscan->b.has_nwid = 1; memcpy(&(wscan->b.nwid), &(event->u.nwid), sizeof(iwparam)); break; case SIOCGIWFREQ: wscan->b.has_freq = 1; wscan->b.freq = iw_freq2float(&(event->u.freq)); wscan->b.freq_flags = event->u.freq.flags; break; case SIOCGIWMODE: wscan->b.mode = event->u.mode; if((wscan->b.mode < IW_NUM_OPER_MODE) && (wscan->b.mode >= 0)) wscan->b.has_mode = 1; break; case SIOCGIWESSID: wscan->b.has_essid = 1; wscan->b.essid_on = event->u.data.flags; memset(wscan->b.essid, '\0', IW_ESSID_MAX_SIZE + 1); if((event->u.essid.pointer) && (event->u.essid.length)) memcpy(wscan->b.essid, event->u.essid.pointer, event->u.essid.length); break; case SIOCGIWENCODE: wscan->b.has_key = 1; wscan->b.key_size = event->u.data.length; wscan->b.key_flags = event->u.data.flags; if(event->u.data.pointer) memcpy(wscan->b.key, event->u.essid.pointer, event->u.data.length); else wscan->b.key_flags |= IW_ENCODE_NOKEY; break; case IWEVQUAL: /* We don't get complete stats, only qual */ wscan->has_stats = 1; memcpy(&wscan->stats.qual, &event->u.qual, sizeof(struct iw_quality)); break; case SIOCGIWRATE: /* Scan may return a list of bitrates. As we have space for only * a single bitrate, we only keep the largest one. */ if((!wscan->has_maxbitrate) || (event->u.bitrate.value > wscan->maxbitrate.value)) { wscan->has_maxbitrate = 1; memcpy(&(wscan->maxbitrate), &(event->u.bitrate), sizeof(iwparam)); } case IWEVCUSTOM: /* How can we deal with those sanely ? Jean II */ default: break; } /* switch(event->cmd) */ return(wscan); } /*------------------------------------------------------------------*/ /* * Initiate the scan procedure, and process results. * This is a non-blocking procedure and it will return each time * it would block, returning the amount of time the caller should wait * before calling again. * Return -1 for error, delay to wait for (in ms), or 0 for success. * Error code is in errno */ int iw_process_scan(int skfd, char * ifname, int we_version, wireless_scan_head * context) { struct iwreq wrq; unsigned char * buffer = NULL; /* Results */ int buflen = IW_SCAN_MAX_DATA; /* Min for compat WE<17 */ unsigned char * newbuf; /* Don't waste too much time on interfaces (150 * 100 = 15s) */ context->retry++; if(context->retry > 150) { errno = ETIME; return(-1); } /* If we have not yet initiated scanning on the interface */ if(context->retry == 1) { /* Initiate Scan */ wrq.u.data.pointer = NULL; /* Later */ wrq.u.data.flags = 0; wrq.u.data.length = 0; /* Remember that as non-root, we will get an EPERM here */ if((iw_set_ext(skfd, ifname, SIOCSIWSCAN, &wrq) < 0) && (errno != EPERM)) return(-1); /* Success : now, just wait for event or results */ return(250); /* Wait 250 ms */ } realloc: /* (Re)allocate the buffer - realloc(NULL, len) == malloc(len) */ newbuf = realloc(buffer, buflen); if(newbuf == NULL) { /* man says : If realloc() fails the original block is left untouched */ if(buffer) free(buffer); errno = ENOMEM; return(-1); } buffer = newbuf; /* Try to read the results */ wrq.u.data.pointer = buffer; wrq.u.data.flags = 0; wrq.u.data.length = buflen; if(iw_get_ext(skfd, ifname, SIOCGIWSCAN, &wrq) < 0) { /* Check if buffer was too small (WE-17 only) */ if((errno == E2BIG) && (we_version > 16) && (buflen < 0xFFFF)) { /* Some driver may return very large scan results, either * because there are many cells, or because they have many * large elements in cells (like IWEVCUSTOM). Most will * only need the regular sized buffer. We now use a dynamic * allocation of the buffer to satisfy everybody. Of course, * as we don't know in advance the size of the array, we try * various increasing sizes. Jean II */ /* Check if the driver gave us any hints. */ if(wrq.u.data.length > buflen) buflen = wrq.u.data.length; else buflen *= 2; /* wrq.u.data.length is 16 bits so max size is 65535 */ if(buflen > 0xFFFF) buflen = 0xFFFF; /* Try again */ goto realloc; } /* Check if results not available yet */ if(errno == EAGAIN) { free(buffer); /* Wait for only 100ms from now on */ return(100); /* Wait 100 ms */ } free(buffer); /* Bad error, please don't come back... */ return(-1); } /* We have the results, process them */ if(wrq.u.data.length) { struct iw_event iwe; struct stream_descr stream; struct wireless_scan * wscan = NULL; int ret; #ifdef DEBUG /* Debugging code. In theory useless, because it's debugged ;-) */ int i; printf("Scan result [%02X", buffer[0]); for(i = 1; i < wrq.u.data.length; i++) printf(":%02X", buffer[i]); printf("]\n"); #endif /* Init */ iw_init_event_stream(&stream, (char *) buffer, wrq.u.data.length); /* This is dangerous, we may leak user data... */ context->result = NULL; /* Look every token */ do { /* Extract an event and print it */ ret = iw_extract_event_stream(&stream, &iwe, we_version); if(ret > 0) { /* Convert to wireless_scan struct */ wscan = iw_process_scanning_token(&iwe, wscan); /* Check problems */ if(wscan == NULL) { free(buffer); errno = ENOMEM; return(-1); } /* Save head of list */ if(context->result == NULL) context->result = wscan; } } while(ret > 0); } /* Done with this interface - return success */ free(buffer); return(0); } /*------------------------------------------------------------------*/ /* * Perform a wireless scan on the specified interface. * This is a blocking procedure and it will when the scan is completed * or when an error occur. * * The scan results are given in a linked list of wireless_scan objects. * The caller *must* free the result himself (by walking the list). * If there is an error, -1 is returned and the error code is available * in errno. * * The parameter we_version can be extracted from the range structure * (range.we_version_compiled - see iw_get_range_info()), or using * iw_get_kernel_we_version(). For performance reason, you should * cache this parameter when possible rather than querying it every time. * * Return -1 for error and 0 for success. */ int iw_scan(int skfd, char * ifname, int we_version, wireless_scan_head * context) { int delay; /* in ms */ /* Clean up context. Potential memory leak if(context.result != NULL) */ context->result = NULL; context->retry = 0; /* Wait until we get results or error */ while(1) { /* Try to get scan results */ delay = iw_process_scan(skfd, ifname, we_version, context); /* Check termination */ if(delay <= 0) break; /* Wait a bit */ usleep(delay * 1000); } /* End - return -1 or 0 */ return(delay); }