blob: 24a22b5219f68a872b71a425cf8472edcc19dba7 [file] [log] [blame]
/*
* Conn-stream management functions
*
* Copyright 2021 Christopher Faulet <cfaulet@haproxy.com>
*
* 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.
*
*/
#include <haproxy/api.h>
#include <haproxy/connection.h>
#include <haproxy/conn_stream.h>
#include <haproxy/pool.h>
#include <haproxy/stream_interface.h>
DECLARE_POOL(pool_head_connstream, "conn_stream", sizeof(struct conn_stream));
DECLARE_POOL(pool_head_cs_endpoint, "cs_endpoint", sizeof(struct cs_endpoint));
void cs_endpoint_init(struct cs_endpoint *endp)
{
endp->target = NULL;
endp->ctx = NULL;
endp->flags = CS_EP_NONE;
}
struct cs_endpoint *cs_endpoint_new()
{
struct cs_endpoint *endp;
endp = pool_alloc(pool_head_cs_endpoint);
if (unlikely(!endp))
return NULL;
cs_endpoint_init(endp);
return endp;
}
void cs_endpoint_free(struct cs_endpoint *endp)
{
pool_free(pool_head_cs_endpoint, endp);
}
/* Tries to allocate a new conn_stream and initialize its main fields. On
* failure, nothing is allocated and NULL is returned.
*/
struct conn_stream *cs_new(struct cs_endpoint *endp)
{
struct conn_stream *cs;
cs = pool_alloc(pool_head_connstream);
if (unlikely(!cs))
goto alloc_error;
cs->obj_type = OBJ_TYPE_CS;
cs->flags = CS_FL_NONE;
cs->app = NULL;
cs->si = NULL;
cs->data_cb = NULL;
if (!endp) {
endp = cs_endpoint_new();
if (unlikely(!endp))
goto alloc_error;
}
cs->endp = endp;
return cs;
alloc_error:
pool_free(pool_head_connstream, cs);
return NULL;
}
struct conn_stream *cs_new_from_mux(struct cs_endpoint *endp, struct session *sess, struct buffer *input)
{
struct conn_stream *cs;
cs = cs_new(endp);
if (unlikely(!cs))
return NULL;
if (unlikely(!stream_new(sess, cs, input))) {
pool_free(pool_head_connstream, cs);
cs = NULL;
}
return cs;
}
struct conn_stream *cs_new_from_applet(struct cs_endpoint *endp, struct session *sess, struct buffer *input)
{
struct conn_stream *cs;
struct appctx *appctx = endp->ctx;
cs = cs_new(endp);
if (unlikely(!cs))
return NULL;
appctx->owner = cs;
if (unlikely(!stream_new(sess, cs, input))) {
pool_free(pool_head_connstream, cs);
cs = NULL;
}
return cs;
}
struct conn_stream *cs_new_from_strm(struct stream *strm, unsigned int flags)
{
struct conn_stream *cs;
cs = cs_new(NULL);
if (unlikely(!cs))
return NULL;
cs->flags |= flags;
cs->si = si_new(cs);
if (unlikely(!cs->si)) {
cs_free(cs);
return NULL;
}
cs->app = &strm->obj_type;
cs->si->ops = &si_embedded_ops;
cs->data_cb = NULL;
return cs;
}
struct conn_stream *cs_new_from_check(struct check *check, unsigned int flags)
{
struct conn_stream *cs;
cs = cs_new(NULL);
if (unlikely(!cs))
return NULL;
cs->flags |= flags;
cs->app = &check->obj_type;
cs->data_cb = &check_conn_cb;
return cs;
}
/* Releases a conn_stream previously allocated by cs_new(), as well as any
* buffer it would still hold.
*/
void cs_free(struct conn_stream *cs)
{
si_free(cs->si);
if (cs->endp) {
cs_endpoint_free(cs->endp);
}
pool_free(pool_head_connstream, cs);
}
/* Attaches a conn_stream to an mux endpoint and sets the endpoint ctx */
void cs_attach_mux(struct conn_stream *cs, void *target, void *ctx)
{
struct connection *conn = ctx;
cs->endp->target = target;
cs->endp->ctx = ctx;
cs->endp->flags |= CS_EP_T_MUX;
if (!conn->ctx)
conn->ctx = cs;
if (cs_strm(cs)) {
cs->si->ops = &si_conn_ops;
cs->data_cb = &si_conn_cb;
}
else if (cs_check(cs))
cs->data_cb = &check_conn_cb;
}
/* Attaches a conn_stream to an applet endpoint and sets the endpoint ctx */
void cs_attach_applet(struct conn_stream *cs, void *target, void *ctx)
{
struct appctx *appctx = target;
cs->endp->target = target;
cs->endp->ctx = ctx;
cs->endp->flags |= CS_EP_T_APPLET;
appctx->owner = cs;
if (cs->si) {
cs->si->ops = &si_applet_ops;
cs->data_cb = NULL;
}
}
/* Attaches a conn_stream to a app layer and sets the relevant callbacks */
int cs_attach_strm(struct conn_stream *cs, struct stream *strm)
{
cs->app = &strm->obj_type;
cs->si = si_new(cs);
if (unlikely(!cs->si))
return -1;
if (cs->endp->flags & CS_EP_T_MUX) {
cs->si->ops = &si_conn_ops;
cs->data_cb = &si_conn_cb;
}
else if (cs->endp->flags & CS_EP_T_APPLET) {
cs->si->ops = &si_applet_ops;
cs->data_cb = NULL;
}
else {
cs->si->ops = &si_embedded_ops;
cs->data_cb = NULL;
}
return 0;
}
/* Detach the conn_stream from the endpoint, if any. For a connecrion, if a mux
* owns the connection ->detach() callback is called. Otherwise, it means the
* conn-stream owns the connection. In this case the connection is closed and
* released. For an applet, the appctx is released. At the end, the conn-stream
* is not released but some fields a reset.
*/
void cs_detach_endp(struct conn_stream *cs)
{
if (cs->endp->flags & CS_EP_T_MUX) {
struct connection *conn = cs_conn(cs);
if (conn->mux) {
/* TODO: handle unsubscribe for healthchecks too */
if (cs->si && cs->si->wait_event.events != 0)
conn->mux->unsubscribe(cs, cs->si->wait_event.events, &cs->si->wait_event);
conn->mux->detach(cs);
}
else {
/* It's too early to have a mux, let's just destroy
* the connection
*/
conn_stop_tracking(conn);
conn_full_close(conn);
if (conn->destroy_cb)
conn->destroy_cb(conn);
conn_free(conn);
}
}
else if (cs->endp->flags & CS_EP_T_APPLET) {
struct appctx *appctx = cs_appctx(cs);
if (cs->si)
si_applet_release(cs->si);
appctx_free(appctx);
}
if (cs->endp) {
cs_endpoint_init(cs->endp);
}
/* FIXME: Rest CS for now but must be reviewed. CS flags are only
* connection related for now but this will evolved
*/
cs->flags = CS_FL_NONE;
if (cs->si)
cs->si->ops = &si_embedded_ops;
cs->data_cb = NULL;
if (cs->app == NULL)
cs_free(cs);
}
void cs_detach_app(struct conn_stream *cs)
{
si_free(cs->si);
cs->app = NULL;
cs->si = NULL;
cs->data_cb = NULL;
if (!cs->endp || !cs->endp->target)
cs_free(cs);
}