Skip to content

Commit

Permalink
Add network namespace to domain configuration
Browse files Browse the repository at this point in the history
Add a configuration key networkNamespace to the server object in the
JSON-based domain file format.

If this optional key is set, the library will attempt to create the
transport layer connection to the server's address originating from
this Linux network namespace.

Resolves #17.

Signed-off-by: Mattias Rönnblom <[email protected]>
  • Loading branch information
m-ronnblom committed May 19, 2022
1 parent 241a1bc commit 1faf230
Show file tree
Hide file tree
Showing 13 changed files with 359 additions and 81 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ source tree.
To run the tests, issue:
make check

For the test suite to cover functionality related to network
namespaces, the CAP_SYS_ADMIN capability is required.

## Documentation

API documentation in Doxygen format is available in paf.h. `make
Expand Down
20 changes: 17 additions & 3 deletions include/paf.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,15 @@ extern "C" {
* with a key "servers". The value of "servers" must be an array of
* zero or more JSON objects, each representing a server.
*
* The server object must have a key "address", with a string value.
* The server object must have a key "address", with the server's
* address in XCM format as its value.
*
* A server object may include a key "networkNamespace". If present,
* the library will make sure the outoing transport layer connection
* originates from a Linux network namespace named per the key's
* value. To switch between network namespaces, the process needs the
* @c CAP_SYS_ADMIN capability. The network namespace needs to be
* named as per iproute2 conventions.
*
* In case the transport protocol uses TLS, three optional keys may be
* present in the server object:
Expand All @@ -111,17 +119,23 @@ extern "C" {
* "address": "tls:5.6.7.8:8888"
* },
* {
* "address": "tcp:fqdn:1111",
* "networkNamespace": "oam"
* },
* {
* "address": "ux:foo"
* }
* ]
* }
* @endcode
*
* The same configuration (minus the certificate-related
* configuration), but in the newline-separated format:
* The same configuration (minus the network namespace and the
* certificate-related configuration), but in the newline-separated
* format:
* @code
* tls:1.2.3.4:4444
* tls:5.6.7.8:8888
* tcp:fqdn:1111
* ux:foo
* @endcode
*
Expand Down
16 changes: 10 additions & 6 deletions src/domain_conf.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ static bool has_server_addr(struct domain_conf *conf, const char *server_addr)
}

static int add_server(struct domain_conf *conf, const char *filename,
const char *addr, const char *cert_file,
const char *key_file, const char *tc_file,
const char *log_ref)
const char *net_ns, const char *addr,
const char *cert_file, const char *key_file,
const char *tc_file, const char *log_ref)
{
char proto[64];

Expand All @@ -65,7 +65,7 @@ static int add_server(struct domain_conf *conf, const char *filename,
ut_realloc(conf->servers, sizeof(struct server *) * new_num_servers);

conf->servers[conf->num_servers] =
server_conf_create(addr, cert_file, key_file, tc_file);
server_conf_create(net_ns, addr, cert_file, key_file, tc_file);

conf->num_servers = new_num_servers;

Expand All @@ -91,7 +91,7 @@ static struct domain_conf *custom_to_conf(const char *filename,
*end = '\0';

if (strlen(start) > 0 && !ut_str_begins_with(start, COMMENT_CHAR)
&& add_server(conf, filename, start, NULL, NULL, NULL,
&& add_server(conf, filename, NULL, start, NULL, NULL, NULL,
log_ref) < 0) {
domain_conf_destroy(conf);
return NULL;
Expand Down Expand Up @@ -179,7 +179,11 @@ static struct domain_conf *json_to_conf(const char *filename,
goto err_free_conf;
}

if (add_server(conf, filename, addr, cert_file, key_file,
const char *net_ns =
get_server_key(filename, server, "networkNamespace", false,
log_ref);

if (add_server(conf, filename, net_ns, addr, cert_file, key_file,
tc_file, log_ref) < 0)
goto err_free_conf;
}
Expand Down
36 changes: 34 additions & 2 deletions src/link.c
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,22 @@ static void try_connect(struct link *link)

ptimer_ack(link->timer, &link->reconnect_tmo);

int old_ns_fd = -1;

if (link->server->net_ns != NULL) {
UT_SAVE_ERRNO;
old_ns_fd = ut_net_ns_enter(link->server->net_ns);
UT_RESTORE_ERRNO(net_ns_errno);

if (old_ns_fd < 0) {
log_link_net_ns_enter_failed(link, link->server->net_ns,
net_ns_errno);
goto err_reconnect;
}

log_link_net_ns_entered(link, link->server->net_ns);
}

UT_SAVE_ERRNO;
#ifdef HAVE_XCM_WRITABLE_ATTRS
struct xcm_attr_map *attrs = xcm_attr_map_create();
Expand All @@ -817,10 +833,23 @@ static void try_connect(struct link *link)
#endif
UT_RESTORE_ERRNO(connect_errno);

if (old_ns_fd != -1) {
UT_SAVE_ERRNO;
int rc = ut_net_ns_return(old_ns_fd);
UT_RESTORE_ERRNO(net_ns_errno);

if (rc < 0) {
log_link_net_ns_return_failed(link, link->server->net_ns,
net_ns_errno);
goto err_reconnect;
}

log_link_net_ns_entered(link, link->server->net_ns);
}

if (link->conn == NULL) {
log_link_xcm_connect_failed(link, link->server->addr, connect_errno);
assure_reconnect_tmo(link);
return;
goto err_reconnect;
}

log_link_xcm_initiated(link, link->server->addr);
Expand All @@ -839,6 +868,9 @@ static void try_connect(struct link *link)
proto_ta_produce_request(hello_ta, link->client_id, PROTO_VERSION,
PROTO_VERSION);
queue_request(link, hello_request);

err_reconnect:
assure_reconnect_tmo(link);
}

int link_process(struct link *link)
Expand Down
18 changes: 18 additions & 0 deletions src/log_link.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@
#define log_link_destroy(link) \
log_link_debug(link, "Link destroyed.")

#define log_link_net_ns_entered(link, ns_name) \
log_link_debug(link, "Entered network namespace \"%s\".", ns_name)

#define log_link_net_ns_returned(link, ns_name) \
log_link_debug(link, "Returned from network namespace \"%s\".", ns_name)

#define log_link_net_ns_op_failed(link, op_name, ns_name, net_ns_errno) \
log_link_debug(link, "Failed to %s network namespace \"%s\"; " \
"errno %d (%s).", op_name, ns_name, net_ns_errno, \
strerror(net_ns_errno))

#define log_link_net_ns_enter_failed(link, ns_name, net_ns_errno) \
log_link_net_ns_op_failed(link, "enter", ns_name, net_ns_errno)

#define log_link_net_ns_return_failed(link, ns_name, net_ns_errno) \
log_link_net_ns_op_failed(link, "return from", ns_name, \
net_ns_errno)

#define log_link_xcm_op_failed(link, op_desc, xcm_addr, xcm_errno) \
log_link_debug(link, "XCM %s to %s " \
"failed; errno %d (%s).", op_desc, xcm_addr, xcm_errno, \
Expand Down
10 changes: 7 additions & 3 deletions src/server_conf.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ bool server_conf_equals(const struct server_conf *server0,
str_equals(server0->tc_file, server1->tc_file);
}

struct server_conf *server_conf_create(const char *addr,
struct server_conf *server_conf_create(const char *net_ns,
const char *addr,
const char *cert_file,
const char *key_file,
const char *tc_file)
{
struct server_conf *server = ut_malloc(sizeof(struct server_conf));

*server = (struct server_conf) {
.net_ns = dup_non_null(net_ns),
.addr = ut_strdup(addr),
.cert_file = dup_non_null(cert_file),
.key_file = dup_non_null(key_file),
Expand All @@ -50,6 +52,7 @@ struct server_conf *server_conf_create(const char *addr,
void server_conf_destroy(struct server_conf *server)
{
if (server != NULL) {
ut_free(server->net_ns);
ut_free(server->addr);

ut_free(server->cert_file);
Expand All @@ -62,7 +65,8 @@ void server_conf_destroy(struct server_conf *server)

struct server_conf *server_conf_clone(const struct server_conf *original)
{
return server_conf_create(original->addr, original->cert_file,
original->key_file, original->tc_file);
return server_conf_create(original->net_ns, original->addr,
original->cert_file, original->key_file,
original->tc_file);
}

4 changes: 3 additions & 1 deletion src/server_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@

struct server_conf
{
char *net_ns;
char *addr;
char *cert_file;
char *key_file;
char *tc_file;
};

struct server_conf *server_conf_create(const char *addr,
struct server_conf *server_conf_create(const char *net_ns,
const char *addr,
const char *cert_file,
const char *key_file,
const char *tc_file);
Expand Down
51 changes: 51 additions & 0 deletions src/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <limits.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -230,3 +232,52 @@ bool ut_str_ary_has(char * const *ary, size_t ary_len, const char *needle)
return false;
}

#define NETNS_NAME_DIR "/run/netns"

static int get_ns_fd(const char *ns) {
char path[strlen(NETNS_NAME_DIR)+strlen(ns)+2];
snprintf(path, sizeof(path), "%s/%s", NETNS_NAME_DIR, ns);
return open(path, O_RDONLY, 0);
}

int ut_net_ns_enter(const char *ns_name)
{
char old_ns[PATH_MAX];
/* we can't use "/proc/self/ns/net" here, because it points
towards the *process* (i.e. main thread's ns), which might not
be the current thread's ns */
snprintf(old_ns, sizeof(old_ns), "/proc/%d/ns/net", gettid());

int old_ns_fd = open(old_ns, O_RDONLY, 0);
if (old_ns_fd < 0)
goto err;

int new_ns_fd = get_ns_fd(ns_name);

if (new_ns_fd < 0)
goto err_close_old;

if (setns(new_ns_fd, CLONE_NEWNET) < 0)
goto err_close_all;

close(new_ns_fd);

return old_ns_fd;

err_close_all:
close(new_ns_fd);
err_close_old:
close(old_ns_fd);
err:
return -1;
}

int ut_net_ns_return(int old_ns_fd)
{
if (setns(old_ns_fd, CLONE_NEWNET) < 0)
return -1;

close(old_ns_fd);

return 0;
}
3 changes: 3 additions & 0 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ ssize_t ut_read_file(int fd, void* buf, size_t capacity);
bool ut_str_begins_with(const char *s, char c);
bool ut_str_ary_has(char * const *ary, size_t ary_len, const char *needle);

int ut_net_ns_enter(const char *ns_name);
int ut_net_ns_return(int old_ns_fd);

#define UT_SAVE_ERRNO \
int _oerrno = errno

Expand Down
26 changes: 17 additions & 9 deletions test/domain_conf_testcases.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,15 @@ TESTCASE(domain_conf, read_file)
CHK(conf == NULL);
CHKERRNOEQ(0);

tu_executef("chmod a-rwx %s/%s", domain_dir, domain_name);
mtime.tv_sec = time(NULL) - 1;
conf = domain_conf_read(domain_name, &mtime, NULL);
CHK(conf == NULL);
CHKERRNOEQ(EACCES);
tu_executef("chmod u+rw %s/%s", domain_dir, domain_name);
/* this test doesn't work for root, since it can read all files */
if (getuid() != 0) {
tu_executef("chmod a-rwx %s/%s", domain_dir, domain_name);
mtime.tv_sec = time(NULL) - 1;
conf = domain_conf_read(domain_name, &mtime, NULL);
CHK(conf == NULL);
CHKERRNOEQ(EACCES);
tu_executef("chmod u+rw %s/%s", domain_dir, domain_name);
}

return UTEST_SUCCESS;
}
Expand All @@ -98,6 +101,7 @@ TESTCASE(domain_conf, json_read_file)
{
const char *addr0 = "ux:foo";
const char *addr1 = "tls:127.0.0.1:4711";
const char *net_ns = "test_ns";
const char *addr2 = "tls:127.0.42.1:42";
const char *cert_file = "/asdf/cert.pem";
const char *key_file = "/asdf/key.pem";
Expand All @@ -114,7 +118,8 @@ TESTCASE(domain_conf, json_read_file)
" \"address\": \"%s\"\n"
" },\n"
" {\n"
" \"address\": \"%s\"\n"
" \"address\": \"%s\",\n"
" \"networkNamespace\": \"%s\"\n"
" },\n"
" {\n"
" \"address\": \"%s\",\n"
Expand All @@ -123,23 +128,26 @@ TESTCASE(domain_conf, json_read_file)
" \"tlsTrustedCaFile\": \"%s\"\n"
" }\n"
" ]\n"
"}\n' > %s/%s", addr0, addr1, addr2, cert_file, key_file,
tc_file, domain_dir, domain_name);
"}\n' > %s/%s", addr0, addr1, net_ns, addr2,
cert_file, key_file, tc_file, domain_dir, domain_name);

struct domain_conf *conf = domain_conf_read(domain_name, &mtime, NULL);
CHK(conf != NULL);
CHKINTEQ(conf->num_servers, 3);

CHKNULL(conf->servers[0]->net_ns);
CHKSTREQ(conf->servers[0]->addr, addr0);
CHKNULL(conf->servers[0]->cert_file);
CHKNULL(conf->servers[0]->key_file);
CHKNULL(conf->servers[0]->tc_file);

CHKSTREQ(conf->servers[1]->net_ns, net_ns);
CHKSTREQ(conf->servers[1]->addr, addr1);
CHKNULL(conf->servers[1]->cert_file);
CHKNULL(conf->servers[1]->key_file);
CHKNULL(conf->servers[1]->tc_file);

CHKNULL(conf->servers[0]->net_ns);
CHKSTREQ(conf->servers[2]->addr, addr2);
CHKSTREQ(conf->servers[2]->cert_file, cert_file);
CHKSTREQ(conf->servers[2]->key_file, key_file);
Expand Down
Loading

0 comments on commit 1faf230

Please sign in to comment.