// :vi sw=4 ts=4 noet: /* ================================================================================== Copyright (c) 2019 Nokia Copyright (c) 2018-2019 AT&T Intellectual Property. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================================================== */ /* Mnemonic: rt_generic_static.c Abstract: These are route table functions which are not specific to the underlying protocol. rtable_static, and rtable_nng_static have transport provider specific code. This file must be included before the nng/nano specific file as it defines types. Author: E. Scott Daniels Date: 5 February 2019 */ #ifndef rt_generic_static_c #define rt_generic_static_c #include #include #include #include #include #include #include #include #include #include /* Passed to a symtab foreach callback to construct a list of pointers from a current symtab. */ typedef struct thing_list { int nalloc; int nused; void** things; } thing_list_t; /* Given a message type create a route table entry and add to the hash keyed on the message type. Once in the hash, endpoints can be added with uta_add_ep. Size is the number of group slots to allocate in the entry. */ static rtable_ent_t* uta_add_rte( route_table_t* rt, int mtype, int nrrgroups ) { rtable_ent_t* rte; if( rt == NULL ) { return NULL; } if( (rte = (rtable_ent_t *) malloc( sizeof( *rte ) )) == NULL ) { fprintf( stderr, "rmr_add_rte: malloc failed for entry\n" ); return NULL; } memset( rte, 0, sizeof( *rte ) ); if( nrrgroups <= 0 ) { nrrgroups = 10; } if( (rte->rrgroups = (rrgroup_t **) malloc( sizeof( rrgroup_t * ) * nrrgroups )) == NULL ) { fprintf( stderr, "rmr_add_rte: malloc failed for rrgroup array\n" ); free( rte ); return NULL; } memset( rte->rrgroups, 0, sizeof( rrgroup_t *) * nrrgroups ); rte->nrrgroups = nrrgroups; rmr_sym_map( rt->hash, mtype, rte ); // add to hash using numeric mtype as key if( DEBUG ) fprintf( stderr, "[DBUG] route table entry created: mt=%d groups=%d\n", mtype, nrrgroups ); return rte; } /* Parse a single record recevied from the route table generator, or read from a static route table file. Start records cause a new table to be started (if a partial table was received it is discarded. Table entry records are added to the currenly 'in progress' table, and an end record causes the in progress table to be finalised and the currently active table is replaced. */ static void parse_rt_rec( uta_ctx_t* ctx, char* buf, int vlevel ) { int i; int ntoks; // number of tokens found in something int ngtoks; int grp; // group number rtable_ent_t* rte; // route table entry added char* tokens[128]; char* gtokens[64]; // groups char* tok; // pointer into a token or string if( ! buf ) { return; } while( *buf && isspace( *buf ) ) { // skip leading whitespace buf++; } for( tok = buf + (strlen( buf ) - 1); tok > buf && isspace( *tok ); tok-- ); // trim trailing spaces too *(tok+1) = 0; if( (ntoks = uta_tokenise( buf, tokens, 128, '|' )) > 0 ) { switch( *(tokens[0]) ) { case 0: // ignore blanks // fallthrough case '#': // and comment lines break; case 'n': // newrt|{start|end} if( strcmp( tokens[1], "end" ) == 0 ) { // wrap up the table we were building if( ctx->new_rtable ) { uta_rt_drop( ctx->old_rtable ); // time to drop one that was previously replaced ctx->old_rtable = ctx->rtable; // currently active becomes old and allowed to 'drain' ctx->rtable = ctx->new_rtable; // one we've been adding to becomes active ctx->new_rtable = NULL; if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] end of route table noticed\n" ); } else { if( DEBUG > 1 ) fprintf( stderr, "[DBUG] end of route table noticed, but one was not started!\n" ); ctx->new_rtable = NULL; } } else { // start a new table. if( ctx->new_rtable != NULL ) { // one in progress? this forces it out if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] new table; dropping incomplete table\n" ); uta_rt_drop( ctx->new_rtable ); } if( ctx->rtable ) { ctx->new_rtable = uta_rt_clone( ctx->rtable ); // create by cloning endpoint entries from active table } else { ctx->new_rtable = uta_rt_init( ); // don't have one yet, just crate empty } if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] start of route table noticed\n" ); } break; case 'r': // assume rt entry if( ! ctx->new_rtable ) { // bad sequence, or malloc issue earlier; ignore siliently break; } if( ((tok = strchr( tokens[1], ',' )) == NULL ) || // no sender names (uta_has_str( tokens[1], ctx->my_name, ',', 127) >= 0) || // our name isn't in the list has_myip( tokens[1], ctx->ip_list, ',', 127 ) ) { // the list has one of our IP addresses if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] create rte for mtype=%s\n", tokens[1] ); if( (ngtoks = uta_tokenise( tokens[2], gtokens, 64, ';' )) > 0 ) { // split last field by groups first rte = uta_add_rte( ctx->new_rtable, atoi( tokens[1] ), ngtoks ); // get/create entry for message type for( grp = 0; grp < ngtoks; grp++ ) { if( (ntoks = uta_tokenise( gtokens[grp], tokens, 64, ',' )) > 0 ) { for( i = 0; i < ntoks; i++ ) { if( DEBUG > 1 || (vlevel > 1)) fprintf( stderr, "[DBUG] add endpoint %s\n", tokens[i] ); uta_add_ep( ctx->new_rtable, rte, tokens[i], grp ); } } } } } else { if( DEBUG || (vlevel > 2) ) fprintf( stderr, "entry not included, sender not matched: %s\n", tokens[1] ); } break; default: if( DEBUG ) fprintf( stderr, "[WRN] rmr_rtc: unrecognised request: %s\n", tokens[0] ); break; } } } /* This function attempts to open a static route table in order to create a 'seed' table during initialisation. The environment variable RMR_SEED_RT is expected to contain the necessary path to the file. If missing, or if the file is empty, no route table will be available until one is received from the generator. This function is probably most useful for testing situations, or extreme cases where the routes are static. */ static void read_static_rt( uta_ctx_t* ctx, int vlevel ) { int i; char* fname; char* fbuf; // buffer with file contents char* rec; // start of the record char* eor; // end of the record int rcount = 0; // record count for debug if( (fname = getenv( ENV_SEED_RT )) == NULL ) { return; } if( (fbuf = uta_fib( fname ) ) == NULL ) { // read file into a single buffer fprintf( stderr, "[WRN] seed route table could not be opened: %s: %s\n", fname, strerror( errno ) ); return; } if( DEBUG ) fprintf( stderr, "[INFO] seed route table successfully opened: %s\n", fname ); for( rec = fbuf; rec && *rec; rec = eor+1 ) { rcount++; if( (eor = strchr( rec, '\n' )) != NULL ) { *eor = 0; } parse_rt_rec( ctx, rec, vlevel ); } if( DEBUG ) fprintf( stderr, "[INFO] seed route table successfully parsed: %d records\n", rcount ); free( fbuf ); } /* Callback driven for each named thing in a symtab. We collect the pointers to those things for later use (cloning). */ static void collect_things( void* st, void* entry, char const* name, void* thing, void* vthing_list ) { thing_list_t* tl; if( (tl = (thing_list_t *) vthing_list) == NULL ) { return; } if( thing == NULL ) { return; } tl->things[tl->nused++] = thing; // save a reference to the thing } /* Called to delete a route table entry struct. We delete the array of endpoint pointers, but NOT the endpoints referenced as those are referenced from multiple entries. */ static void del_rte( void* st, void* entry, char const* name, void* thing, void* data ) { rtable_ent_t* rte; int i; if( (rte = (rtable_ent_t *) thing) == NULL ) { return; } if( rte->rrgroups ) { // clean up the round robin groups for( i = 0; i < rte->nrrgroups; i++ ) { if( rte->rrgroups[i] ) { free( rte->rrgroups[i]->epts ); // ditch list of endpoint pointers (end points are reused; don't trash them) } } free( rte->rrgroups ); } free( rte ); // finally, drop the potato } /* Read an entire file into a buffer. We assume for route table files they will be smallish and so this won't be a problem. Returns a pointer to the buffer, or nil. Caller must free. Terminates the buffer with a nil character for string processing. If we cannot stat the file, we assume it's empty or missing and return an empty buffer, as opposed to a nil, so the caller can generate defaults or error if an empty/missing file isn't tolerated. */ static char* uta_fib( char* fname ) { struct stat stats; off_t fsize = 8192; // size of the file off_t nread; // number of bytes read int fd; char* buf; // input buffer if( (fd = open( fname, O_RDONLY )) >= 0 ) { if( fstat( fd, &stats ) >= 0 ) { if( stats.st_size <= 0 ) { // empty file close( fd ); fd = -1; } else { fsize = stats.st_size; // stat ok, save the file size } } else { fsize = 8192; // stat failed, we'll leave the file open and try to read a default max of 8k } } if( fd < 0 ) { // didn't open or empty if( (buf = (char *) malloc( sizeof( char ) * 1 )) == NULL ) { return NULL; } *buf = 0; return buf; } // add a size limit check here if( (buf = (char *) malloc( sizeof( char ) * fsize + 2 )) == NULL ) { // enough to add nil char to make string close( fd ); errno = ENOMEM; return NULL; } nread = read( fd, buf, fsize ); if( nread < 0 || nread > fsize ) { // failure of some kind free( buf ); errno = EFBIG; // likely too much to handle close( fd ); return NULL; } buf[nread] = 0; close( fd ); return buf; } /* Create and initialise a route table; Returns a pointer to the table struct. */ static route_table_t* uta_rt_init( ) { route_table_t* rt; if( (rt = (route_table_t *) malloc( sizeof( route_table_t ) )) == NULL ) { return NULL; } if( (rt->hash = rmr_sym_alloc( 509 )) == NULL ) { // modest size, prime free( rt ); return NULL; } return rt; } /* Clone (sort of) an existing route table. This is done to preserve the endpoint names referenced in a table (and thus existing sessions) when a new set of message type to endpoint name mappings is received. A new route table with only endpoint name references is returned based on the active table in the context. */ static route_table_t* uta_rt_clone( route_table_t* srt ) { endpoint_t* ep; // an endpoint route_table_t* nrt; // new route table route_table_t* art; // active route table void* sst; // source symtab void* nst; // new symtab thing_list_t things; int i; if( srt == NULL ) { return NULL; } if( (nrt = (route_table_t *) malloc( sizeof( *nrt ) )) == NULL ) { return NULL; } if( (nrt->hash = rmr_sym_alloc( 509 )) == NULL ) { // modest size, prime free( nrt ); return NULL; } things.nalloc = 2048; things.nused = 0; things.things = (void **) malloc( sizeof( void * ) * things.nalloc ); if( things.things == NULL ) { free( nrt->hash ); free( nrt ); return NULL; } sst = srt->hash; // convenience pointers (src symtab) nst = nrt->hash; rmr_sym_foreach_class( sst, 1, collect_things, &things ); // collect the named endpoints in the active table for( i = 0; i < things.nused; i++ ) { ep = (endpoint_t *) things.things[i]; rmr_sym_put( nst, ep->name, 1, ep ); // slam this one into the new table } free( things.things ); return nrt; } /* Given a name, find the endpoint struct in the provided route table. */ static endpoint_t* uta_get_ep( route_table_t* rt, char const* ep_name ) { if( rt == NULL || rt->hash == NULL || ep_name == NULL || *ep_name == 0 ) { return NULL; } return rmr_sym_get( rt->hash, ep_name, 1 ); } /* Drop the given route table. Purge all type 0 entries, then drop the symtab itself. */ static void uta_rt_drop( route_table_t* rt ) { if( rt == NULL ) { return; } rmr_sym_foreach_class( rt->hash, 0, del_rte, NULL ); // free each rte referenced by the hash, but NOT the endpoints rmr_sym_free( rt->hash ); // free all of the hash related data free( rt ); } /* Look up and return the pointer to the endpoint stuct matching the given name. If not in the hash, a new endpoint is created, added to the hash. Should always return a pointer. */ static endpoint_t* rt_ensure_ep( route_table_t* rt, char const* ep_name ) { endpoint_t* ep; if( !rt || !ep_name || ! *ep_name ) { fprintf( stderr, "[WARN] rmr: rt_ensure: internal mishap, something undefined rt=%p ep_name=%p\n", rt, ep_name ); errno = EINVAL; return NULL; } if( (ep = uta_get_ep( rt, ep_name )) == NULL ) { // not there yet, make if( (ep = (endpoint_t *) malloc( sizeof( *ep ) )) == NULL ) { fprintf( stderr, "[WARN] rmr: rt_ensure: malloc failed for endpoint creation: %s\n", ep_name ); errno = ENOMEM; return NULL; } ep->open = 0; // not connected ep->addr = uta_h2ip( ep_name ); ep->name = strdup( ep_name ); rmr_sym_put( rt->hash, ep_name, 1, ep ); } return ep; } #endif