1 /*
2  ** fbinfomodel.c
3  ** IPFIX Information Model and IE storage management
4  **
5  ** ------------------------------------------------------------------------
6  ** Copyright (C) 2006-2019 Carnegie Mellon University. All Rights Reserved.
7  ** ------------------------------------------------------------------------
8  ** Authors: Brian Trammell
9  ** ------------------------------------------------------------------------
10  ** @OPENSOURCE_LICENSE_START@
11  ** libfixbuf 2.0
12  **
13  ** Copyright 2018-2019 Carnegie Mellon University. All Rights Reserved.
14  **
15  ** NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE
16  ** ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS"
17  ** BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND,
18  ** EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT
19  ** LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY,
20  ** EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE
21  ** MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF
22  ** ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR
23  ** COPYRIGHT INFRINGEMENT.
24  **
25  ** Released under a GNU-Lesser GPL 3.0-style license, please see
26  ** LICENSE.txt or contact permission@sei.cmu.edu for full terms.
27  **
28  ** [DISTRIBUTION STATEMENT A] This material has been approved for
29  ** public release and unlimited distribution.  Please see Copyright
30  ** notice for non-US Government use and distribution.
31  **
32  ** Carnegie Mellon(R) and CERT(R) are registered in the U.S. Patent
33  ** and Trademark Office by Carnegie Mellon University.
34  **
35  ** DM18-0325
36  ** @OPENSOURCE_LICENSE_END@
37  ** ------------------------------------------------------------------------
38  */
39 
40 #define _FIXBUF_SOURCE_
41 #include <fixbuf/private.h>
42 
43 
44 #define CERT_PEN 6871
45 
46 struct fbInfoModel_st {
47     GHashTable          *ie_table;
48     GHashTable          *ie_byname;
49     GStringChunk        *ie_names;
50     GStringChunk        *ie_desc;
51 };
52 
53 
54 #include "infomodel.h"
55 
56 
57 static fbInfoElementSpec_t ie_type_spec[] = {
58     {"privateEnterpriseNumber",         4, 0 },
59     {"informationElementId",            2, 0 },
60     {"informationElementDataType",      1, 0 },
61     {"informationElementSemantics",     1, 0 },
62     {"informationElementUnits",         2, 0 },
63     {"paddingOctets",                   6, 1 },
64     {"informationElementRangeBegin",    8, 0 },
65     {"informationElementRangeEnd",      8, 0 },
66     {"informationElementName",          FB_IE_VARLEN, 0 },
67     {"informationElementDescription",   FB_IE_VARLEN, 0 },
68     FB_IESPEC_NULL
69 };
70 
71 
fbInfoElementHash(fbInfoElement_t * ie)72 uint32_t            fbInfoElementHash(
73     fbInfoElement_t     *ie)
74 {
75     return ((ie->ent & 0x0000ffff) << 16) | (ie->num << 2) | (ie->midx << 4);
76 }
77 
fbInfoElementEqual(const fbInfoElement_t * a,const fbInfoElement_t * b)78 gboolean            fbInfoElementEqual(
79     const fbInfoElement_t   *a,
80     const fbInfoElement_t   *b)
81 {
82     return ((a->ent == b->ent) && (a->num == b->num) && (a->midx == b->midx));
83 }
84 
fbInfoElementDebug(gboolean tmpl,fbInfoElement_t * ie)85 void                fbInfoElementDebug(
86     gboolean            tmpl,
87     fbInfoElement_t     *ie)
88 {
89     if (ie->len == FB_IE_VARLEN) {
90         fprintf(stderr, "VL %02x %08x:%04x %2u (%s)\n",
91                     ie->flags, ie->ent, ie->num, ie->midx,
92                     tmpl ? ie->ref.canon->ref.name : ie->ref.name);
93     } else {
94         fprintf(stderr, "%2u %02x %08x:%04x %2u (%s)\n",
95                     ie->len, ie->flags, ie->ent, ie->num, ie->midx,
96                     tmpl ? ie->ref.canon->ref.name : ie->ref.name);
97     }
98 }
99 
fbInfoElementFree(fbInfoElement_t * ie)100 static void         fbInfoElementFree(
101     fbInfoElement_t     *ie)
102 {
103     g_slice_free(fbInfoElement_t, ie);
104 }
105 
fbInfoModelAlloc(void)106 fbInfoModel_t       *fbInfoModelAlloc(
107     void)
108 {
109     fbInfoModel_t       *model = NULL;
110 
111     /* Create an information model */
112     model = g_slice_new0(fbInfoModel_t);
113 
114     /* Allocate information element tables */
115     model->ie_table = g_hash_table_new_full(
116             (GHashFunc)fbInfoElementHash, (GEqualFunc)fbInfoElementEqual,
117             NULL, (GDestroyNotify)fbInfoElementFree);
118 
119     model->ie_byname = g_hash_table_new(g_str_hash, g_str_equal);
120 
121     /* Allocate information element name chunk */
122     model->ie_names = g_string_chunk_new(64);
123     model->ie_desc = g_string_chunk_new(128);
124 
125     /* Add elements to the information model */
126     infomodelAddGlobalElements(model);
127 
128     /* Return the new information model */
129     return model;
130 }
131 
fbInfoModelFree(fbInfoModel_t * model)132 void                fbInfoModelFree(
133     fbInfoModel_t       *model)
134 {
135     if (NULL == model) {
136         return;
137     }
138     g_hash_table_destroy(model->ie_byname);
139     g_string_chunk_free(model->ie_names);
140     g_string_chunk_free(model->ie_desc);
141     g_hash_table_destroy(model->ie_table);
142     g_slice_free(fbInfoModel_t, model);
143 }
144 
fbInfoModelReversifyName(const char * fwdname,char * revname,size_t revname_sz)145 static void         fbInfoModelReversifyName(
146     const char          *fwdname,
147     char                *revname,
148     size_t              revname_sz)
149  {
150     /* paranoid string copy */
151     strncpy(revname + FB_IE_REVERSE_STRLEN, fwdname, revname_sz - FB_IE_REVERSE_STRLEN - 1);
152     revname[revname_sz - 1] = (char)0;
153 
154     /* uppercase first char */
155     revname[FB_IE_REVERSE_STRLEN] = toupper(revname[FB_IE_REVERSE_STRLEN]);
156 
157     /* prepend reverse */
158     memcpy(revname, FB_IE_REVERSE_STR, FB_IE_REVERSE_STRLEN);
159 }
160 
161 #define FB_IE_REVERSE_BUFSZ 256
162 
fbInfoModelAddElement(fbInfoModel_t * model,fbInfoElement_t * ie)163 void                fbInfoModelAddElement(
164     fbInfoModel_t       *model,
165     fbInfoElement_t     *ie)
166 {
167     fbInfoElement_t     *model_ie = NULL;
168     fbInfoElement_t     *found;
169     char                revname[FB_IE_REVERSE_BUFSZ];
170 
171     g_assert(ie);
172 
173     /* Allocate a new information element */
174     model_ie = g_slice_new0(fbInfoElement_t);
175 
176     /* Copy external IE to model IE */
177 
178     model_ie->ref.name = g_string_chunk_insert(model->ie_names, ie->ref.name);
179     model_ie->midx = 0;
180     model_ie->ent = ie->ent;
181     model_ie->num = ie->num;
182     model_ie->len = ie->len;
183     model_ie->flags = ie->flags;
184     model_ie->min = ie->min;
185     model_ie->max = ie->max;
186     model_ie->type = ie->type;
187     if (ie->description) {
188         model_ie->description = g_string_chunk_insert(model->ie_desc,
189                                                       ie->description);
190     }
191 
192     /* Insert model IE into tables */
193     if ((found = g_hash_table_lookup(model->ie_table, model_ie))) {
194         /* use g_hash_table_replace() if the ent/num already exists.
195          * insert() will replace but it only replaces the value, not
196          * the key.  since insert() frees the value and the key and
197          * the value are the same - this creates a problem.  replace()
198          * replaces the key and the value. */
199 
200         /* since it is possible that 'found' has a different name than
201          * 'model_ie', we need to remove 'found' from the ie_byname
202          * table to avoid having a reference to freed memory.  it's
203          * also possible that 'found' is only in the ie_table. */
204         if (g_hash_table_lookup(model->ie_byname, found->ref.name) == found) {
205             g_hash_table_remove(model->ie_byname, found->ref.name);
206         }
207         g_hash_table_replace(model->ie_table, model_ie, model_ie);
208     } else {
209         g_hash_table_insert(model->ie_table, model_ie, model_ie);
210     }
211 
212     g_hash_table_insert(model->ie_byname, (char *)model_ie->ref.name,model_ie);
213 
214     /* Short circuit if not reversible */
215     if (!(ie->flags & FB_IE_F_REVERSIBLE)) {
216         return;
217     }
218 
219     /* Allocate a new reverse information element */
220     model_ie = g_slice_new0(fbInfoElement_t);
221 
222     /* Generate reverse name */
223     fbInfoModelReversifyName(ie->ref.name, revname, sizeof(revname));
224 
225     /* Copy external IE to reverse model IE */
226     model_ie->ref.name = g_string_chunk_insert(model->ie_names, revname);
227     model_ie->midx = 0;
228     model_ie->ent = ie->ent ? ie->ent : FB_IE_PEN_REVERSE;
229     model_ie->num = ie->ent ? ie->num | FB_IE_VENDOR_BIT_REVERSE : ie->num;
230     model_ie->len = ie->len;
231     model_ie->flags = ie->flags;
232     model_ie->min = ie->min;
233     model_ie->max = ie->max;
234     model_ie->type = ie->type;
235 
236     /* Insert model IE into tables */
237     if ((found = g_hash_table_lookup(model->ie_table, model_ie))) {
238         if (g_hash_table_lookup(model->ie_byname, found->ref.name) == found) {
239             g_hash_table_remove(model->ie_byname, found->ref.name);
240         }
241         g_hash_table_replace(model->ie_table, model_ie, model_ie);
242     } else {
243         g_hash_table_insert(model->ie_table, model_ie, model_ie);
244     }
245     g_hash_table_insert(model->ie_byname, (char *)model_ie->ref.name,model_ie);
246 }
247 
fbInfoModelAddElementArray(fbInfoModel_t * model,fbInfoElement_t * ie)248 void                fbInfoModelAddElementArray(
249     fbInfoModel_t       *model,
250     fbInfoElement_t     *ie)
251 {
252     g_assert(ie);
253     for (; ie->ref.name; ie++) fbInfoModelAddElement(model, ie);
254 }
255 
fbInfoModelGetElement(fbInfoModel_t * model,fbInfoElement_t * ex_ie)256 const fbInfoElement_t     *fbInfoModelGetElement(
257     fbInfoModel_t       *model,
258     fbInfoElement_t     *ex_ie)
259 {
260     return g_hash_table_lookup(model->ie_table, ex_ie);
261 }
262 
fbInfoElementCopyToTemplate(fbInfoModel_t * model,fbInfoElement_t * ex_ie,fbInfoElement_t * tmpl_ie)263 gboolean            fbInfoElementCopyToTemplate(
264     fbInfoModel_t       *model,
265     fbInfoElement_t     *ex_ie,
266     fbInfoElement_t     *tmpl_ie)
267 {
268     const fbInfoElement_t     *model_ie = NULL;
269 
270     /* Look up information element in the model */
271     model_ie = fbInfoModelGetElement(model, ex_ie);
272     if (!model_ie) {
273         /* Information element not in model. Note it's alien and add it. */
274         ex_ie->ref.name = g_string_chunk_insert(model->ie_names,
275                                                 "_alienInformationElement");
276         ex_ie->flags |= FB_IE_F_ALIEN;
277         fbInfoModelAddElement(model, ex_ie);
278         model_ie = fbInfoModelGetElement(model, ex_ie);
279         g_assert(model_ie);
280     }
281 
282     /* Refer to canonical IE in the model */
283     tmpl_ie->ref.canon = model_ie;
284 
285     /* Copy model IE to template IE */
286     tmpl_ie->midx = 0;
287     tmpl_ie->ent = model_ie->ent;
288     tmpl_ie->num = model_ie->num;
289     tmpl_ie->len = ex_ie->len;
290     tmpl_ie->flags = model_ie->flags;
291     tmpl_ie->type = model_ie->type;
292     tmpl_ie->min = model_ie->min;
293     tmpl_ie->max = model_ie->max;
294     tmpl_ie->description = model_ie->description;
295 
296     /* All done */
297     return TRUE;
298 }
299 
fbInfoModelGetElementByName(fbInfoModel_t * model,const char * name)300 const fbInfoElement_t     *fbInfoModelGetElementByName(
301     fbInfoModel_t       *model,
302     const char          *name)
303 {
304     g_assert(name);
305     return g_hash_table_lookup(model->ie_byname, name);
306 }
307 
fbInfoModelGetElementByID(fbInfoModel_t * model,uint16_t id,uint32_t ent)308 const fbInfoElement_t    *fbInfoModelGetElementByID(
309     fbInfoModel_t      *model,
310     uint16_t           id,
311     uint32_t           ent)
312 {
313     fbInfoElement_t tempElement;
314 
315     tempElement.midx = 0;
316     tempElement.ent = ent;
317     tempElement.num = id;
318 
319     return fbInfoModelGetElement(model, &tempElement);
320 }
321 
fbInfoElementCopyToTemplateByName(fbInfoModel_t * model,const char * name,uint16_t len_override,fbInfoElement_t * tmpl_ie)322 gboolean            fbInfoElementCopyToTemplateByName(
323     fbInfoModel_t       *model,
324     const char          *name,
325     uint16_t            len_override,
326     fbInfoElement_t     *tmpl_ie)
327 {
328     const fbInfoElement_t     *model_ie = NULL;
329 
330     /* Look up information element in the model */
331     model_ie = fbInfoModelGetElementByName(model, name);
332     if (!model_ie) return FALSE;
333 
334     /* Refer to canonical IE in the model */
335     tmpl_ie->ref.canon = model_ie;
336 
337     /* Copy model IE to template IE */
338     tmpl_ie->midx = 0;
339     tmpl_ie->ent = model_ie->ent;
340     tmpl_ie->num = model_ie->num;
341     tmpl_ie->len = len_override ? len_override : model_ie->len;
342     tmpl_ie->flags = model_ie->flags;
343     tmpl_ie->type = model_ie->type;
344     tmpl_ie->min = model_ie->min;
345     tmpl_ie->max = model_ie->max;
346     tmpl_ie->description = model_ie->description;
347 
348     /* All done */
349     return TRUE;
350 }
351 
fbInfoElementAllocTypeTemplate(fbInfoModel_t * model,GError ** err)352 fbTemplate_t *fbInfoElementAllocTypeTemplate(
353     fbInfoModel_t          *model,
354     GError                 **err)
355 {
356     return fbInfoElementAllocTypeTemplate2(model, TRUE, err);
357 }
358 
fbInfoElementAllocTypeTemplate2(fbInfoModel_t * model,gboolean internal,GError ** err)359 fbTemplate_t *fbInfoElementAllocTypeTemplate2(
360     fbInfoModel_t          *model,
361     gboolean                internal,
362     GError                 **err)
363 {
364     fbTemplate_t *tmpl;
365     uint32_t flags;
366 
367     flags = internal ? ~0 : 0;
368 
369     tmpl = fbTemplateAlloc(model);
370     if (!fbTemplateAppendSpecArray(tmpl, ie_type_spec, flags, err)) {
371         fbTemplateFreeUnused(tmpl);
372         return NULL;
373     }
374     fbTemplateSetOptionsScope(tmpl, 2);
375     return tmpl;
376 }
377 
fbInfoElementWriteOptionsRecord(fBuf_t * fbuf,const fbInfoElement_t * model_ie,uint16_t itid,uint16_t etid,GError ** err)378 gboolean fbInfoElementWriteOptionsRecord(
379     fBuf_t                  *fbuf,
380     const fbInfoElement_t   *model_ie,
381     uint16_t                itid,
382     uint16_t                etid,
383     GError                  **err)
384 {
385     fbInfoElementOptRec_t   rec;
386 
387     g_assert(model_ie);
388     if (model_ie == NULL) {
389         g_set_error(err, FB_ERROR_DOMAIN, FB_ERROR_NOELEMENT,
390                     "Invalid [NULL] Information Element");
391         return FALSE;
392     }
393 
394     rec.ie_range_begin = model_ie->min;
395     rec.ie_range_end = model_ie->max;
396     rec.ie_pen = model_ie->ent;
397     rec.ie_units = FB_IE_UNITS(model_ie->flags);
398     rec.ie_semantic = FB_IE_SEMANTIC(model_ie->flags);
399     rec.ie_id = model_ie->num;
400     rec.ie_type = model_ie->type;
401     memset(rec.padding, 0, sizeof(rec.padding));
402     rec.ie_name.buf = (uint8_t *)model_ie->ref.name;
403     rec.ie_name.len = strlen(model_ie->ref.name);
404     rec.ie_desc.buf = (uint8_t *)model_ie->description;
405     if (model_ie->description) {
406         rec.ie_desc.len = strlen(model_ie->description);
407     } else {
408         rec.ie_desc.len = 0;
409     }
410 
411     if (!fBufSetExportTemplate(fbuf, etid, err)) {
412         return FALSE;
413     }
414 
415     if (!fBufSetInternalTemplate(fbuf, itid, err)) {
416         return FALSE;
417     }
418 
419     if (!fBufAppend(fbuf, (uint8_t *)&rec, sizeof(rec), err)) {
420         return FALSE;
421     }
422 
423     return TRUE;
424 }
425 
fbInfoElementAddOptRecElement(fbInfoModel_t * model,fbInfoElementOptRec_t * rec)426 gboolean fbInfoElementAddOptRecElement(
427     fbInfoModel_t           *model,
428     fbInfoElementOptRec_t   *rec)
429 {
430     fbInfoElement_t     ie;
431     char                name[500];
432     char                description[4096];
433     size_t              len;
434 
435     if (rec->ie_pen != 0) {
436 
437         ie.min = rec->ie_range_begin;
438         ie.max = rec->ie_range_end;
439         ie.ent = rec->ie_pen;
440         ie.num = rec->ie_id;
441         ie.type = rec->ie_type;
442         len = ((rec->ie_name.len < sizeof(name))
443                ? rec->ie_name.len : (sizeof(name) - 1));
444         strncpy(name, (char *)rec->ie_name.buf, len);
445         name[len] = '\0';
446         ie.ref.name = name;
447         len = ((rec->ie_desc.len < sizeof(description))
448                ? rec->ie_desc.len : (sizeof(description) - 1));
449         strncpy(description, (char *)rec->ie_desc.buf, len);
450         description[len] = '\0';
451         ie.description = description;
452         ie.flags = 0;
453         ie.flags |= rec->ie_units << 16;
454         ie.flags |= rec->ie_semantic << 8;
455 
456         /* length is inferred from data type */
457         switch (ie.type) {
458           case FB_OCTET_ARRAY:
459           case FB_STRING:
460           case FB_BASIC_LIST:
461           case FB_SUB_TMPL_LIST:
462           case FB_SUB_TMPL_MULTI_LIST:
463             ie.len = FB_IE_VARLEN;
464             break;
465           case FB_UINT_8:
466           case FB_INT_8:
467           case FB_BOOL:
468             ie.len = 1;
469             break;
470           case FB_UINT_16:
471           case FB_INT_16:
472             ie.len = 2;
473             ie.flags |= FB_IE_F_ENDIAN;
474             break;
475           case FB_UINT_32:
476           case FB_INT_32:
477           case FB_DT_SEC:
478           case FB_FLOAT_32:
479           case FB_IP4_ADDR:
480             ie.len = 4;
481             ie.flags |= FB_IE_F_ENDIAN;
482             break;
483           case FB_MAC_ADDR:
484             ie.len = 6;
485             break;
486           case FB_UINT_64:
487           case FB_INT_64:
488           case FB_DT_MILSEC:
489           case FB_DT_MICROSEC:
490           case FB_DT_NANOSEC:
491           case FB_FLOAT_64:
492             ie.len = 8;
493             ie.flags |= FB_IE_F_ENDIAN;
494             break;
495           case FB_IP6_ADDR:
496             ie.len = 16;
497             break;
498           default:
499             g_warning("Adding element %s with invalid data type [%d]", name,
500                       rec->ie_type);
501             ie.len = FB_IE_VARLEN;
502         }
503 
504         fbInfoModelAddElement(model, &ie);
505         return TRUE;
506     }
507 
508     return FALSE;
509 }
510 
fbInfoModelTypeInfoRecord(fbTemplate_t * tmpl)511 gboolean fbInfoModelTypeInfoRecord(
512     fbTemplate_t            *tmpl)
513 {
514     /* ignore padding. */
515     if (fbTemplateContainsAllFlaggedElementsByName(tmpl, ie_type_spec, 0)) {
516         return TRUE;
517     }
518 
519     return FALSE;
520 }
521 
fbInfoModelCountElements(const fbInfoModel_t * model)522 guint fbInfoModelCountElements(
523     const fbInfoModel_t *model)
524 {
525     return g_hash_table_size(model->ie_table);
526 }
527 
fbInfoModelIterInit(fbInfoModelIter_t * iter,const fbInfoModel_t * model)528 void fbInfoModelIterInit(
529     fbInfoModelIter_t   *iter,
530     const fbInfoModel_t *model)
531 {
532     g_assert(iter);
533     g_hash_table_iter_init(iter, model->ie_table);
534 }
535 
fbInfoModelIterNext(fbInfoModelIter_t * iter)536 const fbInfoElement_t *fbInfoModelIterNext(
537     fbInfoModelIter_t *iter)
538 {
539     const fbInfoElement_t *ie;
540     g_assert(iter);
541     if (g_hash_table_iter_next(iter, NULL, (gpointer *)&ie)) {
542         return ie;
543     }
544     return NULL;
545 }
546 
fbInfoModelAddAlienElement(fbInfoModel_t * model,fbInfoElement_t * ex_ie)547 const fbInfoElement_t     *fbInfoModelAddAlienElement(
548     fbInfoModel_t       *model,
549     fbInfoElement_t     *ex_ie)
550 {
551     const fbInfoElement_t     *model_ie = NULL;
552 
553     if (ex_ie == NULL) {
554         return NULL;
555     }
556     /* Information element not in model. Note it's alien and add it. */
557     ex_ie->ref.name = g_string_chunk_insert(model->ie_names,
558                                             "_alienInformationElement");
559     ex_ie->flags |= FB_IE_F_ALIEN;
560     fbInfoModelAddElement(model, ex_ie);
561     model_ie = fbInfoModelGetElement(model, ex_ie);
562     g_assert(model_ie);
563 
564     return model_ie;
565 }
566