| #include <stdlib.h> |
| #include <apr_file_io.h> |
| #include <apr_strings.h> |
| #include <apr_errno.h> |
| #include <string.h> |
| |
| #include "config.h" |
| #include "flood_profile.h" |
| #include "flood_config.h" |
| #include "flood_net.h" |
| #include "flood_net_ssl.h" |
| |
| #include "flood_round_robin.h" |
| #include "flood_simple_reports.h" |
| #include "flood_easy_reports.h" |
| |
| extern apr_file_t *local_stdout; |
| extern apr_file_t *local_stderr; |
| |
| struct profile_event_handler_t { |
| const char *handler_name; |
| const char *impl_name; |
| void *handler; |
| }; |
| typedef struct profile_event_handler_t profile_event_handler_t; |
| |
| /** |
| * Generic implementation for profile_init. |
| */ |
| static apr_status_t generic_profile_init(profile_t **profile, config_t *config, const char * profile_name, apr_pool_t *pool) |
| { |
| return APR_ENOTIMPL; |
| } |
| |
| /** |
| * Generic implementation for report_init. |
| */ |
| static apr_status_t generic_report_init(report_t **report, config_t *config, const char *profile_name, apr_pool_t *pool) |
| { |
| /* by default, don't generate a report */ |
| *report = NULL; |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * Generic implementation for get_next_url. |
| */ |
| static apr_status_t generic_get_next_url(request_t **request, profile_t *profile) |
| { |
| return APR_ENOTIMPL; |
| } |
| |
| /** |
| * Generic implementation for send_req. |
| */ |
| static apr_status_t generic_send_req(socket_t **sock, request_t *req, apr_pool_t *pool) |
| { |
| apr_socket_t *new_sock = open_socket(pool, req); |
| write_socket(new_sock, req); |
| *sock = new_sock; |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * Generic implementation for recv_resp. |
| */ |
| static apr_status_t generic_recv_resp(response_t **resp, socket_t *sock, apr_pool_t *pool) |
| { |
| response_t *new_resp; |
| apr_status_t status; |
| |
| apr_socket_t *s = (apr_socket_t*)sock; |
| |
| new_resp = apr_pcalloc(pool, sizeof(response_t)); |
| new_resp->rbuftype = POOL; |
| new_resp->rbufsize = MAX_DOC_LENGTH; |
| new_resp->rbuf = apr_pcalloc(pool, new_resp->rbufsize+1); |
| /* XXX: Why +1? if MAX_DOC_LENGTH is a multiple |
| of this OS's pagesize, then you'll end |
| up triggering an extra page allocation. -aaron */ |
| |
| status = read_socket(s, new_resp->rbuf, &new_resp->rbufsize); |
| |
| if (status != APR_SUCCESS && status != APR_EOF) { |
| return status; |
| } |
| |
| *resp = new_resp; |
| |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * Generic implementation for postprocess. |
| */ |
| static apr_status_t generic_postprocess(profile_t *p, request_t *req, response_t *resp) |
| { |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * Generic implementation for verify_resp. |
| */ |
| static apr_status_t generic_verify_resp(int *verified, profile_t *profile, request_t *req, response_t *resp) |
| { |
| return APR_ENOTIMPL; |
| } |
| |
| /** |
| * Generic implementation for process_stats. |
| */ |
| static apr_status_t generic_process_stats(report_t *report, int verified) |
| { |
| /* by default report nothing */ |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * Generic implementation for loop_condition. |
| */ |
| static int generic_loop_condition(profile_t *p) |
| { |
| return 0; /* by default, don't loop */ |
| } |
| |
| /** |
| * Generic implementation for request_destroy. |
| */ |
| static apr_status_t generic_request_destroy(request_t *req) |
| { |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * Generic implementation for response_destroy. |
| */ |
| static apr_status_t generic_response_destroy(response_t *resp) |
| { |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * Generic implementation for socket_destroy. |
| */ |
| static apr_status_t generic_socket_destroy(socket_t *socket) |
| { |
| apr_socket_t *s = (apr_socket_t*)socket; |
| |
| /* this is a generic recv resp, no keepalive support */ |
| close_socket(s); |
| |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * Generic implementation for report_stats. |
| */ |
| static apr_status_t generic_report_stats(report_t *report) |
| { |
| /* nothing to report, but not a failure */ |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * Generic implementation for destroy_report. |
| */ |
| static apr_status_t generic_destroy_report(report_t *report) |
| { |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * Generic implementation for profile_destroy. |
| */ |
| static apr_status_t generic_profile_destroy(profile_t *p) |
| { |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t ssl_send_req(socket_t **sock, request_t *req, apr_pool_t *pool) |
| { |
| ssl_socket_t *s = ssl_open_socket(pool, req); |
| |
| if (!s) |
| return APR_EGENERAL; |
| |
| ssl_write_socket(s, req); |
| |
| *sock = s; |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t ssl_recv_resp(response_t **resp, socket_t *sock, apr_pool_t *pool) |
| { |
| response_t *new_resp; |
| apr_status_t status; |
| ssl_socket_t *s = (ssl_socket_t*)sock; |
| |
| new_resp = apr_pcalloc(pool, sizeof(response_t)); |
| new_resp->rbuftype = POOL; |
| new_resp->rbufsize = MAX_DOC_LENGTH; |
| new_resp->rbuf = apr_pcalloc(pool, new_resp->rbufsize+1); |
| |
| status = ssl_read_socket(s, new_resp->rbuf, &new_resp->rbufsize); |
| |
| if (status != APR_SUCCESS && status != APR_EOF) { |
| return status; |
| } |
| |
| *resp = new_resp; |
| |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * SSL implementation for socket_destroy. |
| */ |
| static apr_status_t ssl_socket_destroy(socket_t *socket) |
| { |
| ssl_socket_t *s = (ssl_socket_t*)socket; |
| |
| /* debatable keepalive support */ |
| ssl_close_socket(s); |
| |
| return APR_SUCCESS; |
| } |
| |
| const char * profile_event_handler_names[] = { |
| "profile_init", |
| "report_init", |
| "get_next_url", |
| "send_req", |
| "recv_resp", |
| "postprocess", |
| "verify_resp", |
| "process_stats", |
| "loop_condition", |
| "request_destroy", |
| "response_destroy", |
| "socket_destroy", |
| "report_stats", |
| "destroy_report", |
| "profile_destroy", |
| NULL |
| }; |
| |
| profile_event_handler_t profile_event_handlers[] = { |
| {"profile_init", "generic_profile_init", &generic_profile_init}, |
| {"report_init", "generic_report_init", &generic_report_init}, |
| {"get_next_url", "generic_get_next_url", &generic_get_next_url}, |
| {"send_req", "generic_send_req", &generic_send_req}, |
| {"recv_resp", "generic_recv_resp", &generic_recv_resp}, |
| {"postprocess", "generic_postprocess", &generic_postprocess}, |
| {"verify_resp", "generic_verify_resp", &generic_verify_resp}, |
| {"process_stats", "generic_process_stats", &generic_process_stats}, |
| {"loop_condition", "generic_loop_condition", &generic_loop_condition}, |
| {"request_destroy", "generic_request_destroy", &generic_request_destroy}, |
| {"response_destroy", "generic_response_destroy", &generic_response_destroy}, |
| {"socket_destroy", "generic_socket_destroy", &generic_socket_destroy}, |
| {"report_stats", "generic_report_stats", &generic_report_stats}, |
| {"destroy_report", "generic_destroy_report", &generic_destroy_report}, |
| {"profile_destroy", "generic_profile_destroy", &generic_profile_destroy}, |
| |
| /* Alternative Implementations that are currently available: */ |
| |
| /* SSL support */ |
| {"send_req", "ssl_send_req", &ssl_send_req}, |
| {"recv_resp", "ssl_recv_resp", &ssl_recv_resp}, |
| {"socket_destroy", "ssl_socket_destroy", &ssl_socket_destroy}, |
| |
| /* Round Robin */ |
| {"profile_init", "round_robin_profile_init", &round_robin_profile_init}, |
| {"get_next_url", "round_robin_get_next_url", &round_robin_get_next_url}, |
| {"postprocess", "round_robin_postprocess", &round_robin_postprocess}, |
| {"loop_condition", "round_robin_loop_condition", &round_robin_loop_condition}, |
| {"profile_destroy", "round_robin_profile_destroy", &round_robin_profile_destroy}, |
| |
| /* Verification by OK/200 */ |
| {"verify_resp", "verify_200", &verify_200}, |
| |
| /* Simple Reports */ |
| {"report_init", "simple_report_init", &simple_report_init}, |
| {"process_stats", "simple_process_stats", &simple_process_stats}, |
| {"report_stats", "simple_report_stats", &simple_report_stats}, |
| {"destroy_report", "simple_destroy_report", &simple_destroy_report}, |
| |
| /* Easy Reports */ |
| {"report_init", "easy_report_init", &easy_report_init}, |
| {"process_stats", "easy_process_stats", &easy_process_stats}, |
| {"report_stats", "easy_report_stats", &easy_report_stats}, |
| {"destroy_report", "easy_destroy_report", &easy_destroy_report}, |
| |
| {NULL} /* sentinel value */ |
| }; |
| |
| /** |
| * Assign the appropriate implementation to the profile_events_t handler |
| * for the given function name and overriden function name. |
| * Returns APR_SUCCESS if an appropriate handler was found and assigned, or |
| * returns APR_ENOTIMPL if no match was found. |
| */ |
| static apr_status_t assign_profile_event_handler(profile_events_t *events, |
| const char *handler_name, |
| const char *impl_name) |
| { |
| profile_event_handler_t *p; |
| |
| for (p = &profile_event_handlers[0]; |
| p; |
| p++) { |
| /* these are case insensitive (both key and value) for the sake of simplicity */ |
| if (strncasecmp(impl_name, (*p).impl_name, FLOOD_STRLEN_MAX) == 0) { |
| if (strncasecmp(handler_name, (*p).handler_name, FLOOD_STRLEN_MAX) == 0) { |
| /* we got a match, assign it */ |
| |
| /* stupid cascading if, no big deal since it only happens at startup */ |
| if (strncasecmp(handler_name, "profile_init", FLOOD_STRLEN_MAX) == 0) { |
| events->profile_init = (*p).handler; |
| } else if (strncasecmp(handler_name, "report_init", FLOOD_STRLEN_MAX) == 0){ |
| events->report_init = (*p).handler; |
| } else if (strncasecmp(handler_name, "get_next_url", FLOOD_STRLEN_MAX) == 0){ |
| events->get_next_url = (*p).handler; |
| } else if (strncasecmp(handler_name, "send_req", FLOOD_STRLEN_MAX) == 0) { |
| events->send_req = (*p).handler; |
| } else if (strncasecmp(handler_name, "recv_resp", FLOOD_STRLEN_MAX) == 0) { |
| events->recv_resp = (*p).handler; |
| } else if (strncasecmp(handler_name, "postprocess", FLOOD_STRLEN_MAX) == 0) { |
| events->postprocess = (*p).handler; |
| } else if (strncasecmp(handler_name, "verify_resp", FLOOD_STRLEN_MAX) == 0) { |
| events->verify_resp = (*p).handler; |
| } else if (strncasecmp(handler_name, "process_stats", FLOOD_STRLEN_MAX) == 0) { |
| events->process_stats = (*p).handler; |
| } else if (strncasecmp(handler_name, "loop_condition", FLOOD_STRLEN_MAX) == 0) { |
| events->loop_condition = (*p).handler; |
| } else if (strncasecmp(handler_name, "request_destroy", FLOOD_STRLEN_MAX) == 0) { |
| events->request_destroy = (*p).handler; |
| } else if (strncasecmp(handler_name, "response_destroy", FLOOD_STRLEN_MAX) == 0) { |
| events->response_destroy = (*p).handler; |
| } else if (strncasecmp(handler_name, "socket_destroy", FLOOD_STRLEN_MAX) == 0) { |
| events->socket_destroy = (*p).handler; |
| } else if (strncasecmp(handler_name, "report_stats", FLOOD_STRLEN_MAX) == 0) { |
| events->report_stats = (*p).handler; |
| } else if (strncasecmp(handler_name, "destroy_report", FLOOD_STRLEN_MAX) == 0) { |
| events->destroy_report = (*p).handler; |
| } else if (strncasecmp(handler_name, "profile_destroy", FLOOD_STRLEN_MAX) == 0) { |
| events->profile_destroy = (*p).handler; |
| } else { |
| /* some internal error, our static structs don't match up */ |
| return APR_EGENERAL; |
| } |
| return APR_SUCCESS; |
| } else { |
| /* invalid implementation for this handler */ |
| return APR_ENOTIMPL; /* XXX: There's probably a better return val than this? */ |
| } |
| } |
| } |
| return APR_ENOTIMPL; /* no implementation found */ |
| } |
| |
| /** |
| * Construct a profile_event_handler_t struct, with the "generic" |
| * implementations in place by default. |
| */ |
| static apr_status_t create_profile_events(profile_events_t **events, apr_pool_t *pool) |
| { |
| apr_status_t stat; |
| profile_events_t *new_events; |
| |
| if ((new_events = apr_pcalloc(pool, sizeof(profile_events_t))) == NULL) |
| return APR_ENOMEM; |
| |
| if ((stat = assign_profile_event_handler(new_events, |
| "profile_init", |
| "generic_profile_init")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "report_init", |
| "generic_report_init")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "get_next_url", |
| "generic_get_next_url")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "send_req", |
| "generic_send_req")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "recv_resp", |
| "generic_recv_resp")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "verify_resp", |
| "generic_verify_resp")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "postprocess", |
| "generic_postprocess")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "process_stats", |
| "generic_process_stats")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "loop_condition", |
| "generic_loop_condition")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "request_destroy", |
| "generic_request_destroy")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "response_destroy", |
| "generic_response_destroy")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "socket_destroy", |
| "generic_socket_destroy")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "report_stats", |
| "generic_report_stats")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "destroy_report", |
| "generic_destroy_report")) != APR_SUCCESS) |
| return stat; |
| if ((stat = assign_profile_event_handler(new_events, |
| "profile_destroy", |
| "generic_profile_destroy")) != APR_SUCCESS) |
| return stat; |
| |
| *events = new_events; |
| |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * Initializes the profile_events_t structure according to what we |
| * find in the given configuration. Dynamically allocated memory |
| * is pulled from the given pool. |
| */ |
| apr_status_t initialize_events(profile_events_t **events, const char * profile_name, config_t *config, apr_pool_t *pool) |
| { |
| apr_status_t stat; |
| const char **p; |
| profile_events_t *new_events; |
| struct apr_xml_elem *root_elem, *profile_elem, *profile_event_elem; |
| char *xml_profile; |
| |
| xml_profile = apr_pstrdup(pool, XML_PROFILE); |
| |
| if ((stat = retrieve_root_xml_elem(&root_elem, config)) != APR_SUCCESS) { |
| return stat; |
| } |
| |
| if ((stat = create_profile_events(&new_events, pool)) != APR_SUCCESS) { |
| return stat; |
| } |
| |
| /* retrieve our profile xml element */ |
| if ((stat = retrieve_xml_elem_with_childmatch( |
| &profile_elem, root_elem, |
| xml_profile, "name", profile_name)) != APR_SUCCESS) |
| return stat; |
| |
| /* For each event in the profile_events_t struct, allow the config |
| * parameters to override an implementation. |
| */ |
| for (p = &profile_event_handler_names[0]; *p; p++) { |
| if ((stat = retrieve_xml_elem_child( |
| &profile_event_elem, |
| profile_elem, |
| *p)) == APR_SUCCESS) { |
| |
| /* search for the implementation in our tables and assign it */ |
| if ((stat = assign_profile_event_handler( |
| new_events, |
| *p, |
| profile_event_elem->first_cdata.first->text)) != APR_SUCCESS) { |
| #ifdef PROFILE_DEBUG |
| apr_file_printf(local_stdout, "Profile '%s' failed to override '%s' with '%s'.\n", |
| profile_name, *p, profile_event_elem->first_cdata.first->text); |
| #endif /* PROFILE_DEBUG */ |
| return stat; |
| } else { |
| #ifdef PROFILE_DEBUG |
| apr_file_printf(local_stdout, "Profile '%s' overrides '%s' with '%s'.\n", |
| profile_name, *p, profile_event_elem->first_cdata.first->text); |
| #endif /* PROFILE_DEBUG */ |
| } |
| |
| } else { |
| #ifdef PROFILE_DEBUG |
| apr_file_printf(local_stdout, "Profile '%s' uses default '%s'.\n", |
| profile_name, *p); |
| #endif /* PROFILE_DEBUG */ |
| } |
| } |
| |
| *events = new_events; |
| |
| return APR_SUCCESS; |
| } |
| |
| /** |
| * Essential guts of the main test loop -- a single run of a test profile: |
| */ |
| apr_status_t run_profile(apr_pool_t *pool, config_t *config, const char * profile_name) |
| { |
| profile_events_t *events; |
| profile_t *profile; |
| report_t *report; |
| request_t *req; |
| response_t *resp; |
| socket_t *socket; |
| apr_status_t stat; |
| int verified; |
| |
| /* init to NULL for the sake of our error checking */ |
| events = NULL; |
| profile = NULL; |
| req = NULL; |
| resp = NULL; |
| socket = NULL; |
| verified = FLOOD_INVALID; |
| |
| /* assign the implementations (function pointers) */ |
| if ((stat = initialize_events(&events, profile_name, config, pool)) != APR_SUCCESS) { |
| return stat; |
| } |
| |
| if (events == NULL) { |
| apr_file_printf(local_stderr, "Error initializing test profile.\n"); |
| return APR_EGENERAL; /* FIXME: What error code to return? */ |
| } |
| |
| /* initialize this profile */ |
| if ((stat = events->profile_init(&profile, config, profile_name, pool)) != APR_SUCCESS) |
| return stat; |
| |
| /* initialize this report */ |
| if ((stat = events->report_init(&report, config, profile_name, pool)) != APR_SUCCESS) |
| return stat; |
| |
| do { |
| if ((stat = events->get_next_url(&req, profile)) != APR_SUCCESS) |
| return stat; |
| |
| /* sample timer "main" */ |
| |
| if ((stat = events->send_req(&socket, req, pool)) != APR_SUCCESS) { |
| apr_file_printf(local_stderr, "send request failed.\n"); |
| return stat; |
| } |
| |
| /* sample timer "send_req" */ |
| |
| if ((stat = events->recv_resp(&resp, socket, pool)) != APR_SUCCESS) { |
| apr_file_printf(local_stderr, "receive request failed.\n"); |
| return stat; |
| } |
| |
| /* sample timer "recv_resp" */ |
| |
| if ((stat = events->postprocess(profile, req, resp)) != APR_SUCCESS) { |
| apr_file_printf(local_stderr, "postprocessing failed.\n"); |
| return stat; |
| } |
| |
| if ((stat = events->verify_resp(&verified, profile, req, resp)) != APR_SUCCESS) { |
| apr_file_printf(local_stderr, "Error while verifying query.\n"); |
| return stat; |
| } |
| |
| /* sample timer "full_resp" */ |
| |
| if ((stat = events->process_stats(report, verified)) != APR_SUCCESS) { |
| apr_file_printf(local_stderr, "Unable to process statistics.\n"); |
| return stat; |
| } |
| |
| if ((stat = events->request_destroy(req)) != APR_SUCCESS) { |
| apr_file_printf(local_stderr, "Error cleaning up request.\n"); |
| return stat; |
| } |
| |
| if ((stat = events->response_destroy(resp)) != APR_SUCCESS) { |
| apr_file_printf(local_stderr, "Error cleaning up Response.\n"); |
| return stat; |
| } |
| |
| if ((stat = events->socket_destroy(socket)) != APR_SUCCESS) { |
| apr_file_printf(local_stderr, "Error cleaning up Socket.\n"); |
| return stat; |
| } |
| |
| |
| } while (events->loop_condition(profile)); |
| |
| if ((stat = events->report_stats(report)) != APR_SUCCESS) { |
| apr_file_printf(local_stderr, "Unable to report statistics.\n"); |
| return stat; |
| } |
| |
| if (events->destroy_report(report) != APR_SUCCESS) { |
| apr_file_printf(local_stderr, "Error cleaning up report '%s'.\n", profile_name); |
| return APR_EGENERAL; /* FIXME: What error code to return? */ |
| } |
| |
| if (events->profile_destroy(profile) != APR_SUCCESS) { |
| apr_file_printf(local_stderr, "Error cleaning up profile '%s'.\n", profile_name); |
| return APR_EGENERAL; /* FIXME: What error code to return? */ |
| } |
| |
| return APR_SUCCESS; |
| } |