]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MAJOR: namespace: add Linux network namespace support
authorKOVACS Krisztian <hidden@balabit.com>
Mon, 17 Nov 2014 14:11:45 +0000 (15:11 +0100)
committerWilly Tarreau <w@1wt.eu>
Fri, 21 Nov 2014 06:51:57 +0000 (07:51 +0100)
This patch makes it possible to create binds and servers in separate
namespaces.  This can be used to proxy between multiple completely independent
virtual networks (with possibly overlapping IP addresses) and a
non-namespace-aware proxy implementation that supports the proxy protocol (v2).

The setup is something like this:

net1 on VLAN 1 (namespace 1) -\
net2 on VLAN 2 (namespace 2) -- haproxy ==== proxy (namespace 0)
net3 on VLAN 3 (namespace 3) -/

The proxy is configured to make server connections through haproxy and sending
the expected source/target addresses to haproxy using the proxy protocol.

The network namespace setup on the haproxy node is something like this:

= 8< =
$ cat setup.sh
ip netns add 1
ip link add link eth1 type vlan id 1
ip link set eth1.1 netns 1
ip netns exec 1 ip addr add 192.168.91.2/24 dev eth1.1
ip netns exec 1 ip link set eth1.$id up
...
= 8< =

= 8< =
$ cat haproxy.cfg
frontend clients
  bind 127.0.0.1:50022 namespace 1 transparent
  default_backend scb

backend server
  mode tcp
  server server1 192.168.122.4:2222 namespace 2 send-proxy-v2
= 8< =

A bind line creates the listener in the specified namespace, and connections
originating from that listener also have their network namespace set to
that of the listener.

A server line either forces the connection to be made in a specified
namespace or may use the namespace from the client-side connection if that
was set.

For more documentation please read the documentation included in the patch
itself.

Signed-off-by: KOVACS Tamas <ktamas@balabit.com>
Signed-off-by: Sarkozi Laszlo <laszlo.sarkozi@balabit.com>
Signed-off-by: KOVACS Krisztian <hidden@balabit.com>
15 files changed:
Makefile
doc/network-namespaces.txt [new file with mode: 0644]
include/common/namespace.h [new file with mode: 0644]
include/proto/connection.h
include/types/connection.h
include/types/listener.h
include/types/server.h
src/backend.c
src/cfgparse.c
src/connection.c
src/haproxy.c
src/namespace.c [new file with mode: 0644]
src/proto_tcp.c
src/server.c
src/session.c

index ac93fed74e869734826f6bb33d79e0d46563cad6..4671759cd83f41eeb017b5a2d19ea24dfd826561 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -34,6 +34,7 @@
 #   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).
@@ -617,6 +618,11 @@ TRACE_COPTS := $(filter-out -O0 -O1 -O2 -pg -finstrument-functions,$(COPTS)) -O3
 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.
@@ -657,7 +663,8 @@ OBJS = src/haproxy.o src/sessionhash.o src/base64.o src/protocol.o \
        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 \
diff --git a/doc/network-namespaces.txt b/doc/network-namespaces.txt
new file mode 100644 (file)
index 0000000..9448f43
--- /dev/null
@@ -0,0 +1,106 @@
+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.
diff --git a/include/common/namespace.h b/include/common/namespace.h
new file mode 100644 (file)
index 0000000..e67742b
--- /dev/null
@@ -0,0 +1,24 @@
+#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 */
index c9972b1550f7c963e774ee2e706b68367f2e929d..76093a51b5e4278f890f4426111793e2e81b9403 100644 (file)
@@ -458,6 +458,7 @@ static inline void conn_init(struct connection *conn)
        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
index b31700737ba88342d045426ff52adac1c6ccba35..4c610969eaef96abd03eaf5fdd45238494cfb429 100644 (file)
@@ -261,6 +261,7 @@ struct connection {
                } 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 */
