5d37b46df93fb352d468b8d814fc401725ca6455
[vpp.git] / src / vcl / vcl_test_server.c
1 /*
2  * Copyright (c) 2017-2018 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <unistd.h>
17 #include <errno.h>
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <time.h>
23 #include <ctype.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <vcl/vcl_test.h>
27 #include <sys/epoll.h>
28
29 typedef struct
30 {
31   uint8_t is_alloc;
32   int fd;
33   uint8_t *buf;
34   uint32_t buf_size;
35   sock_test_cfg_t cfg;
36   sock_test_stats_t stats;
37   vppcom_endpt_t endpt;
38   uint8_t ip[16];
39 } sock_server_conn_t;
40
41 typedef struct
42 {
43   uint32_t port;
44   uint32_t address_ip6;
45   uint32_t transport_udp;
46 } sock_server_cfg_t;
47
48 #define SOCK_SERVER_MAX_TEST_CONN  10
49 #define SOCK_SERVER_MAX_EPOLL_EVENTS 10
50 typedef struct
51 {
52   int listen_fd;
53   sock_server_cfg_t cfg;
54   int epfd;
55   struct epoll_event listen_ev;
56   struct epoll_event wait_events[SOCK_SERVER_MAX_EPOLL_EVENTS];
57   size_t num_conn;
58   size_t conn_pool_size;
59   sock_server_conn_t *conn_pool;
60   int nfds;
61   fd_set rd_fdset;
62   fd_set wr_fdset;
63   struct timeval timeout;
64 } sock_server_main_t;
65
66 sock_server_main_t sock_server_main;
67
68 static inline void
69 conn_pool_expand (size_t expand_size)
70 {
71   sock_server_main_t *ssm = &sock_server_main;
72   sock_server_conn_t *conn_pool;
73   size_t new_size = ssm->conn_pool_size + expand_size;
74   int i;
75
76   conn_pool = realloc (ssm->conn_pool, new_size * sizeof (*ssm->conn_pool));
77   if (conn_pool)
78     {
79       for (i = ssm->conn_pool_size; i < new_size; i++)
80         {
81           sock_server_conn_t *conn = &conn_pool[i];
82           memset (conn, 0, sizeof (*conn));
83           sock_test_cfg_init (&conn->cfg);
84           sock_test_buf_alloc (&conn->cfg, 1 /* is_rxbuf */ ,
85                                &conn->buf, &conn->buf_size);
86           conn->cfg.txbuf_size = conn->cfg.rxbuf_size;
87         }
88
89       ssm->conn_pool = conn_pool;
90       ssm->conn_pool_size = new_size;
91     }
92   else
93     {
94       int errno_val = errno;
95       perror ("ERROR in conn_pool_expand()");
96       fprintf (stderr, "SERVER: ERROR: Memory allocation "
97                "failed (errno = %d)!\n", errno_val);
98     }
99 }
100
101 static inline sock_server_conn_t *
102 conn_pool_alloc (void)
103 {
104   sock_server_main_t *ssm = &sock_server_main;
105   int i;
106
107   for (i = 0; i < ssm->conn_pool_size; i++)
108     {
109       if (!ssm->conn_pool[i].is_alloc)
110         {
111           ssm->conn_pool[i].endpt.ip = ssm->conn_pool[i].ip;
112           ssm->conn_pool[i].is_alloc = 1;
113           return (&ssm->conn_pool[i]);
114         }
115     }
116
117   return 0;
118 }
119
120 static inline void
121 conn_pool_free (sock_server_conn_t * conn)
122 {
123   conn->fd = 0;
124   conn->is_alloc = 0;
125 }
126
127 static inline void
128 sync_config_and_reply (sock_server_conn_t * conn, sock_test_cfg_t * rx_cfg)
129 {
130   conn->cfg = *rx_cfg;
131   sock_test_buf_alloc (&conn->cfg, 1 /* is_rxbuf */ ,
132                        &conn->buf, &conn->buf_size);
133   conn->cfg.txbuf_size = conn->cfg.rxbuf_size;
134
135   if (conn->cfg.verbose)
136     {
137       printf ("\nSERVER (fd %d): Replying to cfg message!\n", conn->fd);
138       sock_test_cfg_dump (&conn->cfg, 0 /* is_client */ );
139     }
140   (void) vcl_test_write (conn->fd, (uint8_t *) & conn->cfg,
141                          sizeof (conn->cfg), NULL, conn->cfg.verbose);
142 }
143
144 static void
145 stream_test_server_start_stop (sock_server_conn_t * conn,
146                                sock_test_cfg_t * rx_cfg)
147 {
148   sock_server_main_t *ssm = &sock_server_main;
149   int client_fd = conn->fd;
150   sock_test_t test = rx_cfg->test;
151
152   if (rx_cfg->ctrl_handle == conn->fd)
153     {
154       int i;
155       clock_gettime (CLOCK_REALTIME, &conn->stats.stop);
156
157       for (i = 0; i < ssm->conn_pool_size; i++)
158         {
159           sock_server_conn_t *tc = &ssm->conn_pool[i];
160
161           if (tc->cfg.ctrl_handle == conn->fd)
162             {
163               sock_test_stats_accumulate (&conn->stats, &tc->stats);
164
165               if (conn->cfg.verbose)
166                 {
167                   static char buf[64];
168
169                   sprintf (buf, "SERVER (fd %d) RESULTS", tc->fd);
170                   sock_test_stats_dump (buf, &tc->stats, 1 /* show_rx */ ,
171                                         test == SOCK_TEST_TYPE_BI
172                                         /* show tx */ ,
173                                         conn->cfg.verbose);
174                 }
175             }
176         }
177
178       sock_test_stats_dump ("SERVER RESULTS", &conn->stats, 1 /* show_rx */ ,
179                             (test == SOCK_TEST_TYPE_BI) /* show_tx */ ,
180                             conn->cfg.verbose);
181       sock_test_cfg_dump (&conn->cfg, 0 /* is_client */ );
182       if (conn->cfg.verbose)
183         {
184           printf ("  sock server main\n"
185                   SOCK_TEST_SEPARATOR_STRING
186                   "       buf:  %p\n"
187                   "  buf size:  %u (0x%08x)\n"
188                   SOCK_TEST_SEPARATOR_STRING,
189                   conn->buf, conn->buf_size, conn->buf_size);
190         }
191
192       sync_config_and_reply (conn, rx_cfg);
193       printf ("\nSERVER (fd %d): %s-directional Stream Test Complete!\n"
194               SOCK_TEST_BANNER_STRING "\n", conn->fd,
195               test == SOCK_TEST_TYPE_BI ? "Bi" : "Uni");
196     }
197   else
198     {
199       printf ("\n" SOCK_TEST_BANNER_STRING
200               "SERVER (fd %d): %s-directional Stream Test!\n"
201               "  Sending client the test cfg to start streaming data...\n",
202               client_fd, test == SOCK_TEST_TYPE_BI ? "Bi" : "Uni");
203
204       rx_cfg->ctrl_handle = (rx_cfg->ctrl_handle == ~0) ? conn->fd :
205         rx_cfg->ctrl_handle;
206
207       sync_config_and_reply (conn, rx_cfg);
208
209       /* read the 1st chunk, record start time */
210       memset (&conn->stats, 0, sizeof (conn->stats));
211       clock_gettime (CLOCK_REALTIME, &conn->stats.start);
212     }
213 }
214
215
216 static inline void
217 stream_test_server (sock_server_conn_t * conn, int rx_bytes)
218 {
219   int client_fd = conn->fd;
220   sock_test_t test = conn->cfg.test;
221
222   if (test == SOCK_TEST_TYPE_BI)
223     (void) vcl_test_write (client_fd, conn->buf, rx_bytes, &conn->stats,
224                            conn->cfg.verbose);
225
226   if (conn->stats.rx_bytes >= conn->cfg.total_bytes)
227     {
228       clock_gettime (CLOCK_REALTIME, &conn->stats.stop);
229     }
230 }
231
232 static inline void
233 new_client (void)
234 {
235   sock_server_main_t *ssm = &sock_server_main;
236   int client_fd;
237   sock_server_conn_t *conn;
238
239   if (ssm->conn_pool_size < (ssm->num_conn + SOCK_SERVER_MAX_TEST_CONN + 1))
240     conn_pool_expand (SOCK_SERVER_MAX_TEST_CONN + 1);
241
242   conn = conn_pool_alloc ();
243   if (!conn)
244     {
245       fprintf (stderr, "\nSERVER: ERROR: No free connections!\n");
246       return;
247     }
248
249   client_fd = vppcom_session_accept (ssm->listen_fd, &conn->endpt, 0);
250   if (client_fd < 0)
251     {
252       int errno_val;
253       errno_val = errno = -client_fd;
254       perror ("ERROR in new_client()");
255       fprintf (stderr, "SERVER: ERROR: accept failed "
256                "(errno = %d)!\n", errno_val);
257       return;
258     }
259
260   printf ("SERVER: Got a connection -- fd = %d (0x%08x)!\n",
261           client_fd, client_fd);
262
263   conn->fd = client_fd;
264
265   {
266     struct epoll_event ev;
267     int rv;
268
269     ev.events = EPOLLIN;
270     ev.data.u64 = conn - ssm->conn_pool;
271     rv = vppcom_epoll_ctl (ssm->epfd, EPOLL_CTL_ADD, client_fd, &ev);
272     if (rv < 0)
273       {
274         int errno_val;
275         errno_val = errno = -rv;
276         perror ("ERROR in new_client()");
277         fprintf (stderr, "SERVER: ERROR: epoll_ctl failed (errno = %d)!\n",
278                  errno_val);
279       }
280     else
281       ssm->nfds++;
282   }
283 }
284
285 void
286 print_usage_and_exit (void)
287 {
288   fprintf (stderr,
289            "sock_test_server [OPTIONS] <port>\n"
290            "  OPTIONS\n"
291            "  -h               Print this message and exit.\n"
292            "  -6               Use IPv6\n"
293            "  -u               Use UDP transport layer\n");
294   exit (1);
295 }
296
297 int
298 main (int argc, char **argv)
299 {
300   sock_server_main_t *ssm = &sock_server_main;
301   int client_fd, rv, main_rv = 0;
302   int tx_bytes, rx_bytes, nbytes;
303   sock_server_conn_t *conn;
304   sock_test_cfg_t *rx_cfg;
305   uint32_t xtra = 0;
306   uint64_t xtra_bytes = 0;
307   struct sockaddr_storage servaddr;
308   int errno_val;
309   int c, v, i;
310   uint16_t port = SOCK_TEST_SERVER_PORT;
311   vppcom_endpt_t endpt;
312
313   opterr = 0;
314   while ((c = getopt (argc, argv, "6D")) != -1)
315     switch (c)
316       {
317       case '6':
318         ssm->cfg.address_ip6 = 1;
319         break;
320
321       case 'D':
322         ssm->cfg.transport_udp = 1;
323         break;
324
325       case '?':
326         switch (optopt)
327           {
328           default:
329             if (isprint (optopt))
330               fprintf (stderr, "SERVER: ERROR: Unknown "
331                        "option `-%c'.\n", optopt);
332             else
333               fprintf (stderr, "SERVER: ERROR: Unknown "
334                        "option character `\\x%x'.\n", optopt);
335           }
336         /* fall thru */
337       case 'h':
338       default:
339         print_usage_and_exit ();
340       }
341
342   if (argc < (optind + 1))
343     {
344       fprintf (stderr, "SERVER: ERROR: Insufficient number of arguments!\n");
345       print_usage_and_exit ();
346     }
347
348   if (sscanf (argv[optind], "%d", &v) == 1)
349     port = (uint16_t) v;
350   else
351     {
352       fprintf (stderr, "SERVER: ERROR: Invalid port (%s)!\n", argv[optind]);
353       print_usage_and_exit ();
354     }
355
356   conn_pool_expand (SOCK_SERVER_MAX_TEST_CONN + 1);
357
358   rv = vppcom_app_create ("vcl_test_server");
359   if (rv)
360     {
361       errno_val = errno = -rv;
362       perror ("ERROR in main()");
363       fprintf (stderr, "SERVER: ERROR: vppcom_app_create() failed "
364                "(errno = %d)!\n", errno_val);
365       return -1;
366     }
367   else
368     {
369       ssm->listen_fd = vppcom_session_create (ssm->cfg.transport_udp ?
370                                               VPPCOM_PROTO_UDP :
371                                               VPPCOM_PROTO_TCP,
372                                               0 /* is_nonblocking */ );
373     }
374   if (ssm->listen_fd < 0)
375     {
376       errno_val = errno = -ssm->listen_fd;
377       perror ("ERROR in main()");
378       fprintf (stderr, "SERVER: ERROR: vppcom_session_create() failed "
379                "(errno = %d)!\n", errno_val);
380       return -1;
381     }
382
383   memset (&servaddr, 0, sizeof (servaddr));
384
385   if (ssm->cfg.address_ip6)
386     {
387       struct sockaddr_in6 *server_addr = (struct sockaddr_in6 *) &servaddr;
388       server_addr->sin6_family = AF_INET6;
389       server_addr->sin6_addr = in6addr_any;
390       server_addr->sin6_port = htons (port);
391     }
392   else
393     {
394       struct sockaddr_in *server_addr = (struct sockaddr_in *) &servaddr;
395       server_addr->sin_family = AF_INET;
396       server_addr->sin_addr.s_addr = htonl (INADDR_ANY);
397       server_addr->sin_port = htons (port);
398     }
399
400   if (ssm->cfg.address_ip6)
401     {
402       struct sockaddr_in6 *server_addr = (struct sockaddr_in6 *) &servaddr;
403       endpt.is_ip4 = 0;
404       endpt.ip = (uint8_t *) & server_addr->sin6_addr;
405       endpt.port = (uint16_t) server_addr->sin6_port;
406     }
407   else
408     {
409       struct sockaddr_in *server_addr = (struct sockaddr_in *) &servaddr;
410       endpt.is_ip4 = 1;
411       endpt.ip = (uint8_t *) & server_addr->sin_addr;
412       endpt.port = (uint16_t) server_addr->sin_port;
413     }
414
415   rv = vppcom_session_bind (ssm->listen_fd, &endpt);
416   if (rv < 0)
417     {
418       errno_val = errno = -rv;
419       perror ("ERROR in main()");
420       fprintf (stderr, "SERVER: ERROR: bind failed (errno = %d)!\n",
421                errno_val);
422       return -1;
423     }
424
425   rv = vppcom_session_listen (ssm->listen_fd, 10);
426   if (rv < 0)
427     {
428       errno_val = errno = -rv;
429       perror ("ERROR in main()");
430       fprintf (stderr, "SERVER: ERROR: listen failed "
431                "(errno = %d)!\n", errno_val);
432       return -1;
433     }
434
435   ssm->epfd = vppcom_epoll_create ();
436   if (ssm->epfd < 0)
437     {
438       errno_val = errno = -ssm->epfd;
439       perror ("ERROR in main()");
440       fprintf (stderr, "SERVER: ERROR: epoll_create failed (errno = %d)!\n",
441                errno_val);
442       return -1;
443     }
444
445   ssm->listen_ev.events = EPOLLIN;
446   ssm->listen_ev.data.u32 = ~0;
447   rv = vppcom_epoll_ctl (ssm->epfd, EPOLL_CTL_ADD, ssm->listen_fd,
448                          &ssm->listen_ev);
449   if (rv < 0)
450     {
451       errno_val = errno = -rv;
452       perror ("ERROR in main()");
453       fprintf (stderr, "SERVER: ERROR: epoll_ctl failed "
454                "(errno = %d)!\n", errno_val);
455       return -1;
456     }
457   printf ("\nSERVER: Waiting for a client to connect on port %d...\n", port);
458
459   while (1)
460     {
461       int num_ev;
462       num_ev = vppcom_epoll_wait (ssm->epfd, ssm->wait_events,
463                                   SOCK_SERVER_MAX_EPOLL_EVENTS, 60.0);
464       if (num_ev < 0)
465         {
466           errno = -num_ev;
467           perror ("epoll_wait()");
468           fprintf (stderr, "\nSERVER: ERROR: epoll_wait() "
469                    "failed -- aborting!\n");
470           main_rv = -1;
471           goto done;
472         }
473       else if (num_ev == 0)
474         {
475           fprintf (stderr, "\nSERVER: epoll_wait() timeout!\n");
476           continue;
477         }
478       for (i = 0; i < num_ev; i++)
479         {
480           conn = &ssm->conn_pool[ssm->wait_events[i].data.u32];
481           if (ssm->wait_events[i].events & (EPOLLHUP | EPOLLRDHUP))
482             {
483               vppcom_session_close (conn->fd);
484               continue;
485             }
486           if (ssm->wait_events[i].data.u32 == ~0)
487             {
488               new_client ();
489               continue;
490             }
491           client_fd = conn->fd;
492
493           if (EPOLLIN & ssm->wait_events[i].events)
494             {
495               rx_bytes = vcl_test_read (client_fd, conn->buf,
496                                         conn->buf_size, &conn->stats);
497               if (rx_bytes > 0)
498                 {
499                   rx_cfg = (sock_test_cfg_t *) conn->buf;
500                   if (rx_cfg->magic == SOCK_TEST_CFG_CTRL_MAGIC)
501                     {
502                       if (rx_cfg->verbose)
503                         {
504                           printf ("SERVER (fd %d): Received a cfg message!\n",
505                                   client_fd);
506                           sock_test_cfg_dump (rx_cfg, 0 /* is_client */ );
507                         }
508
509                       if (rx_bytes != sizeof (*rx_cfg))
510                         {
511                           printf ("SERVER (fd %d): Invalid cfg message "
512                                   "size (%d)!\n  Should be %lu bytes.\n",
513                                   client_fd, rx_bytes, sizeof (*rx_cfg));
514                           conn->cfg.rxbuf_size = 0;
515                           conn->cfg.num_writes = 0;
516                           if (conn->cfg.verbose)
517                             {
518                               printf ("SERVER (fd %d): Replying to "
519                                       "cfg message!\n", client_fd);
520                               sock_test_cfg_dump (rx_cfg, 0 /* is_client */ );
521                             }
522                           vcl_test_write (client_fd, (uint8_t *) & conn->cfg,
523                                           sizeof (conn->cfg), NULL,
524                                           conn->cfg.verbose);
525                           continue;
526                         }
527
528                       switch (rx_cfg->test)
529                         {
530                         case SOCK_TEST_TYPE_NONE:
531                         case SOCK_TEST_TYPE_ECHO:
532                           sync_config_and_reply (conn, rx_cfg);
533                           break;
534
535                         case SOCK_TEST_TYPE_BI:
536                         case SOCK_TEST_TYPE_UNI:
537                           stream_test_server_start_stop (conn, rx_cfg);
538                           break;
539
540                         case SOCK_TEST_TYPE_EXIT:
541                           printf ("SERVER: Have a great day, "
542                                   "connection %d!\n", client_fd);
543                           vppcom_session_close (client_fd);
544                           conn_pool_free (conn);
545                           printf ("SERVER: Closed client fd %d\n", client_fd);
546                           ssm->nfds--;
547                           if (!ssm->nfds)
548                             {
549                               printf ("SERVER: All client connections "
550                                       "closed.\n\nSERVER: "
551                                       "May the force be with you!\n\n");
552                               goto done;
553                             }
554                           break;
555
556                         default:
557                           fprintf (stderr,
558                                    "SERVER: ERROR: Unknown test type!\n");
559                           sock_test_cfg_dump (rx_cfg, 0 /* is_client */ );
560                           break;
561                         }
562                       continue;
563                     }
564
565                   else if ((conn->cfg.test == SOCK_TEST_TYPE_UNI) ||
566                            (conn->cfg.test == SOCK_TEST_TYPE_BI))
567                     {
568                       stream_test_server (conn, rx_bytes);
569                       continue;
570                     }
571
572                   else if (isascii (conn->buf[0]))
573                     {
574                       /* If it looks vaguely like a string,
575                        * make sure it's terminated.
576                        */
577                       ((char *) conn->buf)[rx_bytes <
578                                            conn->buf_size ? rx_bytes :
579                                            conn->buf_size - 1] = 0;
580                       printf ("SERVER (fd %d): RX (%d bytes) - '%s'\n",
581                               conn->fd, rx_bytes, conn->buf);
582                     }
583                 }
584               else              // rx_bytes < 0
585                 {
586                   if (errno == ECONNRESET)
587                     {
588                       printf ("\nSERVER: Connection reset by remote peer.\n"
589                               "  Y'all have a great day now!\n\n");
590                       break;
591                     }
592                   else
593                     continue;
594                 }
595
596               if (isascii (conn->buf[0]))
597                 {
598                   /* If it looks vaguely like a string,
599                    * make sure it's terminated
600                    */
601                   ((char *) conn->buf)[rx_bytes <
602                                        conn->buf_size ? rx_bytes :
603                                        conn->buf_size - 1] = 0;
604                   if (xtra)
605                     fprintf (stderr, "SERVER: ERROR: "
606                              "FIFO not drained in previous test!\n"
607                              "       extra chunks %u (0x%x)\n"
608                              "        extra bytes %lu (0x%lx)\n",
609                              xtra, xtra, xtra_bytes, xtra_bytes);
610
611                   xtra = 0;
612                   xtra_bytes = 0;
613
614                   if (conn->cfg.verbose)
615                     printf ("SERVER (fd %d): Echoing back\n", client_fd);
616
617                   nbytes = strlen ((const char *) conn->buf) + 1;
618
619                   tx_bytes = vcl_test_write (client_fd, conn->buf,
620                                              nbytes, &conn->stats,
621                                              conn->cfg.verbose);
622                   if (tx_bytes >= 0)
623                     printf ("SERVER (fd %d): TX (%d bytes) - '%s'\n",
624                             conn->fd, tx_bytes, conn->buf);
625                 }
626
627               else              // Extraneous read data from non-echo tests???
628                 {
629                   xtra++;
630                   xtra_bytes += rx_bytes;
631                 }
632             }
633         }
634     }
635
636 done:
637   vppcom_session_close (ssm->listen_fd);
638   vppcom_app_destroy ();
639
640   if (ssm->conn_pool)
641     free (ssm->conn_pool);
642
643   return main_rv;
644 }
645
646 /*
647  * fd.io coding-style-patch-verification: ON
648  *
649  * Local Variables:
650  * eval: (c-set-style "gnu")
651  * End:
652  */