Added basic http-specific option parsing
[people/xl0/gpxe.git] / src / util / prototester.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <errno.h>
6 #include <time.h>
7 #include <sys/socket.h>
8 #include <sys/un.h>
9 #include <net/if.h>
10 #include <net/ethernet.h>
11 #include <netinet/in.h>
12 #include <arpa/inet.h>
13 #include <getopt.h>
14
15 typedef int irq_action_t;
16
17 struct nic {
18         struct nic_operations   *nic_op;
19         unsigned char           *node_addr;
20         unsigned char           *packet;
21         unsigned int            packetlen;
22         void                    *priv_data;     /* driver private data */
23 };
24
25 struct nic_operations {
26         int ( *connect ) ( struct nic * );
27         int ( *poll ) ( struct nic *, int retrieve );
28         void ( *transmit ) ( struct nic *, const char *,
29                              unsigned int, unsigned int, const char * );
30         void ( *irq ) ( struct nic *, irq_action_t );
31 };
32
33 /*****************************************************************************
34  *
35  * Net device layer
36  *
37  */
38
39 #include "../proto/uip/uip_arp.h"
40
41 static unsigned char node_addr[ETH_ALEN];
42 static unsigned char packet[ETH_FRAME_LEN];
43 struct nic static_nic = {
44         .node_addr = node_addr,
45         .packet = packet,
46 };
47
48 /* Must be a macro because priv_data[] is of variable size */
49 #define alloc_netdevice( priv_size ) ( {        \
50         static char priv_data[priv_size];       \
51         static_nic.priv_data = priv_data;       \
52         &static_nic; } )
53
54 static int register_netdevice ( struct nic *nic ) {
55         struct uip_eth_addr hwaddr;
56
57         memcpy ( &hwaddr, nic->node_addr, sizeof ( hwaddr ) );
58         uip_setethaddr ( hwaddr );
59         return 0;
60 }
61
62 static inline void unregister_netdevice ( struct nic *nic ) {
63         /* Do nothing */
64 }
65
66 static inline void free_netdevice ( struct nic *nic ) {
67         /* Do nothing */
68 }
69
70 static int netdev_poll ( int retrieve, void **data, size_t *len ) {
71         int rc = static_nic.nic_op->poll ( &static_nic, retrieve );
72         *data = static_nic.packet;
73         *len = static_nic.packetlen;
74         return rc;
75 }
76
77 static void netdev_transmit ( const void *data, size_t len ) {
78         uint16_t type = ntohs ( *( ( uint16_t * ) ( data + 12 ) ) );
79         static_nic.nic_op->transmit ( &static_nic, data, type,
80                                       len - ETH_HLEN,
81                                       data + ETH_HLEN );
82 }
83
84 /*****************************************************************************
85  *
86  * Hijack device interface
87  *
88  * This requires a hijack daemon to be running
89  *
90  */
91
92 struct hijack {
93         int fd;
94 };
95
96 struct hijack_device {
97         char *name;
98         void *priv;
99 };
100
101 static inline void hijack_set_drvdata ( struct hijack_device *hijack_dev,
102                                         void *data ) {
103         hijack_dev->priv = data;
104 }
105
106 static inline void * hijack_get_drvdata ( struct hijack_device *hijack_dev ) {
107         return hijack_dev->priv;
108 }
109
110 static int hijack_poll ( struct nic *nic, int retrieve ) {
111         struct hijack *hijack = nic->priv_data;
112         fd_set fdset;
113         struct timeval tv;
114         int ready;
115         ssize_t len;
116
117         /* Poll for data */
118         FD_ZERO ( &fdset );
119         FD_SET ( hijack->fd, &fdset );
120         tv.tv_sec = 0;
121         tv.tv_usec = 500; /* 500us to avoid hogging CPU */
122         ready = select ( ( hijack->fd + 1 ), &fdset, NULL, NULL, &tv );
123         if ( ready < 0 ) {
124                 fprintf ( stderr, "select() failed: %s\n",
125                           strerror ( errno ) );
126                 return 0;
127         }
128         if ( ready == 0 )
129                 return 0;
130
131         /* Return if we're not retrieving data yet */
132         if ( ! retrieve )
133                 return 1;
134
135         /* Fetch data */
136         len = read ( hijack->fd, nic->packet, ETH_FRAME_LEN );
137         if ( len < 0 ) {
138                 fprintf ( stderr, "read() failed: %s\n",
139                           strerror ( errno ) );
140                 return 0;
141         }
142         nic->packetlen = len;
143
144         return 1;
145 }
146
147 static void hijack_transmit ( struct nic *nic, const char *dest,
148                               unsigned int type, unsigned int size,
149                               const char *packet ) {
150         struct hijack *hijack = nic->priv_data;
151         unsigned int nstype = htons ( type );
152         unsigned int total_size = ETH_HLEN + size;
153         char txbuf[ total_size ];
154
155         /* Build packet header */
156         memcpy ( txbuf, dest, ETH_ALEN );
157         memcpy ( txbuf + ETH_ALEN, nic->node_addr, ETH_ALEN );
158         memcpy ( txbuf + 2 * ETH_ALEN, &nstype, 2 );
159         memcpy ( txbuf + ETH_HLEN, packet, size );
160
161         /* Transmit data */
162         if ( write ( hijack->fd, txbuf, total_size ) != total_size ) {
163                 fprintf ( stderr, "write() failed: %s\n",
164                           strerror ( errno ) );
165         }
166 }
167
168 static int hijack_connect ( struct nic *nic ) {
169         return 1;
170 }
171
172 static void hijack_irq ( struct nic *nic, irq_action_t action ) {
173         /* Do nothing */
174 }
175
176 static struct nic_operations hijack_operations = {
177         .connect        = hijack_connect,
178         .transmit       = hijack_transmit,
179         .poll           = hijack_poll,
180         .irq            = hijack_irq,
181 };
182
183 int hijack_probe ( struct hijack_device *hijack_dev ) {
184         struct nic *nic;
185         struct hijack *hijack;
186         struct sockaddr_un sun;
187         int i;
188
189         nic = alloc_netdevice ( sizeof ( *hijack ) );
190         if ( ! nic ) {
191                 fprintf ( stderr, "alloc_netdevice() failed\n" );
192                 goto err_alloc;
193         }
194         hijack = nic->priv_data;
195         memset ( hijack, 0, sizeof ( *hijack ) );
196
197         /* Create socket */
198         hijack->fd = socket ( PF_UNIX, SOCK_SEQPACKET, 0 );
199         if ( hijack->fd < 0 ) {
200                 fprintf ( stderr, "socket() failed: %s\n",
201                           strerror ( errno ) );
202                 goto err;
203         }
204
205         /* Connect to hijack daemon */
206         sun.sun_family = AF_UNIX;
207         snprintf ( sun.sun_path, sizeof ( sun.sun_path ), "/var/run/hijack-%s",
208                    hijack_dev->name );
209         if ( connect ( hijack->fd, ( struct sockaddr * ) &sun,
210                        sizeof ( sun ) ) < 0 ) {
211                 fprintf ( stderr, "could not connect to %s: %s\n",
212                           sun.sun_path, strerror ( errno ) );
213                 goto err;
214         }
215
216         /* Generate MAC address */
217         srand ( time ( NULL ) );
218         for ( i = 0 ; i < ETH_ALEN ; i++ ) {
219                 nic->node_addr[i] = ( rand() & 0xff );
220         }
221         nic->node_addr[0] &= 0xfe; /* clear multicast bit */
222         nic->node_addr[0] |= 0x02; /* set "locally-assigned" bit */
223
224         nic->nic_op = &hijack_operations;
225         if ( register_netdevice ( nic ) < 0 )
226                 goto err;
227
228         hijack_set_drvdata ( hijack_dev, nic );
229         return 1;
230
231  err:
232         if ( hijack->fd >= 0 )
233                 close ( hijack->fd );
234         free_netdevice ( nic );
235  err_alloc:
236         return 0;
237 }
238
239 static void hijack_disable ( struct hijack_device *hijack_dev ) {
240         struct nic *nic = hijack_get_drvdata ( hijack_dev );
241         struct hijack *hijack = nic->priv_data;
242         
243         unregister_netdevice ( nic );
244         close ( hijack->fd );
245 }
246
247 /*****************************************************************************
248  *
249  * uIP wrapper layer
250  *
251  */
252
253 #include "../proto/uip/uip.h"
254 #include "../proto/uip/uip_arp.h"
255
256 void UIP_APPCALL ( void ) {
257         printf ( "appcall\n" );
258 }
259
260 void udp_appcall ( void ) {
261 }
262
263 static void init_tcpip ( void ) {
264         uip_init();
265         uip_arp_init();
266 }
267
268 static void uip_transmit ( void ) {
269         uip_arp_out();
270         netdev_transmit ( uip_buf, uip_len );
271         uip_len = 0;
272 }
273
274 static int tcp_connect ( struct sockaddr_in *sin ) {
275         u16_t ipaddr[2];
276
277         * ( ( uint32_t * ) ipaddr ) = sin->sin_addr.s_addr;
278         return uip_connect ( ipaddr, sin->sin_port ) ? 1 : 0;
279 }
280
281 static void run_tcpip ( void ) {
282         void *data;
283         size_t len;
284         uint16_t type;
285         int i;
286         
287         if ( netdev_poll ( 1, &data, &len ) ) {
288                 /* We have data */
289                 memcpy ( uip_buf, data, len );
290                 uip_len = len;
291                 type = ntohs ( *( ( uint16_t * ) ( uip_buf + 12 ) ) );
292                 if ( type == ETHERTYPE_ARP ) {
293                         uip_arp_arpin();
294                 } else {
295                         uip_arp_ipin();
296                         uip_input();
297                 }
298                 if ( uip_len > 0 )
299                         uip_transmit();
300         } else {
301                 for ( i = 0 ; i < UIP_CONNS ; i++ ) {
302                         uip_periodic ( i );
303                         if ( uip_len > 0 )
304                                 uip_transmit();
305                 }
306         }
307 }
308
309 /*****************************************************************************
310  *
311  * HTTP protocol tester
312  *
313  */
314
315 struct http_request {
316         struct sockaddr_in sin;
317         const char *filename;
318         void ( *callback ) ( struct http_request *http );
319         int complete;
320 };
321
322 static int http_get ( struct http_request *http ) {
323         return tcp_connect ( &http->sin );
324 }
325
326
327
328
329 struct http_options {
330         struct sockaddr_in server;
331         char *filename;
332 };
333
334 static void http_usage ( char **argv ) {
335         fprintf ( stderr,
336                   "Usage: %s [global options] http [http-specific options]\n"
337                   "\n"
338                   "http-specific options:\n"
339                   "  -h|--host              Host IP address\n"
340                   "  -f|--file              Filename\n",
341                   argv[0] );
342 }
343
344 static int http_parse_options ( int argc, char **argv,
345                                 struct http_options *options ) {
346         static struct option long_options[] = {
347                 { "host", 1, NULL, 'h' },
348                 { "file", 1, NULL, 'f' },
349                 { },
350         };
351         int c;
352
353         /* Set default options */
354         memset ( options, 0, sizeof ( *options ) );
355         inet_aton ( "192.168.0.1", &options->server.sin_addr );
356         options->server.sin_port = htons ( 80 );
357         options->filename = "index.html";
358
359         /* Parse command-line options */
360         while ( 1 ) {
361                 int option_index = 0;
362                 
363                 c = getopt_long ( argc, argv, "h:f:", long_options,
364                                   &option_index );
365                 if ( c < 0 )
366                         break;
367
368                 switch ( c ) {
369                 case 'h':
370                         if ( inet_aton ( optarg,
371                                          &options->server.sin_addr ) == 0 ) {
372                                 fprintf ( stderr, "Invalid IP address %s\n",
373                                           optarg );
374                                 return -1;
375                         }
376                         break;
377                 case 'f':
378                         options->filename = optarg;
379                         break;
380                 case '?':
381                         /* Unrecognised option */
382                         return -1;
383                 default:
384                         fprintf ( stderr, "Unrecognised option '-%c'\n", c );
385                         return -1;
386                 }
387         }
388
389         /* Check there are no remaining arguments */
390         if ( optind != argc ) {
391                 http_usage ( argv );
392                 return -1;
393         }
394         
395         return optind;
396 }
397
398 static void test_http_callback ( struct http_request *http ) {
399         
400 }
401
402 static int test_http ( int argc, char **argv ) {
403         struct http_options options;
404         struct http_request http;
405
406         /* Parse http-specific options */
407         if ( http_parse_options ( argc, argv, &options ) < 0 )
408                 return -1;
409
410         /* Construct http request */
411         memset ( &http, 0, sizeof ( http ) );
412         http.filename = options.filename;
413         http.sin = options.server;
414         http.callback = test_http_callback;
415         fprintf ( stderr, "http fetching http://%s/%s\n",
416                   inet_ntoa ( http.sin.sin_addr ), http.filename );
417
418         /* Issue http request and run to completion */
419         http_get ( &http );
420         while ( ! http.complete ) {
421                 run_tcpip ();
422         }
423
424         return 0;
425 }
426
427 /*****************************************************************************
428  *
429  * Protocol tester
430  *
431  */
432
433 struct protocol_test {
434         const char *name;
435         int ( *exec ) ( int argc, char **argv );
436 };
437
438 static struct protocol_test tests[] = {
439         { "http", test_http },
440 };
441
442 #define NUM_TESTS ( sizeof ( tests ) / sizeof ( tests[0] ) )
443
444 static void list_tests ( void ) {
445         int i;
446
447         for ( i = 0 ; i < NUM_TESTS ; i++ ) {
448                 printf ( "%s\n", tests[i].name );
449         }
450 }
451
452 static struct protocol_test * get_test_from_name ( const char *name ) {
453         int i;
454
455         for ( i = 0 ; i < NUM_TESTS ; i++ ) {
456                 if ( strcmp ( name, tests[i].name ) == 0 )
457                         return &tests[i];
458         }
459
460         return NULL;
461 }
462
463 /*****************************************************************************
464  *
465  * Parse command-line options
466  *
467  */
468
469 struct tester_options {
470         char interface[IF_NAMESIZE];
471 };
472
473 static void usage ( char **argv ) {
474         fprintf ( stderr,
475                   "Usage: %s [global options] <test> [test-specific options]\n"
476                   "\n"
477                   "Global options:\n"
478                   "  -h|--help              Print this help message\n"
479                   "  -i|--interface intf    Use specified network interface\n"
480                   "  -l|--list              List available tests\n"
481                   "\n"
482                   "Use \"%s <test> -h\" to view test-specific options\n",
483                   argv[0], argv[0] );
484 }
485
486 static int parse_options ( int argc, char **argv,
487                            struct tester_options *options ) {
488         static struct option long_options[] = {
489                 { "interface", 1, NULL, 'i' },
490                 { "list", 0, NULL, 'l' },
491                 { "help", 0, NULL, 'h' },
492                 { },
493         };
494         int c;
495
496         /* Set default options */
497         memset ( options, 0, sizeof ( *options ) );
498         strncpy ( options->interface, "eth0", sizeof ( options->interface ) );
499
500         /* Parse command-line options */
501         while ( 1 ) {
502                 int option_index = 0;
503                 
504                 c = getopt_long ( argc, argv, "+i:hl", long_options,
505                                   &option_index );
506                 if ( c < 0 )
507                         break;
508
509                 switch ( c ) {
510                 case 'i':
511                         strncpy ( options->interface, optarg,
512                                   sizeof ( options->interface ) );
513                         break;
514                 case 'l':
515                         list_tests ();
516                         return -1;
517                 case 'h':
518                         usage ( argv );
519                         return -1;
520                 case '?':
521                         /* Unrecognised option */
522                         return -1;
523                 default:
524                         fprintf ( stderr, "Unrecognised option '-%c'\n", c );
525                         return -1;
526                 }
527         }
528
529         /* Check there is a test specified */
530         if ( optind == argc ) {
531                 usage ( argv );
532                 return -1;
533         }
534         
535         return optind;
536 }
537
538 /*****************************************************************************
539  *
540  * Main program
541  *
542  */
543
544 int main ( int argc, char **argv ) {
545         struct tester_options options;
546         struct protocol_test *test;
547         struct hijack_device hijack_dev;
548
549         /* Parse command-line options */
550         if ( parse_options ( argc, argv, &options ) < 0 )
551                 exit ( 1 );
552
553         /* Identify test */
554         test = get_test_from_name ( argv[optind] );
555         if ( ! test ) {
556                 fprintf ( stderr, "Unrecognised test \"%s\"\n", argv[optind] );
557                 exit ( 1 );
558         }
559         optind++;
560
561         /* Initialise the protocol stack */
562         init_tcpip();
563
564         /* Open the hijack device */
565         hijack_dev.name = options.interface;
566         if ( ! hijack_probe ( &hijack_dev ) )
567                 exit ( 1 );
568
569         /* Run the test */
570         if ( test->exec ( argc, argv ) < 0 )
571                 exit ( 1 );
572
573         /* Close the hijack device */
574         hijack_disable ( &hijack_dev );
575
576         return 0;
577 }