@@ -330,7 +331,9 @@ struct proxy_hdr_v2 {
 #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;
index 83b63afd70026a5feeb9ac009ad345306101c434..725808b347d2d87c3632390bccb868d9ead48ca1 100644 (file)
@@ -177,6 +177,8 @@ struct listener {
        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 */
index 5798fab4e16180bd668ff9c0fabe9b5f13810a04..4847defd1f8ead494390f8aea7af5fe5276488ee 100644 (file)
@@ -85,6 +85,7 @@ enum srv_admin {
 #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 */
@@ -191,6 +192,7 @@ struct server {
        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 */
index 6fe03f95176342ff8c72b9854379b3985763bb74..e2221603eb0b947ded5b7e06a7c1b3740541a38d 100644 (file)
@@ -26,6 +26,7 @@
 #include <common/hash.h>
 #include <common/ticks.h>
 #include <common/time.h>
+#include <common/namespace.h>
 
 #include <types/global.h>
 
@@ -720,7 +721,6 @@ int assign_server(struct session *s)
        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
@@ -803,11 +803,13 @@ int assign_server_address(struct session *s)
                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.
index a6a00517d0451c0e65200611e553b298ec952c9c..c8b15467f7fe61c6aac58912d5ebc87cdbf0b222 100644 (file)
@@ -41,6 +41,7 @@
 #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>
@@ -5644,6 +5645,48 @@ stats_error_parsing:
        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)
 {
@@ -5856,7 +5899,8 @@ int readcfgfile(const char *file)
            !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)
index b9f5c42b44e620207fc1b0a860ccff0ffb67d8bf..c21c98bae95a6ffd12e6f41f84eb9311d0ec4b9b 100644 (file)
@@ -14,6 +14,7 @@
 
 #include <common/compat.h>
 #include <common/config.h>
+#include <common/namespace.h>
 
 #include <proto/connection.h>
 #include <proto/fd.h>
@@ -217,6 +218,14 @@ void conn_update_sock_polling(struct connection *c)
        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 :
  *
@@ -245,6 +254,7 @@ int conn_recv_proxy(struct connection *conn, int flag)
        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)
@@ -434,6 +444,7 @@ int conn_recv_proxy(struct connection *conn, int flag)
                        ((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)
@@ -446,8 +457,35 @@ int conn_recv_proxy(struct connection *conn, int flag)
                        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 */
@@ -597,8 +635,8 @@ int make_proxy_line_v1(char *buf, int buf_len, struct sockaddr_storage *src, str
        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;
 
@@ -623,8 +661,8 @@ int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct connec
        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;
@@ -639,6 +677,7 @@ int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct connec
                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;
@@ -681,8 +720,7 @@ int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct connec
                        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;
@@ -693,8 +731,7 @@ int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct connec
                        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);
                                }
                        }
                }
@@ -704,6 +741,14 @@ int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct connec
        }
 #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;
index 13c3d265e821bece42d1a56cae37bba4e5756697..a3069529a2413560b95752510d695d4e5fb6fc14 100644 (file)
@@ -66,6 +66,7 @@
 #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>
@@ -354,6 +355,10 @@ void display_build_opts()
 #endif
               "\n");
 #endif
+
+#if defined(CONFIG_HAP_NS)
+       printf("Built with network namespace support\n");
+#endif
        putchar('\n');
 
        list_pollers(stdout);
@@ -721,6 +726,14 @@ void init(int argc, char **argv)
                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;
diff --git a/src/namespace.c b/src/namespace.c
new file mode 100644 (file)
index 0000000..a22f1a5
--- /dev/null
@@ -0,0 +1,114 @@
+#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;
+}
index cfa62f72223cdf1033225b46a0d5b0470b5bed7e..afb9e155a2c03466e27e6b74009658885f7bf5de 100644 (file)
@@ -33,6 +33,7 @@
 #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>
@@ -247,6 +248,15 @@ int tcp_bind_socket(int fd, int flags, struct sockaddr_storage *local, struct so
        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
@@ -301,7 +311,9 @@ int tcp_connect_server(struct connection *conn, int data, int delack)
                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) {
@@ -741,10 +753,14 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
        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) {
@@ -2007,6 +2023,34 @@ static int bind_parse_interface(char **args, int cur_arg, struct proxy *px, stru
 }
 #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 },
@@ -2065,6 +2109,9 @@ static struct bind_kw_list bind_kws = { "TCP", { }, {
 #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 },
index 94a31b6659bed753184c3e6385d3f8d04e5ad17b..4f9fad859b9a2afc6485773dd1c1b2de909a2a61 100644 (file)
@@ -16,6 +16,7 @@
 #include <common/cfgparse.h>
 #include <common/config.h>
 #include <common/errors.h>
+#include <common/namespace.h>
 #include <common/time.h>
 
 #include <types/global.h>
@@ -1501,6 +1502,31 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
                                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;
index 675f26df3d5a6e0c0cc99dbafbdda547fdf1c141..0ceb031b992008a25c37389d1570010728e95341 100644 (file)
@@ -90,6 +90,7 @@ int session_accept(struct listener *l, int cfd, struct sockaddr_storage *addr)
        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;