2 This file is part of GNUnet.
3 (C) 2010, 2011 Christian Grothoff (and other contributing authors)
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
22 * @file src/transport/gnunet-transport-wlan-helper.c
23 * @brief wlan layer two server; must run as root (SUID will do)
24 * This code will work under GNU/Linux only.
25 * @author David Brodski
27 * This program serves as the mediator between the wlan interface and
32 #include <sys/socket.h>
33 #include <sys/ioctl.h>
34 #include <sys/types.h>
39 #include <netpacket/packet.h>
40 #include <linux/if_ether.h>
42 #include <linux/wireless.h>
43 #include <netinet/in.h>
44 #include <linux/if_tun.h>
52 //#include <sys/utsname.h>
53 #include <sys/param.h>
61 //#include "platform.h"
62 #include "gnunet_constants.h"
63 #include "gnunet_os_lib.h"
64 #include "gnunet_transport_plugin.h"
65 #include "transport.h"
66 #include "gnunet_util_lib.h"
67 #include "plugin_transport_wlan.h"
68 #include "gnunet_common.h"
69 #include "gnunet-transport-wlan-helper.h"
70 #include "gnunet_crypto_lib.h"
72 #include "wlan/radiotap-parser.h"
73 /* radiotap-parser defines types like u8 that
74 * ieee80211_radiotap.h needs
76 * we use our local copy of ieee80211_radiotap.h
78 * - since we can't support extensions we don't understand
79 * - since linux does not include it in userspace headers
81 #include "wlan/ieee80211_radiotap.h"
82 #include "wlan/crctable_osdep.h"
83 #include "wlan/loopback_helper.h"
84 #include "wlan/ieee80211.h"
86 #define ARPHRD_IEEE80211 801
87 #define ARPHRD_IEEE80211_PRISM 802
88 #define ARPHRD_IEEE80211_FULL 803
90 #include "wlan/loopback_helper.h"
94 #define MAC_ADDR_SIZE 6
99 struct sendbuf write_pout;
104 * Name of the interface, not necessarily 0-terminated (!).
106 char iface[IFNAMSIZ];
107 unsigned char pl_mac[MAC_ADDR_SIZE];
111 int getChannelFromFrequency(int frequency);
113 // FIXME: make nice...
115 calc_crc_osdep(unsigned char * buf, int len)
117 unsigned long crc = 0xFFFFFFFF;
119 for (; len > 0; len--, buf++)
120 crc = crc_tbl_osdep[(crc ^ *buf) & 0xFF] ^ (crc >> 8);
125 /* CRC checksum verification routine */
127 // FIXME: make nice...
129 check_crc_buf_osdep(unsigned char *buf, int len)
136 crc = calc_crc_osdep(buf, len);
138 return (((crc) & 0xFF) == buf[0] && ((crc >> 8) & 0xFF) == buf[1] && ((crc
139 >> 16) & 0xFF) == buf[2] && ((crc >> 24) & 0xFF) == buf[3]);
143 // FIXME: make nice...
145 linux_get_channel(struct Hardware_Infos *dev)
151 memset(&wrq, 0, sizeof(struct iwreq));
153 strncpy(wrq.ifr_name, dev->iface, IFNAMSIZ );
156 if (0 > ioctl(fd, SIOCGIWFREQ, &wrq))
159 frequency = wrq.u.freq.m;
160 if (100000000 < frequency )
162 else if (1000000 < frequency )
165 if (1000 < frequency)
166 chan = getChannelFromFrequency(frequency);
174 // FIXME: make nice...
176 linux_read (struct Hardware_Infos *dev,
177 unsigned char *buf, /* FIXME: void*? */
179 struct Radiotap_rx *ri)
181 unsigned char tmpbuf[buf_size];
183 int n, got_signal, got_noise, got_channel, fcs_removed;
185 n = got_signal = got_noise = got_channel = fcs_removed = 0;
187 caplen = read(dev->fd_raw, tmpbuf, buf_size);
193 "Failed to read from RAW socket: %s\n",
198 memset(buf, 0, buf_size);
199 memset(ri, 0, sizeof(*ri));
201 switch (dev->arptype_in)
203 case ARPHRD_IEEE80211_PRISM:
205 /* skip the prism header */
206 if (tmpbuf[7] == 0x40)
208 /* prism54 uses a different format */
209 ri->ri_power = tmpbuf[0x33];
210 ri->ri_noise = *(unsigned int *) (tmpbuf + 0x33 + 12);
211 ri->ri_rate = (*(unsigned int *) (tmpbuf + 0x33 + 24)) * 500000;
218 ri->ri_mactime = *(u_int64_t*) (tmpbuf + 0x5C - 48);
219 ri->ri_channel = *(unsigned int *) (tmpbuf + 0x5C - 36);
220 ri->ri_power = *(unsigned int *) (tmpbuf + 0x5C);
221 ri->ri_noise = *(unsigned int *) (tmpbuf + 0x5C + 12);
222 ri->ri_rate = (*(unsigned int *) (tmpbuf + 0x5C + 24)) * 500000;
226 n = *(int *) (tmpbuf + 4);
229 if (n < 8 || n >= caplen)
234 case ARPHRD_IEEE80211_FULL:
236 struct ieee80211_radiotap_iterator iterator;
237 struct ieee80211_radiotap_header *rthdr;
239 rthdr = (struct ieee80211_radiotap_header *) tmpbuf;
241 if (ieee80211_radiotap_iterator_init(&iterator, rthdr, caplen) < 0)
244 /* go through the radiotap arguments we have been given
248 while (ieee80211_radiotap_iterator_next(&iterator) >= 0)
251 switch (iterator.this_arg_index)
254 case IEEE80211_RADIOTAP_TSFT:
255 ri->ri_mactime = le64_to_cpu(*((uint64_t*) iterator.this_arg));
258 case IEEE80211_RADIOTAP_DBM_ANTSIGNAL:
261 if (*iterator.this_arg < 127)
262 ri->ri_power = *iterator.this_arg;
264 ri->ri_power = *iterator.this_arg - 255;
270 case IEEE80211_RADIOTAP_DB_ANTSIGNAL:
273 if (*iterator.this_arg < 127)
274 ri->ri_power = *iterator.this_arg;
276 ri->ri_power = *iterator.this_arg - 255;
282 case IEEE80211_RADIOTAP_DBM_ANTNOISE:
285 if (*iterator.this_arg < 127)
286 ri->ri_noise = *iterator.this_arg;
288 ri->ri_noise = *iterator.this_arg - 255;
294 case IEEE80211_RADIOTAP_DB_ANTNOISE:
297 if (*iterator.this_arg < 127)
298 ri->ri_noise = *iterator.this_arg;
300 ri->ri_noise = *iterator.this_arg - 255;
306 case IEEE80211_RADIOTAP_ANTENNA:
307 ri->ri_antenna = *iterator.this_arg;
310 case IEEE80211_RADIOTAP_CHANNEL:
311 ri->ri_channel = *iterator.this_arg;
315 case IEEE80211_RADIOTAP_RATE:
316 ri->ri_rate = (*iterator.this_arg) * 500000;
319 case IEEE80211_RADIOTAP_FLAGS:
320 /* is the CRC visible at the end?
323 if (*iterator.this_arg & IEEE80211_RADIOTAP_F_FCS)
329 if (*iterator.this_arg & IEEE80211_RADIOTAP_F_RX_BADFCS)
335 n = le16_to_cpu(rthdr->it_len);
336 if (n <= 0 || n >= caplen)
340 case ARPHRD_IEEE80211:
350 //detect fcs at the end, even if the flag wasn't set and remove it
351 if ( (0 == fcs_removed) &&
352 (1 == check_crc_buf_osdep(tmpbuf + n, caplen - 4)) )
356 memcpy(buf, tmpbuf + n, caplen);
358 ri->ri_channel = linux_get_channel(dev);
365 * @return 0 on success
368 openraw (struct Hardware_Infos *dev)
372 struct packet_mreq mr;
373 struct sockaddr_ll sll;
375 /* find the interface index */
376 memset(&ifr, 0, sizeof(ifr));
377 strncpy(ifr.ifr_name, dev->iface, IFNAMSIZ);
378 if (-1 == ioctl(dev->fd_raw, SIOCGIFINDEX, &ifr))
381 "ioctl(SIOCGIFINDEX) on interface `%.*s' failed: %s\n",
388 /* lookup the hardware type */
389 memset (&sll, 0, sizeof(sll));
390 sll.sll_family = AF_PACKET;
391 sll.sll_ifindex = ifr.ifr_ifindex;
392 sll.sll_protocol = htons(ETH_P_ALL);
393 if (-1 == ioctl(dev->fd_raw, SIOCGIFHWADDR, &ifr))
396 "ioctl(SIOCGIFHWADDR) on interface `%.*s' failed: %s\n",
404 memset(&wrq, 0, sizeof(struct iwreq));
405 strncpy(wrq.ifr_name, dev->iface, IFNAMSIZ);
406 if (-1 == ioctl(dev->fd_raw, SIOCGIWMODE, &wrq))
408 /* most probably not supported (ie for rtap ipw interface) *
409 * so just assume its correctly set... */
410 wrq.u.mode = IW_MODE_MONITOR;
413 if ( ( (ifr.ifr_hwaddr.sa_family != ARPHRD_IEEE80211) &&
414 (ifr.ifr_hwaddr.sa_family != ARPHRD_IEEE80211_PRISM) &&
415 (ifr.ifr_hwaddr.sa_family != ARPHRD_IEEE80211_FULL) ) ||
416 (wrq.u.mode != IW_MODE_MONITOR) )
419 "Error: interface `%.*s' is not in monitor mode\n",
425 /* Is interface st to up, broadcast & running ? */
426 if ((ifr.ifr_flags | IFF_UP | IFF_BROADCAST | IFF_RUNNING) != ifr.ifr_flags)
428 /* Bring interface up*/
429 ifr.ifr_flags |= IFF_UP | IFF_BROADCAST | IFF_RUNNING;
431 if (-1 != ioctl(dev->fd_raw, SIOCSIFFLAGS, &ifr))
434 "ioctl(SIOCSIFFLAGS) on interface `%.*s' failed: %s\n",
442 /* bind the raw socket to the interface */
443 if (-1 == bind(dev->fd_raw, (struct sockaddr *) &sll, sizeof(sll)))
446 "Failed to bind interface `%.*s': %s\n",
453 /* lookup the hardware type */
454 if (-1 != ioctl(dev->fd_raw, SIOCGIFHWADDR, &ifr))
457 "ioctl(SIOCGIFHWADDR) on interface `%.*s' failed: %s\n",
465 ifr.ifr_hwaddr.sa_data,
467 dev->arptype_in = ifr.ifr_hwaddr.sa_family;
468 if ( (ifr.ifr_hwaddr.sa_family != ARPHRD_IEEE80211) &&
469 (ifr.ifr_hwaddr.sa_family != ARPHRD_IEEE80211_PRISM) &&
470 (ifr.ifr_hwaddr.sa_family != ARPHRD_IEEE80211_FULL) )
473 "Unsupported hardware link type %d on interface `%.*s'\n",
474 ifr.ifr_hwaddr.sa_family,
480 /* enable promiscuous mode */
481 memset(&mr, 0, sizeof(mr));
482 mr.mr_ifindex = sll.sll_ifindex;
483 mr.mr_type = PACKET_MR_PROMISC;
484 if (0 != setsockopt(dev->fd_raw, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr)))
487 "Failed to enable promiscuous mode on interface `%.*s'\n",
497 * @return 0 on success
500 wlaninit (struct Hardware_Infos *dev,
507 dev->fd_raw = socket (PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
511 "Failed to create raw socket: %s\n",
515 if (dev->fd_raw >= FD_SETSIZE)
518 "File descriptor too large for select (%d > %d)\n",
525 /* mac80211 stack detection */
526 ret = snprintf(strbuf,
528 "/sys/class/net/%s/phy80211/subsystem",
531 (ret >= sizeof (strbuf)) ||
532 (0 != stat(strbuf, &sbuf)) )
535 "Did not find 802.11 interface `%s'. Exiting.\n",
540 strncpy(dev->iface, iface, IFNAMSIZ);
541 if (0 != openraw(dev))
551 * Function to test incoming packets mac for being our own.
553 * @param u8aIeeeHeader buffer of the packet
554 * @param dev the Hardware_Infos struct
555 * @return 0 if mac belongs to us, 1 if mac is for another target
558 mac_test (const struct ieee80211_frame *u8aIeeeHeader,
559 const struct Hardware_Infos *dev)
561 if (0 != memcmp(u8aIeeeHeader->i_addr3, &mac_bssid, MAC_ADDR_SIZE))
563 if (0 == memcmp(u8aIeeeHeader->i_addr1, dev->pl_mac, MAC_ADDR_SIZE))
565 if (0 == memcmp(u8aIeeeHeader->i_addr1, &bc_all_mac, MAC_ADDR_SIZE))
572 * function to set the wlan header to make attacks more difficult
573 * @param buf buffer of the packet
574 * @param dev pointer to the Hardware_Infos struct
577 mac_set (struct ieee80211_frame *u8aIeeeHeader,
578 const struct Hardware_Infos * dev)
580 u8aIeeeHeader->i_fc[0] = 0x08;
581 u8aIeeeHeader->i_fc[1] = 0x00;
582 memcpy(u8aIeeeHeader->i_addr2,
585 memcpy(u8aIeeeHeader->i_addr3,
593 stdin_send_hw (void *cls,
595 const struct GNUNET_MessageHeader *hdr)
597 struct Hardware_Infos * dev = cls;
598 struct sendbuf *write_pout = &dev->write_pout;
599 struct Radiotap_Send * header = (struct Radiotap_Send *) &hdr[1];
600 struct ieee80211_frame * wlanheader;
603 // struct? // FIXME: make nice...
604 unsigned char u8aRadiotap[] =
605 { 0x00, 0x00, // <-- radiotap version
606 0x0c, 0x00, // <- radiotap header length
607 0x04, 0x80, 0x00, 0x00, // <-- bitmap
609 0x00, // <-- padding for natural alignment
610 0x18, 0x00, // <-- TX flags
613 sendsize = ntohs(hdr->size);
614 if (sendsize < sizeof(struct Radiotap_Send) + sizeof(struct GNUNET_MessageHeader))
617 "Function stdin_send_hw: mailformed packet (too small)\n");
620 sendsize -= sizeof(struct Radiotap_Send) + sizeof(struct GNUNET_MessageHeader);
622 if (MAXLINE < sendsize)
625 "Function stdin_send_hw: Packet too big for buffer\n");
628 if (GNUNET_MESSAGE_TYPE_WLAN_HELPER_DATA != ntohs(hdr->type))
630 fprintf(stderr, "Function stdin_send: wrong packet type\n");
634 u8aRadiotap[2] = htole16(sizeof(u8aRadiotap)); // WTF?
635 u8aRadiotap[8] = header->rate;
636 memcpy(write_pout->buf, u8aRadiotap, sizeof(u8aRadiotap));
637 memcpy(write_pout->buf + sizeof(u8aRadiotap), &header[1], sendsize);
638 /* payload contains MAC address, but we don't trust it, so we'll
639 overwrite it with OUR MAC address again to prevent mischief */
640 wlanheader = (struct ieee80211_frame *) (write_pout->buf + sizeof(u8aRadiotap));
641 mac_set(wlanheader, dev);
642 write_pout->size = sendsize + sizeof(u8aRadiotap);
647 maketest(unsigned char * buf, struct Hardware_Infos * dev)
650 static uint16_t seqenz = 0;
651 static int first = 0;
653 const int rate = 11000000;
656 "Hallo1Hallo2 Hallo3 Hallo4...998877665544332211Hallo1Hallo2 Hallo3 Hallo4...998877665544332211";
658 unsigned char u8aRadiotap[] =
659 { 0x00, 0x00, // <-- radiotap version
660 0x00, 0x00, // <- radiotap header length
661 0x04, 0x80, 0x02, 0x00, // <-- bitmap
663 0x00, // <-- padding for natural alignment
664 0x10, 0x00, // <-- TX flags
668 /*uint8_t u8aRadiotap[] =
670 0x00, 0x00, // <-- radiotap version
671 0x19, 0x00, // <- radiotap header length
672 0x6f, 0x08, 0x00, 0x00, // <-- bitmap
673 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // <-- timestamp
674 0x00, // <-- flags (Offset +0x10)
675 0x6c, // <-- rate (0ffset +0x11)
676 0x71, 0x09, 0xc0, 0x00, // <-- channel
677 0xde, // <-- antsignal
678 0x00, // <-- antnoise
682 u8aRadiotap[8] = (rate / 500000);
683 u8aRadiotap[2] = htole16(sizeof(u8aRadiotap));
685 static struct ieee80211_frame u8aIeeeHeader;
687 uint8_t u8aIeeeHeader_def[] =
688 { 0x08, 0x00, // Frame Control 0x08= 00001000 -> | b1,2 = 0 -> Version 0;
689 // b3,4 = 10 -> Data; b5-8 = 0 -> Normal Data
690 // 0x01 = 00000001 -> | b1 = 1 to DS; b2 = 0 not from DS;
691 0x00, 0x00, // Duration/ID
693 //0x00, 0x1f, 0x3f, 0xd1, 0x8e, 0xe6, // mac1 - in this case receiver
694 0x00, 0x1d, 0xe0, 0xb0, 0x17, 0xdf, // mac1 - in this case receiver
695 0xC0, 0x3F, 0x0E, 0x44, 0x2D, 0x51, // mac2 - in this case sender
696 //0x02, 0x1d, 0xe0, 0x00, 0x01, 0xc4,
697 0x13, 0x22, 0x33, 0x44, 0x55, 0x66, // mac3 - in this case bssid
698 0x10, 0x86, //Sequence Control
702 memcpy(&u8aIeeeHeader, u8aIeeeHeader_def, sizeof(struct ieee80211_frame));
703 memcpy(u8aIeeeHeader.i_addr2, dev->pl_mac, MAC_ADDR_SIZE);
707 tmp16 = (uint16_t*) u8aIeeeHeader.i_dur;
709 = (uint16_t) htole16((sizeof(txt) + sizeof(struct ieee80211_frame) * 1000000) / rate + 290);
710 tmp16 = (uint16_t*) u8aIeeeHeader.i_seq;
711 *tmp16 = (*tmp16 & IEEE80211_SEQ_FRAG_MASK) | (htole16(seqenz)
712 << IEEE80211_SEQ_SEQ_SHIFT);
715 memcpy(buf, u8aRadiotap, sizeof(u8aRadiotap));
716 memcpy(buf + sizeof(u8aRadiotap), &u8aIeeeHeader, sizeof(u8aIeeeHeader));
717 memcpy(buf + sizeof(u8aRadiotap) + sizeof(u8aIeeeHeader), txt, sizeof(txt));
718 return sizeof(u8aRadiotap) + sizeof(u8aIeeeHeader) + sizeof(txt);
725 * function to create GNUNET_MESSAGE_TYPE_WLAN_HELPER_CONTROL message for plugin
726 * @param buffer pointer to buffer for the message
727 * @param mac pointer to the mac address
728 * @return number of bytes written
730 // FIXME: use 'struct MacAddress' for 'mac' (everywhere in this file)
732 send_mac_to_plugin(char * buffer, uint8_t * mac)
734 struct Wlan_Helper_Control_Message macmsg;
736 macmsg.hdr.size = htons(sizeof(struct Wlan_Helper_Control_Message));
737 macmsg.hdr.type = htons(GNUNET_MESSAGE_TYPE_WLAN_HELPER_CONTROL);
738 memcpy(macmsg.mac.mac, mac, sizeof(struct MacAddress));
739 memcpy(buffer, &macmsg, sizeof(struct Wlan_Helper_Control_Message));
740 return sizeof(struct Wlan_Helper_Control_Message);
745 hardwaremode (int argc,
749 struct Hardware_Infos dev;
750 char readbuf[MAXLINE];
751 struct sendbuf write_std;
758 struct GNUNET_SERVER_MessageStreamTokenizer * stdin_mst;
763 "This program must be started with the interface argument.\n");
765 "Usage: interface-name\n");
768 if (0 != wlaninit(&dev, argv[1]))
771 if (0 != setresuid(uid, uid, uid))
774 "Failed to setresuid: %s\n",
776 /* not critical, continue anyway */
779 dev.write_pout.size = 0;
780 dev.write_pout.pos = 0;
781 stdin_mst = GNUNET_SERVER_mst_create (&stdin_send_hw, &dev);
783 /* send mac to STDOUT first */
785 write_std.size = send_mac_to_plugin((char *) &write_std.buf, dev.pl_mac);
792 if ( (0 == dev.write_pout.size) &&
795 FD_SET (STDIN_FILENO, &rfds);
796 maxfd = MAX (maxfd, STDIN_FILENO);
798 if (0 == write_std.size)
800 FD_SET (dev.fd_raw, &rfds);
801 maxfd = MAX(maxfd, dev.fd_raw);
804 if (0 < write_std.size)
806 FD_SET (STDOUT_FILENO, &wfds);
807 maxfd = MAX(maxfd, STDOUT_FILENO);
809 if (0 < dev.write_pout.size)
811 FD_SET(dev.fd_raw, &wfds);
812 maxfd = MAX(maxfd, dev.fd_raw);
814 retval = select(maxfd + 1, &rfds, &wfds, NULL, NULL);
815 if ( (-1 == retval) &&
821 "select failed: %s\n",
826 if (FD_ISSET(STDOUT_FILENO, &wfds))
828 ret = write(STDOUT_FILENO,
829 write_std.buf + write_std.pos,
830 write_std.size - write_std.pos);
834 "Failed to write to STDOUT: %s\n",
838 write_std.pos += ret;
839 if (write_std.pos == write_std.size)
846 if (FD_ISSET(dev.fd_raw, &wfds))
848 ret = write (dev.fd_raw,
850 dev.write_pout.size);
854 "Failed to write to WLAN device: %s\n",
858 dev.write_pout.pos += ret;
859 if ( (dev.write_pout.pos != dev.write_pout.size) &&
863 "Write error, partial send: %u/%u\n",
864 dev.write_pout.pos, dev.write_pout.size);
867 if (dev.write_pout.pos == dev.write_pout.size)
869 dev.write_pout.pos = 0;
870 dev.write_pout.size = 0;
874 if (FD_ISSET(STDIN_FILENO, &rfds))
876 ret = read(STDIN_FILENO, readbuf, sizeof(readbuf));
880 "Read error from STDIN: %s\n",
886 /* stop reading... */
889 GNUNET_SERVER_mst_receive (stdin_mst, NULL,
891 GNUNET_NO, GNUNET_NO);
894 if (FD_ISSET(dev.fd_raw, &rfds))
896 struct GNUNET_MessageHeader * header;
897 struct Radiotap_rx * rxinfo;
898 struct ieee80211_frame * datastart;
900 header = (struct GNUNET_MessageHeader *) write_std.buf;
901 rxinfo = (struct Radiotap_rx *) &header[1];
902 datastart = (struct ieee80211_frame *) &rxinfo[1];
903 ret = linux_read (&dev,
904 (unsigned char *) datastart,
905 sizeof(write_std.buf) - sizeof(struct Radiotap_rx) - sizeof(struct GNUNET_MessageHeader),
910 "Read error from raw socket: %s\n",
915 (0 == mac_test(datastart, &dev)) )
917 write_std.size = ret + sizeof(struct GNUNET_MessageHeader) + sizeof(struct Radiotap_rx);
918 header->size = htons(write_std.size);
919 header->type = htons(GNUNET_MESSAGE_TYPE_WLAN_HELPER_DATA);
924 /* Error handling, try to clean up a bit at least */
925 GNUNET_SERVER_mst_destroy(stdin_mst);
931 main(int argc, char *argv[])
936 "This program must be started with the interface and the operating mode as argument.\n");
938 "Usage: interface-name options\n"
939 "options: 0 = with hardware\n"
940 "1 = first loopback file\n"
941 "2 = second loopback file\n"
945 if (strstr(argv[2], "1") || strstr(argv[2], "2"))
946 return testmode(argc, argv);
947 return hardwaremode(argc - 1, argv);