stf_status send_crypto_helper_request(struct pluto_crypto_req *r, struct pluto_crypto_req_cont *cn) { static int pc_worker_num = 0; /* index of last worker assigned work */ struct pluto_crypto_worker *w; /* best worker for task */ struct pluto_crypto_worker *c; /* candidate worker */ struct state *st = cur_state; /* TRANSITIONAL */ /* * transitional: caller must have set pcrc_serialno. * It ought to match cur_state->st_serialno. */ passert(cn->pcrc_serialno == st->st_serialno); passert(st->st_serialno != SOS_NOBODY); cn->pcrc_serialno = st->st_serialno; passert(cn->pcrc_func != NULL); /* do it all ourselves? */ if (pc_workers == NULL) { reset_cur_state(); pluto_do_crypto_op(r, -1); /* call the continuation */ (*cn->pcrc_func)(cn, r); pfree(cn); /* ownership transferred from caller */ /* indicate that we completed the work */ return STF_INLINE; } /* attempt to send to a worker thread */ /* set up the id */ r->pcr_id = pcw_id++; cn->pcrc_id = r->pcr_id; cn->pcrc_pcr = r; /* Find the first of the least-busy workers (if any) */ w = NULL; for (c = pc_workers; c != &pc_workers[pc_workers_cnt]; c++) { DBG(DBG_CONTROL, DBG_log("crypto helper %d%s: pcw_work: %d", pc_worker_num, c->pcw_dead? " DEAD" : "", c->pcw_work)); if (!c->pcw_dead && (w == NULL || c->pcw_work < w->pcw_work)) { w = c; /* c is the best so far */ if (c->pcw_work == 0) break; /* early out: cannot do better */ } } if (w != NULL && (w->pcw_work < w->pcw_maxbasicwork || (w->pcw_work < w->pcw_maxcritwork && r->pcr_pcim > pcim_ongoing_crypto))) { /* allocate task to worker w */ /* link it to the worker's active list * cn transferred from caller */ TAILQ_INSERT_TAIL(&w->pcw_active, cn, pcrc_list); passert(w->pcw_master_fd != -1); cn->pcrc_reply_stream = reply_stream; if (pbs_offset(&reply_stream) != 0) { /* copy partially built reply stream to heap * IMPORTANT: don't leak this. */ cn->pcrc_reply_buffer = clone_bytes(reply_stream.start, pbs_offset(&reply_stream), "saved reply buffer"); } if (!crypto_write_request(w, r)) { /* free the heap space */ if (pbs_offset(&cn->pcrc_reply_stream) != 0) pfree(cn->pcrc_reply_buffer); cn->pcrc_reply_buffer = NULL; loglog(RC_LOG_SERIOUS, "cannot start crypto helper %d: failed to write", w->pcw_helpernum); return STF_FAIL; } w->pcw_work++; } else if (r->pcr_pcim >= pcim_demand_crypto) { /* Task is important: put it all on the backlog queue for later */ /* cn transferred from caller */ TAILQ_INSERT_TAIL(&backlog, cn, pcrc_list); /* copy the request */ r = clone_bytes(r, r->pcr_len, "saved crypto request"); cn->pcrc_pcr = r; cn->pcrc_reply_stream = reply_stream; if (pbs_offset(&reply_stream) != 0) { /* copy partially built reply stream to heap * IMPORTANT: don't leak this. */ cn->pcrc_reply_buffer = clone_bytes(reply_stream.start, pbs_offset(&reply_stream), "saved reply buffer"); } backlog_queue_len++; DBG(DBG_CONTROL, DBG_log("critical demand crypto operation queued on backlog as %dth item; request ID %u", backlog_queue_len, r->pcr_id)); } else { /* didn't find any available workers */ DBG(DBG_CONTROL, DBG_log("failed to find any available crypto worker (import=%s)", enum_name(&pluto_cryptoimportance_names, r->pcr_pcim))); loglog(RC_LOG_SERIOUS, "cannot start crypto helper: failed to find any available worker"); pfree(cn); /* ownership transferred from caller */ return STF_TOOMUCHCRYPTO; } /* cn ownership transferred on to backlog */ DBG(DBG_CONTROLMORE, DBG_log("#%lu %s:%u st->st_calculating = TRUE;", st->st_serialno, __FUNCTION__, __LINE__)); st->st_calculating = TRUE; delete_event(st); event_schedule(EVENT_CRYPTO_FAILED, EVENT_CRYPTO_FAILED_DELAY, st); return STF_SUSPEND; }
/* * for aggressive mode, this is sub-optimal, since we should have * had the crypto helper actually do everything, but we need to do * some additional work to set that all up, so this is fine for now. * */ static void aggr_inI1_outR1_continue1(struct pluto_crypto_req_cont *pcrc, struct pluto_crypto_req *r, err_t ugh) { struct ke_continuation *ke = (struct ke_continuation *)pcrc; struct msg_digest *md = ke->ke_md; struct state *const st = md->st; stf_status e; DBG(DBG_CONTROLMORE, DBG_log("aggr inI1_outR1: calculated ke+nonce, calculating DH")); if (st == NULL) { loglog(RC_LOG_SERIOUS, "%s: Request was disconnected from state", __FUNCTION__); release_any_md(&ke->ke_md); return; } /* XXX should check out ugh */ passert(ugh == NULL); passert(cur_state == NULL); passert(st != NULL); passert(st->st_suspended_md == ke->ke_md); unset_suspended(st); /* no longer connected or suspended */ set_cur_state(st); DBG(DBG_CONTROLMORE, DBG_log("#%lu %s:%u st->st_calculating = FALSE;", st->st_serialno, __FUNCTION__, __LINE__)); st->st_calculating = FALSE; /* unpack first calculation */ unpack_KE(st, r, &st->st_gr); /* unpack nonce too */ unpack_nonce(&st->st_nr, r); /* NOTE: the "r" reply will get freed by our caller */ /* set up second calculation */ { struct dh_continuation *dh = alloc_thing( struct dh_continuation, "aggr outR1 DH"); dh->dh_md = md; set_suspended(st, md); dh->dh_pcrc.pcrc_serialno = st->st_serialno; /* transitional */ pcrc_init(&dh->dh_pcrc, aggr_inI1_outR1_continue2); e = start_dh_secretiv(&dh->dh_pcrc, st, st->st_import, O_RESPONDER, st->st_oakley.group->group); if (e != STF_SUSPEND) { if (dh->dh_md != NULL) { complete_v1_state_transition(&dh->dh_md, e); release_any_md(&dh->dh_md); } } reset_cur_state(); } }
/* * this function is called with a request to do some cryptographic operations * along with a continuation structure, which will be used to deal with * the response. * * This may fail if there are no helpers that can take any data, in which * case an error is returned. * */ err_t send_crypto_helper_request(struct pluto_crypto_req *r , struct pluto_crypto_req_cont *cn , bool *toomuch) { struct pluto_crypto_worker *w; int cnt; /* do it all ourselves? */ if(pc_workers == NULL) { reset_cur_state(); #ifdef HAVE_LIBNSS pluto_do_crypto_op(r,pc_helper_num); #else pluto_do_crypto_op(r); #endif /* call the continuation */ (*cn->pcrc_func)(cn, r, NULL); /* indicate that we did everything ourselves */ *toomuch = TRUE; pfree(cn); return NULL; } /* set up the id */ r->pcr_id = pcw_id++; cn->pcrc_id = r->pcr_id; cn->pcrc_pcr = r; /* find an available worker */ cnt = pc_workers_cnt; do { pc_worker_num++; if(pc_worker_num >= pc_workers_cnt) { pc_worker_num = 0; } w = &pc_workers[pc_worker_num]; DBG(DBG_CONTROL , DBG_log("%d: w->pcw_dead: %d w->pcw_work: %d cnt: %d", pc_worker_num, w->pcw_dead, w->pcw_work, cnt)); /* see if there is something to clean up after */ if(w->pcw_dead == TRUE && w->pcw_reaped == TRUE) { cleanup_crypto_helper(w, 0); DBG(DBG_CONTROL , DBG_log("clnup %d: w->pcw_dead: %d w->pcw_work: %d cnt: %d", pc_worker_num, w->pcw_dead, w->pcw_work, cnt)); } } while(((w->pcw_work >= w->pcw_maxbasicwork)) && --cnt > 0); if(cnt == 0 && r->pcr_pcim > pcim_ongoing_crypto) { cnt = pc_workers_cnt; while((w->pcw_work >= w->pcw_maxcritwork) && --cnt > 0) { /* find an available worker */ pc_worker_num++; if(pc_worker_num >= pc_workers_cnt) { pc_worker_num = 0; } w = &pc_workers[pc_worker_num]; /* see if there is something to clean up after */ if(w->pcw_dead == TRUE && w->pcw_reaped == TRUE) { cleanup_crypto_helper(w, 0); } } DBG(DBG_CONTROL , DBG_log("crit %d: w->pcw_dead: %d w->pcw_work: %d cnt: %d", pc_worker_num, w->pcw_dead, w->pcw_work, cnt)); } if(cnt == 0 && r->pcr_pcim >= pcim_demand_crypto) { /* it is very important. Put it all on a queue for later */ TAILQ_INSERT_TAIL(&backlog, cn, pcrc_list); /* copy the request */ r = clone_bytes(r, r->pcr_len, "saved cryptorequest"); cn->pcrc_pcr = r; cn->pcrc_reply_stream = reply_stream; if (pbs_offset(&reply_stream)) { cn->pcrc_reply_buffer = clone_bytes(reply_stream.start , pbs_offset(&reply_stream), "saved reply buffer"); } backlogqueue_len++; DBG(DBG_CONTROL , DBG_log("critical demand crypto operation queued on backlog as %d'th item, id: q#%u" , backlogqueue_len, r->pcr_id)); *toomuch = FALSE; return NULL; } if(cnt == 0) { /* didn't find any workers */ DBG(DBG_CONTROL , DBG_log("failed to find any available worker (import=%s)" , enum_name(&pluto_cryptoimportance_names,r->pcr_pcim))); *toomuch = TRUE; return "failed to find any available worker"; } /* w points to a work. Make sure it is live */ if(w->pcw_pid == -1) { init_crypto_helper(w, pc_worker_num); if(w->pcw_pid == -1) { DBG(DBG_CONTROL , DBG_log("found only a dead helper, and failed to restart it")); *toomuch = TRUE; return "failed to start a new helper"; } } /* link it to the active worker list */ TAILQ_INSERT_TAIL(&w->pcw_active, cn, pcrc_list); passert(w->pcw_pid != -1); passert(w->pcw_pipe != -1); passert(w->pcw_work < w->pcw_maxcritwork); cn->pcrc_reply_stream = reply_stream; if (pbs_offset(&reply_stream)) cn->pcrc_reply_buffer = clone_bytes(reply_stream.start , pbs_offset(&reply_stream), "saved reply buffer"); if(!crypto_write_request(w, r)) { openswan_log("failed to write crypto request: %s\n", strerror(errno)); if (pbs_offset(&cn->pcrc_reply_stream)) pfree(cn->pcrc_reply_buffer); cn->pcrc_reply_buffer = NULL; return "failed to write"; } w->pcw_work++; *toomuch = FALSE; return NULL; }
/* * this function is called when there is a helper pipe that is ready. * we read the request from the pipe, and find the associated continuation, * and dispatch to that continuation. * * this function should process only a single answer, and then go back * to the select call to get called again. This is not most efficient, * but is is most fair. * */ void handle_helper_comm(struct pluto_crypto_worker *w) { struct pluto_crypto_req reqbuf[2]; unsigned char *inloc; struct pluto_crypto_req *r; int restlen; int actlen; struct pluto_crypto_req_cont *cn; DBG(DBG_CRYPT|DBG_CONTROL , DBG_log("helper %u has finished work (cnt now %d)" ,w->pcw_helpernum ,w->pcw_work)); /* read from the pipe */ memset(reqbuf, 0, sizeof(reqbuf)); actlen = read(w->pcw_pipe, (char *)reqbuf, sizeof(r->pcr_len)); if(actlen != sizeof(r->pcr_len)) { if(actlen != 0) { loglog(RC_LOG_SERIOUS, "read failed with %d: %s" , actlen, strerror(errno)); } /* * eof, mark worker as dead. If already reaped, then free. */ w->pcw_dead = TRUE; if(w->pcw_reaped) { cleanup_crypto_helper(w, 0); } return; } /* we can accept more work now that we have read from the pipe */ w->pcw_work--; r = &reqbuf[0]; if(r->pcr_len > sizeof(reqbuf)) { loglog(RC_LOG_SERIOUS, "helper(%d) pid=%lu screwed up length: %lu > %lu, killing it" , w->pcw_helpernum , (unsigned long)w->pcw_pid, (unsigned long)r->pcr_len , (unsigned long)sizeof(reqbuf)); killit: #ifdef HAVE_LIBNSS pthread_cancel((pthread_t)w->pcw_pid); #else kill(w->pcw_pid, SIGTERM); #endif w->pcw_dead = TRUE; return; } restlen = r->pcr_len-sizeof(r->pcr_len); inloc = ((unsigned char*)reqbuf)+sizeof(r->pcr_len); while(restlen > 0) { /* okay, got a basic size, read the rest of it */ actlen = read(w->pcw_pipe, inloc, restlen); if(actlen <= 0) { /* faulty read. note this fact, and close pipe. */ /* we actually need to restart this query, but we'll do that * another day. */ loglog(RC_LOG_SERIOUS , "cryptographic handler(%d) read(%d)=%d failed: %s\n" , w->pcw_pipe, restlen, actlen, strerror(errno)); goto killit; } restlen -= actlen; inloc += actlen; } DBG(DBG_CRYPT|DBG_CONTROL, DBG_log("helper %u replies to id: q#%u" ,w->pcw_helpernum ,r->pcr_id)); /* * if there is work queued, then send it off after reading, since this * avoids the most deadlocks */ crypto_send_backlog(w); /* now match up request to continuation, and invoke it */ for(cn = w->pcw_active.tqh_first; cn!=NULL && r->pcr_id != cn->pcrc_id; cn = cn->pcrc_list.tqe_next); if(cn == NULL) { loglog(RC_LOG_SERIOUS , "failed to find continuation associated with req %u\n", (unsigned int)r->pcr_id); return; } /* unlink it */ TAILQ_REMOVE(&w->pcw_active, cn, pcrc_list); passert(cn->pcrc_func != NULL); DBG(DBG_CRYPT, DBG_log("calling callback function %p" ,cn->pcrc_func)); reply_stream = cn->pcrc_reply_stream; if (pbs_offset(&reply_stream)) { memcpy(reply_stream.start, cn->pcrc_reply_buffer , pbs_offset(&reply_stream)); pfree(cn->pcrc_reply_buffer); } cn->pcrc_reply_buffer = NULL; /* call the continuation */ cn->pcrc_pcr = r; reset_cur_state(); (*cn->pcrc_func)(cn, r, NULL); /* now free up the continuation */ pfree(cn); }
/* * This function is called when there is socketpair input from a helper. * This is the answer from the helper. * We read the request from the socketpair, and find the associated continuation, * and dispatch to that continuation. * * This function should process only a single answer, and then go back * to the select call to get called again. This is not most efficient, * but is is most fair. * */ static void handle_helper_answer(struct pluto_crypto_worker *w) { struct pluto_crypto_req rr; ssize_t actlen; struct pluto_crypto_req_cont *cn; DBG(DBG_CONTROL, DBG_log("crypto helper %d has finished work (pcw_work now %d)", w->pcw_helpernum, w->pcw_work)); /* read from the socketpair in one gulp */ errno = 0; actlen = read(w->pcw_master_fd, (void *)&rr, sizeof(rr)); if (actlen != sizeof(rr)) { if (actlen == -1) { loglog(RC_LOG_SERIOUS, "read from crypto helper %d failed: %s. Killing helper.", w->pcw_helpernum, strerror(errno)); kill_helper(w); } else if (actlen == 0) { /* EOF: mark worker as dead. */ w->pcw_dead = TRUE; } else if (errno == 0) { loglog(RC_LOG_SERIOUS, "read from crypto helper %d failed with short length %zd of %zu. Killing helper.", w->pcw_helpernum, actlen, sizeof(rr)); kill_helper(w); } else { loglog(RC_LOG_SERIOUS, "read from crypto helper %d failed with short length %zd of %zu (errno=%s). Killing helper.", w->pcw_helpernum, actlen, sizeof(rr), strerror(errno)); kill_helper(w); } return; } if (rr.pcr_len != sizeof(rr)) { loglog(RC_LOG_SERIOUS, "crypto helper %d screwed up length: %zu != %zu; killing it", w->pcw_helpernum, rr.pcr_len, sizeof(rr)); kill_helper(w); return; } DBG(DBG_CONTROL, DBG_log("crypto helper %d replies to request ID %u", w->pcw_helpernum, rr.pcr_id)); /* worker w can accept more work now that we have read from its socketpair */ w->pcw_work--; /* * if there is work queued, then send it off after reading, since this * avoids the most deadlocks */ crypto_send_backlog(w); /* now match up request to continuation, and invoke it */ for (cn = w->pcw_active.tqh_first; cn != NULL && rr.pcr_id != cn->pcrc_id; cn = cn->pcrc_list.tqe_next) ; if (cn == NULL) { loglog(RC_LOG_SERIOUS, "failed to find crypto continuation associated with request ID %u performed by crypto helper %d", rr.pcr_id, w->pcw_helpernum); return; } /* unlink it */ TAILQ_REMOVE(&w->pcw_active, cn, pcrc_list); passert(cn->pcrc_func != NULL); DBG(DBG_CONTROL, DBG_log("calling continuation function %p", cn->pcrc_func)); reply_stream = cn->pcrc_reply_stream; if (pbs_offset(&reply_stream) != 0) { memcpy(reply_stream.start, cn->pcrc_reply_buffer, pbs_offset(&reply_stream)); pfree(cn->pcrc_reply_buffer); } cn->pcrc_reply_buffer = NULL; /* call the continuation (skip if suppressed) */ cn->pcrc_pcr = &rr; reset_cur_state(); if (cn->pcrc_serialno != SOS_NOBODY) (*cn->pcrc_func)(cn, &rr); /* now free up the continuation */ pfree(cn); }