Skip to content

Commit

Permalink
MAJOR: namespace: add Linux network namespace support
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
krisztian-kovacs authored and wtarreau committed Nov 21, 2014
1 parent efd3aa9 commit b3e54fe
Show file tree
Hide file tree
Showing 15 changed files with 453 additions and 16 deletions.
9 changes: 8 additions & 1 deletion Makefile
Expand Up @@ -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).
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 \
Expand Down
106 changes: 106 additions & 0 deletions doc/network-namespaces.txt
@@ -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.
24 changes: 24 additions & 0 deletions include/common/namespace.h
@@ -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 */
1 change: 1 addition & 0 deletions include/proto/connection.h
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions include/types/connection.h
Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions include/types/listener.h
Expand Up @@ -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 */
Expand Down
2 changes: 2 additions & 0 deletions include/types/server.h
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down
6 changes: 4 additions & 2 deletions src/backend.c
Expand Up @@ -26,6 +26,7 @@
#include <common/hash.h>
#include <common/ticks.h>
#include <common/time.h>
#include <common/namespace.h>

#include <types/global.h>

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
46 changes: 45 additions & 1 deletion src/cfgparse.c
Expand Up @@ -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>
Expand Down Expand Up @@ -5644,6 +5645,48 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
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)
{
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit b3e54fe

Please sign in to comment.