MINOR: proto_reverse_connect: parse rev@ addresses for bind

Implement parsing for "rev@" addresses on bind line. On config parsing,
server name is stored on the bind_conf.

Several new callbacks are defined on reverse_connect protocol to
complete parsing. listen callback is used to retrieve the server
instance from the bind_conf server name. If found, the server instance
is stored on the receiver. Checks are implemented to ensure HTTP/2
protocol only is used by the server.
This commit is contained in:
Amaury Denoyelle 2023-08-07 17:56:36 +02:00
parent 008e8f67ee
commit 0747e493a0
8 changed files with 82 additions and 2 deletions

View File

@ -4993,6 +4993,10 @@ bind /<path> [, ...] [param*]
- 'quic6@' -> address is resolved as IPv6 and protocol UDP - 'quic6@' -> address is resolved as IPv6 and protocol UDP
is used. The performance note for QUIC over IPv4 applies is used. The performance note for QUIC over IPv4 applies
as well. as well.
- 'rev@' -> used for reverse HTTP. Address must be a server
with the format '<backend>/<server>'. The server will be
used to instantiate connections to a remote address. The
listener will try to maintain 'maxconn' connections.
You may want to reference some environment variables in the You may want to reference some environment variables in the
address parameter, see section 2.3 about environment address parameter, see section 2.3 about environment

View File

@ -205,6 +205,7 @@ struct bind_conf {
char *arg; /* argument passed to "bind" for better error reporting */ char *arg; /* argument passed to "bind" for better error reporting */
char *file; /* file where the section appears */ char *file; /* file where the section appears */
int line; /* line where the section appears */ int line; /* line where the section appears */
char *reverse_srvname; /* name of server when using "rev@" address */
__decl_thread(HA_RWLOCK_T sni_lock); /* lock the SNI trees during add/del operations */ __decl_thread(HA_RWLOCK_T sni_lock); /* lock the SNI trees during add/del operations */
struct thread_set thread_set; /* entire set of the allowed threads (0=no restriction) */ struct thread_set thread_set; /* entire set of the allowed threads (0=no restriction) */
struct rx_settings settings; /* all the settings needed for the listening socket */ struct rx_settings settings; /* all the settings needed for the listening socket */

View File

@ -7,6 +7,7 @@
int rev_bind_receiver(struct receiver *rx, char **errmsg); int rev_bind_receiver(struct receiver *rx, char **errmsg);
int rev_bind_listener(struct listener *listener, char *errmsg, int errlen); int rev_bind_listener(struct listener *listener, char *errmsg, int errlen);
void rev_unbind_receiver(struct listener *l);
int rev_accepting_conn(const struct receiver *rx); int rev_accepting_conn(const struct receiver *rx);

View File

@ -79,6 +79,10 @@ struct receiver {
#ifdef USE_QUIC #ifdef USE_QUIC
struct mt_list rxbuf_list; /* list of buffers to receive and dispatch QUIC datagrams. */ struct mt_list rxbuf_list; /* list of buffers to receive and dispatch QUIC datagrams. */
#endif #endif
struct {
struct server *srv; /* Underlying server used to initiate reverse pre-connect. */
} reverse_connect;
/* warning: this struct is huge, keep it at the bottom */ /* warning: this struct is huge, keep it at the bottom */
struct sockaddr_storage addr; /* the address the socket is bound to */ struct sockaddr_storage addr; /* the address the socket is bound to */
}; };

View File

