67adc854f8479221496cb9aae43e91ca33c9b6af
[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_h2ip        -- look up host name and return an ip address
26                                         uta_lookup_rtg  -- looks in env for rtg host:port
27                                         uta_has_str     -- searches buffer of tokens for a string
28
29                                         uta_link2       -- establish a nanomsg connection to a host
30
31         Author:         E. Scott Daniels
32         Date:           30 November 2018
33 */
34
35 #ifndef _tools_static_c
36 #define _tools_static_c
37
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <netdb.h>
41 #include <errno.h>
42 #include <string.h>
43 #include <errno.h>
44 #include <pthread.h>
45 #include <ctype.h>
46
47 #include <sys/types.h>          // these are needed to suss out ip addresses from interfaces
48 #include <ifaddrs.h>
49 #include <arpa/inet.h>
50 #include <sys/socket.h>
51 #include <netdb.h>
52
53
54 /*
55         Simple tokeniser. Split a null terminated string into tokens recording the
56         pointers in the tokens array provided.  Tokens MUST be large enough. Max is
57         the max number of tokens to split into.  Returns the actual number of tokens
58         recorded in the pointer array.
59
60         CAUTION: this modifies the string passed in!!
61 */
62 static int uta_tokenise( char* buf, char** tokens, int max, char sep ) {
63         char* end;                                      // end of token
64         int     n = 0;
65
66         if( !buf || ! tokens || !(*buf) ) {
67                 return 0;
68         }
69
70         tokens[n++] = buf;
71         end = buf;
72         while( n < max && *end && (end = strchr( end, sep )) != NULL ) {
73                 *end = 0;
74                 tokens[n++] = ++end;
75         }
76
77         return n;
78 }
79
80 /*
81         Xlate hostname (expected to be name:port) to an IP address that nano will tolerate.
82         We'll use the first address from the list to keep it simple. If the first character
83         of the name is a digit, we assume it's really an IP address and just return that.
84
85         Return is a string which the caller must free. Even if the string passed in is already
86         an IP address, a duplicate will be returend so that it can always be freed.
87         On error a nil pointer is returned.
88 */
89 static char* uta_h2ip( char const* hname ) {
90         char                    buf[120];
91         struct hostent* hent;
92         unsigned int    octs[4];
93         unsigned int    a;
94         int                             i;
95         char*                   tok;
96         char*                   dname;          // duplicated name for distruction
97
98         dname = strdup( hname );
99
100         if( isdigit( *dname ) || *dname == '[' ) {              // hostnames can't start with digit, or ipv6 [; assume ip address
101                 return dname;
102         }
103
104         if( (tok = strchr( dname, ':' )) != NULL ) {
105                 *(tok++) = 0;
106         }
107
108         hent = gethostbyname( dname );
109         if( hent == NULL || hent->h_addr_list == NULL ) {
110                 //fprintf( stderr, "[WARN] h2ip: dns lookup failed for: %s\n", dname );
111                 free( dname );
112                 return NULL;
113         }
114
115         a = ntohl( *((unsigned int *)hent->h_addr_list[0]) );
116         for( i = 3; i >= 0; i-- ) {
117                 octs[i] = a & 0xff;
118                 a = a >> 8;
119         }
120
121         if( tok ) {                                                     // if :port was given, smash it back on
122                 snprintf( buf, sizeof( buf ), "%d.%d.%d.%d:%s", octs[0], octs[1], octs[2], octs[3], tok );
123         } else {
124                 snprintf( buf, sizeof( buf ), "%d.%d.%d.%d", octs[0], octs[1], octs[2], octs[3] );
125         }
126
127         free( dname );
128         return strdup( buf );
129 }
130
131
132 /*
133         Looks for the environment variable RMR_RTG_SVC which we assume to be name[:port], and
134         does a dns lookup on the name. If the env does not have such a variable, we default to
135         "rtg" and a port of 5656.
136
137         Returns true (1) if lookup found something;
138
139         CAUTION:  this is ONLY used if the RTG is a pub and we are using pub/sub to get updates.
140                         There are issues with some underlying transport pub/sub implementations so this
141                         is likley NOT needed/used.
142 */
143 static int uta_lookup_rtg( uta_ctx_t* ctx ) {
144         char*   ev;                                     // pointer to the env value
145         char*   def_port = "5656";
146         char*   port = NULL;
147         char*   dstr = NULL;
148
149         if( ctx == NULL ) {
150                 return 0;
151         }
152
153
154         if( ctx->rtg_addr ) {
155                 free( ctx->rtg_addr );
156         }
157
158         if( (ev = getenv( "RMR_RTG_SVC" )) == NULL ) {
159                 ev = "rtg";
160                 port = def_port;
161         } else {
162                 dstr = strdup( ev );                    // copy so we can trash it
163                 if( (port = strchr( dstr, ':' )) == NULL ) {
164                         port = def_port;
165                 } else {
166                         *port = 0;
167                         port++;                                         // point at the first digit
168                 }
169                 ev = dstr;                                              // all references below assume ev
170         }
171
172         ctx->rtg_addr = uta_h2ip( ev );         // convert name to IP addr
173         ctx->rtg_port = atoi( port );
174         if( dstr ) {
175                 free( dstr );
176         }
177
178         return ctx->rtg_addr != NULL;
179 }
180
181
182 /*
183         Expects a buffer of 'sep' separated tokens and looks to see if
184         the given string is one of those tokens. Returns the token
185         index (0 - n-1) if the string is found; -1 otherwise. The max
186         parameter supplies the maximum number of tokens to search in
187         the buffer.
188
189         On failure (-1) errno will be set in cases where memory cannot
190         be alocated (is this even possible any more?). If errno is 0
191         and failure is returned, then the caller should assume that
192         the token isn't in the list, or the list had no elements.
193 */
194 static int uta_has_str( char const* buf, char const* str, char sep, int max ) {
195         char*   dbuf;                   // duplicated buf so we can trash
196         char** tokens;                  // pointer to tokens from the string
197         int             ntokens;                // number of tokens buf split into
198         int             i;
199         int             rc;                             // return code
200
201         if( max < 2 ) {
202                 return -1;
203         }
204
205         dbuf = strdup( buf );
206         if( dbuf == NULL  ) {
207                 errno = ENOMEM;
208                 return -1;
209         }
210
211         if( (tokens = (char **) malloc( sizeof( char * ) * max )) == NULL ) {
212                 errno = ENOMEM;
213                 free( dbuf );
214                 return -1;
215         }
216
217         ntokens = uta_tokenise( dbuf, tokens, max, sep );
218         errno = 0;
219         rc = -1;
220         for( i = 0; rc < 0 && i < ntokens; i++ ) {
221                 if( tokens[i] ) {
222                         if( strcmp( tokens[i], str ) == 0 ) {
223                                 rc = i;
224                         }
225                 }
226         }
227
228         free( dbuf );
229         free( tokens );
230         return rc;
231 }
232
233 /*
234         Generate a list of all IP address associated with the interfaces available.
235         For now we capture them all, but we may need to limit. The port is smashed
236         onto each IP we find so that we can do a direct compare against the addr
237         that could be in the route table.
238
239         If the environment variable which limits the binding of our listen port
240         to a single interface (ENV_BIND_IF) then ONLY that interface/address is added
241         to the list so that we don't pick up entries from the rtable that are for other
242         processes listening on different interfaces.
243
244         The ENV_BIN_IF environment variable may be either an IP address (v6 must be in 
245         square braces), or an interface name (e.g. eth0).
246 */
247 if_addrs_t*  mk_ip_list( char* port ) {
248         if_addrs_t* l;
249         struct  ifaddrs *ifs;           // pointer to head
250         struct  ifaddrs *ele;           // pointer into the list
251         char    octs[NI_MAXHOST+1];
252         char    wbuf[NI_MAXHOST+128];
253         char*   fmt;
254         char*   envp;                           // at the environment var if there
255         char*   target_if = NULL;       // target interface supplied by ENV_BIND_IF
256         char*   tok;
257
258
259         if( (l = (if_addrs_t *) malloc( sizeof( if_addrs_t ) )) == NULL ) {
260                 return NULL;
261         }
262         memset( l, 0, sizeof( if_addrs_t ) );
263         l->addrs = (char **) malloc( sizeof( char* ) * 128 );
264         if( l->addrs == NULL ) {
265                 free( l );
266                 return NULL;
267         }
268
269         if( (envp = getenv( ENV_BIND_IF )) != NULL ) {
270                 if( isdigit( *envp ) || *envp == '[' ) {                                        // ip address given and not device name
271                         snprintf( wbuf, sizeof( wbuf ), "%s:%s", envp, port );          // smash port onto the addr as is
272                         l->addrs[l->naddrs] = strdup( wbuf );
273                         l->naddrs++;
274                         if( DEBUG ) fprintf( stderr, "[INFO] rmr: using only specific bind interface when searching specific RT entries: %s\n", wbuf );
275                         return l;
276                 }
277
278                 target_if = envp;               // device name given, suss it out below
279         }
280
281         getifaddrs( &ifs );
282         for( ele = ifs; ele; ele = ele->ifa_next ) {
283                 memset( octs, 0, sizeof( octs ) );
284                 if( ele && strcmp( ele->ifa_name, "lo" ) &&                                                                     // do NOT capture the loopback interface address
285                         (target_if == NULL || strcmp( ele->ifa_name, target_if ) == 0 ) ) {             // no target, or matches ENV_BIND_IF target
286
287                         if( ele->ifa_addr->sa_family == AF_INET ) {
288                                 getnameinfo( ele->ifa_addr, sizeof( struct sockaddr_in ),  octs, NI_MAXHOST, NULL, 0, NI_NUMERICHOST );
289                                 fmt = "%s:%s";
290                         } else {
291                                 if( ele->ifa_addr->sa_family == AF_INET6 ) {
292                                         getnameinfo( ele->ifa_addr, sizeof( struct sockaddr_in6 ),  octs, NI_MAXHOST, NULL, 0, NI_NUMERICHOST );
293                                         fmt = "[%s]:%s";
294                                 }
295                         }
296
297                         if( *octs ) {
298                                 if( (tok = strchr( octs, '%' )) != NULL ) {                     // for unknown reasons some ip6 addrs have %if-name appended; truncate
299                                         *tok = 0;
300                                 }
301                                 if( l->naddrs < 128 ) {
302                                         if( DEBUG ) fprintf( stderr, "[DBUG] capture address: %s: %s\n", ele->ifa_name, octs );
303
304                                         snprintf( wbuf, sizeof( wbuf ), fmt, octs, port );              // smash port onto the addr
305                                         l->addrs[l->naddrs] = strdup( wbuf );
306                                         l->naddrs++;
307                                 }
308                         }
309                 }
310         }
311
312         if( ifs ) {
313                 freeifaddrs( ifs );
314         }
315
316         return l;
317 }
318
319 /*
320         Check the address:port passed in and return true if it matches
321         one of the addresses we saw when we built the list. Right now
322         this isn't a speed intensive part of our processing, so we just
323         do a straight search through the list. We don't expect this to
324         ever be a higly driven functions so not bothering to optimise.
325 */
326 int is_this_myip( if_addrs_t* l, char* addr ) {
327         int i;
328
329         if( l == NULL ) {
330                 return 0;
331         }
332
333         for( i = 0; i < l->naddrs; i++ ) {
334                 if( strcmp( addr, l->addrs[i] ) == 0 ) {
335                         return 1;
336                 }
337         }
338
339         return 0;
340 }
341
342 /*
343         Expects a buffer containing "sep" separated tokens, and a list of
344         IP addresses anchored by ip_list.  Searches the tokens to see if
345         any are an ip address:port which is in the ip list.  Returns true
346         (1) if a token is in the list, false otherwise.
347 */
348 static int has_myip( char const* buf, if_addrs_t* list, char sep, int max ) {
349         char*   dbuf;                   // duplicated buf so we can trash
350         char** tokens;                  // pointer to tokens from the string
351         int             ntokens;                // number of tokens buf split into
352         int             i;
353         int             rc = 0;                 // return code
354
355         if( max < 2 ) {
356                 return 0;
357         }
358
359         if( buf == NULL ) {
360                 return 0;
361         }
362
363         if( list == NULL ) {
364                 return 0;
365         }
366
367
368         dbuf = strdup( buf );                   // get a copy we can mess with
369         if( dbuf == NULL  ) {
370                 errno = ENOMEM;
371                 return 0;
372         }
373
374         if( (tokens = (char **) malloc( sizeof( char * ) * max )) == NULL ) {
375                 errno = ENOMEM;
376                 free( dbuf );
377                 return 0;
378         }
379
380         ntokens = uta_tokenise( dbuf, tokens, max, sep );
381         errno = 0;
382         rc = 0;
383         for( i = 0; ! rc  && i < ntokens; i++ ) {
384                 if( tokens[i] ) {
385                         if( is_this_myip( list, tokens[i] ) ) {
386                                 rc = 1;
387                                 break;
388                         }
389                 }
390         }
391
392         free( dbuf );
393         free( tokens );
394         return rc;
395 }
396
397 /*
398         Given a list manager block, return the default IP address.
399         For now, that is just the first address on the list which
400         easily could be non-deterministic and change with each restart
401         of the application if a specific interface is not provided via
402         the environment variable (ENV_BIND_IF) and if there is more than
403         one device available on the container/physical host.
404 */
405 static char* get_default_ip( if_addrs_t* iplist ) {
406
407         if( iplist != NULL  &&  iplist->naddrs > 0  &&  iplist->addrs != NULL ) {
408                 return strdup( iplist->addrs[0] );
409         }
410
411         return NULL;
412 }
413
414 #endif