Added netmask and gateway global options.
[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 <getopt.h>
12 #include <assert.h>
13
14 #include <gpxe/ip.h>
15 #include <gpxe/tcp.h>
16 #include <gpxe/hello.h>
17 #include <gpxe/iscsi.h>
18
19 typedef int irq_action_t;
20
21 struct nic {
22         struct nic_operations   *nic_op;
23         unsigned char           *node_addr;
24         unsigned char           *packet;
25         unsigned int            packetlen;
26         void                    *priv_data;     /* driver private data */
27 };
28
29 struct nic_operations {
30         int ( *connect ) ( struct nic * );
31         int ( *poll ) ( struct nic *, int retrieve );
32         void ( *transmit ) ( struct nic *, const char *,
33                              unsigned int, unsigned int, const char * );
34         void ( *irq ) ( struct nic *, irq_action_t );
35 };
36
37 /*****************************************************************************
38  *
39  * Net device layer
40  *
41  */
42
43 #include "../proto/uip/uip_arp.h"
44
45 static unsigned char node_addr[ETH_ALEN];
46 static unsigned char packet[ETH_FRAME_LEN];
47 struct nic static_nic = {
48         .node_addr = node_addr,
49         .packet = packet,
50 };
51
52 /* Must be a macro because priv_data[] is of variable size */
53 #define alloc_netdevice( priv_size ) ( {        \
54         static char priv_data[priv_size];       \
55         static_nic.priv_data = priv_data;       \
56         &static_nic; } )
57
58 static int register_netdevice ( struct nic *nic ) {
59         struct uip_eth_addr hwaddr;
60
61         memcpy ( &hwaddr, nic->node_addr, sizeof ( hwaddr ) );
62         uip_setethaddr ( hwaddr );
63         return 0;
64 }
65
66 static inline void unregister_netdevice ( struct nic *nic ) {
67         /* Do nothing */
68 }
69
70 static inline void free_netdevice ( struct nic *nic ) {
71         /* Do nothing */
72 }
73
74 int netdev_poll ( int retrieve, void **data, size_t *len ) {
75         int rc = static_nic.nic_op->poll ( &static_nic, retrieve );
76         *data = static_nic.packet;
77         *len = static_nic.packetlen;
78         return rc;
79 }
80
81 void netdev_transmit ( const void *data, size_t len ) {
82         uint16_t type = ntohs ( *( ( uint16_t * ) ( data + 12 ) ) );
83         static_nic.nic_op->transmit ( &static_nic, data, type,
84                                       len - ETH_HLEN,
85                                       data + ETH_HLEN );
86 }
87
88 /*****************************************************************************
89  *
90  * Utility functions
91  *
92  */
93
94 static void hex_dump ( const void *data, size_t len ) {
95         unsigned int index;
96         for ( index = 0; index < len; index++ ) {
97                 if ( ( index % 16 ) == 0 ) {
98                         printf ( "\n%08x :", index );
99                 }
100                 printf ( " %02x", * ( ( unsigned char * ) ( data + index ) ) );
101         }
102         printf ( "\n" );
103 }
104
105 /*****************************************************************************
106  *
107  * Hijack device interface
108  *
109  * This requires a hijack daemon to be running
110  *
111  */
112
113 struct hijack {
114         int fd;
115 };
116
117 struct hijack_device {
118         char *name;
119         void *priv;
120 };
121
122 static inline void hijack_set_drvdata ( struct hijack_device *hijack_dev,
123                                         void *data ) {
124         hijack_dev->priv = data;
125 }
126
127 static inline void * hijack_get_drvdata ( struct hijack_device *hijack_dev ) {
128         return hijack_dev->priv;
129 }
130
131 static int hijack_poll ( struct nic *nic, int retrieve ) {
132         struct hijack *hijack = nic->priv_data;
133         fd_set fdset;
134         struct timeval tv;
135         int ready;
136         ssize_t len;
137
138         /* Poll for data */
139         FD_ZERO ( &fdset );
140         FD_SET ( hijack->fd, &fdset );
141         tv.tv_sec = 0;
142         tv.tv_usec = 500; /* 500us to avoid hogging CPU */
143         ready = select ( ( hijack->fd + 1 ), &fdset, NULL, NULL, &tv );
144         if ( ready < 0 ) {
145                 fprintf ( stderr, "select() failed: %s\n",
146                           strerror ( errno ) );
147                 return 0;
148         }
149         if ( ready == 0 )
150                 return 0;
151
152         /* Return if we're not retrieving data yet */
153         if ( ! retrieve )
154                 return 1;
155
156         /* Fetch data */
157         len = read ( hijack->fd, nic->packet, ETH_FRAME_LEN );
158         if ( len < 0 ) {
159                 fprintf ( stderr, "read() failed: %s\n",
160                           strerror ( errno ) );
161                 return 0;
162         }
163         nic->packetlen = len;
164
165         return 1;
166 }
167
168 static void hijack_transmit ( struct nic *nic, const char *dest,
169                               unsigned int type, unsigned int size,
170                               const char *packet ) {
171         struct hijack *hijack = nic->priv_data;
172         unsigned int nstype = htons ( type );
173         unsigned int total_size = ETH_HLEN + size;
174         char txbuf[ total_size ];
175
176         /* Build packet header */
177         memcpy ( txbuf, dest, ETH_ALEN );
178         memcpy ( txbuf + ETH_ALEN, nic->node_addr, ETH_ALEN );
179         memcpy ( txbuf + 2 * ETH_ALEN, &nstype, 2 );
180         memcpy ( txbuf + ETH_HLEN, packet, size );
181
182         /* Transmit data */
183         if ( write ( hijack->fd, txbuf, total_size ) != total_size ) {
184                 fprintf ( stderr, "write() failed: %s\n",
185                           strerror ( errno ) );
186         }
187 }
188
189 static int hijack_connect ( struct nic *nic ) {
190         return 1;
191 }
192
193 static void hijack_irq ( struct nic *nic, irq_action_t action ) {
194         /* Do nothing */
195 }
196
197 static struct nic_operations hijack_operations = {
198         .connect        = hijack_connect,
199         .transmit       = hijack_transmit,
200         .poll           = hijack_poll,
201         .irq            = hijack_irq,
202 };
203
204 int hijack_probe ( struct hijack_device *hijack_dev ) {
205         struct nic *nic;
206         struct hijack *hijack;
207         struct sockaddr_un sun;
208         int i;
209
210         nic = alloc_netdevice ( sizeof ( *hijack ) );
211         if ( ! nic ) {
212                 fprintf ( stderr, "alloc_netdevice() failed\n" );
213                 goto err_alloc;
214         }
215         hijack = nic->priv_data;
216         memset ( hijack, 0, sizeof ( *hijack ) );
217
218         /* Create socket */
219         hijack->fd = socket ( PF_UNIX, SOCK_SEQPACKET, 0 );
220         if ( hijack->fd < 0 ) {
221                 fprintf ( stderr, "socket() failed: %s\n",
222                           strerror ( errno ) );
223                 goto err;
224         }
225
226         /* Connect to hijack daemon */
227         sun.sun_family = AF_UNIX;
228         snprintf ( sun.sun_path, sizeof ( sun.sun_path ), "/var/run/hijack-%s",
229                    hijack_dev->name );
230         if ( connect ( hijack->fd, ( struct sockaddr * ) &sun,
231                        sizeof ( sun ) ) < 0 ) {
232                 fprintf ( stderr, "could not connect to %s: %s\n",
233                           sun.sun_path, strerror ( errno ) );
234                 goto err;
235         }
236
237         /* Generate MAC address */
238         srand ( time ( NULL ) );
239         for ( i = 0 ; i < ETH_ALEN ; i++ ) {
240                 nic->node_addr[i] = ( rand() & 0xff );
241         }
242         nic->node_addr[0] &= 0xfe; /* clear multicast bit */
243         nic->node_addr[0] |= 0x02; /* set "locally-assigned" bit */
244
245         nic->nic_op = &hijack_operations;
246         if ( register_netdevice ( nic ) < 0 )
247                 goto err;
248
249         hijack_set_drvdata ( hijack_dev, nic );
250         return 1;
251
252  err:
253         if ( hijack->fd >= 0 )
254                 close ( hijack->fd );
255         free_netdevice ( nic );
256  err_alloc:
257         return 0;
258 }
259
260 static void hijack_disable ( struct hijack_device *hijack_dev ) {
261         struct nic *nic = hijack_get_drvdata ( hijack_dev );
262         struct hijack *hijack = nic->priv_data;
263         
264         unregister_netdevice ( nic );
265         close ( hijack->fd );
266 }
267
268 /*****************************************************************************
269  *
270  * "Hello world" protocol tester
271  *
272  */
273
274 struct hello_options {
275         struct sockaddr_in server;
276         const char *message;
277 };
278
279 static void hello_usage ( char **argv ) {
280         fprintf ( stderr,
281                   "Usage: %s [global options] hello [hello-specific options]\n"
282                   "\n"
283                   "hello-specific options:\n"
284                   "  -h|--help              Print this help message\n"
285                   "  -s|--server ip_addr    Server IP address\n"
286                   "  -p|--port port         Port number\n"
287                   "  -m|--message msg       Message to send\n",
288                   argv[0] );
289 }
290
291 static int hello_parse_options ( int argc, char **argv,
292                                  struct hello_options *options ) {
293         static struct option long_options[] = {
294                 { "server", 1, NULL, 's' },
295                 { "port", 1, NULL, 'p' },
296                 { "message", 1, NULL, 'm' },
297                 { "help", 0, NULL, 'h' },
298                 { },
299         };
300         int c;
301         char *endptr;
302
303         /* Set default options */
304         memset ( options, 0, sizeof ( *options ) );
305         inet_aton ( "192.168.0.1", &options->server.sin_addr );
306         options->server.sin_port = htons ( 80 );
307         options->message = "Hello world!";
308
309         /* Parse command-line options */
310         while ( 1 ) {
311                 int option_index = 0;
312                 
313                 c = getopt_long ( argc, argv, "s:p:m:h", long_options,
314                                   &option_index );
315                 if ( c < 0 )
316                         break;
317
318                 switch ( c ) {
319                 case 's':
320                         if ( inet_aton ( optarg,
321                                          &options->server.sin_addr ) == 0 ) {
322                                 fprintf ( stderr, "Invalid IP address %s\n",
323                                           optarg );
324                                 return -1;
325                         }
326                         break;
327                 case 'p':
328                         options->server.sin_port =
329                                 htons ( strtoul ( optarg, &endptr, 0 ) );
330                         if ( *endptr != '\0' ) {
331                                 fprintf ( stderr, "Invalid port %s\n",
332                                           optarg );
333                                 return -1;
334                         }
335                         break;
336                 case 'm':
337                         options->message = optarg;
338                         break;
339                 case 'h':
340                         hello_usage ( argv );
341                         return -1;
342                 case '?':
343                         /* Unrecognised option */
344                         return -1;
345                 default:
346                         fprintf ( stderr, "Unrecognised option '-%c'\n", c );
347                         return -1;
348                 }
349         }
350
351         /* Check there are no remaining arguments */
352         if ( optind != argc ) {
353                 hello_usage ( argv );
354                 return -1;
355         }
356         
357         return optind;
358 }
359
360 static void test_hello_callback ( char *data, size_t len ) {
361         int i;
362         char c;
363
364         for ( i = 0 ; i < len ; i++ ) {
365                 c = data[i];
366                 if ( c == '\r' ) {
367                         /* Print nothing */
368                 } else if ( ( c == '\n' ) || ( c >= 32 ) || ( c <= 126 ) ) {
369                         putchar ( c );
370                 } else {
371                         putchar ( '.' );
372                 }
373         }       
374 }
375
376 static int test_hello ( int argc, char **argv ) {
377         struct hello_options options;
378         struct hello_request hello;
379
380         /* Parse hello-specific options */
381         if ( hello_parse_options ( argc, argv, &options ) < 0 )
382                 return -1;
383
384         /* Construct hello request */
385         memset ( &hello, 0, sizeof ( hello ) );
386         hello.tcp.sin = options.server;
387         hello.message = options.message;
388         hello.callback = test_hello_callback;
389         fprintf ( stderr, "Saying \"%s\" to %s:%d\n", hello.message,
390                   inet_ntoa ( hello.tcp.sin.sin_addr ),
391                   ntohs ( hello.tcp.sin.sin_port ) );
392
393         /* Issue hello request and run to completion */
394         hello_connect ( &hello );
395         while ( ! hello.complete ) {
396                 run_tcpip ();
397         }
398
399         return 0;
400 }
401
402 /*****************************************************************************
403  *
404  * iSCSI protocol tester
405  *
406  */
407
408 struct iscsi_options {
409         struct sockaddr_in server;
410         const char *initiator;
411         const char *target;
412 };
413
414 static void iscsi_usage ( char **argv ) {
415         fprintf ( stderr,
416                   "Usage: %s [global options] iscsi [iscsi-specific options]\n"
417                   "\n"
418                   "iscsi-specific options:\n"
419                   "  -h|--help              Print this help message\n"
420                   "  -s|--server ip_addr    Server IP address\n"
421                   "  -p|--port port         Port number\n"
422                   "  -i|--initiator iqn     iSCSI initiator name\n"
423                   "  -t|--target iqn        iSCSI target name\n",
424                   argv[0] );
425 }
426
427 static int iscsi_parse_options ( int argc, char **argv,
428                                  struct iscsi_options *options ) {
429         static struct option long_options[] = {
430                 { "server", 1, NULL, 's' },
431                 { "port", 1, NULL, 'p' },
432                 { "initiator", 1, NULL, 'i' },
433                 { "target", 1, NULL, 't' },
434                 { "help", 0, NULL, 'h' },
435                 { },
436         };
437         int c;
438         char *endptr;
439
440         /* Set default options */
441         memset ( options, 0, sizeof ( *options ) );
442         inet_aton ( "192.168.0.1", &options->server.sin_addr );
443         options->server.sin_port = htons ( 3260 );
444         options->initiator = "iqn.1900-01.localdomain.localhost:initiator";
445         options->target = "iqn.1900-01.localdomain.localhost:target";
446
447         /* Parse command-line options */
448         while ( 1 ) {
449                 int option_index = 0;
450                 
451                 c = getopt_long ( argc, argv, "s:p:i:t:h", long_options,
452                                   &option_index );
453                 if ( c < 0 )
454                         break;
455
456                 switch ( c ) {
457                 case 's':
458                         if ( inet_aton ( optarg,
459                                          &options->server.sin_addr ) == 0 ) {
460                                 fprintf ( stderr, "Invalid IP address %s\n",
461                                           optarg );
462                                 return -1;
463                         }
464                         break;
465                 case 'p':
466                         options->server.sin_port =
467                                 htons ( strtoul ( optarg, &endptr, 0 ) );
468                         if ( *endptr != '\0' ) {
469                                 fprintf ( stderr, "Invalid port %s\n",
470                                           optarg );
471                                 return -1;
472                         }
473                         break;
474                 case 'i':
475                         options->initiator = optarg;
476                         break;
477                 case 't':
478                         options->target = optarg;
479                         break;
480                 case 'h':
481                         iscsi_usage ( argv );
482                         return -1;
483                 case '?':
484                         /* Unrecognised option */
485                         return -1;
486                 default:
487                         fprintf ( stderr, "Unrecognised option '-%c'\n", c );
488                         return -1;
489                 }
490         }
491
492         /* Check there are no remaining arguments */
493         if ( optind != argc ) {
494                 iscsi_usage ( argv );
495                 return -1;
496         }
497         
498         return optind;
499 }
500
501 struct test_iscsi_buffer {
502         unsigned char data[512];
503 };
504
505 static void test_iscsi_callback ( void *private, const void *data,
506                                   unsigned long offset, size_t len ) {
507         struct test_iscsi_buffer *buffer = private;
508
509         assert ( ( offset + len ) <= sizeof ( buffer->data ) );
510         memcpy ( buffer->data + offset, data, len );
511 }
512
513 static int test_iscsi_block ( struct iscsi_session *iscsi,
514                               unsigned int block ) {
515         struct test_iscsi_buffer buffer;
516
517         iscsi->block_size = 512;
518         iscsi->block_start = block;
519         iscsi->block_count = 1;
520         iscsi->block_read_callback = test_iscsi_callback;
521         iscsi->block_read_private = &buffer;
522         memset ( buffer.data, 0x61, sizeof ( buffer.data ) );
523
524         /* Start up iscsi session */
525         iscsi_wakeup ( iscsi );
526         while ( iscsi_busy ( iscsi ) ) {
527                 run_tcpip ();
528         }
529
530         /* Check for errors */
531         if ( iscsi_error ( iscsi ) ) {
532                 fprintf ( stderr, "iSCSI error on block %d\n", block );
533                 return -1;
534         }
535
536         /* Dump out data */
537         hex_dump ( buffer.data, sizeof ( buffer.data ) );
538
539         return 0;
540 }
541
542 static int test_iscsi ( int argc, char **argv ) {
543         struct iscsi_options options;
544         struct iscsi_session iscsi;
545         unsigned int block;
546
547         /* Parse iscsi-specific options */
548         if ( iscsi_parse_options ( argc, argv, &options ) < 0 )
549                 return -1;
550
551         /* Construct iscsi session */
552         memset ( &iscsi, 0, sizeof ( iscsi ) );
553         iscsi.tcp.sin = options.server;
554         iscsi.initiator = options.initiator;
555         iscsi.target = options.target;
556
557         /* Read some blocks */
558         for ( block = 0 ; block < 4 ; block += 2 ) {
559                 if ( test_iscsi_block ( &iscsi, block ) < 0 )
560                         return -1;
561         }
562
563         return 0;
564 }
565
566 /*****************************************************************************
567  *
568  * Protocol tester
569  *
570  */
571
572 struct protocol_test {
573         const char *name;
574         int ( *exec ) ( int argc, char **argv );
575 };
576
577 static struct protocol_test tests[] = {
578         { "hello", test_hello },
579         { "iscsi", test_iscsi },
580 };
581
582 #define NUM_TESTS ( sizeof ( tests ) / sizeof ( tests[0] ) )
583
584 static void list_tests ( void ) {
585         int i;
586
587         for ( i = 0 ; i < NUM_TESTS ; i++ ) {
588                 printf ( "%s\n", tests[i].name );
589         }
590 }
591
592 static struct protocol_test * get_test_from_name ( const char *name ) {
593         int i;
594
595         for ( i = 0 ; i < NUM_TESTS ; i++ ) {
596                 if ( strcmp ( name, tests[i].name ) == 0 )
597                         return &tests[i];
598         }
599
600         return NULL;
601 }
602
603 /*****************************************************************************
604  *
605  * Parse command-line options
606  *
607  */
608
609 struct tester_options {
610         char interface[IF_NAMESIZE];
611         struct in_addr in_addr;
612         struct in_addr netmask;
613         struct in_addr gateway;
614 };
615
616 static void usage ( char **argv ) {
617         fprintf ( stderr,
618                   "Usage: %s [global options] <test> [test-specific options]\n"
619                   "\n"
620                   "Global options:\n"
621                   "  -h|--help              Print this help message\n"
622                   "  -i|--interface intf    Use specified network interface\n"
623                   "  -f|--from ip-addr      Use specified local IP address\n"
624                   "  -n|--netmask mask      Use specified netmask\n"
625                   "  -g|--gateway ip-addr   Use specified default gateway\n"
626                   "  -l|--list              List available tests\n"
627                   "\n"
628                   "Use \"%s <test> -h\" to view test-specific options\n",
629                   argv[0], argv[0] );
630 }
631
632 static int parse_options ( int argc, char **argv,
633                            struct tester_options *options ) {
634         static struct option long_options[] = {
635                 { "interface", 1, NULL, 'i' },
636                 { "from", 1, NULL, 'f' },
637                 { "netmask", 1, NULL, 'n' },
638                 { "gateway", 1, NULL, 'g' },
639                 { "list", 0, NULL, 'l' },
640                 { "help", 0, NULL, 'h' },
641                 { },
642         };
643         int c;
644
645         /* Set default options */
646         memset ( options, 0, sizeof ( *options ) );
647         strncpy ( options->interface, "eth0", sizeof ( options->interface ) );
648         inet_aton ( "192.168.0.2", &options->in_addr );
649
650         /* Parse command-line options */
651         while ( 1 ) {
652                 int option_index = 0;
653                 
654                 c = getopt_long ( argc, argv, "+i:f:n:g:hl", long_options,
655                                   &option_index );
656                 if ( c < 0 )
657                         break;
658
659                 switch ( c ) {
660                 case 'i':
661                         strncpy ( options->interface, optarg,
662                                   sizeof ( options->interface ) );
663                         break;
664                 case 'f':
665                         if ( inet_aton ( optarg, &options->in_addr ) == 0 ) {
666                                 fprintf ( stderr, "Invalid IP address %s\n",
667                                           optarg );
668                                 return -1;
669                         }
670                         break;
671                 case 'n':
672                         if ( inet_aton ( optarg, &options->netmask ) == 0 ) {
673                                 fprintf ( stderr, "Invalid IP address %s\n",
674                                           optarg );
675                                 return -1;
676                         }
677                         break;
678                 case 'g':
679                         if ( inet_aton ( optarg, &options->gateway ) == 0 ) {
680                                 fprintf ( stderr, "Invalid IP address %s\n",
681                                           optarg );
682                                 return -1;
683                         }
684                         break;
685                 case 'l':
686                         list_tests ();
687                         return -1;
688                 case 'h':
689                         usage ( argv );
690                         return -1;
691                 case '?':
692                         /* Unrecognised option */
693                         return -1;
694                 default:
695                         fprintf ( stderr, "Unrecognised option '-%c'\n", c );
696                         return -1;
697                 }
698         }
699
700         /* Check there is a test specified */
701         if ( optind == argc ) {
702                 usage ( argv );
703                 return -1;
704         }
705         
706         return optind;
707 }
708
709 /*****************************************************************************
710  *
711  * Main program
712  *
713  */
714
715 int main ( int argc, char **argv ) {
716         struct tester_options options;
717         struct protocol_test *test;
718         struct hijack_device hijack_dev;
719
720         /* Parse command-line options */
721         if ( parse_options ( argc, argv, &options ) < 0 )
722                 exit ( 1 );
723
724         /* Identify test */
725         test = get_test_from_name ( argv[optind] );
726         if ( ! test ) {
727                 fprintf ( stderr, "Unrecognised test \"%s\"\n", argv[optind] );
728                 exit ( 1 );
729         }
730         optind++;
731
732         /* Initialise the protocol stack */
733         init_tcpip();
734         set_ipaddr ( options.in_addr );
735         set_netmask ( options.netmask );
736         set_gateway ( options.gateway );
737
738         /* Open the hijack device */
739         hijack_dev.name = options.interface;
740         if ( ! hijack_probe ( &hijack_dev ) )
741                 exit ( 1 );
742
743         /* Run the test */
744         if ( test->exec ( argc, argv ) < 0 )
745                 exit ( 1 );
746
747         /* Close the hijack device */
748         hijack_disable ( &hijack_dev );
749
750         return 0;
751 }