/*! \brief Main serving funtion This function is the one which does the job. It is the main() of the child thread, which is created as soon as a new connection is accepted. \param ptr: a void pointer that keeps the reference of the 'pthread_chain' value corrisponding to this thread. This variable is casted into a 'pthread_chain' value in order to retrieve the socket we're currently using, the therad ID, and some pointers to the previous and next elements into this struct. \return None. */ void daemon_serviceloop( void *ptr ) { char errbuf[PCAP_ERRBUF_SIZE + 1]; // keeps the error string, prior to be printed char source[PCAP_BUF_SIZE]; // keeps the string that contains the interface to open struct rpcap_header header; // RPCAP message general header pcap_t *fp= NULL; // pcap_t main variable struct daemon_slpars *pars; // parameters related to the present daemon loop pthread_t threaddata= 0; // handle to the 'read from daemon and send to client' thread unsigned int ifdrops, ifrecv, krnldrop, svrcapt; // needed to save the values of the statistics struct rpcap_sampling samp_param; // in case sampling has been requested // Structures needed for the select() call fd_set rfds; // set of socket descriptors we have to check struct timeval tv; // maximum time the select() can block waiting for data int retval; // select() return value pars= (struct daemon_slpars *) ptr; *errbuf= 0; // Initialize errbuf // If we're in active mode, this is not a separate thread if (! pars->isactive) { // Modify thread params so that it can be killed at any time if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) ) goto end; if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL) ) goto end; } auth_again: // If we're in active mode, we have to check for the initial timeout if (!pars->isactive) { FD_ZERO(&rfds); // We do not have to block here tv.tv_sec = RPCAP_TIMEOUT_INIT; tv.tv_usec = 0; FD_SET(pars->sockctrl, &rfds); retval = select(pars->sockctrl + 1, &rfds, NULL, NULL, &tv); if (retval == -1) { sock_geterror("select(): ", errbuf, PCAP_ERRBUF_SIZE); rpcap_senderror(pars->sockctrl, errbuf, PCAP_ERR_NETW, NULL); goto end; } // The timeout has expired // So, this was a fake connection. Drop it down if (retval == 0) { rpcap_senderror(pars->sockctrl, "The RPCAP initial timeout has expired", PCAP_ERR_INITTIMEOUT, NULL); goto end; } } retval= daemon_checkauth(pars->sockctrl, pars->nullAuthAllowed, errbuf); if (retval) { // the other user requested to close the connection // It can be also the case of 'active mode', in which this host is not // allowed to connect to the other peer; in that case, it drops down the connection if (retval == -3) goto end; // It can be an authentication failure or an unrecoverable error rpcap_senderror(pars->sockctrl, errbuf, PCAP_ERR_AUTH, NULL); // authentication error if (retval == -2) { // suspend for 1 sec // WARNING: this day is inserted only in this point; if the user drops down the connection // and it connects again, this suspension time does not have any effects. pthread_suspend(RPCAP_SUSPEND_WRONGAUTH*1000); goto auth_again; } // Unrecoverable error if (retval == -1) goto end; } while (1) { int retval; errbuf[0]= 0; // clear errbuf // Avoid zombies connections; check if the connection is opens but no commands are performed // from more than RPCAP_TIMEOUT_RUNTIME // Conditions: // - I have to be in normal mode (no active mode) // - if the device is open, I don't have to be in the middle of a capture (fp->rmt_sockdata) // - if the device is closed, I have always to check if a new command arrives // // Be carefully: the capture can have been started, but an error occurred (so fp != NULL, but // rmt_sockdata is 0 if ( (!pars->isactive) && ( (fp == NULL) || ( (fp != NULL) && (fp->rmt_sockdata == 0) ) )) { // Check for the initial timeout FD_ZERO(&rfds); // We do not have to block here tv.tv_sec = RPCAP_TIMEOUT_RUNTIME; tv.tv_usec = 0; FD_SET(pars->sockctrl, &rfds); retval = select(pars->sockctrl + 1, &rfds, NULL, NULL, &tv); if (retval == -1) { sock_geterror("select(): ", errbuf, PCAP_ERRBUF_SIZE); rpcap_senderror(pars->sockctrl, errbuf, PCAP_ERR_NETW, NULL); goto end; } // The timeout has expired // So, this was a fake connection. Drop it down if (retval == 0) { SOCK_ASSERT("The RPCAP runtime timeout has expired", 1); rpcap_senderror(pars->sockctrl, "The RPCAP runtime timeout has expired", PCAP_ERR_RUNTIMETIMEOUT, NULL); goto end; } } if (sock_recv(pars->sockctrl, (char *) &header, sizeof(struct rpcap_header), SOCK_RECEIVEALL_YES, errbuf, PCAP_ERRBUF_SIZE) == -1) goto end; // Checks if the message is correct // In case it is wrong, it discard the data retval= rpcap_checkmsg(errbuf, pars->sockctrl, &header, RPCAP_MSG_FINDALLIF_REQ, RPCAP_MSG_OPEN_REQ, RPCAP_MSG_STARTCAP_REQ, RPCAP_MSG_UPDATEFILTER_REQ, RPCAP_MSG_STATS_REQ, RPCAP_MSG_ENDCAP_REQ, RPCAP_MSG_SETSAMPLING_REQ, RPCAP_MSG_CLOSE, RPCAP_MSG_ERROR, 0); switch (retval) { case -3: // Unrecoverable network error goto end; // Do nothing; just exit from findalldevs; the error code is already into the errbuf case -2: // The other endpoint send a message that is not allowed here { rpcap_senderror(pars->sockctrl, "The RPCAP daemon received a message that is not valid", PCAP_ERR_WRONGMSG, errbuf); } case -1: // The other endpoint has a version number that is not compatible with our { rpcap_senderror(pars->sockctrl, "RPCAP version number mismatch", PCAP_ERR_WRONGVER, errbuf); } break; case RPCAP_MSG_FINDALLIF_REQ: { // Checks that the header does not contain other data; if so, discard it if (ntohl(header.plen)) sock_discard(pars->sockctrl, ntohl(header.plen), errbuf, PCAP_ERRBUF_SIZE); if (daemon_findalldevs(pars->sockctrl, errbuf) ) SOCK_ASSERT(errbuf, 1); break; }; case RPCAP_MSG_OPEN_REQ: { retval= daemon_opensource(pars->sockctrl, source, sizeof(source), ntohl(header.plen), errbuf); if (retval == -1) SOCK_ASSERT(errbuf, 1); break; }; case RPCAP_MSG_SETSAMPLING_REQ: { retval= daemon_setsampling(pars->sockctrl, &samp_param, ntohl(header.plen), errbuf); if (retval == -1) SOCK_ASSERT(errbuf, 1); break; }; case RPCAP_MSG_STARTCAP_REQ: { fp= daemon_startcapture(pars->sockctrl, &threaddata, source, pars->isactive, &samp_param, ntohl(header.plen), errbuf); if (fp == NULL) SOCK_ASSERT(errbuf, 1); break; }; case RPCAP_MSG_UPDATEFILTER_REQ: { if (fp) { if (daemon_updatefilter(fp, ntohl(header.plen)) ) SOCK_ASSERT(fp->errbuf, 1); } else { rpcap_senderror(pars->sockctrl, "Device not opened. Cannot update filter", PCAP_ERR_UPDATEFILTER, errbuf); } break; }; case RPCAP_MSG_STATS_REQ: { // Checks that the header does not contain other data; if so, discard it if (ntohl(header.plen)) sock_discard(pars->sockctrl, ntohl(header.plen), errbuf, PCAP_ERRBUF_SIZE); if (fp) { if (daemon_getstats(fp) ) SOCK_ASSERT(fp->errbuf, 1); } else { SOCK_ASSERT("GetStats: this call should't be allowed here", 1); if (daemon_getstatsnopcap(pars->sockctrl, ifdrops, ifrecv, krnldrop, svrcapt, errbuf) ) SOCK_ASSERT(errbuf, 1); // we have to keep compatibility with old applications, which ask for statistics // also when the capture has already stopped // rpcap_senderror(pars->sockctrl, "Device not opened. Cannot get statistics", PCAP_ERR_GETSTATS, errbuf); } break; }; case RPCAP_MSG_ENDCAP_REQ: // The other endpoint close the current capture session { if (fp) { struct pcap_stat stats; // Save statistics (we can need them in the future) if (pcap_stats(fp, &stats) ) { ifdrops= stats.ps_ifdrop; ifrecv= stats.ps_recv; krnldrop= stats.ps_drop; svrcapt= fp->md.TotCapt; } else ifdrops= ifrecv= krnldrop= svrcapt= 0; if ( daemon_endcapture(fp, &threaddata, errbuf) ) SOCK_ASSERT(errbuf, 1); fp= NULL; } else { rpcap_senderror(pars->sockctrl, "Device not opened. Cannot close the capture", PCAP_ERR_ENDCAPTURE, errbuf); } break; }; case RPCAP_MSG_CLOSE: // The other endpoint close the pcap session { // signal to the main that the user closed the control connection // This is used only in case of active mode pars->activeclose= 1; SOCK_ASSERT("The other end system asked to close the connection.", 1); goto end; break; }; case RPCAP_MSG_ERROR: // The other endpoint reported an error { // Do nothing; just exit; the error code is already into the errbuf SOCK_ASSERT(errbuf, 1); break; }; default: { SOCK_ASSERT("Internal error.", 1); break; }; } } end: // The child thread is about to end // perform pcap_t cleanup, in case it has not been done if (fp) { if (threaddata) { pthread_cancel(threaddata); threaddata= 0; } if (fp->rmt_sockdata) { sock_close(fp->rmt_sockdata, NULL, 0); fp->rmt_sockdata= 0; } pcap_close(fp); fp= NULL; } // Print message and exit SOCK_ASSERT("I'm exiting from the child loop", 1); SOCK_ASSERT(errbuf, 1); if (!pars->isactive) { if (pars->sockctrl) sock_close(pars->sockctrl, NULL, 0); free(pars); #ifdef WIN32 pthread_exit(0); #endif } }
void main_startup(void) { char errbuf[PCAP_ERRBUF_SIZE + 1]; // keeps the error string, prior to be printed struct addrinfo *addrinfo; // keeps the addrinfo chain; required to open a new socket int i; #ifdef WIN32 pthread_t threadId; // Pthread variable that keeps the thread structures pthread_attr_t detachedAttribute; // PThread attribute needed to create the thread as detached #else pid_t pid; #endif i= 0; addrinfo= NULL; memset(errbuf, 0, sizeof(errbuf) ); // Starts all the active threads while ( (activelist[i].address[0] != 0) && (i < MAX_ACTIVE_LIST) ) { activelist[i].ai_family= mainhints.ai_family; // use global preselected interface if not set in config file if ((activelist[i].ifname[0] == '\0') && (rpcapd_opt.preselected_ifname[0] != '\0')) { snprintf(activelist[i].ifname, sizeof(activelist[i].ifname), "%s", rpcapd_opt.preselected_ifname); } #ifdef WIN32 /* GV we need this to create the thread as detached. */ /* GV otherwise, the thread handle is not destroyed */ pthread_attr_init(&detachedAttribute); pthread_attr_setdetachstate(&detachedAttribute, PTHREAD_CREATE_DETACHED); if ( pthread_create( &threadId, &detachedAttribute, (void *) &main_active, (void *) &activelist[i]) ) { log_warn("Error creating the active child thread"); pthread_attr_destroy(&detachedAttribute); continue; } pthread_attr_destroy(&detachedAttribute); #else if ( (pid= fork() ) == 0) // I am the child { main_active( (void *) &activelist[i]); exit(0); } #endif i++; } /* The code that manages the active connections is not blocking; vice versa, the code that manages the passive connection is blocking. So, if the user do not want to run in passive mode, we have to block the main thread here, otherwise the program ends and all threads are stopped. WARNING: this means that in case we have only active mode, the program does not terminate even if all the child thread terminates. The user has always to press Ctrl+C (or send a SIGTERM) to terminate the program. */ if (passivemode) { struct addrinfo *tempaddrinfo; // Do the work if (sock_initaddress((address[0]) ? address : NULL, port, &mainhints, &addrinfo, errbuf, PCAP_ERRBUF_SIZE) == -1) { log_warn("%s", errbuf); return; } tempaddrinfo= addrinfo; while (tempaddrinfo) { SOCKET *socktemp; if ( (sockmain= sock_open(tempaddrinfo, SOCKOPEN_SERVER, SOCKET_MAXCONN, errbuf, PCAP_ERRBUF_SIZE)) == -1) { log_warn("%s", errbuf); tempaddrinfo= tempaddrinfo->ai_next; continue; } // This trick is needed in order to allow the child thread to save the 'sockmain' variable // withouth getting it overwritten by the sock_open, in case we want to open more than one waiting sockets // For instance, the pthread_create() will accept the socktemp variable, and it will deallocate immediately that variable socktemp= (SOCKET *) malloc (sizeof (SOCKET)); if (socktemp == NULL) exit(0); *socktemp= sockmain; #ifdef WIN32 /* GV we need this to create the thread as detached. */ /* GV otherwise, the thread handle is not destroyed */ pthread_attr_init(&detachedAttribute); pthread_attr_setdetachstate(&detachedAttribute, PTHREAD_CREATE_DETACHED); if ( pthread_create( &threadId, &detachedAttribute, (void *) &main_passive, (void *) socktemp ) ) { log_warn("Error creating the passive child thread"); pthread_attr_destroy(&detachedAttribute); continue; } pthread_attr_destroy(&detachedAttribute); #else if ( (pid= fork() ) == 0) // I am the child { main_passive( (void *) socktemp); return; } #endif tempaddrinfo= tempaddrinfo->ai_next; } freeaddrinfo(addrinfo); } // All the previous calls are no blocking, so the main line of execution goes here // and I have to avoid that the program terminates while (1) pthread_suspend(10*60*1000); // it wakes up every 10 minutes; it seems to me reasonable }
/*! \brief 'true' main of the program in case the active mode is turned on. It does not have any return value nor parameters. This function loops forever trying to connect to the remote host, until the daemon is turned down. \param ptr: it keeps the 'activepars' parameters. It is a 'void *' just because pthreads want this format. */ void main_active(void *ptr) { char errbuf[PCAP_ERRBUF_SIZE + 1]; // keeps the error string, prior to be printed SOCKET sockctrl; // keeps the socket ID for this control connection struct addrinfo hints; // temporary struct to keep settings needed to open the new socket struct addrinfo *addrinfo; // keeps the addrinfo chain; required to open a new socket struct active_pars *activepars; struct daemon_slpars *pars; // parameters needed by the daemon_serviceloop() activepars= (struct active_pars *) ptr; // Prepare to open a new server socket memset(&hints, 0, sizeof(struct addrinfo)); // WARNING Currently it supports only ONE socket family among IPv4 and IPv6 hints.ai_family = AF_INET; // PF_UNSPEC to have both IPv4 and IPv6 server hints.ai_socktype = SOCK_STREAM; hints.ai_family= activepars->ai_family; log_info("Connecting to host %s, port %s, using protocol %s", activepars->address, activepars->port, (hints.ai_family == AF_INET) ? "IPv4": (hints.ai_family == AF_INET6) ? "IPv6" : "Unspecified"); // Initialize errbuf memset(errbuf, 0, sizeof(errbuf) ); // Do the work if (sock_initaddress(activepars->address, activepars->port, &hints, &addrinfo, errbuf, PCAP_ERRBUF_SIZE) == -1) { log_warn("%s", errbuf); return; } while (1) { int activeclose; if ( (sockctrl= sock_open(addrinfo, SOCKOPEN_CLIENT, 0, errbuf, PCAP_ERRBUF_SIZE)) == -1) { log_warn("%s", errbuf); snprintf(errbuf, PCAP_ERRBUF_SIZE, "Error connecting to host %s, port %s, using protocol %s", activepars->address, activepars->port, (hints.ai_family == AF_INET) ? "IPv4": (hints.ai_family == AF_INET6) ? "IPv6" : "Unspecified" ); log_warn("%s", errbuf); pthread_suspend(RPCAP_ACTIVE_WAIT * 1000); continue; } pars= (struct daemon_slpars *) malloc ( sizeof(struct daemon_slpars) ); if (pars == NULL) { snprintf(errbuf, PCAP_ERRBUF_SIZE, "malloc() failed: %s", pcap_strerror(errno)); continue; } pars->sockctrl= sockctrl; pars->activeclose= 0; pars->isactive= 1; pars->nullAuthAllowed= nullAuthAllowed; pars->preselected_ifname = (activepars->ifname[0] != '\0') ? activepars->ifname : NULL; daemon_serviceloop( (void *) pars); activeclose= pars->activeclose; free(pars); // If the connection is closed by the user explicitely, don't try to connect to it again // just exit the program if (activeclose == 1) break; } }
static inline void ccxx_suspend(cctid_t tid) { pthread_suspend(tid); }