52c60dc40ffc3f8f841bef9d53cf90bcd640aa44
[vpp.git] / src / uri / sock_test_server.c
1 /*
2  * Copyright (c) 2017 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 <uri/sock_test.h>
25
26 typedef struct
27 {
28   uint8_t is_alloc;
29   int fd;
30   uint8_t *buf;
31   uint32_t buf_size;
32   sock_test_cfg_t cfg;
33   sock_test_stats_t stats;
34 #ifdef VCL_TEST
35   vppcom_endpt_t endpt;
36   uint8_t ip[16];
37 #endif
38 } sock_server_conn_t;
39
40 #define SOCK_SERVER_MAX_TEST_CONN  10
41 typedef struct
42 {
43   int listen_fd;
44   size_t num_conn;
45   size_t conn_pool_size;
46   sock_server_conn_t *conn_pool;
47   int nfds;
48   fd_set rd_fdset;
49   fd_set wr_fdset;
50   struct timeval timeout;
51 } sock_server_main_t;
52
53 sock_server_main_t sock_server_main;
54
55 static inline int
56 get_nfds (void)
57 {
58   sock_server_main_t *ssm = &sock_server_main;
59   int i, nfds;
60
61   for (nfds = i = 0; i < FD_SETSIZE; i++)
62     {
63       if (FD_ISSET (i, &ssm->rd_fdset) || FD_ISSET (i, &ssm->wr_fdset))
64         nfds = i + 1;
65     }
66   return nfds;
67 }
68
69 static inline void
70 conn_fdset_set (sock_server_conn_t * conn, fd_set * fdset)
71 {
72   sock_server_main_t *ssm = &sock_server_main;
73
74   FD_SET (conn->fd, fdset);
75   ssm->nfds = get_nfds ();
76 }
77
78 static inline void
79 conn_fdset_clr (sock_server_conn_t * conn, fd_set * fdset)
80 {
81   sock_server_main_t *ssm = &sock_server_main;
82
83   FD_CLR (conn->fd, fdset);
84   ssm->nfds = get_nfds ();
85 }
86
87 static inline void
88 conn_pool_expand (size_t expand_size)
89 {
90   sock_server_main_t *ssm = &sock_server_main;
91   sock_server_conn_t *conn_pool;
92   size_t new_size = ssm->conn_pool_size + expand_size;
93   int i;
94
95   conn_pool = realloc (ssm->conn_pool, new_size * sizeof (*ssm->conn_pool));
96   if (conn_pool)
97     {
98       for (i = ssm->conn_pool_size; i < new_size; i++)
99         {
100           sock_server_conn_t *conn = &conn_pool[i];
101           memset (conn, 0, sizeof (*conn));
102           sock_test_cfg_init (&conn->cfg);
103           sock_test_buf_alloc (&conn->cfg, 1 /* is_rxbuf */ ,
104                                &conn->buf, &conn->buf_size);
105           conn->cfg.txbuf_size = conn->cfg.rxbuf_size;
106         }
107
108       ssm->conn_pool = conn_pool;
109       ssm->conn_pool_size = new_size;
110     }
111   else
112     {
113       int errno_val = errno;
114       perror ("ERROR in conn_pool_expand()");
115       fprintf (stderr, "ERROR: Memory allocation failed (errno = %d)!\n",
116                errno_val);
117     }
118 }
119
120 static inline sock_server_conn_t *
121 conn_pool_alloc (void)
122 {
123   sock_server_main_t *ssm = &sock_server_main;
124   int i;
125
126   for (i = 0; i < ssm->conn_pool_size; i++)
127     {
128       if (!ssm->conn_pool[i].is_alloc)
129         {
130 #ifdef VCL_TEST
131           ssm->conn_pool[i].endpt.ip = ssm->conn_pool[i].ip;
132 #endif
133           ssm->conn_pool[i].is_alloc = 1;
134           return (&ssm->conn_pool[i]);
135         }
136     }
137
138   return 0;
139 }
140
141 static inline void
142 conn_pool_free (sock_server_conn_t * conn)
143 {
144   sock_server_main_t *ssm = &sock_server_main;
145
146   conn_fdset_clr (conn, &ssm->rd_fdset);
147   conn_fdset_clr (conn, &ssm->wr_fdset);
148   conn->fd = 0;
149   conn->is_alloc = 0;
150 }
151
152 static inline void
153 sync_config_and_reply (sock_server_conn_t * conn, sock_test_cfg_t * rx_cfg)
154 {
155   conn->cfg = *rx_cfg;
156   sock_test_buf_alloc (&conn->cfg, 1 /* is_rxbuf */ ,
157                        &conn->buf, &conn->buf_size);
158   conn->cfg.txbuf_size = conn->cfg.rxbuf_size;
159
160   if (conn->cfg.verbose)
161     {
162       printf ("\nSERVER (fd %d): Replying to cfg message!\n", conn->fd);
163       sock_test_cfg_dump (&conn->cfg, 0 /* is_client */ );
164     }
165   (void) sock_test_write (conn->fd, (uint8_t *) & conn->cfg,
166                           sizeof (conn->cfg), NULL, conn->cfg.verbose);
167 }
168
169 static void
170 stream_test_server_start_stop (sock_server_conn_t * conn,
171                                sock_test_cfg_t * rx_cfg)
172 {
173   sock_server_main_t *ssm = &sock_server_main;
174   int client_fd = conn->fd;
175   sock_test_t test = rx_cfg->test;
176
177   if (rx_cfg->ctrl_handle == conn->fd)
178     {
179       int i;
180       clock_gettime (CLOCK_REALTIME, &conn->stats.stop);
181
182       for (i = 0; i < ssm->conn_pool_size; i++)
183         {
184           sock_server_conn_t *tc = &ssm->conn_pool[i];
185
186           if (tc->cfg.ctrl_handle == conn->fd)
187             {
188               sock_test_stats_accumulate (&conn->stats, &tc->stats);
189
190               if (conn->cfg.verbose)
191                 {
192                   static char buf[64];
193
194                   sprintf (buf, "SERVER (fd %d) RESULTS", tc->fd);
195                   sock_test_stats_dump (buf, &tc->stats, 1 /* show_rx */ ,
196                                         test == SOCK_TEST_TYPE_BI
197                                         /* show tx */ ,
198                                         conn->cfg.verbose);
199                 }
200             }
201         }
202
203       sock_test_stats_dump ("SERVER RESULTS", &conn->stats, 1 /* show_rx */ ,
204                             (test == SOCK_TEST_TYPE_BI) /* show_tx */ ,
205                             conn->cfg.verbose);
206       sock_test_cfg_dump (&conn->cfg, 0 /* is_client */ );
207       if (conn->cfg.verbose)
208         {
209           printf ("  sock server main\n"
210                   SOCK_TEST_SEPARATOR_STRING
211                   "       buf:  %p\n"
212                   "  buf size:  %u (0x%08x)\n"
213                   SOCK_TEST_SEPARATOR_STRING,
214                   conn->buf, conn->buf_size, conn->buf_size);
215         }
216
217       sync_config_and_reply (conn, rx_cfg);
218       printf ("\nSERVER (fd %d): %s-directional Stream Test Complete!\n"
219               SOCK_TEST_BANNER_STRING "\n", conn->fd,
220               test == SOCK_TEST_TYPE_BI ? "Bi" : "Uni");
221     }
222   else
223     {
224       printf ("\n" SOCK_TEST_BANNER_STRING
225               "SERVER (fd %d): %s-directional Stream Test!\n"
226               "  Sending client the test cfg to start streaming data...\n",
227               client_fd, test == SOCK_TEST_TYPE_BI ? "Bi" : "Uni");
228
229       rx_cfg->ctrl_handle = (rx_cfg->ctrl_handle == ~0) ? conn->fd :
230         rx_cfg->ctrl_handle;
231
232       sync_config_and_reply (conn, rx_cfg);
233
234       /* read the 1st chunk, record start time */
235       memset (&conn->stats, 0, sizeof (conn->stats));
236       clock_gettime (CLOCK_REALTIME, &conn->stats.start);
237     }
238 }
239
240
241 static inline void
242 stream_test_server (sock_server_conn_t * conn, int rx_bytes)
243 {
244   int client_fd = conn->fd;
245   sock_test_t test = conn->cfg.test;
246
247   if (test == SOCK_TEST_TYPE_BI)
248     (void) sock_test_write (client_fd, conn->buf, rx_bytes, &conn->stats,
249                             conn->cfg.verbose);
250
251   if (conn->stats.rx_bytes >= conn->cfg.total_bytes)
252     {
253       clock_gettime (CLOCK_REALTIME, &conn->stats.stop);
254     }
255 }
256
257 static inline void
258 new_client (void)
259 {
260   sock_server_main_t *ssm = &sock_server_main;
261   int client_fd;
262   sock_server_conn_t *conn;
263
264   if (ssm->conn_pool_size < (ssm->num_conn + SOCK_SERVER_MAX_TEST_CONN + 1))
265     conn_pool_expand (SOCK_SERVER_MAX_TEST_CONN + 1);
266
267   conn = conn_pool_alloc ();
268   if (!conn)
269     {
270       fprintf (stderr, "\nERROR: No free connections!\n");
271       return;
272     }
273
274 #ifdef VCL_TEST
275   client_fd = vppcom_session_accept (ssm->listen_fd, &conn->endpt,
276                                      -1.0 /* wait forever */ );
277 #else
278   client_fd = accept (ssm->listen_fd, (struct sockaddr *) NULL, NULL);
279 #endif
280   if (client_fd < 0)
281     {
282       int errno_val;
283       errno_val = errno;
284       perror ("ERROR in main()");
285       fprintf (stderr, "ERROR: accept failed (errno = %d)!\n", errno_val);
286     }
287
288   printf ("SERVER: Got a connection -- fd = %d (0x%08x)!\n",
289           client_fd, client_fd);
290
291   conn->fd = client_fd;
292   conn_fdset_set (conn, &ssm->rd_fdset);
293 }
294
295 int
296 main (int argc, char **argv)
297 {
298   sock_server_main_t *ssm = &sock_server_main;
299   int client_fd, rv, main_rv = 0;
300   int tx_bytes, rx_bytes, nbytes;
301   sock_server_conn_t *conn;
302   sock_test_cfg_t *rx_cfg;
303   uint32_t xtra = 0;
304   uint64_t xtra_bytes = 0;
305   struct sockaddr_in servaddr;
306   int errno_val;
307   int v, i;
308   uint16_t port = SOCK_TEST_SERVER_PORT;
309   fd_set _rfdset, *rfdset = &_rfdset;
310 #ifdef VCL_TEST
311   vppcom_endpt_t endpt;
312 #else
313   fd_set _wfdset, *wfdset = &_wfdset;
314 #endif
315
316   if ((argc == 2) && (sscanf (argv[1], "%d", &v) == 1))
317     port = (uint16_t) v;
318
319   conn_pool_expand (SOCK_SERVER_MAX_TEST_CONN + 1);
320
321 #ifdef VCL_TEST
322   rv = vppcom_app_create ("vcl_test_server");
323   if (rv)
324     {
325       errno = -rv;
326       ssm->listen_fd = -1;
327     }
328   else
329     {
330       ssm->listen_fd =
331         vppcom_session_create (VPPCOM_VRF_DEFAULT, VPPCOM_PROTO_TCP,
332                                0 /* is_nonblocking */ );
333     }
334 #else
335   ssm->listen_fd = socket (AF_INET, SOCK_STREAM, 0);
336 #endif
337   if (ssm->listen_fd < 0)
338     {
339       errno_val = errno;
340       perror ("ERROR in main()");
341       fprintf (stderr, "ERROR: socket() failed (errno = %d)!\n", errno_val);
342       return ssm->listen_fd;
343     }
344
345   memset (&servaddr, 0, sizeof (servaddr));
346
347   servaddr.sin_family = AF_INET;
348   servaddr.sin_addr.s_addr = htons (INADDR_ANY);
349   servaddr.sin_port = htons (port);
350
351 #ifdef VCL_TEST
352   endpt.vrf = VPPCOM_VRF_DEFAULT;
353   endpt.is_ip4 = (servaddr.sin_family == AF_INET);
354   endpt.ip = (uint8_t *) & servaddr.sin_addr;
355   endpt.port = (uint16_t) servaddr.sin_port;
356
357   rv = vppcom_session_bind (ssm->listen_fd, &endpt);
358   if (rv)
359     {
360       errno = -rv;
361       rv = -1;
362     }
363 #else
364   rv =
365     bind (ssm->listen_fd, (struct sockaddr *) &servaddr, sizeof (servaddr));
366 #endif
367   if (rv < 0)
368     {
369       errno_val = errno;
370       perror ("ERROR in main()");
371       fprintf (stderr, "ERROR: bind failed (errno = %d)!\n", errno_val);
372       return rv;
373     }
374
375 #ifdef VCL_TEST
376   rv = vppcom_session_listen (ssm->listen_fd, 10);
377   if (rv)
378     {
379       errno = -rv;
380       rv = -1;
381     }
382 #else
383   rv = listen (ssm->listen_fd, 10);
384 #endif
385   if (rv < 0)
386     {
387       errno_val = errno;
388       perror ("ERROR in main()");
389       fprintf (stderr, "ERROR: listen failed (errno = %d)!\n", errno_val);
390       return rv;
391     }
392
393   FD_ZERO (&ssm->wr_fdset);
394   FD_ZERO (&ssm->rd_fdset);
395
396   FD_SET (ssm->listen_fd, &ssm->rd_fdset);
397   ssm->nfds = ssm->listen_fd + 1;
398
399   printf ("\nSERVER: Waiting for a client to connect on port %d...\n", port);
400
401   while (1)
402     {
403       _rfdset = ssm->rd_fdset;
404
405 #ifdef VCL_TEST
406       rv = vppcom_select (ssm->nfds, (uint64_t *) rfdset, NULL, NULL, 0);
407 #else
408       {
409         struct timeval timeout;
410         timeout = ssm->timeout;
411         _wfdset = ssm->wr_fdset;
412         rv = select (ssm->nfds, rfdset, wfdset, NULL, &timeout);
413       }
414 #endif
415       if (rv < 0)
416         {
417           perror ("select()");
418           fprintf (stderr, "\nERROR: select() failed -- aborting!\n");
419           main_rv = -1;
420           goto done;
421         }
422       else if (rv == 0)
423         continue;
424
425       if (FD_ISSET (ssm->listen_fd, rfdset))
426         new_client ();
427
428       for (i = 0; i < ssm->conn_pool_size; i++)
429         {
430           if (!ssm->conn_pool[i].is_alloc)
431             continue;
432
433           conn = &ssm->conn_pool[i];
434           client_fd = conn->fd;
435
436           if (FD_ISSET (client_fd, rfdset))
437             {
438               rx_bytes = sock_test_read (client_fd, conn->buf,
439                                          conn->buf_size, &conn->stats);
440               if (rx_bytes > 0)
441                 {
442                   rx_cfg = (sock_test_cfg_t *) conn->buf;
443                   if (rx_cfg->magic == SOCK_TEST_CFG_CTRL_MAGIC)
444                     {
445                       if (rx_cfg->verbose)
446                         {
447                           printf ("SERVER (fd %d): Received a cfg message!\n",
448                                   client_fd);
449                           sock_test_cfg_dump (rx_cfg, 0 /* is_client */ );
450                         }
451
452                       if (rx_bytes != sizeof (*rx_cfg))
453                         {
454                           printf ("SERVER (fd %d): Invalid cfg message "
455                                   "size (%d)!\n  Should be %lu bytes.\n",
456                                   client_fd, rx_bytes, sizeof (*rx_cfg));
457                           conn->cfg.rxbuf_size = 0;
458                           conn->cfg.num_writes = 0;
459                           if (conn->cfg.verbose)
460                             {
461                               printf ("SERVER (fd %d): Replying to "
462                                       "cfg message!\n", client_fd);
463                               sock_test_cfg_dump (rx_cfg, 0 /* is_client */ );
464                             }
465                           sock_test_write (client_fd, (uint8_t *) & conn->cfg,
466                                            sizeof (conn->cfg), NULL,
467                                            conn->cfg.verbose);
468                           continue;
469                         }
470
471                       switch (rx_cfg->test)
472                         {
473                         case SOCK_TEST_TYPE_NONE:
474                         case SOCK_TEST_TYPE_ECHO:
475                           sync_config_and_reply (conn, rx_cfg);
476                           break;
477
478                         case SOCK_TEST_TYPE_BI:
479                         case SOCK_TEST_TYPE_UNI:
480                           stream_test_server_start_stop (conn, rx_cfg);
481                           break;
482
483                         case SOCK_TEST_TYPE_EXIT:
484                           printf ("SERVER: Have a great day, "
485                                   "connection %d!\n", client_fd);
486 #ifdef VCL_TEST
487                           vppcom_session_close (client_fd);
488 #else
489                           close (client_fd);
490 #endif
491                           conn_pool_free (conn);
492
493                           if (ssm->nfds == (ssm->listen_fd + 1))
494                             {
495                               printf ("SERVER: All client connections "
496                                       "closed.\n\nSERVER: "
497                                       "May the force be with you!\n\n");
498                               goto done;
499                             }
500                           break;
501
502                         default:
503                           fprintf (stderr, "ERROR: Unknown test type!\n");
504                           sock_test_cfg_dump (rx_cfg, 0 /* is_client */ );
505                           break;
506                         }
507                       continue;
508                     }
509
510                   else if ((conn->cfg.test == SOCK_TEST_TYPE_UNI) ||
511                            (conn->cfg.test == SOCK_TEST_TYPE_BI))
512                     {
513                       stream_test_server (conn, rx_bytes);
514                       continue;
515                     }
516
517                   else if (strlen ((char *) conn->buf))
518                     printf ("\nSERVER (fd %d): RX (%d bytes) - '%s'\n",
519                             conn->fd, rx_bytes, conn->buf);
520                 }
521               else              // rx_bytes < 0
522                 {
523                   if (errno == ECONNRESET)
524                     {
525                       printf ("\nSERVER: Connection reset by remote peer.\n"
526                               "  Y'all have a great day now!\n\n");
527                       break;
528                     }
529                   else
530                     continue;
531                 }
532
533               if (isascii (conn->buf[0]) && strlen ((const char *) conn->buf))
534                 {
535                   if (xtra)
536                     fprintf (stderr,
537                              "ERROR: FIFO not drained in previous test!\n"
538                              "       extra chunks %u (0x%x)\n"
539                              "        extra bytes %lu (0x%lx)\n",
540                              xtra, xtra, xtra_bytes, xtra_bytes);
541
542                   xtra = 0;
543                   xtra_bytes = 0;
544
545                   if (conn->cfg.verbose)
546                     printf ("SERVER (fd %d): Echoing back\n", client_fd);
547
548                   nbytes = strlen ((const char *) conn->buf) + 1;
549
550                   tx_bytes = sock_test_write (client_fd, conn->buf,
551                                               nbytes, &conn->stats,
552                                               conn->cfg.verbose);
553                   if (tx_bytes >= 0)
554                     printf ("SERVER (fd %d): TX (%d bytes) - '%s'\n",
555                             conn->fd, tx_bytes, conn->buf);
556                 }
557
558               else              // Extraneous read data from non-echo tests???
559                 {
560                   xtra++;
561                   xtra_bytes += rx_bytes;
562                 }
563             }
564         }
565     }
566
567 done:
568 #ifdef VCL_TEST
569   vppcom_session_close (ssm->listen_fd);
570   vppcom_app_destroy ();
571 #else
572   close (ssm->listen_fd);
573 #endif
574   if (ssm->conn_pool)
575     free (ssm->conn_pool);
576
577   return main_rv;
578 }
579
580 /*
581  * fd.io coding-style-patch-verification: ON
582  *
583  * Local Variables:
584  * eval: (c-set-style "gnu")
585  * End:
586  */