Add better support for manual app testing
[ric-plt/lib/rmr.git] / src / rmr / common / src / tools_static.c
1 // :vi sw=4 ts=4 noet:
2 /*
3 ==================================================================================
4         Copyright (c) 2019-2021 Nokia
5         Copyright (c) 2018-2021 AT&T Intellectual Property.
6
7    Licensed under the Apache License, Version 2.0 (the "License");
8    you may not use this file except in compliance with the License.
9    You may obtain a copy of the License at
10
11            http://www.apache.org/licenses/LICENSE-2.0
12
13    Unless required by applicable law or agreed to in writing, software
14    distributed under the License is distributed on an "AS IS" BASIS,
15    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16    See the License for the specific language governing permissions and
17    limitations under the License.
18 ==================================================================================
19 */
20
21 /*
22         Mnemonic:       tools_static.c
23         Abstract:       A small set of very simple tools to support Uta == RMR.
24                                         uta_tokenise -- simple string tokeniser
25                                         uta_rmip_tokenise -- tokenise and remove ip addresses from the list
26                                         uta_h2ip        -- look up host name and return an ip address
27                                         uta_lookup_rtg  -- looks in env for rtg host:port
28                                         uta_has_str     -- searches buffer of tokens for a string
29
30                                         uta_link2       -- establish a nanomsg connection to a host
31
32                                         uta_dump_env -- dump the environment variables to stdout that are
33                                                                 of importance to RMR.
34
35         Author:         E. Scott Daniels
36         Date:           30 November 2018
37 */
38
39 #ifndef _tools_static_c
40 #define _tools_static_c
41
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <netdb.h>
45 #include <errno.h>
46 #include <string.h>
47 #include <errno.h>
48 #include <pthread.h>
49 #include <ctype.h>
50
51 #include <sys/types.h>          // these are needed to suss out ip addresses from interfaces
52 #include <ifaddrs.h>
53 #include <arpa/inet.h>
54 #include <sys/socket.h>
55 #include <netdb.h>
56
57 // --- some protos needed for better organisation --------
58 static int is_this_myip( if_addrs_t* l, char* addr );
59
60
61 // ----------------------------------------------------------------------------------
62
63
64 /*
65         A strncpy() replacement that ensures the resulting dest buffer has
66         a zero (nil) terminator even if the source is longer than the dest
67         length.
68         A max of len-1 bytes are copied from src to dest. The copy stops when
69         a zero (nil) is encountered in the src, or len-1 bytes are copied.
70         The string is always nil terminated.
71         The string length is returned.
72
73         It is the responsiblity of the caller to ensure that dest is at
74         least len bytes in length.
75
76         If either src/dest is invalid (nil) a value of -1 is returned.
77 */
78 static inline int zt_buf_fill( char* dest, char const* src, int len ) {
79         char*           dp;
80         char const*     sp;
81         int                     n;              // num moved
82
83         if( dest == NULL || src == NULL ) {
84                 return -1;
85         }
86
87         dp = dest;
88         sp = src;
89         n = 0;
90         while(  *sp && n < len-1 ) {
91                 *(dp++) = *(sp++);
92                 n++;
93         }
94
95         *dp = 0;
96         return n;
97 }
98
99 /*
100         Simple tokeniser. Split a null terminated string into tokens recording the
101         pointers in the tokens array provided.  Tokens MUST be large enough. Max is
102         the max number of tokens to split into.  Returns the actual number of tokens
103         recorded in the pointer array.
104
105         CAUTION: this modifies the string passed in!!
106 */
107 static int uta_tokenise( char* buf, char** tokens, int max, char sep ) {
108         char* end;                                      // end of token
109         int     n = 0;
110
111         if( !buf || ! tokens || !(*buf) ) {
112                 return 0;
113         }
114
115         tokens[n++] = buf;
116         end = buf;
117         while( n < max && *end && (end = strchr( end, sep )) != NULL ) {
118                 *end = 0;
119                 tokens[n++] = ++end;
120         }
121
122         return n;
123 }
124
125 /*
126         Tokenise and remove matches.
127         Given a buffer of 'sep' separated tokens, and a list of things,
128         return up to max tokens with any tokens that matched things in
129         the list. Toks is the user supplied array of char* which we will
130         fill in (up to max) with pointers to tokens from buf.  This
131         damages buf, so the caller must dup the string if it must be
132         preserved for later, original, use.  The pointers returned in
133         toks will reference portions of bufs.
134
135         Returns the number of tokens referenced by toks.
136 */
137 static int uta_rmip_tokenise( char* buf, if_addrs_t* iplist, char** toks, int max, char sep ) {
138         int     ntoks = 0;                      // total toks in the original buffer
139         int             pcount = 0;                     // count after prune
140         char**  all_toks;
141         int i;
142         int j;
143
144
145         all_toks = malloc( sizeof( char * ) * max );                                    // refernce to all tokens; we'll prune
146         pcount = ntoks = uta_tokenise( buf, all_toks, max, sep );               // split them up
147         j = 0;
148         if( ntoks > 0 ) {
149                 for( i = 0; i < ntoks; i++ ) {
150                         if( is_this_myip( iplist, all_toks[i] ) ) {
151                                 pcount--;                                                                       // ours, prune
152                         } else {
153                                 toks[j++] = all_toks[i];                                        // not one of ours, keep it
154                         }
155                 }
156         }
157
158         free( all_toks );
159         return pcount;
160 }
161
162 /*
163         Xlate hostname (expected to be name:port) to an IP address that nano will tolerate.
164         We'll use the first address from the list to keep it simple. If the first character
165         of the name is a digit, we assume it's really an IP address and just return that.
166
167         Return is a string which the caller must free. Even if the string passed in is already
168         an IP address, a duplicate will be returend so that it can always be freed.
169         On error a nil pointer is returned.
170 */
171 static char* uta_h2ip( char const* hname ) {
172         char                    buf[120];
173         struct hostent* hent;
174         unsigned int    octs[4];
175         unsigned int    a;
176         int                             i;
177         char*                   tok;
178         char*                   dname;          // duplicated name for distruction
179
180         dname = strdup( hname );
181
182         if( isdigit( *dname ) || *dname == '[' ) {              // hostnames can't start with digit, or ipv6 [; assume ip address
183                 return dname;
184         }
185
186         if( (tok = strchr( dname, ':' )) != NULL ) {
187                 *(tok++) = 0;
188         }
189
190         hent = gethostbyname( dname );                  // valgrind will complain that this leaks, but we cannot free it!
191         if( hent == NULL || hent->h_addr_list == NULL ) {
192                 //rmr_vlog( RMR_VL_WARN, "h2ip: dns lookup failed for: %s\n", dname );
193                 free( dname );
194                 return NULL;
195         }
196
197         a = ntohl( *((unsigned int *)hent->h_addr_list[0]) );
198         for( i = 3; i >= 0; i-- ) {
199                 octs[i] = a & 0xff;
200                 a = a >> 8;
201         }
202
203         if( tok ) {                                                     // if :port was given, smash it back on
204                 snprintf( buf, sizeof( buf ), "%d.%d.%d.%d:%s", octs[0], octs[1], octs[2], octs[3], tok );
205         } else {
206                 snprintf( buf, sizeof( buf ), "%d.%d.%d.%d", octs[0], octs[1], octs[2], octs[3] );
207         }
208
209         free( dname );
210         return strdup( buf );
211 }
212
213
214 #ifdef RTG_PUB
215 /*
216         Looks for the environment variable RMR_RTG_SVC which we assume to be name[:port], and
217         does a dns lookup on the name. If the env does not have such a variable, we default to
218         "rtg" and a port of 5656.
219
220         Returns true (1) if lookup found something;
221
222         CAUTION:  this is ONLY used if the RTG is a pub and we are using pub/sub to get updates.
223                         There are issues with some underlying transport pub/sub implementations so this
224                         is likley NOT needed/used.
225 */
226 static int uta_lookup_rtg( uta_ctx_t* ctx ) {
227         char*   ev;                                     // pointer to the env value
228         char*   def_port = "5656";
229         char*   port = NULL;
230         char*   dstr = NULL;
231
232         if( ctx == NULL ) {
233                 return 0;
234         }
235
236
237         if( ctx->rtg_addr ) {
238                 free( ctx->rtg_addr );
239         }
240
241         if( (ev = getenv( "RMR_RTG_SVC" )) == NULL ) {
242                 ev = "rtg";
243                 port = def_port;
244         } else {
245                 dstr = strdup( ev );                    // copy so we can trash it
246                 if( (port = strchr( dstr, ':' )) == NULL ) {
247                         port = def_port;
248                 } else {
249                         *port = 0;
250                         port++;                                         // point at the first digit
251                 }
252                 ev = dstr;                                              // all references below assume ev
253         }
254
255         ctx->rtg_addr = uta_h2ip( ev );         // convert name to IP addr
256         ctx->rtg_port = atoi( port );
257         if( dstr ) {
258                 free( dstr );
259         }
260
261         return ctx->rtg_addr != NULL;
262 }
263 #endif
264
265
266 /*
267         Expects a buffer of 'sep' separated tokens and looks to see if
268         the given string is one of those tokens. Returns the token
269         index (0 - n-1) if the string is found; -1 otherwise. The max
270         parameter supplies the maximum number of tokens to search in
271         the buffer.
272
273         On failure (-1) errno will be set in cases where memory cannot
274         be alocated (is this even possible any more?). If errno is 0
275         and failure is returned, then the caller should assume that
276         the token isn't in the list, or the list had no elements.
277 */
278 static int uta_has_str( char const* buf, char const* str, char sep, int max ) {
279         char*   dbuf;                   // duplicated buf so we can trash
280         char** tokens;                  // pointer to tokens from the string
281         int             ntokens;                // number of tokens buf split into
282         int             i;
283         int             rc;                             // return code
284
285         if( max < 2 ) {
286                 return -1;
287         }
288
289         dbuf = strdup( buf );
290         if( dbuf == NULL  ) {
291                 errno = ENOMEM;
292                 return -1;
293         }
294
295         if( (tokens = (char **) malloc( sizeof( char * ) * max )) == NULL ) {
296                 errno = ENOMEM;
297                 free( dbuf );
298                 return -1;
299         }
300
301         ntokens = uta_tokenise( dbuf, tokens, max, sep );
302         errno = 0;
303         rc = -1;
304         for( i = 0; rc < 0 && i < ntokens; i++ ) {
305                 if( tokens[i] ) {
306                         if( strcmp( tokens[i], str ) == 0 ) {
307                                 rc = i;
308                         }
309                 }
310         }
311
312         free( dbuf );
313         free( tokens );
314         return rc;
315 }
316
317 /*
318         Generate a list of all IP address associated with the interfaces available.
319         For now we capture them all, but we may need to limit. The port is smashed
320         onto each IP we find so that we can do a direct compare against the addr
321         that could be in the route table.
322
323         If the environment variable which limits the binding of our listen port
324         to a single interface (ENV_BIND_IF) then ONLY that interface/address is added
325         to the list so that we don't pick up entries from the rtable that are for other
326         processes listening on different interfaces.
327
328         The ENV_BIN_IF environment variable may be either an IP address (v6 must be in
329         square braces), or an interface name (e.g. eth0).
330 */
331 static if_addrs_t*  mk_ip_list( char* port ) {
332         if_addrs_t* l;
333         struct  ifaddrs *ifs;           // pointer to head
334         struct  ifaddrs *ele;           // pointer into the list
335         char    octs[NI_MAXHOST+1];
336         char    wbuf[NI_MAXHOST+128];
337         char*   fmt = NULL;                     // address format (v4 or v6)
338         char*   envp;                           // at the environment var if there
339         char*   target_if = NULL;       // target interface supplied by ENV_BIND_IF
340         char*   tok;
341
342
343         if( (l = (if_addrs_t *) malloc( sizeof( if_addrs_t ) )) == NULL ) {
344                 return NULL;
345         }
346         memset( l, 0, sizeof( if_addrs_t ) );
347         l->addrs = (char **) malloc( sizeof( char* ) * 128 );
348         if( l->addrs == NULL ) {
349                 free( l );
350                 return NULL;
351         }
352
353         if( (envp = getenv( ENV_BIND_IF )) != NULL ) {
354                 if( isdigit( *envp ) || *envp == '[' ) {                                        // ip address given and not device name
355                         snprintf( wbuf, sizeof( wbuf ), "%s:%s", envp, port );          // smash port onto the addr as is
356                         l->addrs[l->naddrs] = strdup( wbuf );
357                         l->naddrs++;
358                         if( DEBUG ) rmr_vlog( RMR_VL_INFO, "rmr: using only specific bind interface when searching specific RT entries: %s\n", wbuf );
359                         return l;
360                 }
361
362                 target_if = envp;               // device name given, suss it out below
363         }
364
365         getifaddrs( &ifs );
366         for( ele = ifs; ele; ele = ele->ifa_next ) {
367                 memset( octs, 0, sizeof( octs ) );
368                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "checking interface: %s\n", ele->ifa_name );
369                 if( ele && strcmp( ele->ifa_name, "lo" ) &&                                                                     // do NOT capture the loopback interface address
370                         (target_if == NULL || strcmp( ele->ifa_name, target_if ) == 0 ) ) {             // no target, or matches ENV_BIND_IF target
371
372                         if( ele->ifa_addr != NULL ) {                                           // possible for some interfaces to not have an address
373                                 if( ele->ifa_addr->sa_family == AF_INET ) {
374                                         getnameinfo( ele->ifa_addr, sizeof( struct sockaddr_in ),  octs, NI_MAXHOST, NULL, 0, NI_NUMERICHOST );
375                                         fmt = "%s:%s";
376                                 } else {
377                                         if( ele->ifa_addr->sa_family == AF_INET6 ) {
378                                                 getnameinfo( ele->ifa_addr, sizeof( struct sockaddr_in6 ),  octs, NI_MAXHOST, NULL, 0, NI_NUMERICHOST );
379                                                 fmt = "[%s]:%s";
380                                         } else {
381                                                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "unrecognised IF family (not v4 or v6)\n" );
382                                                 continue;
383                                         }
384                                 }
385
386                                 if( *octs  && fmt != NULL ) {                           // possible that we didn't recognise the format (v4 or v6), don't try if we didn't
387                                         if( (tok = strchr( octs, '%' )) != NULL ) {                     // for unknown reasons some ip6 addrs have %if-name appended; truncate
388                                                 *tok = 0;
389                                         }
390                                         if( l->naddrs < 128 ) {
391                                                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "capture address: %s: %s\n", ele->ifa_name, octs );
392
393                                                 snprintf( wbuf, sizeof( wbuf ), fmt, octs, port );              // smash port onto the addr
394                                                 l->addrs[l->naddrs] = strdup( wbuf );
395                                                 l->naddrs++;
396                                         }
397                                 } else {
398                                         if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "unrecognised or no octets octs=%x fmt=%p\n", *octs, fmt );
399                                 }
400                         } else {
401                                 if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "no ip address for interface: %s\n", ele->ifa_name );
402                         }
403                 }
404         }
405
406         if( ifs ) {
407                 freeifaddrs( ifs );
408         }
409
410         return l;
411 }
412
413 /*
414         Check the address:port passed in and return true if it matches
415         one of the addresses we saw when we built the list. Right now
416         this isn't a speed intensive part of our processing, so we just
417         do a straight search through the list. We don't expect this to
418         ever be a higly driven functions so not bothering to optimise.
419 */
420 static int is_this_myip( if_addrs_t* l, char* addr ) {
421         int i;
422
423         if( l == NULL ) {
424                 return 0;
425         }
426
427         if( addr == NULL ) {
428                 return 0;
429         }
430
431         for( i = 0; i < l->naddrs; i++ ) {
432                 if( l->addrs[i] != NULL  &&  strcmp( addr, l->addrs[i] ) == 0 ) {
433                         return 1;
434                 }
435         }
436
437         return 0;
438 }
439
440 /*
441         Expects a buffer containing "sep" separated tokens, and a list of
442         IP addresses anchored by ip_list.  Searches the tokens to see if
443         any are an ip address:port which is in the ip list.  Returns true
444         (1) if a token is in the list, false otherwise.
445 */
446 static int has_myip( char const* buf, if_addrs_t* list, char sep, int max ) {
447         char*   dbuf;                   // duplicated buf so we can trash
448         char** tokens;                  // pointer to tokens from the string
449         int             ntokens;                // number of tokens buf split into
450         int             i;
451         int             rc = 0;                 // return code
452
453         if( max < 2 ) {
454                 return 0;
455         }
456
457         if( buf == NULL ) {
458                 return 0;
459         }
460
461         if( list == NULL ) {
462                 return 0;
463         }
464
465
466         dbuf = strdup( buf );                   // get a copy we can mess with
467         if( dbuf == NULL  ) {
468                 errno = ENOMEM;
469                 return 0;
470         }
471
472         if( (tokens = (char **) malloc( sizeof( char * ) * max )) == NULL ) {
473                 errno = ENOMEM;
474                 free( dbuf );
475                 return 0;
476         }
477
478         ntokens = uta_tokenise( dbuf, tokens, max, sep );
479         errno = 0;
480         rc = 0;
481         for( i = 0; ! rc  && i < ntokens; i++ ) {
482                 if( tokens[i] ) {
483                         if( is_this_myip( list, tokens[i] ) ) {
484                                 rc = 1;
485                                 break;
486                         }
487                 }
488         }
489
490         free( dbuf );
491         free( tokens );
492         return rc;
493 }
494
495 /*
496         Given a list manager block, return the default IP address.
497         For now, that is just the first address on the list which
498         easily could be non-deterministic and change with each restart
499         of the application if a specific interface is not provided via
500         the environment variable (ENV_BIND_IF) and if there is more than
501         one device available on the container/physical host.
502 */
503 static char* get_default_ip( if_addrs_t* iplist ) {
504
505         if( iplist != NULL  &&  iplist->naddrs > 0  &&  iplist->addrs != NULL ) {
506                 return strdup( iplist->addrs[0] );
507         }
508
509         return NULL;
510 }
511
512 /*
513         Write all environment variables that we consider to be important to stderr.
514 */
515 static void uta_dump_env( ) {
516         char* token;
517         char* elist[] = {
518                         ENV_BIND_IF,
519                         ENV_RTG_PORT,
520                         ENV_RTG_ADDR,
521                         ENV_SEED_RT,
522                         ENV_SEED_MEMAP,
523                         ENV_RTG_RAW,
524                         ENV_VERBOSE_FILE,
525                         ENV_NAME_ONLY,
526                         ENV_WARNINGS,
527                         ENV_SRC_ID,
528                         ENV_LOG_HR,
529                         ENV_LOG_VLEVEL,
530                         ENV_CTL_PORT,
531                         ENV_RTREQ_FREA
532         };
533         int i;
534
535         for( i = 0; i < sizeof( elist ) / sizeof( char *); i ++ ) {
536                 token = getenv( elist[i] );
537                 if( token != NULL ) {
538                         rmr_vlog( RMR_VL_INFO, "dump_env: %s = '%s'\n", elist[i], token );
539                 } else {
540                         rmr_vlog( RMR_VL_INFO, "dump_env: %s = <unset>\n", elist[i] );
541                 }
542         }
543 }
544
545 #endif