void bud_client_shutdown_cb(uv_shutdown_t* req, int status) { bud_client_t* client; bud_client_side_t* side; client = req->data; if (req == &client->frontend.shutdown_req) side = &client->frontend; else side = &client->backend; side->shutdown = kBudProgressDone; if (status == UV_ECANCELED) return; if (status == 0) DBG_LN(side, "shutdown cb"); if (status != 0) { bud_client_close(client, bud_client_error(bud_error_num(kBudErrClientShutdownCb, status), side)); /* If either closing, or shutdown both sides - kill both sockets! */ } else if (side->close == kBudProgressRunning || client->frontend.shutdown == client->backend.shutdown || (side == &client->frontend && !client->config->frontend.allow_half_open)) { bud_client_close(client, bud_client_ok(side)); } }
void bud_client_read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { int r; bud_client_t* client; bud_client_side_t* side; bud_client_side_t* opposite; bud_client_error_t cerr; client = stream->data; side = bud_client_side_by_tcp(client, (uv_tcp_t*) stream); /* Commit data if there was no error */ r = 0; if (nread >= 0) r = ringbuffer_write_append(&side->input, nread); DBG(side, "after read_cb() => %d", nread); /* Handle EOF */ if (nread == UV_EOF) { side->reading = kBudProgressDone; /* Shutdown opposite side */ opposite = side == &client->frontend ? &client->backend : &client->frontend; cerr = bud_client_shutdown(client, opposite); if (!bud_is_ok(cerr.err)) goto done; } /* Try writing out data anyway */ cerr = bud_client_cycle(client); if (!bud_is_ok(cerr.err)) goto done; if ((r != 0 || nread < 0) && nread != UV_EOF) { if (nread < 0) cerr = bud_client_error(bud_error_num(kBudErrClientReadCb, nread), side); else cerr = bud_client_error(bud_error(kBudErrClientWriteAppend), side); /* Unrecoverable socket error, close */ return bud_client_close(client, cerr); } /* If buffer is full - stop reading */ cerr = bud_client_throttle(client, side, &side->input); done: if (!bud_is_ok(cerr.err) && cerr.err.code != kBudErrClientThrottle) bud_client_close(client, cerr); }
void bud_client_handshake_start_cb(const SSL* ssl) { bud_client_t* client; uint64_t now; uint64_t limit; client = SSL_get_ex_data(ssl, kBudSSLClientIndex); now = uv_now(client->config->loop); /* NOTE: config's limit is in ms */ limit = (uint64_t) client->config->frontend.reneg_window * 1000; if (now - client->last_handshake > limit) client->handshakes = 0; /* First handshake */ if (client->last_handshake == 0) goto end; DBG(&client->frontend, "renegotation %d", client->handshakes); /* Too many renegotiations in a small time window */ if (++client->handshakes > client->config->frontend.reneg_limit) { bud_client_close( client, bud_client_error(bud_error(kBudErrClientRenegotiationAttack), &client->frontend)); } end: client->last_handshake = now; }
void bud_client_handshake_done_cb(const SSL* ssl) { bud_client_t* client; bud_context_t* context; bud_client_error_t cerr; client = SSL_get_ex_data(ssl, kBudSSLClientIndex); context = SSL_get_ex_data(ssl, kBudSSLSNIIndex); bud_trace_handshake(client); if (client->selected_backend != NULL) return; if (client->config->balance_e != kBudBalanceSNI) return; if (context != NULL) client->selected_backend = context->backend; if (client->selected_backend != NULL) { /* Backend provided - connect */ cerr = bud_client_connect(client); } else { /* No backend in SNI response */ cerr = bud_client_error(bud_error(kBudErrClientNoBackendInSNI), &client->frontend); } if (!bud_is_ok(cerr.err)) bud_client_close(client, cerr); }
void bud_client_close(bud_client_t* client, bud_client_error_t err) { bud_client_side_t* side; side = err.side; if (bud_is_ok(err.err) || (err.err.code == kBudErrClientSSLRead && err.err.ret == SSL_ERROR_ZERO_RETURN)) { DBG_LN(side, "bud_client_close()"); } else if (side == &client->backend) { WARNING(side, "closed because: %s", bud_error_to_str(err.err)); } else { NOTICE(side, "closed because: %s", bud_error_to_str(err.err)); } if (client->close == kBudProgressRunning) { /* Force close, even if waiting */ if (side->close == kBudProgressRunning) { DBG_LN(side, "force closing"); uv_close((uv_handle_t*) &side->tcp, bud_client_close_cb); side->close = kBudProgressDone; client->close = kBudProgressDone; } return; } else if (client->close == kBudProgressDone) { return; } /* Close offending side, and wait for write finish on other side */ client->close = kBudProgressRunning; if (side->type == kBudBackend && !ringbuffer_is_empty(&client->frontend.output)) { client->frontend.close = kBudProgressRunning; } else if (client->frontend.close != kBudProgressDone) { DBG_LN(&client->frontend, "force closing (and waiting for other)"); uv_close((uv_handle_t*) &client->frontend.tcp, bud_client_close_cb); client->frontend.close = kBudProgressDone; } if (side->type == kBudFrontend && !ringbuffer_is_empty(&client->backend.output)) { client->backend.close = kBudProgressRunning; } else if (client->backend.close != kBudProgressDone) { DBG_LN(&client->backend, "force closing (and waiting for other)"); uv_close((uv_handle_t*) &client->backend.tcp, bud_client_close_cb); client->backend.close = kBudProgressDone; } /* Close side-independent handles */ uv_close((uv_handle_t*) &client->retry_timer, bud_client_close_cb); /* Cycle data if one of backends is not closed */ if (client->backend.close != kBudProgressDone || client->frontend.close != kBudProgressDone) { err = bud_client_cycle(client); if (!bud_is_ok(err.err)) return bud_client_close(client, err); } }
bud_client_error_t bud_client_cycle(bud_client_t* client) { bud_client_error_t cerr; /* Parsing, must wait */ if (client->hello_parse != kBudProgressDone) { return bud_client_parse_hello(client); } else if (client->cycle == kBudProgressRunning) { /* Recursive call detected, ask cycle loop to run one more time */ client->recycle++; return bud_client_ok(&client->frontend); } else { client->cycle = kBudProgressRunning; cerr = bud_client_ok(&client->frontend); do { client->recycle = 0; cerr = bud_client_backend_in(client); if (!bud_is_ok(cerr.err) || client->close != kBudProgressNone) break; cerr = bud_client_backend_out(client); if (!bud_is_ok(cerr.err) || client->close != kBudProgressNone) break; cerr = bud_client_send(client, &client->frontend); if (!bud_is_ok(cerr.err) || client->close != kBudProgressNone) break; cerr = bud_client_send(client, &client->backend); if (!bud_is_ok(cerr.err) || client->close != kBudProgressNone) break; if (client->recycle) DBG_LN(&client->frontend, "recycle"); } while (client->recycle); client->cycle = kBudProgressNone; if (!bud_is_ok(cerr.err)) bud_client_close(client, cerr); return cerr; } }
void bud_client_send_cb(uv_write_t* req, int status) { bud_client_t* client; bud_client_error_t cerr; bud_client_side_t* side; bud_client_side_t* opposite; /* Closing, ignore */ if (status == UV_ECANCELED) return; client = req->data; if (req == &client->frontend.write_req) { side = &client->frontend; opposite = &client->backend; } else { side = &client->backend; opposite = &client->frontend; } if (status != 0) { side->write = kBudProgressDone; bud_client_close( client, bud_client_error(bud_error_num(kBudErrClientWriteCb, status), side)); return; } /* Consume written data */ DBG(side, "write_cb => %d", side->write_size); ringbuffer_read_skip(&side->output, side->write_size); /* Skip data in xforward parser */ if (side == &client->backend) bud_client_xforward_skip(client, side->write_size); side->write = kBudProgressNone; side->write_size = 0; if (opposite->reading == kBudProgressNone) { if ((client->retry == kBudProgressRunning || client->connect == kBudProgressRunning) && opposite == &client->backend) { /* Set reading mark on backend to resume it after reconnecting */ opposite->reading = kBudProgressRunning; } else if (opposite->close != kBudProgressDone && side->close != kBudProgressDone && side->shutdown != kBudProgressDone && !ringbuffer_is_full(&side->output)) { DBG_LN(opposite, "read_start"); cerr = bud_client_read_start(client, opposite); if (!bud_is_ok(cerr.err)) return bud_client_close(client, cerr); opposite->reading = kBudProgressRunning; } } /* Cycle again */ cerr = bud_client_cycle(client); if (!bud_is_ok(cerr.err)) return bud_client_close(client, cerr); if (side->close == kBudProgressRunning || side->shutdown == kBudProgressRunning) { if (!ringbuffer_is_empty(&side->output)) return; /* No new data, destroy or shutdown */ if (side->shutdown == kBudProgressRunning) { cerr = bud_client_shutdown(client, side); if (!bud_is_ok(cerr.err)) bud_client_close(client, cerr); return; } bud_client_close(client, bud_client_ok(side)); } }
void bud_client_sni_cb(bud_http_request_t* req, bud_error_t err) { bud_client_t* client; bud_config_t* config; bud_client_error_t cerr; int r; STACK_OF(X509)* chain; SSL_CTX* ctx; X509* x509; EVP_PKEY* pkey; client = req->data; config = client->config; client->sni_req = NULL; client->async_hello = kBudProgressDone; if (!bud_is_ok(err)) { WARNING(&client->frontend, "SNI cb failed: \"%s\"", bud_error_to_str(err)); goto fatal; } if (req->code == 404) { /* Not found */ DBG(&client->frontend, "SNI name not found: \"%.*s\"", client->hello.servername_len, client->hello.servername); goto done; } /* Parse incoming JSON */ err = bud_sni_from_json(config, req->response, &client->sni_ctx); if (!bud_is_ok(err)) { WARNING(&client->frontend, "SNI from json failed: \"%s\"", bud_error_to_str(err)); goto fatal; } /* Success */ DBG(&client->frontend, "SNI name found: \"%.*s\"", client->hello.servername_len, client->hello.servername); if (!SSL_set_ex_data(client->ssl, kBudSSLSNIIndex, &client->sni_ctx)) { err = bud_error(kBudErrClientSetExData); goto fatal; } /* NOTE: reference count is not increased by this API methods */ ctx = client->sni_ctx.ctx; x509 = SSL_CTX_get0_certificate(ctx); pkey = SSL_CTX_get0_privatekey(ctx); r = SSL_CTX_get0_chain_certs(ctx, &chain); if (r == 1) r = SSL_use_certificate(client->ssl, x509); if (r == 1) r = SSL_use_PrivateKey(client->ssl, pkey); if (r == 1 && chain != NULL) r = SSL_set1_chain(client->ssl, chain); if (r != 1) { err = bud_error(kBudErrClientSetSNICert); goto fatal; } /* Update context, may be needed for early ticket key generation */ SSL_set_SSL_CTX(client->ssl, ctx); /* Do not loose the cert callback! */ SSL_set_cert_cb(client->ssl, bud_client_ssl_cert_cb, client); client->ssl->options = client->sni_ctx.ctx->options; done: /* Request stapling info if needed */ if (config->stapling.enabled && client->hello.ocsp_request != 0) { err = bud_client_ocsp_stapling(client); if (!bud_is_ok(err)) goto fatal; } json_value_free(req->response); if (client->async_hello == kBudProgressDone) { cerr = bud_client_cycle(client); if (!bud_is_ok(cerr.err)) bud_client_close(client, cerr); } return; fatal: bud_client_close(client, bud_client_error(err, &client->frontend)); }
void bud_client_sni_cb(bud_http_request_t* req, bud_error_t err) { bud_client_t* client; bud_config_t* config; bud_client_error_t cerr; client = req->data; config = client->config; client->sni_req = NULL; client->hello_parse = kBudProgressDone; if (!bud_is_ok(err)) { WARNING(&client->frontend, "SNI cb failed: \"%s\"", bud_error_to_str(err)); goto fatal; } if (req->code == 404) { /* Not found */ DBG(&client->frontend, "SNI name not found: \"%.*s\"", client->hello.servername_len, client->hello.servername); goto done; } /* Parse incoming JSON */ err = bud_sni_from_json(config, req->response, &client->sni_ctx); if (!bud_is_ok(err)) { WARNING(&client->frontend, "SNI from json failed: \"%s\"", bud_error_to_str(err)); goto fatal; } /* Success */ DBG(&client->frontend, "SNI name found: \"%.*s\"", client->hello.servername_len, client->hello.servername); if (!SSL_set_ex_data(client->ssl, kBudSSLSNIIndex, &client->sni_ctx)) { err = bud_error(kBudErrClientSetExData); goto fatal; } done: /* Request stapling info if needed */ if (config->stapling.enabled && client->hello.ocsp_request != 0) { err = bud_client_ocsp_stapling(client); if (!bud_is_ok(err)) goto fatal; } json_value_free(req->response); if (client->hello_parse == kBudProgressDone) { cerr = bud_client_cycle(client); if (!bud_is_ok(cerr.err)) bud_client_close(client, cerr); } return; fatal: bud_client_close(client, bud_client_error(err, &client->frontend)); }