# USE_ZLIB : enable zlib library support.
# USE_CPU_AFFINITY : enable pinning processes to CPU on Linux. Automatic.
# USE_TFO : enable TCP fast open. Supported on Linux >= 3.7.
+# USE_NS : enable network namespace support. Supported on Linux >= 2.6.24.
#
# Options can be forced by specifying "USE_xxx=1" or can be disabled by using
# "USE_xxx=" (empty string).
COPTS += -finstrument-functions
endif
+ifneq ($(USE_NS),)
+OPTIONS_CFLAGS += -DCONFIG_HAP_NS
+BUILD_OPTIONS += $(call ignore_implicit,USE_NS)
+endif
+
#### Global link options
# These options are added at the end of the "ld" command line. Use LDFLAGS to
# add options at the beginning of the "ld" command line if needed.
src/stream_interface.o src/dumpstats.o src/proto_tcp.o \
src/session.o src/hdr_idx.o src/ev_select.o src/signal.o \
src/acl.o src/sample.o src/memory.o src/freq_ctr.o src/auth.o \
- src/compression.o src/payload.o src/hash.o src/pattern.o src/map.o
+ src/compression.o src/payload.o src/hash.o src/pattern.o src/map.o \
+ src/namespace.o
EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o \
$(EBTREE_DIR)/eb32tree.o $(EBTREE_DIR)/eb64tree.o \
--- /dev/null
+Linux network namespace support for HAProxy
+===========================================
+
+HAProxy supports proxying between Linux network namespaces. This
+feature can be used, for example, in a multi-tenant networking
+environment to proxy between different networks. HAProxy can also act
+as a front-end proxy for non namespace-aware services.
+
+The proxy protocol has been extended to support transferring the
+namespace information, so the originating namespace information can be
+kept. This is useful when chaining multiple proxies and services.
+
+To enable Linux namespace support, compile HAProxy with the `USE_NS=1`
+make option.
+
+
+## Setting up namespaces on Linux
+
+To create network namespaces, use the 'ip netns' command. See the
+manual page ip-netns(8) for details.
+
+Make sure that the file descriptors representing the network namespace
+are located under `/var/run/netns`.
+
+For example, you can create a network namespace and assign one of the
+networking interfaces to the new namespace:
+
+```
+$ ip netns add netns1
+$ ip link set eth7 netns netns1
+```
+
+
+## Listing namespaces in the configuration file
+
+HAProxy uses namespaces explicitly listed in its configuration file.
+If you are not using namespace information received through the proxy
+protocol, this usually means that you must specify namespaces for
+listeners and servers in the configuration file with the 'namespace'
+keyword.
+
+However, if you're using the namespace information received through
+the proxy protocol to determine the namespace of servers (see
+'namespace * below'), you have to explicitly list all allowed
+namespaces in the namespace_list section of your configuration file:
+
+```
+namespace_list
+ namespace netns1
+ namespace netns2
+```
+
+
+## Namespace information flow
+
+The haproxy process always runs in the namespace it was started on.
+This is the default namespace.
+
+The bind addresses of listeners can have their namespace specified in
+the configuration file. Unless specified, sockets associated with
+listener bind addresses are created in the default namespace. For
+example, this creates a listener in the netns2 namespace:
+
+```
+frontend f_example
+ bind 192.168.1.1:80 namespace netns2
+ default_backend http
+```
+
+Each client connection is associated with its source namespace. By
+default, this is the namespace of the bind socket it arrived on, but
+can be overridden by information received through the proxy protocol.
+Proxy protocol v2 supports transferring namespace information, so if
+it is enabled for the listener, it can override the associated
+namespace of the connection.
+
+Servers can have their namespaces specified in the configuration file
+with the 'namespace' keyword:
+
+```
+backend b_example
+ server s1 192.168.1.100:80 namespace netns2
+```
+
+If no namespace is set for a server, it is assumed that it is in the
+default namespace. When specified, outbound sockets to the server are
+created in the network namespace configured. To create the outbound
+(server) connection in the namespace associated with the client, use
+the '*' namespace. This is especially useful when using the
+destination address and namespace received from the proxy protocol.
+
+```
+frontend f_example
+ bind 192.168.1.1:9990 accept-proxy
+ default_backend b_example
+
+backend b_example
+ mode tcp
+ source 0.0.0.0 usesrc clientip
+ server snodes * namespace *
+```
+
+If HAProxy is configured to send proxy protocol v2 headers to the
+server, the outgoing header will always contain the namespace
+associated with the client connection, not the namespace configured
+for the server.
--- /dev/null
+#ifndef NAMESPACE_H
+
+#include <stdlib.h>
+#include <ebistree.h>
+
+struct netns_entry;
+int my_socketat(const struct netns_entry *ns, int domain, int type, int protocol);
+
+#ifdef CONFIG_HAP_NS
+
+struct netns_entry
+{
+ struct ebpt_node node;
+ size_t name_len;
+ int fd;
+};
+
+struct netns_entry* netns_store_insert(const char *ns_name);
+const struct netns_entry* netns_store_lookup(const char *ns_name, size_t ns_name_len);
+
+int netns_init(void);
+#endif /* CONFIG_HAP_NS */
+
+#endif /* NAMESPACE_H */
conn->t.sock.fd = -1; /* just to help with debugging */
conn->err_code = CO_ER_NONE;
conn->target = NULL;
+ conn->proxy_netns = NULL;
}
/* Tries to allocate a new connection and initialized its main fields. The
} sock;
} t;
enum obj_type *target; /* the target to connect to (server, proxy, applet, ...) */
+ const struct netns_entry *proxy_netns;
struct {
struct sockaddr_storage from; /* client address, or address to spoof when connecting to the server */
struct sockaddr_storage to; /* address reached by the client, or address to connect to */
#define PP2_TYPE_SSL 0x20
#define PP2_TYPE_SSL_VERSION 0x21
#define PP2_TYPE_SSL_CN 0x22
+#define PP2_TYPE_NETNS 0x30
+#define TLV_HEADER_SIZE 3
struct tlv {
uint8_t type;
uint8_t length_hi;
int maxseg; /* for TCP, advertised MSS */
char *interface; /* interface name or NULL */
+ const struct netns_entry *netns; /* network namespace of the listener*/
+
struct list by_fe; /* chaining in frontend's list of listeners */
struct list by_bind; /* chaining in bind_conf's list of listeners */
struct bind_conf *bind_conf; /* "bind" line settings, include SSL settings among other things */
#define SRV_F_BACKUP 0x0001 /* this server is a backup server */
#define SRV_F_MAPPORTS 0x0002 /* this server uses mapped ports */
#define SRV_F_NON_STICK 0x0004 /* never add connections allocated to this server to a stick table */
+#define SRV_F_USE_NS_FROM_PP 0x0008 /* use namespace associated with connection if present */
/* configured server options for send-proxy (server->pp_opts) */
#define SRV_PP_V1 0x0001 /* proxy protocol version 1 */
unsigned lb_nodes_now; /* number of lb_nodes placed in the tree (C-HASH) */
struct tree_occ *lb_nodes; /* lb_nodes_tot * struct tree_occ */
+ const struct netns_entry *netns; /* contains network namespace name or NULL. Network namespace comes from configuration */
/* warning, these structs are huge, keep them at the bottom */
struct sockaddr_storage addr; /* the address to connect to */
struct protocol *proto; /* server address protocol */
#include <common/hash.h>
#include <common/ticks.h>
#include <common/time.h>
+#include <common/namespace.h>
#include <types/global.h>
return err;
}
-
/*
* This function assigns a server address to a session, and sets SN_ADDR_SET.
* The address is taken from the currently assigned server, or from the
return SRV_STATUS_INTERNAL;
}
+ /* Copy network namespace from client connection */
+ srv_conn->proxy_netns = cli_conn->proxy_netns;
+
s->flags |= SN_ADDR_SET;
return SRV_STATUS_OK;
}
-
/* This function assigns a server to session <s> if required, and can add the
* connection to either the assigned server's queue or to the proxy's queue.
* If ->srv_conn is set, the session is first released from the server.
#include <common/standard.h>
#include <common/time.h>
#include <common/uri_auth.h>
+#include <common/namespace.h>
#include <types/capture.h>
#include <types/compression.h>
return err_code;
}
+int
+cfg_parse_netns(const char *file, int linenum, char **args, int kwm)
+{
+#ifdef CONFIG_HAP_NS
+ const char *err;
+ const char *item = args[0];
+
+ if (!strcmp(item, "namespace_list")) {
+ return 0;
+ }
+ else if (!strcmp(item, "namespace")) {
+ size_t idx = 1;
+ const char *current;
+ while (*(current = args[idx++])) {
+ err = invalid_char(current);
+ if (err) {
+ Alert("parsing [%s:%d]: character '%c' is not permitted in '%s' name '%s'.\n",
+ file, linenum, *err, item, current);
+ return ERR_ALERT | ERR_FATAL;
+ }
+
+ if (netns_store_lookup(current, strlen(current))) {
+ Alert("parsing [%s:%d]: Namespace '%s' is already added.\n",
+ file, linenum, current);
+ return ERR_ALERT | ERR_FATAL;
+ }
+ if (!netns_store_insert(current)) {
+ Alert("parsing [%s:%d]: Cannot open namespace '%s'.\n",
+ file, linenum, current);
+ return ERR_ALERT | ERR_FATAL;
+ }
+ }
+ }
+
+ return 0;
+#else
+ Alert("parsing [%s:%d]: namespace support is not compiled in.",
+ file, linenum);
+ return ERR_ALERT | ERR_FATAL;
+#endif
+}
+
int
cfg_parse_users(const char *file, int linenum, char **args, int kwm)
{
!cfg_register_section("defaults", cfg_parse_listen) ||
!cfg_register_section("global", cfg_parse_global) ||
!cfg_register_section("userlist", cfg_parse_users) ||
- !cfg_register_section("peers", cfg_parse_peers))
+ !cfg_register_section("peers", cfg_parse_peers) ||
+ !cfg_register_section("namespace_list", cfg_parse_netns))
return -1;
if ((f=fopen(file,"r")) == NULL)
#include <common/compat.h>
#include <common/config.h>
+#include <common/namespace.h>
#include <proto/connection.h>
#include <proto/fd.h>
c->flags = f;
}
+/*
+ * Get data length from tlv
+ */
+static int get_tlv_length(const struct tlv *src)
+{
+ return (src->length_hi << 8) | src->length_lo;
+}
+
/* This handshake handler waits a PROXY protocol header at the beginning of the
* raw data stream. The header looks like this :
*
char *line, *end;
struct proxy_hdr_v2 *hdr_v2;
const char v2sig[] = PP2_SIGNATURE;
+ int tlv_length = 0;
/* we might have been called just after an asynchronous shutr */
if (conn->flags & CO_FL_SOCK_RD_SH)
((struct sockaddr_in *)&conn->addr.to)->sin_addr.s_addr = hdr_v2->addr.ip4.dst_addr;
((struct sockaddr_in *)&conn->addr.to)->sin_port = hdr_v2->addr.ip4.dst_port;
conn->flags |= CO_FL_ADDR_FROM_SET | CO_FL_ADDR_TO_SET;
+ tlv_length = ntohs(hdr_v2->len) - PP2_ADDR_LEN_INET;
break;
case 0x21: /* TCPv6 */
if (ntohs(hdr_v2->len) < PP2_ADDR_LEN_INET6)
memcpy(&((struct sockaddr_in6 *)&conn->addr.to)->sin6_addr, hdr_v2->addr.ip6.dst_addr, 16);
((struct sockaddr_in6 *)&conn->addr.to)->sin6_port = hdr_v2->addr.ip6.dst_port;
conn->flags |= CO_FL_ADDR_FROM_SET | CO_FL_ADDR_TO_SET;
+ tlv_length = ntohs(hdr_v2->len) - PP2_ADDR_LEN_INET6;
break;
}
+
+ /* TLV parsing */
+ if (tlv_length > 0) {
+ int tlv_offset = trash.len - tlv_length;
+
+ while (tlv_offset + TLV_HEADER_SIZE <= trash.len) {
+ const struct tlv *tlv_packet = (struct tlv *) &trash.str[tlv_offset];
+ const int tlv_len = get_tlv_length(tlv_packet);
+ tlv_offset += tlv_len + TLV_HEADER_SIZE;
+
+ switch (tlv_packet->type) {
+#ifdef CONFIG_HAP_NS
+ case PP2_TYPE_NETNS: {
+ const struct netns_entry *ns;
+ ns = netns_store_lookup((char*)tlv_packet->value, tlv_len);
+ if (ns)
+ conn->proxy_netns = ns;
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+ }
+ }
+
/* unsupported protocol, keep local connection address */
break;
case 0x00: /* LOCAL command */
return ret;
}
-#ifdef USE_OPENSSL
-static int make_tlv(char *dest, int dest_len, char type, uint16_t length, char *value)
+#if defined(USE_OPENSSL) || defined(CONFIG_HAP_NS)
+static int make_tlv(char *dest, int dest_len, char type, uint16_t length, const char *value)
{
struct tlv *tlv;
struct sockaddr_storage null_addr = {0};
struct sockaddr_storage *src = &null_addr;
struct sockaddr_storage *dst = &null_addr;
+
#ifdef USE_OPENSSL
- int tlv_len = 0;
char *value = NULL;
struct tlv_ssl *tlv;
int ssl_tlv_len = 0;
src = &remote->addr.from;
dst = &remote->addr.to;
}
+
if (src && dst && src->ss_family == dst->ss_family && src->ss_family == AF_INET) {
if (buf_len < PP2_HDR_LEN_INET)
return 0;
tlv->client |= PP2_CLIENT_SSL;
value = ssl_sock_get_version(remote);
if (value) {
- tlv_len = make_tlv(&buf[ret+ssl_tlv_len], (buf_len-ret-ssl_tlv_len), PP2_TYPE_SSL_VERSION, strlen(value), value);
- ssl_tlv_len += tlv_len;
+ ssl_tlv_len += make_tlv(&buf[ret+ssl_tlv_len], (buf_len-ret-ssl_tlv_len), PP2_TYPE_SSL_VERSION, strlen(value), value);
}
if (ssl_sock_get_cert_used_sess(remote)) {
tlv->client |= PP2_CLIENT_CERT_SESS;
if (srv->pp_opts & SRV_PP_V2_SSL_CN) {
cn_trash = get_trash_chunk();
if (ssl_sock_get_remote_common_name(remote, cn_trash) > 0) {
- tlv_len = make_tlv(&buf[ret+ssl_tlv_len], (buf_len - ret - ssl_tlv_len), PP2_TYPE_SSL_CN, cn_trash->len, cn_trash->str);
- ssl_tlv_len += tlv_len;
+ ssl_tlv_len += make_tlv(&buf[ret+ssl_tlv_len], (buf_len - ret - ssl_tlv_len), PP2_TYPE_SSL_CN, cn_trash->len, cn_trash->str);
}
}
}
}
#endif
+#ifdef CONFIG_HAP_NS
+ if (remote && (remote->proxy_netns)) {
+ if ((buf_len - ret) < sizeof(struct tlv))
+ return 0;
+ ret += make_tlv(&buf[ret], buf_len, PP2_TYPE_NETNS, remote->proxy_netns->name_len, remote->proxy_netns->node.key);
+ }
+#endif
+
hdr->len = htons((uint16_t)(ret - PP2_HEADER_LEN));
return ret;
#include <common/errors.h>
#include <common/memory.h>
#include <common/mini-clist.h>
+#include <common/namespace.h>
#include <common/regex.h>
#include <common/standard.h>
#include <common/time.h>
#endif
"\n");
#endif
+
+#if defined(CONFIG_HAP_NS)
+ printf("Built with network namespace support\n");
+#endif
putchar('\n');
list_pollers(stdout);
exit(1);
}
+#ifdef CONFIG_HAP_NS
+ err_code |= netns_init();
+ if (err_code & (ERR_ABORT|ERR_FATAL)) {
+ Alert("Failed to initialize namespace support.\n");
+ exit(1);
+ }
+#endif
+
if (global.mode & MODE_CHECK) {
struct peers *pr;
struct proxy *px;
--- /dev/null
+#define _GNU_SOURCE
+
+#include <common/namespace.h>
+#include <common/compiler.h>
+#include <common/hash.h>
+#include <common/errors.h>
+#include <proto/log.h>
+#include <types/global.h>
+
+#include <sched.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/socket.h>
+
+#include <string.h>
+#ifdef CONFIG_HAP_NS
+
+/* Opens the namespace <ns_name> and returns the FD or -1 in case of error
+ * (check errno).
+ */
+static int open_named_namespace(const char *ns_name)
+{
+ if (chunk_printf(&trash, "/var/run/netns/%s", ns_name) < 0)
+ return -1;
+ return open(trash.str, O_RDONLY);
+}
+
+static int default_namespace = -1;
+
+static int init_default_namespace()
+{
+ if (chunk_printf(&trash, "/proc/%d/ns/net", getpid()) < 0)
+ return -1;
+ default_namespace = open(trash.str, O_RDONLY);
+ return default_namespace;
+}
+
+static struct eb_root namespace_tree_root = EB_ROOT;
+
+int netns_init(void)
+{
+ int err_code = 0;
+
+ /* if no namespaces have been defined in the config then
+ * there is no point in trying to initialize anything:
+ * my_socketat() will never be called with a valid namespace
+ * structure and thus switching back to the default namespace
+ * is not needed either */
+ if (!eb_is_empty(&namespace_tree_root)) {
+ if (init_default_namespace() < 0) {
+ Alert("Failed to open the default namespace.\n");
+ err_code |= ERR_ALERT | ERR_FATAL;
+ }
+ }
+
+ return err_code;
+}
+
+struct netns_entry* netns_store_insert(const char *ns_name)
+{
+ struct netns_entry *entry = NULL;
+ int fd = open_named_namespace(ns_name);
+ if (fd == -1)
+ goto out;
+
+ entry = (struct netns_entry *)calloc(1, sizeof(struct netns_entry));
+ if (!entry)
+ goto out;
+ entry->fd = fd;
+ entry->node.key = strdup(ns_name);
+ entry->name_len = strlen(ns_name);
+ ebis_insert(&namespace_tree_root, &entry->node);
+out:
+ return entry;
+}
+
+const struct netns_entry* netns_store_lookup(const char *ns_name, size_t ns_name_len)
+{
+ struct ebpt_node *node;
+
+ node = ebis_lookup_len(&namespace_tree_root, ns_name, ns_name_len);
+ if (node)
+ return ebpt_entry(node, struct netns_entry, node);
+ else
+ return NULL;
+}
+#endif
+
+/* Opens a socket in the namespace described by <ns> with the parameters <domain>,
+ * <type> and <protocol> and returns the FD or -1 in case of error (check errno).
+ */
+int my_socketat(const struct netns_entry *ns, int domain, int type, int protocol)
+{
+ int sock;
+
+#ifdef CONFIG_HAP_NS
+ if (default_namespace < 0 ||
+ (ns && setns(ns->fd, CLONE_NEWNET) == -1))
+ return -1;
+#endif
+ sock = socket(domain, type, protocol);
+
+#ifdef CONFIG_HAP_NS
+ if (ns && setns(default_namespace, CLONE_NEWNET) == -1) {
+ close(sock);
+ return -1;
+ }
+#endif
+
+ return sock;
+}
#include <common/errors.h>
#include <common/mini-clist.h>
#include <common/standard.h>
+#include <common/namespace.h>
#include <types/global.h>
#include <types/capture.h>
return 0;
}
+static int create_server_socket(struct connection *conn)
+{
+ const struct netns_entry *ns = objt_server(conn->target)->netns;
+
+ if (objt_server(conn->target)->flags & SRV_F_USE_NS_FROM_PP)
+ ns = conn->proxy_netns;
+
+ return my_socketat(ns, conn->addr.to.ss_family, SOCK_STREAM, IPPROTO_TCP);
+}
/*
* This function initiates a TCP connection establishment to the target assigned
return SN_ERR_INTERNAL;
}
- if ((fd = conn->t.sock.fd = socket(conn->addr.to.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
+ fd = conn->t.sock.fd = create_server_socket(conn);
+
+ if (fd == -1) {
qfprintf(stderr, "Cannot get a server socket.\n");
if (errno == ENFILE) {
fd = listener->fd;
ext = (fd >= 0);
- if (!ext && (fd = socket(listener->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
- err |= ERR_RETRYABLE | ERR_ALERT;
- msg = "cannot create listening socket";
- goto tcp_return;
+ if (!ext) {
+ fd = my_socketat(listener->netns, listener->addr.ss_family, SOCK_STREAM, IPPROTO_TCP);
+
+ if (fd == -1) {
+ err |= ERR_RETRYABLE | ERR_ALERT;
+ msg = "cannot create listening socket";
+ goto tcp_return;
+ }
}
if (fd >= global.maxsock) {
}
#endif
+#ifdef CONFIG_HAP_NS
+/* parse the "namespace" bind keyword */
+static int bind_parse_namespace(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
+{
+ struct listener *l;
+ char *namespace = NULL;
+
+ if (!*args[cur_arg + 1]) {
+ memprintf(err, "'%s' : missing namespace id", args[cur_arg]);
+ return ERR_ALERT | ERR_FATAL;
+ }
+ namespace = args[cur_arg + 1];
+
+ list_for_each_entry(l, &conf->listeners, by_bind) {
+ l->netns = netns_store_lookup(namespace, strlen(namespace));
+
+ if (l->netns == NULL)
+ l->netns = netns_store_insert(namespace);
+
+ if (l->netns == NULL) {
+ Alert("Cannot open namespace '%s'.\n", args[cur_arg + 1]);
+ return ERR_ALERT | ERR_FATAL;
+ }
+ }
+ return 0;
+}
+#endif
+
static struct cfg_kw_list cfg_kws = {ILH, {
{ CFG_LISTEN, "tcp-request", tcp_parse_tcp_req },
{ CFG_LISTEN, "tcp-response", tcp_parse_tcp_rep },
#ifdef IPV6_V6ONLY
{ "v4v6", bind_parse_v4v6, 0 }, /* force socket to bind to IPv4+IPv6 */
{ "v6only", bind_parse_v6only, 0 }, /* force socket to bind to IPv6 only */
+#endif
+#ifdef CONFIG_HAP_NS
+ { "namespace", bind_parse_namespace, 1 },
#endif
/* the versions with the NULL parse function*/
{ "defer-accept", NULL, 0 },
#include <common/cfgparse.h>
#include <common/config.h>
#include <common/errors.h>
+#include <common/namespace.h>
#include <common/time.h>
#include <types/global.h>
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
+ else if (!defsrv && !strcmp(args[cur_arg], "namespace")) {
+#ifdef CONFIG_HAP_NS
+ char *arg = args[cur_arg + 1];
+ if (!strcmp(arg, "*")) {
+ newsrv->flags |= SRV_F_USE_NS_FROM_PP;
+ } else {
+ newsrv->netns = netns_store_lookup(arg, strlen(arg));
+
+ if (newsrv->netns == NULL)
+ newsrv->netns = netns_store_insert(arg);
+
+ if (newsrv->netns == NULL) {
+ Alert("Cannot open namespace '%s'.\n", args[cur_arg + 1]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ }
+#else
+ Alert("parsing [%s:%d] : '%s' : '%s' option not implemented.\n",
+ file, linenum, args[0], args[cur_arg]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+#endif
+ cur_arg += 2;
+ }
else {
static int srv_dumped;
struct srv_kw *kw;
cli_conn->addr.from = *addr;
cli_conn->flags |= CO_FL_ADDR_FROM_SET;
cli_conn->target = &l->obj_type;
+ cli_conn->proxy_netns = l->netns;
if (unlikely((s = pool_alloc2(pool2_session)) == NULL))
goto out_free_conn;