@ -173,6 +173,10 @@ int str2listener(char *str, struct proxy *curproxy, struct bind_conf *bind_conf,
else else
bind_conf->options |= BC_O_USE_XPRT_STREAM; bind_conf->options |= BC_O_USE_XPRT_STREAM;
if (ss2->ss_family == AF_CUST_REV_SRV) {
bind_conf->reverse_srvname = strdup(str + strlen("rev@"));
}
if (!create_listeners(bind_conf, ss2, port, end, fd, proto, err)) { if (!create_listeners(bind_conf, ss2, port, end, fd, proto, err)) {
memprintf(err, "%s for address '%s'.\n", *err, str); memprintf(err, "%s for address '%s'.\n", *err, str);
goto fail; goto fail;

View File

@ -799,6 +799,8 @@ int create_listeners(struct bind_conf *bc, const struct sockaddr_storage *ss,
l->rx.iocb = proto->default_iocb; l->rx.iocb = proto->default_iocb;
l->rx.fd = fd; l->rx.fd = fd;
l->rx.reverse_connect.srv = NULL;
memcpy(&l->rx.addr, ss, sizeof(*ss)); memcpy(&l->rx.addr, ss, sizeof(*ss));
if (proto->fam->set_port) if (proto->fam->set_port)
proto->fam->set_port(&l->rx.addr, port); proto->fam->set_port(&l->rx.addr, port);
@ -1945,6 +1947,9 @@ struct bind_conf *bind_conf_alloc(struct proxy *fe, const char *file,
bind_conf->sni_w_ctx = EB_ROOT; bind_conf->sni_w_ctx = EB_ROOT;
#endif #endif
LIST_INIT(&bind_conf->listeners); LIST_INIT(&bind_conf->listeners);
bind_conf->reverse_srvname = NULL;
return bind_conf; return bind_conf;
err: err:

View File

@ -1,8 +1,13 @@
#include <stdio.h>
#include <string.h>
#include <haproxy/api.h> #include <haproxy/api.h>
#include <haproxy/errors.h> #include <haproxy/errors.h>
#include <haproxy/list.h> #include <haproxy/list.h>
#include <haproxy/listener.h> #include <haproxy/listener.h>
#include <haproxy/protocol.h> #include <haproxy/protocol.h>
#include <haproxy/proxy.h>
#include <haproxy/server.h>
#include <haproxy/proto_reverse_connect.h> #include <haproxy/proto_reverse_connect.h>
@ -17,8 +22,10 @@ struct protocol proto_reverse_connect = {
.name = "rev", .name = "rev",
/* connection layer */ /* connection layer */
.listen = rev_bind_listener, .listen = rev_bind_listener,
.add = default_add_listener, .unbind = rev_unbind_receiver,
.add = default_add_listener,
.resume = default_resume_listener,
/* address family */ /* address family */
.fam = &proto_fam_reverse_connect, .fam = &proto_fam_reverse_connect,
@ -33,12 +40,65 @@ struct protocol proto_reverse_connect = {
int rev_bind_receiver(struct receiver *rx, char **errmsg) int rev_bind_receiver(struct receiver *rx, char **errmsg)
{ {
rx->flags |= RX_F_BOUND;
return ERR_NONE; return ERR_NONE;
} }
int rev_bind_listener(struct listener *listener, char *errmsg, int errlen) int rev_bind_listener(struct listener *listener, char *errmsg, int errlen)
{ {
struct proxy *be;
struct server *srv;
struct ist be_name, sv_name;
char *name = NULL;
name = strdup(listener->bind_conf->reverse_srvname);
if (!name) {
snprintf(errmsg, errlen, "Out of memory.");
goto err;
}
sv_name = ist(name);
be_name = istsplit(&sv_name, '/');
if (!istlen(sv_name)) {
snprintf(errmsg, errlen, "Invalid server name: '%s'.", name);
goto err;
}
if (!(be = proxy_be_by_name(ist0(be_name)))) {
snprintf(errmsg, errlen, "No such backend: '%s'.", name);
goto err;
}
if (!(srv = server_find_by_name(be, ist0(sv_name)))) {
snprintf(errmsg, errlen, "No such server: '%s/%s'.", ist0(be_name), ist0(sv_name));
goto err;
}
/* TODO check que on utilise pas un serveur @reverse */
if (srv->flags & SRV_F_REVERSE) {
snprintf(errmsg, errlen, "Cannot use reverse server '%s/%s' as target to a reverse bind.", ist0(be_name), ist0(sv_name));
goto err;
}
/* Check that server uses HTTP/2 either with proto or ALPN. */
if ((!srv->mux_proto || !isteqi(srv->mux_proto->token, ist("h2"))) &&
(!srv->use_ssl || !isteqi(ist(srv->ssl_ctx.alpn_str), ist("\x02h2")))) {
snprintf(errmsg, errlen, "Cannot reverse connect with server '%s/%s' unless HTTP/2 is activated on it with either proto or alpn keyword.", name, ist0(sv_name));
goto err;
}
ha_free(&name);
listener->rx.reverse_connect.srv = srv;
return ERR_NONE; return ERR_NONE;
err:
ha_free(&name);
return ERR_ALERT | ERR_FATAL;
}
void rev_unbind_receiver(struct listener *l)
{
l->rx.flags &= ~RX_F_BOUND;
} }
int rev_accepting_conn(const struct receiver *rx) int rev_accepting_conn(const struct receiver *rx)

View File

@ -334,6 +334,7 @@ void free_proxy(struct proxy *p)
free(bind_conf->arg); free(bind_conf->arg);
free(bind_conf->settings.interface); free(bind_conf->settings.interface);
LIST_DELETE(&bind_conf->by_fe); LIST_DELETE(&bind_conf->by_fe);
free(bind_conf->reverse_srvname);
free(bind_conf); free(bind_conf);
} }