/* Add a new event for a given IOD. msevents are stored in separate event lists * (in the nsock pool) and are grouped by IOD within each list. * * This function appends the event _before_ the first similar event we have for * the given IOD, or append it to the end of the list if no similar event is * already present. * * Note that adding the event before the similar ones is important for * reentrancy, as it will prevent the new event to be processed in the event * loop just after its addition. */ static int iod_add_event(msiod *iod, msevent *nse) { switch(nse->type) { case NSE_TYPE_CONNECT: case NSE_TYPE_CONNECT_SSL: if (iod->first_connect) iod->first_connect = gh_list_insert_before(&iod->nsp->connect_events, iod->first_connect, nse); else iod->first_connect = gh_list_append(&iod->nsp->connect_events, nse); break; case NSE_TYPE_READ: if (iod->first_read) iod->first_read = gh_list_insert_before(&iod->nsp->read_events, iod->first_read, nse); else iod->first_read = gh_list_append(&iod->nsp->read_events, nse); break; case NSE_TYPE_WRITE: if (iod->first_write) iod->first_write = gh_list_insert_before(&iod->nsp->write_events, iod->first_write, nse); else iod->first_write = gh_list_append(&iod->nsp->write_events, nse); break; #if HAVE_PCAP case NSE_TYPE_PCAP_READ: { char add_read = 0, add_pcap_read = 0; #if PCAP_BSD_SELECT_HACK /* BSD hack mode: add event to both read and pcap_read lists */ add_read = add_pcap_read = 1; #else if (((mspcap *)iod->pcap)->pcap_desc >= 0) { add_read = 1; } else { add_pcap_read = 1; } #endif if (add_read) { if (iod->first_read) iod->first_read = gh_list_insert_before(&iod->nsp->read_events, iod->first_read, nse); else iod->first_read = gh_list_append(&iod->nsp->read_events, nse); } if (add_pcap_read) { if (iod->first_pcap_read) iod->first_pcap_read = gh_list_insert_before(&iod->nsp->pcap_read_events, iod->first_pcap_read, nse); else iod->first_pcap_read = gh_list_append(&iod->nsp->pcap_read_events, nse); } break; } #endif default: fatal("Unknown event type (%d) for IOD #%d\n", nse->type, iod->id); } return 0; }
/* A proxy chain is a comma-separated list of proxy specification strings: * proto://[user:pass@]host[:port] */ int nsock_proxychain_new(const char *proxystr, nsock_proxychain *chain, nsock_pool nspool) { struct npool *nsp = (struct npool *)nspool; struct proxy_chain *pxc, **pchain = (struct proxy_chain **)chain; *pchain = NULL; pxc = (struct proxy_chain *)safe_malloc(sizeof(struct proxy_chain)); gh_list_init(&pxc->nodes); if (proxystr) { struct proxy_parser *parser; parser = proxy_parser_new(proxystr); while (!parser->done) { gh_list_append(&pxc->nodes, &parser->value->nodeq); proxy_parser_next(parser); } proxy_parser_delete(parser); } if (nsp) { if (nsock_pool_set_proxychain(nspool, pxc) < 0) { nsock_proxychain_delete(pxc); return -1; } } *pchain = pxc; return 1; }
static int nevent_unref(struct npool *nsp, struct nevent *nse) { switch (nse->type) { case NSE_TYPE_CONNECT: case NSE_TYPE_CONNECT_SSL: gh_list_remove(&nsp->connect_events, &nse->nodeq_io); break; case NSE_TYPE_READ: gh_list_remove(&nsp->read_events, &nse->nodeq_io); break; case NSE_TYPE_WRITE: gh_list_remove(&nsp->write_events, &nse->nodeq_io); break; #if HAVE_PCAP case NSE_TYPE_PCAP_READ: { char read = 0; char pcap = 0; #if PCAP_BSD_SELECT_HACK read = pcap = 1; #else if (((mspcap *)nse->iod->pcap)->pcap_desc >= 0) read = 1; else pcap = 1; #endif /* PCAP_BSD_SELECT_HACK */ if (read) gh_list_remove(&nsp->read_events, &nse->nodeq_io); if (pcap) gh_list_remove(&nsp->pcap_read_events, &nse->nodeq_pcap); break; } #endif /* HAVE_PCAP */ case NSE_TYPE_TIMER: /* Nothing to do */ break; default: fatal("Unknown event type %d", nse->type); } gh_list_append(&nsp->free_events, &nse->nodeq_io); return 0; }
/* This version allows you to associate an existing sd with the msi so that you can read/write it using the nsock infrastructure. For example, you may want to watch for data from STDIN_FILENO at the same time as you read/wrtie various sockets. Ths sd is dup()ed, so you may close or otherwise manipulate your copy. The duped copy will be destroyed when the nsi is destroyed */ nsock_iod nsi_new2(nsock_pool nsockp, int sd, void *userdata) { mspool *nsp = (mspool *) nsockp; msiod *nsi; nsi = (msiod *) gh_list_pop(&nsp->free_iods); if (!nsi) nsi = (msiod * ) safe_malloc(sizeof(msiod)); memset(nsi, 0, sizeof(*nsi)); if (sd == -1) { nsi->sd = -1; nsi->state = NSIOD_STATE_INITIAL; } else { nsi->sd = dup(sd); if (nsi->sd == -1) { free(nsi); return NULL; } nsock_unblock_socket(nsi->sd); nsi->state = NSIOD_STATE_UNKNOWN; } nsi->userdata = userdata; nsi->nsp = (mspool *) nsockp; nsi->events_pending = 0; #if HAVE_OPENSSL nsi->ssl_session = NULL; #endif nsi->id = nsp->next_iod_serial++; if (nsi->id == 0) nsi->id = nsp->next_iod_serial++; /* The nsp keeps track of active msiods so it can delete them if it is deleted */ nsi->entry_in_nsp_active_iods = gh_list_append(&nsi->nsp->active_iods, nsi); return (nsock_iod) nsi; }
void process_iod_events(struct npool *nsp, struct niod *nsi, int ev) { int i = 0; /* store addresses of the pointers to the first elements of each kind instead * of storing the values, as a connect can add a read for instance */ gh_lnode_t **start_elems[] = { &nsi->first_connect, &nsi->first_read, &nsi->first_write, #if HAVE_PCAP &nsi->first_pcap_read, #endif NULL }; gh_list_t *evlists[] = { &nsp->connect_events, &nsp->read_events, &nsp->write_events, #if HAVE_PCAP &nsp->pcap_read_events, #endif NULL }; assert(nsp == nsi->nsp); nsock_log_debug_all("Processing events on IOD %lu (ev=%d)", nsi->id, ev); /* We keep the events separate because we want to handle them in the * order: connect => read => write => timer for several reasons: * * 1) Makes sure we have gone through all the net i/o events before * a timer expires (would be a shame to timeout after the data was * available but before we delivered the events * * 2) The connect() results often lead to a read or write that can be * processed in the same cycle. In the same way, read() often * leads to write(). */ for (i = 0; evlists[i] != NULL; i++) { gh_lnode_t *current, *next, *last; /* for each list, get the last event and don't look past it as an event * could add another event in the same list and so on... */ last = gh_list_last_elem(evlists[i]); for (current = *start_elems[i]; current != NULL && gh_lnode_prev(current) != last; current = next) { struct nevent *nse; #if HAVE_PCAP if (evlists[i] == &nsi->nsp->pcap_read_events) nse = lnode_nevent2(current); else #endif nse = lnode_nevent(current); /* events are grouped by IOD. Break if we're done with the events for the * current IOD */ if (nse->iod != nsi) break; process_event(nsp, evlists[i], nse, ev); next = gh_lnode_next(current); if (nse->event_done) { /* event is done, remove it from the event list and update IOD pointers * to the first events of each kind */ update_first_events(nse); gh_list_remove(evlists[i], current); gh_list_append(&nsp->free_events, &nse->nodeq_io); if (nse->timeout.tv_sec) gh_heap_remove(&nsp->expirables, &nse->expire); } } } }
/* Adds an event to the appropriate nsp event list, handles housekeeping such as * adjusting the descriptor select/poll lists, registering the timeout value, * etc. */ void nsp_add_event(mspool *nsp, msevent *nse) { if (nsp->tracelevel > 5) nsock_trace(nsp, "NSE #%lu: Adding event", nse->id); /* First lets do the event-type independent stuff, starting with timeouts */ if (nse->event_done) { nsp->next_ev = nsock_tod; } else { if (nse->timeout.tv_sec != 0) { if (nsp->next_ev.tv_sec == 0) nsp->next_ev = nse->timeout; else if (TIMEVAL_AFTER(nsp->next_ev, nse->timeout)) nsp->next_ev = nse->timeout; } } nsp->events_pending++; /* Now we do the event type specific actions */ switch(nse->type) { case NSE_TYPE_CONNECT: case NSE_TYPE_CONNECT_SSL: if (!nse->event_done) { assert(nse->iod->sd >= 0); socket_count_read_inc(nse->iod); socket_count_write_inc(nse->iod); update_events(nse->iod, nsp, EV_READ|EV_WRITE|EV_EXCEPT, EV_NONE); } iod_add_event(nse->iod, nse); break; case NSE_TYPE_READ: if (!nse->event_done) { assert(nse->iod->sd >= 0); socket_count_read_inc(nse->iod); update_events(nse->iod, nsp, EV_READ, EV_NONE); #if HAVE_OPENSSL if (nse->iod->ssl) nse->sslinfo.ssl_desire = SSL_ERROR_WANT_READ; #endif } iod_add_event(nse->iod, nse); break; case NSE_TYPE_WRITE: if (!nse->event_done) { assert(nse->iod->sd >= 0); socket_count_write_inc(nse->iod); update_events(nse->iod, nsp, EV_WRITE, EV_NONE); #if HAVE_OPENSSL if (nse->iod->ssl) nse->sslinfo.ssl_desire = SSL_ERROR_WANT_WRITE; #endif } iod_add_event(nse->iod, nse); break; case NSE_TYPE_TIMER: gh_list_append(&nsp->timer_events, nse); break; #if HAVE_PCAP case NSE_TYPE_PCAP_READ: { mspcap *mp = (mspcap *)nse->iod->pcap; assert(mp); if (mp->pcap_desc >= 0) { /* pcap descriptor present */ if (!nse->event_done) { socket_count_readpcap_inc(nse->iod); update_events(nse->iod, nsp, EV_READ, EV_NONE); } if (nsp->tracelevel > 8) nsock_trace(nsp, "PCAP NSE #%lu: Adding event to READ_EVENTS", nse->id); #if PCAP_BSD_SELECT_HACK /* when using BSD hack we must do pcap_next() after select(). * Let's insert this pcap to bot queues, to selectable and nonselectable. * This will result in doing pcap_next_ex() just before select() */ if (nsp->tracelevel > 8) nsock_trace(nsp, "PCAP NSE #%lu: Adding event to PCAP_READ_EVENTS", nse->id); #endif } else { /* pcap isn't selectable. Add it to pcap-specific queue. */ if (nsp->tracelevel > 8) nsock_trace(nsp, "PCAP NSE #%lu: Adding event to PCAP_READ_EVENTS", nse->id); } iod_add_event(nse->iod, nse); break; } #endif default: assert(0); break; /* unreached */ } }
/* This version allows you to associate an existing sd with the msi so that you * can read/write it using the nsock infrastructure. For example, you may want * to watch for data from STDIN_FILENO at the same time as you read/write * various sockets. STDIN_FILENO is a special case, however. Any other sd is * dup()ed, so you may close or otherwise manipulate your copy. The duped copy * will be destroyed when the nsi is destroyed. */ nsock_iod nsock_iod_new2(nsock_pool nsockp, int sd, void *userdata) { struct npool *nsp = (struct npool *)nsockp; gh_lnode_t *lnode; struct niod *nsi; lnode = gh_list_pop(&nsp->free_iods); if (!lnode) { nsi = (struct niod *)safe_malloc(sizeof(*nsi)); memset(nsi, 0, sizeof(*nsi)); } else { nsi = container_of(lnode, struct niod, nodeq); } if (sd == -1) { nsi->sd = -1; nsi->state = NSIOD_STATE_INITIAL; } else if (sd == STDIN_FILENO) { nsi->sd = STDIN_FILENO; nsi->state = NSIOD_STATE_UNKNOWN; } else { nsi->sd = dup_socket(sd); if (nsi->sd == -1) { free(nsi); return NULL; } unblock_socket(nsi->sd); nsi->state = NSIOD_STATE_UNKNOWN; } nsi->first_connect = NULL; nsi->first_read = NULL; nsi->first_write = NULL; #if HAVE_PCAP nsi->first_pcap_read = NULL; nsi->readpcapsd_count = 0; #endif nsi->readsd_count = 0; nsi->write_count = 0; nsi->userdata = userdata; nsi->nsp = (struct npool *)nsockp; nsi->_flags = 0; nsi->read_count = 0; nsi->write_count = 0; nsi->hostname = NULL; nsi->ipopts = NULL; nsi->ipoptslen = 0; #if HAVE_OPENSSL nsi->ssl_session = NULL; #endif if (nsp->px_chain) { nsi->px_ctx = proxy_chain_context_new(nsp); } else { nsi->px_ctx = NULL; } nsi->id = nsp->next_iod_serial++; if (nsi->id == 0) nsi->id = nsp->next_iod_serial++; /* The nsp keeps track of active iods so it can delete them if it is deleted */ gh_list_append(&nsp->active_iods, &nsi->nodeq); nsock_log_info("nsock_iod_new (IOD #%lu)", nsi->id); return (nsock_iod)nsi; }
/* An internal function for cancelling an event when you already have a pointer * to the msevent (use nsock_event_cancel if you just have an ID). The * event_list passed in should correspond to the type of the event. For example, * with NSE_TYPE_READ, you would pass in &nsp->read_events;. elem is the list * element in event_list which holds the event. Pass a nonzero for notify if * you want the program owning the event to be notified that it has been * cancelled */ int msevent_cancel(mspool *nsp, msevent *nse, gh_list_t *event_list, gh_lnode_t *elem, int notify) { if (nse->event_done) { /* This event has already been marked for death somewhere else -- it will be * gone soon (and if we try to kill it now all hell will break loose due to * reentrancy. */ return 0; } nsock_log_info(nsp, "msevent_cancel on event #%li (type %s)", nse->id, nse_type2str(nse->type)); /* Now that we found the event... we go through the motions of cleanly * cancelling it */ switch (nse->type) { case NSE_TYPE_CONNECT: case NSE_TYPE_CONNECT_SSL: handle_connect_result(nsp, nse, NSE_STATUS_CANCELLED); break; case NSE_TYPE_READ: handle_read_result(nsp, nse, NSE_STATUS_CANCELLED); break; case NSE_TYPE_WRITE: handle_write_result(nsp, nse, NSE_STATUS_CANCELLED); break; case NSE_TYPE_TIMER: handle_timer_result(nsp, nse, NSE_STATUS_CANCELLED); break; #if HAVE_PCAP case NSE_TYPE_PCAP_READ: handle_pcap_read_result(nsp, nse, NSE_STATUS_CANCELLED); break; #endif default: fatal("Invalid nsock event type (%d)", nse->type); } assert(nse->event_done); if (nse->timeout.tv_sec) gh_heap_remove(&nsp->expirables, &nse->expire); if (event_list) { update_first_events(nse); gh_list_remove(event_list, elem); } gh_list_append(&nsp->free_events, &nse->nodeq_io); nsock_log_debug_all(nsp, "NSE #%lu: Removing event from list", nse->id); #if HAVE_PCAP #if PCAP_BSD_SELECT_HACK if (nse->type == NSE_TYPE_PCAP_READ) { nsock_log_debug_all(nsp, "PCAP NSE #%lu: CANCEL TEST pcap=%p read=%p curr=%p sd=%i", nse->id, &nsp->pcap_read_events, &nsp->read_events, event_list,((mspcap *)nse->iod->pcap)->pcap_desc); /* If event occurred, and we're in BSD_HACK mode, then this event was added to * two queues. read_event and pcap_read_event Of course we should * destroy it only once. I assume we're now in read_event, so just unlink * this event from pcap_read_event */ if (((mspcap *)nse->iod->pcap)->pcap_desc >= 0 && event_list == &nsp->read_events) { /* event is done, list is read_events and we're in BSD_HACK mode. So unlink * event from pcap_read_events */ gh_list_remove(&nsp->pcap_read_events, &nse->nodeq_pcap); nsock_log_debug_all(nsp, "PCAP NSE #%lu: Removing event from PCAP_READ_EVENTS", nse->id); } if (((mspcap *)nse->iod->pcap)->pcap_desc >= 0 && event_list == &nsp->pcap_read_events) { /* event is done, list is read_events and we're in BSD_HACK mode. * So unlink event from read_events */ gh_list_remove(&nsp->read_events, &nse->nodeq_io); nsock_log_debug_all(nsp, "PCAP NSE #%lu: Removing event from READ_EVENTS", nse->id); } } #endif #endif msevent_dispatch_and_delete(nsp, nse, notify); return 1; }
/* If nsp_new returned success, you must free the nsp when you are done with it * to conserve memory (and in some cases, sockets). After this call, nsp may no * longer be used. Any pending events are sent an NSE_STATUS_KILL callback and * all outstanding iods are deleted. */ void nsp_delete(nsock_pool ms_pool) { mspool *nsp = (mspool *)ms_pool; msevent *nse; msiod *nsi; int i; gh_lnode_t *current, *next; gh_list_t *event_lists[] = { &nsp->connect_events, &nsp->read_events, &nsp->write_events, #if HAVE_PCAP &nsp->pcap_read_events, #endif NULL }; assert(nsp); /* First I go through all the events sending NSE_STATUS_KILL */ for (i = 0; event_lists[i] != NULL; i++) { while (gh_list_count(event_lists[i]) > 0) { gh_lnode_t *lnode = gh_list_pop(event_lists[i]); assert(lnode); #if HAVE_PCAP if (event_lists[i] == &nsp->pcap_read_events) nse = lnode_msevent2(lnode); else #endif nse = lnode_msevent(lnode); assert(nse); nse->status = NSE_STATUS_KILL; nsock_trace_handler_callback(nsp, nse); nse->handler(nsp, nse, nse->userdata); if (nse->iod) { nse->iod->events_pending--; assert(nse->iod->events_pending >= 0); } msevent_delete(nsp, nse); } gh_list_free(event_lists[i]); } /* Kill timers too, they're not in event lists */ while (gh_heap_count(&nsp->expirables) > 0) { gh_hnode_t *hnode; hnode = gh_heap_pop(&nsp->expirables); nse = container_of(hnode, msevent, expire); if (nse->type == NSE_TYPE_TIMER) { nse->status = NSE_STATUS_KILL; nsock_trace_handler_callback(nsp, nse); nse->handler(nsp, nse, nse->userdata); msevent_delete(nsp, nse); gh_list_append(&nsp->free_events, &nse->nodeq_io); } } gh_heap_free(&nsp->expirables); /* foreach msiod */ for (current = gh_list_first_elem(&nsp->active_iods); current != NULL; current = next) { next = gh_lnode_next(current); nsi = container_of(current, msiod, nodeq); nsi_delete(nsi, NSOCK_PENDING_ERROR); gh_list_remove(&nsp->active_iods, current); gh_list_prepend(&nsp->free_iods, &nsi->nodeq); } /* Now we free all the memory in the free iod list */ while ((current = gh_list_pop(&nsp->free_iods))) { nsi = container_of(current, msiod, nodeq); free(nsi); } while ((current = gh_list_pop(&nsp->free_events))) { nse = lnode_msevent(current); free(nse); } gh_list_free(&nsp->active_iods); gh_list_free(&nsp->free_iods); gh_list_free(&nsp->free_events); nsock_engine_destroy(nsp); #if HAVE_OPENSSL if (nsp->sslctx != NULL) SSL_CTX_free(nsp->sslctx); #endif free(nsp); }
int main(int argc, char *argv[]) { gh_list lists[16]; gh_list_elem *current, *next; int num = 0; int ret; int i; for(i=0; i < 16; i++) gh_list_init(&lists[i]); for(num=25000; num < 50000; num++) { for(i=0; i < 16; i++) { gh_list_append(&lists[i], (void *)num); } } for(num=24999; num >= 0; num--) { for(i=0; i < 16; i++) { gh_list_prepend(&lists[i], (void *)num); } } for(num=0; num < 50000; num++) { for(i=0; i < 16; i++) { ret = (int) gh_list_pop(&lists[i]); if (ret != num) fatal("prepend_test: Bogus return value %d when expected %d\n", ret, num); } } for(i=0; i < 16; i++) { ret = (int) gh_list_pop(&lists[i]); if (ret != 0) fatal("Ret is bogus for list %d", i); } printf("Done with first set\n"); for(num=24999; num >= 0; num--) { for(i=0; i < 16; i++) { gh_list_prepend(&lists[i], (void *)num); } } for(num=25000; num < 50000; num++) { for(i=0; i < 16; i++) { gh_list_append(&lists[i], (void *)num); } } for(num=0; num < 50000; num++) { for(i=0; i < 16; i++) { ret = (int) gh_list_pop(&lists[i]); if (ret != num) fatal("prepend_test: Bogus return value %d when expected %d\n", ret, num); } } printf("Done with second set\n"); for(num=25000; num < 50000; num++) { for(i=0; i < 16; i++) { gh_list_append(&lists[i], (void *)num); } } for(num=24999; num >= 0; num--) { for(i=0; i < 16; i++) { gh_list_prepend(&lists[i], (void *)num); } } for(num=0; num < 50000; num++) { for(i=0; i < 16; i++) { ret = (int) gh_list_pop(&lists[i]); if (ret != num) fatal("prepend_test: Bogus return value %d when expected %d\n", ret, num); } } printf("Done with third set ...\n"); for(num=24999; num >= 0; num--) { for(i=0; i < 16; i++) { gh_list_prepend(&lists[i], (void *)num); } } for(num=25000; num < 50000; num++) { for(i=0; i < 16; i++) { gh_list_append(&lists[i], (void *)num); } } for(i=0; i < 16; i++) { num=0; for(current = GH_LIST_FIRST_ELEM(&lists[i]); current; current = next) { next = GH_LIST_ELEM_NEXT(current); if ((int)GH_LIST_ELEM_DATA(current) != num) fatal("Got %d when I expected %d\n", (int)GH_LIST_ELEM_DATA(current), num); gh_list_remove_elem(&lists[i], current); num++; } if (num != 50000) fatal("Number is %d, even though %d was expected", num, 50000); if (GH_LIST_COUNT(&lists[i]) != 0) { fatal("List should be empty, but instead it has %d members!\n", GH_LIST_COUNT(&lists[i])); } } printf("Done with fourth set, freeing buffers\n"); for(i=0; i < 16; i++) { gh_list_free(&lists[i]); } }