/* echld_dispatcher.c * epan working child API internals * Dispatcher process routines and definitions * * $Id$ * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * Copyright (c) 2013 by Luis Ontanon * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "echld-int.h" /** DISPATCHER **/ struct dispatcher_child { unsigned chld_id; child_state_t state; echld_reader_t reader; int write_fd; int pid; gboolean closing; }; struct dispatcher { int parent_out; echld_reader_t parent_in; struct dispatcher_child* children; int max_children; int nchildren; int reqh_id; int pid; int ppid; struct _encs { child_encoder_t* to_parent; echld_parent_encoder_t* to_child; } enc; struct _decs { child_decoder_t* from_parent; parent_decoder_t* from_child; } dec; gboolean closing; }; struct dispatcher* dispatcher; #define DISP_RESP(B,T) (echld_write_frame( dispatcher->parent_out, (B), 0, (T), dispatcher->reqh_id, NULL)) #ifdef DEBUG_DISPATCHER static int dbg_level = 0; void dispatcher_debug(int level, char* fmt, ...) { va_list ap; char* str; if (dbg_levelpid, dispatcher->reqh_id, level, str); g_free(str); } static char* param_get_dbg_level(char** err ) { return g_strdup_printf("%d",dbg_level); } static echld_bool_t param_set_dbg_level(char* val , char** err ) { char* p; int lvl = strtol(val, &p, 10); if (p<=val) { *err = g_strdup("not an integer"); return FALSE; } else if (lvl < 0 || lvl > 5) { *err = g_strdup_printf("invalid level=%d (min=0 max=5)",lvl); return FALSE; } dbg_level = lvl; return TRUE; } #define DISP_DBG(attrs) ( dispatcher_debug attrs ) #else #define DISP_DBG(attrs) #endif /* parameters */ int wait_chldn = 0; static char* param_get_wait_chldn(char** err ) { return g_strdup_printf("%d",wait_chldn); } static echld_bool_t param_set_wait_chldn(char* val , char** err ) { char* p; int lvl = strtol(val, &p, 10); if (p<=val) { *err = g_strdup("not an integer"); return FALSE; } else if (lvl < 0 || lvl > 10) { *err = g_strdup_printf("invalid level=%d (min=0 max=5)",lvl); return FALSE; } wait_chldn = lvl; return TRUE; } static param_t disp_params[] = { #ifdef DEBUG_DISPATCHER {"dbg_level", param_get_dbg_level, param_set_dbg_level}, # endif {"wait_chldn",param_get_wait_chldn,param_set_wait_chldn}, {NULL,NULL,NULL} }; static param_t* get_paramset(char* name) { int i; for (i = 0; disp_params[i].name != NULL;i++) { if (strcmp(name,disp_params[i].name) == 0 ) return &(disp_params[i]); } return NULL; } void dispatcher_err(int err, const char* fmt, ...) { size_t len= 1024; guint8* b[len]; char err_str[len]; GByteArray* em; va_list ap; va_start(ap, fmt); g_vsnprintf(err_str,len,fmt,ap); va_end(ap); fprintf(stderr, "%s[%d]: error=%d '%s'\n", "dispatcher", dispatcher->pid, err, err_str); em = (void*)dispatcher->enc.to_parent->error(err, err_str); echld_write_frame(dispatcher->parent_out, em, 0, ECHLD_ERROR, dispatcher->reqh_id, NULL); g_ptr_array_free((void*)em,TRUE); } static struct dispatcher_child* dispatcher_get_child(struct dispatcher* d, guint16 chld_id) { int i; struct dispatcher_child* cc = d->children; int max_children = d->max_children; for(i = 0; i < max_children; i++) { struct dispatcher_child* c = &(c[i]); if (c->chld_id == chld_id) return c; } return NULL; } static void dispatcher_clear_child(struct dispatcher_child* c) { echld_reset_reader(&(c->reader), -1, 4096); c->chld_id = 0; c->write_fd = 0; c->pid = 0; c->closing = 0; } static void preinit_epan() { /* Here we do initialization of parts of epan that will be the same for every child we fork */ } static void dispatcher_clear() { /* remove unnecessary stuff for the working child */ } void dispatcher_reaper(int sig) { int status; int i; struct dispatcher_child* cc = dispatcher->children; int max_children = dispatcher->max_children; int pid = waitpid(-1, &status, WNOHANG); size_t len= 1024; guint8* b[len]; GByteArray* em; for(i = 0; i < max_children; i++) { struct dispatcher_child* c = &(cc[i]); if ( c->pid == pid ) { if (c->closing || dispatcher->closing) { em = (void*)dispatcher->enc.to_parent->child_dead("OK"); } else { /* here we do collect crash data !!! */ em = (void*)dispatcher->enc.to_parent->child_dead("Unexpected, probably crashed"); dispatcher_err(ECHLD_ERR_CRASHED_CHILD, "Unexpected dead: pid=%d chld_id=%d", pid, c->chld_id); } echld_write_frame(dispatcher->parent_out, em, c->chld_id, ECHLD_CHILD_DEAD, 0, NULL); dispatcher_clear_child(c); g_byte_array_free(em,TRUE); return; } } dispatcher_err(ECHLD_ERR_UNKNOWN_PID, "Unkown child pid: %d", pid); } static void dispatcher_destroy() { int i; int max_children = dispatcher->max_children; struct dispatcher_child* cc = dispatcher->children; /* destroy the dispatcher stuff at closing */ dispatcher->closing = TRUE; /* kill all alive children */ for(i = 0; i < max_children; i++) { struct dispatcher_child* c = &(cc[i]); if ( c->chld_id ) { kill(c->pid,SIGTERM); DISP_DBG((1,"Killing chld_id=%d pid=%d")); continue; } } exit(6666); } /* stuff coming from child going to parent */ static int dispatch_to_parent(guint8* b, size_t len, echld_chld_id_t chld_id, echld_msg_type_t type, echld_reqh_id_t reqh_id, void* data) { struct dispatcher_child* c = data; dispatcher->reqh_id = reqh_id; /* TODO: timeouts, clear them */ /* TODO: keep stats */ GByteArray in_ba; in_ba.data = b; in_ba.len = len; if (chld_id != c->chld_id) { goto misbehabing; } switch(type) { case ECHLD_ERROR: break; case ECHLD_TIMED_OUT: break; case ECHLD_HELLO: c->state = IDLE; break; case ECHLD_CLOSING: c->closing = TRUE; c->state = CLOSED; break; case ECHLD_PARAM: break; case ECHLD_PONG: break; case ECHLD_FILE_INFO: break; // file_info(pre_encoded) case ECHLD_FILTER_CKD: break; // filter_ckd(ok,df) case ECHLD_FILE_OPENED: c->state = READING; break; case ECHLD_INTERFACE_INFO: break; // intf_info(pre-encoded) case ECHLD_INTERFACE_OPENED: c->state = READY; break; case ECHLD_CAPTURE_STARTED: c->state = CAPTURING; break; case ECHLD_NOTIFY: break; // notify(pre-encoded) case ECHLD_PACKET_SUM: break; // packet_sum(pre-encoded) case ECHLD_TREE: break; //tree(framenum, tree(pre-encoded) ) case ECHLD_BUFFER: break; // buffer (name,range,totlen,data) case ECHLD_EOF: c->state = DONE; break; case ECHLD_CAPTURE_STOPPED: c->state = DONE; break; case ECHLD_NOTE_ADDED: break; case ECHLD_PACKET_LIST: break; // packet_list(name,filter,range); case ECHLD_FILE_SAVED: break; default: goto misbehabing; } return echld_write_frame(dispatcher->parent_out, &in_ba, chld_id, type, reqh_id, NULL); misbehabing: c->state = ERRORED; c->closing = TRUE; kill(c->pid,SIGTERM); dispatcher_err(ECHLD_ERR_CRASHED_CHILD,"chld_id=%d",chld_id); return 0; } void dispatch_new_child(struct dispatcher* dd) { struct dispatcher_child* c = dispatcher_get_child(dd, 0); int reqh_id = dd->reqh_id; int pid; if ( c ) { int parent_pipe_fds[2]; int child_pipe_fds[2]; int pipe_to_parent; int pipe_from_parent; int pipe_to_child; int pipe_from_child; if( pipe(parent_pipe_fds) < 0) { dispatcher_err(ECHLD_ERR_CANNOT_FORK,"CANNOT OPEN PARENT PIPE: %s",strerror(errno)); return; } pipe_from_parent = parent_pipe_fds[0]; pipe_to_child = parent_pipe_fds[1]; if( pipe(child_pipe_fds) < 0) { close(pipe_from_parent); close(pipe_to_child); dispatcher_err(ECHLD_ERR_CANNOT_FORK,"CANNOT OPEN CHILD PIPE: %s",strerror(errno)); return; } pipe_from_child = child_pipe_fds[0]; pipe_to_parent = child_pipe_fds[1]; switch (( pid = fork() )) { case -1: { close(pipe_to_child); close(pipe_to_parent); close(pipe_from_child); close(pipe_from_parent); dispatcher_err(ECHLD_ERR_CANNOT_FORK,"CANNOT FORK: %s",strerror(errno)); return; } case 0: { /* I'm the child */ int i; int fdt_len = getdtablesize(); dispatcher_clear(dd); for(i=0;ireader), pipe_from_child,4096); c->write_fd = pipe_to_child; c->pid = pid; dispatcher->nchildren++; /* configure child */ echld_write_frame(pipe_to_child, &out_ba, c->chld_id, ECHLD_NEW_CHILD, dispatcher->reqh_id, NULL); return; } } } else { dispatcher_err(ECHLD_ERR_CANNOT_FORK, "MAX CHILDREN REACHED: max_children=%d",dispatcher->max_children); return; } } /* process signals sent from parent */ static int dispatch_to_child(guint8* b, size_t len, echld_chld_id_t chld_id, echld_msg_type_t type, echld_reqh_id_t reqh_id, void* data) { struct dispatcher* disp = data; disp->reqh_id = reqh_id; GByteArray in_ba; in_ba.data = b; in_ba.len = len; if (chld_id == 0) { /* these are messages to the dispatcher itself */ switch(type) { case ECHLD_CLOSE_CHILD: dispatcher_destroy(); return 0; case ECHLD_PING: echld_write_frame(disp->parent_out, &in_ba, chld_id, ECHLD_PONG, reqh_id, NULL); return 0; case ECHLD_NEW_CHILD: dispatch_new_child(disp); return 0; case ECHLD_SET_PARAM:{ char* param; char* value; if ( disp->dec.from_parent->set_param(b,len,¶m,&value) ) { GByteArray* ba; param_t* p = get_paramset(param); char* err; if (!p) { dispatcher_err(ECHLD_CANNOT_SET_PARAM,"no such param='%s'",param); return 0; } if (! p->set ) { dispatcher_err(ECHLD_CANNOT_SET_PARAM,"reason='read only'"); return 0; } if (! p->set(value,&err) ) { dispatcher_err(ECHLD_CANNOT_SET_PARAM,"reason='%s'",err); g_free(err); return 0; } ba = (void*)disp->enc.to_parent->param(param,value); DISP_RESP(ba,ECHLD_PARAM); g_byte_array_free(ba,TRUE); DISP_DBG((1,"Set Param: param='%s' value='%s'",param,value)); return 0; } else { dispatcher_err(ECHLD_CANNOT_SET_PARAM,"reason='decoder error'"); return 0; } } case ECHLD_GET_PARAM: { GByteArray* ba; char* param; if ( disp->dec.from_parent->get_param(b,len,¶m) ) { char* err; char* val; param_t* p = get_paramset(param); if (!p) { dispatcher_err(ECHLD_CANNOT_GET_PARAM,"no such param='%s'",param); return 0; } if (! p->get ) { dispatcher_err(ECHLD_CANNOT_SET_PARAM,"reason='write only'"); return 0; } if (!(val = p->get(&err))) { dispatcher_err(ECHLD_CANNOT_GET_PARAM,"reason='%s'",err); g_free(err); return 0; } ba = (void*)disp->enc.to_parent->param(param,val); DISP_RESP(ba,ECHLD_PARAM); g_byte_array_free(ba,TRUE); DISP_DBG((2,"Get Param: param='%s' value='%s'",param,val)); return 0; } else { dispatcher_err(ECHLD_CANNOT_GET_PARAM,"reason='decoder error'"); return 0; } } default: dispatcher_err(ECHLD_ERR_WRONG_MSG, "wrong message to dispatcher type='%c'", type); return 0; } } else { struct dispatcher_child* c; if (! (c = dispatcher_get_child(dispatcher, chld_id)) ) { dispatcher_err(ECHLD_ERR_NO_SUCH_CHILD, "wrong chld_id %d", chld_id); return 0; } switch(type) { case ECHLD_CLOSE_CHILD: c->closing = TRUE; c->state = CLOSED; goto relay_frame; case ECHLD_OPEN_FILE: c->state = READING; goto relay_frame; case ECHLD_OPEN_INTERFACE: c->state = READY; goto relay_frame; case ECHLD_START_CAPTURE: c->state = CAPTURING; goto relay_frame; case ECHLD_STOP_CAPTURE: c->state = DONE; goto relay_frame; case ECHLD_SAVE_FILE: case ECHLD_APPLY_FILTER: case ECHLD_SET_PARAM: case ECHLD_GET_PARAM: case ECHLD_PING: case ECHLD_LIST_FILES: case ECHLD_CHK_FILTER: case ECHLD_LIST_INTERFACES: case ECHLD_GET_SUM: case ECHLD_GET_TREE: case ECHLD_GET_BUFFER: case ECHLD_ADD_NOTE: relay_frame: return echld_write_frame(c->write_fd, &in_ba, chld_id, type, reqh_id, NULL); default: dispatcher_err(ECHLD_ERR_WRONG_MSG, "wrong message %d %c", reqh_id, type); return 0; } } } int dispatcher_loop() { int parent_out = dispatcher->parent_out; int parent_in = dispatcher->parent_in.fd; struct dispatcher_child* children = dispatcher->children; do { fd_set rfds; fd_set efds; struct timeval timeout; struct dispatcher_child* c; int nfds; FD_ZERO(&rfds); FD_ZERO(&efds); FD_SET(parent_in,&rfds); FD_SET(parent_in,&efds); FD_SET(parent_out,&efds); for (c = children, nfds = 0; c->pid; c++) { if (c->chld_id) { FD_SET(c->reader.fd, &rfds); FD_SET(c->reader.fd, &efds); } nfds++; } nfds = select(nfds, &rfds, NULL, &efds, &timeout); if ( FD_ISSET(parent_in, &efds) || FD_ISSET(parent_out, &efds) ) { /* XXX deep shit */ break; } if (FD_ISSET(parent_in, &rfds)) { int st = echld_read_frame(&(dispatcher->parent_in), dispatch_to_child, dispatcher); if (st < 0) { /* XXX */ continue; } } for (c=children; c->pid; c++) { if (c->chld_id) { if ( FD_ISSET(c->reader.fd,&efds) ) { /* XXX cleanup child and report */ continue; } if (FD_ISSET(c->reader.fd,&rfds)) { int st = echld_read_frame(&(c->reader), dispatch_to_parent, c); if (st < 0) { /* XXX cleanup child and report */ continue; } continue; } } } } while(1); /* won't */ return 1; } void echld_dispatcher_start(int* in_pipe_fds, int* out_pipe_fds) { static struct dispatcher d; int fdt_len = getdtablesize(); int i; preinit_epan(); signal(SIGCHLD,dispatcher_reaper); dispatcher = &d; echld_init_reader(&(d.parent_in),in_pipe_fds[0],4096); d.parent_out = out_pipe_fds[1]; d.children = g_malloc0(ECHLD_MAX_CHILDREN * sizeof(struct dispatcher_child)); d.max_children = ECHLD_MAX_CHILDREN; d.nchildren = 0; d.reqh_id = -1; d.pid = getpid(); echld_get_all_codecs(&(d.enc.to_parent), &(d.dec.from_parent), &(d.enc.to_child), &(d.dec.from_child)); dispatcher_clear(); /* close all fds but those used */ for(i=0;i