# HG changeset patch # User Sebastien Decugis # Date 1368071974 -28800 # Node ID f38d77f9cfd32478b355a15f7b269f0de8f7ead0 # Parent 4d2dcb54d9a6480c5a5ae9fb59545f11533f00d5 Initial implementation of the hook mechanism diff -r 4d2dcb54d9a6 -r f38d77f9cfd3 include/freeDiameter/freeDiameter-host.h.in --- a/include/freeDiameter/freeDiameter-host.h.in Mon May 06 18:49:59 2013 +0800 +++ b/include/freeDiameter/freeDiameter-host.h.in Thu May 09 11:59:34 2013 +0800 @@ -94,6 +94,9 @@ #define FD_DEFAULT_CONF_FILENAME "freeDiameter.conf" #endif /* FD_DEFAULT_CONF_FILENAME */ +/* Maximum number of hooks handlers that can be registered. Make this compilation option if needed */ +#define FD_HOOK_HANDLE_LIMIT 5 + #ifdef __cplusplus } #endif diff -r 4d2dcb54d9a6 -r f38d77f9cfd3 include/freeDiameter/libfdcore.h --- a/include/freeDiameter/libfdcore.h Mon May 06 18:49:59 2013 +0800 +++ b/include/freeDiameter/libfdcore.h Thu May 09 11:59:34 2013 +0800 @@ -878,7 +878,7 @@ /* These functions allow an extension to collect state information about the * framework, as well as being hooked at some key checkpoints in the processing - * for logging / statistics purpose. + * for logging or statistics purpose. */ @@ -886,11 +886,11 @@ * * PARAMETERS: * type : The type of hook that triggered this call, in case same cb is registered for several hooks. - * msg : If relevant, the pointer to the message trigging the call. NULL otherwise. + * msg : If relevant, the pointer to the message triggering the call. NULL otherwise. * peer : If relevant, the pointer to the peer associated with the call. NULL otherwise. * other : For some callbacks, the remaining information is passed in this parameter. See each hook detail. * permsgdata : Structure associated with a given message, across several hooks. - * Same structure is associated with requests and corresponding answers. + * A different structure is associated with requests and corresponding answers. * See fd_hook_data_hdl below for details. * If no fd_hook_data_hdl is registered with this callback, this parameter is always NULL * regdata : Data pointer stored at registration, opaque for the framework. @@ -915,10 +915,8 @@ /* Hook called as soon as a message has been received from the network, after TLS & boundary processing. - {msg} is NULL. - {peer} is NULL. - - {*other} is NULL, {other} points to a valid location where you can store a pointer. - This same pointer will then passed to the next hook, once message is processed. - IMPORTANT: free() will be called on this pointer if any problem, so this pointer must be malloc'd. - - {permsgdata} is NULL. + - {other} is a pointer to a structure { size_t len; uint8_t * buf; } containing the received buffer. + - {permsgdata} points to either a new empty structure allocated for this message (cf. fd_hook_data_hdl), or NULL if no hdl is registered. */ HOOK_MESSAGE_RECEIVED, @@ -927,8 +925,8 @@ try to call fd_msg_parse_dict, it will slow down the operation of a relay agent. - {peer} is set if the message is received from a peer's connection, and NULL if the message is from a new client connected and not yet identified - - {*other} contains the pointer stored with the HOOK_DATA_RECEIVED hook if any, NULL otherwise. After this hook returns, free(*other) is called if not NULL. - - {permsgdata} points to either a new empty structure allocated for this request (cf. fd_hook_data_hdl), or the request's existing structure if the message is an answer. + - {other} is NULL. + - {permsgdata} points to either a new empty structure allocated for this message or the one passed to HOOK_DATA_RECEIVED if used. */ HOOK_MESSAGE_LOCAL, @@ -945,10 +943,9 @@ - {msg} points to the sent message. Again, the objects may not have been dictionary resolved. If you try to call fd_msg_parse_dict, it will slow down the operation of a relay agent. - {peer} is set if the message is sent to a peer's connection, and NULL if the message is sent to a new client - connected and not yet identified / being rejected + connected and not yet identified, or being rejected - {other} is NULL. - {permsgdata} points to existing structure if any, or a new structure otherwise. - If the message is an answer, the structure is shared with the corresponding request. */ HOOK_MESSAGE_FAILOVER, @@ -958,7 +955,7 @@ try to call fd_msg_parse_dict, it might slow down the operation of a relay agent, although this hook is not on the normal execution path. - {peer} is the peer this message was previously sent to. - {other} is NULL. - - {permsgdata} points to existing structure associated with this request. + - {permsgdata} points to existing structure if any, or a new structure otherwise. */ HOOK_MESSAGE_ROUTING_ERROR, @@ -993,8 +990,8 @@ */ HOOK_MESSAGE_DROPPED, - /* Hook called when a message is being discarded by the framework because of some error. - It is probably a good idea to log this for analysis. + /* Hook called when a message is being discarded by the framework because of some error condition (normal or abnormal). + It is probably a good idea to log this for analysis / backup. - {msg} points to the message, which will be freed as soon as the hook returns. - {peer} is NULL. - {other} is a char * pointer to the error message (human-readable). @@ -1021,7 +1018,7 @@ }; -/* Type if the {permsgdata} ointer. It is up to each extension to define its own structure. This is opaque for the framework. */ +/* Type if the {permsgdata}. It is up to each extension to define its own structure. This is opaque for the framework. */ struct fd_hook_permsgdata; /* A handle that will be associated with the extension, and with the permsgdata structures. */ @@ -1034,14 +1031,17 @@ * FUNCTION: fd_hook_data_register * * PARAMETERS: - * permsgdata_new_cb : function called to initialize a new empty fd_hook_permsgdata structure, when a hook will be called for a message with not structure yet. If the function returns NULL, it will be called again for the next hook. - * permsgdata_destroy_cb : function called when a message is being disposed. It should free the resources associated with the fd_hook_permsgdata. - * new_handle : On success, a handler to the registered callback is stored here. + * permsgdata_size : the size of the fd_hook_permsgdata structure. + * permsgdata_init_cb : function called to initialize a new fd_hook_permsgdata structure, when a hook will be called for a message that does not have such structure yet. + * The memory is already allocated and blanked, so you can pass NULL if no further handling is required. + * permsgdata_fini_cb : function called when a message is being disposed. It should free the resources associated with the fd_hook_permsgdata. + * You can pass NULL if no special handling is required. The memory of the permsgdata structure itself will be freed by the framework. + * new_handle : On success, a handler to the registered callback is stored here. * This handler will be used to unregister the cb. * * DESCRIPTION: * Register a new fd_hook_data_hdl. This handle is used during hooks registration (see below) in order to associate data with the messages, to allow keeping tracking of the message easily. - * Note that these handlers are statically allocated and cannot be unregistered. + * Note that these handlers are statically allocated and cannot be unregistered. FD_HOOK_HANDLE_LIMIT handlers can be registered at maximum (recompile libfdproto if you change this value) * * RETURN VALUE: * 0 : The callback is registered. @@ -1049,12 +1049,12 @@ * ENOSPC : Too many handles already registered. You may need to increase the limit in the code. */ int fd_hook_data_register( - struct fd_hook_permsgdata * (*permsgdata_new_cb) (void), - void (*permsgdata_destroy_cb) (struct fd_hook_permsgdata *), - struct fd_hook_data_hdl ** new_handle + size_t permsgdata_size, + void (*permsgdata_init_cb) (struct fd_hook_permsgdata *), + void (*permsgdata_fini_cb) (struct fd_hook_permsgdata *), + struct fd_hook_data_hdl **new_handle ); - /* A handler associated with a registered hook callback (for cleanup) */ struct fd_hook_hdl; @@ -1062,7 +1062,7 @@ * FUNCTION: fd_hook_register * * PARAMETERS: - * type : The fd_hook_type for which this cb is registered. Call several times if you want to register for several hooks. + * type_mask : A bitmask of fd_hook_type bits for which this cb is registered, e.g. ((1 << HOOK_MESSAGE_RECEIVED) || (1 << HOOK_MESSAGE_SENT)) * fd_hook_cb : The callback function to register (see prototype above). * regdata : Pointer to pass to the callback when it is called. The data is opaque to the daemon. * data_hdl : If permsgdata is requested for the hooks, a handler registered with fd_hook_data_register. NULL otherwise. @@ -1078,9 +1078,9 @@ * EINVAL : A parameter is invalid. * ENOMEM : Not enough memory to complete the operation */ -int fd_hook_register ( enum fd_hook_type type, - void (*fd_hook_cb)(enum fd_hook_type type, struct msg * msg, struct peer_hdr * peer, void * other, void * regdata), - void * regdata, +int fd_hook_register ( uint32_t type_mask, + void (*fd_hook_cb)(enum fd_hook_type type, struct msg * msg, struct peer_hdr * peer, void * other, struct fd_hook_permsgdata *pmd, void * regdata), + void *regdata, struct fd_hook_data_hdl *data_hdl, struct fd_hook_hdl ** handler ); diff -r 4d2dcb54d9a6 -r f38d77f9cfd3 include/freeDiameter/libfdproto.h --- a/include/freeDiameter/libfdproto.h Mon May 06 18:49:59 2013 +0800 +++ b/include/freeDiameter/libfdproto.h Thu May 09 11:59:34 2013 +0800 @@ -2549,6 +2549,14 @@ int fd_msg_sess_set(struct msg * msg, struct session * session); +/* Helper for the hooks mechanism, for use from libfdcore */ +struct fd_msg_pmdl { + struct fd_list sentinel; /* if the sentinel.o field is NULL, the structure is not initialized. Otherwise it points to the cleanup function in libfdcore. */ + pthread_mutex_t lock; +}; +#define FD_MSG_PMDL_INITIALIZER(pmdl_ptr) { FD_LIST_INITIALIZER( (pmdl_ptr)->sentinel ), PTHREAD_MUTEX_INITIALIZER } +struct fd_msg_pmdl * fd_msg_pmdl_get(struct msg * msg); + /***************************************/ /* Manage AVP values */ /***************************************/ diff -r 4d2dcb54d9a6 -r f38d77f9cfd3 libfdcore/cnxctx.c --- a/libfdcore/cnxctx.c Mon May 06 18:49:59 2013 +0800 +++ b/libfdcore/cnxctx.c Thu May 09 11:59:34 2013 +0800 @@ -735,6 +735,8 @@ received += ret; } + // fd_msg_log(....) + /* We have received a complete message, pass it to the daemon */ CHECK_FCT_DO( fd_event_send( fd_cnx_target_queue(conn), FDEVP_CNX_MSG_RECV, length, newmsg), /* continue or destroy everything? */); diff -r 4d2dcb54d9a6 -r f38d77f9cfd3 libfdcore/core.c --- a/libfdcore/core.c Mon May 06 18:49:59 2013 +0800 +++ b/libfdcore/core.c Thu May 09 11:59:34 2013 +0800 @@ -186,6 +186,7 @@ CHECK_FCT( fd_dict_base_protocol(fd_g_config->cnf_dict) ); /* Initialize some modules */ + CHECK_FCT( fd_hooks_init() ); CHECK_FCT( fd_queues_init() ); CHECK_FCT( fd_msg_init() ); CHECK_FCT( fd_sess_start() ); diff -r 4d2dcb54d9a6 -r f38d77f9cfd3 libfdcore/fdcore-internal.h --- a/libfdcore/fdcore-internal.h Mon May 06 18:49:59 2013 +0800 +++ b/libfdcore/fdcore-internal.h Thu May 09 11:59:34 2013 +0800 @@ -362,4 +362,8 @@ /* Flags for the fd_cnx_send function : */ #define FD_CNX_ORDERED (1 << 0) /* All messages sent with this flag set will be delivered in the same order. No guarantee on other messages */ +/* Internal calls of the hook mechanism */ +void fd_hook_call(enum fd_hook_type type, struct msg * msg, struct fd_peer * peer, void * other, struct fd_msg_pmdl * pmdl); +void fd_hook_associate(struct msg * msg, struct fd_msg_pmdl * pmdl); +int fd_hooks_init(void); #endif /* _FDCORE_INTERNAL_H */ diff -r 4d2dcb54d9a6 -r f38d77f9cfd3 libfdcore/hooks.c --- a/libfdcore/hooks.c Mon May 06 18:49:59 2013 +0800 +++ b/libfdcore/hooks.c Thu May 09 11:59:34 2013 +0800 @@ -35,30 +35,225 @@ #include "fdcore-internal.h" -struct fd_hook_hdl; -struct fd_hook_data_hdl; +/* Structures for the fd_hook_data_hdl management */ +static struct fd_hook_data_hdl { + size_t pmd_size; + void (*pmd_init_cb)(struct fd_hook_permsgdata *); + void (*pmd_fini_cb)(struct fd_hook_permsgdata *); +} HDH_array[FD_HOOK_HANDLE_LIMIT]; +static int max_index = 0; +static pthread_mutex_t HDH_lock = PTHREAD_MUTEX_INITIALIZER; + +/* The structure linked from the msg structure list */ +struct pmd_list_item { + struct fd_list chain; /* this list is ordered by hdl */ + struct fd_hook_data_hdl * hdl; + struct fd_hook_permsgdata { } pmd; /* this data belongs to the extension; we only know the size of it */ +}; + +#define sizeof_pmd(hdl) (((size_t)&((struct pmd_list_item *)0)->pmd) + hdl->pmd_size) -int fd_hook_data_register( - struct fd_hook_permsgdata * (*permsgdata_new_cb) (void), - void (*permsgdata_destroy_cb) (struct fd_hook_permsgdata *), - struct fd_hook_data_hdl ** new_handle -) +/* Now a hook registered by an extension */ +struct fd_hook_hdl { + struct fd_list chain[HOOK_PEER_LAST+1]; + void (*fd_hook_cb)(enum fd_hook_type type, struct msg * msg, struct peer_hdr * peer, void * other, struct fd_hook_permsgdata *pmd, void * regdata); + void *regdata; + struct fd_hook_data_hdl *data_hdl; +}; + +/* Array of those hooks */ +struct { + struct fd_list sentinel; + pthread_rwlock_t rwlock; +} HS_array[HOOK_PEER_LAST+1]; + +/* Initialize the array of sentinels for the hooks */ +int fd_hooks_init(void) { - return ENOTSUP; + int i; + for (i=0; i <= HOOK_PEER_LAST; i++) { + fd_list_init(&HS_array[i].sentinel, NULL); + CHECK_POSIX( pthread_rwlock_init(&HS_array[i].rwlock, NULL) ); + } + return 0; } -int fd_hook_register ( enum fd_hook_type type, - void (*fd_hook_cb)(enum fd_hook_type type, struct msg * msg, struct peer_hdr * peer, void * other, void * regdata), - void * regdata, +/* Get a slot in the array */ +int fd_hook_data_register( + size_t permsgdata_size, + void (*permsgdata_init_cb) (struct fd_hook_permsgdata *), + void (*permsgdata_fini_cb) (struct fd_hook_permsgdata *), + struct fd_hook_data_hdl **new_handle) +{ + int ret = ENOSPC, idx; + TRACE_ENTRY("%zd %p %p %p", permsgdata_size, permsgdata_init_cb, permsgdata_fini_cb, new_handle); + + CHECK_PARAMS( permsgdata_size && new_handle ); + + CHECK_POSIX( pthread_mutex_lock(&HDH_lock) ); + if (max_index < FD_HOOK_HANDLE_LIMIT) { + idx = max_index++; + ret = 0; + } + CHECK_POSIX( pthread_mutex_unlock(&HDH_lock) ); + + if (ret == 0) { + HDH_array[idx].pmd_size = permsgdata_size; + HDH_array[idx].pmd_init_cb = permsgdata_init_cb; + HDH_array[idx].pmd_fini_cb = permsgdata_fini_cb; + *new_handle = &HDH_array[idx]; + } + + return ret; +} + +/* Register a new hook callback */ +int fd_hook_register ( uint32_t type_mask, + void (*fd_hook_cb)(enum fd_hook_type type, struct msg * msg, struct peer_hdr * peer, void * other, struct fd_hook_permsgdata *pmd, void * regdata), + void *regdata, struct fd_hook_data_hdl *data_hdl, struct fd_hook_hdl ** handler ) { - return ENOTSUP; + struct fd_hook_hdl * newhdl = NULL; + int i; + + TRACE_ENTRY("%x %p %p %p %p", type_mask, fd_hook_cb, regdata, data_hdl, handler); + + CHECK_PARAMS( fd_hook_cb && handler ); + + CHECK_MALLOC( newhdl = malloc(sizeof(struct fd_hook_hdl)) ); + memset(newhdl, 0, sizeof(struct fd_hook_hdl)); + + newhdl->fd_hook_cb = fd_hook_cb; + newhdl->regdata = regdata; + newhdl->data_hdl = data_hdl; + + for (i=0; i <= HOOK_PEER_LAST; i++) { + fd_list_init(&newhdl->chain[i], newhdl); + if (type_mask & (1<chain[i]); + CHECK_POSIX( pthread_rwlock_unlock(&HS_array[i].rwlock) ); + } + } + + *handler = newhdl; + return 0; +} + +/* free this hook callback */ +int fd_hook_unregister( struct fd_hook_hdl * handler ) +{ + int i; + TRACE_ENTRY("%p", handler); + CHECK_PARAMS( handler ); + + for (i=0; i <= HOOK_PEER_LAST; i++) { + if ( ! FD_IS_LIST_EMPTY(&handler->chain[i])) { + CHECK_POSIX( pthread_rwlock_wrlock(&HS_array[i].rwlock) ); + fd_list_unlink(&handler->chain[i]); + CHECK_POSIX( pthread_rwlock_unlock(&HS_array[i].rwlock) ); + } + } + + free(handler); + + return 0; +} + +/* callback for the libfdproto to free the data associated with a message */ +static void pmdl_free(struct fd_msg_pmdl *pmdl) +{ + /* destroy all the items in the list */ + while (!FD_IS_LIST_EMPTY(&pmdl->sentinel)) { + struct pmd_list_item * li = (struct pmd_list_item *)(pmdl->sentinel.next); + if (li->hdl->pmd_fini_cb) { + (*li->hdl->pmd_fini_cb)(&li->pmd); + } + fd_list_unlink(&li->chain); + free(li); + } + CHECK_POSIX_DO( pthread_mutex_destroy(&pmdl->lock), ); + pmdl->sentinel.o = NULL; +} + +/* Save the list of pmd into the message structure, as well as the callback to free this list */ +void fd_hook_associate(struct msg * msg, struct fd_msg_pmdl * pmdl) +{ + struct fd_msg_pmdl * in_msg; + + CHECK_PARAMS_DO( msg && pmdl, return ); + in_msg = fd_msg_pmdl_get(msg); + ASSERT(in_msg && (in_msg->sentinel.o == NULL)); /* error / already initialized ??? */ + fd_list_init(&in_msg->sentinel, pmdl_free); + CHECK_POSIX_DO( pthread_mutex_init(&in_msg->lock, NULL), ); + /* Now move all items from the pmdl pointer into the initialized list */ + CHECK_POSIX_DO( pthread_mutex_lock(&pmdl->lock), ); + fd_list_move_end(&in_msg->sentinel, &pmdl->sentinel); + CHECK_POSIX_DO( pthread_mutex_unlock(&pmdl->lock), ); + pmdl_free(pmdl); + /* We're done */ +} + +/* Return the location of the permsgdata area corresponding to this handle, after eventually having created it. Return NULL in case of failure */ +static struct fd_hook_permsgdata * get_or_create_pmd(struct fd_msg_pmdl *pmdl, struct fd_hook_hdl * h) +{ + struct fd_hook_permsgdata * ret = NULL; + struct fd_list * li; + CHECK_POSIX_DO( pthread_mutex_lock(&pmdl->lock), ); + + /* Search in the list for an item with the same handle. The list is ordered by this handle */ + for (li=pmdl->sentinel.next; li != &pmdl->sentinel; li = li->next) { + struct pmd_list_item * pli = (struct pmd_list_item *) li; + if (pli->hdl == h->data_hdl) + ret = &pli->pmd; + if (pli->hdl >= h->data_hdl) + break; + } + if (!ret) { + /* we need to create a new one and insert before li */ + struct pmd_list_item * pli; + CHECK_MALLOC_DO( pli = malloc(sizeof_pmd(h->data_hdl)), ); + if (pli) { + memset(pli, 0, sizeof_pmd(h->data_hdl)); + fd_list_init(&pli->chain, pli); + pli->hdl = h->data_hdl; + ret = &pli->pmd; + if (h->data_hdl->pmd_init_cb) { + (*h->data_hdl->pmd_init_cb)(ret); + } + fd_list_insert_before(li, &pli->chain); + } + } + + CHECK_POSIX_DO( pthread_mutex_unlock(&pmdl->lock), ); + return ret; } -int fd_hook_unregister( struct fd_hook_hdl * handler ) +/* The function that does the work of calling the extension's callbacks and also managing the permessagedata structures */ +void fd_hook_call(enum fd_hook_type type, struct msg * msg, struct fd_peer * peer, void * other, struct fd_msg_pmdl * pmdl) { - return ENOTSUP; + struct fd_list * li; + ASSERT(type <= HOOK_PEER_LAST); + + /* lock the list of hooks for this type */ + CHECK_POSIX_DO( pthread_rwlock_rdlock(&HS_array[type].rwlock), ); + + /* for each registered hook */ + for (li = HS_array[type].sentinel.next; li != &HS_array[type].sentinel; li = li->next) { + struct fd_hook_hdl * h = (struct fd_hook_hdl *)li->o; + struct fd_hook_permsgdata * pmd = NULL; + + /* do we need to handle pmd ? */ + if (h->data_hdl && pmdl) { + pmd = get_or_create_pmd(pmdl, h); + } + + /* Now, call this callback */ + (*h->fd_hook_cb)(type, msg, &peer->p_hdr, other, pmd, h->regdata); + } + + /* done */ + CHECK_POSIX_DO( pthread_rwlock_unlock(&HS_array[type].rwlock), ); } - diff -r 4d2dcb54d9a6 -r f38d77f9cfd3 libfdproto/messages.c --- a/libfdproto/messages.c Mon May 06 18:49:59 2013 +0800 +++ b/libfdproto/messages.c Thu May 09 11:59:34 2013 +0800 @@ -136,7 +136,7 @@ } msg_cb; /* Callback to be called when an answer is received, or timeout expires, if not NULL */ DiamId_t msg_src_id; /* Diameter Id of the peer this message was received from. This string is malloc'd and must be freed */ size_t msg_src_id_len; /* cached length of this string */ - + struct fd_msg_pmdl msg_pmdl; /* list of permessagedata structures. */ }; /* Macro to compute the message header size */ @@ -673,6 +673,10 @@ CHECK_FCT_DO( fd_sess_reclaim_msg ( &_M(obj)->msg_sess ), /* continue */); } + if ((obj->type == MSG_MSG) && (_M(obj)->msg_pmdl.sentinel.o != NULL)) { + ((void (*)(struct fd_msg_pmdl *))_M(obj)->msg_pmdl.sentinel.o)(&_M(obj)->msg_pmdl); + } + /* free the object */ free(obj); @@ -1497,6 +1501,13 @@ return 0; } +/* Retrieve the location of the pmd list for the message; return NULL if failed */ +struct fd_msg_pmdl * fd_msg_pmdl_get(struct msg * msg) +{ + CHECK_PARAMS_DO( CHECK_MSG(msg), return NULL ); + return &msg->msg_pmdl; +} + /******************* End-to-end counter *********************/ static uint32_t fd_eteid;