mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-11-23 03:41:01 +01:00
This function collects all the receiver-specific code from both tcp_bind_listener() and udp_bind_listener() in order to provide a more generic AF_INET/AF_INET6 socket binding function. For now the API is not very elegant because some info are still missing from the receiver while there's no ideal place to fill them except when calling ->listen() at the protocol level. It looks like some polishing code is needed in check_config_validity() or somewhere around this in order to finalize the receivers' setup. The main issue is that listeners and receivers are created *before* bind_conf options are parsed and that there's no finishing step to resolve some of them. The function currently sets up a receiver and subscribes it to the poller. In an ideal world we wouldn't subscribe it but let the caller do it after having finished to configure the L4 stuff. The problem is that the caller would then need to perform an fd_insert() call and to possibly set the exported flag on the FD while it's not its job. Maybe an improvement could be to have a separate sock_start_receiver() call in sock.c. For now the function is not used but it will soon be. It's already referenced as tcp and udp's ->bind().
366 lines
11 KiB
C
366 lines
11 KiB
C
/*
|
|
* AF_CUST_UDP/AF_CUST_UDP6 UDP protocol layer
|
|
*
|
|
* Copyright 2019 HAProxy Technologies, Frédéric Lécaille <flecaille@haproxy.com>
|
|
*
|
|
* Partial merge by Emeric Brun <ebrun@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 <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <netinet/udp.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include <haproxy/fd.h>
|
|
#include <haproxy/listener.h>
|
|
#include <haproxy/log.h>
|
|
#include <haproxy/namespace.h>
|
|
#include <haproxy/port_range.h>
|
|
#include <haproxy/protocol.h>
|
|
#include <haproxy/proto_udp.h>
|
|
#include <haproxy/proxy.h>
|
|
#include <haproxy/server.h>
|
|
#include <haproxy/sock.h>
|
|
#include <haproxy/sock_inet.h>
|
|
#include <haproxy/task.h>
|
|
|
|
static int udp_bind_listener(struct listener *listener, char *errmsg, int errlen);
|
|
static void udp4_add_listener(struct listener *listener, int port);
|
|
static void udp6_add_listener(struct listener *listener, int port);
|
|
|
|
/* Note: must not be declared <const> as its list will be overwritten */
|
|
static struct protocol proto_udp4 = {
|
|
.name = "udp4",
|
|
.sock_domain = AF_CUST_UDP4,
|
|
.sock_type = SOCK_DGRAM,
|
|
.sock_prot = IPPROTO_UDP,
|
|
.sock_family = AF_INET,
|
|
.sock_addrlen = sizeof(struct sockaddr_in),
|
|
.l3_addrlen = 32/8,
|
|
.accept = NULL,
|
|
.connect = NULL,
|
|
.bind = sock_inet_bind_receiver,
|
|
.listen = udp_bind_listener,
|
|
.enable_all = enable_all_listeners,
|
|
.get_src = udp_get_src,
|
|
.get_dst = udp_get_dst,
|
|
.pause = udp_pause_listener,
|
|
.add = udp4_add_listener,
|
|
.addrcmp = sock_inet4_addrcmp,
|
|
.listeners = LIST_HEAD_INIT(proto_udp4.listeners),
|
|
.nb_listeners = 0,
|
|
};
|
|
|
|
INITCALL1(STG_REGISTER, protocol_register, &proto_udp4);
|
|
|
|
/* Note: must not be declared <const> as its list will be overwritten */
|
|
static struct protocol proto_udp6 = {
|
|
.name = "udp6",
|
|
.sock_domain = AF_CUST_UDP6,
|
|
.sock_type = SOCK_DGRAM,
|
|
.sock_prot = IPPROTO_UDP,
|
|
.sock_family = AF_INET6,
|
|
.sock_addrlen = sizeof(struct sockaddr_in6),
|
|
.l3_addrlen = 128/8,
|
|
.accept = NULL,
|
|
.connect = NULL,
|
|
.bind = sock_inet_bind_receiver,
|
|
.listen = udp_bind_listener,
|
|
.enable_all = enable_all_listeners,
|
|
.get_src = udp6_get_src,
|
|
.get_dst = udp6_get_dst,
|
|
.pause = udp_pause_listener,
|
|
.add = udp6_add_listener,
|
|
.addrcmp = sock_inet6_addrcmp,
|
|
.listeners = LIST_HEAD_INIT(proto_udp6.listeners),
|
|
.nb_listeners = 0,
|
|
};
|
|
|
|
INITCALL1(STG_REGISTER, protocol_register, &proto_udp6);
|
|
|
|
/*
|
|
* Retrieves the source address for the socket <fd>, with <dir> indicating
|
|
* if we're a listener (=0) or an initiator (!=0). It returns 0 in case of
|
|
* success, -1 in case of error. The socket's source address is stored in
|
|
* <sa> for <salen> bytes.
|
|
*/
|
|
int udp_get_src(int fd, struct sockaddr *sa, socklen_t salen, int dir)
|
|
{
|
|
int ret;
|
|
|
|
ret = sock_get_src(fd, sa, salen, dir);
|
|
if (!ret)
|
|
sa->sa_family = AF_CUST_UDP4;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Retrieves the source address for the socket <fd>, with <dir> indicating
|
|
* if we're a listener (=0) or an initiator (!=0). It returns 0 in case of
|
|
* success, -1 in case of error. The socket's source address is stored in
|
|
* <sa> for <salen> bytes.
|
|
*/
|
|
int udp6_get_src(int fd, struct sockaddr *sa, socklen_t salen, int dir)
|
|
{
|
|
int ret;
|
|
|
|
ret = sock_get_src(fd, sa, salen, dir);
|
|
if (!ret)
|
|
sa->sa_family = AF_CUST_UDP6;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Retrieves the original destination address for the socket <fd>, with <dir>
|
|
* indicating if we're a listener (=0) or an initiator (!=0). In the case of a
|
|
* listener, if the original destination address was translated, the original
|
|
* address is retrieved. It returns 0 in case of success, -1 in case of error.
|
|
* The socket's source address is stored in <sa> for <salen> bytes.
|
|
*/
|
|
int udp_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir)
|
|
{
|
|
int ret;
|
|
|
|
ret = sock_inet_get_dst(fd, sa, salen, dir);
|
|
if (!ret)
|
|
sa->sa_family = AF_CUST_UDP4;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Retrieves the original destination address for the socket <fd>, with <dir>
|
|
* indicating if we're a listener (=0) or an initiator (!=0). In the case of a
|
|
* listener, if the original destination address was translated, the original
|
|
* address is retrieved. It returns 0 in case of success, -1 in case of error.
|
|
* The socket's source address is stored in <sa> for <salen> bytes.
|
|
*/
|
|
int udp6_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir)
|
|
{
|
|
int ret;
|
|
|
|
ret = sock_get_dst(fd, sa, salen, dir);
|
|
if (!ret)
|
|
sa->sa_family = AF_CUST_UDP6;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* This function tries to bind a UDPv4/v6 listener. It may return a warning or
|
|
* an error message in <errmsg> if the message is at most <errlen> bytes long
|
|
* (including '\0'). Note that <errmsg> may be NULL if <errlen> is also zero.
|
|
* The return value is composed from ERR_ABORT, ERR_WARN,
|
|
* ERR_ALERT, ERR_RETRYABLE and ERR_FATAL. ERR_NONE indicates that everything
|
|
* was alright and that no message was returned. ERR_RETRYABLE means that an
|
|
* error occurred but that it may vanish after a retry (eg: port in use), and
|
|
* ERR_FATAL indicates a non-fixable error. ERR_WARN and ERR_ALERT do not alter
|
|
* the meaning of the error, but just indicate that a message is present which
|
|
* should be displayed with the respective level. Last, ERR_ABORT indicates
|
|
* that it's pointless to try to start other listeners. No error message is
|
|
* returned if errlen is NULL.
|
|
*/
|
|
int udp_bind_listener(struct listener *listener, char *errmsg, int errlen)
|
|
{
|
|
__label__ udp_return, udp_close_return;
|
|
int fd, err;
|
|
const char *msg = NULL;
|
|
/* copy listener addr because sometimes we need to switch family */
|
|
struct sockaddr_storage addr_inet = listener->rx.addr;
|
|
|
|
/* force to classic sock family */
|
|
addr_inet.ss_family = listener->rx.proto->sock_family;
|
|
|
|
/* ensure we never return garbage */
|
|
if (errlen)
|
|
*errmsg = 0;
|
|
|
|
if (listener->state != LI_ASSIGNED)
|
|
return ERR_NONE; /* already bound */
|
|
|
|
err = ERR_NONE;
|
|
|
|
if (listener->rx.flags & RX_F_BOUND)
|
|
goto bound;
|
|
|
|
/* TODO: Implement reuse fd. Take care that to identify fd to reuse
|
|
* listeners uses a special AF_CUST_ family and we MUST consider
|
|
* IPPROTO (sockaddr is not enough)
|
|
*/
|
|
|
|
fd = my_socketat(listener->rx.settings->netns,
|
|
listener->rx.proto->sock_family,
|
|
listener->rx.proto->sock_type,
|
|
listener->rx.proto->sock_prot);
|
|
if (fd == -1) {
|
|
err |= ERR_RETRYABLE | ERR_ALERT;
|
|
msg = "cannot create listening socket";
|
|
goto udp_return;
|
|
}
|
|
|
|
if (fd >= global.maxsock) {
|
|
err |= ERR_FATAL | ERR_ABORT | ERR_ALERT;
|
|
msg = "not enough free sockets (raise '-n' parameter)";
|
|
goto udp_close_return;
|
|
}
|
|
|
|
if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
|
|
err |= ERR_FATAL | ERR_ALERT;
|
|
msg = "cannot make socket non-blocking";
|
|
goto udp_close_return;
|
|
}
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) {
|
|
/* not fatal but should be reported */
|
|
msg = "cannot do so_reuseaddr";
|
|
err |= ERR_ALERT;
|
|
}
|
|
|
|
#ifdef SO_REUSEPORT
|
|
/* OpenBSD and Linux 3.9 support this. As it's present in old libc versions of
|
|
* Linux, it might return an error that we will silently ignore.
|
|
*/
|
|
if (global.tune.options & GTUNE_USE_REUSEPORT)
|
|
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
|
|
#endif
|
|
|
|
if (listener->rx.settings->options & RX_O_FOREIGN) {
|
|
switch (addr_inet.ss_family) {
|
|
case AF_INET:
|
|
if (!sock_inet4_make_foreign(fd)) {
|
|
msg = "cannot make listening socket transparent";
|
|
err |= ERR_ALERT;
|
|
}
|
|
break;
|
|
case AF_INET6:
|
|
if (!sock_inet6_make_foreign(fd)) {
|
|
msg = "cannot make listening socket transparent";
|
|
err |= ERR_ALERT;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef SO_BINDTODEVICE
|
|
/* Note: this might fail if not CAP_NET_RAW */
|
|
if (listener->rx.settings->interface) {
|
|
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
|
|
listener->rx.settings->interface,
|
|
strlen(listener->rx.settings->interface) + 1) == -1) {
|
|
msg = "cannot bind listener to device";
|
|
err |= ERR_WARN;
|
|
}
|
|
}
|
|
#endif
|
|
#if defined(IPV6_V6ONLY)
|
|
if (listener->rx.addr.ss_family == AF_INET6) {
|
|
/* Prepare to match the v6only option against what we really want. Note
|
|
* that sadly the two options are not exclusive to each other and that
|
|
* v6only is stronger than v4v6.
|
|
*/
|
|
if ((listener->rx.settings->options & RX_O_V6ONLY) ||
|
|
(sock_inet6_v6only_default && !(listener->rx.settings->options & RX_O_V4V6)))
|
|
setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
|
|
else
|
|
setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero));
|
|
}
|
|
#endif
|
|
|
|
if (bind(fd, (struct sockaddr *)&addr_inet, listener->rx.proto->sock_addrlen) < 0) {
|
|
err |= ERR_RETRYABLE | ERR_ALERT;
|
|
msg = "cannot bind socket";
|
|
goto udp_close_return;
|
|
}
|
|
listener->rx.flags |= RX_F_BOUND;
|
|
|
|
bound:
|
|
/* the socket is ready */
|
|
listener->rx.fd = fd;
|
|
listener->state = LI_LISTEN;
|
|
|
|
if (listener->bind_conf->frontend->mode == PR_MODE_SYSLOG)
|
|
fd_insert(fd, listener, syslog_fd_handler,
|
|
thread_mask(listener->rx.settings->bind_thread) & all_threads_mask);
|
|
else {
|
|
err |= ERR_FATAL | ERR_ALERT;
|
|
msg = "UDP is not yet supported on this proxy mode";
|
|
goto udp_close_return;
|
|
}
|
|
|
|
udp_return:
|
|
if (msg && errlen) {
|
|
char pn[INET6_ADDRSTRLEN];
|
|
|
|
addr_to_str(&addr_inet, pn, sizeof(pn));
|
|
snprintf(errmsg, errlen, "%s [%s:%d]", msg, pn, get_host_port(&addr_inet));
|
|
}
|
|
return err;
|
|
|
|
udp_close_return:
|
|
close(fd);
|
|
goto udp_return;
|
|
}
|
|
|
|
/* Add <listener> to the list of udp4 listeners, on port <port>. The
|
|
* listener's state is automatically updated from LI_INIT to LI_ASSIGNED.
|
|
* The number of listeners for the protocol is updated.
|
|
*/
|
|
static void udp4_add_listener(struct listener *listener, int port)
|
|
{
|
|
if (listener->state != LI_INIT)
|
|
return;
|
|
listener->state = LI_ASSIGNED;
|
|
listener->rx.proto = &proto_udp4;
|
|
((struct sockaddr_in *)(&listener->rx.addr))->sin_port = htons(port);
|
|
LIST_ADDQ(&proto_udp4.listeners, &listener->rx.proto_list);
|
|
proto_udp4.nb_listeners++;
|
|
}
|
|
|
|
/* Add <listener> to the list of udp6 listeners, on port <port>. The
|
|
* listener's state is automatically updated from LI_INIT to LI_ASSIGNED.
|
|
* The number of listeners for the protocol is updated.
|
|
*/
|
|
static void udp6_add_listener(struct listener *listener, int port)
|
|
{
|
|
if (listener->state != LI_INIT)
|
|
return;
|
|
listener->state = LI_ASSIGNED;
|
|
listener->rx.proto = &proto_udp6;
|
|
((struct sockaddr_in *)(&listener->rx.addr))->sin_port = htons(port);
|
|
LIST_ADDQ(&proto_udp6.listeners, &listener->rx.proto_list);
|
|
proto_udp6.nb_listeners++;
|
|
}
|
|
|
|
/* Pause a listener. Returns < 0 in case of failure, 0 if the listener
|
|
* was totally stopped, or > 0 if correctly paused.
|
|
*/
|
|
int udp_pause_listener(struct listener *l)
|
|
{
|
|
/* we don't support pausing on UDP */
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-indent-level: 8
|
|
* c-basic-offset: 8
|
|
* End:
|
|
*/
|