df49dd848722ec2aac5e67dc3f122e1f422392b8
[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 Nokia
5         Copyright (c) 2018-2019 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 int is_this_myip( if_addrs_t* l, char* addr );
59
60
61 // ----------------------------------------------------------------------------------
62
63 /*
64         Simple tokeniser. Split a null terminated string into tokens recording the
65         pointers in the tokens array provided.  Tokens MUST be large enough. Max is
66         the max number of tokens to split into.  Returns the actual number of tokens
67         recorded in the pointer array.
68
69         CAUTION: this modifies the string passed in!!
70 */
71 static int uta_tokenise( char* buf, char** tokens, int max, char sep ) {
72         char* end;                                      // end of token
73         int     n = 0;
74
75         if( !buf || ! tokens || !(*buf) ) {
76                 return 0;
77         }
78
79         tokens[n++] = buf;
80         end = buf;
81         while( n < max && *end && (end = strchr( end, sep )) != NULL ) {
82                 *end = 0;
83                 tokens[n++] = ++end;
84         }
85
86         return n;
87 }
88
89 /*
90         Tokenise and remove matches.
91         Given a buffer of 'sep' separated tokens, and a list of things,
92         return up to max tokens with any tokens that matched things in
93         the list. Toks is the user supplied array of char* which we will
94         fill in (up to max) with pointers to tokens from buf.  This
95         damages buf, so the caller must dup the string if it must be
96         preserved for later, original, use.  The pointers returned in
97         toks will reference portions of bufs.
98
99         Returns the number of tokens referenced by toks.
100 */
101 static int uta_rmip_tokenise( char* buf, if_addrs_t* iplist, char** toks, int max, char sep ) {
102         int     ntoks = 0;                      // total toks in the original buffer
103         int             pcount = 0;                     // count after prune
104         char**  all_toks;
105         int i;
106         int j;
107
108
109         all_toks = malloc( sizeof( char * ) * max );                                    // refernce to all tokens; we'll prune
110         pcount = ntoks = uta_tokenise( buf, all_toks, max, sep );               // split them up
111         j = 0;
112         if( ntoks > 0 ) {
113                 for( i = 0; i < ntoks; i++ ) {
114                         if( is_this_myip( iplist, all_toks[i] ) ) {
115                                 pcount--;                                                                       // ours, prune
116                         } else {
117                                 toks[j++] = all_toks[i];                                        // not one of ours, keep it
118                         }
119                 }
120         }
121
122         free( all_toks );
123         return pcount;
124 }
125
126 /*
127         Xlate hostname (expected to be name:port) to an IP address that nano will tolerate.
128         We'll use the first address from the list to keep it simple. If the first character
129         of the name is a digit, we assume it's really an IP address and just return that.
130
131         Return is a string which the caller must free. Even if the string passed in is already
132         an IP address, a duplicate will be returend so that it can always be freed.
133         On error a nil pointer is returned.
134 */
135 static char* uta_h2ip( char const* hname ) {
136         char                    buf[120];
137         struct hostent* hent;
138         unsigned int    octs[4];
139         unsigned int    a;
140         int                             i;
141         char*                   tok;
142         char*                   dname;          // duplicated name for distruction
143
144         dname = strdup( hname );
145
146         if( isdigit( *dname ) || *dname == '[' ) {              // hostnames can't start with digit, or ipv6 [; assume ip address
147                 return dname;
148         }
149
150         if( (tok = strchr( dname, ':' )) != NULL ) {
151                 *(tok++) = 0;
152         }
153
154         hent = gethostbyname( dname );
155         if( hent == NULL || hent->h_addr_list == NULL ) {
156                 //rmr_vlog( RMR_VL_WARN, "h2ip: dns lookup failed for: %s\n", dname );
157                 free( dname );
158                 return NULL;
159         }
160
161         a = ntohl( *((unsigned int *)hent->h_addr_list[0]) );
162         for( i = 3; i >= 0; i-- ) {
163                 octs[i] = a & 0xff;
164                 a = a >> 8;
165         }
166
167         if( tok ) {                                                     // if :port was given, smash it back on
168                 snprintf( buf, sizeof( buf ), "%d.%d.%d.%d:%s", octs[0], octs[1], octs[2], octs[3], tok );
169         } else {
170                 snprintf( buf, sizeof( buf ), "%d.%d.%d.%d", octs[0], octs[1], octs[2], octs[3] );
171         }
172
173         free( dname );
174         return strdup( buf );
175 }
176
177
178 /*
179         Looks for the environment variable RMR_RTG_SVC which we assume to be name[:port], and
180         does a dns lookup on the name. If the env does not have such a variable, we default to
181         "rtg" and a port of 5656.
182
183         Returns true (1) if lookup found something;
184
185         CAUTION:  this is ONLY used if the RTG is a pub and we are using pub/sub to get updates.
186                         There are issues with some underlying transport pub/sub implementations so this
187                         is likley NOT needed/used.
188 */
189 static int uta_lookup_rtg( uta_ctx_t* ctx ) {
190         char*   ev;                                     // pointer to the env value
191         char*   def_port = "5656";
192         char*   port = NULL;
193         char*   dstr = NULL;
194
195         if( ctx == NULL ) {
196                 return 0;
197         }
198
199
200         if( ctx->rtg_addr ) {
201                 free( ctx->rtg_addr );
202         }
203
204         if( (ev = getenv( "RMR_RTG_SVC" )) == NULL ) {
205                 ev = "rtg";
206                 port = def_port;
207         } else {
208                 dstr = strdup( ev );                    // copy so we can trash it
209                 if( (port = strchr( dstr, ':' )) == NULL ) {
210                         port = def_port;
211                 } else {
212                         *port = 0;
213                         port++;                                         // point at the first digit
214                 }
215                 ev = dstr;                                              // all references below assume ev
216         }
217
218         ctx->rtg_addr = uta_h2ip( ev );         // convert name to IP addr
219         ctx->rtg_port = atoi( port );
220         if( dstr ) {
221                 free( dstr );
222         }
223
224         return ctx->rtg_addr != NULL;
225 }
226
227
228 /*
229         Expects a buffer of 'sep' separated tokens and looks to see if
230         the given string is one of those tokens. Returns the token
231         index (0 - n-1) if the string is found; -1 otherwise. The max
232         parameter supplies the maximum number of tokens to search in
233         the buffer.
234
235         On failure (-1) errno will be set in cases where memory cannot
236         be alocated (is this even possible any more?). If errno is 0
237         and failure is returned, then the caller should assume that
238         the token isn't in the list, or the list had no elements.
239 */
240 static int uta_has_str( char const* buf, char const* str, char sep, int max ) {
241         char*   dbuf;                   // duplicated buf so we can trash
242         char** tokens;                  // pointer to tokens from the string
243         int             ntokens;                // number of tokens buf split into
244         int             i;
245         int             rc;                             // return code
246
247         if( max < 2 ) {
248                 return -1;
249         }
250
251         dbuf = strdup( buf );
252         if( dbuf == NULL  ) {
253                 errno = ENOMEM;
254                 return -1;
255         }
256
257         if( (tokens = (char **) malloc( sizeof( char * ) * max )) == NULL ) {
258                 errno = ENOMEM;
259                 free( dbuf );
260                 return -1;
261         }
262
263         ntokens = uta_tokenise( dbuf, tokens, max, sep );
264         errno = 0;
265         rc = -1;
266         for( i = 0; rc < 0 && i < ntokens; i++ ) {
267                 if( tokens[i] ) {
268                         if( strcmp( tokens[i], str ) == 0 ) {
269                                 rc = i;
270                         }
271                 }
272         }
273
274         free( dbuf );
275         free( tokens );
276         return rc;
277 }
278
279 /*
280         Generate a list of all IP address associated with the interfaces available.
281         For now we capture them all, but we may need to limit. The port is smashed
282         onto each IP we find so that we can do a direct compare against the addr
283         that could be in the route table.
284
285         If the environment variable which limits the binding of our listen port
286         to a single interface (ENV_BIND_IF) then ONLY that interface/address is added
287         to the list so that we don't pick up entries from the rtable that are for other
288         processes listening on different interfaces.
289
290         The ENV_BIN_IF environment variable may be either an IP address (v6 must be in
291         square braces), or an interface name (e.g. eth0).
292 */
293 if_addrs_t*  mk_ip_list( char* port ) {
294         if_addrs_t* l;
295         struct  ifaddrs *ifs;           // pointer to head
296         struct  ifaddrs *ele;           // pointer into the list
297         char    octs[NI_MAXHOST+1];
298         char    wbuf[NI_MAXHOST+128];
299         char*   fmt;
300         char*   envp;                           // at the environment var if there
301         char*   target_if = NULL;       // target interface supplied by ENV_BIND_IF
302         char*   tok;
303
304
305         if( (l = (if_addrs_t *) malloc( sizeof( if_addrs_t ) )) == NULL ) {
306                 return NULL;
307         }
308         memset( l, 0, sizeof( if_addrs_t ) );
309         l->addrs = (char **) malloc( sizeof( char* ) * 128 );
310         if( l->addrs == NULL ) {
311                 free( l );
312                 return NULL;
313         }
314
315         if( (envp = getenv( ENV_BIND_IF )) != NULL ) {
316                 if( isdigit( *envp ) || *envp == '[' ) {                                        // ip address given and not device name
317                         snprintf( wbuf, sizeof( wbuf ), "%s:%s", envp, port );          // smash port onto the addr as is
318                         l->addrs[l->naddrs] = strdup( wbuf );
319                         l->naddrs++;
320                         if( DEBUG ) rmr_vlog( RMR_VL_INFO, "rmr: using only specific bind interface when searching specific RT entries: %s\n", wbuf );
321                         return l;
322                 }
323
324                 target_if = envp;               // device name given, suss it out below
325         }
326
327         getifaddrs( &ifs );
328         for( ele = ifs; ele; ele = ele->ifa_next ) {
329                 memset( octs, 0, sizeof( octs ) );
330                 if( ele && strcmp( ele->ifa_name, "lo" ) &&                                                                     // do NOT capture the loopback interface address
331                         (target_if == NULL || strcmp( ele->ifa_name, target_if ) == 0 ) ) {             // no target, or matches ENV_BIND_IF target
332
333                         if( ele->ifa_addr != NULL ) {                                           // possible for some interfaces to not have an address
334                                 if( ele->ifa_addr->sa_family == AF_INET ) {
335                                         getnameinfo( ele->ifa_addr, sizeof( struct sockaddr_in ),  octs, NI_MAXHOST, NULL, 0, NI_NUMERICHOST );
336                                         fmt = "%s:%s";
337                                 } else {
338                                         if( ele->ifa_addr->sa_family == AF_INET6 ) {
339                                                 getnameinfo( ele->ifa_addr, sizeof( struct sockaddr_in6 ),  octs, NI_MAXHOST, NULL, 0, NI_NUMERICHOST );
340                                                 fmt = "[%s]:%s";
341                                         }
342                                 }
343                         }
344
345                         if( *octs ) {
346                                 if( (tok = strchr( octs, '%' )) != NULL ) {                     // for unknown reasons some ip6 addrs have %if-name appended; truncate
347                                         *tok = 0;
348                                 }
349                                 if( l->naddrs < 128 ) {
350                                         if( DEBUG ) rmr_vlog( RMR_VL_DEBUG, "capture address: %s: %s\n", ele->ifa_name, octs );
351
352                                         snprintf( wbuf, sizeof( wbuf ), fmt, octs, port );              // smash port onto the addr
353                                         l->addrs[l->naddrs] = strdup( wbuf );
354                                         l->naddrs++;
355                                 }
356                         }
357                 }
358         }
359
360         if( ifs ) {
361                 freeifaddrs( ifs );
362         }
363
364         return l;
365 }
366
367 /*
368         Check the address:port passed in and return true if it matches
369         one of the addresses we saw when we built the list. Right now
370         this isn't a speed intensive part of our processing, so we just
371         do a straight search through the list. We don't expect this to
372         ever be a higly driven functions so not bothering to optimise.
373 */
374 int is_this_myip( if_addrs_t* l, char* addr ) {
375         int i;
376
377         if( l == NULL ) {
378                 return 0;
379         }
380
381         if( addr == NULL ) {
382                 return 0;
383         }
384
385         for( i = 0; i < l->naddrs; i++ ) {
386                 if( l->addrs[i] != NULL  &&  strcmp( addr, l->addrs[i] ) == 0 ) {
387                         return 1;
388                 }
389         }
390
391         return 0;
392 }
393
394 /*
395         Expects a buffer containing "sep" separated tokens, and a list of
396         IP addresses anchored by ip_list.  Searches the tokens to see if
397         any are an ip address:port which is in the ip list.  Returns true
398         (1) if a token is in the list, false otherwise.
399 */
400 static int has_myip( char const* buf, if_addrs_t* list, char sep, int max ) {
401         char*   dbuf;                   // duplicated buf so we can trash
402         char** tokens;                  // pointer to tokens from the string
403         int             ntokens;                // number of tokens buf split into
404         int             i;
405         int             rc = 0;                 // return code
406
407         if( max < 2 ) {
408                 return 0;
409         }
410
411         if( buf == NULL ) {
412                 return 0;
413         }
414
415         if( list == NULL ) {
416                 return 0;
417         }
418
419
420         dbuf = strdup( buf );                   // get a copy we can mess with
421         if( dbuf == NULL  ) {
422                 errno = ENOMEM;
423                 return 0;
424         }
425
426         if( (tokens = (char **) malloc( sizeof( char * ) * max )) == NULL ) {
427                 errno = ENOMEM;
428                 free( dbuf );
429                 return 0;
430         }
431
432         ntokens = uta_tokenise( dbuf, tokens, max, sep );
433         errno = 0;
434         rc = 0;
435         for( i = 0; ! rc  && i < ntokens; i++ ) {
436                 if( tokens[i] ) {
437                         if( is_this_myip( list, tokens[i] ) ) {
438                                 rc = 1;
439                                 break;
440                         }
441                 }
442         }
443
444         free( dbuf );
445         free( tokens );
446         return rc;
447 }
448
449 /*
450         Given a list manager block, return the default IP address.
451         For now, that is just the first address on the list which
452         easily could be non-deterministic and change with each restart
453         of the application if a specific interface is not provided via
454         the environment variable (ENV_BIND_IF) and if there is more than
455         one device available on the container/physical host.
456 */
457 static char* get_default_ip( if_addrs_t* iplist ) {
458
459         if( iplist != NULL  &&  iplist->naddrs > 0  &&  iplist->addrs != NULL ) {
460                 return strdup( iplist->addrs[0] );
461         }
462
463         return NULL;
464 }
465
466 /*
467         Write all environment variables that we consider to be important to stderr.
468 */
469 static void uta_dump_env( ) {
470         char* token;
471         char* elist[] = {
472                         ENV_BIND_IF,
473                         ENV_RTG_PORT,
474                         ENV_RTG_ADDR,
475                         ENV_SEED_RT,
476                         ENV_SEED_MEMAP,
477                         ENV_RTG_RAW,
478                         ENV_VERBOSE_FILE,
479                         ENV_NAME_ONLY,
480                         ENV_WARNINGS,
481                         ENV_SRC_ID,
482                         ENV_LOG_HR,
483                         ENV_LOG_VLEVEL,
484                         ENV_CTL_PORT,
485                         ENV_RTREQ_FREA
486         };
487         int i;
488
489         for( i = 0; i < sizeof( elist ) / sizeof( char *); i ++ ) {
490                 token = getenv( elist[i] );
491                 if( token != NULL ) {
492                         rmr_vlog( RMR_VL_INFO, "dump_env: %s = '%s'\n", elist[i], token );
493                 } else {
494                         rmr_vlog( RMR_VL_INFO, "dump_env: %s = <unset>\n", elist[i] );
495                 }
496         }
497 }
498
499 #endif